Thursday, February 19, 2009

Development and Customization tips for CRM 4.0

If you are working with Microsoft Dynamics CRM 4.0 and your planning to do code customizations then you probably should keep on reading. And why am I talking about code customizations and not just customizations? Well just because in CRM you can do a lot without a single line of code... and this time I want especially to talk about the code customizations.
I have few topics on my mind that aren't related in anyway but I'm going to sum up them into this post:
1) Custom ASPX pages => Weird VirtualPathProvider error
2) RetrieveMultipleRequest and DynamicEntities
3) Microsoft.Crm.Sdk.Query.ConditionOperator vs. ConditionOperator through Web Service
4) Using your own Web.config to store settings
Okay here we go!
Custom ASPX Pages => Weird VirtualPathProvider error
First of all... If you want to create your own custom ASPX pages, then THE location for the files is the ISV folder (any other place would be unsupported):
I personally prefer adding my own ASPX pages to another folder (i.e. C:\MyApp\Pages) and just create virtual folder under IIS ISV-folder. And all your DLLs should go to GAC. I used the _should_ word just because during development time you might want to copy your DLLs to bin folder directly. It makes life a lot easier (and faster!) because you can just drop them over and you don't have to do any IISRESET in order to test your chances. BUT at the production that is definitely no-go approach.
So you have created you nice Page.aspx and everything should be fine... but you get pretty interesting exception:

The VirtualPathProvider returned a VirtualFile object with VirtualPath set to '/CRM/ISV/Page.aspx' instead of the expected '//CRM/ISV/Page.aspx'.
You most likely start blaming CRM for that error... but your wrong :-) This exception happens if you have compilation errors in your file. That can happen easily if you have i.e. typo inside your .aspx file. But that error message doesn't help you much when your trying to sort it out. So open up your Page.aspx and start fixing the error using regular debugging methods (like narrowing down the code to get the location that causes the error).

RetrieveMultipleRequest and DynamicEntities
If you have added some new entities and you used following code to retrieve them:
using Microsoft.Crm.Sdk;
using Microsoft.Crm.SdkTypeProxy;
// ...
Microsoft.Crm.SdkTypeProxy.CrmService service =
new Microsoft.Crm.SdkTypeProxy.CrmService();
service.CrmAuthenticationTokenValue = token;
service.Credentials = System.Net.CredentialCache.DefaultCredentials;
service.UnsafeAuthenticatedConnectionSharing = true;
Microsoft.Crm.Sdk.Query.QueryExpression queryExpression =
new Microsoft.Crm.Sdk.Query.QueryExpression();
queryExpression.EntityName = "mcs_mycustomentity";
queryExpression.ColumnSet = new Microsoft.Crm.Sdk.Query.AllColumns();
Microsoft.Crm.SdkTypeProxy.RetrieveMultipleRequest request =
new Microsoft.Crm.SdkTypeProxy.RetrieveMultipleRequest();
request.Query = queryExpression;
You'll probably hit this on the line 19:
And it's pretty obvious that the error message...
Exception: System.InvalidOperationException: There is an error in XML document (1, 503). ---> *System.InvalidOperationException: The specified type was not recognized: name='mcs_mycustomentity'
...doesn't help you that much :-) Okay now we need to start debugging and sort this thing out. First I'll take use try Fiddler and see what happens:
And if you look carefully enough you see something interesting: ReturnDynamicEntities=false (marked as red). And of course that rings a bell... your query is trying to return entity that would have been returned as dynamic entity. And if that's not allowed then you get your weird InvalidOperationException. Luckily that's easy to fix... just add this one line of code:
request.ReturnDynamicEntities = true;
Put that line of code before Execute call and you'll be fine.
Another way would be using directly the Web Reference to retrieve the CrmService. Then you could use strongly typed classes to implement your queries and you wouldn't have to work with DynamicEntities. If you can choose this option then I strongly recommend it. But if you need to create code that works with multiple tenants and you need to use entities that doesn't exist in all tenants... then you need to use DynamicEntities.

Microsoft.Crm.Sdk.Query.ConditionOperator vs. ConditionOperator through Web Service
If you use Microsoft.Crm.Sdk.Query.ConditionOperator you have 59 operators (I calculated them by hand so let say ~59 :-) and if you use ConditionOperator through the web service you have only 48 operators. And if you use one that doesn't exist in both i.e. Microsoft.Crm.Sdk.Query.ConditionOperator.EndsWith in your queries you'll get this InvalidOperationException:
Like the error message says "InvalidOperationException: Instance validation error: '56' is not a valid value for Microsoft.Crm.Sdk.Query.ConditionOperator" you can't use those values since they haven't been implemented in the other one. I just wanted to give this hint that you don't scratch your head too much if you get this error :-) And of course you can achieve the same functionality using the other operators so this is NOT limiting factor. For example query for word "searchword" with operator ConditionOperator.EndsWith is equal to ConditionOperator.Like with "%" + "searchword" :-)
Using your own Web.config to store settings
You probably know that it's strickly forbidden to touch web.config of CRM installation (file that exists in CRMWeb folder). So if you need to store some custom stuff in your own web.config you may think that it's piece of cake. So let's say that you put your own web.config into "/ISV/MyApp/"-folder. If you then use your CRM with the default organization (your url is http://localhost/loader.aspx and not http://localhost/MyOrg/loader.aspx) you can access your web.config appSettings as you would in any ASP.NET application. But if you use it through "the long" url where your organizations name is at the url too... then you probably notice that you can't access the web.config as you would expect. You'll only get empty parameters from your web.config even if your sure that everything is fine. Reason for that is actually pretty simple: VirtualPathProvider. Since CRM 4.0 now supports multiple tenants you don't actually access your tenants from "physical" urls anymore. They are now virtualized so that you can have any number of tenants at your system. But this makes your life a bit harder since you need to "load" your own web.confg in your code. It's not difficult but you just need to know what to do. Here's is small example:
using System.Configuration;
using System.Web.Configuration;
// ...
Configuration configuration = WebConfigurationManager.OpenWebConfiguration("/ISV/MyApp");
string myParameter = configuration.AppSettings.Settings["MyParameter"].Value;
In line 5 you just specify virtual path of your web.config. After that you can use settings normally

1 comment:

  1. You saved my day with that "ReturnDynamicEntities = true"

    Thanks a lot!