Wednesday, July 16, 2008

Plug-ins Error out when CRM is not on default website

I was recently coding a plug-in and every time I ran it it gave me errors, 401 or 404. I then backed my code out to the basics and every time I tried to call the crm service it failed. I went as far as to reading the SDK and Documentation. Then copying code from there. I still received errors. This was very frustrating; however, I came upon the attached blog that solved my issue. First there IS A BUG WITH CRM (go figure) that will return localhost instead of your crm server website. NICE!!! So basically if your CRM website is not resolvable to 127.0.0.1 then plug-in FAIL. Binding 127.0.0.1 to your CRM website will fix this for now.

George wrote this Article


Yesterday I was working on some plug-in code which was working fine earlier. I changed pipeline to synchronous and started to get consistent "404 - Not Found" error from calls to CrmService. I reduced the problem to something like this:

public class MyPlugin: IPlugin
{
    public void Execute(IPluginExecutionContext context)
    {
        ICrmService service = context.CreateCrmService(true);
        account a = new account();
        a.name = "test";
        service.Create(a);      // this line fails with 404
    }
}

And it just was not working. We all know about very "informative" SoapException which would have been fine but I was getting simple http-level 404. Something was pointing somewhere it shouldn't have...

Stepping through with the debugger I managed to find out that, if plugin is registered for synchronous execution, call to context.CreateCrmService seems to return a service wrapper that ALWAYS points to http://localhost/MSCrmServices/2007/CrmService.asmx regardless whether CRM is handling requests to http://localhost or not. In my environment CRM was installed outside of the default web site and, as such, did not handle requests sent to localhost. Hence consistent 404 error.

I managed to "reflectorise" the problem down to the class Microsoft.Crm.Extensibility.CrmServiceFactory in the assembly Microsoft.Crm.ObjectModel. In this class GetServerUrl() method contains something like this:

    string deploymentSetting;     // protocol, i.e http or https

    int nullable? = null;      // this is actually local port for crm, e.g. 5555 on the server or 2525 offline

<snip >some cassini logic and getting localsdkport setting from registry</snip>

    string str2 = "localhost";   // here you have it
    if (nullable.HasValue)
    {
        str2 = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", new object[] { str2, nullable });
    }
    return string.Format(CultureInfo.InvariantCulture, "{0}://{1}/MSCRMServices", new object[] { deploymentSetting, str2 });  // this is always a localhost?!

So, basically it always returns something like http://localhost[:optionalport]/MSCRMServices. Which is fine if you are in offline mode or if you have your http://localhost pointing to your CRM web site which is not the case in my development environment.

Plug-ins registered for asynchronous execution are getting CrmService wrapper from a different proxy factory located in assembly CrmAsyncService.exe which seem to honour all configuration settings.

Workaround

I could not think of anything better but to add localhost to the list of headers handled by CRM web site. Simple yet effective - the problem's gone. This workaround is fine in development environment but not in a deployment where for one reason or another localhost must point elsewhere.

I am curious to hear from fellow developers if anybody has come across of this problem as well as from anyone from MSFT if this behaviour is by design or it's a bug.

No comments: