The Problem

Recently I was creating a Silverlight based app for one of our customers. The applications runs on the contact form embedded in an IFrame. It used OData to retrieve a defined set of columns for the contact. All was looking nice, until I noticed, that after changing some of the columns and saving the contact, my Silverlight application still used old data – even after refreshing the page.

Internally it used an HttpWebRequest object to retrieve data from CRM via a URL like /XrmServices/2011/OrganizationData.svc/ContactSet(GUID'{0}’)?$select=FullName. Unfortunately Silverlight aggressively caches Web-Requests. Since the URL for a contact never changes, Silverlight does not even really do a further request and pulls data from the cache instead. The HttpWebRequest class in the Silverlight Framework does not provide means to disable caching.

There are a lot of posts and forum topics out there discussing that problem.

The solution

Obviously the problem is, that the URL does not change. To overcome the caching issue, we just have to make sure, the address changes on every request. The solution I came up with was simply adding a made up parameter to the URL. Fortunately the OData Endpoint simply ignores unknown parameters. I called it ignoreCache, but of course any name will do here. The value of that parameter will be set to basically the current time. Now the URL changes on every request and we can work with the actual data:

  1. Dim urlRaw As String = String.Format(“/XrmServices/2011/OrganizationData.svc/ContactSet(GUID'{0}’)?$select=FullName&IgnoreCache={1}”, id, Date.Now.TimeOfDay.Ticks)

When working on web-applications or services, you often need to deliver the latest bits to your QA team. Usually you have a separate web-server where the latest version for in-house testing is installed. Usually this is done like this:

  1. Compile/publish the application
  2. Modify web.config to suit the test environment
  3. Remote-connect to the web server
  4. Bonus points for backup
  5. Copy the new version to the web server

This can be very time consuming and error prone. If this sounds familiar, you should go on reading.

The process of publishing the application to your IIS can be automated, so every time a developer checks in a new version to TFS, it will be deployed automatically to IIS within a short period of time without performing any manual steps.

Web.config transformation

Before you go on reading, you should get familiar with the concept of web.config transformations. I recommend the lecture of http://www.hanselman.com/blog/WebDeploymentMadeAwesomeIfYoureUsingXCopyYoureDoingItWrong.aspx by Scott Hanselman. Although it targets Visual Studio 2010, it still applies to Visual Studio 2012 and 2013.

While there are a lot of tutorials out there, they all end with a package that has to be imported manually in IIS or with the deployment from Visual Studio – again manually. But at least, the second step from the list above is automated now. Let’s move on to the web server.

Install and configure web deploy

Automated deployment has to be enabled on IIS. Therefore you need to install and configure web-deploy. Although it is available through the web platform installer, it does not install all components – at least by the time of writing this. That said, I strongly recommend using the MSI installer which can be downloaded at http://www.iis.net/downloads/microsoft/web-deploy.

A basic tutorial for installing and configuring web deploy is available at http://www.iis.net/learn/install/installing-publishing-technologies/installing-and-configuring-web-deploy.

After installing web deploy (use Full), the web deployment feature for the site has to be enabled in IIS. Therefore open IIS Manager, right click the web site and chose deploy –> Configure Web Deploy Publishing. Personally I prefer using an IIS Manager user. Using a domain account, you might experience some security issues that require giving permissions to some working directories of IIS itself. Of course you could also use a domain account here, which should be at least the account your build service runs as. I will go on with an IIS User here.

After enabling web deploy for your site, a settings file will be created. This can be imported to Visual Studio later.

Next, the user who will be publishing the site needs some permissions in order to do so. In IIS Manager select the server level node and open Management Service Delegation from the right panel. Then add a new rule from the Deploy Application with content template. You should then modify the rule to include the Providers contentPath, createApp, iisApp, setAcl. Now add your deployment user to the rule.

Again: Bonus points for backups.

Test from Visual Studio

Now open the solution in Visual Studio, right click your web project click Publish. On the profile tab click import and import the settings file created earlier, when Web Deploy has been enabled on the IIS site. Complete the connection Info by entering the password and add a subdirectory to the site name if necessary. In the field destination URL a url can be entered that should be opened in the web browser after deployment has succeeded. In case of a web service, the .svc address can be used here.

Validate the connection info, choose the Build Configuration on the settings tab (the one with your web.config transformation of course) and publish your site.

If your site was published successfully it will be opened in the web browser. Now let’s create a build definition.

Creating the build definition

In order to get the site published automatically on each check-in, first we need to create a new build definition for the web project in TFS. Set the trigger to Continuous or Rolling Builds as you prefer. Configure the Source Settings accordingly.

On the Process tab expand the Advanced area and define the MSBuild Arguments as follows:

/p:DeployOnBuild=true;PublishProfile=”your publish profile name as in Visual Studio”;Password=secret;CreatePackageOnPublish=True;AllowUntrustedCertificate=True

Alternatively you can specifiy:

/p:DeployOnBuild=True /p:DeployTarget=MsDeployPublish /p:CreatePackageOnPublish=True /p:MSDeployPublishMethod=WMSVC /p:MSDeployServiceUrl=https://myWebBox:8172/msdeploy.axd /p:DeployIisAppPath=”Default Web Site/Subfolder” /p:UserName=webdeployUser /p:Password=secret /p:AllowUntrustedCertificate=True

Finally set the MSBuild Platform to X86, since WebDeploy will not work with Automatic Platform.

Now make a change to your web application, check-in and see the magic happening.

Of course you can take all this one step further from here and create a (manual) build, to publish your app to the production box with the right settings with a single click (OK, admit it takes three clicks to queue a build) without ever bugging your admins again for each and every update you need to roll out.

Sometimes you will be in need for globalization support in CRM Plug-Ins, e.g. if you want to use an InvalidPluginExcecutionException to inform the user about something, or maybe because you want to create a task with a localized subject and description.

Now if we have a look at the default means provided by .NET, you would create a .resx file for the neutral language and a .de.resx file with the same name for german translation and leave the rest up to the .NET framework. While this is perfect for ASP.NET and WinForms, the main disadvantage regarding a plug-in for MS CRM is, that only the resource file for the neutral language will be compiled into your Assembly, while the compiler creates a satellite assembly for any additional language, placed in a subfolder for the language. This means, you will have to register your plug-in from disk and deploy the culture-specific resource files as well. In order to be able, to deploy your plug-ins in a solution, it has to be deployed to the database as a single assembly.

What we do, is adding a resource file (e.g. LocalizedMessages.resx) to the plug-in project. It will contain a key for each message in the base or fallback language. This key will be used to get the localized message later. For localization we will create a key with the same name and append an underscore and the LCID of the language, e.g. “_1031” for german translation.

RESX

The access to the resources including fallback to the neutral language will be managed by a small helper class like this:

  1. Imports System.Resources
  2. Public Class PluginLocalizer
  3.    Private Shared rm As ResourceManager = My.Resources.LocalizedMessages.ResourceManager
  4.    Public Shared Function GetString(ByVal resourceKey As String, ByVal lcid As Integer) As String
  5.       ‘ look for the localized resource
  6.       Dim locale As String = rm.GetString(String.Format(“{0}_{1}”, resourceKey, lcid))
  7.       ‘ GetString returns null, if the key is not found
  8.       If Not String.IsNullOrEmpty(locale) Then
  9.          Return locale
  10.       Else
  11.          ‘ fallback to the “neutral” resource
  12.          Return rm.GetString(resourceKey.ToString())
  13.       End If
  14.    End Function
  15. End Class

 

In order to determine the language of a specific system user, we have to read his or her user settings from CRM and use the uilanguagecodeid:

  1. Public Shared Function RetrieveUserLCID(ByVal userId As Guid, ByVal svc As IOrganizationService) As Integer
  2.       Dim cols As New ColumnSet(New String() {“uilanguageid”})
  3.       Dim settingsReq As New RetrieveUserSettingsSystemUserRequest() With {.EntityId = userId, .ColumnSet = cols}
  4.       Dim settingsResp As RetrieveUserSettingsSystemUserResponse = DirectCast(svc.Execute(settingsReq), RetrieveUserSettingsSystemUserResponse)
  5.       Return DirectCast(settingsResp.Entity(“uilanguageid”), Integer)
  6.    End Function

Now it takes two lines of code, to generate a message localized for the current user:

  1. Dim userLcid As Integer = CrmHelper.RetrieveUserLCID(context.InitiatingUserId, crmService)
  2.             Throw New InvalidPluginExecutionException(OperationStatus.Succeeded, PluginLocalizer.GetString(EResourceKey.MaximumUsersReached.ToString(), userLcid))