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.
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 }....
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.
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");
});
}
}
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);
});
}
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 }
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);
});
}
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.
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
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) });
}
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With