1

I'm just getting started with complex types in Web API. I created a simple class that the default formatter should have no problem working with:

public class GridColumnArgs
{
    public GridColumnArgs() { }

    public int FieldID { get; set; }
    public bool Visible { get; set; }
}

And set up a Web API method that takes a GridColumnArgs:

public void PutGridLevel(int gridLevelID, GridColumnArgs columnArgs)
{
    // my breakpoint is set here
}

And in JavaScript, made a simple testing method:

var columnArgs = { FieldID: 1, Visible: true };
myHelperLibrary.put({
    url: 'api/grid?gridLevelID=123',
    obj: columnArgs,
    done: function () { alert('ok!') },
    fail: function () { alert('no good!') }
});

Works perfectly. Hits the breakpoint, my properties set on the JavaScript object literal are there, great.

However I need to pass in a collection of these GridColumnArgs objects. So if I change my Web API method thusly:

public void PutGridLevel(int gridLevelID, GridColumnArgs[] columnArgs)
{
    // my breakpoint is set here
}

And adjust my testing JavaScript like this:

var columnArgs = [
    { FieldID: 1, Visible: true },
    { FieldID: 2, Visible: false }
];

myHelperLibrary.put({
    url: 'api/grid?gridLevelID=123',
    obj: columnArgs,
    done: function () { alert('ok!') },
    fail: function () { alert('no good!') }
});

The call does not work. Routing works - I hit my breakpoint. But the parameter is an empty array.

So obviously the default formatters are not able to work with an array of a complex type as easily as they are with a single complex type.

I've read some answers and articles that reference writing a custom media formatter to solve this problem, but that seems like a lot of trouble to go to to accept an array of objects that the default media formatters already know how to work with... What is the quickest way to get this working?

Thanks very much for your help!

3 Answers 3

1

Not sure if this is gonna help, but you could try adding attributes.

[JsonProperty("FieldID")]
public int FieldID { get; set; }

or change

public void PutGridLevel(int gridLevelID, dynamic columnArgs)

to eliminate possible type issues. It might by 'var' instead of dynamic type or try 'object' can't recal exactly and can't check away from VS atm.

Sign up to request clarification or add additional context in comments.

4 Comments

The JsonPropertyAttribute is mostly useful when the JSON property differs in name than the C# property. By default, it uses the C# property name to map.
Thanks for the idea LIUFA, but this did not fix the problem.
I tried your second suggestion of changing the parameter to dynamic. The parameter that arrived was an "object" but I can't seem to do anything with it - doesn't cast as an ExpandoObject, Array, or IEnumerable, doesn't have a Length or Count property.
Try to add it to 'watch' in VS or do things with it in 'Immediate Window' to see what that object is, that might give you some ideas.
1

I found another alternative to bind complex object types in Web API. I defined a model binder in which I am reading request body as string and then using JSON.NET to deserialize it to required object type. It can be used to map array of complex object types as well.

I added a model binder as follows:

public class PollRequestModelBinder : IModelBinder
{
    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
    {
        var body = actionContext.Request.Content.ReadAsStringAsync().Result;
        var pollRequest = JsonConvert.DeserializeObject<PollRequest>(body);
        bindingContext.Model = pollRequest;
        return true;
    }
}

And then I am using it in Web API controller as follows:

    public async Task<PollResponse> Post(Guid instanceId, [ModelBinder(typeof(PollRequestModelBinder))]PollRequest request)
    {
       // api implementation  
    }

Comments

0

I've got a working solution that takes a JObject in the parameters and uses Json.NET to parse the array. I'm still able to use default functionality to actually instantiate the objects but this still seems like too much work and fragile (not to mention I'll need to implement my own overloading if I want to support another PUT route, etc.) More answers would be greatly appreciated.

Client-side code:

var columnArgs = [
    { FieldID: 1, Visible: true },
    { FieldID: 2, Visible: false }
];

myHelperLibrary.put({
    url: 'api/grid?gridLevelID=123',
    obj: { columns: columnArgs },
    done: function () { alert('ok!') },
    fail: function () { alert('no good!') }
});

On the server:

public void PutGridLevel(int gridLevelID, JObject json)
{
    var jprop = json.Properties().FirstOrDefault(p => p.Name.Equals("columns", StringComparison.OrdinalIgnoreCase));
    if (jprop == null)
        throw new ArgumentException("The parameter object must contain an array property called \"columns\".");
    var columns = jprop.Value.ToArray().Select(obj => obj.ToObject<GridColumnArgs>());

// etc...
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.