Loading...

How To Define Custom Action Parameters For Plugin Contexts

How To Define Custom Action Parameters For Plugin Contexts
In my previous post “I’ve Been Doing Plugin Parameters Wrong For 7 Years”, I walk through IMHO the correct way to access parameters from the requests coming in for standard Dynamics 365 for Customer Engagement (hereafter referred to as CRM) Messages.  Applying the same logic for custom action plugins was a little more complicated because naturally, there are no message classes in the SDK for custom actions.  The CrmSvcUtil does generate early-bound classes for custom actions, which work fairly well for input parameters since they are editable, but response parameters by default are read-only.  So any attempt to set a value in the response via the parameter property, is not allowed.  There is also no defined list of parameter logical names, which could be used to set the values in the response context in a late-bound manner.

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”:

ActionsForEBG

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 more
.Net Dust
.Net Dust

NULL

Share post:

Related 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...

1 year ago

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...

3 years ago

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...

3 years ago

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...

4 years ago

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...

4 years ago

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”?. ...

4 years ago

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...

6 years ago

Newsletter

Get the latest Dynamics 365 and Power Platform content in your inbox

A curated digest of community blogs, product news, videos, and podcasts — delivered without the noise.

Weekly updates Unsubscribe anytime Fresh community picks
We use your email only for the newsletter and you can unsubscribe at any time.
By subscribing, you agree to the privacy policy.