C# WebApi-从Uri和主体绑定
可以从Uri和主体绑定模型吗 例如,考虑到以下情况:C# WebApi-从Uri和主体绑定,c#,asp.net-web-api,C#,Asp.net Web Api,可以从Uri和主体绑定模型吗 例如,考虑到以下情况: routes.MapHttpRoute( name: "API Default", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); public class ProductsController : ApiController { public HttpResponseMessa
routes.MapHttpRoute(
name: "API Default",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
public class ProductsController : ApiController
{
public HttpResponseMessage Put(UpdateProduct model)
{
}
}
public class UpdateProduct
{
int Id { get; set;}
string Name { get; set; }
}
是否可以创建自定义活页夹,以便将放入
/api/products/1
JSON主体为:
{
"Name": "Product Name"
}
将导致UpdateProduct
模型填充Id=1
和Name=“Product Name”
更新
我理解我可以将行动签名更改为
public HttpResponseMessage Put(int id, UpdateProduct model)
{
}
但是,正如问题中所述,我特别希望绑定到单个模型对象
我还将此问题发布到了如果我理解你的意思,这应该是开箱即用的,例如,这对我来说很有用:
[HttpPost]
public ActionResult Test(TempModel model)
{
ViewBag.Message = "Test: " + model.Id +", " + model.Name;
return View("About");
}
public class TempModel
{
public int Id { get; set; }
public string Name { get; set; }
}
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
根据请求:localhost:56329/Home/Test/22
正文:{“名称”:“工具”}
我将模型的属性相应地设置为22和“工具”。好的,我想出了一个方法。基本上,我制作了一个动作过滤器,它将在从JSON填充模型后运行。然后,它将查看URL参数,并在模型上设置适当的属性。完整资料来源如下:
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
public class UrlPopulatorFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var model = actionContext.ActionArguments.Values.FirstOrDefault();
if (model == null) return;
var modelType = model.GetType();
var routeParams = actionContext.ControllerContext.RouteData.Values;
foreach (var key in routeParams.Keys.Where(k => k != "controller"))
{
var prop = modelType.GetProperty(key, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public);
if (prop != null)
{
var descriptor = TypeDescriptor.GetConverter(prop.PropertyType);
if (descriptor.CanConvertFrom(typeof(string)))
{
prop.SetValueFast(model, descriptor.ConvertFromString(routeParams[key] as string));
}
}
}
}
}
您可以定义自己的DefaultActionValueBinder。然后您可以从body和uri进行混合和匹配。下面是一篇博客文章,其中有一个用于Web Api的MvcActionValueBinder示例。制作您自己的DefaultActionValueBinderi是首选解决方案,因为它可以保证在执行任何其他ActionFilterAttribute之前完成绑定
更新:
我在博客文章中的实现遇到了一些问题,并试图让它使用我的自定义媒体格式化程序。幸运的是,我所有的请求对象都是从request
基类扩展而来的,所以我制作了自己的格式化程序
在WebApiConfig中
config.ParameterBindingRules.Insert(0, descriptor => descriptor.ParameterType.IsSubclassOf(typeof (Request)) ? new BodyAndUriParameterBinding(descriptor) : null);
BodyAndUriParameterBinding.cs
public class BodyAndUriParameterBinding : HttpParameterBinding
{
private IEnumerable<MediaTypeFormatter> Formatters { get; set; }
private IBodyModelValidator BodyModelValidator { get; set; }
public BodyAndUriParameterBinding(HttpParameterDescriptor descriptor)
: base (descriptor)
{
var httpConfiguration = descriptor.Configuration;
Formatters = httpConfiguration.Formatters;
BodyModelValidator = httpConfiguration.Services.GetBodyModelValidator();
}
private Task<object> ReadContentAsync(HttpRequestMessage request, Type type,
IEnumerable<MediaTypeFormatter> formatters, IFormatterLogger formatterLogger, CancellationToken cancellationToken)
{
var content = request.Content;
if (content == null)
{
var defaultValue = MediaTypeFormatter.GetDefaultValueForType(type);
return defaultValue == null ? Task.FromResult<object>(null) : Task.FromResult(defaultValue);
}
return content.ReadAsAsync(type, formatters, formatterLogger, cancellationToken);
}
public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext,
CancellationToken cancellationToken)
{
var paramFromBody = Descriptor;
var type = paramFromBody.ParameterType;
var request = actionContext.ControllerContext.Request;
var formatterLogger = new ModelStateFormatterLogger(actionContext.ModelState, paramFromBody.ParameterName);
return ExecuteBindingAsyncCore(metadataProvider, actionContext, paramFromBody, type, request, formatterLogger, cancellationToken);
}
// Perf-sensitive - keeping the async method as small as possible
private async Task ExecuteBindingAsyncCore(ModelMetadataProvider metadataProvider, HttpActionContext actionContext,
HttpParameterDescriptor paramFromBody, Type type, HttpRequestMessage request, IFormatterLogger formatterLogger,
CancellationToken cancellationToken)
{
var model = await ReadContentAsync(request, type, Formatters, formatterLogger, cancellationToken);
if (model != null)
{
var routeParams = actionContext.ControllerContext.RouteData.Values;
foreach (var key in routeParams.Keys.Where(k => k != "controller"))
{
var prop = type.GetProperty(key, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public);
if (prop == null)
{
continue;
}
var descriptor = TypeDescriptor.GetConverter(prop.PropertyType);
if (descriptor.CanConvertFrom(typeof(string)))
{
prop.SetValue(model, descriptor.ConvertFromString(routeParams[key] as string));
}
}
}
// Set the merged model in the context
SetValue(actionContext, model);
if (BodyModelValidator != null)
{
BodyModelValidator.Validate(model, type, metadataProvider, actionContext, paramFromBody.ParameterName);
}
}
}
公共类BodyAndUriParameterBinding:HttpParameterBinding
{
私有IEnumerable格式化程序{get;set;}
私有IBodyModelValidator BodyModelValidator{get;set;}
public BodyAndUriParameterBinding(HttpParameterDescriptor描述符)
:基(描述符)
{
var httpConfiguration=descriptor.Configuration;
Formatters=httpConfiguration.Formatters;
BodyModelValidator=httpConfiguration.Services.GetBodyModelValidator();
}
私有任务ReadContentAsync(HttpRequestMessage请求,类型,
IEnumerable格式化程序、IFormatterLogger格式化程序记录器、CancellationToken CancellationToken)
{
var content=request.content;
if(content==null)
{
var defaultValue=MediaTypeFormatter.GetDefaultValueForType(类型);
返回defaultValue==null?Task.FromResult(null):Task.FromResult(defaultValue);
}
返回content.ReadAsAsync(类型、格式化程序、格式化程序记录器、取消令牌);
}
公共覆盖任务ExecuteBindingAsync(ModelMetadataProvider metadataProvider,HttpActionContext actionContext,
取消令牌(取消令牌)
{
var paramFromBody=描述符;
变量类型=paramFromBody.ParameterType;
var request=actionContext.ControllerContext.request;
var formatterLogger=new ModelStateFormatterLogger(actionContext.ModelState,paramFromBody.ParameterName);
返回ExecuteBindingAsyncCore(metadataProvider、actionContext、paramFromBody、类型、请求、formatterLogger、cancellationToken);
}
//性能敏感-保持异步方法尽可能小
专用异步任务ExecuteBindingAsyncCore(ModelMetadataProvider metadataProvider,HttpActionContext actionContext,
HttpParameterDescriptor paramFromBody,类型类型,HttpRequestMessage请求,IFormatterLogger formatterLogger,
取消令牌(取消令牌)
{
var model=await ReadContentAsync(请求、类型、格式化程序、格式化程序记录器、取消令牌);
如果(型号!=null)
{
var routeParams=actionContext.ControllerContext.RouteData.Values;
foreach(routeParams.Keys.Where(k=>k!=“controller”)中的var键)
{
var prop=type.GetProperty(key,BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public);
if(prop==null)
{
继续;
}
var descriptor=TypeDescriptor.GetConverter(prop.PropertyType);
if(descriptor.CanConvertFrom(typeof(string)))
{
prop.SetValue(model,descriptor.ConvertFromString(routeParams[key]as string));
}
}
}
//在上下文中设置合并模型
SetValue(actionContext,model);
if(BodyModelValidator!=null)
{
验证(模型、类型、元数据提供程序、actionContext、paramFromBody.ParameterName);
}
}
}
Request.cs
public abstract class Request : IValidatableObject
{
public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
yield return ValidationResult.Success;
}
}
公共抽象类请求:IValidatableObject
{
公共虚拟IEnumerable验证(ValidationContext ValidationContext)
{
产生返回验证结果。成功;
}
}
以下是odyth答案的改进版本:
也适用于无体请求,并且
从查询字符串和路由值中获取参数
为了简洁起见,我刚刚发布了ExecuteBindingAsyncCore方法和一个新的辅助方法,该类的其余部分是相同的
private async Task ExecuteBindingAsyncCore(ModelMetadataProvider metadataProvider, HttpActionContext actionContext,
HttpParameterDescriptor paramFromBody, Type type, HttpRequestMessage request, IFormatterLogger formatterLogger,
CancellationToken cancellationToken)
{
var model = await ReadContentAsync(request, type, Formatters, formatterLogger, cancellationToken);
if(model == null) model = Activator.CreateInstance(type);
var routeDataValues = actionContext.ControllerContext.RouteData.Values;
var routeParams = routeDataValues.Except(routeDataValues.Where(v => v.Key == "controller"));
var queryStringParams = new Dictionary<string, object>(QueryStringValues(request));
var allUriParams = routeParams.Union(queryStringParams).ToDictionary(pair => pair.Key, pair => pair.Value);
foreach(var key in allUriParams.Keys) {
var prop = type.GetProperty(key, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public);
if(prop == null) {
continue;
}
var descriptor = TypeDescriptor.GetConverter(prop.PropertyType);
if(descriptor.CanConvertFrom(typeof(string))) {
prop.SetValue(model, descriptor.ConvertFromString(allUriParams[key] as string));
}
}
// Set the merged model in the context
SetValue(actionContext, model);
if(BodyModelValidator != null) {
BodyModelValidator.Validate(model, type, metadataProvider, actionContext, paramFromBody.ParameterName);
}
}
private static IDictionary<string, object> QueryStringValues(HttpRequestMessage request)
{
var queryString = string.Join(string.Empty, request.RequestUri.ToString().Split('?').Skip(1));
var queryStringValues = System.Web.HttpUtility.ParseQueryString(queryString);
return queryStringValues.Cast<string>().ToDictionary(x => x, x => (object)queryStringValues[x]);
}
专用异步任务ExecuteBindingAsyncCore(ModelMetadataProvider metadataProvider、HttpActionContext actionContext、,
HttpParameterDescriptor paramFromBody,类型类型,HttpRequestMessage请求,IFormatterLogger formatterLogger,
取消令牌(取消令牌)
{
var model=await ReadContentAsync(请求、类型、格式化程序、格式化程序记录器、取消令牌);
如果(model==null)model=Activator.CreateInstance(type);
var routeDataValues=actionContext.ControllerContext.RouteData.Values;
var路由图