WebApi - Bind from both Uri and Body

前端 未结 4 1536
小鲜肉
小鲜肉 2020-12-13 12:51

Is it possible to bind a model from both the Uri and Body?

For instance, given the following:

routes.MapHttpRoute(
    name: \"API Default\",
    rou         


        
4条回答
  •  失恋的感觉
    2020-12-13 13:09

    You can define your own DefaultActionValueBinder. Then you can mix and match from body and uri. Here is a blog post with an example of an MvcActionValueBinder for Web Api. Making your own DefaultActionValueBinderis a preferred solution because it guarantees the binder will have finished before any other ActionFilterAttribute are executed.

    http://blogs.msdn.com/b/jmstall/archive/2012/04/18/mvc-style-parameter-binding-for-webapi.aspx

    UPDATE:

    I had some trouble with the implementation in the blog post and trying to get it to use my custom media formatters. Luckily all my request objects extend from a base class of Request so I made my own formatter.

    in WebApiConfig

    config.ParameterBindingRules.Insert(0, descriptor => descriptor.ParameterType.IsSubclassOf(typeof (Request)) ? new BodyAndUriParameterBinding(descriptor) : null);
    

    BodyAndUriParameterBinding.cs

    public class BodyAndUriParameterBinding : HttpParameterBinding
    {
        private IEnumerable 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 ReadContentAsync(HttpRequestMessage request, Type type,
            IEnumerable formatters, IFormatterLogger formatterLogger, CancellationToken cancellationToken)
        {
            var content = request.Content;
            if (content == null)
            {
                var defaultValue = MediaTypeFormatter.GetDefaultValueForType(type);
                return defaultValue == null ? Task.FromResult(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);
            }
        }
    }
    
    
    

    Request.cs

    public abstract class Request : IValidatableObject
    {
        public virtual IEnumerable Validate(ValidationContext validationContext)
        {
            yield return ValidationResult.Success;
        }
    }
    

    提交回复
    热议问题