Comment unsuccessful. Please correct the errors below.

Synchronous plugins want localhost

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... [more]

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.

Update

Hotfix KB950542 resolves the issue.

Posted by: George Doubinski
Last revised: 05 Dec, 2012 05:25 PM

Comments

Christina
Christina
26 Feb, 2008 07:07 AM

I also encounter this issue, surf the internet and there is a solution provided by other people to rewrite in below in order to obtain the web service.

            //ICrmService service = (ICrmService)context.CreateCrmService(true);

            CrmAuthenticationToken token = new CrmAuthenticationToken();
            token.AuthenticationType = AuthenticationType.AD;
            token.CallerId = context.UserId;

            // Remove any blank spaces in the organization's name.
            token.OrganizationName = context.OrganizationName.Replace(&quot; &quot;, &quot;&quot;);

            // Instantiate the CrmService Web service. 
            CrmService service = new CrmService();
            service.UseDefaultCredentials = true;
            service.CrmAuthenticationTokenValue = token;

            // Obtain the service Url from the registry.
            service.Url = (string)(Registry.LocalMachine.OpenSubKey(&quot;Software\\Microsoft\\MSCRM&quot;).GetValue(&quot;ServerUrl&quot;));
            service.Url += &quot;/2007/crmservice.asmx&quot;;
26 Feb, 2008 11:59 AM

@Christina,

I understand that I can acquire CrmService pointer "manually". Firstly, I wanted to re-use one-liner that was given by SDK instead of typing my own lines dealing with tokens etc but, it seems that implementation is indeed buggy. Secondly, the code would have to be adjusted a bit - as it stands it won't work offline (that's another thing that CreateCrmService call is supposed to take care of. The other important aspect of CreateCrmService is flexibility in impersonation options - while possible to do it all in your own code, the task suddenly becomes quite complicated.

George

07 Mar, 2008 06:27 PM

I had the same problem, for me the solution was to generate the publictokenkey .snk for the plugin project and it start to work in synchronous mode and i created the account like you do. .

This is how to bind a PublicKeyToken to an assembly (essentially making it a strongly named assembly, suitable for installing in the GAC.

sn.exe file is located in "C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin"

(1). sn.exe -k PublicPrivateKeyFile.snk This will create a public-private key pair and put it in the file (.snk extension)

(2). Take the above file and put it in the folder where your source files are stored for that assembly (to be strongly named).

(3). Then open the solution for that assmbly and go to the project for that assembly and open its properties (select the project, right click and select "Properties").

(4). In that Properties page, at the bottom-left you can see "Signing" tab, select it. Then CHECK the box saying "Sign Assembly" and UNCHECK the box saying "Delay sign only" (if you want to be able to debug through the assembly).

(5). Save it and compile the whole solution. Voila..., You got your assembly, this time strongly named. You can verify the "-T" option of SN.EXE to obtain the public key, you should get it this time.

08 Mar, 2008 01:59 AM

@Christina,

forgot to mention that context.CreateCrmService has one important advantage over creating your own CrmService: it prevents recursion in plug-ins by maintaining the invocation depth. Another feature is linking to a parent context when your plug-in is executed in a child pipeline (e.g. creating invoicedetail records when invoice is created).

@Szczepan,

I wish signing assembly solved the issue. Firstly, plug-in assemblies [b]must[/b] be signed otherwise they won't load. Secondly, signing assembly has nothing to do with the problem that I described, namely, getting incorrect endpoint for web services in synchronous plug-ins. It's been confirmed as a bug and the fix will be coming at some point in the future.

Your Comments

Comment unsuccessful. Please correct the errors below.
Used for your gravatar. Not required. Will not be public.
Posting code? Indent it by four spaces to make it look nice. Learn more about Markdown.

Preview