8
votes

I've been using the MVC4 beta and am currently working to upgrade to the recently released RC version.

It appears that model-binding complex request types has changed, but I can't figure out how / what I'm doing wrong.

For example, say I have the following API controller:

public class HomeApiController : ApiController
{
    public TestModel Get()
    {
        return new TestModel
        {
            Id = int.MaxValue,
            Description = "TestDescription",
            Time = DateTime.Now
        };
    }
}

This yields the expected result:

<TestModel xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/xxxx">
    <Description>TestDescription</Description>
    <Id>2147483647</Id>
    <Time>2012-06-07T10:30:01.459147-04:00</Time>
</TestModel>

Now say I just change the signature, taking in a request type, like this:

public TestModel Get(TestRequestModel request)
{
    ...

public class TestRequestModel
{
    public int? SomeParameter { get; set; }
}

I now get the following error:

<Exception xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/System.Web.Http.Dispatcher">
    <ExceptionType>System.InvalidOperationException</ExceptionType>
    <Message>
        No MediaTypeFormatter is available to read an object of type 'TestRequestModel' from content with media type ''undefined''.
    </Message>
    <StackTrace>
    at System.Net.Http.HttpContentExtensions.ReadAsAsync[T](HttpContent content, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger) at System.Net.Http.HttpContentExtensions.ReadAsAsync(HttpContent content, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger) at System.Web.Http.ModelBinding.FormatterParameterBinding.ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken) at System.Web.Http.Controllers.HttpActionBinding.<>c__DisplayClass1.<ExecuteBindingAsync>b__0(HttpParameterBinding parameterBinder) at System.Linq.Enumerable.WhereSelectArrayIterator`2.MoveNext() at System.Threading.Tasks.TaskHelpers.IterateImpl(IEnumerator`1 enumerator, CancellationToken cancellationToken)
    </StackTrace>
</Exception>

I've looked at the source code of where this exception is thrown in the HttpContentExtensions, but it looks like it checks for content headers (which I should have), and if it doesn't have that it tries to get a formatter from the MediaTypeFormatter collection it has for the specific type (which it can't) and then throws.

Anyone else experienced this? Some global registration I'm missing?

2
What is the HTTP request in the fiddler? Specifically what is the Content-Type header value?Aliostad
I can manually pass a Content-Type of application/json, which, interestingly, gets me past that error (I had been passing application/json in the accept header only). But now complex types are coming in as null, which seems to share some common underlying problem.Brandon Linton
So what is in you accept then? Accept: application/json does not work?? Try using [FromBody] on the parameter.Aliostad

2 Answers

13
votes

I see your original question was answered, but to answer the other one, Model binding has changed somewhat in the RC.

http://weblogs.thinktecture.com/cweyer/2012/06/aspnet-web-api-changes-from-beta-to-rc.html

This link has some details about it. But to sum up the change that appears to be affecting you, Model binding pulls its values from either the body, or the uri of the request. This is true for previous releases as well, but with the release candidate, MVC4 will, by default, look to the body for complex types, and the uri for value types.

So, if you submit a body with your request containing the "SomeParameter" key, you should see it bind. Or you could bind with the url if you change the declaration to:

 public TestModel Get(int? someParameter)
 {

 }

Thankfully, the team foresaw the potential problems with this and left us with attributes we could use to override this behavior.

 public TestModel Get([FromUri]TestRequestModel request)
 {

 }

The key here is the [FromUri] which tells the model binder to look in the uri for the values. There is also [FromBody] if you want to put a value type in the body of a request.

2
votes

We were seeing the same thing. In our case the problem was a complex object being passed into a get method. We needed to add a [FromUri] attribute in the parameter to that method.

http://forums.asp.net/t/1809925.aspx/1?GET+requests+with+complex+object+as+input+parameter

public class SearchController : ApiController
{
    // added [FromUri] in beta to RC transition otherwise media type formatter error
    public IQueryable<SearchResultEventModel> Get( [FromUri]SearchSpecModel search )
    {
        // ...
    }
}