-2

I have an object that I'm trying to post to my C# API that is just two strings:

Body Example:

fetch(url, {
  method: 'POST',
  body: { username: 'myusername', password: 'mypassword' },
  headers:{
    'Content-Type': 'application/json'
  }
});

My API method looks like this:

[HttpPost("auth")]
public async Task<IActionResult>(NetworkCredential creds) { ... }

But, I want it to be this:

[HttpPost("auth")]
public async Task<IActionResult>(string username, string password) { ... }

I really don't want to create a model just for two strings (which is why I'm currently using NetworkCredential.) I have other instances where it's not username and password so I can't get away with using NetworkCredential in all cases.

Is there any magic combination/way to setup an API method to sniff out individual strings from the payload without requiring the creation of a model with two properties? I've played around with some of the attributes ([FromBody] for example) but no luck.

mwilson
  • 12,295
  • 7
  • 55
  • 95
  • Please share how you're `POST`ing your object. The two snippets should both work regardless of whether you separate the params or use a model. – Tyler Roper Jul 23 '19 at 18:06
  • It's just a simple `HttpPost` from javascript. I'm actually using the Angular `HttpClient` module in practicality, but playing around with postman for quicker development. – mwilson Jul 23 '19 at 18:08
  • Having a model allows you to apply validation attributes, something I don't *think* you can do with a pair of string parameters. –  Jul 23 '19 at 18:10
  • 2
    consider using a form body. – Daniel A. White Jul 23 '19 at 18:10
  • @amy I don't care about validation on the model level. I'm totally okay with doing simple `null` checks on two strings. My use case is pretty straightforward (which is why I don't want models for all of my "2 string endpoints" – mwilson Jul 23 '19 at 18:11
  • 1
    IIRC to pass in multiple simple values, use form data instead of JSON, as suggested by @DanielA.White. Unfortunately, I don't know how to do that using `fetch`. –  Jul 23 '19 at 18:15
  • 1
    @mwilson You could write an extension method in JavaScript to turn your object into a query string, fitting the default `form-urlencoded` content-type (much like jQuery does in its `$.ajax`'s `data` option / `$.param` method). – Tyler Roper Jul 23 '19 at 18:15
  • I'll give form data a shot. – mwilson Jul 23 '19 at 18:18
  • 1
    @mwilson Check this out: [angular - HttpClient POST request using x-www-form-urlencoded](https://stackoverflow.com/questions/46714480/httpclient-post-request-using-x-www-form-urlencoded) – Tyler Roper Jul 23 '19 at 18:19
  • Just sending a `FormData` object works like a charm. Thanks everyone. – mwilson Jul 23 '19 at 18:21

3 Answers3

0

Thanks to @Daniel A. White and @Amy for the direction.

My solution was to pass the data as FormData rather than json from the client. Doing it this way allows my 'wanted' method to work.

NOTE: this._httpClient is from the Angular HttpClient module

Example:

const formData = new FormData();
formData.append('username', 'myusername');
formData.append('password', 'mypassword');
const response = await this._httpClient.post(<url>, formData).toPromise();

API Method:

[HttpPost("auth")]
public async Task<IActionResult>(string username, string password) { ... }
mwilson
  • 12,295
  • 7
  • 55
  • 95
0

You can do what you are describing with your current setup. In fact, it should work if you leave it almost the way you have it in your question.

Web API endpoint:

[HttpPost("auth")]
public async Task<IActionResult> Authenticate(string username, string password) { ... }

Post from JS:

fetch(url, {
  method: 'POST',
  body: { username: 'myusername', password: 'mypassword' },
  headers:{
    'Content-Type': 'application/json'
  }
});

The default binding in Web API will try to map properties from the body above by name to 1) individual parameters of the action, or 2 ) if the names don't match, and there is a model object, then to the properties of that object.

In the case above, it should map to the 2 individual parameters.

It is usually considered good practice to wrap individual parameters into a model, though. If you decide in the future to do that, below is an example:

class MyModel {
    public string UserName {get;set;}
    public string Password {get;set;}
}

and the endpoint would look like:

[HttpPost("auth")]
public async Task<IActionResult> Authenticate(MyModel model) { ... }

This way, if you need to add more parameters in the future, you won't have to change the signature of the action.

Daniel Gabriel
  • 3,939
  • 2
  • 26
  • 37
  • I think there's more setup required to make that work. That is what I had originally and it did not work. Both `username` and `password` came in as null. – mwilson Jul 23 '19 at 20:38
  • Unless you changed the default setup of Web API, JSON support should be enabled. This seems more like a debugging issue. I would verify a couple of things: 1) is the right data present in the request when you receive it in Web API? 2) is the data correctly deserialized? For (1), put a breakpoint in the action and look at `this.Request`? For (2), if `this.Request` contains the expected data, ensure you are not removing JSON support somewhere (similar to this): https://stackoverflow.com/questions/10250098/disable-json-support-in-asp-net-mvc-web-api – Daniel Gabriel Jul 23 '19 at 21:18
  • That's definitely possible. There's some custom stuff going on (big application) so I wouldn't doubt it if someone messed with the web api settings somewhere. – mwilson Jul 23 '19 at 21:20
-1

You cannot do this w/ Web API controller, as the javascript cannot invoke the method unless an object is passed.

However, there are "technically" (2) ways to do this, w/o creating model classes in your .NET project.

  1. Pass FormDataCollection object

    public async Task<IActionResult> YourMethod(FormDataCollection form) { var whatever = form.Get("username"); var whatever2 = form.Get("password"); }

You'll of course have to set up your ajax call to pass the form collection object

  1. Pass JObject (dynamic JSON).

You'll need Newtonsoft.JSON library for this

public async Task<IActionResult> YourMethod(JObject json)
{
    dynamic jsonObj = json;
    var whatever = jsonObj.username;
    var whatever2 = jsonObj.password;
}

Above, you may have to add [FromBody] attribute in front of JObject (I forget). Alternatively, you can use dynamic instead of JObject

(ie - YourMethod(dynamic json) {....}

Rob Scott
  • 7,921
  • 5
  • 38
  • 63
  • You can. As stated in my comments, just passing the payload as `FormData` (opposed to json) does the trick. – mwilson Jul 23 '19 at 19:26
  • You try using the `dynamic` or `JObject` yet? And I did not read your comments, only the OP. Thx for the -1 though ;) – Rob Scott Jul 23 '19 at 19:29
  • Using `JObject` along with `dynamic` isn't what was asked. I provided an example of the method-setup I wanted. Your answer is the opposite of what I was going for and didn't provide any helpful input. – mwilson Jul 23 '19 at 19:34