Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
325 views
in Technique[技术] by (71.8m points)

c# - WebApi - Bind from both Uri and Body

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

For instance, given the following:

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; }
}

Is it possible to create a custom binder so that a PUT to

/api/products/1

with a JSON body of:

{
    "Name": "Product Name"
}

will result in the UpdateProduct model populated with Id = 1 and Name = "Product Name"?

Update

I understand that I could change the action signature to

public HttpResponseMessage Put(int id, UpdateProduct model)
{

}

However as stated in the question, I specifically want to bind to a single model object

I have also posted this question to the WebApi Codeplex discussion forum

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Here's an improved version of odyth's answer that:

  1. Works for bodiless requests too, and
  2. Gets parameters from the query string in addition to from route values.

For brevity I just post the ExecuteBindingAsyncCore method and a new auxiliary method, the rest of the class is the same.

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]);
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...