The ability to clone much of the detail of the current record onto a new instance of that entity is a neat trick that can have lots of very useful applications. A couple of examples could be an opportunity renewal whereby you want to copy most of the original opportunity but maybe want to update a couple of dates to reflect the renewal; or where you want to duplicate a case instead of re-opening an old case, to track time spent more effectively.
There is both a coded and a codeless approach that can be utilised, each of which I will describe below, along with the benefits of each.
Codeless
The premise of the codeless approach is basically to use a self-referencing relationship and relationship mapping. The idea being that you would end up with a list of "child" entities on your parent entity and as child entities are added from the grid the relationship mappings would kick in and populate the data for you on the new child entity. This is the most basic form of cloning – but you can get a working model up and running in minutes. If the user experience is OK to your users then this is the way to go. Configurable and no C#/JavaScript in sight. The downside to this is the aforementioned user experience. Will users want to go to a grid and select "Add New Record"? Probably not. And also do you need some more "run-time" control over what values get set on the new entity? Relationship mapping is great but it is static, "Copy Field A from Parent Entity to Field A of Child Entity" and so on for each property to be mapped. There is no way to say "in a certain circumstance, actually don't just copy the field, but add some new data to it".
A detailed guide to this approach can be found here:
http://rc.crm.dynamics.com/RC/2011/en-us/online/5.1_CTP/cloneRecords.aspx
Coded
The coded approach offers some more flexibility, and involves adding a "Clone" button to your entity ribbon that calls a JavaScript method on your page. This method is great and a detailed write up is available here:
http://garethtuckercrm.com/2012/07/31/duplicate-record-button-using-jscript-in-microsoft-crm-2011/
However, this approach also has its drawbacks. We have now lost our link between the parent and child entities so we have lost that relationship (if we need that relationship of course). And we are now totally coded – there is no configuration available now, the person who wants to amend this function MUST be able to do a little JavaScript. There is also the fact that if you pass too many values to the new form you may end up going too long for a URL (OK you would have to pass a lot of values to get to this, but a few large notes fields would get you well on your way).
My solution to most of the drawbacks of each approach is to combine the two. So we use a self-referencing relationship and relationship mapping as described in the "Codeless" approach, but combine it with a "Clone" entity ribbon button and JavaScript function as described in the "Coded" approach. In order to do this we need to tweak the JavaScript function somewhat. What we are effectively doing is "fooling" CRM into thinking we have actually clicked the "Add New Record" from the related child entity grid ribbon, so we can inherit all of the benefits of the relationship mapping. At the same time we can also override a mapping value by passing the value to the opening form. So when the user clicks the button we can have some logic to say whether we want a particular value overridden, such as a type or a date field.
Here is the JavaScript to pass the parent entity to the new form in order to use field mapping:
parameters["_CreateFromId"] = Xrm.Page.data.entity.getId();
parameters["_CreateFromType"] = 3;
parameters["etc"] = 3;
The _CreateFromId parameter is the ID of the parent record (which is the current record ID). The _CreateFromType is the EntityTypeCode of the current entity. In this case 3 for opportunity. The Entity Type Code list can be found here http://msdn.microsoft.com/en-us/library/bb887791.aspx). Here is a post that describes how to get the Entity Type Code at runtime for a custom entity: http://mileyja.blogspot.co.uk/2011/05/how-to-retrieve-metadata-for-entity.html. The etc parameter is again the Entity Type Code (what the etc stands for). Here it is in the full JavaScript function:
function openChildWindow()
{
var parameters = {};
parameters["_CreateFromId"] = Xrm.Page.data.entity.getId();
parameters["_CreateFromType"] = 3;
parameters["etc"] = 3;
Xrm.Utility.openEntityForm("opportunity", null, parameters);
}
As mentioned before, we could override other values that would be handled by the relationship mapping simply by setting them here (as defined in the "Coded" approach above):
parameters["name"] = "NEW NAME";
That would set the "name" property of the new cloned entity to "NEW NAME" regardless of the relationship mapping put in place.
One last thing to mention – you need to place the self-referential lookup on the form somewhere, and hide it.
That's about it for this one.
The above approaches will achieve cloning of entity level fields only; it will not be able to clone one to many records, such as products on an opportunity. If you need to do this then a plugin would be required to fire on create of the entity. From the plugin you could use the entity populated in the self-referential lookup to obtain all the one to many records and add them into the new entity.
Cheers,
Nick
A technical / functional blog based mainly around Microsoft Dynamics CRM, with a little bit of ASP.NET/C# thrown in for good measure.
Thursday, 14 February 2013
Monday, 11 February 2013
Connecting CRMSVCUTIL/External ASP.NET Application and CRM Online
The connection properties differ somewhat between connecting
to CRM “On Premise” and CRM Online. The extra parameters required for CRM
Online make up the difference, these being “Device ID” and “Device Password”.
These are required as an extra security precaution for the online web services.
So how do you get them? In order to get your very own device id and password
you use the device registration executable in the SDK_Directory\Tools\deviceregistration\bin\debug
directory (you will need to download the Dynamics CRM SDK and install it on
your machine somewhere). You will need to open the deviceregistration Visual C#
project and build it once in order to see the executable. Once this is done run
the Device Registration exe from the command line, with the following
parameters:
deviceregistration.exe
/Operation:Register /Name:"123456789101" /Password:"123456789101"
The name and password are OPTIONAL. Here I am telling the
device registration tool that I want my device name (ID) and password to
specifically be this. If you leave them out it will generate a new device
id/password automatically for you. Once it gives you these values these are
what go into you connection string for your application. An example of this in a “CRM Online” connection string for an ASP.NET
application:
<add name="Conn" connectionString="Url=https://CRM_ONLINE_URL;
UserName=CRM_LOGIN(WLID/O365 ID); Password=XXXXX; Device ID=123456789101; Device Password=123456789101"/>
You do not however need the Device ID and Device Password for using CRMSVUTIL.
TROUBLESHOOTING
With regard to troubleshooting two issues I had and couldn’t find
a resolution for online was an authentication issue: on CRMSVCUTIL the message
was “Authentication Failure”: in this instance I had accidentally included the Device ID and Device Password parameters, and it doesn't like that. The other issue was a message from the ASP.NET website of “The
device authentication failed!”. The second message is clearer as to what it has
a problem with. At this point try running the Device Registration tool again
with the Device ID/Password that you would like to use, and if you get a
message along the lines of “device id already registered”, then this side of
things is correct – your device is correctly registered. The issue in my case
was the server time was out of sync with the CRM Online server. Mine was a full
10 minutes out, so setting it correctly and re-running the website
resolved the issue.
Another issue I have seen is an incorrect locally stored
device id profile. When you run the Device Registration tool it places an XML
file with encrypted values in:
C:\%USERPROFILE%\LiveDeviceID
Delete the LiveDeviceID.xml file in here and re-run Device
Registration, as per the instructions above.
Thanks,
Nick
Thursday, 7 February 2013
ASP.NET Charts and CRM 2011 Using FetchXML
I was recently tasked with implementing some “dashboard”
functionality within the standard Dynamics CRM 2011 Customer Portal to allow
clients to see an overview of current cases, using various metrics. After
thinking through the possibilities (Silverlight, JQuery, 3rd party
controls) I decided to utilise ASP.NET Charts as a) they have been greatly
improved in the latest versions of the .NET Framework and b) well, the standard
dashboard in CRM uses them so they can’t be that bad! The initial plan was to
have an ASP.NET Chart with a LINQ data source (pointing at CRM) however, due to
some limitation in CRM for LINQ I had to resort to using FetchXML. The problem was
around the “groupby” LINQ method not being supported by the CRM for LINQ
libraries (at least not at the time of writing this). FetchXML however does have
aggregate behaviour, so was an obvious backup plan.
Now, if you know ASP.NET and its data bound controls you
will probably think there is no way you could have an EntityCollection (the
result of performing a FetchXML operation) as a DataSource for an ASP:Chart (or
ASP:Gridview for that matter). Well you would be right. The trick is to convert
the resulting EntityCollection into a standard .NET Datatable, and then set
that as the DataSource for the chart.
To summarise, this is the requirement: a pie chart in the
Customer Portal showing the client an overview and summary of their current
active cases, grouped by status (statuscode). There are more complex graphs and
metrics required for the overall dashboard, but I have chosen the simplest one
to demonstrate as part of this post.
The process for achieving this is as follows.
1. Add a chart to your aspx
page (from the ToolBox), set its type to pie. If you are using Visual Studio
2010 or greater the chart controls will already be right there in the Toolbox:
If you are using an older version you may need to download
and install the .NET Framework Chart Controls add on.
The mark up for the pie chart should be:
<asp:Chart ID="ChartActiveCasesByStatus" runat="server" Width="460px" >
<Series>
<asp:Series Name="Cases"
YValueMembers="incidentcount"
IsValueShownAsLabel="true"
XValueMember="status"
IsVisibleInLegend="true"
ChartType="Pie">
</asp:Series>
</Series>
<ChartAreas>
<asp:ChartArea Name="ChartArea1"
Area3DStyle-Enable3D="false">
<AxisX
LineColor="DarkGray">
<MajorGrid LineColor="LightGray"
/>
</AxisX>
<AxisY
LineColor="DarkGray">
<MajorGrid LineColor="LightGray"
/>
</AxisY>
<Area3DStyle Enable3D="false"></Area3DStyle>
<InnerPlotPosition Height="100" Width="100" />
</asp:ChartArea>
</ChartAreas>
<Legends>
<asp:Legend Title="Status">
</asp:Legend>
</Legends>
</asp:Chart>
All of the elements within this mark-up can be tailored to
suit the look and feel you require. This is just the standard look with a
couple of minor tweaks to add a title to the legend, and also set the “InnerPlotPosition”
to have the pie chart more central within the control.
2. Once the chart is in place the next thing that is required is the
code to get the data we need from CRM in the right format, converted to a
DataTable, and set as the DataSource for the chart (and then a DataBind() call on
the chart of course). The code is remarkably simple:
string casesByStatus = @"
<fetch distinct='false'
mapping='logical' aggregate='true'>
<entity name='incident'>
<attribute name='incidentid'
alias='incidentcount' aggregate='count'/>
<attribute name='statuscode'
groupby='true' alias='status' />
<filter type='and'>
<condition
attribute='customerid' operator='eq' value='{" +
Contact.ParentCustomerId.Id.ToString() + "}'
/>" +
"<condition attribute='statecode'
operator='eq' value='0' />" +
"</filter> " +
"</entity> " +
"</fetch>";
EntityCollection quotes_result =
ServiceContext.RetrieveMultiple(new FetchExpression(casesByStatus));
ChartActiveCasesByStatus.DataSource = ConvertToDataTable(quotes_result);
ChartActiveCasesByStatus.DataBind();
We have our FetchXML string, retrieving all the active cases
(incidents) for the logged in client, a count of them, grouped by statuscode.
The ConvertToDataTable(quotes_result) is where we call our
method to convert our EntityCollection into a DataTable ready to be a valid
DataSource for our chart. The code for this:
private static DataTable ConvertToDataTable(EntityCollection entityCollection)
{
DataTable dt = new
DataTable();
int total = entityCollection.Entities.Count;
for (int i = 0; i
< total; i++)
{
DataRow row = dt.NewRow();
Entity myEntity = (Entity)entityCollection.Entities[i];
var keys = myEntity.Attributes.Keys;
foreach (var
key in keys)
{
if (key.EndsWith("id"))
continue;
string columnName = key;
string value =
GetValuefromAttribute(myEntity.Attributes[key], myEntity.FormattedValues.FirstOrDefault(y
=> y.Key == key));
if (dt.Columns.IndexOf(columnName) ==
-1)
{
dt.Columns.Add(key, Type.GetType("System.String"));
}
row[columnName] = value;
}
dt.Rows.Add(row);
}
return dt;
}
private static string GetValuefromAttribute(object
unformattedAttribute, KeyValuePair<string, string> formattedAttribute)
{
if (formattedAttribute.Value == null)
return
GetValueFromCRMTypeAttribute(unformattedAttribute);
else
{
return formattedAttribute.Value;
}
}
private static string GetValueFromCRMTypeAttribute(object p)
{
if (p.ToString() == "Microsoft.Xrm.Sdk.OptionSetValue")
{
return ((OptionSetValue)p).Value.ToString();
}
if
(p.ToString() == "Microsoft.Xrm.Sdk.Money")
{
return ((Money)p).Value.ToString();
}
if (p.ToString() == "Microsoft.Xrm.Sdk.EntityReference")
{
return ((Microsoft.Xrm.Sdk.EntityReference)p).Name;
}
if (p.ToString() == "Microsoft.Xrm.Sdk.AliasedValue")
{
return
GetValueFromCRMTypeAttribute(((Microsoft.Xrm.Sdk.AliasedValue)p).Value);
}
else
{
return p.ToString();
}
}
This code goes through every Entity in the EntityCollection
and generates a new row for each. Then a calculation is performed on each CRM
data type within the entity to retrieve the value as a string and put it into
a Column of the DataRow. The code was adapted to CRM
2011 from an existing forum post that showed how to do this in CRM 4.0 (http://www.codeproject.com/Articles/42334/Convert-BusinessEntityCollection-to-a-DataTable
)
That’s it. This is what it looks like in the portal:
Pretty basic with regard to presentation, but this is where
you can play with that mark-up, get the colour scheme you want, make it 3d etc.
etc.
A fairly painless introduction to ASP.NET charts and CRM,
but things do get a little hairier with line charts with multiple series &
categories….maybe I will save that for another post.
Cheers,
Nick
Subscribe to:
Posts (Atom)