Friday, March 10, 2017

I’ve Been Doing Plugin Parameters Wrong For 7 Years

TLDR: Use request/response classes to get typed parameters in your plugins, and share your knowledge!

I’ve been an active Stack Overflow (SO) user for about 7 years now.  There are 3 ways to increase your knowledge from SO.
  1. Being able to ask a question and get an answer in hours.  It’s not always the correct, or the answer you want, but more often than not, it is a great way to increase your knowledge.  This though, is not the primary conduit to increasing your knowledge, that privilege belongs to way #2.
  2. Finding the answers to the questions that have already been asked, and instantaneously getting the knowledge you want (sometimes by the former, “smarter” you).  This is by far the most common method of learning on SO.  It also brings up a new not quite as beneficial coding paradigms (Stack-Overflow-Copy-and-Paste-Yourself-to-Victory).  If this second option is the bread and butter of learning for developers today, then third option is the dessert, the unexpected delightfulness that makes the SO community a great place to contribute to, and learn from.
  3. Providing an answer to a question that you believe to be 100% correct, but then having someone else respond with an even better/more awesome/even more “correcter” answer, to which you tweet about, and then someone else responds with an even better/more awesome/even more “correcter”/more ludicrous answer.  This third option is the subject of today’s post.
Here is how this particular instance of #3 went down…

  • I posted an answer to a SO question about plugin parameters almost 6 months ago (Feel free to refer to the SO question mentioned here: How to know what InputParameters values are possible in Dynamics CRM Plugin context?).  
  • Then Federico Jousset commented on my answer with a helper web page that he has created that is a better tool, IMHO, to answer the OP’s question.  
  • I then tweeted about it (since this knowledge should be shared among the global Dynamics 365 community).
  • Martin Tange responded with a blog post (which I’m assuming he wrote in direct response to my tweet) that brought to light how I’ve been doing plugin parameters wrong since day one.
  • And now you're here learning about it as well!

The MSDN documented approach to accessing parameters is to use “magic” strings and assumed casting
// The InputParameters collection contains all the data passed in the message request.
if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is EntityReference)
{
    // Obtain the target entity from the input parameters.
    EntityReference entity = (EntityReference)context.InputParameters["Target"];
}
Martin’s technique is so simple, I can't believe I haven't seen it used before:
var createReq = new CreateRequest { Parameters = context.InputParameters };
createReq.Target; // Is typed as Entity vs EntityReference (DeleteRequest)
To which I’ve wrapped in an extension method to help remove some additional key strokes (which I’ve added to the DLaB.Xrm.Plugin namespace):
/// <summary>
/// Populates a local version of the request using the parameters from the context.  This exposes (most of) the parameters of that particular request
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="context">The context.</param>
/// <returns></returns>
public static T GetRequestParameters<T>(this IPluginExecutionContext context) where T: OrganizationRequest
{
    var request = Activator.CreateInstance<T>();
    request.Parameters = context.InputParameters;
    return request;
}
 
/// <summary>
/// Populates a local version of the response using the parameters from the context.  This exposes (most of) the parameters of that particular response
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="context">The context.</param>
/// <returns></returns>
public static T GetResponseParameters<T>(this IPluginExecutionContext context) where T : OrganizationResponse
{
    var response = Activator.CreateInstance<T>();
    response.Results = context.OutputParameters;
    return response;
}
So now, this is even shorter:
var parameters = context.GetRequestParameters<CreateRequest>();
parameters.Target;
So my challenge to you dear reader, get involved in your community.  You may never know when an attempt to share your knowledge with someone else, ends up unexpectedly resulting in others sharing their knowledge with you!

5 comments:

jrpetersjr said...

You should do a follow up post about using the generated actions if possible.

Daryl said...

Good Idea @jrpetersjr. Done!

http://dotnetdust.blogspot.com/2017/07/how-to-define-custom-action-parameters.html

Leszek Szejka said...

Daryl,
nice tweak regarding above post relates to pre/post images.
Have u been using more than 1 pre and post image at the same time ? I have not. So why each time be cautious about the image name since we do not care about it.

LocalPluginContext can be extended (or any other IServiceProvider wrapper) to just take a first one and set additional PreImage and PostImage property during constructing object.

localContext.PluginExecutionContext.PostEntityImages.FirstOrDefault();

Leszek

Daryl said...

Leszek, I think I'm understanding what you're saying. I too have never had more than 1 Pre Image, or more than 1 Post Image. Are you just requesting that I udpate the LocalPluginContext to do that, rather than specifying the actual name? If so, create an issue in the GitHub repository, and I'd love to handle that.

Leszek Szejka said...

Daryl,
this is just my spot on the Image usage. It might be usefull for someone else. If u need/want to add it to your's source code please do. I am not using in my project yours nuget packages due to licences perspective.