Thursday, February 24, 2011

Plugin Registration Error: A proxy type with the name account has been defined by another assembly

So I ran into a very tricky error when building a custom tool to register CRM 2011 Plug-ins.  I wouldn't be surprised if other people run into the same issue when registering plug-ins programmatically. And its such a strange problem I thought it deserved an explanation.

Here is the exception I got when retrieving a list of records from CRM:
A proxy type with the name account has been defined by another assembly. Current type: Plugins.Sdk.Account, Plugins, Version=, Culture=neutral, PublicKeyToken=0b5679ac811dc738, Existing type: CrmSdk.Account, PluginRegisterTool, Version=, Culture=neutral, PublicKeyToken=null
Parameter name: account
In this case I was retrieving a list of PluginAssembly records, but the same exception happens when I try to retrieve ANY kind of entity.  Also note that "Plugins.Sdk.Account, Plugins" is a type in the plug-in assembly that I'm trying to register, and "CrmSdk.Account, PluginRegistrationTool" is the type in the application that's actually doing the registration.

A look at the stack trace also gives us a hint about what's really happening here:
Server stack trace:
   at Microsoft.Xrm.Sdk.AppDomainBasedKnownProxyTypesProvider.AddTypeMapping(Assembly assembly, Type type, String proxyName)
   at Microsoft.Xrm.Sdk.KnownProxyTypesProvider.LoadKnownTypes(Assembly assembly)
   at Microsoft.Xrm.Sdk.KnownProxyTypesProvider.RegisterAssembly(Assembly assembly)
   at Microsoft.Xrm.Sdk.KnownProxyTypesProvider.InitializeLoadedAssemblies()
   at Microsoft.Xrm.Sdk.AppDomainBasedKnownProxyTypesProvider..ctor()

So what's really happening here?

In order to register a plug-in I need to know certain information about the plug-in assembly, such as the version number, culture, and public key token. To get this info out of the assembly I use reflection:

PluginAssembly assembly = new PluginAssembly();
Assembly dll = Assembly.LoadFile(path);  

string[] assemblyProp = dll.GetName().FullName.Split(",= ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
assembly.Culture = assemblyProp[ASSEMBLY_PROP_CULTURE];
assembly.Name = assemblyProp[ASSEMBLY_PROP_NAME];
assembly.PublicKeyToken = assemblyProp[ASSEMBLY_PROP_PUBLICKEYTOKEN];
assembly.Version = assemblyProp[ASSEMBLY_PROP_VERSION];

However, the Microsoft.Xrm.Sdk DLL is also doing its own reflection magic! 

As soon as plug-in assembly is loaded into memory by Assembly.LoadFile(),  the Microsoft.Xrm.Sdk.KnownProxyTypesProvider will scan and cache all of the entity types in the assembly into its own internal collection.  The SDK assembly actually uses this information for converting the generic "Entity" records it gets back from CRM into strongly typed classes (e.g., account, contact, systemuser).

Unfortunately the KnownProxyTypesProvider can only hold one "account" entity, and it doesn't differentiate between two "account" classes from different assemblies  (I'm not really sure if this should be considered a bug or not).   And so I get an exception because I've filled up the KnownProxyTypesProvider with all the types from the plug-in assembly and not the entity types from the currently running application.

To get around this issue you need to "prime" the KnownProxyTypesProvider with the correct entity types.  All you have to do is make sure you retrieve a record from CRM before you load the plug-in assembly:

QueryExpression query = new QueryExpression(SystemUser.EntityLogicalName);
query.PageInfo = new PagingInfo() { Count = 1, PageNumber = 1 };
This is a completely useless query, but the simple act of retrieving a single SystemUser prevents the KnownProxyTypesProvider from going loading the wrong types from the plug-in assembly.