OnSave Mode List
In the past I have had to explain to people that because you are filling in a seperate (non-customisable) form to mark an oppertunity as won then there is no way to tell what is happening to the opp as it is saved.However while searching for something else I came across this article that details a very neat work around and one that I will be using in the future.
Adding a Value to a Picklist
I have had cause to add values programmatically to a picklist in the contact entity. It is a custom picklist whose values change depending on actions on another entity. I struggled to pull together all the elements that I needed for this solution from areas on the net so I thought I would post this in case anyone faces a similar problem.For a new picklist item you need a string to appear in the dropdown box and an integer that represents the choice in the entity.
This is the code I used:
public void addCourse(string course)
{
if (!inPicklist(course))
{
InsertOptionValueRequest request;
int lastValue = 0;
Option newCourse = new Option();
LocLabel label = new LocLabel();
newCourse.Label = new CrmLabel();
newCourse.Value = new CrmNumber();
foreach (Option o in _picklist.Options)
{
int thisValue = o.Value.Value;
if (thisValue > lastValue) lastValue = thisValue;
}
newCourse.Value.Value = lastValue + 1;
label.Label = course;
newCourse.Label.LocLabels = new LocLabel[] { label };
newCourse.Label.LocLabels[0].LanguageCode = new CrmNumber();
newCourse.Label.LocLabels[0].LanguageCode.Value = 1033;
request = new InsertOptionValueRequest();
request.EntityLogicalName = ENTITYLOGICALNAME;
request.AttributeLogicalName = LOGICALNAME;
request.Label = newCourse.Label;
request.Value = newCourse.Value;
InsertOptionValueResponse response =
(InsertOptionValueResponse)_metadataService.Execute(request);
publishChange();
}
}
The important thing to note is that you cannot edit the picklist directly. It is a concept I forget and struggle with sometimes when working with metadata that it exists in both a published and unpublished state. We use the InsertOptionValueRequest method to alter the picklist, adding an unpublished option to the list. If it were just to stay at that then the value would not appear until the next time someone published the entity, although you could see the value in the entity design screens. For clarity my two constants above that are declared elsewhere in the code are:
ENTITYLOGICALNAME = "contact";
LOGICALNAME="new_course"; //the list I want to change
The very last line of my codeblock then forces the publication of entity.
private void publishChange()
{
CrmService.PublishXmlRequest request =
new CrmService.PublishXmlRequest();
request.ParameterXml = @"<importexportxml>
<entities>
<entity>contact</entity>
</entities>
<nodes>
<securityroles>
<settings>
<workflows>
</importexportxml>";
CrmService.PublishXmlResponse response =
(CrmService.PublishXmlResponse)_crmService.Execute(request);
}
For performance I had put this code into a DLL in the GAC (it contains a library of actions I wanted in a single place) and is called by asynchronous process in a plugin. Having said that the action that fires it will be infrequent and may not actually happen once the system is provisioned and goes live. But either way I hope that I have mitigated any lag when the user clicks the Save button.
Beginning Plugin Development - Part 1
Eventually we will need to code some plugins. If you want to do anything with this platform that is worth anything a plugin will be required more often than not.
I hope to cover off some basic concepts of plugin development and give some hints to how to complete different problems.
Pre-requisites for this will be:
- Visual Studio 2008
- CRM 4.0
- MSCRM SDK
- and an operational Plugin Registration (comes with the SDK) . Good guide here
I don't intend to go over creating a plugin from scratch, I use a VS template that builds the basic plugin library for me. Lazy I know but worth it. The templates are also in the SDK.
In this article I will cover off a simple requirement I have had. I need to relate a single entity to my contact entity and display it in an IFRAME. The IRFAME code is very simple and takes the custom entity object ID from a field in the contact record and uses that to build the frame URL.
So my steps are:
- Create a new contact record
- Create the related entity
- Write the related entity ID to the contact record
The first step is simple enough. When a new contact record (in my example representing a student) is created the plugin is fired. I want this to be on the post-create event so that I can be sure that the student has passed all other validation before I create the finance record for them. This is achieved by registering the plugin as post stage thus
Of course we need some code to write first. We are going to get the contactid of the record, create a new custom entity type and save that to the CRM system.
ICrmService crmService = context.CreateCrmService(true);
DynamicEntity StudentDetails =
(DynamicEntity)context.PostEntityImages["StudentDetails"];
DynamicEntity
workingStudent = GetBlankEntity("contact", "contactid",
((Key)StudentDetails.Properties["contactid"]).Value);
//Create a
finance account against the student
DynamicEntity finance =
provisionFinanceAccount(((Key)StudentDetails.Properties["contactid"]).Value);
finance.Properties.Add(new
StringProperty("xxx_name",StudentDetails.Properties["fullname"].ToString()));
Guid financeKey = crmService.Create(finance);
//Write finance
account ID back to student record
workingStudent.Properties.Add(new
StringProperty("xxx_financeaccount",financeKey.ToString()));
crmService.Update(workingStudent);
So the first thing to cover off is the PostEntityImages call on the second line. This allows us to see what data we want in the final record. The registration tool gives us a really neat little window on this via the GUI. We register an image to the step (in this case I have called it "StudentDetails") and you can add or remove whatever fields you get. By default when you register an image it gives you all columns. Best practice is to remove everything and then explicitly add in what you need. In my case I want the contactid and the fullname fields
Then from the code
(DynamicEntity)context.PostEntityImages["StudentDetails"];
We can access the image we have created in the registration tool by simply passing the name to the collection of PostEntityImages casting it to DynamicEntity on the way back.
Second "Best Practice" element is how you interact with the contact entity. By default mine passes 28 fields to the plugin. If I were to add a field to this collection and pass it back for update that is a huge network overhead that on production environments could start to grind things down. In this case I have a method that takes in the entity type, the primary key name and value and returns a blank DynamicEntity for you to work with. The source was taken from Andrew Zimmer's blog on best practice.
Now I can add the field to store the finance object's ID and send that back to the service, I have reduced 28 fields to 3.private static DynamicEntity GetBlankEntity(string entityName,
string primaryKeyAttribute, Guid id)
{
DynamicEntity entity = new DynamicEntity(entityName);
entity.Properties[primaryKeyAttribute] = new Key(id);
return entity;
}
Finally the whole point of the plugin. To create the finance object.
This returns a DynamicEntity with the basiscs of what I need, the object type and a lookup to the contact record set. Once this has been created I add the contact.fullname into the custom entity (for seperate views I will use this to indicate what student it belongs to but will expand to include student number as the system develops). Once this is assigned we can create the finance object.private DynamicEntity provisionFinanceAccount(Guid contactid)
{
DynamicEntity finance = new DynamicEntity();
finance.Properties =
new PropertyCollection();
finance.Name = "xxx_financeaccount";
Lookup student = new Lookup();
student.type =
EntityName.contact.ToString();
student.Value = contactid;
finance.Properties.Add(new LookupProperty("xxx_ownerid", student));
return finance;
}
Guid financeKey = crmService.Create(finance);
The crmService.Create method returns the GUID of the object once it is created. I need this to link the IFRAME on my form so I am using this in a second step.
workingStudent.Properties.Add(new StringProperty("xxx_financeaccount",financeKey.ToString()));
crmService.Update(workingStudent);
As this workingStudent object already has the primary key set calling the updated takes the new property and writes it back to the database without disturbing any other data.
And that is that. Following the creation of a student record my system automatically creates a finance account, storing the finance id back in the student record where I use it in an IFRAME to show the single entity with further sub-forms.
All the code discussed here:
public void Execute(IPluginExecutionContext context)
{
DynamicEntity entity = null;
// Check if the InputParameters property bag contains a target
// of the current operation and that target is of type DynamicEntity.
if (context.InputParameters.Properties.Contains(ParameterName.Target) &&
context.InputParameters.Properties[ParameterName.Target] is DynamicEntity)
{
// Obtain the target business entity from the input parmameters.
entity = (DynamicEntity)context.InputParameters.Properties[ParameterName.Target];
if (entity.Name != EntityName.contact.ToString())
{
return;
}
}
else
{
return;
}
try
{
ICrmService crmService = context.CreateCrmService(true);
DynamicEntity StudentDetails = (DynamicEntity)context.PostEntityImages["StudentDetails"];
DynamicEntity workingStudent = GetBlankEntity("contact", "contactid", ((Key)StudentDetails.Properties["contactid"]).Value);
//Create a finance account against the student
DynamicEntity finance = provisionFinanceAccount(((Key)StudentDetails.Properties["contactid"]).Value);
finance.Properties.Add(new StringProperty("kcb_name",StudentDetails.Properties["fullname"].ToString()));
Guid financeKey = crmService.Create(finance);
//Write finance account ID back to student record
workingStudent.Properties.Add(new StringProperty("kcb_financeaccount",financeKey.ToString()));
crmService.Update(workingStudent);
}
catch (System.Web.Services.Protocols.SoapException ex)
{
throw new InvalidPluginExecutionException(
String.Format("An error occurred in the {0} plug-in.",
this.GetType().ToString()),
ex);
}
}
private DynamicEntity provisionFinanceAccount(Guid contactid)
{
DynamicEntity finance = new DynamicEntity();
finance.Properties = new PropertyCollection();
finance.Name = "kcb_financeaccount";
Lookup student = new Lookup();
student.type = EntityName.contact.ToString();
student.Value = contactid;
finance.Properties.Add(new LookupProperty("kcb_ownerid", student));
//Need to set the initial account balances to 0 here.
return finance;
}
///
/// Returns a clone of the required entity but with only the primary key. You can then add
/// the columns you are changing. Keeps network traffic down as only those columns being changed
/// are sent back to the service.
///
///
///
///
///
private static DynamicEntity GetBlankEntity(string entityName, string primaryKeyAttribute, Guid id)
{
DynamicEntity entity = new DynamicEntity(entityName);
entity.Properties[primaryKeyAttribute] = new Key(id);
return entity;
}
Showing a custom entity in an IFRAME
I have had cause to enforce a 1:1 relationship between my main entity (modification of the Contact entity) and a custom one. There are several ways that this can happen but I decided the way I was going to do this was to remove the user's ability to create the object and use a plugin instead.On creation of the Contact entity a single custom entity instance is created, it's object ID from creation is then written back into a field on the Contact entity. I then use an IFRAME to display the entity itself (bypassing the related entity list) thus:
iFinance.src = "../../userdefined/edit.aspx?id={90450BF2-BF0C-DF11-9A07-0003FF5808D8}&etc=10011#";
The ID value will be taken from the field in production but for development purposes I have hard coded a single record in the form at the moment.
You can use this to show any custom entity, to double check just navigate to the entity you want and then check the IIS logs for the request made.
But once you have that address you see a form inside a form and that is not ideal, especially for screen layout and space. So you need to make a few alterations as well. I found this code out there but have lost the citation (anyone recognise it then drop me a line and I will add the link):
iFinance.attachEvent( "onreadystatechange" , financeReady);
function financeReady()
{
var iDoc = iFinance.contentWindow.document;
if(iDoc.getElementById("crmMenuBar") != null)// && iDoc.getElementById("btnBack") != null)
{
iDoc.getElementById("crmMenuBar").removeNode(true); // hide the top menu bar
iDoc.getElementById("leftNavBreadcrumbText").removeNode(true);
iDoc.getElementById("leftNavBreadcrumbImg").removeNode(true);
iDoc.getElementById("tdRelatedInformationPane").removeNode(true);
var imgs = iDoc.getElementsByTagName('img');
for (var i=0;i[less than]imgs.length;i++) {
var html = imgs[i].outerHTML;
if (html.indexOf('LargeEntityIcon')!=-1) {
imgs[i].parentNode.innerHTML=""; //Delete the large icon on the top left
}
}
This results in almost all the screen apart from the tabs being removed and the tabs expand left to fill out the space. Unfortunately I have yet to figure out how to get rid of the space to the top of that screen (where the large icon lives) so the form looks a little squashed on the page.
Of course you also have to remove the node on the left navigation pane of the contact record so that the user cannot navigate to the entity relation list on their own.
Spawning instances of templates
My new project involves the interesting challenge of creating a set of entities based on a set of templates. Both are MSCRM objects (so one is called xxxxxTemplate and the other is just xxxxx).I create a new object that will contain all the other ones. This will kick off 1..n creations of a first child object and this object will then kick off the creation of probably about 10 child objects of its own. Each object created would take some basic info from the templates (usually just a name and one or two other pieces).
Thinking that using a plugin to control this action would be best (one on the top level creation and one on the creation of the first level child objects to create the second level). Does anyone have a better solution? Just a thought - I am a week or two away from beginning that part of the project so if anyone has any ideas please feel free to email me.
Virtual Machine Development Environments
I have just completed the rather long task of putting together a self contained development environment.Starting with a Windows 2003 base image I installed SQL 2005 (had intended to put 2008 on as well but the machine was not of my making originally so ran out of space), CRM 4.0, VS2008 and the CRM SDK. Once my next project is up and running with it I will also look at putting the Analytics accelerator onto the base reusable image.
I suppose my main difficulty was not with the technology but the platform. Taking a pre-installed domain controller with Exchange was a step up but found that I had to take SQL off and re-install to my own preference (while adding SSRS/SSAS as well). But mainly I struggled against the VM. Capturing ISO images of VS that it said was corrupt one minute but fine the next, occasionally dropping the USB connection to the external HDD where the image is located and loosing work. Still - at least I have had a lifetime of practice at installing CRM now!!!!
And back again
Wow, this is weird to be blogging again.Well, I moved jobs because the last one was not selling CRM anymore and I was left with just the "hobby" of the internal system to look after.
The people I have moved to have immediately landed me with a student record system to be built on CRM 4.0 so have spend the first few days since Christmas working out entity relationship diagrams.
I will post more on my design considerations as I get through them.