6
votes

I'm building a simple report Web app for rendering reports using ASP.NET MVC3 + WebForms. The reports themselves are rendered by the ReportViewer ASP.NET WebForms control, but I'd like use ASP.NET MVC to create the parameter entry.

I'd like to have that all requests follow the default routing scheme of '~/{controller}/{action}/{parameters}', except requests for ~/Report, which should go to the report rendering WebForm. What's the right way to do this?

Expanding a bit..

I have two routes in Global.asax.cs - the default one and one for the WebForms page.

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapRoute(
        "Default", // Route name
        "{controller}/{action}/{id}", // URL with parameters
        new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
    );

    routes.MapPageRoute("report-rendering", "Report", "~/Render.aspx");
}

The URLs get rendered fine, but the problem with this is that when the request comes in, the first route also eats the URLs for the second one, i.e. ~/Report?id=7 tries to call the Index method on the ReportController (which doesn't exist).

If I change it so that the 'report-rendering' route comes before the 'Default' route, like so:

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapPageRoute("report-rendering", "Report", "~/Render.aspx");

    routes.MapRoute(
        "Default", // Route name
        "{controller}/{action}/{id}", // URL with parameters
        new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
    );
}

Now calls to the Html.ActionLink() render incorrect URLs, i.e.

`@Html.ActionLink("Report list", "Index", "ReportList")`

Renders

`http://localhost:49910/Report?action=Index&controller=ReportList`

My current workaround puts the 'Default' route first, while adding a regex constraint to ignore requests for the 'Report' controller, like so:

routes.MapRoute(
    "Default", // Route name
    "{controller}/{action}/{id}", // URL with parameters
    new { controller = "Home", action = "Index", id = UrlParameter.Optional }, // Parameter defaults
    new { controller = @"(?!report$).*" }
);

This doesn't feel clean. Again, What's the right way of doing this?

Also, I haven't yet decided how I'll pass the parameters to the rendering form: I could use both query parameters or POST them. I'm guessing that query params are more flexible. What's the best practice here?

EDIT:

While researching the answer by @LeftyX, seems like I've found an answer. To quote P. Haack from his Routing chapter in the Professional ASP.NET MVC 3 (Named Routes, Chapter 9, page 233):

... Use names for all your routes and always use the route name when generating URLs. Most of the time, letting Routing sort out which route you want to use to generate a URL is really leaving it to chance, which is not something that sits well with the obsessive-compulsive control freak developer. When generating a URL, you generally know exactly which route you want to link to, so you might as well specify it by name.

The mentioned section discusses a very similar situation to the one I described.

But since Html.ActionLink() doesn't have an overload with the route name parameter, does this mean I cannot reliably use it anywhere in the entire app if have a route like this?

1

1 Answers

1
votes

This is the best solution I've figured out.
I've registered my route with MapPageRoute (I've put my Report page under a folder called Reports)

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapPageRoute(
          "report-rendering",
          "Report/{id}",
          "~/Reports/Report.aspx"
         );

        routes.MapRoute(
            "Default", // Route name
            "{controller}/{action}/{id}", // URL with parameters
            new { controller = "Home", action = "Index", id = UrlParameter.Optional } 
            );
}

I've created my link using RouteLink so you can specify the route to use:

@Html.RouteLink("Report list", "report-rendering", new { id = 7 })

and I can get the id in my WebForm page like this:

protected void Page_Load(object sender, EventArgs e)
{
    var id = Page.RouteData.Values["id"] as string;
}

Hope it helps.

UPDATE:

I've created an Extension Method to make your life easier:

public static class ExtensionMethods
{
    public static MvcHtmlString WebFormActionLink(this HtmlHelper htmlHelper, string linkText, string ruoteName, object routeValues)
    {
        var helper = new UrlHelper(htmlHelper.ViewContext.RequestContext);

        var anchor = new TagBuilder("a");
        anchor.Attributes["href"] = helper.RouteUrl(routeName, routeValues);
        anchor.SetInnerText(linkText);
        return MvcHtmlString.Create(anchor.ToString());
    }
}

The best would have been to use ActionLink instead of WebFormActionLink but I have problems with signatures and I am not an expert on this.