February, 2005

Posts
  • Michaeljon Miller

    MS-CRM Programming model example

    • 5 Comments

    I posted the source over on microsoft.public.crm.developer. At some point I think it'll probably be put in a more permanent location.

    Updated 1321 -

    Sorry about not getting the context straight here. I usually don't read blogs through RSS so I keep forgetting that the feeds come in little disconnected pieces. Anyway, the code has beed posted to microsoft.public.crm.developer. The original posting talked about different ways to build a programming model on top of MS-CRM. These go back several months now and many of my original rants are long gone. Hopefully this sample and the "documentation" (usually I'm pretty good at writing docs, but this one is painfully short of useful information) will help get a few people off the XML strings and the old-school RPC/Encoded interfaces (Matt Powell, are you listening?).

     

  • Michaeljon Miller

    More fun with CRM web services

    • 1 Comments

    I’ve received a dozen or so requests for the sample WSDL wrappers for CRM. I haven’t forgotten about sending these, I’ve been working on cleaning up and extending the sample code so it’s slightly more useful and includes some missing functionality that I think ISVs are using. Shouldn’t be long now.

  • Michaeljon Miller

    Playing with Microsoft CRM programming models

    • 8 Comments

    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="http://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.

     

  • Michaeljon Miller

    Should CRM services return an error for invalid attributes?

    • 6 Comments

    It turns out that I was always working with an altered version of the CRM platform which silently was ignoring invalid-for-create and more importantly invalid-for-update attributes. Why is this important? Well, my recommendations for creating a friendlier programming environment were missing a critical step which isn’t available to “outside” developers. This means that creating classes from the XRM entity XSD will only work for a subset of the core scenarios.

    If you’re using the classes for data binding operations then you’ll quickly realize that a READ-EDIT-SAVE operation won’t always work. In fact, any time you run the READ-EDIT-SAVE loop on the same instance things will break. There are two options available for working around this problem. One of them puts the burden on the developer and still doesn’t solve the R-E-S scenario: create different schemas for each of the Read, Create, and Update operations, then create corresponding classes to use them. This doesn’t work well because you now really need to pay attention to which attributes are applicable. The second option is for us to take a design change (which probably won’t make into CRM 1.2, but it might be early enough to get it into CRM 2005) that makes the platform silently ignore “extra” attributes instead of returning an error.

    Obviously I’m leaning toward the latter solution where we fix the problem… er, where we change the design. This isn’t a done deal though and there are strong feelings on both sides of the issue. So I’m asking the CRM development community (at least folks who read the stuff I write) for your opinion. Is it better to silently ignore attributes that don’t make sense for the operation or would you prefer the current behavior where we return an error?

     

  • Michaeljon Miller

    Roles, Privileges, and CRM Security

    • 8 Comments

     

    One of the feature requests that we get on a regular basis, and a feature that a few enterprising ISVs have started to build, is the ability to have per-role UI. This also comes under the name “field-level security” – but only because hiding access to attributes in the UI is a possible, but insecure, solution to the problem. That is, one way to implement per-attribute security is to implement per-role UI. At the surface that makes a lot of sense, but there’s always something deeper than the surface that we need to worry about first.

    Before we talk about why implementing per-role UI is only a surface-level solution, and an insecure one at that, it’ll help if we figure out what a role is in the context of MS-CRM. Believe it or not neither the platform nor the application really has any notion of a role. It’s a concept that was invented as a way to package privileges in a way that’s easy for administrators to manage. Current CRM builds have just over 320 distinct privileges. Managing these individually would be extremely difficult. So, the idea of a security role as a privilege container was created. As a side note, early V1 builds had 20 or more security roles, it wasn’t until the last minute that we trimmed that list down to a manageable 7.

    So, what does this all mean? It means that the platform and application worry about what users can do by inspecting the set of privileges that the user has. If a service needs privilege X to continue, then that user better have a privilege X somewhere in one of the roles that are assigned to that user. Which raises another side issue: which direction is the assignment? Do we assign roles to users, or users to roles? If you’re a CRM administrator then it really doesn’t matter. You assign a role to a user. If you’re a CRM developer on the platform team working on V1 code, then you assign the user to a role.

    “OK”, you ask, “what does this have to do with per-role UI?” Well, it has everything to do with it. The application loads the one form for the entity (ignoring the special forms like Quick Create and Print) but looking in the form cache. This is a very fast and very simple lookup (ignoring the organization- and language-specific stuff). We could add a second level to the map key, perhaps something like a role identifier. That wouldn’t be difficult to do. We look at the user and then grab the information about that user from the cache. Simple enough. Next we look at the cached user information. Hmm, there’s no role information in there (remember, I said that the application doesn’t really know about roles), so we’ll need to extend the cache to hold on to the users’ roles. It’s all just code and memory, so that one’s simple enough. But wait, isn’t it possible to assign a user to multiple roles? Sure it is.

    That’s where the per-role UI falls apart. It’s entirely possible that a user has been assigned to multiple, overlapping roles. If each role has an associated UI, and those UI are different, then we don’t have a way to know which UI to load for the entity, because we don’t know which role governs the interaction.

    One option is to introduce a new concept orthogonal to the security role. Well, it’s really not orthogonal because the management of security roles and these new “UI roles” would best be done in the same spot, by the same person, at the same time. If there were two places to manage this information, then we’d double the administrative work, and probably bury the support team. There are other things that we could do, like force a single UI role on a user. But what happens if you want a role that has the ability to set attributes on Create, but only read them for Update? Then you need two pieces of UI that are different. This just gets us mired into the per-process, per-user, per-document UI mess, which doesn’t seem to have an elegant solution… yet.

Page 1 of 1 (5 items)