Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

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

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

1 Answer

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...