Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strongly typed AND arbitrary properties in Web API request object

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?

like image 864
Arve Systad Avatar asked Jan 03 '14 12:01

Arve Systad


People also ask

Which is the default formatter for the incoming request body?

HTTP is the default formatting style, provided by the DefaultHttpLogFormatter .

Does Web API return JSON by default?

By default, Web API produces XML but if there is need for JSON, given syntax will do it. Open WebApiConfig. cs file in solution and add mentioned line in it as shown in example.

How Web API works in. net?

The Web API returns the data on request from the client, and it can be in the format XML or JSON. The MVC architecture is the Model-View-Controller Pattern. It is used for isolating an application to simplify testing and the maintenance of the code of the application.


1 Answers

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

like image 177
LostInComputer Avatar answered Oct 13 '22 08:10

LostInComputer