Sometimes it’s hard to tell where to start with CRM. So, a goal oriented example: Lets add a button to do something on a CRM form.
First, a plug for two things:
- The .chm file in the sdk is fantastic. Read it. Ditto the examples in the example directories.
- Developing ISV Applications using Microsoft Dynamics CRM 4.0 has some high level concept overview info when you have some time to read. It can help you to know what different concepts exist.
On to our task:
Step 1: Make a decision. We actually have four different ways we can add a button on a CRM form. Yes, 4. Here they are:
- Add an iframe pointing to a page under /ISV. /ISV is a folder MS has designated for Independent Software Vendors to add new pages to CRM.
- Lets you use all of your normal C#/ASP.NET tools.
- User is already logged in to the CRM website, so no worry about authentication.
- Probably the easiest for a C# developer to understand.
Add an iframe pointing to a page running in a separate web app
- It’s an iframe. Iframes can behave weird, particularly with regard to sizing. They have to be square.
- Offline Outlook client does not automatically sync files under /ISV. If you need to support offline users with features backed by /ISV pages, you need to deal with making and distrubing a separate installer package for your pages.
- Iframe loads separately from your main page.
- Separately addressable via url, a user can bookmark just your iframe.
- Might not be an option depending on your hosting.
- A separate site can be easier to configure and set up, you get your own web.config file. (it’s best to avoid relying on putting things in CRM’s web.config)
- A separate site can have separate access control, such as be part of a public facing website.
- Always an option.
- Same as #1, plus:
- Depending on what your page will be doing, you may have to deal with user authentication in some way.
- Consistent interface, lots of things in CRM work via buttons on the menu at the top of the form.
- Offline Outlook clients sync up the isv.config file, so you can deal with them consistently this way.
- Always an option.
- Few choices – you get a button added to the right of the current menu.
- If you add too many items on the menu, the labels will be removed to make room.
- Sometimes it isn’t obvious to a new user that they should look up there. Sometimes you really want a button somewhere in particular in the form.
- You can put the button right where you want in the page.
- Always an option.
- Working in JS directly on the form is hard to test. Lots of save/publish/reload/wait.
- JS directly on the form is stored in the DB. Hard to version control.
- Relying on the exact layout of things in the DOM is generally unsupported and likely to break one day with an MS upgrade.
Ok. Now, knowing a bit about what we’re getting into, we’re ready to start.
For now, we’re picking option #1, an ISV page, because it’s very very straightforward and probably has the fewest gotchas.
Things to know about an ISV page:
- /ISV is basically the only spot in CRM that MicroSoft has said that you can put things and they won’t be touched.
Originally you had to put your dlls in the main /bin or in the GAC, but very early on MS fixed it so that you could use /ISV/[yourapp]/bin. You might still find some old pages saying you have to GAC it or use the main /bin while you are searching.
So, starting with a simple example, setting up.
Life will be easiest if you have a copy of CRM that you can work directly on. Lacking that, make good backups, and then life will be second easiest if you can at least get filesystem access, say via a mounted drive.
First, start a download of crm sdk. You’ll want it handy for the help file and examples, they are quite good, and often better than what you’ll find on googling. I google it every time in case a new one has come out since I last started a project.
Now, a new web application project in visual studio. This will hold our ASP.NET page that will end up in an IFrame when we are done:
Lets do some plumbing:
Add references to the CRM Sdk. There are three ways to do this. (MSDN on this topic) My opinion:
- Use the web services by adding a web reference and pointing it to CRM’s wsdl files.
- You get static typed classes including all of your customized classes and customized attributes. (I am strongly on the side of this being a fantastically good thing. Others don’t agree.)
Take the CRM SDK dlls from the SDK you downloaded and add them as references.
- If you work with plugins, you’re not supposed to use this approach, so you’ll need to learn a separate way to work with DynamicEntity.
- CRM WSDL uses weird wrapper types like CrmBoolean and have tricky gotchas like you can’t just null things by setting them to null.
- This is what you’ll see for plugins, so if you do it for your webpages too you’ll be used to the syntax.
- Your code will be more flexible and easier to deploy on new CRM instances with different customizations. If you want to write a generic tool, don’t pick choice 1.
Feed your CRM WSDL to the latest and greatest CRM SDK tool to get strongly typed entities that are friendlier than method #1 – the weird wrapper types are gone, and you can use LINQ. Last I checked, however, this still wasn’t meant for plugins, so you still need dynamic entity if you work with plugins. Personally, I have clients with a lot of plugins, so I haven’t spoiled myself with this API yet because I don’t want to get too used to it.
- You don’t get to use the static typed classes unless you’re willing to limit yourself to the built-in non-customized versions. (Avoid these like the plague because if you try to mix them together you can force it to compile via casting foo, but good luck making it actually work. If using the CRM SDK dlls, I always stick 100% to using just DynamicEntity.)
- DynamicEntity is essentially a glorified hashtable with some annoyingly confusing tricks to it. The compiler can’t catch typos in hashtable key strings for you, so you’ll be relying on more tricks like calling ToString() on enum values.
- Your code will take longer to write and debug than option #1 or #3.
We’re going to start with option #1, because it’s reasonably quick to develop but we still learn a good bit of CRM guts. Also we will be able to understand all of the current example code we find on the internet in blogs and in the sdk itself. The new CRM SDK is still too new to have many good examples available. Make a promise to yourself now that you’ll learn it in a few months.
There are two web services. One is “CrmSdk”, the other is “MetadataSdk”. We’ll ignore metadata for now; just know that if you need to learn what values are valid for a given entity, or what values you can choose for a picklist, the metadata is where you would look. Often if you are doing a client-specific custom app you can completely ignore it; if you are doing a more general purpose app it will be utterly essential.
(See “Accessing the Server Using the Web Service” in the help file.)
(Note: Some versions of Visual Studio have hidden ‘web references’ as an extra link you click under service references. You want to add a web reference.)
CRM publishes its WSDL at http://<servername>[:<port>]/mscrmservices/2007/crmservice.asmx
By convention, name it CrmSdk:
Next up, grab the helper classes from the CRM SDK and add them to your project. They add a bunch of useful utility methods to the CrmSdk for you. The directions might be out of date.
The namespace doesn’t match and needs to be fixed up. If you take a look at your CrmSdk Web Reference, you’ll see it has namespace projectname.WhateverYouTypedOnAddWhichShouldBeCrmSdk. Search-and-replace on helpers to match that.
You now have the most basic skeleton for talking to CRM. You can build to be sure you got it right so far:
Now we need a web page:
First things first, lets add the Assembly we were told to add back at the very beginning to point to our dll in the bin directory (from “Custom Code Best Practices” page). It needs to be the first directive, even before page.
<%@ Assembly Name="arkecrmexample.dll" %>
You can drag your example button from the toolbox or manually type it in. I usually manually type the main asp:button id= runat= part, then switch to designer to get properties to add the onclick because I forget the exact parameter types sometimes. If you doubleclick on the event in the properties:
You get in your codebehind :
protected void btnExample_Click(object sender, EventArgs e)
Now I want to do something.
Crm Sdk has an example of a utility for getting a reference to the Crm Server in sdk\server\reference\cs\crmserviceutility.cs , but it’s unfortunately rather limited. We’ll still start with it as the basis for making our own utility class, just like everybody else who has ever started working in Crm.
Drag a copy to your helpers folder, change the namespace on it, and drop the using CrmSdk at the top since it’s in the right package now. The two Metadata methods won’t compile unless you add metadata sdk; I comment them out for now.
/* removed using CrmSdk and Metadata Sdk statements */
namespace arkecrmexample.CrmSdk /* used to be Microsoft.Crm.Sdk.Utility*/
public class CrmServiceUtility
Now, lets make our button talk to CRM for something. We’ll send a simple ‘WhoAmI’ request.
Because this is a blocking service call it probably should be done as an async call, but we’ll do it the quick way for now and do a future blog post about changing it to an async page.
Add a using statement for our CrmSdk:
See sdk\server\reference\cs\misc\whoami.cs and snag its code, put it into our button click for now. Remove the MS namespace on your CrmServiceUtility call.
/* used to be Microsoft.Crm.Sdk.Utility.CrmServiceUtility*/
CrmService service = CrmServiceUtility.GetCrmService(crmServerUrl, orgName);
service.PreAuthenticate = true;
// Create the request object.
WhoAmIRequest userRequest = new WhoAmIRequest();
// Execute the request.
WhoAmIResponse user = (WhoAmIResponse)service.Execute(userRequest);
Now we need a server url and an org name.
Note that CRM servers often have two URLs, an internal (“Active Directory” authentication) and an external (“Internet Facing Deployment”, or IFD, which authenticates using asp.net web forms). There is also different authentication for ‘partner hosted’ and ‘microsoft hosted’
We are going to punt on this as a topic for later and just look at the Active Directory authentication for now. There are examples in the sdk of the other hosting types.
The easiest way to check your orgName is to load CRM and grab the first directory name.
I am evilly hard-coding these things for now because configuration is very slightly harder than web.config. We’ll change this to a config file in a future blog post. For now:
string crmServerUrl = "arkecrm01";
string orgName = "ArkeCRM";
Finally, we want to do something with the result. Stock asp.net, I add a literal and set its text.
<div><asp:Literal ID="output" runat="server" Mode="Encode" /></div>
Code Behind, I added:
if (user != null)
success = true;
// this tostring is ugly, see http://blog.arkesystems.com/post/2010/08/Microsoft-Dynamics-CRM-40-ndash3b-Guids-ToString-ndash3b-Even-simple-things-can-be-complicated.aspx
// for a nice extension method for it.
output.Text = "User Id is " + user.UserId.ToString("B", System.Globalization.CultureInfo.InvariantCulture).ToUpper(System.Globalization.CultureInfo.InvariantCulture);
Now, we’re almost done. A brief word on authentication, then on to putting the page on our server.
The CRM helper method we used authenticated to CRM using ‘System.Net.CredentialCache.DefaultCredentials’, which means it told the CRM SDK that it is whoever is running the code.
CRM web servers run with identity impersonate="true" in their web.config, which means that the person who is running the code will automatically appear to be whoever is viewing the CRM website.
That is fine for the specific situation we are in here - /ISV page on the CRM server, active directory install. If we were doing the page outside of /ISV on a separate web server, or dealing with something like Internet Facing Deployment, we would net to get fancier on identifying the user. One thing you can do for some calls is just always make the call as a particular crm user. Another thing is CRM has an “Impersonate” feature, where you log in to the server as an admin user but then impersonate the actual user performing an action. Search the help file for ‘Impersonation’ when you find yourself in a more complicated situation.
Ok. Time to compile and put this on our web server.
CRM usually runs at c:\inetpub\wwwroot. If you aren’t sure where it is, take a look at the IIS config on the web server. If you have multiple CRM web servers, you’ll want to deploy your code to each of them.
We want to make a subdirectory under /ISV.
This subdirectory will contain our aspx and a bin folder.
The bin folder will contain our dll.
Important note: I am *not* copying my web.config. web.config causes headaches under isv. If you do copy it, you’ll have to remove some things. In particular the default
<authentication mode="Windows" />
will cause trouble. But just avoid your web.config for now. You’re running under CRM, you have its web.config. We’ll do application settings in a separate config file somewhere else soon.
So, I’m manually publishing these files for now. Later we’ll look at post-build steps to automate. I could also just straight copy these files instead of using publish.
I opened up explorer, found my ISV directory, and made a directory for my app.
Now back in visual studio I *deleted my web config entirely* – it’s trouble and I don’t want it there. Now I right click on the project and pick publish.
The most important thing here is to triple check that path and be absolutely sure that I am publishing to a ISV\directory and not to the root. Seriously. Triple check. Then go back up CRM. Then check again.
After checking, again, I publish.
Explorer looks good, just what I expected:
Now I can pull this file up on the server as a quick sanity check.
There are two ways to pull up a URL under CRM:
1) Include org name – goes through CRM virtual path handler.
2) Leave out org name – goes through IIS at a lower level.
#1 is usually what you want, even if it gives you trouble at first, the trouble is usually because you missed a detail.
Hooray I have a button! PUSH BUTTAN!
Would you believe that worked on my first try?
Take a quick break to pretty things up a bit, snag the style sheets off a view source on the main page.
<link rel="stylesheet" type="text/css" href="/ArkeCRM/_common/styles/global.css.aspx?lcid=1033" />
<link rel="stylesheet" type="text/css" href="/ArkeCRM/_common/styles/global-styles.css.aspx?lcid=1033" />
<link rel="stylesheet" type="text/css" href="/ArkeCRM/_common/styles/global-dynamic-styles.css.aspx?lcid=1033" />
<link rel="stylesheet" type="text/css" href="/ArkeCRM/_common/styles/fonts.aspx?lcid=1033" />
Publish again or rebuild and recopy your dll to pick up the changes.
Ok. Now to add my useless button to an iframe on an entity. Grab an entity to work on. I’ll make a new one.
I name my entity, turned off note and activities and added it to the settings tab. I kept ownership at user because it will be useful for some examples later, but in general if the entity doesn’t involve records being owned by people I would flip it to Organization.
Save, then add some attributes. Your attributes will have a different prefix, the default is “new_”.
Edit the form and add some stuff to it.
Most importantly, add an IFRAME!
If you pick ‘pass record object-type code and unique identifier as parameters’, your URL will have parameters passed into it about the record the form is displaying. This will be useful later, so I check it.
(Dependencies is strictly a protect-users-from-themselves features; if you add a dependency, the dependent field will not be allowed to be removed from the form in the form editor. That’s all it does.)
Ok, save and close, and publish.
Go make a new one of these to check out your form. (You have to reload the main CRM page before the left hand menu picks up the new options after you make a new entity, so hit f5 on the main window then go to settings.)
Taking a quick break to go over one really common situation:
You’ll find lots of references on the internet telling you to set it to about:blank. This can cause problems in odd cases, such as security warnings. Ignore the about:blank advice and use /_root/Blank.aspx
Now, a quick review of some shortcuts that I took that we’ll want to fix in the near future. I try not to take shortcuts, but this is long enough as is. Still, it’s important you know about them:
- I hard-coded the org name in various places. I should build up org name strings instead of hard-coding it.
- I didn’t use an external config file to grab my connection to CRM.
- I only dealt with one type of CRM installation, the on-premise active directory setup.
- My onclick method should call some helper functions – connecting to crm and whoami should be separate calls.
- I didn’t use an async call when making a blocking web request. This ties up my app pool threads more than necessary.
Now, a parting picture:
And thats it, we now have a button on a CRM form that talks to CRM, start to finish everything that you need to know.