Tag Archives: Visual Studio 2010

WEBCAST: CRM 2011 Team Development With Shan McArthur

UPDATE: If you missed the webcast, you can watch the recording via the details link below.

Team development for Dynamics CRM 2011 customizations is a topic I get into often with customers and partners.  The best, most comprehensive session I have seen on team development with Dynamics CRM 2011 was delivered by Shan McArthur (blog, twitter) at eXtreme CRM (Las Vegas) this year.  My biggest frustration about his session was that only the people at eXtreme CRM could benefit from knowledge he was sharing.  Good news!  Shan’s delivering an updated and extended (2 hour) session on this topic for the XRM Virtual User Group on 11/29/2012 (sorry for the short notice).  Details here:

http://www.xrmvirtual.com/events/team_dev_ShanMc_CRM2011

This is a MUST SEE webcast in my opinion.  Watching it just may improve your life as a Dynamics CRM developer.

NOTE: The “Register for Event” button will be disabled unless you are signed in to the site.  Have no fear, there is also a “Attend Live Meeting Here” link on the page in the event you are undecided about becoming a member.  If you aren’t a member, you should be (it’s free). 

@devkeydet

How I develop and unit test CRM 2011 plugins

This is probably the most promised, but never published blog post on my list of potential posts.  I’ve managed to convince enough CRM developers I’ve run into that they should get some test driven development with unit testing religion.  Especially because of the added productivity benefits for CRM plugin development.  This post isn’t to convince you of the benefits of unit testing.  There are plenty of articles/blogs which cover that topic just fine.  I’m not interested in debating the virtues of unit testing.  Love it or leave it;). 

The purpose of this post/video is to show you how I develop and unit test plugins.  There are a few articles out there about how to unit test CRM 2011 plugins.  I haven’t been satisfied with the approaches I’ve seen.  I landed on an approach which combines using the CRM 2011 Plugin Testing Tools for mocking IServiceProvider and Moles fore detouring types used inside the plugin which are difficult or time consuming to mock such as OrganizationServiceContext, Web Service calls, and others.

You can grab the finished example here:

http://sdrv.ms/StxcgU

You can also grab the starter solution I mention in the video here:

http://sdrv.ms/ScFUNf

Once you’ve learned the basics in the video, hop on over to the series below for some more advanced examples:

http://zhongchenzhou.wordpress.com/2012/07/08/dynamics-crm-2011-unit-test-part-1-introduction-and-series-contents/

The series above focuses on using Fakes in VS2012.  Fakes is the successor to Moles. However, the general techniques still apply whether using Fakes/Moles.  If you are using VS2012, then use Fakes.  If you are using VS2010, then use Moles.

I find many people who are new to unit testing have questions about where it fits in relation to other forms of testing.  Unit testing does not replace other forms of testing such as integration testing (i.e. testing the plugin running in CRM).  TDD through unit testing is an upfront developer discipline.  It requires a certain culture and mindset on a dev team.  In my opinion and experience, it helps me produce better code and allows me to catch more (not all) bugs early on, before handing things off to other testers.  It also makes the others testers lives/jobs easier. 

When applying this approach to CRM plugins, there’s a bonus side effect that (for me) allows me to be more productive in authoring my plugin code because I can:

  • Iterate (make code changes, build, debug and/or test, repeat) rapidly without needing to deploy to a CRM server
  • Iterate on my code rapidly without affecting the external systems
  • Use code coverage to identify untested scenarios which enable me to build better, more complete tests. 
    • This, in turn, allows me to build better, less buggy code.
  • The nature of having a suite of automated tests allows you to identify issues and regressions in your code early on as you make changes
    • This becomes more and more valuable over time as you build out your plugins and corresponding tests
  • Other devs don’t have to know how to test your code.  They just run the tests you’ve written

@devkeydet

CRM 2011, Visual Studio, and Source Control of non-code customizations

In the latest release of the Dynamics CRM 2011 SDK, a new tool was introduced which creates new options for how teams can source control their non-code customizations.  The tool is called SolutionPackager.  I encourage you to read the following SDK documentation:

Solution Tools for Team Development

As the documentation states:

“The tool identifies individual components in the compressed solution file and extracts them out to individual files. The tool can also re-create a solution file by packing the files that had been previously extracted. This enables multiple people to work independently on a single solution and extract their changes into a common location. Because each component in the solution file is broken into multiple files, it becomes possible to merge customizations without overwriting prior changes. A secondary use of the SolutionPackager tool is that it can be invoked from an automated build process to generate a compressed solution file from previously extracted component files without needing an active Microsoft Dynamics CRM server.”

I recommend you thoroughly read through the SDK documentation before continuing.  I will not explain SolutionPackager in great detail since the documentation does a good job of that already.

Since SolutionPackager is a cmd line based tool, it can be used to integrate with any source control system.  However, most Visual Studio developers probably want to integrate SolutionPackager into Visual Studio.  Furthermore, most probably want to integrate it with the Developer Toolkit for 2011.  The video below walks you through a sample I built which demonstrates how to do just that.  You can download my completed sample here.

@devkeydet

Building VB.NET plugins with the developer toolkit

Scenario:

“I’ve seen the Developer Toolkit for Microsoft Dynamics CRM 2011 and Microsoft Dynamics CRM Online, but it only ships with C# templates.  I’m a VB.NET developer.  How can I get the developer productivity from the toolkit while still writing my plugin code using VB.NET?”

The good news is that SDK obviously supports VB.NET.  The .NET Framework has always allowed you can make calls from C# code to VB.NET code through assembly reference.  Therefore, we can devise a strategy to just make the generated C# code from the toolkit the entry point, but put our plugin code in a referenced VB.NET assembly.

“But my plugin assemblies are sandboxed and database deployed which don’t support referencing other assemblies.”

True, but there’s this wonderful little tool called ILMerge (also available through NuGet).  With ILMerge and a little refactoring, we can achieve this scenario and VB.NET developers can use their language of choice with the developer toolkit.  Here’s a video with step by step instructions.  I use ReSharper in the video to make the refactoring easier.  Everyone who knows me knows I swear by ReSharper, but I understand not everyone loves it like I do or has the budget to acquire it.  I do my best to call out what you’d need to do manually, but you might have to pause now and then if you want to follow along step by step.

You can download the Visual Studio solution that is the output of the walkthrough here.  Reuse it or just grab the post build commands to get this setup yourself, etc.  Hope this helps anyone looking to get more productive with plugin development using VB.NET and the developer toolkit.

@devkeydet

Updated Developer Tips and Tricks Deck

I spend a lot of time talking to .NET developers who are looking at building business applications using Dynamics CRM.  They journey to understanding how to be a productive Dynamics CRM developer can sometimes be daunting.  There’s a bit to learn on top of your existing .NET / web dev skills.  If you go through any CRM developer training, including the Dynamics CRM 2011 Developer Training Kit, you usually won’t get to the level of productivity building solutions that a seasoned CRM developer has.  My Developer Tips and Tricks deck is aimed at helping people get there faster.  I just spent a good part of the day updating the deck.  It’s a living document where I modify as I learn new things.

@devkeydet

CRM Solution Manager

Full disclosure, I received a free license from the authors of the tool.  However, I do believe it’s definitely worth the license fee.

I’ve been using the CRM Solution Manager add in for Visual Studio quite a bit lately.  I’ve grown fond of using it over the Developer Toolkit for web resource development.  The biggest reason is because I can right click a set of web resources, deploy AND publish in one click:

image

Another nice feature is that it translates folder structures into web resource names that follow the relative url naming recommendations here:

image

image

One more web resource feature that’s nice is that it allows you to configure JavaScript and CSS minification during the deploy/publish process:

image

I uncheck these during development so I can debug the code, but it’s a nice way to get everything minified for your production release.  While I still find the Developer Toolkit useful for plugins and workflow activities, the CRM Solution Manager has become my tool of choice for web resource development.  It also has other nice features that I haven’t dug into yet.  Check it out! 

@devkeydet

Geocoding and displaying a map for an address in CRM 2011

There are a few blog posts out there that cover this topic, but the ones I’ve found don’t take you through it step by step.  I will, however, make assumptions like you know how to create an entity, add web resources to a form, etc.  Here goes…

The overall solution is going to consist of:

If you are a “yea yea, blog blah blah, just give me the code” type then jump to the bottom of the postSmile.

Let’s get started.  First, you will need the Developer Toolkit for Microsoft Dynamics CRM 2011 and Microsoft Dynamics CRM Online and NuGet installed.  Let’s create an entity called GeocodeMapSample. As a habit, I tend to uncheck all the entity defaults after the Options for Entity section of the form.  You can always go back and turn additional features on as you need them after you create the entity.

image

Next create the following fields (all of type single line of text):

  • AddressLine1
  • AddressLine2
  • City
  • State
  • Zip
  • Latitude
  • Longitude

Yours should look like mine except you will have a different prefix than dkdt_.

image

Finally, put everything on the form:

image

Once you’ve done all this, go ahead and Publish your changes.  Next, create a new Dynamics CRM 2011 Package project called GeocodeMapSamplePackage:

image

Fill out the Connect to Dynamics CRM Server dialog:

image

If you are using CRM Online, then your discovery service is dev.crm.dynamics.com if you log in with a Live ID or disco.crm.dynamics.com if your org was provisioned through Office 365.  Make sure you select HTTPS as the protocol if necessary.  Add a Dynamics CRM 2011 Plug-in Library called GeocodMapSamplePlugins to the Visual Studio solution:

image

Find the GeocodeMapSample entity in the Entities node of CRM Explorer, right-click it and select Create Plug-in:

image

Make your dialog look like mine:

image

I am picking the Pre-Operation for the Pipeline Stage because I want to be able to throw an exception back to the caller and roll the transaction back if geocoding fails because my business requirement is that addresses must be valid.  Create another plug-in for the same entity.  This time, we want to do a few things differently.  First, set the value of Message to Update, but DO NOT click OK yet:

image

We only want this plug-in to fire when address related fields change.  Click the Filtering Attributes ellipsis (…) to bring up the following dialog:

image

Make sure you have deselected all of the fields, then ONLY selected items in the image.  In an update scenario, we want to make sure the plugin has the previous values from when the form was loaded.  Click the Parameters ellipsis (…) next to Pre Image Alias to bring up a similar dialog to before and select the same set of attributes.  We’re going to ask the Bing Maps REST service for JavaScript Object Notation (JSON) since it’s the fastest across the wire.  Add the DynamicJson package from NuGet to the plug-in project:

image

image

Now it’s time to generate some early-bound types.  The CRM SDK covers early-bound vs. late-bound here.  Most developers prefer the early bound approach unless they truly need a late-bound for writing very generic, reusable code.  This is because you become more productive and less error prone due to intellisense and compile time checking.  While the developer toolkit does have the ability to generate strongly typed classes:

clip_image002

…it does not give you any control over what classes get generated.  It just brute force creates one for each entity you have read permissions to.  Therefore, you end up generating a bunch of unnecessary code that bloats your codebase and makes your plugin WAY BIGGER than it needs to be.  Instead, I prefer to use this approach to generate the classes:

http://dkdt.me/LO71gI

Which one you choose, is up to you.  Generate Wrapper is OK for now since you are just learning, but take my advice and learn the approach above for production codeSmile.  Add a new class to the plug-in project called GeocodeMapSampleCommon.cs.  Replace the entire contents of the class declaration with the following code:

using System;

using System.Net;

using System.Text;

using Codeplex.Data;

using GeocodeMapSamplePackage.GeocodeMapSamplePlugins.Entities;

using Microsoft.Xrm.Sdk;

 

namespace GeocodeMapSamplePackage.GeocodeMapSamplePlugins

{

    class GeocodeMapSampleCommon

    {

        internal static void GeocodeAddress(Plugin.LocalPluginContext localContext, Entity preImageEntity)

        {

            if (localContext == null)

            {

                throw new ArgumentNullException("localContext");

            }

 

            var pluginExecutionContext = localContext.PluginExecutionContext;

            var targetEntity = pluginExecutionContext.InputParameters["Target"] as Entity;

 

            if (targetEntity == null)

            {

                throw new NullReferenceException("targetEntity");

            }

 

            try

            {

                var targetGeocodeMapSampleEntity = targetEntity.ToEntity<dkdt_GeocodeMapSample>();

                var sb = new StringBuilder();

                

                // see http://dkdt.me/KH4roL for Bing Maps REST API reference

                const string restQueryStart = "https://dev.virtualearth.net/REST/v1/Locations/";

                const string noAddress = restQueryStart + ",,,,";

                var address1 = string.Empty;

                var address2 = string.Empty;

                var city = string.Empty;

                var state = string.Empty;

                var zip = string.Empty;

 

                sb.Append(restQueryStart);

 

                if (preImageEntity != null)

                {

                    var previousGeocodMapSampleEntity = preImageEntity.ToEntity<dkdt_GeocodeMapSample>();

 

                    if (targetGeocodeMapSampleEntity.dkdt_AddressLine1 == null && previousGeocodMapSampleEntity.dkdt_AddressLine1 != null)

                        address1 = previousGeocodMapSampleEntity.dkdt_AddressLine1;

                    if (targetGeocodeMapSampleEntity.dkdt_AddressLine2 == null && previousGeocodMapSampleEntity.dkdt_AddressLine2 != null)

                        address2 = previousGeocodMapSampleEntity.dkdt_AddressLine2;

                    if (targetGeocodeMapSampleEntity.dkdt_City == null && previousGeocodMapSampleEntity.dkdt_City != null)

                        city = previousGeocodMapSampleEntity.dkdt_City;

                    if (targetGeocodeMapSampleEntity.dkdt_State == null && previousGeocodMapSampleEntity.dkdt_State != null)

                        state = previousGeocodMapSampleEntity.dkdt_State;

                    if (targetGeocodeMapSampleEntity.dkdt_Zip == null && previousGeocodMapSampleEntity.dkdt_Zip != null)

                        zip = previousGeocodMapSampleEntity.dkdt_Zip;

                }

 

                if (targetGeocodeMapSampleEntity.dkdt_AddressLine1 != null)

                    address1 = targetGeocodeMapSampleEntity.dkdt_AddressLine1.Trim();

                sb.Append(address1);

                sb.Append(",");

                if (targetGeocodeMapSampleEntity.dkdt_AddressLine2 != null)

                    address2 = targetGeocodeMapSampleEntity.dkdt_AddressLine2.Trim();

                sb.Append(address2);

                sb.Append(",");

                if (targetGeocodeMapSampleEntity.dkdt_City != null)

                    city = targetGeocodeMapSampleEntity.dkdt_City.Trim();

                sb.Append(city);

                sb.Append(",");

                if (targetGeocodeMapSampleEntity.dkdt_State != null)

                    state = targetGeocodeMapSampleEntity.dkdt_State.Trim();

                sb.Append(state);

                sb.Append(",");

                if (targetGeocodeMapSampleEntity.dkdt_Zip != null)

                    zip = targetGeocodeMapSampleEntity.dkdt_Zip.Trim();

                sb.Append(zip);

 

                var restQuery = sb.ToString();

 

                if (restQuery == noAddress)

                {

                    return;

                }

 

                // TODO: Move key to plugin configuration

                const string key = "?key=[INSERT_YOUR_BING_MAPS_KEY]";

 

                var webClient = new WebClient();

 

                var jsonString = webClient.DownloadString(restQuery + key);

                var response = DynamicJson.Parse(jsonString);

 

                if (response.statusCode != 200)

                {

                    throw new InvalidPluginExecutionException("Bad address.  Please fix it and try again.");

                }

 

                var coordinates = response.resourceSets[0].resources[0].point.coordinates;

                targetGeocodeMapSampleEntity.dkdt_Latitude = coordinates[0].ToString();

                targetGeocodeMapSampleEntity.dkdt_Longitude = coordinates[1].ToString();

            }

            catch (Exception ex)

            {

                throw new InvalidPluginExecutionException("Something went wrong in the plug-in.  This must be buggy sample code eh?", ex);

            }

        }

    }

}

This code does all the geocoding work.  Your generated classes will have different prefix values than dkdt_, so fix the errors by replacing dkdt_ with your prefix.  You will also have to replace the [INSERT_YOUR_BING_MAPS_KEY] string with yours from http://dkdt.me/KH4pNx.  Because of the way I am sharing this class across multiple plugins we, need to make a slight tweak to the Plugin.cs class.  Change protected class Plugin : IPlugin to protected internal class Plugin : IPlugin.  Now is a good time to build.  If you get build errors, well fix em Smile.  It’s a blog post after all.  Assuming you got your build errors fixed, there are a couple more things to do.  Open PreGeocodeMapSampleCreate.cs and replace the contents of  the ExecutePreGeocodeMapSampleCreate method with:

GeocodeMapSampleCommon.GeocodeAddress(localContext, null);

Open PreGeocodeMapSampleCreate.cs and replace the contents of  the ExecutePreGeocodeMapSampleCreate method with:

if (localContext == null)

{

    throw new ArgumentNullException("localContext");

}

 

var context = localContext.PluginExecutionContext;

 

var preImageEntity = (context.PreEntityImages != null && context.PreEntityImages.Contains(preImageAlias)) ? context.PreEntityImages[preImageAlias] : null;

 

GeocodeMapSampleCommon.GeocodeAddress(localContext, preImageEntity);

Probably a good time to build and troubleshoot again.  If your build is successful, then sign the plug-in assembly:

image

image

Ok, now you should be able to right-click the package project and deploy:

image

This will register your plug-in and messages.  Now go create a new GeocodeMapSample entity through the CRM UI.  Save, but don’t save and close.  You will see Latitude and Longitude have values after the save.  So now we have to present this stuff on a map.  Let’s do the easy one first: static map image.  Go back to Visual Studio and add a new web resource to the package project:

image

image

Replace the contents of StaticMap.htm with the following:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">

<html>

    <head>

        <title></title>

        <script type="text/javascript">
   1:  

   2:             function renderStaticMap() {

   3:                 // ReSharper disable InconsistentNaming

   4:                 var Xrm = window.parent.Xrm;

   5:                 // ReSharper restore InconsistentNaming

   6:  

   7:                 var latitude = Xrm.Page.getAttribute("dkdt_latitude").getValue();

   8:                 var longitude = Xrm.Page.getAttribute("dkdt_longitude").getValue();

   9:  

  10:                 if (latitude == null) {

  11:                     return;

  12:                 }

  13:                 if (longitude == null) {

  14:                     return;

  15:                 }

  16:  

  17:                 // see http://dkdt.me/KH4roL for Bing Maps REST API reference

  18:                 var imageUrl = "http://dkdt.me/KH4rF4" +

  19:                 latitude + "," + longitude +

  20:                     "/18?key=[INSERT_YOUR_BING_MAPS_KEY]";

  21:                 document.getElementById("mapImage").setAttribute("src", imageUrl);

  22:             }

  23:         

</script>

    </head>

    <body onload="renderStaticMap();" style="BORDER-RIGHT-WIDTH: 0px; BACKGROUND-COLOR: rgb(246,248,250); MARGIN: 0px; PADDING-LEFT: 0px; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; PADDING-TOP: 0px">

        <img id="mapImage" alt="An map image of the current record" src=""/>

    </body>

</html>

Again, replace dkdt_ with your prefix.  You will also have to replace the [INSERT_YOUR_BING_MAPS_KEY] string with yours from http://dkdt.me/KH4pNx.  Drop the web resource in the GeocodeMapSample entity form.  Publish it all and you should find a static map image on the form next time you load/refresh it.  Go through the same process to build a web resource named InteractiveMap.htm.  Replace the contents of the file with the following:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">

<html>

    <head>

        <title></title>

        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>

        <script type="text/javascript" src="https://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0&s=1"></script>
   1:  

   2:         <script type="text/javascript">

   3:         var _map;

   4:  

   5:         function loadMap() {

   6:             _map = new Microsoft.Maps.Map(document.getElementById("map"), { credentials: "[INSERT_YOUR_BING_MAPS_KEY]" });

   7:  

   8:             // ReSharper disable InconsistentNaming

   9:             var Xrm = window.parent.Xrm;

  10:             // ReSharper restore InconsistentNaming

  11:  

  12:             var latitude = Xrm.Page.getAttribute("dkdt_latitude").getValue();

  13:             var longitude = Xrm.Page.getAttribute("dkdt_longitude").getValue();

  14:  

  15:             if (latitude == null) {

  16:                 return;

  17:             }

  18:             if (longitude == null) {

  19:                 return;

  20:             }

  21:  

  22:             var location = new Microsoft.Maps.Location(latitude, longitude);

  23:             var pushpin = new Microsoft.Maps.Pushpin(location);

  24:             _map.setView({ center: location, zoom: 10 });

  25:             _map.entities.push(pushpin);

  26:         }

  27:         

</script>

    </head>

    <body onload="loadMap();" style="BORDER-RIGHT-WIDTH: 0px; BACKGROUND-COLOR: rgb(246,248,250); MARGIN: 0px; PADDING-LEFT: 0px; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; PADDING-TOP: 0px">

        <div id="map" style="position: relative; width: 100%; height: 100%"></div>

    </body>

</html>

Again, replace dkdt_ with your prefix.  You will also have to replace the [INSERT_YOUR_BING_MAPS_KEY] string with yours from http://dkdt.me/KH4pNx.  Drop the web resource in the GeocodeMapSample entity form.  Drop the web resource in the GeocodeMapSample entity form.  Publish it all and you should find a static map image on the form next time you load/refresh it.  Finally, Latitude & Longitude really are just there for code interaction.  They don’t need to be visible on the form.  Go ahead and hide them.

That’s it!  If you made it through the whole post, you now know the fundamentals of using plug-ins, web resources, and the Bing Maps SDKs to add basic “geospatial” functionality to CRM 2011.  Of course, you probably won’t want to scatter your Bing Maps key through out your code, but I wanted to keep the walkthrough as simple to follow as possible.  You can grab the unmanaged solution and Visual Studio source code here:

http://sdrv.ms/KH4j8D

@devkeydet

Minify Selection in Visual Studio 2010

You learn something new every day!  One of the things I’ve been talking to CRM folks about a bit lately is Minifying your CRM 2011 JavaScript and CSS.  Well, I just discovered that when you install the Microsoft Ajax Minifier, you get a handy little context menu option to minify.

image

It shows up in both .js and .htm files when you select the JavaScript.  Pretty cool!

@devkeydet

TIP: Associate a schema with an XML file in Visual Studio 2010

It’s been a while since I had to do it.  However, as I’ve started to need to tweak FetchXML after I’ve built the baseline using my little trick, I realized I was doing things the hard way.  I was looking at the FetchXML reference on MSDN and typing things “notepad" style” but with syntax highlighting.  Then I had that “DUH” moment.  Just point the file to the FetchXML schema in the SDK and you’ll get intellisense.  Here’s how:

Take a look at the Properties of your XML file in Visual Studio:

image

In the Properties pane, click on the ellipsis () for the Schemas property:

image

Click the Add… button:

image

Find the fetch.xsd file that’s in the schemas folder of wherever you unzipped the Microsoft Dynamics CRM 2011 Software Development Kit (SDK) and click the Open button:

image

Now you have XML intellisense for your FetchXML:

image

@devkeydet

Are you using Visual Studio PerfWatson?

You can do your part to make future releases of Visual Studio faster by installing this extension:

“Would you like your performance issues to be reported automatically? Well now you can, with PerfWatson extension! Install this extension and assist the Visual Studio team in providing a faster future IDE for you…”

http://bit.ly/vsperfwat

I have it installed and haven’t noticed it getting in the way.