开发者

MVC Routes based on POST parameters

We have an a PHP application that we are converting to MVC. The goal is to have the application remain identical in terms of URLs and HTML (SEO and the like + PHP site is still being worked on). We have a booking process made of 3 views and in the current PHP site, all these view post back to the same URL, sending a hidden field to differentiate which page/step in the booking process is being sent back (data between pages is stored in state as the query is built up).

To replicate this in MVC, we could have a single action method that all 3 pages post to, with a single binder that only populates a portion of the model depending on which page it was posted from, and the controller looks at the model and decides what stage is next in the booking process. Or if this is possible (and this is my question), set up a route that can read the POST parameters and based on the values of the POST parameters, route to a differen action method.

As far as i understand there is no support for this in MVC routing as it stands (but i would love to be wrong on this), so where would i need to look at extending MVC in order to support this? (i think multiple action methods is clea开发者_开发百科ner somehow).

Your help would be much appreciated.


I have come upon two solutions, one devised by someone I work with and then another more elegant solution by me!

The first solution was to specify a class that extends MVcRouteHandler for the specified route. This route handler could examine the route in Form of the HttpContext, read the Form data and then update the RouteData in the RequestContext.

MapRoute(routes, 
            "Book",
            "{locale}/book",
            new { controller = "Reservation", action = "Index" }).RouteHandler = new ReservationRouteHandler();

The ReservationRouteHandler looks like this:

public class ReservationRouteHandler: MvcRouteHandler
{
    protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        var request = requestContext.HttpContext.Request;

        // First attempt to match one of the posted tab types
        var action = ReservationNavigationHandler.GetActionFromPostData(request);

        requestContext.RouteData.Values["action"] = action.ActionName;
        requestContext.RouteData.Values["viewStage"] = action.ViewStage;

        return base.GetHttpHandler(requestContext);
    }        

The NavigationHandler actually does the job of looking in the form data but you get the idea.

This solution works, however, it feels a bit clunky and from looking at the controller class you would never know this was happening and wouldn't realise why en-gb/book would point to different methods, not to mention that this doesn't really feel that reusable.

A better solution is to have overloaded methods on the controller i.e. they are all called book in this case and then define your own custome ActionMethodSelectorAttribute. This is what the HttpPost Attribute derives from.

 public class FormPostFilterAttribute : ActionMethodSelectorAttribute
{
    private readonly string _elementId;
    private readonly string _requiredValue;

    public FormPostFilterAttribute(string elementId, string requiredValue)
   { 
        _elementId = elementId;
        _requiredValue = requiredValue;
    }

    public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo)
    {
        if (string.IsNullOrEmpty(controllerContext.HttpContext.Request.Form[_elementId]))
        {
            return false;
        }

        if (controllerContext.HttpContext.Request.Form[_elementId] != _requiredValue)
        {
            return false;
        }

        return true;
    }
}

MVC calls this class when it tries to resolve the correct action method on a controller given a URL. We then declare the action methods as follows:

public ActionResult Book(HotelSummaryPostData hotelSummary)
    {
        return View("CustomerDetails");
    }

    [FormFieldFilter("stepID", "1")]
    public ActionResult Book(YourDetailsPostData yourDetails, RequestedViewPostData requestedView)
    {
        return View(requestedView.RequestedView);
    }

    [FormFieldFilter("stepID", "2")]
    public ActionResult Book(RoomDetailsPostData roomDetails, RequestedViewPostData requestedView)
    {
        return View(requestedView.RequestedView);
    }

    [HttpGet]
    public ActionResult Book()
    {
        return View();
    }

We have to define the hidden field stepID on the different pages so that when the forms on these pages post back to the common URL the SelectorAttributes correctly determines which action method to invoke. I was suprised that it correctly selects an action method when an identically named method exists with not attribute set, but also glad.

I haven't looked into whether you can stack these method selectors, i imagine that you can though which would make this a pretty damn cool feature in MVC.

I hope this answer is of some use to somebody other than me. :)

0

上一篇:

下一篇:

精彩评论

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

最新问答

问答排行榜