Multi-language JavaScript messages in Dynamics 365 9.0 using RESX files

One of the not-so-shiny-and-mentioned-on-twitter-and-blogs feature of Dynamics 365 9.0 is simplification of localization of Web Resources. If you have ever worked on a international Dynamics CRM project, which required multiple Language Packs and not all users were using the same language, you probably had some difficulties when trying to show some message on the form using JavaScript or throw an exception with a message from plugin. There was no out-of-the-box localization strategy – some common dictionary with values for multiple languages from which we could easily get the value in current system user language. Of course there are a lot of possibilities here, all of which include usage of some kind of Web Resource like XML or JSON just to store the proper value in all required languages.

Dynamics 365 9.0 introduces new Web Resource type – RESX. This is the very well known, plain old resources file, which we have always used, long before JavaScript has become the leading technology in web development. It was (and still is) super easy to apply multiple languages on pages for our application using this kind of files. There is also a great Visual Studio addon – ResXManager which made it even easier to handle all the translations (so I strongly recommend to use it, if you have never heard of it). You can use this RESX types quite easily in your scripts to display messages in different languages based on current user preferred language.

How to achieve that? Firstly, you should prepare your RESX files in Visual Studio. As an example, I prepared 3 files – for English, Polish and German language:

1

Each file contains one key:

2

Now, let’s import the files into CRM system. The most important part here is that name of the Web Resource should be built using the following convention:

NAME.LCID.resx

So for example:

messages.1045.resx

for Polish messages.

There are some other possibilities here, I will write more about that in a second. You must remember to choose proper “Language” value when creating web resource. So you should end up with something like this:

3

Now we are ready to use them in our scripts. We should first register all the required resources as dependencies for script in which we want to use them, so if I want to use them in my lead.js I should go to the new tab “Dependencies” in properties of my lead.js Web Resource:

4

If you did not hear about this new functionality of D365 9.0 – dependencies allow you to register dependent libraries for your script, so if it uses jQuery, react.js and your custom common.js, you can simply register them as dependencies and don’t have to register all this scripts always together with lead.js (this is also working in ribbons, so no more isNaN dummy calls!)

After all that we can simply call Xrm.Utility.getResourceString function to get the proper token:

 

And we can happily see our translated message, when we change to Polish language:

5

We can switch to English and we will see:

6

Really easy, isn’t it?

How this getResourceString function works? Well it simply looks for the proper Web Resource based on the current user preferred language, but it must have the proper name. If, for example, we call Xrm.Utility.getResourceString(“messages”, “myKey”) and our current language is Polish (and our Organization base language is English), it will be looking for the following files:

messages.resx
messages.1045.resx
messages
messages.1045
messages.1033

Then it will choose whichever first of these will have matching Language chosen in WebResource properties. That’s why I suggested to use NAME.LCID.resx – although there are some other possible ways of naming, this one looks the most clear and consistent for me.

Query Builder Error when exporting solution in Dynamics 365

Solutions in Dynamics 365 are very useful and there is no doubt about that. One of the latest features is possibility to add single fields, views or forms to the solution, not the whole entity with all its dependencies. That allows us to better control what is transferred and deployed on out staging and production environments. Few times I came across the problem, which is caused clearly by this granularity of solutions:

error

Query Builder Error

The specified field does not exist in Microsoft Dynamics 365

Fortunately this error was in on-premise environment, so I was able to check the tracing logs and the problem has become clear:

“attribute with id=’86e616c0-64fc-413c-9957-470272c3f198′ does not exist”

So, apparently, someone has removed some attribute from the system, but somehow it was orphaned in our solution and is causing the export to fail (no doubt – we are trying to export the solution, so system is getting all the metadata, for all the Solution Components and cannot find one of the component). This is very unfortunate, because we cannot see this missing object in the solution view, it’s only in database in SolutionComponentBase table.  In on-premise environment, it’s not a problem – we simply check the id in traces and then go to our database and do:

SELECT FROM SolutionComponentBase  where ObjectId = ‘guid of missing component’

This will get us the failing components from all our solutions. We can remove them on DB level (sic!) but if you are afraid that this is not supported (which it is, but really cannot break anything) you can do it using SDK.

But what if we have online instance without access to the database? We can request Microsoft Support for the traces and then we will know which attribute is failing. But if we are in a hurry, we would have to loop through all Solution Components is our solution, check if it exists in the system and remove it from the solution. I believe that in 99% of cases, the missing component would be an attribute, so here is the code that would help you to get rid of this error:

It simply iterates through all attributes in the solution and tries to retrieve metadata for each one of them. If it fails, it simply removes this component from the solution. That should be enough for most of the cases, but if there will be any other component types missing, you will have to modify the code to check for those missing components (which I described in one of my previous posts). I have prepared a very simple program, that iterates through all of the solution components and tries to retrieve every single type – and if it fails it just removes it from the solution. Feel free to use it for any means necessary:

https://github.com/pawelgradecki/SolutionExportFixer

Disable columns in editable grid

Editable grids are a long awaited feature of Dynamics 365. Without any fancy coding, they allow to provide users a nice and easy to use interface, for updating multiple records in an Excel-like experience. We can easily create a subgrid on the form, configure it to use an Editable grid as control and we are good to go, we can edit all the grid columns.

Przechwytywanie

But what if we would like to edit only some of the columns of the grid, not all of them? This is not configurable, either we have an editable grid with all the columns editable, or we don’t have editable grid at all. The only way of handling this with configuration is enabling Field Security Profile – if user will not have privilege to update the field, it will appear as disabled for him. Fortunately there are quite a few client SDK features, that will allow us to achieve our goal with a little bit of JavaScript. First, we should register a function for OnRecordSelect event on our grid:

Przechwytywanie

And my gridRowSelected function looks like that:

This function basically gets all attributes for the selected row and for specified attribute (in my case it was “odx_monthid”) disables the grid cell. Effects are the following:

Przechwytywanie

Great, so only Month column is currently not editable, but we can still edit Amount column. The only drawback of this solution is that this “lock” on the field is shown only when you click on the row (because we registered the function to be fired on OnRowSelect event). Couldn’t it be fired for OnLoad of the form? Unfortunately, according to MSDN documentation:

we cannot run this, when form loads, because the internals of the grid are not yet loaded. Still it is quite an acceptable workaround. Maybe in future releases, editable grid will receive some more features like configuration of disabled columns.

Using early bounds in plugin for multiple entities

Recently I was caught up in the discussion about early bound entities and late bound entities. One of the argument of the “late bound supporters” was the fact, that using late-bound allows reusing the same plugin for multiple entities (so for example create a plugin that runs for all activities or a common plugin for leads/opportunities/accounts). This argument is not valid, I will show you how to write a clean code, that uses modern approach for coding and is perfectly testable.

Let’s start with the late bound version of the plugin to give you an idea what are we trying to achieve here:

The plugin simply adds values of “new_netamount” and “new_margin” fields on the entity and sets field “new_totalsum” to a resulting value. Of course this can be simply achieved by using calculated field, but I just want to show the basic idea here, real world scenario is usually much more complicated.

If we register this field on pre-update of any entity containing field “new_totalsum”, “new_netamount” and “new_margin” it would basically do its job. If you are not an experienced CRM Developer, who remembers CRM 3.0 or 4.0, just an ordinary .NET Developer, you would probably say, that this code looks ugly. It’s almost like it was taken from some JavaScript library – where are all the types, what is the type of “new_totalsum”, which year is that, are we still in 2017? And yes, you’ll be right – this code simply looks awful, does not allow to use any of great refactoring tools, because it uses some “magic” dictionaries and keys and without access to CRM you would not even have a clue, what are the proper types of the properties. It’s exactly the same story as with ORM – who of us still uses plain ADO .NET and not, for example, Entity Framework? Such approach is much less error prone, testable and also much more friendly for CRM newbies. Code using early bounds may look like that:

Now it’s much better, but there is a significant problem with this code. We assumed that our plugin runs on Account entity. What if we will register it on Contact entity? Well, it will simply crash when we will try to convert our target!

So the basic approach would be to create a switch statement and simply check the entity type:

Ok so now we can register the plugin on multiple entities, but our code looks even worse than the first version, because we have a lot of code that is copy-pasted. Imagine that the operation would be much more complex and would change frequently in time – we would have to change all the switch statements every time.

To overcome this problem we will take advantage of the fact that all early-bound classes are partial, so they can implement any interface we want. Let’s create such interface and some helper partial classes:

It would be great if we could use this like that:

But unfortunately, our “Target” is an entity and we have to somehow convert it to an early bound. I developed a simple utility class that is capable of that:

Simple explanation of the code – during first execution of the plugin, all the early-bound types are cached in a dictionary, that can be later fast accessed from the code (in this case it’s InterfaceMocker). It simply instantiates the early-bound and copies all attributes inside. There is also an extension method that simplifies the usage for Entity. Now our plugin can simply look like that:

And that’s it! This code looks much better then late bound version, provides type-checking for entity attributes and also is very nice when we implement unit tests, as we have an interface to test, not a full early-bound. If our logic is more complex we can create some additional classes that will be dependent upon IEntityWithTotalSum interface, not Account or Opportunity entity.