Pages

Friday, July 8, 2011

Silverlight and CRM 4 (continued, again)

When I delivered the code example in my last post on the subject, I knew that it would probably be worthwhile to pick apart the individual code elements in order to provide a better understanding of what’s going on underneath it all.  This marks the final chapter of the saga which started as a 10-minute presentation, and culminates about 40-hours of work.

Again, this subject matter all stems from my participation in CRMUG’s ongoing 10@10of10 series.  To date, I’ve slept in too long to catch any of the other presentations live—for that I apologize to the other presenters, because I really am interested in their content matter.  Problem is, I’ve been up until 4 A.M. most mornings doing freelance work, or dealing with other things.  Thankfully, CRMUG will be posting the session recordings to their site—or so I’m told.

The code I will present will be in snippets from the Simple CRM 4 Silverlight Application project that I’ve uploaded to CodePlex.  If you didn’t already download it to build the application for yourself using my last post’s instructions, you’ll probably want to do so now for what follows.  (You will also need the Silverlight 4 Tools for VS 2010 and the Silverlight 4 for Developers Runtime to work with the project files.)

What follows: A Silverlight primer

Ok, so… first thing’s first:  If you’re inexperienced with Silverlight, I recommend the Silverlight Jumpstart book by David Yack (downloadable as a PDF).  It serves as a pretty good primer for working with Silverlight in general.  I used it, and found it very informative and useful. 

Mr. Yack also wrote another book, CRM as a Rapid Development Platform that contains a chapter on Silverlight development for CRM.  I also found that chapter useful in my project, but diverged from his examples by using the WSDL for web-service interaction.  His point about WSDL bloat upon the final XAP file are valid, and should be considered when developing smaller, single-purpose products.  However, for richer and fuller Silverlight apps, I find the WSDL to be integral to the speed of development.

I’m not going to get into the ins-and-outs of Silverlight development.  There’s too much to cover for this space, and I will simply proceed under the assumption that you, the reader, will find this knowledge for yourself and use it to obtain an understanding from what follows.

What follows: Connecting to CRM

There are two things necessary to connect Silverlight to CRM:

  1. knowing the service endpoint and authentication method1
  2. having an asynchronous interface to the endpoint methods3

For ease of use within the context of the ISV folder, I split these requirements across two projects, respectively:

  1. an ASPX host page for the Silverlight control
  2. the WSDL in the Silverlight application

Because Silverlight must ultimately satisfy both requirements internally, there is a third, intermediary requirement caused by my division of responsibility:  passing service endpoint and authentication information from the host page into Silverlight2.

Knowing the service endpoint and authentication method

Because I decided to use an ASPX page and code-behind file to meet this requirement, the process is decidedly simple, and is explained best in these snippets of code:

SimpleCrmApp.aspx.cs (line 35):
serviceUrl = this.Request.Url.Scheme + "://" + this.Request.Url.Host + "/MSCRMServices/2007/CrmService.asmx";

The snippet above uses the Page.Request member to assemble relevant components of the scheme used to view the page (“http” or “https”), and the hostname.

SimpleCrmApp.aspx.cs (lines 19 and 38):
orgName = Request.QueryString["orgname"];
//... jump over code
CrmAuthenticationToken token = CrmAuthenticationToken.ExtractCrmAuthenticationToken(Context, orgName);

The snippet above shows how the code-behind grabs the value of “orgname” from the same Page.Request to complete our ExtractCrmAuthenticationToken() method call.  This parameter must be passed to the host page somehow.  In our example, we accomplish this in this part of the SiteMap.xml configuration:

SiteMap.xml:
<SubArea Id="SimpleCrmApp" PassParams="1" Url="/../ISV/SCA/SimpleCrmApp.aspx" AvailableOffline="false">

Here, we rely on PassParams to do our dirty work for us.

Now, you might be misled into thinking that the token would be sufficient for Silverlight to authenticate its own SOAP messages.  Many in the forums have.  The reason you would be wrong is that the ExtractCrmAuthenticationToken() method provides you with the CrmAuthenticationToken instance used by CRM’s platform to communicate with the web service endpoints.

As I previously explained, this poses a problem because this token is designed to always operate under two conditions:

  1. Use to communicate with SOAP under a CrmImpersonator() call, which removes impersonation from the thread, thereby bringing “SYSTEM” user network credentials for use with…
  2. Integrated Authentication

It’s important to note that the reason token won’t work for Silverlight in strictly Integrated Authentication environments is because of the presence of the CallerId value. Any time this value is not Guid.Empty, CRM assumes impersonation is taking place, and checks the credentials for membership in the PrivUserGroup.  Because CRM does not communicate with itself over IFD, the CrmAuthenticationToken provided with always have an AuthenticationType of “0” and will never contain a CrmTicket.

When authenticating via IFD, you must supply a value for CallerId.  That’s what explains this bit:

SimpleCrmApp.aspx.cs (line 40):
callerId = token.CallerId.ToString();

Finally, I need a sensible way to determine if CRM is being used in an IFD scenario or not.  I could go the complicated way, parsing the URL to find out if the hostname has the orgname in it—but that’s a lot of code.  A more elegant way, is to look for the browser-cookie “MSCRMSession”:

SimpleCrmApp.aspx.cs (line 28):
// A broadly applicable mechanism for detecting IFD is the presence of the MSCRMSession cookie
if (Request.Cookies["MSCRMSession"] is HttpCookie)
    authType = "2";
else
    authType = "0";

Fun, huh?

Here’s why “MSCRMSession” is also important: it contains the value of the CrmTicket.  See, whenever the browser is authenticated via IFD, it uses a browser-cookie (which is inaccessible through JavaScript or Silverlight) to hold this ticket value.  The nice thing is, that it’s passed in every HTTP request header that originates from the browser (including Silverlight) to its domain of origin.  Therefore, we do not need to worry about finding it or passing it through the headers of our own SOAP requests.

Notice how every value I’m taking is ending up in string format?  Well that leads into the next requirement.

Passing service endpoint and authentication information from the host page into Silverlight

There are a handful of ways to obtain data from a Silverlight application’s hosting page:

  • Inspect the DOM.
  • Call a JavaScript function.
  • Use InitParams.

I’m sure there are others I’m unfamiliar with, but let me tell you why I choose the InitParams: it’s easy, and it’s a one-way stream into Silverlight that doesn’t require any backward movement by the Silverlight app into its execution context.  It simply doesn’t care.  In fact, if I had some other way that I would like to instantiate my Silverlight app in a wholly different context, I could rely on data passed into InitParams to define its operation.

To achieve this, there are two code-snippets on the server side, and one on the Silverlight side to illustrate how this works:

SimpleCrmApp.aspx (line 69):
<param name="initParams" value="<%= BuildInitValue() %>" />

The snippet above, from the <object> container for the Silverlight control, instructs Silverlight to receive the value and pass it into the Startup event arguments.  To construct the value, I have this function:

SimpleCrmApp.aspx.cs (lines 43 – 47):
protected string BuildInitValue()
{
    // build a series of parameters to be piped into the Silverlight app from the hosting control page
    return "callerId=" + callerId + ",orgName=" + orgName + ",serviceUrl=" + serviceUrl + ",authType=" + authType;
}

Since the values are being embedded into the host page by this function, I need to work with strings—which flows from the code in the first requirement.  Then, it’s a matter of examining the values from the Startup event arguments in Silverlight:

App.xaml.cs (lines 30 – 34):
if (e.InitParams == null ||
    e.InitParams["callerId"] == String.Empty ||
    e.InitParams["orgName"] == String.Empty ||
    e.InitParams["serviceUrl"] == String.Empty)
    throw new ArgumentException("This Silverlight application requires values for callerId, orgName, urlScheme, and serviceUrl parameters.");

The snippet above occurs as the first statement in the handler Application_Startup(), which has been attached to the Startup event of the Application class in my custom constructor:

App.xaml.cs (line 20):
this.Startup += this.Application_Startup;

As you can see, the type StartupEventArgs (and its instance, e) expose the member InitParams, from which I obtain the values I submitted from the hosting page via its implementation of IDictionary.

So, now what?  Well, since we have all of the necessary information to connect to CRM, it’s time to start working with the code from my first post.

Having an asynchronous interface to the endpoint methods

Since I have all the information I need to connect to CRM within the Application_Startup() call, I refrain from storing the information and instead use it immediately to construct an instance of my CrmServiceInstance class:

App.xaml.cs (lines 36 – 51):
// Construct a new CrmAuthenticationToken from some of the InitParams elements
CrmAuthenticationToken authToken = new CrmAuthenticationToken();
 
authToken.AuthenticationType = Convert.ToInt32(e.InitParams["authType"]);
 
// This is an important piece for determining proper IFD communication
if (authToken.AuthenticationType != 0)
    authToken.CallerId = new Guid(e.InitParams["callerId"]);
else
    authToken.CallerId = Guid.Empty;
 
authToken.OrganizationName = e.InitParams["orgName"];
 
// Create a new CrmServiceInstance and assign a new CrmServiceConnectionParams object to its ConnectionParams member
CrmServiceInstance crmService = new CrmServiceInstance();
crmService.ConnectionParams = new CrmServiceConnectionParams(e.InitParams["serviceUrl"], authToken);

In the snippet above, you can see that I’m constructing a new CrmAuthenticationToken object, with all the various input received through InitParams.  I then pass this and the endpoint URL (also taken from InitParams) to the ConnectionParams member of crmService

Because I perform these immediately upon the application startup, I have no need to examine the IsCrmServiceReady member or attach any handlers to the OnCrmServiceReady event—it is simply ready for me to proceed.  However, I put an example of how this might be performed in this code snippet from the MainPage_Loaded() event handler:

MainPage.xaml.cs (lines 42 – 46):
// Validate the readiness of the crmServiceInstance before proceeding further; use an event handler to work out the kinks
if (!crmServiceInstance.IsCrmServiceReady)
    crmServiceInstance.OnCrmServiceReady += new EventHandler<EventArgs>(crmService_OnCrmServiceReady);
else
    crmService_OnCrmServiceReady(this, new EventArgs());

Where this event is handy, is if I had created my CrmServiceInstance in XAML—rather than directly in the code, as with this example.

From here, I pass crmService into the customized constructor for my MainPage class.  Once this is done, a connection to CRM has been adequately defined and is now available for my Silverlight page, MainPage, to use for what follows.

What follows: Using CrmServiceSoapClient

Because Silverlight requires web-service interaction to operate in an asynchronous way (for non-interfering performance reasons), all of the traditional methods from [I]CrmService are implemented in CrmServiceSoapClient with an “Async” suffix.  Execute() becomes ExecuteAsync(), and RetrieveMultiple() becomes RetrieveMultipleAsync(), for example.  Working with these asynchronous counterparts can be perplexing, considering that they all have no return.

The returns are provided through events.  Each traditional method not only has an “Async” representation in Silverlight, but also a “Completed” event; ExecuteCompleted and RetrieveMultipleCompleted, for example.  This means that every method call to the web-services is handled by every active event handler registered to these events.  This can complicate the design of your Silverlight application, to a degree—given that:

  1. You should always have the event handler in place before the method is called; and
  2. Every event handler will catch every execution of the method for the same CrmServiceSoapClient instance

For our example, however, all I need is a simple, one-time query for all active Account names.  I achieve that with the following code in the crmService_OnCrmServiceReady() method (again, this method is an event handler for the CrmServiceInstance.OnCrmServiceReady event, and is not needed by the code, but provided for example purposes):

MainPage.xaml.cs (lines 51 – 66):
// Query for active accounts
QueryByAttribute query = new QueryByAttribute();
query.EntityName = EntityName.account.ToString();
query.Attributes = new string[] { "statecode" };
query.Values = new object[] { 0 };
 
// Query three specific string columns
ColumnSet columns = new ColumnSet();
columns.Attributes = new string[] { "name" };
 
query.ColumnSet = columns;
 
// Assign a handler to deal with the results, before triggering the execution of the query
crmServiceInstance.CrmService.RetrieveMultipleCompleted += new EventHandler<RetrieveMultipleCompletedEventArgs>(CrmService_RetrieveMultipleCompleted);
 
crmServiceInstance.CrmService.RetrieveMultipleAsync(query);

Note that one statement before I call RetrieveMultipleAsync(), I attach the handler CrmServce_RetrieveMultipleCompleted() to the RetrieveMultipleCompleted event.  I do this to prevent any possible—though unlikely—race condition by which the thread processing the “RetrieveMultiple” message might finish and trigger the event, before the event handler is assigned.

So, interpreting the return is an important function of CrmService_RetrieveMultipleCompleted().  Here’s the body of that method:

MainPage.xaml.cs (lines 69 – 82):
void CrmService_RetrieveMultipleCompleted(object sender, RetrieveMultipleCompletedEventArgs e)
{
    // Instantiate a new List<account> for our results
    List<account> retrievedAccounts = new List<account>();
 
    // Assign the results of our query to the new List
    foreach (account a in e.Result.BusinessEntities)
    {
        retrievedAccounts.Add(a);
    }
 
    // Push the results into our RetrievedRecords data context
    RetrievedRecords.AccountRecords = retrievedAccounts;
}

As you can see, RetrieveMultipleCompletedEventArgs (as an instance, e) has a Result member that contains our retrieved records, much the same way as the traditional RetrieveMultipleResponse would.

The rest of my code operates the way a basic Silverlight application should when pushing data to the view.  To make a long story short, I use a “ViewModel” for the data, represented by a collection of DisplayAccount.  These are handled by the class CrmRecords, which serves as the type for the RetrievedRecords member of MainPage.  These serve as abstractions of the account type to limit the amount of columns automatically generated by the DataGrid.

What follows:  Bed Time

Ok, I’ve been up late working on this post.  Time for bed.  Hopefully, it all makes sense.  Comment below if you have additional inquiries.

Thursday, July 7, 2011

New Twitter Feed

As you may notice, on the right-side of this blog is now a small reproduction of a twitter feed I setup for this blog, and for other CRM related goodness.  Many MVPs engage in Twitter, and a few have asked me to use it to broadcast blog updates.  Apparently RSS and Atom up and died since last I checked.

Anyway, new posts will be dropped in that space as well, if you’re keen to follow that kind of thing.  Another post will be coming tonight to complete the “Silverlight and CRM 4” trilogy.  Stay tuned.

Wednesday, July 6, 2011

Silverlight and CRM 4 (continued)

[UPDATE: The conclusion of this series is up, and in it I discuss the code and break-down how I developed it and why it works the way it does.]

I promised in my last post on the subject that I would include an example of the code I presented during my CRMUG 10@10of10 segment.  Then, I went to delay that example for a couple of weeks while I was looking for a good time to produce it.  Well, today was that day.

For starters, the example code is hosted at CodePlex.  I put the project together in Visual Studio 2010, so the solution and project files will all reflect that.  Also, you will need the Silverlight 4 Tools for VS 2010 and the Silverlight 4 for Developers Runtime to open the project files.  Head on over there to download it, decompress it to your favorite demonstration code directory, and come back here for the instructions on putting it together.

Putting It Together

First, download the WSDL from a CRM deployment you would like to compile the project against.  I personally prefer a “vanilla” deployment of CRM to keep the WSDL small, and rely on DynamicEntity to perform all the dirty work for me—however that is not necessary for this sample project (in fact, I don’t use DynamicEntity at all in it, though my special “NamedProperties” extension is available to the project for your own tinkering amusement).

1 - Customization

2 - Download WSDL

3 - Download WSDL for CrmService

4 - Download WSDL for CrmService - Save As

Save the resultant XML file somewhere easy to locate, such as the root folder for our project.

5 - Save WSDL as Xml File

Open the solution in Visual Studio, and add a “Service Reference” to the Simple CRM App project.

6 - Add Service Reference

Be sure to specify the XML file location as the “Address” and then click Go.  Upon a successful load, the “Services” frame will contain an item titled “CrmService”.  Specify the “Namespace” value as CrmSDK.  Click OK when finished.

7 - Add Service Reference - Location and Namespace

Next, add a “Reference” to the Simple CRM App.Web project.

8 - Add Reference

Locate and select the assembly microsoft.crm.sdk.dll from the CRM 4 SDK.  Click OK.

9 - Add Reference - CRM SDK

Now, build the whole solution.

To deploy the project to CRM, create this new folder:

<crm web root>/ISV/SCA

In this folder place the following files from the Simple CRM App.Web project folder:

Silverlight.js
SimpleCrmApp.aspx
ClientBin/Simple CRM App.xap  (copy the folder)

Deploy this file:

Simple CRM App.Web/bin/SimpleCRMApp.Web.dll

Into the following location:

<crm web root>/bin/

Now, in order to view the project, export your SiteMap.xml from the customizations, and insert this code into some reasonable area

<SubArea Id="SimpleCrmApp" PassParams="1" Url="/../ISV/SCA/SimpleCrmApp.aspx" AvailableOffline="false">
  <Titles>
    <Title LCID="1033" Title="Simple CRM App" />
  </Titles>
  <Descriptions>
    <Description LCID="1033" Description="A simple Silverlight demonstration." />
  </Descriptions>
</SubArea>

Import the altered SiteMap.xml, and refresh your CRM window to view the changes.  Upon clicking on the new navigation item, you should be treated to a very basic Silverlight page that shows a list of all active accounts by name.

I’ll get into the details of the code a little more with another post to follow within a day or two.

Friday, July 1, 2011

I’m a Microsoft MVP! (Still)

It’s surprising to me to think that a year ago, I was welcomed graciously into the arms of the MVP program.  Mostly, because this year seems to have just flown by.  Awaiting news of my renewal status caused even more anxiety than I had last year, because my daughter’s illness dropped my activity level in the community quite a bit.

I tried desperately to find time to contribute as often as I could, so that I might secure my MVP renewal—and it appears those efforts paid off.  Soon, our daughter’s treatments will be largely over, and things brought to a more stable and relaxed atmosphere around the home.  This is exactly the situation I need to move forward with CRM 2011, and really make a return to the forums a stronger developer for it.

Thanks to Microsoft, Melissa (my MVP team lead), and the many MVPs whom I’ve come to know on a personal level over the last year.  I promise to make the MVP Global Summit this year to make up for last year.