Stack Overflow is a community of 4.7 million programmers, just like you, helping each other.

Join them; it only takes a minute:

Sign up
Join the Stack Overflow community to:
  1. Ask programming questions
  2. Answer and help your peers
  3. Get recognized for your expertise

How do you properly create a Web API POST of complex object or multiple parameters using Angular2?

I have a service component in Angular2 as seen below:

public signin(inputEmail: string, inputPassword: string): Observable<Response> {
    return this.http.post('/api/account/signin', JSON.stringify({ Email: inputEmail, Password: inputPassword}), this.options);
}

The targeted web api is seen below:

[HttpPost]
[Route("signin")]
public async Task<IActionResult> Signin(string email, string password)
{
       ....
}

This does not work because I need to convert the parameters of the web api into a single POCO class entity with Email and Password properties and put the [FromBody] attribute: Signin([FromBody] Credential credential)

Without using [FromURI] (POST requests with query strings?), how can I make POSTs of multiple parameters or complex objects without converting these parameters into a single POCO class?

Because what if I have numerous Web API POST actions with parameters like (string sensitiveInfo1, string name, int sensitiveInfo2) or (ClassifiedInfo info, string sensitiveInfo1, string sensitiveInfo2), do I need to convert them all to POCO classes and always use [FromBody]?

PS. I was using RestangularJS before and it can posts anything (mulitple primitive objects and complex objects) without my Web API actions having [FromBody] attributes. Will about to investigate how RestangularJS do it.

share|improve this question

This question has an open bounty worth +100 reputation from Roj Beraña ending in 19 hours.

This question has not received enough attention.

1  
Since you have define the arguments on the action with email and password with e and p in lower case, I think the javascript code should be the same, for sample: JSON.stringify({ email: inputEmail, password: inputPassword}). – Felipe Oriani Mar 29 at 17:12
1  
@FelipeOriani This is not true for .NET using JSON.Net as the JSON serializer. By default, it is not case sensitive. – David L Mar 29 at 19:14

Without using [FromURI] (POST requests with query strings?), how can I make POSTs of multiple parameters or complex objects without converting these parameters into a single POCO class?

I know its not what you want to hear but out of the box this is not possible. It is not a limitation of the browser code that is making the request. This means it does not matter if you are using Angular, JQuery, straight JavaScript, or even RestangularJS. This is a limitation (I use that word loosely as I am sure this is by design) of Web API (any version). Here is the documentation on this design: Parameter Binding in ASP.NET Web API by Mike Wasson.

At most one parameter is allowed to read from the message body. So this will not work:

// Caution: Will not work!
public HttpResponseMessage Post([FromBody] int id, [FromBody] string name) { ... }

So the question becomes, what are your options?

Create a model

This is the thing you were trying to avoid but I list it first because this is how Web API was intended to behave. I have not yet heard a compelling reason not to do this. This approach allows you to extend your model easily without having to change the method signature. It also allows for model validation on the model itself. Personally I really like this approach.

public class SignInModel{
    public string Email {get;set;}
    public string Password {get;set;}
}

[HttpPost]
[Route("signin")]
public async Task<IActionResult> Signin(SignInModel signInModel)
{
       // ....
}

I did not repeat your existing JavaScript code because what you have works as is with the above web api code

URL

Again, what you were trying to avoid. This does make what you want possible with the limitation that you have to pass these parameters using the Query string on the URL. The JavaScript would change but the signature you had on the Web API method would not.

public signin(inputEmail: string, inputPassword: string): Observable<Response> {
    return this.http.post('/api/account/signin/?email=inputEmail&password=inputPassword', null, this.options);
}

I did not repeat your existing Web API code because what you have works as is with the above web JavaScript code (by default FromUri is assumed I believe)

Custom Model Binder

See Passing multiple POST parameters to Web API Controller Methods by Rick Strahl. This option allows you to create a custom model binder that could do what you are asking. It is a whole bunch of extra code though for, IMHO, not much benefit. Maybe there are situations where it would be useful although I really cannot think of any off the top of my head.

Dynamic

Finally you could also pass in a dynamic object as the parameter of your Web API. This is essentially the same as receiving the JSON as a string and making your Controller code responsible for the deserialization of content. Again, I believe that this would make your code worse in most situations as you have to implement custom validation and type checks. This answer was proposed previously on SO by Bes Ley. Again, maybe there are situations where it would be useful although I really cannot think of any off the top of my head.

share|improve this answer
    
This is on spot. Regardless of which javascript framework your using they all uses the same protocol (http) which has its rules and limitations. – Marcus H 6 hours ago

WebApi will be able to deserialize your Credential object provided the JSON object has the same field names (I am not sure about case so you may be right here). You seem to be missing the headers from the post call in your Angular2 component.

Can you check the Content-Type using Chrome Debugger or Fiddler? It should be application/json.

share|improve this answer
    
i added the header on the options parameter. my problem is if your post parameters are only three strings, should you still convert them into a single object always? – Roj Beraña Mar 30 at 0:51
    
Query strings on posts are usually there to designate how the posted Body should be dealt with. If you are persisting anything on the server, or mutating anything, use the body and a poco. I would use multiple lightweight pocos, this is considered good design. ServiceStack for instance actually maps routes to Pocos. github.com/ServiceStack/ServiceStack/wiki/Routing Does that help? – Mojo80 Mar 30 at 1:06
    
kinda. but i have seen some other 3rd party js framework that can post to web apis that have multiple complex parameters without using [FromBody] (eg restangular) – Roj Beraña Mar 30 at 1:17

Try this, passing a complex class object into a single data parameter.

var SearchQuery = function () {
    this.Alphabet = null;
    this.Search = false;
    this.Keyword = null;
    this.RegionList = null;
};
var para = new SearchQuery();
{ data: JSON.stringify(para) } - Post Data

you can receive it using a JObject in your API controller and deserialize it as according to your classes.

share|improve this answer

Perhaps you should post with options:

{ 
   headers: new Headers({
   'Content-Type': 'application/x-www-form-urlencoded'
   })
}

and encode data like

jQuery.param({user:'bla', password: 'bla'});
share|improve this answer

WebAPI does not provide this out of the box. If you try to get understanding of web API bindings, you might be able to figure out why.

I think this article might help.

The generic rules are:

– simple, string-convertible parameters (value types, strings, Guids, DateTimes and so on) are by default read from URI

– complex types are by default read from the body

– collections of simple parameters are by default read from the body too

– you cannot compose a single model based on input from both URI and request body, it has to be one or the other

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.