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

前端 未结 3 1233
执念已碎
执念已碎 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: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
    

提交回复
热议问题