Playing with Microsoft CRM programming models

I’ve spent the last few days playing with a new programming model on top of the CRM platform. This work was part prototype, part investigation, and part complaint. I wanted to see what might be possible if I took a completely radical approach to the API. Some of you might remember an earlier article I wrote (which isn’t linked here because it’s been wiped out) about the CRM programming model in which I mentioned that I didn’t like the v1.x API set and that I wanted to see something different for V2. Well, we’re still working on a V2 model and we’re still trying to get our heads around what might make sense. In the meantime though I decided to see what was possible using V1.x.

The motivation behind this is simple: I don’t like the mess we got ourselves in to on the V1 product. There’s a lot of history about why we have what we have and I won’t try to defend it. Instead, I’m going to present a sample web service along with the build steps. This work is based on a few of the previous articles that I’ve written. The service is simple, completely type-safe, supports ‘pre’ and ‘post’ method events (because you own the code), and can be extended as necessary.

The interface looks like so... the first thing you should notice is that this is the complete CRM web service; I’ve removed the per-entity endpoints because I think they just clutter up the story.

[WebService(Namespace="https://www.microsoft.com/mbs/crm/services/2005")]

[SoapDocumentService(SoapBindingUse.Literal, SoapParameterStyle.Bare)]

public class Services : WebService

{

    [WebMethod()]

    [return: XmlElement("entityId", typeof(Guid))]

    public Guid Save(businessEntity theEntity)

    {

    }

    [WebMethod()]

    public void Delete(businessEntity theEntity)

  {

    }

    [WebMethod()]

    [return: XmlElement("businessEntity", Namespace=EntityNamespace)]

    public businessEntity Get(string entityType, Guid id)

    {

    }

    [WebMethod()]

    public void SetAccess(businessEntity theEntity, securityAccessType accessType)

    {

    }

    [WebMethod()]

    [return: XmlArray("securityAccessTypes", Namespace=EntityNamespace)]

    public securityAccessType[] GetAccess(businessEntity theEntity)

    {

    }

    [WebMethod()]

    [return: XmlElement("results", Namespace=EntityNamespace)]

    public XmlElement Find(Microsoft.Crm.Query.fetch theQuery)

    {

    }

}

The client side is just as simple now. I won’t show the whole interface (in particular I’m going to skip showing what Find() looks like because I haven’t come up with a reasonable way to construct the <fetch> queries in code. For now using XML actually is better.

CRMServices.Services webService = new CRMServices.Services();

webService.Credentials = new System.Net.NetworkCredential("", "", "");

CRMServices.account theAccount = new CRMServices.account();

theAccount.accountcategorycode = new CRMServices.picklistType();

theAccount.accountcategorycode.Value = 1;

theAccount.accountclassificationcode = new CRMServices.picklistType();

theAccount.accountclassificationcode.Value = 5;

theAccount.accountnumber = "A123456";

theAccount.name = "A sample account - " + DateTime.Now.ToLongTimeString();

theAccount.address1_line1 = "

One Microsoft Way

";

theAccount.address1_line2 = "110/2284";

theAccount.address1_city = "Redmond";

theAccount.address1_stateorprovince = "Washington";

theAccount.emailaddress1 = "mikemill@microsoft.com";

theAccount.donotphone = new CRMServices.booleanType();

theAccount.donotphone.Value = true;

Guid id = webService.Save(theAccount);

 

The steps I went through to build this site were:

1. Create the correct XML schemas for the interesting entities. This was done using a modified version of the sample code I previously provided.

2. I ran the resulting XML schema document (all of the entities are defined in a single schema to make life easier) through the XSD Object Generator. To work around a bug in the 1.1 CLR I needed to modify the resulting class and rename the __fooSpecified fields.

3. Next, I created a schema for <fetch> so I could turn that into a class as well. That’s a topic for another rant someday.

At this point I have schemas and classes for all the entity definitions. This works for added attributes as well because I just regenerate the schemas and classes as necessary. One way around this would be to add an extension point to the schema and let the client side figure out where to put the “found” attributes. I guess I prefer the code generator approach.

4. The next step was to create the web service itself using the above class. By the way, I also wrote the test driver code at the same time to make sure that what I was building was going to work the way I wanted it to. TDD can be your friend when you’re experimenting.

I decided to use the unsupported COM proxy for this project for a few reasons. First, I wanted to sit as close to the platform code as I could because I just didn’t see the point in going through yet another serialization step when I already had done that. Using the COM proxy is fairly straightforward, but there are a few gotchas (particularly around memory management issues). This resulted in code that looks a bit like this (this is an account Save operation).

case "account":

{

    account theObject = (account)theEntity;

    theObject.ownerid.Value = ua.UserId;

    theObject.ownerid.type = (int) ObjectType.otSystemUser;

    string entityXml = ToString(theObject);

    CRMAccount theService = new CRMAccountClass();

    if (IsMissingElement(theObject.accountid)

    {

        id = theService.Create(ref ua, entityXml);

    }

    else

    {

        theService.Update(ref ua, theObject.accountid.Value, entityXml);

    }

    break;

}

 

The ugly thing about this code is the big switch on entity type. There are ways around this, but none of them are really pretty. So, for now this code will be messy. As you might have noticed already the methods don’t need the CUserAuth structure because the web service takes care of this for you. I figured that it was rare enough for a platform caller to actually want to supply different credentials (and if that’s the case then adding a SOAP header to this with the desired credentials would be the better way to go).

One of the things I debated was changing the signature for Get to take a businessEntity as well. That would remove the need to pass the entity name. I might still do that, but I want to play around with this some more first.

I have tested this code as part of a default CRM v1.2 installation. All I did was drop the resulting DLL into the wwwroot/bin directory and drop the ASMX file at the root of the application. Gotta love XCOPY installations.

Please keep in mind that this is all sample code, that the V2 product will likely differ in style and substance, and that I don’t speak for the CRM team. That said though, this might make someone’s job a little easier for a while. If you’d like a copy of all the code let me know and I’ll package it up and send it. Right now I don’t have anywhere to post the bits for public consumption (I don’t want to use my SU personal pages) so I’ll have to send email.

I have a few things left that I’d like to add to this sample – I really think we need a simple way to deliver commands as business documents which allow arbitrary business logic. I’ll probably do this using some flavor of Execute(). I also want to add support for the V1.x <columnset> parameter to Get(). That should be fairly simple, but I’ve left it out for now. I thought about using a delegate or reflection based model for loading ISV-defined code in Execute and for ‘callouts’, but that one I decided to leave as an exercise for the reader.