How To Define Custom Action Parameters For Plugin Contexts
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!
Published on:
Learn moreRelated posts
PowerFx Formatting If Statement Idiosyncrasies
Disclaimer: This entire blog is an opinion, and opinions are like butts, we all have one. This is not doctrinal truth that must be obser...
Separating Plugin Logic: A Guide to Testing Dataverse Plugins with IOC
I’m not a pure TDD developer. I frequently take my best guess at a Dataverse plugin, then apply TDD until everything works. This c...
How to Filter Dates in Canvas Apps Using Greater Than/Less Than Operators
Defining the ProblemRecently I was attempting to filter an on-premise SQL table by a DateTime field using a “greater than” operator, and displ...
Enabling or Disabling All Plugin Steps In Dataverse
If you are facing an issue with a recent bug in PowerPlatform.BuildTools version 0.0.81 that caused your plugin steps to become disabled, fret...
Using AutoFixture To Create Early Bound Entities
AutoFixture is an open source library that is used in testing to create objects without having to explicit set all the values. I recentl...
Long Functions Are Always A Code Smell
This article is in response to fellow MVP Alex Shelga’s recent article Long functions in dataverse plugins – is it still ” code smell”?. ...
Handle All Your Plugin Exceptions In One Place, And Then Hide It!
In coding, one of the recommended best practices is to avoid creating duplicate code as it leads to maintenance issues. Therefore, it is advis...