Angular JS MVC Web API 模型/参数未绑定 .NET Core
Angular JS MVC Web API Model/ Parameter not binding .NET Core
我正在使用 Angular JS 与 TypeScript 和 ASP.NET 核心 MVC/API。
我有一个 apiService
处理对服务器的所有 POST
和 GET
请求,它看起来像这样:
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);
}
}
}
}
现在当我发送这个请求时:
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);
});
}
它没有绑定控制器中的 companyId
这是控制器:
[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");
});
}
}
即使当我在浏览器中检查请求时,也显示 companyId 在有效负载中。
NOTE: The same function works when I post a ViewModel
编辑
在上面的场景中,我只将一个参数传递给控制器,但在某些情况下,我希望能够在不使用 ViewModel 的情况下传递 2 或 3 个参数。
例如
public IActionResult GetAssetListByCompany([FromBody]int companyId, [FromBody]int assetId)
{....
或
public IActionResult GetAssetListByCompany([FromBody]int companyId, [FromBody]int assetId, [FromBody]bool canEdit = false)
{.....
然后在客户端我可以这样做:
this.apiService.post('/api/Dashboard/GetAssetListByCompany', { companyId: selectedCompany.id, assetId: 123 }.....
或
this.apiService.post('/api/Dashboard/GetAssetListByCompany', { companyId: selectedCompany.id, canEdit: true, assetId: 22 }....
GET 请求总是比 POST 更好。如果您想将其更改为 GET 方法,请制作 $http.get 并在 api 属性中,将其设置为:
[HttpGet] //not required, this will be get as per naming convention
[Route("GetAssetListByCompany/{companyId}")]
public IActionResult GetAssetListByCompany(int companyId)
但如果您仍然需要执行 POST,那么您在 $http.post 中的数据应该类似于数据:'"123456"'
$http.post("/api/Dashboard/GetAssetListByCompany", '"123456"' )
将原始数据传递给 api 时需要上述解决方法,这解释了为什么在传递视图模型时它工作正常。
UPDATE:经过进一步讨论,OP需要POST多个原始类型到api并且需要使用密钥访问数据而不需要依赖绑定模型的顺序。
一种选择是使用字典作为输入参数类型
[HttpPost]
[Route("GetAssetListByCompany")]
public IHttpActionResult GetAssetListByCompany(Dictionary<string,int> data)
{
return Ok();
}
在客户端:
var data = {};
data["CustomerId1"] = 123;
data["CustomerId2"] = 345;
data["CustomerId3"] = 1623;
data["CustomerId4"] = 7655;
$http.post("/api/Dashboard/GetAssetListByCompany", data);
结果
这里最好的方法是遵循 HTTP 指南并将您的操作从 POST 更改为 GET,因为您没有修改任何数据。这非常简单,并且仍然能够使用 URI 随请求发送数据。
MVC 变化
有关各种选项,请参阅 Model Binding,这里最好的方法是基于查询字符串进行绑定,因为您只需要一个基本类型。如果您有一个原始类型数组,您仍然可以绑定到查询字符串,查询字符串变量名称将为每个值重复一次。
所以我们所做的唯一更改是指定参数来自查询字符串并且它与 Http Get 请求相关联而不是 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 变化
我们扩展了 api 服务以允许使用 HttpGet 为调用传递数据。这可以使用 params on the $http call 来完成,它将根据传入的数据动态创建 URL,使用名称作为查询字符串值名称,将值作为值部分。
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) });
}
}
在通话中,我们只需将 post
更改为 get
即可。
// 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);
});
}
编辑 - 这很灵活
还有一个重要的注意事项,这种设计在 angular 方面是灵活的。如果您扩展您的 MVC 操作或具有采用其他参数的各种操作,则无需实施任何其他更改即可工作。示例:
[HttpGet]
[Route("GetSomethingElseFromServer")]
public IActionResult GetSomethingElseFromServer([FromQuery]int companyId, [FromQuery]string assetName, [FromQuery]string companyModelNumber) // use FromQuery
您的 angular api 的电话是
this.apiService.get('/api/Dashboard/GetSomethingElseFromServer', { companyId: companyId, assetName: somePassedInAssetNameVar, companyModelNumber: somePassedInModelNumber }
编辑 - 您也可以发送数组
要回答有关如何将多个基本类型作为数组发送的问题,您可以这样做。同样,这假设您发送的不是复杂类型,而是例如公司 ID 列表。
c#代码
[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调用,注意不用改服务
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);
});
}
编辑 - 如果你想 POST 无论如何
您目前只发送一个原始字段,因此 MVC 框架无法在 POST 中正确反序列化。您需要将参数包装在视图模型中,将其作为查询字符串部分发送,或者将其作为表单字段值发送。这是带有查询字符串部分的 POST,效果很好。
选项 1
将其附加到 URL
[HttpPost] // change to HttpGet
[Route("GetAssetListByCompany")]
public IActionResult GetAssetListByCompany([FromQuery] int companyId) // use FromQuery
Angular 通话
this.apiService.post('/api/Dashboard/GetAssetListByCompany/?companyId=' + selectedCompany.id + , null, // the rest of the code remains unchanged so I did not include it
选项 2
扩展 api 服务以获取参数对象,以便它可以构建您的查询。无论哪种方式,调用者都必须知道一些有关正在进行的 http 调用的信息。
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) });
}
选项 3
更新您的视图模型以采用复杂类型,这不需要更改您的 angular 代码。
public class ListByCompanyModel {
public int CompanyId {get;set;}
}
[HttpPost] // change to HttpGet
[Route("GetAssetListByCompany")]
public IActionResult GetAssetListByCompany([FromBody] ListByCompanyModel model) // use FromQuery
您发送的 { companyId: selectedCompany.id }
是一个对象,其字段 companyId
的值为 selectedCompany.id
;
要么切换到 GET 请求作为上述答案,要么创建一个包装器 class 具有一个整数字段 companyId 并将其用作服务器端函数的输入。
您可以尝试的其他方法:
- 只发送整数而不是对象
- 将整数作为字符串发送,negine 可能会将其解析为 int。
我正在使用 Angular JS 与 TypeScript 和 ASP.NET 核心 MVC/API。
我有一个 apiService
处理对服务器的所有 POST
和 GET
请求,它看起来像这样:
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);
}
}
}
}
现在当我发送这个请求时:
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);
});
}
它没有绑定控制器中的 companyId
这是控制器:
[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");
});
}
}
即使当我在浏览器中检查请求时,也显示 companyId 在有效负载中。
NOTE: The same function works when I post a ViewModel
编辑
在上面的场景中,我只将一个参数传递给控制器,但在某些情况下,我希望能够在不使用 ViewModel 的情况下传递 2 或 3 个参数。
例如
public IActionResult GetAssetListByCompany([FromBody]int companyId, [FromBody]int assetId)
{....
或
public IActionResult GetAssetListByCompany([FromBody]int companyId, [FromBody]int assetId, [FromBody]bool canEdit = false)
{.....
然后在客户端我可以这样做:
this.apiService.post('/api/Dashboard/GetAssetListByCompany', { companyId: selectedCompany.id, assetId: 123 }.....
或
this.apiService.post('/api/Dashboard/GetAssetListByCompany', { companyId: selectedCompany.id, canEdit: true, assetId: 22 }....
GET 请求总是比 POST 更好。如果您想将其更改为 GET 方法,请制作 $http.get 并在 api 属性中,将其设置为:
[HttpGet] //not required, this will be get as per naming convention
[Route("GetAssetListByCompany/{companyId}")]
public IActionResult GetAssetListByCompany(int companyId)
但如果您仍然需要执行 POST,那么您在 $http.post 中的数据应该类似于数据:'"123456"'
$http.post("/api/Dashboard/GetAssetListByCompany", '"123456"' )
将原始数据传递给 api 时需要上述解决方法,这解释了为什么在传递视图模型时它工作正常。
UPDATE:经过进一步讨论,OP需要POST多个原始类型到api并且需要使用密钥访问数据而不需要依赖绑定模型的顺序。
一种选择是使用字典作为输入参数类型
[HttpPost]
[Route("GetAssetListByCompany")]
public IHttpActionResult GetAssetListByCompany(Dictionary<string,int> data)
{
return Ok();
}
在客户端:
var data = {};
data["CustomerId1"] = 123;
data["CustomerId2"] = 345;
data["CustomerId3"] = 1623;
data["CustomerId4"] = 7655;
$http.post("/api/Dashboard/GetAssetListByCompany", data);
结果
这里最好的方法是遵循 HTTP 指南并将您的操作从 POST 更改为 GET,因为您没有修改任何数据。这非常简单,并且仍然能够使用 URI 随请求发送数据。
MVC 变化
有关各种选项,请参阅 Model Binding,这里最好的方法是基于查询字符串进行绑定,因为您只需要一个基本类型。如果您有一个原始类型数组,您仍然可以绑定到查询字符串,查询字符串变量名称将为每个值重复一次。
所以我们所做的唯一更改是指定参数来自查询字符串并且它与 Http Get 请求相关联而不是 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 变化
我们扩展了 api 服务以允许使用 HttpGet 为调用传递数据。这可以使用 params on the $http call 来完成,它将根据传入的数据动态创建 URL,使用名称作为查询字符串值名称,将值作为值部分。
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) });
}
}
在通话中,我们只需将 post
更改为 get
即可。
// 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);
});
}
编辑 - 这很灵活
还有一个重要的注意事项,这种设计在 angular 方面是灵活的。如果您扩展您的 MVC 操作或具有采用其他参数的各种操作,则无需实施任何其他更改即可工作。示例:
[HttpGet]
[Route("GetSomethingElseFromServer")]
public IActionResult GetSomethingElseFromServer([FromQuery]int companyId, [FromQuery]string assetName, [FromQuery]string companyModelNumber) // use FromQuery
您的 angular api 的电话是
this.apiService.get('/api/Dashboard/GetSomethingElseFromServer', { companyId: companyId, assetName: somePassedInAssetNameVar, companyModelNumber: somePassedInModelNumber }
编辑 - 您也可以发送数组
要回答有关如何将多个基本类型作为数组发送的问题,您可以这样做。同样,这假设您发送的不是复杂类型,而是例如公司 ID 列表。
c#代码
[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调用,注意不用改服务
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);
});
}
编辑 - 如果你想 POST 无论如何
您目前只发送一个原始字段,因此 MVC 框架无法在 POST 中正确反序列化。您需要将参数包装在视图模型中,将其作为查询字符串部分发送,或者将其作为表单字段值发送。这是带有查询字符串部分的 POST,效果很好。
选项 1
将其附加到 URL
[HttpPost] // change to HttpGet
[Route("GetAssetListByCompany")]
public IActionResult GetAssetListByCompany([FromQuery] int companyId) // use FromQuery
Angular 通话
this.apiService.post('/api/Dashboard/GetAssetListByCompany/?companyId=' + selectedCompany.id + , null, // the rest of the code remains unchanged so I did not include it
选项 2
扩展 api 服务以获取参数对象,以便它可以构建您的查询。无论哪种方式,调用者都必须知道一些有关正在进行的 http 调用的信息。
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) });
}
选项 3
更新您的视图模型以采用复杂类型,这不需要更改您的 angular 代码。
public class ListByCompanyModel {
public int CompanyId {get;set;}
}
[HttpPost] // change to HttpGet
[Route("GetAssetListByCompany")]
public IActionResult GetAssetListByCompany([FromBody] ListByCompanyModel model) // use FromQuery
您发送的 { companyId: selectedCompany.id }
是一个对象,其字段 companyId
的值为 selectedCompany.id
;
要么切换到 GET 请求作为上述答案,要么创建一个包装器 class 具有一个整数字段 companyId 并将其用作服务器端函数的输入。
您可以尝试的其他方法:
- 只发送整数而不是对象
- 将整数作为字符串发送,negine 可能会将其解析为 int。