views:

518

answers:

2

My unfamiliarity with the ASP.NET MVC framework and the plumbing thereof has brought me here, and I appreciate the patience it will take for anyone to read and consider my question!

Okay, here is the scenario: I have an application that has numerous pages with grids that display data based on searches, drilling down from other data, reports based on context-specific data (i.e. they are on a details page for Foo, then click on a link that shows a table of data related to Foo), etc.

From any and all of these pages, which are all over the app, the user can save the "report" or grid by giving it a name and a description. This doesn't really save the data, displayed in the grid, so much as saves the parameters that define what the grid looks like, saves the parameters that were used to get the data, and saves the parameters that define "where" in the app they are (the action, controller, route) - basically a bunch of metadata about the report/grid and how to construct it.

All of these saved reports are available in a single list, displaying the name and description, on a certain page in the app, with each linking to a generic URL, like "/Reports/Saved/248" (where 248 is an example of the report's ID).

Here is the part I need help on:

When I get to the action via the url "/Reports/Saved/248" and pull the metadata out of the database for that particular report, how can I redirect that data and the request to the same action, controller and route used to display the view that the report was originally saved from? Essentially, I want the user to view the report in the same view, with the same URL as it was saved from. If possible, it would be nice for me to be able to basically "call" that same action as though I am making a method call.


UPDATE: Unfortunately, our report pages (i.e. the pages these grids appear on) are NOT using RESTful URLs - for example, we have what we call an Advanced Search page, which takes a rather large number of potential parameters (nearly 30) that come from a form containing select lists, textboxes, etc. When the user submits that page, we do a POST to an action which accepts a complex type that the model binder builds for us - that same action is what I want to call when the user selects a saved Advanced Search from the database. That example epitomizes my problem.

Thanks

A: 

You can use the RedirectToAction method to issue a 301 redirect to a specific action method on any controller, along with route values:

ReportMeta meta = _reportDataAccess.Get(id);
return RedirectToAction(meta.Action, meta.Controller, meta.RouteData);

where those values are something like:

meta.Action = "Bar";
meta.Controller = "Foo";
meta.RouteData = new {
    // possibly settings for the grid
    start = DateTime.Min,
    end = DateTime.Now,
    sort = "Date"
    // you get the idea
};

Of course, the immediate issue I can see with this is what happens when your controller/action methods change over time, the report data will be invalid. But then you probably thought of that already.

roryf
Thank you for repsonding, please see the update to my question.
Jason Bunting
+2  A: 

I think that you'll want to use RedirectToAction with the signature that takes a RouteValueDictionary. The method that you are redirecting to will need to be able to pull the values from the ValueProvider on the controller. It might look something like:

public ActionResult Saved( int id )
{
    var reportParams = db.Reports.SingleOrDefault( r => r.ID == id );
    if (reportParams == null)
       ...handle error...

    var routeValues = ParamsToRouteValueDictionary( reportParams );

    return RedirectToAction( reportParams.Action, reportParams.Controller, routeValues );
}

private RouteValueDictionary ParamsToRouteValueDictionary( object parameters )
{
     var values = new RouteValueDictionary();
     var properties = parameters.GetType().GetProperties()
                                .Where( p => p.Name != "Action" && p.Name != "Controller" );
     foreach (var prop in properties)
     {
         values.Add( prop.Name, prop.GetValue(parameters,null) );
     }

     return values;
}

EDIT

Using a filter model as the parameter for your method actually may make it easier. You just need GET and POST versions of your action.

 [ActionName("People")]
 [AcceptVerbs( HttpVerbs.Get )]
 public ActionResult PeopleDisplay( SearchModel filter )
 {
     return People( filter );
 }

 [AcceptVerbs( HttpVerbs.Post)]
 [ValidateAntiForgeryToken]
 public ActionResult People( SearchModel filter )
 {
     ....
 }

Then you would store in your db for the report the filter parameters (by name), the Action ("People"), and the Controller. The redirect result will use GET and be directed to the PeopleDisplay method, which in turns simply calls the People method with the correct parameter. Posting from the form calls the People method directly. Using two methods allows you to use the CSRF prevention mechanism. You might be able to use a flag in TempData to ensure that the GET action is only invoked via the redirection mechanism if you care to restrict access to it.

END EDIT

Another alternative, would be to simply store the View used as well and instead of doing a redirect, just render the appropriate view. One of the things that you'll want to consider is that doing the redirect will end up with a URL containing all the parameters, whereas rendering the View will leave the URL alone and just display the same view as the URL used when creating the report.

tvanfosson
Thank you for repsonding, please see the update to my question.
Jason Bunting
I think that the ModelBinder will work with either route parameters or form parameters indistinguisably -- since it uses the ValueProvider. The only think you would need to do is make sure that your action can accept either a GET or a POST. I'll update with a sample.
tvanfosson
Okay, thank you for your response - I will be able to test it out shortly. I appreciate your time!
Jason Bunting
As luck would have it, we did something completely different, but this is the best answer and you spent extra time coming back to respond to my edit, so I am marking this as the answer. Thanks again!
Jason Bunting
I'm curious Jason as to what solution you settled on? If you could post that here as another answer, it might be helpful to people like me landing here via google. :-)
Ogre Psalm33