This post provides some hints on creating a simple autonumber plugin for CRM 2011. I was pretty new to CRM when I wrote the original but it has since been refactored a time or two and I’m pretty happy how it came out.
I’ll start with a great blog post that details how to create a basic auto registering plugin using the new 2011 CRM SDK and Visual Studio 2010.
http://mscrmshop.blogspot.com/2012/01/step-by-step-plugin-tutorial-using.html
With this method the plugin will compile, link and then register automatically into your CRM solution. Note that this *REQUIRES* CRM On-Premise, you can’t do it with the On-Line version. Once you have it working, it will export and work in an online situation but any development and debugging will require the On-Premise.
Anyhow, don’t worry about the specific code on that blog, just note the basics that provide you with an entry point. The event you want to intercept is ‘post-create’, that is, after the record is written into the database. That lets us pull it out and mess with it just after it’s saved.
Once you have the basic solution open and configured, save that and go back to the CRM system.
In CRM, create a new Entity called AutoNumberConfiguration. This will be used to hold all the variables we need for each of the autonumber methods.
Mine ended up looking like this:
The format of the entry form is not critical, however you do need to have the following fields: ses_autonumberenabled, ses_currentvalue, ses_numberofdigits, ses_prefix and ses_startingvalue. You can rename these but you will have to change the values in the code.
After you have this setup, add an entry for one of the entities. We do education insitutions, so I’m going to use a student record. Each time a new student is entered, a new student ID is automatically assigned and we want a prefix of STU appended to the front. So we ad a new autonumber record with the name “ses_student” and enter all the info.
Once you have a record to work with, its time to get some code in the plugin. If you followed the steps in the above tutorial link, you should have quite a bit of code that was autogenerated by VisualStudio. As I mentioned, I do all of the record updating “PostCreate”, so I clicked on the Student Entity in VisualStudio (which is specific to my installation of CRM) and let VS autogenerate the “PostStudentCreate.cs” Module. I then populated it with the following code, note that the bulk of the work is done by a common method, AutoGen.GenerateAutoNumber.
//// Copyright (c) 2012 All Rights Reserved // //// 7/13/2012 10:21:36 AM // Implements the PostStudentCreate Plugin. //// This code was generated by a tool. // Runtime Version:4.0.30319.1 // namespace CRMAutoNumberPlugin.AutoNumber { using System; using System.ServiceModel; using System.ServiceModel.Description; using System.Collections.Generic; using System.Linq; using Microsoft.Xrm.Sdk; using System.Xml.Linq; using Microsoft.Crm.Sdk.Messages; using Microsoft.Xrm.Sdk.Query; using Microsoft.Xrm.Sdk.Client; using AutoGenerate; /// /// PostStudentCreate Plugin. /// public class PostStudentCreate: Plugin { /// /// Initializes a new instance of theclass. /// public PostStudentCreate() : base(typeof(PostStudentCreate)) { base.RegisteredEvents.Add(new Tuple >(40, "Create", "ses_student", new Action (ExecutePostStudentCreate))); // Note : you can register for more events here if this plugin is not specific to an individual entity and message combination. // You may also need to update your RegisterFile.crmregister plug-in registration file to reflect any change. } /// /// Executes the plug-in. /// /// For improved performance, Microsoft Dynamics CRM caches plug-in instances. /// The plug-in's Execute method should be written to be stateless as the constructor /// is not called for every invocation of the plug-in. Also, multiple system threads /// could execute the plug-in at the same time. All per invocation state information /// is stored in the context. This means that you should not use global variables in plug-ins. /// protected void ExecutePostStudentCreate(LocalPluginContext localContext) { if (localContext == null) { throw new ArgumentNullException("localContext"); } IPluginExecutionContext context = localContext.PluginExecutionContext; IOrganizationService service = localContext.OrganizationService; string entityname = "ses_student"; AutoNumberGenerate AutoGen = new AutoNumberGenerate(); if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity) { // Obtain the target entity from the input parmameters. Entity entity = (Entity)context.InputParameters["Target"]; string entityLogicalName = entity.LogicalName; if (entityLogicalName == entityname) { AutoGen.GenerateAutoNumber(context, service, entity, entityLogicalName); } } } } }
Each of the Entities that need autonumber need a event handler like this one. We autonumber tons of things like Alumni, Classes, etc, so we have many of these entries in this plugin. Anyhow, the meat of the situation is the Autonumber method, this is shown here:
using System; using System.ServiceModel; using System.ServiceModel.Description; using System.Collections.Generic; using System.Linq; using Microsoft.Xrm.Sdk; using System.Xml.Linq; using Microsoft.Crm.Sdk.Messages; using Microsoft.Xrm.Sdk.Query; using Microsoft.Xrm.Sdk.Client; namespace AutoGenerate { public class AutoNumberGenerate { public void GenerateAutoNumber(IPluginExecutionContext context, IOrganizationService service, Entity entity, string entityname) { // fetch the autonumber configuration try { // set up the query QueryByAttribute query = new QueryByAttribute("ses_autonumberconfiguration"); query.ColumnSet = new ColumnSet("ses_name", "ses_prefix", "ses_autonumberenabled", "ses_currentvalue", "ses_numberofdigits", "ses_startingvalue"); query.Attributes.AddRange("ses_name"); query.Values.AddRange(entityname); // get the data EntityCollection retrieved = service.RetrieveMultiple(query); string prefix = ""; string enabled = ""; int currentval = 0; int numdigits = 0; int startvalue = 0; // set up for the update of the current value after we are done with it Entity entityObj = new Entity("ses_autonumberconfiguration"); // pull out the autoconfiguration parms for this entity foreach (var c in retrieved.Entities) { entityObj.Id = (Guid)c.Attributes["ses_autonumberconfigurationid"]; prefix = (string)c.Attributes["ses_prefix"]; enabled = (string)c.Attributes["ses_autonumberenabled"]; currentval = (int)c.Attributes["ses_currentvalue"]; numdigits = (int)c.Attributes["ses_numberofdigits"]; startvalue = (int)c.Attributes["ses_startingvalue"]; } int value = Convert.ToInt32(currentval) + startvalue; // increment the current value currentval = currentval + 1; // use the id from the retrieved record to update the value entityObj.Attributes.Add("ses_currentvalue", currentval); service.Update(entityObj); // prepare the new id and update it string sformat = "{0:D" + numdigits + "}"; string newid = prefix + "-" + string.Format(sformat, value); entity.Attributes[entityname] = newid; service.Update(entity); } catch { return; } } } }
Hopefully the process is fairly obvious. After the record is created, control passes to the OnStudentCreate which then calls the AutoGenerate. The AutoGenerate fetches the autonumber record for that entity, extracts the counter, the prefix and the number of digits, uses those to construct a new student number and then writes that into the student record. A fairly easy algorithm and one that I could have done in five minutes in Ruby on Rails or PHP with MySQL, but Microsoft has its own way of doing things with CRM records. Anyhow, one last little part, some JScript, is needed on each form to put -autonumber- in the field and then gray it out so the student number can’t be entered by the user. The script for that follows, you just enter this as a web resource and then in the form properties call the method on formload:
function AutoNumber( fieldname ) { if (!Xrm.Page.getAttribute(fieldname).getValue()) { Xrm.Page.getAttribute(fieldname).setValue(""); } Xrm.Page.ui.controls.get(fieldname).setDisabled(true); }