开发者

MVC 3 Route Constraints that check the POST

So I have a method that accepts some JSON data and binds the data to some variables. The route only has the method name in it, nothing else.

Is it possible to have a route constraint that looks at the POST data and checks some variables to determine whether it is the correct route?

The methods:

public ActionResult GetDataV1(string userId)
{
    // Do stuff one way
}

public ActionResult GetDataV2(string userId)
{
    // Do stuff another way
}

The routes:

routes.MapRoute开发者_运维技巧(
    "API GetData V1",
    "GetData",
    new { controller = "API", action = "GetDataV1" },
    new { version = new APIVersionRoutingConstraint("1") }
);

routes.MapRoute(
    "API GetData V2",
    "GetData",
    new { controller = "API", action = "GetDataV2" },
    new { version = new APIVersionRoutingConstraint("2") }
);

The client would post { "version":"1", "userId":"mrjohn" } to /GetData and would get response from GetDataV1. The APIVersionRoutingConstraint would make sure the right method is called depending on the version variable.

Would it be good practice to try to deserialise the request stream inside the constraint? Maybe it would be better to put the version in the URL like /GetData/1 and the other variables in the JSON body?


Rather than trying to check the version as a route constraint, why not go to one Action that then checks the version to execute the appropriate work?

I haven't tested this, but the idea is:

public ActionResult GetData(string userId, int version)
{
    switch(version)
    {
        case 1:
            return GetDataV1(userId);
        case 2:
            return GetDataV2(userId);
        // You could always add more cases here as you get more versions, 
        // and still only have the one route
        default:
            // Probably more appropriate to return a result that contains error info,
            // but you get the idea
            throw new ArgumentOutOfRangeException("version");
    }
}

// Made these private since they are called from the public action
private ActionResult GetDataV1(string userId)
{
    // Do stuff one way
}

private ActionResult GetDataV2(string userId)
{
    // Do stuff another way
}

And then you only need the one route

routes.MapRoute(
    "GetData",
    "GetData",
    new { controller = "API", action = "GetData" },
    new { version = "\d+" } // Require a numeric version
);


Made the APIVersionRoutingConstraint like this:

public class APIVersionRoutingConstraint : IRouteConstraint
{
    private string _versionNumber;

    public APIVersionRoutingConstraint(string versionNumber)
    {
        _versionNumber = versionNumber;
    }

    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        string version = null;

        if (httpContext.Request.ContentType.Contains("application/json"))
        {
            string body = new StreamReader(httpContext.Request.InputStream).ReadToEnd();
            httpContext.Request.InputStream.Position = 0;
            var vals = new JavaScriptSerializer().DeserializeObject(body) as Dictionary<string, object>;
            if (vals.ContainsKey("version"))
                version = vals["version"].ToString();
        }

        // Must have version to pass constraint
        return !string.IsNullOrEmpty(version) && version == _versionNumber;
    }
}

It's not super-efficient I guess, but it gets the job done. Only the route that matches the version in the POST body gets used.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜