5

I have an object that looks pretty much like this:

public class MyNiceRequest 
{
    [Required]
    public string FirstName { get; set; }
    [Required]
    public string LastName { get; set; }

    public string SomeOptionalField { get; set; }
}

And my controller looks like the following, pretty standard still:

public MyNiceResponse Post(MyNiceRequest request) {
...
}

From the front end of the calling application, I want to include more fields than the three specified in the object. These fields are runtime generated (controlled via an admin interface), so I cannot apply them to my request class. However, I've not found a good way to retrieve them in the controller.

I can make my request object (MyNiceRequest) inherit from Dictionary<string,string> - then I'll get them all, but they won't be bound to their respective properties on the strongly typed class (seems like the Dictionary is bound before the rest in whatever model binder is used). Plus, more importantly, validation - which is crucial to the application - stops working.

I have seen this question, but it doesn't give me anything as the Request.Content.Read...-methods give me empty results (since it's already read and bound to a model?).

Let's say I want the following fields from the front end:

  • FirstName (should bind to strongly typed, nowhere else)
  • LastName (should bind to strongly typed, nowhere else)
  • SomeOptionalField (should bind to strongly typed, nowhere else)
  • RuntimeGenerated1 (should end up in dictionary)
  • RuntimeGenerated2 (should end up in dictionary)

I want one of two solutions:

  • Either be able to inherit from Dictionary<string,string>, but let the dictionary be bound AFTER the strongly typed properties to let the validation do it's work
  • Have a separate property on MyNiceRequest that could be something like Dictionary<string,string> TheRest { get; set; } and bind that to the remaining incoming properties somewhere.

Rewriting the front end to pass in the runtime generated fields as a separate collection is not an option.

..and can this at all be achieved by reusing/reordering existing stuff, or will I have to write a complete media type formatter and/or model binder from scratch?

Community
  • 1
  • 1
Arve Systad
  • 5,471
  • 1
  • 32
  • 58

2 Answers2

3

For application/json content type, you can use DynamicObject with WebAPI's default JSON formatter.

public class MyNiceRequest : DynamicObject
{
    private Dictionary<string, string> _dynamicMembers = new Dictionary<string, string>();

    [Required]
    public string FirstName { get; set; }

    [Required]
    public string LastName { get; set; }

    public string SomeOptionalField { get; set; }

    [JsonIgnore]
    public Dictionary<string, string> DynamicMembers
    {
        get { return _dynamicMembers; }
    }

    public override bool TryGetMember(GetMemberBinder binder, out object value)
    {
        string stringValue;
        var isFound = _dynamicMembers.TryGetValue(binder.Name, out stringValue);
        value = stringValue;
        return isFound;
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        if (value is string)
        {
            _dynamicMembers[binder.Name] = (string)value;
            return true;
        }
        return false;
    }
}

Edit

  1. If you want the object to be serialized with the same format, implement IDictionary<string, string>. This is easy, just delegate the interface implementation to _dynamicMembers

  2. This solution doesn't work with the default XML and x-www-form-urlencoded formatters :(

LostInComputer
  • 15,188
  • 4
  • 41
  • 49
2

You can have the DTO like this.

public class MyNiceRequest
{
    [Required]
    public string FirstName { get; set; }
    [Required]
    public string LastName { get; set; }

    public string SomeOptionalField { get; set; }

    public Dictionary<string, string> TheRest { get; set; }
}

If you post a JSON in request body, like this {"firstname":"arve", "therest":{"key":"value"}}, FirstName property will be populated. TheRest will have an item with key "key" and value "value". Of course, ModelState.IsValid will be false, since JSON did not contain LastName.

  • It's a fair answer, but I don't want to rewrite the front end to put all these dynamic fields into a separate collection (`TheRest`) - they need to be on the "base level" like everything else. Clarified my original question. – Arve Systad Jan 03 '14 at 13:28