Showing posts with label SOAP. Show all posts
Showing posts with label SOAP. Show all posts

Tuesday, March 1, 2011

Debugging Silverlight web-resources that connect to CRM Online.

Debugging Silverlight web-resources in CRM 2011 can be pretty tedious when you have to constantly re-upload your XAP file to the CRM server and then manually attach the debugger to the IE process. If you run the Silverlight application from your own development machine (using the ASP.NET Development server) you can quickly start debugging by hitting F5 in Visual Studio, but the app won't be able to make any requests to the CRM server because of Silverlight's cross-domain restrictions.

If you have an on-premise CRM server you can get around the cross-domain restrictions by putting a ClientAccessPolicy.xml file in the root of the CRM web application (See Markus Konrad's post on creating the policy file), but CRM online won't let you upload a ClientAccessPolicy, so you have to be a little more clever.  One method is to just create a proxy to pass all the requests to the CRM server.

Creating a proxy for CRM SOAP requests
A quick note: This works for the SOAP Organization service and the example at the end of this post only uses the SOAP service. I'm not sure if a similar proxy would work for the REST endpoint  (Given the REST endpoint's limitation I rarely use it anyway).

To get web-service requests to go from a Silverlight application w just need a few things:
1. Create an aspx page in the web project that's hosting the Silverlight app that will act as our proxy.
2. Program the proxy page to forward all posted requests to the CRM Online web-service endpoint.
3. Authenticate against CRM Online and attach the LiveId security token to each request.
4. Configure the Silverlight app to point to the proxy page.


Steps 1 and 2 are actually pretty straightforward once you pull out fiddler and figure out what the soap requests work like. Step 3 is the complicated bit but the sample code in the CRM SDK provides an example of authenticating against CRM Online (see sdk\samplecode\cs\wsdlbasedproxies\online). Once you've authenticated and have the security token you can just inject the token right into the header of the SOAP request.

I've built an example application which you can download here.  It's just a simple Silverlight application that makes create, retrieve, update, delete, and execute request to CRM Online using the proxy described above.





















To try it out just update the constants defined at the top of Proxy.aspx.cs:
//Update these constants to match your CRM Online account
const string host = "orgName.api.crm.dynamics.com";
const string userName = "erik.pool@example.com";
const string password = "Password";

Also note the GetOrganizationService method in the Silverlight app. If the current page is running on "localhost" it assumes you're debugging and points the org service url at the proxy.aspx page.  This way the same silverlight app works on my dev machine and when it's deployed to CRM Online.

if (href.Contains("localhost"))
{
   Uri baseUrl = new Uri(href, UriKind.Absolute);
   orgServiceUrl = new Uri(baseUrl, "SoapProxy/Proxy.aspx");
}
else
{
   orgServiceUrl = new Uri(location.GetProperty("protocol") + "//" + location.GetProperty("host") + "/xrmservices/2011/organization.svc/web");
}

Download the source code here.

-Erik

Tuesday, December 28, 2010

How to use the CRM 2011 Organization (SOAP) Service from Silverlight

Now that the CRM 2011 RC1 is out it’s finally possible to use the SOAP web service from Silverlight applications that are deployed as web-resources to the CRM server.  The SOAP service offers a number of advantages over the other REST service, such as the ability to retrieve CRM metadata, execute fetchxml queries, and the ability to work with dynamic entity objects instead of strongly typed classes.   

Unfortunately the SDK still doesn’t provide much information, and a number of people have been asking in the beta forums about how to get this working.  The page titled “Use the SOAP endpoint for Silverlight clients” currently just says “Content coming soon”.  On top of that there are still a couple of serialization bugs that prevent certain SDK messages from working correctly when sent from a Silverlight client.  But we can get around these bugs with a couple tricks.  Here’s how you do it:

  1. Add the Organization service as a Service Reference to your Silverlight project

    First you need to add the service reference. You can do this the same way you would add a reference for the REST service.  In CRM, go to Settings -> Customizations -> Developer Resources, and download the WSDL file for the organization service. Then in your Silverlight project click Add Service Reference, and enter the path to the wsdl file you downloaded.  


  2. Fixing the service reference classes

    Now that we have the service reference, we need to make some modifications to it to get around those serialization bugs.  Without these changes some of the SDK messages (such as retrieving metadata) will fail.  Microsoft has already indicated that they are working on these issues, so hopefully these changes are just be a temporary workaround until it gets fixed for real.
    The main problem is that the Reference.cs file generated by Visual Studio doesn’t serialize the requests in the same format that CRM is expecting, but we can fix this by making some modifications to Reference.cs.  Find the Reference.cs file that was generated by Visual Studio when you added the service reference and open it in Visual Studio. 

    The first issue is that the reference doesn’t create serializers for certain classes. When trying to retrieve metadata you will see a "Type 'EntityFilters' with data contract name 'EntityFilters:http://schemas.microsoft.com/xrm/2011/Metadata' is not expected" error.  You can fix this by adding a knownTypeAttribute to the OrganizationRequest class, like this:

    [System.Runtime.Serialization.KnownTypeAttribute(typeof(EntityFilters))]
    public
    partial class OrganizationRequest : object, System.ComponentModel.INotifyPropertyChanged {

    You will also need to add the following KnownTypeAttribute to the OrganizationResponse class:


    [System.Runtime.Serialization.KnownTypeAttribute(typeof(EntityMetadata))]
    public
    partial class OrganizationResponse : object, System.ComponentModel.INotifyPropertyChanged {

    Note: These are the only two KnownTypeAttributes I had to add, but I didn’t test every possibly type of CRM SDK message. It’s possible that other SDK message will require adding more KnownTypeAttributes to the reference.cs file.  Fortunately the error message you will get is pretty descriptive and will tell you which type you need to add the KnownTypeAttribute for. 


    UPDATE:  you will also need to add the following KnownTypeAttributes to the Entity class:
    [System.Runtime.Serialization.KnownTypeAttribute(typeof(OptionSetValue))]
    [System.Runtime.Serialization.KnownTypeAttribute(typeof(EntityReference))]
    public partial class Entity : object, System.ComponentModel.INotifyPropertyChanged




    The second issue is the way that it serializes the KeyValuePair class.  For some reason it serializes them as "Key" and "Value" (with capitalization), while CRM will only accept "key" and "value" (all lowercase).  To get around this you can create your own KeyValuePair class and replace all references to System.Collections.Generic.KeyValuePair in Reference.cs with your own class.  So first create the following class (I just put this class in the same namespace as the rest of the classes in Reference.cs):


    [DataContract(Name = "KeyValuePairOfstringanyType", Namespace = "http://schemas.datacontract.org/2004/07/System.Collections.Generic")]
    public
    class KeyValuePair<TKey, TValue>
    {

        public KeyValuePair(TKey key, TValue value)

        {

            this._key = key;

            this._value = value;

        }


        private TKey _key;

        private TValue _value;


        [DataMember(Name = "key")]

        public TKey Key

        {

            get { return _key; }

            set { _key = value; }

        }
        [DataMember(Name = "value")]
        public TValue Value

        {

            get { return _value; }

            set { _value = value; }

        }

    }


    Then do a find-and-replace in the Reference.cs file and replace "System.Collections.Generic.KeyValuePair" with "[yournamespace].KeyValuePair".   The new KeyValuePair class will actually serialize the way CRM wants it to, and you will be able to execute requests. 


    UPDATE:  Don't replace every instance of KeyValuePair in the reference.cs file.  You actually do not want to replace any of the KeyValuePairs that appear inside KnownTypeAttribute attributes.


  3. Use the service reference

    To use the service you need to have the correct binding.  The following code will set up the binding correctly for an on-premise installation:


    String url = "http://myServer/myOrganization/xrmservices/2011/organization.svc/web";
    BasicHttpBinding
    binding = new BasicHttpBinding(BasicHttpSecurityMode.None);
    binding.MaxReceivedMessageSize = 2147483647;

    OrganizationServiceClient
    client = new OrganizationServiceClient(binding, new EndpointAddress(url));

    There are three interesting thing in here. First, notice is that the url has “/web” on the end of it. This is required for the organization service to work from Silverlight.  Second, notice that the binding is using the “None” security mode, because the URL is using the HTTP scheme.  If you need to connect over HTTPS then you will need to use BasicHttpSecurityMode.Transport instead of BasicHttpSecurityMode.None.   CRM Online always uses HTTPs so it will always require the Transport security mode. 


    Also, if you’re using CRM Online you will need to make sure the URL has the exact same domain as the page hosting the Silverlight application.  The CRM documentation claims you should connect to [organization].api.crm.dynamic.com, but this won’t work because the Silverlight application you deploy as a web resource will not be on that domain.  The domain used for web-resources won’t have the “.api” in it, so your web-service URL should be the same.
     
  4. Executing service requests

    Making requests to the organization service from Silverlight is almost the same as from C# code.  The main difference is that you do not have the strongly typed entity classes that are generated by CrmSvcUtil.exe, and you do not have any of the strongly typed message classes found in Microsoft.Xrm.Sdk.dll.  For Create, Update, and Retrieve messages you will have to use the late-bound Entity class, and set attributes using the entity[“propertyname”] = value syntax.


    For Execute messages, you will have to use the dynamic OrganizationRequest class, and add all the parameters required for each specific message.  For example, the following will retrieve metadata for the account entity:



    private void button1_Click(object sender, RoutedEventArgs e)
    {
        OrganizationServiceClient client = GetOrganizationService();
               
        OrganizationRequest req = new OrganizationRequest();
        req.RequestName = "RetrieveEntity";
        req.Parameters = new ParameterCollection();
        req.Parameters.Add(new KeyValuePair<string, object>("LogicalName", "ae_tasker"));
        req.Parameters.Add(new KeyValuePair<string, object>("EntityFilters", EntityFilters.Attributes));
        req.Parameters.Add(new KeyValuePair<string, object>("MetadataId", new Guid("00000000-0000-0000-0000-000000000000")));
        req.Parameters.Add(new KeyValuePair<string, object>("RetrieveAsIfPublished", false));

        client.ExecuteCompleted += new EventHandler<ExecuteCompletedEventArgs>(client_ExecuteCompleted);
        client.ExecuteAsync(req);
    }

    void client_ExecuteCompleted(object sender, ExecuteCompletedEventArgs e)
    {
        EntityMetadata data = (EntityMetadata)e.Result.Results[0].Value;
        string s = "attributes: ";
        for (int i = 0; i < 10; i++)
        {
            s += data.Attributes[i].LogicalName + ", ";
        }
        MessageBox.Show(s);
    }


    Update: Added an example silveright application making SOAP requests to CRM (including source code and all the changes made to the reference.cs file).  You can download it here.