Dismiss
Announcing Stack Overflow Documentation

We started with Q&A. Technical documentation is next, and we need your help.

Whether you're a beginner or an experienced developer, you can contribute.

Sign up and start helping → Learn more about Documentation →

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

I have an apiService which deals with all POST and GET requests to the server, which looks like this:

module TBApp {

    export class apiService {

        static $inject = ['$http', 'notificationService'];

        constructor(private $http, private notificationService: notificationService) {

        }

        get(url, config, success, failure) {

            return this.$http.get(url, config)

                .then(result => { this.handleResponse(result, success); }, result => { this.handleError(result, failure) });
        }

        post(url, data, success, failure) {

            return this.$http.post(url,data)
                .then(result => { this.handleResponse(result, success); }, result => { this.handleError(result, failure) });
        }

        handleResponse(result, success) {

            alert('success');
            success(result);

        }

        handleError(result, failure) {

            if (result.status === '401') {

                this.notificationService.displayError('Authentication required.');
                //this.$rootScope.previousState = this.$location.path();
                //this.$location.path('/login');

            }
            else if (failure !== null) {
                failure(result);
            }
        }
    }
}

Now when I send this request:

    onCompanyChanged(selectedCompany, model, companyName) {

        this.apiService.post('/api/Dashboard/GetAssetListByCompany', { companyId: selectedCompany.id },

            response => {

                this.assetListViewModel = response.data.data;


            }, response => {
            this.notificationService.displayError(response.data.message);
        });
    }

It is not binding the companyId in the controller

Here is the controller:

   [Route("api/[controller]")]
    public class DashboardController : BaseController
    {
        [HttpPost]
        [Route("GetAssetListByCompany")]
        public IActionResult GetAssetListByCompany([FromBody]int companyId)
        {
            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");

            });
        }

}

even though when I check the Request in the Browser, in shows that the companyId is in the Payload.

enter image description here

NOTE: The same function works when I post a ViewModel

EDIT

In the above scenario I am only passing one Parameter to the controller, but in some cases I want to be able to pass 2 or 3 parameters without using a ViewModel.

e.g.

public IActionResult GetAssetListByCompany([FromBody]int companyId, [FromBody]int assetId)
{....

OR

public IActionResult GetAssetListByCompany([FromBody]int companyId, [FromBody]int assetId, [FromBody]bool canEdit = false)
    {.....

and then on the client side I can do this:

this.apiService.post('/api/Dashboard/GetAssetListByCompany', { companyId: selectedCompany.id, assetId: 123 }.....

OR

this.apiService.post('/api/Dashboard/GetAssetListByCompany', { companyId: selectedCompany.id, canEdit: true, assetId: 22 }....
share|improve this question
2  
Don't use the name MVC6, it's called "ASP.NET Core MVC" and has the version number 1.0.0, not 6. – Tseng Aug 24 at 13:14
    
Is there a reason not to use a view model? – adem caglin Aug 24 at 15:05
    
@ademcaglin because there is only one parameter for this method - I don't want to create a ViewModel for all the methods taking one or two parameters - in the old MVC/API it was possible to post the {variableName: Value} – Dawood Awan Aug 24 at 15:06
up vote 2 down vote accepted
+100

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
share|improve this answer
    
you are right. But OP is looking for something more. Please check the discussions made in my answer to get more info – Developer Aug 26 at 13:34
    
@Igor I have to use POST in this case. If I pass this from the client: this.apiService.post('/api/Dashboard/GetAssetListByCompany', selectedCompany, null null) - The parameter seems to bind, but I need the { parameter_name : value } syntax so I can post two different parameters aswell – Dawood Awan Aug 26 at 13:43
    
@DawoodAwan - I do not see why you have to post. The params takes in as many parameters as you want. I can update it for Post though, its a similar concept but everything is transferred using the message body. See my update, Get works great for simple/primitive types and also where the query string is not longer than 2k characters (if i recall correctly). Give me a second, I will add a Post example. – Igor Aug 26 at 13:46
    
@DawoodAwan - I updated the answer with 3 options if you want to go with POST. – Igor Aug 26 at 13:56
    
@Igor - in option 2, instead of GET i think it should be POST - Cheers I will give these a try and get back to you – Dawood Awan Aug 26 at 14:00

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.
share|improve this answer

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 enter image description here

share|improve this answer
    
I have to use POST - your answer doesn't work - but even if it did what if we want to post 3 variables without a view model? e.g. {companyId: 1, newCompanyId: 2, newwCompanyId: 4} – Dawood Awan Aug 24 at 13:27
    
@Dawood - the above mentioned stuff works with $.ajax, may be i'll update after testing it with $http. Another approach is to send it via route parameter [Route("GetAssetListByCompany/{companyId1}/{companyId2}")] and update the action accordingly. Make sure you remove [FromBody] stuff – Developer Aug 24 at 14:38
    
if we use $.ajax with angular, - we have to add a $watch or $apply on the model in the angular controller for the update to take place in the view - otherwise it doesn't update the view with latest view model when changed automatically - i don't want to use jQuery - – Dawood Awan Aug 24 at 14:46
    
using [Route("GetAssetListByCompany/{companyId1}/{companyId2}")] means the company Id's have to be in the correct order - which again doesn't seem like a good solution - that is why I want to use this method: {companyId: 1, newCompanyId: 2, newwCompanyId: 4} , and the order of params doesn't matter - so the binding is by name – Dawood Awan Aug 24 at 14:47
1  
@DawoodAwan - Please find the updated answer. 1. data: '"123456"' - works. 2. For sending multiple items, use Dictionary<string,int> – Developer Aug 25 at 5:17

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.