-1

I have a controller with the following setup for API requests:

endpoints.MapControllerRoute(
    name: "default",
    pattern: "{controller}/{action}/{id?}",
    defaults: new { controller = "Home", action = "Index" }
);

This works great for GET requests, but for POST requests my parameters do not seem to have any values. On the frontend, in JavaScript, I can see my parameters in the payload so I know they are there. However, my controller must not be set up correctly to take the POST requests.

Here is my GET request which works, and my POST which doesn't. I believe my issue is that I set the controller route to require the ? and take parameters. However, I still need to do post requests!

GET request:

public ActionResult Contacts([FromQuery] String DD_INPUT)
{

    //We have parameters here just in case we want to use them
    IEnumerable queryResult;
    String query = "Exec dbo.storedprocedure2 @DD_INPUT";

    using (var connection = new SqlConnection(connectionString))
    {
        queryResult = connection.Query(query, new { DD_INPUT = DD_INPUT });
    }

    return Ok(queryResult);

}

POST request:

[HttpPost]
public ActionResult AddFirm([FromBody] String FIRM_NAME)
{

    String query = "exec dbo.storeprocedername @FIRM_NAME";
    System.Diagnostics.Debug.WriteLine("value:" + FIRM_NAME);
    using (var connection = new SqlConnection(connectionString))
    {
        var json = connection.QuerySingle<string>(query, new { FIRM_NAME = FIRM_NAME});
        return Content(json, "application/json");
    }

}

POST Request JavaScript

axios.post(window.location.origin + '/API/AddFirm', {
    FIRM_NAME: this.FirmName
}).then(response => {
    this.FirmsArray = response.data;
}).catch(error => {
    console.log(error.response.data.error);
});
Jeremy Caney
  • 7,102
  • 69
  • 48
  • 77
Irish Redneck
  • 983
  • 7
  • 32
  • Does this answer your question? [FromBody string parameter is giving null](https://stackoverflow.com/questions/40853188/frombody-string-parameter-is-giving-null) – TDiblik Aug 12 '21 at 15:03
  • Those answers use models which I do not use. Could you post a answer that doesnt use a model? I have wasted so much time on this I cannot believe I cannot get a simple POST request to work from axios and asp.net core web api! – Irish Redneck Aug 12 '21 at 15:30
  • @Bradyboyy88 honestly, if you don't want to "waste" time, then you can probably read up on extensive docs on the "whys" of asp.net. If you want to go bare metal and understand ramifications, you _can_ - in that link above, you missed the part _"Of course you can skip the model binding and retrieve the provided data directly by accessing HttpContext.Request in the controller. The HttpContext.Request.Body property gives you the content stream or you can access the form data via `HttpContext.Request.Forms`. I personally prefer the model binding because of the type safety"_ – EdSF Aug 12 '21 at 16:07

3 Answers3

1

Your configuration up top is your service configuration where you configure that all controllers and all endpoints in your controllers have the format of "{controller}/{action}/{id?}".

You can configure how your route is built not only on API level like you did in your example, but also on Controller and Endpoint level:

i.e.

[ApiController]
[Route("{controller}s"}
public class FirmController : ControllerBase
{
    [HttpGet]
    [Route("/{firmId}/contacts/{contactId}"]
    public ActionResult GetContacts([FromRoute] int firmId, [FromRoute] int contactId)
    {
        ...
    }

    [HttpPost]
    public ActionResult AddFirm([FromBody] string firmName)
    {
        ...
    }
}

or even better, add a FirmModel for adding a new firm.

Also give https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design a read for properly designing an API.

Bruellhusten
  • 318
  • 1
  • 5
  • But, but,.. He can pass the argument in the body. He can either: a, `[FromBody] CustomModel model` or b, just say he's expecting `string model` – TDiblik Aug 12 '21 at 15:06
  • yeah that would be better for sure, but tried to stay as close to his example as possible. Changed my answer - thanks! – Bruellhusten Aug 12 '21 at 15:09
  • My json array which is in the POST request is like this {FIRM_NAME: "example name"}. I guess I assumed the POST request basically takes the parameters and I access them directly as I did in my code above. What is the correct way to do this? – Irish Redneck Aug 12 '21 at 15:32
  • if you have just one property that will work. The FirmModel is for when your firms have more than just a FirmName. – Bruellhusten Aug 12 '21 at 15:44
  • I have made it work by using models but I hate using models. Is there a approach that I can just grab the data directly from the request? – Irish Redneck Aug 12 '21 at 15:45
  • yes - [FromBody] string FirmName should work if your JSON looks like {FirmName: "name"}. It will stop working if you have more than one firm name. – Bruellhusten Aug 12 '21 at 15:46
  • @Bruellhusten I have posted, [in my opinion, optimal solution](https://stackoverflow.com/a/68761276/16638833) – TDiblik Aug 12 '21 at 16:43
1

So, the optimal solution would be this (Changes are explained in code comments):

    [HttpPost]
    public ActionResult AddFirm(string FIRM_NAME) // Can accept value from body AND query string. If you have more firm names then simply use List<string>/IEnumable<string> to represent it. If you have more parameters you want to pass, then simply write them like this: string FIRM_NAME, int NUM_OF_EMPLYEES and so on.
    {
        // Why are you using class String, when you can use the string keyword that does exactlly the same thing?
        String query = "exec dbo.storeprocedername @FIRM_NAME";
        System.Diagnostics.Debug.WriteLine("value:" + FIRM_NAME);
        using (var connection = new SqlConnection(connectionString))
        {
            var json = connection.QuerySingle<string>(query, new { FIRM_NAME = FIRM_NAME});
            return Content(json, "application/json"); // When sending JSON answear, be aware that every parameter from object will start with lowercased letter.
        }

    }

If you incist on using the [FromBody] tag, then you sadlly have to use models.

TDiblik
  • 522
  • 4
  • 18
  • I appreciate the answer but unfortunately it does not work in my case and does not pass the data. Do I need to convert the axios data field to a string using JSON.stringify? As of now axios when I look at the payload its a json array {FIRM_NAME: "example"}. I think this is where my issues have been and have been holding me up. Also, I have made using a model work for this but then when I try to use a model with more than one parameter it fails and for some reason the model object passes as null. I wil review the one link you posted originally to see if i can find something! – Irish Redneck Aug 12 '21 at 19:24
  • Hmm, weird, it works on my machine.... I am using Ajax tho, as I have nearly no experience with axios.... I also found [this resource](https://www.tutorialsteacher.com/webapi/parameter-binding-in-web-api) for Asp. net core, try and have a look at it... – TDiblik Aug 12 '21 at 21:01
  • I have tested so many combinations to try and get this stuff to work I need to go back and retry this but with the data corrected to {"key1": "dsdfd" , "key2":"sdfsf"}. That format of the json array that worked for the models and [FromBody]. I had to use String() for each JSON item. – Irish Redneck Aug 13 '21 at 11:09
  • When you got this to work what did your json array look like in the payload? I just cannot understand why it is working for you but not me ha. – Irish Redneck Aug 13 '21 at 13:52
1

if you want to use [frombody] in your action, you will have to stringify your data

const json =  JSON.stringify(this.FirmName);

 await axios.post(window.location.origin + '/API/AddFirm', json, {
  headers: {
        'Content-Type': 'application/json'
  }
}).then(response => {
  this.FirmsArray = response.data;
 }).catch(error => {
 console.log(error.response.data.error);
  });

I am only not sure about url that you are offering. Usually Api has a different url than application it is called from. And it usually looks like "/MyController/MyAction". Unfortunately you didn't post your controller header.

UPDATE if you need post several properties using [frombody] action you need to change your action too

Create view model

public class ViewModel
{
public string FirmName {get; set;} 
public string Email {get; set;}
}

action

[HttpPost]
public ActionResult AddFirm([FromBody] ViewModel viewModel)

ajax

data: JSON.stringify({FirmName: this.FirmName, Email: this.CurrentEmail}),
Serge
  • 40,935
  • 4
  • 18
  • 45
  • I am using this same approach for even more parameters as well and I found that JSON.stringify does not seem to work on for me when there is multiple parameters. For instance if I do const json = JSON.stringify({FIRM_NAME: this.FirmName, EMAIL: this.CurrentEmail}) then the API shows the parameters as being null or the model since I am using that now; So I have been forced to do const json ={FIRM_NAME: String(this.FirmName), EMAIL: String(this.CurrentEmail)}. I havent seen anyone been forced to do String() for each data field but its the only thing that worked for me. – Irish Redneck Aug 13 '21 at 19:58
  • I think the issue really comes down to the stringify. The data types from axios to the controller are SO picky that stringify really does the trick. – Irish Redneck Aug 24 '21 at 14:26
  • can you use this method to post files to the server as well? For instance if I had a file attachment component with multiple selected. Can I pass that through JSON then to the model? – Irish Redneck Aug 24 '21 at 23:24