Bruno Martinez
Bruno Martinez

Reputation: 2966

Binding to form encoded JSON in ASP.NET MVC/Web API

github can alert you and call your webservice when commits are pushed to a repository (Post-Receive Hooks). github sends the commit information as JSON but inside a form encoded parameter. That is, the content type is application/x-www-form-urlencoded and the http request is

POST /my_uri HTTP/1.1
Content-Type: application/x-www-form-urlencoded

payload=%7B%22ref%22%3A%22refs%2Fheads...

I want to write the webservice that processes the new commits in ASP.NET MVC or WebAPI. I've defined some classes to deserialize the json, but I can't get the framework to initialize my objects directly. What I have now is

public string my_uri(string payload)
{
    var s = new JavaScriptSerializer();
    var p = s.Deserialize(payload, typeof(Payload)) as Payload;
    ...
}

but I would want

public string my_uri(Payload payload)
{
    ...
}

I've read about ValueProviders but I didn't find a way to chain them. I need to compose FormValueProviderFactory and JsonValueProviderFactory. How can I get ASP to do the binding?

Upvotes: 0

Views: 791

Answers (1)

Kiran
Kiran

Reputation: 57989

First, I am a bit confused as to why they would stuff Json data in form-encoded body data. If a service in the end can understand Json(since it has to deserialize it), why not post as "application/json" itself? Is it because of CORS that they are doing this way?

That aside, you could create a custom parameter binding like below and see if it fits your needs:

Action:

public Payload Post([PayloadParamBinding]Payload payload)

Custom Parameter Binding:

public class PayloadParamBindingAttribute : ParameterBindingAttribute
{
    public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter)
    {
        return new PayloadParamBinding(parameter);
    }
}

public class PayloadParamBinding : HttpParameterBinding
{
    HttpParameterBinding _defaultFormatterBinding;

    public PayloadParamBinding(HttpParameterDescriptor desc)
        :base(desc)
    {
        _defaultFormatterBinding = new FromBodyAttribute().GetBinding(desc);
    }

    public override async Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken)
    {
        if (actionContext.Request.Content != null)
        {
            NameValueCollection nvc = await actionContext.Request.Content.ReadAsFormDataAsync();

            StringContent sc = new StringContent(nvc["payload"]);
            //set the header so that Json formatter comes into picture
            sc.Headers.ContentType = new MediaTypeHeaderValue("application/json");

            actionContext.Request.Content = sc;

            //Doing like this here because we want to simulate the default behavior of when a request
            //is posted as Json and the Json formatter would have been picked up and also the model validation is done.
            //This way you are simulating the experience as of a normal "application/json" post request.
            await _defaultFormatterBinding.ExecuteBindingAsync(metadataProvider, actionContext, cancellationToken);
        }
    }

    public override bool WillReadBody
    {
        get
        {
            return true;
        }
    }
}

Upvotes: 1

Related Questions