Angular JS MVC Web API Model/ Parameter not binding .NET Core

前端 未结 3 1232
执念已碎
执念已碎 2021-01-12 17:08

I am using Angular JS with TypeScript and ASP.NET Core MVC/API.

I have an apiService which deals with all POST and GET reques

相关标签:
3条回答
  • 2021-01-12 17:22

    You sent { companyId: selectedCompany.id } which is an object with has a field companyId that has the value selectedCompany.id;

    Either switch to a GET request as said answer or create a wrapper class having one integer field companyId and use it as input to your function on server-side.

    Other thing you can try :

    • send only the integer instead of an object
    • send the integer as string, the negine may parse it as an int.
    0 讨论(0)
  • 2021-01-12 17:26

    Its always better to do a GET instead of POST for get requests. If you would like to change it to a GET method, then make $http.get and in api attribute, have it like:

        [HttpGet] //not required, this will be get as per naming convention
        [Route("GetAssetListByCompany/{companyId}")]
        public IActionResult GetAssetListByCompany(int companyId)
    

    But if you still need to do a POST, then your data in $http.post should look like data: '"123456"'

    $http.post("/api/Dashboard/GetAssetListByCompany", '"123456"' )
    

    The above workaround is needed when you pass primitive data to api and that explains why it worked fine when you passed your viewmodel.

    UPDATE: After further discussion, OP needs to POST multiple primitive types to api and need to access the data using a key without having dependency with the order in which the model is being bind.

    One option is to use Dictionary as input param type

     [HttpPost]
     [Route("GetAssetListByCompany")]
     public IHttpActionResult GetAssetListByCompany(Dictionary<string,int> data)
            {
                return Ok();
            }
    

    and in client side:

        var data = {};
        data["CustomerId1"] = 123;
        data["CustomerId2"] = 345;
        data["CustomerId3"] = 1623;
        data["CustomerId4"] = 7655;
    
        $http.post("/api/Dashboard/GetAssetListByCompany", data);
    

    Result

    0 讨论(0)
  • 2021-01-12 17:35

    The best approach here is to follow the HTTP guidelines and change your action from POST to GET as you are not modifying any data. This is fairly simple to do and still be able to send data with your request using the URI.

    MVC changes

    See Model Binding for the various options, the best approach here is to bind based on the query string as you want only a single primitive type. If you had an array of primitive types you could still bind to the query string, the query string variable name would be repeated once for each value.

    So the only changes we make are to specify that the parameter is coming from the Query string and that it is associated with an Http Get request instead of Post.

    [Route("api/[controller]")]
    public class DashboardController : BaseController
    {
        [HttpGet] // change to HttpGet
        [Route("GetAssetListByCompany")]
        public IActionResult GetAssetListByCompany([FromQuery]int companyId) // use FromQuery
        {
            return CreateJsonResult(() =>
            {
                if (companyId == 0) { return new xPTJsonResult(null, xPTStatusCodesEnum.Success, "Company Id is 0"); }
    
               //var treeModel = _dashboardProvider.GetTreeModelByCompany(companyId, userModel);
    
                return new xPTJsonResult(null, xPTStatusCodesEnum.Success, "Loaded assets successfully");
    
            });
        }
    }
    

    AngularJS changes

    We extend the apiService to allow passing for data for calls using HttpGet. This can be done using the params on the $http call, it will dynamically create the URL based on the passed in data using the name as the query string value name and the value as the value part.

    export class apiService {
        /* all other code is left as is, just change the get method to also accept data via the params. If null is passed in then it is ignored. */
        get(url, config, data, success, failure) {
            return this.$http({
                url: url,
                config: config,
                params: data,
                method: "GET"
                })
                .then(result => { this.handleResponse(result, success); }, result => { this.handleError(result, failure) });
        }
    }
    

    On the call we just need to change from post to get and it should work.

    // only change from post to get
    onCompanyChanged(selectedCompany, model, companyName) {
        this.apiService.get('/api/Dashboard/GetAssetListByCompany', { companyId: selectedCompany.id },
            response => {
                this.assetListViewModel = response.data.data;
            }, response => {
            this.notificationService.displayError(response.data.message);
        });
    }
    

    Edit - This is flexible

    One more important note, this design is flexible on the angular side. If you extend your MVC Action or have various actions that take additional parameters it works without having to implement any other changes. Example:

    [HttpGet]
    [Route("GetSomethingElseFromServer")]
    public IActionResult GetSomethingElseFromServer([FromQuery]int companyId, [FromQuery]string assetName, [FromQuery]string companyModelNumber) // use FromQuery
    

    the call to your angular api would be

    this.apiService.get('/api/Dashboard/GetSomethingElseFromServer', { companyId: companyId, assetName: somePassedInAssetNameVar, companyModelNumber: somePassedInModelNumber }
    

    Edit - You can also send arrays

    To answer the question on how to send multiple primitive types as an array you can do that this way. Again this assumes that its not a complex type that you are sending but, for example, a list of company ids.

    c# code

    [HttpGet]
    [Route("GetAssetListByCompany")]
    public IActionResult GetAssetListByCompany([FromQuery]int[] companyIds) // use an array of int ie. int[]. i changed the variable name to make it clear there can be more than 1
    

    Angular call, note there is no need to change the service

    onCompanyChanged(selectedCompany, model, companyName) {
        this.apiService.get('/api/Dashboard/GetAssetListByCompany', { "companyIds[]": [id1, id2, id3] }, // note the name is now enclosed in quotes, made plural, and includes []. The value is an array
            response => {
                this.assetListViewModel = response.data.data;
            }, response => {
            this.notificationService.displayError(response.data.message);
        });
    }
    

    Edit - If you want to POST anyways

    You are curretly only sending a single primitive field so this will not be correctly deserialized by the MVC framework in a POST. You need to either wrap the parameter in a view model, send it as a query string part, or send it as a form field value. Here is the POST with a query string part which works just fine.

    Option 1

    Append it to the URL

    [HttpPost] // change to HttpGet
    [Route("GetAssetListByCompany")]
    public IActionResult GetAssetListByCompany([FromQuery] int companyId) // use FromQuery
    

    Angular call

    this.apiService.post('/api/Dashboard/GetAssetListByCompany/?companyId=' + selectedCompany.id + , null, // the rest of the code remains unchanged so I did not include it
    

    Option 2

    Extend the apiService to also take the params object so it can build up your query. Either way you are stuck with the caller having to know a little bit about the http call being made.

    this.apiService.post('/api/Dashboard/GetAssetListByCompany', null, {companyId: selectedCompany.id}, null, // the rest of the code remains unchanged so I did not include it
    
    post(url, config, data, params, success, failure) {
        return this.$http({
            url: url,
            config: config,
            data: data,
            params: params,
            method: "POST"
            })
            .then(result => { this.handleResponse(result, success); }, result => { this.handleError(result, failure) });
    }
    

    Option 3

    Update your view model to take a complex type, this requires no changes to your angular code.

    public class ListByCompanyModel {
        public int CompanyId {get;set;}
    }
    
    [HttpPost] // change to HttpGet
    [Route("GetAssetListByCompany")]
    public IActionResult GetAssetListByCompany([FromBody] ListByCompanyModel model) // use FromQuery
    
    0 讨论(0)
提交回复
热议问题