Payloads as dynamic Objects in ASP.NET MVC

Even though the dynamic type seems to be everywhere these days, ASP.NET MVC still doesn’t support having dynamic parameters in controller action methods out of the box; which is to say, this doesn’t work as expected:

[HttpPost]
public ActionResult Post(dynamic payload)
{
    //payload will be an object without any properties
 
    return this.View();
}

However, because MVC is so extensible, it is very easy to achieve it. For that, we need to build a custom model binder and apply it to our action method parameter. We’ll assume that the content will come as JSON from the HTTP POST payload. Note that this does not happen with Web API, but still happens with MVC Core!

There are a couple of ways by which we can bind a model binder to a parameter:


First, let’s focus on the actual model binder, the core for any of the above solutions; we need to implement the IModelBinder interface, which isn’t really that hard to do:

public sealed class DynamicModelBinder : IModelBinder
{
    private const string ContentType = "application/json";
 
    public DynamicModelBinder(bool useModelName = false)
    {
        this.UseModelName = useModelName;
    }
 
    public bool UseModelName { get; private set; }
 
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        dynamic data = null;
 
        if ((controllerContext.HttpContext.Request.AcceptTypes.Any(x => x.StartsWith(ContentType, StringComparison.OrdinalIgnoreCase) == true) &&
            (controllerContext.HttpContext.Request.ContentType.StartsWith(ContentType, StringComparison.OrdinalIgnoreCase) == true)))
        {
            controllerContext.HttpContext.Request.InputStream.Position = 0;
 
            using (var reader = new StreamReader(controllerContext.HttpContext.Request.InputStream))
            {
                var payload = reader.ReadToEnd();
 
                if (string.IsNullOrWhiteSpace(payload) == false)
                {
                    data = JsonConvert.DeserializeObject(payload);
 
                    if (this.UseModelName == true)
                    {
                        data = data[bindingContext.ModelName];
                    }
                }
            }
        }
 
        return data;
    }
}

Nothing fancy here; it will check to see if both the Accept and the Content-Type HTTP headers are present and set to application/json, the official MIME type for JSON, before parsing the posted content. If any content is present, JSON.NET will parse it into it’s own object. The UseModelName property is used to bind to a specific property of the payload, for example, say you are binding to a parameter called firstName, and you want it populated with the contents of the firstName field in the payload. In our case, we don’t need it, we want the whole thing, so it is set to false.

Now, the way I recommend for applying this model binder is through a custom attribute:

[Serializable]
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public sealed class DynamicModelBinderAttribute : CustomModelBinderAttribute
{
    public DynamicModelBinderAttribute(bool useModelName = false)
    {
        this.UseModelName = useModelName;
    }
 
    public bool UseModelName { get; private set; }
 
    public override IModelBinder GetBinder()
    {
        return new DynamicModelBinder(this.UseModelName);
    }
}

It goes like this:

[HttpPost]
public ActionResult Post([DynamicModelBinder] dynamic payload)
{
    //payload will be populated with the contents of the HTTP POST payload
 
    return this.View();
}

Or, if you want to do it for several dynamic parameters, just set a global model binder provider for type Object; this is safe, because we would never have a parameter of type Object:

public sealed class DynamicModelBinderProvider : IModelBinderProvider
{
    public static readonly IModelBinderProvider Instance = new DynamicModelBinderProvider();
 
    public IModelBinder GetBinder(Type modelType)
    {
        if (modelType == typeof (object))
        {
            return new DynamicModelBinder();
        }
 
        return null;
    }
}

And we register is as this:

ModelBinderProviders.BinderProviders.Insert(0, DynamicModelBinderProvider.Instance);

And that’s it. This is one of what I consider to be ASP.NET MVC flaws, and they will deserve another post, soon. Web API already solves this problem, but it is still there in the future version of MVC Core, and can be solved in the same way.






                             

11 Comments

  • Nice one Ricardo!

  • If I was reviewing a PR where the dev had used dynamics for API contracts, I would reject it!

  • I agree with Antony. Using dynamic APIs not the pit of success that MVC should be helping you toward.

  • Antony & Joseph:
    I suppose you guys enjoy the ViewBag property?
    ;-)

  • I agree with Antony. i think his right
    it can help me,thanks

  • @RicardoPeres ugh, just no.

  • Guys,
    I personally have never used this technique... *but* I think it is an acceptable one, in some cases:
    - we want to have an action method be called by several parties, with different payloads (a callback/web hook style)
    - the payload is dynamically built
    - etc.
    I am not saying that we *should* do it, just *how* we can do it! And by all means, feel free to disagree with me, my blog will always be open for critical discussion! ;-)

  • I could not disagree more with Antony & Joseph and I'm sure I'd reject much of their code for lacking innovation and imagination. don't get me wrong, I'm no cowboy and I embrace good coding standards. But closed minds are not welcome in my team.

    The fact is, as Ricardo points out, use of this ModelBinder would be an edge case. Heck, that's why he created an Attribute for ad hoc usage.

    99.99% of action methods would use a strongly typed model of some kind. In fact, in my applications, 100% so far have. HOWEVER, that does not definitively rule out the usage of a dynamic variable to be model bound in an Action method forever. There may come a time where you want to give the user some massive flexibility in terms of what gets sent to the server in the payload. Perhaps you have an action that receives an object to be logged and you want that Action method to service all AJAX'd logging calls (the equivalent of something like Raygun - where you can send it virtually anything). I'm thinking off the top of my head here, but you get the picture.

    This may be an elegant, efficient way to solve a complex problem. One which we haven't faced yet.
    So, I would only be rejecting this in a review if it did not solve the right problem; one which could have been solved with a strongly-typed model.

    This is pretty cool and I will be bookmarking this post for future reference. Nice work!

  • Thanks, Dave! :-)

  • You could simply accept a JObject...

  • Natan:
    I don't like to expose third-party classes in my API methods. Using JSON.NET or whatever is an implementation detail.

Add a Comment

As it will appear on the website

Not displayed

Your website