So, how does one work around the read-only response parameters and lack of a defined list of parameter logical names? By extending CrmSvcUtil to make the read-only fields editable, and to generate a list of parameter logical names. This isn’t very simple to do since you have to work directly with the DOM (no, not the html DOM, the C# DOM, it’s way less fun). The good news is I have already done this, and provide it as a set of options in the Early-Bound Generator (If you aren’t already using the Early-Bound Generator, just download it from the Plugin Store within the XrmToolBox). On the Actions Tab, just check “Generate Attribute Name Consts” and “Make Responses Editable”:
With those two options checked, when actions are generated, responses will be editable, and both request and response classes will contain a static class with consts for each parameter. The absolute simplest and most immediate method of taking advantage of these generated action request/response classes would be to manually create them in the plugin like so:
public void Execute(IServiceProvider serviceProvider) { var context = (IPluginExecutionContext) serviceProvider.GetService(typeof(IPluginExecutionContext)); var request = new your_CustomActionRequest { Parameters = context.InputParameters }; var response = new your_CustomActionResponse { Results = context.OutputParameters }; // Use the Request and Response Properties response.Result = Process(request.SomeInputValue); }
This isn’t horrible, but if you call any other methods, you’ll probably end up passing in the context and the request and/or response to that method. The request and the response truly are part of the context, so if would make sense if we can make them just appear as a part of the context. This can be done by creating your own custom context class:
public class ActionContext<TRequest, TResponse> : DLaB.Xrm.Plugin.DLaBExtendedPluginContextBase where TRequest : Microsoft.Xrm.Sdk.OrganizationRequest, new() where TResponse : Microsoft.Xrm.Sdk.OrganizationResponse, new() { public TRequest Request { get; set; } public TResponse Response { get; set; } public ActionContext(IServiceProvider serviceProvider, DLaB.Xrm.Plugin.IRegisteredEventsPluginHandler plugin) : base(serviceProvider, plugin) { Request = new TRequest { Parameters = PluginExecutionContext.InputParameters }; Response = new TResponse { Results = PluginExecutionContext.OutputParameters }; } }
*Note* I’m using the DLaBExtendedPluginContextBase as my base class since this exposes all the IPluginExecutionContext methods for you (this is available on Nuget DLaB.Xrm.2015/2016). If you don’t want to use that, your ActionContext base class will need to implement the IPluginExecutionContext interface.
Now our plugin code can be written with even less keystrokes:
public void Execute(IServiceProvider serviceProvider) { var context = new ActionContext<your_CustomActionRequest, your_CustomActionResponse>(serviceProvider, this); // Use the Request and Response Properties context.Response.Result = Process(context.Request.SomeInputValue); }
It might even make sense to create your own specific action context class to perform additional validation. This would technically be cleaner code as well, since the context should be responsible for determining if it’s valid, not the plugin. For example, here is a Custom Action class for processing payment that performs some validations:
using System; using Consoto.Xrm.Plugin; using DLaB.Xrm.Plugin; using Microsoft.Xrm.Sdk; using Input = Consoto.Xrm.Entities.cnst_SubmitPaymentRequest.Fields; namespace Consoto.Xrm.ExternalRest.Finance.Actions { public class SubmitPaymentContext : ActionContext<Entities.cnst_SubmitPaymentRequest, Entities.cnst_SubmitPaymentResponse> { public const int CreditCardPaymentType = 1; public const int BankPaymentType = 2; public SubmitPaymentContext(IServiceProvider serviceProvider, IRegisteredEventsPluginHandler plugin) : base(serviceProvider, plugin) { AssertIsPopulated(Input.paymentType); AssertIsPopulated(Input.customerNumber); AssertIsPopulated(Input.firstName); AssertIsPopulated(Input.lastName); AssertIsPopulated(Input.tax); switch (Request.paymentType) { case CreditCardPaymentType: AssertIsPopulated(Input.creditCardNumber); AssertIsPopulated(Input.creditCardExpMonth); AssertIsPopulated(Input.creditCardExpYear); break; case BankPaymentType: AssertIsPopulated(Input.accountNumber); AssertIsPopulated(Input.routingNumber); break; default: throw new InvalidPluginExecutionException($"Payment Type of {Request.paymentType} is unknown!"); } } } }
This has the added benefit of not having to redefine the Request and Response Types in the plugin:
public void Execute(IServiceProvider serviceProvider) { var context = new YourCustomActionContext(serviceProvider, this); // Use the Request and Response Properties context.Response.Result = Process(context.Request.SomeInputValue); }
It used to take me over an hour (depending on the number of parameters in the custom action) to create the correctly defined custom action contexts, and of course I’d usually have a type-o or two that would require time debugging and re-updating. Now that all the parameters are automatically generated, I get to spend that time on more productive things, like this blog post ;)
If you have any comments/feedback/suggestions/better methods, I’d love to hear them. Happy coding!