Pogo69's Blog

January 9, 2013

Converting Queries/Charts/Dashboards from User to System

Filed under: C#, CRM, Cutting Code — pogo69 [Pat Janes] @ 09:48

Overview

If you have created your own Saved Queries, Charts or Dashboards, you may have had occasion to share those objects with other CRM Users.  If you find that they become universally reusable to the point of wanting to “promote” them to System objects, there is no mechanism in the CRM Web Application to do so.

Code to the Rescue

You can however, convert such items using some relatively simple C# (or VB if you must) code.

Charts

// chart
var chart =
  (
    from uqv in xrm.UserQueryVisualizationSet
    where uqv.Name == "Pogo's Chart"
    select uqv
  ).FirstOrDefault();

Xrm2011.SavedQueryVisualization newChart = new Xrm2011.SavedQueryVisualization
{
  Name = chart.Name,
  DataDescription = chart.DataDescription,
  Description = chart.Description,
  IsCustomizable = new BooleanManagedProperty(true),
  IsDefault = false,
  IsManaged = false,
  PresentationDescription = chart.PresentationDescription,
  PrimaryEntityTypeCode = chart.PrimaryEntityTypeCode,
  WebResourceId = chart.WebResourceId
};

xrm.Create(newChart);

Views

// view
var view =
  (
    from uq in xrm.UserQuerySet
    where uq.Name == "Pogo's View"
    select uq
  ).FirstOrDefault();

Xrm2011.SavedQuery newView = new Xrm2011.SavedQuery
{
  Name = view.Name,
  AdvancedGroupBy = view.AdvancedGroupBy,
  CanBeDeleted = new BooleanManagedProperty(true),
  ColumnSetXml = view.ColumnSetXml,
  ConditionalFormatting = view.ConditionalFormatting,
  Description = view.Description,
  FetchXml = view.FetchXml,
  IsCustomizable = new BooleanManagedProperty(true),
  IsDefault = false,
  IsManaged = false,
  LayoutXml = view.LayoutXml,
  QueryType = view.QueryType,
  ReturnedTypeCode = view.ReturnedTypeCode
};

xrm.Create(newView);

Dashboards

// dashboard
var dashboard =
  (
    from d in xrm.UserFormSet
    where d.Name == "Pogo's Dashboard"
    select d
  ).FirstOrDefault();

Xrm2011.SystemForm newDashboard = new Xrm2011.SystemForm
{
  Name = dashboard.Name,
  CanBeDeleted = new BooleanManagedProperty(true),
  Description = dashboard.Description,
  FormXml = dashboard.FormXml,
  IsCustomizable = new BooleanManagedProperty(true),
  IsDefault = false,
  IsManaged = false,
  ObjectTypeCode = dashboard.ObjectTypeCode,
  Type = dashboard.Type
};

newDashboard.Id = xrm.Create(newDashboard);
PublishXmlRequest reqPublish = new PublishXmlRequest
{
  ParameterXml = string.Format(
    @"
      <importexportxml>
        <dashboards>
          <dashboard>{0}</dashboard>
        </dashboards>
      </importexportxml>",
    newDashboard.Id)
};
PublishXmlResponse respPublish = (PublishXmlResponse)xrm.Execute(reqPublish);

I had issues with privileges on the newly migrated Dashboard the first time I tried it.  The addition of the the PublishXmlRequest fixed it.  I had no such issue with the Views or Charts.

Reference Implementation

You can download a reference implementation of the code described above from the following:

http://sdrv.ms/W1z1Ra

It contains a CRM 2011 Managed Solution that allows Users to migrate User objects (Views, Charts, Dashboards) to System equivalents.  I am in the process of uploading the source code to Codeplex; upon completion you will be able to download and modify as you see fit.

NB: The current implementation will not migrate User Dashboards that contain User components.  A future version will migrate embedded components in addition to the Dashboard that houses them.

Update

You can now download the Managed Solution and/or all source code from the following Codeplex site:

http://userobjectmigration.codeplex.com

Advertisements

January 6, 2013

Using PreEntityImages and PostEntityImages in Custom Workflow Activities

Filed under: Cutting Code — pogo69 [Pat Janes] @ 08:03

Overview

As with many of my blog postings, the inspiration for this post originated with a discussion on the Dynamics CRM Forums.  In the following forum posting, Mostafa El Moatassem bellah asked how to use PreEntityImages and/or PostEntityImages within a Custom Workflow Activity to obtain the values of attributes before/after the operation that triggered a Workflow:

http://social.microsoft.com/Forums/is/crmdevelopment/thread/bd40aecb-c0a1-4e50-89bf-8cb2a11a67b5

My initial response (and understanding) was that you are unable to do so.  Images (PreEntity and PostEntity) are registered against Steps, which are in turn, registered against Plugins.  No such facility exists for Custom Workflow Activities as you cannot register Steps.  The “triggers” for Workflows are instead controlled via the Workflow customisation UI.

How the CRM Implements Images in Custom Workflow Activities

After writing a short diagnostic Custom Workflow Activity, I was able to verify that the CRM utilises PreEntityImages and PostEntityImages internally to store copies of the Entity attributes before and after the operation that triggered the Workflow.

NB: While the findings in this blog post are verifiable and repeatable, it is NOT documented in the SDK and is therefore considered to be unsupported and subject to change at any time.  Use at your own risk.

Accessing the Image Collections

// attach note to primary entity instance
Entity note = new Entity("annotation");
note.Attributes.Add("objectid", new EntityReference(context.PrimaryEntityName, context.PrimaryEntityId));
note.Attributes.Add("objecttypecode", context.PrimaryEntityName);

StringBuilder sb = new StringBuilder();
sb.AppendFormat("********************************************\r\n");
sb.AppendFormat("PreEntityImages\r\n");
sb.AppendFormat("--------------------------------------------\r\n");
foreach (string imageName in context.PreEntityImages.Keys)
{
  sb.AppendFormat("[{0}]\r\n", imageName);

  Entity image = (Entity)context.PreEntityImages[imageName];

  foreach (string attributeName in image.Attributes.Keys)
  {
    sb.AppendFormat("- {0}\r\n", attributeName);
  }
}
sb.AppendFormat("********************************************\r\n");
sb.AppendFormat("PostEntityImages\r\n");
sb.AppendFormat("--------------------------------------------\r\n");
foreach (string imageName in context.PostEntityImages.Keys)
{
  sb.AppendFormat("[{0}]\r\n", imageName);

  Entity image = (Entity)context.PostEntityImages[imageName];

  foreach (string attributeName in image.Attributes.Keys)
  {
    sb.AppendFormat("- {0}\r\n", attributeName);
  }
}

note.Attributes.Add("notetext", sb.ToString());
service.Create(note);

In effect, the code creates a Note and attaches it to the Primary Entity for which the Workflow was triggered.  The Note text contains a list of every PreEntityImage and PostEntityImage with the names of their respective attributes.

Image Collection Names

I constructed a simple Workflow on the Account entity and triggered an update with the following results:

********************************************
PreEntityImages
——————————————–
[account]
– accountclassificationcode
– accountid

[PreBusinessEntity]
– accountclassificationcode
– accountid

********************************************
PostEntityImages
——————————————–
[account]
– accountclassificationcode
– accountid

[PostBusinessEntity]
– accountclassificationcode
– accountid

Thus, the PreEntityImages collection contains 2 Entity Images:

  1. The first shares the Logical Entity name of the Primary Entity
  2. The second is called PreBusinessEntity

And the PostEntityImages collection contains 2 Entity Images:

  1. The first shares the Logical Entity name of the Primary Entity
  2. The second is called PostBusinessEntity

Both PreEntity Images appear to contain the same data; likewise for the PostEntity Images.

Image Content

Update Trigger

As with the Images supplied via the Plugin architecture and/or via the Target Entity in Update Plugin operations; the Pre/Post Entity Images described above contain values in the Attributes collection only for attributes that contain values in the CRM.  For example; if the Account email address is missing prior to the Update, the emailaddress1 attribute will exist only in the PostEntity Images.

Create Trigger

If a Workflow is triggered by a Create operation, only the PostEntity Images are created (PreEntityImages collection is empty).

On Demand Trigger

If a Workflow is triggered On Demand, only the PreEntity Images are created (PostEntityImages collection is empty).

Conclusion

As per my warning above, the usage of PreEntity and PostEntity Images in Custom Workflow Activities is undocumented and as such, unsupported.

It appears to provide a convenient mechanism to access the values of an Entity instance before and after the operation that triggered the Workflow.  However, its exclusion from the SDK means that the implementation could change at any time, causing code based on anecdotal assumption such as these to fail.

Thanks to Mostafa for the inspiration.

Create a free website or blog at WordPress.com.