Impersonation in CRM Plug-In and Workflow – the easy way

Some time ago I wrote a post about impersonation in dynamics crm plug-ins.

Today I found a much easier solution: use the impersonated organizationService from localcontext.

IOrganizationService service = localContext.OrganizationServiceImpersonated;

Ok, first you have to create this property. Do this in Plugin.cs, which gets generated by Visual Studio. Create the property:

internal IOrganizationService OrganizationServiceImpersonated
{
    get;
    private set;
}

Add the following line to the constructor of LocalPluginContext:

// Use the factory to generate the impersonated Organization Service.
this.OrganizationServiceImpersonated = factory.CreateOrganizationService(null);

Instead of null you can also pass a systemusers GUID. Null will impersonate as systemadministrator.

And when you are just there, also set the ServiceProvider correctly, so that it’s not null. Then the whole constructor looks like this:

internal LocalPluginContext(IServiceProvider serviceProvider)
{
    if (serviceProvider == null)
    {
        throw new ArgumentNullException("serviceProvider");
    }

    // 1. extra line: Set LocalContext ServiceProvider
    this.ServiceProvider = serviceProvider;

    // Obtain the execution context service from the service provider.
    this.PluginExecutionContext = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));

    // Obtain the tracing service from the service provider.
    this.TracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));

    // Obtain the Organization Service factory service from the service provider
    IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));

    // Use the factory to generate the Organization Service.
    this.OrganizationService = factory.CreateOrganizationService(this.PluginExecutionContext.UserId);

    // 2. extra line: Use the factory to generate the impersonated Organization Service.
    this.OrganizationServiceImpersonated = factory.CreateOrganizationService(null);
}

To get impersonated service in Workflows is a little easier:

var serviceFactory = executionContext.GetExtension<IOrganizationServiceFactory>();
var service = serviceFactory.CreateOrganizationService(null);      //Create impersonated service

Create records in CRM 2011 using the CRM Web Service

When I began to create records in CRM using the webservice, I started with records containing simple text fields. And there was no problem. The following method creates a new contact entity in CRM:

public static Guid createCandidate(OrganizationServiceClient serviceProxy, string firstname, string lastname, string email)
{

Entity application = new Entity() { LogicalName = "contact" };

// Set Contact Properties
AttributeCollection Attributes = new AttributeCollection();

Attributes.Add(new KeyValuePair<string, object>("firstname", firstname));
Attributes.Add(new KeyValuePair<string, object>("lastname", lastname));
Attributes.Add(new KeyValuePair<string, object>("emailaddress2", email));

application.Attributes = Attributes;

//Create Contact
Guid appGuid = serviceProxy.Create(application);

return appGuid;
}

But then I also wanted to fill OptionSet fields (DropDowns) and ReferenceEntity fields (LookUp). But the Webservice always threw an exception:

Die InnerException-Nachricht war “Der Typ “*.OptionSetValue” mit dem Datenvertragsnamen “OptionSetValue:http://schemas.microsoft.com/xrm/2011/Contracts” wurde nicht erwartet. Fügen Sie alle statisch nicht bekannten Typen der Liste der bekannten Typen hinzu. Verwenden Sie dazu z. B. das Attribut “KnownTypeAttribute”, oder fügen Sie die Typen der an DataContractSerializer übergebenen Liste von bekannten Typen hinzu.”.

After a long time I found the solution here:

1. Create a new class in your project with name you prefer.

2. Keep the namespace of this class similar to your Reference.cs (This is important so do not forget it)

3. Now create partial classes as below

[System.Runtime.Serialization.KnownTypeAttribute(typeof(OptionSetValue))]
[System.Runtime.Serialization.KnownTypeAttribute(typeof(EntityReference))]
public partial class Entity { }

[System.Runtime.Serialization.KnownTypeAttribute(typeof(OptionSetValue))]
[System.Runtime.Serialization.KnownTypeAttribute(typeof(EntityReference))]
public partial class EntityCollection { }

[System.Runtime.Serialization.KnownTypeAttribute(typeof(EntityReference))]
[System.Runtime.Serialization.KnownTypeAttribute(typeof(PrincipalAccess))]
[System.Runtime.Serialization.KnownTypeAttribute(typeof(OptionSetValue))]
public partial class OrganizationRequest { }

4. Now go to your actual code, and for lookup field use like this:

Attributes.Add(new KeyValuePair<string,object>("parentcustomerid", new EntityReference() {Id = t.Id, LogicalName= t.LogicalName}));

5. Build the solution

Now everything should work as expectedt!

Impersonation of Service Executions in Plug-Ins and Workflows

A service execution can be impersonated by instantiating a ServiceProxy an setting the CallerId.
The Plug-In (and maybe Workflow) has to be registered as full trust (IsolationMode=”None”) to use the DefaultNetworkCredentials, otherwise they will be null. Set the isolationmode in registerfile.
If you use Early Binding, don’t forget to enable proxy types by calling serviceProxy.EnableProxyTypes().

protected void ExecutePreAccountUpdate(LocalPluginContext localContext)
{
    if (localContext == null)
    {
        throw new ArgumentNullException("localContext");
    }

    IPluginExecutionContext context = localContext.PluginExecutionContext;

    var clientCredentials = new ClientCredentials();
    // Using custom credentials
    credentials.Windows.ClientCredential = new NetworkCredential(, , );

    // Using Default Credentials
    // credentials.Windows.ClientCredential = CredentialCache.DefaultNetworkCredentials;

    var orgUrl = "http:///XRMServices/2011/Organization.svc";
    var organizationServiceUri = new Uri(orgUrl);

    // To use SSL
    if (!string.IsNullOrEmpty(orgUrl) && orgUrl.Contains("https")) {
	ServicePointManager.ServerCertificateValidationCallback = delegate(object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; };
    }

    using (var serviceProxy = new OrganizationServiceProxy(organizationServiceUri, null, clientCredentials, null))
    {
        serviceProxy.EnableProxyTypes();
        serviceProxy.CallerId = new Guid("AAB4EBA7-BDD1-E211-A6C5-00155D0B2738");    //Impersonating serviceProxy

        var newTask = new Task();
        newTask.Description = "ServiceProxy created Task";
        newTask.Subject = "ServiceProxy created";

        var newTaskGuid = serviceProxy.Create(newTask);
    }
}

Blog post about the different userIds, when impersonating Plug-Ins, Dialogs and Workflows: https://community.dynamics.com/crm/b/develop1/archive/2013/08/09/user-impersonation-in-plugins-workflow-and-dialogs.aspx

Debugging JavaScript that executes on Ribbon Button clicks

When you click a button in the ribbon, dynamics uses ajax to load the file where the js function is in (only once). Therefore you cannot set a breakpoint on the function, before the button click. Here I will present some solutions to debug such functions anyways:

  1. Set incorrect library in Ribbon definition
  2. Use “debugger;” statement
  3. Throw an error
  4. Use sourceUrl property

Set incorrect library in Ribbon definition

Interestingly dynamics loads the file where the function for the ribbon is in at page load. So the file is there, but won’t be used. By putting an incorrect library in the ribbon definition the ajax call will result in an 401 error. Luckily the function from the already loaded file will be used.
In this solution you can set a breakpoint before clicking on the button. Unfortunately this will also result in an error screen by dynamics.
Works in FF, Chrome. Not tested in IE.

Use “debugger;” statement

When you put

debugger;

into your sourcecode the debugger will pause on this line.
Works in Chrome. Not tested in IE.

Throw an error

You can also throw an catch an error in your code:

        try
        {
            throw new Error('breakpoint');   //debugger will break here
        }
        catch (e)
        {
        };

Then set your debugger to pause on all errors.
Works in Chrome, but not in FF. Not tested in IE.

Use sourceUrl property

By putting a

//@ sourceURL=filename.js

property into your javascript file, it will be listet as file in Chrome debugger, after it has been loaded. As it will be listet after you have clicked the button, you can only set a breakpoint after execution of the js function.
Works in Chrome, but not in FF. Not tested in IE.

Getting E-Mail and Attachments in Plug-In using LINQ

When updating an e-mail entity, only updated metadata is available in InputParameters.
Here is some code to get the whole entity and attachments using LINQ:

protected void ExecutePostEMailUpdate(LocalPluginContext localContext)
{
    if (localContext == null)
	{
		throw new ArgumentNullException("localContext");
	}

	IPluginExecutionContext context = localContext.PluginExecutionContext;
	IOrganizationService service = localContext.OrganizationService;
	ITracingService tracingService = localContext.TracingService;

	OrganizationServiceContext serviceContext = new OrganizationServiceContext(service);

	if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
	{
		Entity targetEntity = (Entity)context.InputParameters["Target"];
		if (targetEntity.LogicalName != "email")
			return;

		try
		{
			Email email = targetEntity.ToEntity();

			email = (from mail in serviceContext.CreateQuery()
				where (mail.Id == email.Id)
				select mail).FirstOrDefault();

			var attachmentQuery = (from a in serviceContext.CreateQuery()
				where (a.ObjectId == email.ToEntityReference())
				select a);

			foreach (var attachment in attachmentQuery)
			{
				var fileName = attachment.FileName;
			}
		}
		catch (FaultException e)
		{
			throw new InvalidPluginExecutionException(e.ToString());
		}
	}
}