ASP.NET MVC + WebForms - route conflict - asp.net-mvc

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?

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.

Related

How can I get my asp.net MVC routing to use a default action within a sub-folder?

I have an ASP.Net MVC app where I need to place a certain section of code in a sub-folder named web.
I would like for the user to be able to simply type "http://www.mywebsite.com/web/mycontroller" and have it default to the action of index.
However, I can't seem to figure out how to set up my routing to default the action. From what I can tell, it's trying to use the controller = web, action = mycontroller.
My route code looks like this:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"web", "web/{controller}/{action}",
new { action = "Index" });
}
The request works fine as long as I include the /index on the end of my URL, but it doesn't seem to want to use the action = "Index" default if I exclude it.
How can I accomplish this?
Change your code as follows:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "web",
url: "web/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
I ended up using Areas, and this has fixed my problem.

Is this the correct MVC route setup or is there another way?

I want to reserve the root of my website to be for standard webforms and have the MVC pages in a subdirectory Views so I have the following..
routes.MapRoute(
"Default", // Route name
"Views/{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
However, even though this works and I see a few work-arounds, I'm not quite happy that the RedirectToAction seems to direct me to the wrong page e.g.
return RedirectToAction("Index", "Home");
Takes me to http://localhost/Views which gives me a resource not found and the Index action on the HomeController doesn't fire. Is there a better way of implementing what I want here or am I missing something obvious?
As you know Views is kind of a reserved name in ASP.NET MVC. It's an existing directory. You could set the RouteExistingFiles to true in your route definitions:
public static void RegisterRoutes(RouteCollection routes)
{
routes.RouteExistingFiles = true;
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default",
"views/{controller}/{action}/{id}",
new { controller = "home", action = "index", id = UrlParameter.Optional }
);
}
Now when you navigate to http://example.com/views or http://example.com/views/homeor http://example.com/views/home/index it will be the Index action of Home controller that will get executed.

Routing in MVC not working

I have the following route:
routes.MapRoute(
"Property",
"{language}/property/{propertyUrlId}",
new { controller = "PropertyDetails", action = "Property" }
This is the Controller that should be called for that route:
public class PropertyDetailsController : Controller
{
public ActionResult Property(string language, string propertyUrlId)
{
etc.
And the following URL that should use that route:
http://domain.com/en-us/property/3
Instead, I get 404. Any ideas why?
Here are my routes:
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
"Property",
"property/{propertyUrlId}",
//new { controller = "PropertyDetails", action = "Property" }, new { language = #"[a-zA-Z]{2}-[a-zA-Z]{2}" }
new { controller = "PropertyDetails", action = "Property" }
);
}
Didn't work with language, or with language/country, either.
You most likely have registered the default route before your Property route. Default route typically looks like this:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
Just register your Property route BEFORE this default route and it will work.
Why it fails? (Assuming you are indeed registering default route first)
en-us -> is interpreted as controller
property -> is interpreted as action
Since you don't have a en-usController with a Property action -> 404
Use "en-us" as a segment of the URL is completely fine. I guess you have registered other routes as well. Try to bring this route to the top of others and at least on top of the default route.
I have tested the scenario, it works just fine for me.
Considering that you want to have the structure of the url as:
http://domain.com/en-us/property/3
use this routing:
routes.MapRoute(
"Property", // Route name
"{language}/property/{propertyUrlId}", // URL with parameters
new { controller = "PropertyDetails", action = "Property", propertyUrlId = UrlParameter.Optional } // Parameter defaults
);
if there is a default routing in your Global.asax file, like this:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
Put the routint above this block of code.
And your Controller Action should look like this:
public ActionResult Property(int propertyUrlId)
{
return View();
}
First of all, there is no reason to break {language} apart into two chunks in the route. As some of you stated, this is fine:
routes.MapRoute(
"Property",
"{language}/property/{propertyUrlId}",
new { controller = "PropertyDetails", action = "Property" }
Second, I omitted some information which was crucial to the solving of this problem. It didn't occur to me to include this in my problem description, as I didn't know there was any relationship. The MVC project is in a solution which also contains a website (non-MVC) which is using the Sitecore CMS as its datastore. Sitecore was stripping out the language segment of the URL and storing it, itself. Once I learned that this was happening, I was able to deal with the problem.
I appreciate all the input, and I apologize for the confusion.

ASP.NET MVC3 Routing various subfolders to the same controller

I'm trying to set up my MVC project to have URLs so that I can go to:
/Groups/
/Groups/Register
/Groups/Whatever
But in my controller, I can also flag some actions as admin only, so that they are accessed at:
/Admin/Groups/Delete/{id}
I would like to keep one GroupController, and have actions so that:
public class GroupController : Controller
{
public ActionResult Index(){
return View();
}
[AdminAction]
public ActionResult Delete(int id){
...
return View();
}
}
Allows:
/Groups is a valid URL.
/Admin/Groups is a valid URL (but would call some other action besides Index - maybe)
/Admin/Groups/Delete/{id} is a valid URL (post only, whatever)
/Groups/Delete is an INVALID url.
I realize this is probably a pretty broad question, but I'm new to MVC and I'm not really sure where to start looking, so if you could just point me in the right direction that would be hugely appreciated.
As we discussed in the comments below, while it is possible to use my original answer below to achieve the routing solution you requested, a better solution is to use Areas, establish an Admin area, and create controllers in your Admin area to handle the administrative tasks for different objects, such as Group, User, etc. This allows you to set up restricted administrative functions more easily, and is both a better design and a better security model.
ORIGINAL ANSWER
What you want can be accomplished by using the following routes:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Admin", // Route name
"admin/{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
However, as Akos said in the comments, it is a much better design to separate the administrative functions into a different controller. While this is possible, I would recommend against using this design.
UPDATE
It is possible to use a RouteConstraint on your Default route to make it fail if Admin actions are requested. The Default route would look like this:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional, // Parameter defaults
new { action = IsNotAdminAction() } // route constraint
);
The RouteConstraint would look like this:
public class IsNotAdminAction : IRouteConstraint
{
private string adminActions = "create~delete~edit";
public IsNotAdminAction()
{ }
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
// return false if there is a match
return !adminActions.Contains(values[parameterName].ToString().ToLowerInvariant());
}
}

ASP.NET MVC URL routing doesn't give me pretty URLs

I have set up an ASP.NET MVC project, and everything is working great, but I do have one problem with the routing. My Global.asax looks like this:
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 = "" } // Parameter defaults
);
}
So, nothing out of the ordinary. My problem is that when I link to a controller/action/params with an HTML.ActionLink like so:
<%= Html.ActionLink("My link", "SomeAction", "SomeController", new {param="someParam"})%>
it should generate (at least what makes sense in my head) a link such as: http://www.localhost/SomeController/SomeAction/someParam.
But instead it generates a link like this: http://localhost/SomeController/SomeAction?param=someParam
If i manually make a link that links to the expected result (SomeController/SomeAction/someParam) then the right controller and action are called, but the parameter defined in the action method is always null.
Any ideas?
try adding:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{param}", // URL with parameters
new { controller = "Home", action = "Index", param = "" } // Parameter defaults
);
I think that link will only use the default route like you expect if the parameter name is id instead of param. You'll have to create a different route if you want to provide some other parameter there.

Resources