4

Currently I am trying to write a controller in MVC 6 that is capable of accepting data from a post request. The issue is that depending on the client (which is not always a web browser), the data can come in as either key value pairs in the request URL, or as JSON in the request body.

Currently this method works for accepting the data from the URL:

[HttpPost]
public async Task<CaptureResponse> CaptureData(CaptureInfo capture) {
    ...
}

After a lot of trial and error (and stack overflow answers), I figured out that the [FromBody] attribute tells the model binder to look in the request body, which is required now because MVC 6 combines WebApi and standard MVC together. The following code parses data from JSON in the form body:

[HttpPost]
public async Task<CaptureResponse> CaptureData([FromBody] CaptureInfo capture) {
   ...
}

For simplicity, I would like to combine the two together somehow, so the model binder gives me the data in the same parameter variable. So far, the only way I can get the data into the same Action is to specify two parameters, one for the URL and one for the body, and do some null checking on each like so:

[HttpPost]
public async Task<CaptureResponse> CaptureData(CaptureInfo capture, [FromBody] CaptureInfo bodyCapture) {
    if (bodyCapture != null) {
        if (bodyCapture.RequiredProperty1!= null
        && bodyCapture.RequiredProperty2!= null) {
            capture = bodyCapture;
        }
    }
    ...
}

I have tried specifying multiple properties before the input attribute like this:

[HttpPost]
public async Task<CaptureResponse> CaptureData(CaptureInfo [FromQuery][FromRoute][FromForm][FromBody] capture) {
    ...
}

But it does not work. Any ideas if something like this is possible?

Ryan
  • 3,852
  • 4
  • 18
  • 18

2 Answers2

0

As far as I know, it is just not possible. Of course you can try using workarounds, basically doing all request parsing yourself. It doesn't sound good, does it?

If you really want the things your way, I believe the best approach is to have two distinct endpoints in the controller and the private method for actual processing. Or, perhaps, even extract that method into an additional abstraction layer with BlaBlaService (CaptureService in your case, probably) class(es) responsible for all the dirty work. Sometimes it makes sense to separate your controllers layer from business logic - for example, for testing purposes.

PS: Your idea is quite similar to what was the approach in good old .NET & PHP times, and believe me, this particular idea is not the one that made those times good. You know, the MVC is much about the REST-like approach, so each endpoint of your controller is supposed to be dedicated to its own single function and obey to a single and uniform "intuitive" protocol.

The "intuitive" way to submit data to POST request for developers acquainted with REST is through the request body. I suggest you to consider going with this approach as the only one.

Konstantin
  • 339
  • 2
  • 15
-4

You need use Request.Form

like:

   string username = HttpContext.Current.Request.Form.GetValues("key1")[0].ToString();
    string password = HttpContext.Current.Request.Form.GetValues("key2")[0].ToString();
    string something = HttpContext.Current.Request.Form.GetValues("key3")[0].ToString();
David Abaev
  • 690
  • 5
  • 22
  • These methods appear to be static, HttpContext doesn't seem to contain a "Current" property. I tried to use `(await Context.Request.GetFormAsync())`, but it always returns null. This method also doesn't appear to do any sort of parsing or deserialization to the class object like the default model binder does. – Ryan Jan 07 '15 at 05:24