One controller for whole ASP.NET MVC application - c#

I want to have one controller for all site urls
eg:
www.mysite.com/page1
www.mysite.com/another
www.mysite.com/third/some-text
www.mysite.com/fourth/some/some/some/some
Urls will be created manually, also url can be of "every type"(teoretically you must have posibility to put everything in url)
I don't want to put controller name or anything(action name or id) in the url.
How to write url route rule for this ? I am not intersted in disadvantages of such design.
This is default ASP.NET MVC routing rule
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);

I won't tell you this is a bad plan. But it is. Anyhow, if you don't need to handle an unlimited url folder depth, the trick is to run things in reverse order:
//maps virtual redirects
routes.MapRoute("virtual redirects",
"{redirect}",
new
{
area = "",
controller = "Content",
action = "VirtualRedirect"
}
);
routes.MapRoute("two level",
"{firstPart}/{secondPart}",
new
{
area = "",
controller = "Content",
action = "PageUrl",
thirdPart = ""
}
);
//maps general content pages
routes.MapRoute("page catcher",
"{firstPart}/{secondPart}/{thirdPart}",
new
{
area = "",
controller = "Content",
action = "PageUrl"
}
);
Note you'll want to put any more specific routes above this and you'll still need a default route declared after this for some parts of MVC to work.

I think you can do this way, just override routHandler
routes.MapRoute(
"dbroute",
"{controller}/{action}/{id}",
new { id = "" }
).RouteHandler = new MyRouteHandler();
and you new route handler like this
public class MyRouteHandler : IRouteHandler
{
#region IRouteHandler Members
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
IRouteHandler handler = new MvcRouteHandler();
var vals = requestContext.RouteData.Values;
if (vals["controller"] != null &&
(vals["controller"].ToString() == "page1" ||
vals["controller"].ToString() == "another" ||
vals["controller"].ToString() == "others" ) )
{
vals["controller"] = "Test";// your controller;
vals["action"] = "Index";//your action
}
return handler.GetHttpHandler(requestContext);
}
#endregion
}

igor,
unless there's a really compelling reason to do so, i'd think of alternative ways to present the urls that you want and think about the discreet functionality 'under the covers' that each logical unit is performing. a similar question was asked a while back which i responded to:
What is the advantage of using multiple Controller classes in ASP.NET MVC?
might give you a few ideas.

Related

Tenant area route with default route

So I have a site that has basically an 'area' per tenant. so it will show up as www.site.com/ and that will go to that groups page using an area.
Thing is I also have a default route for outside the area so you can go to www.site.com/ which will take you to the actual ~/Views/Home/Index page. However if you try to type www.site.com/Home/Index or say the page to create a new group www.site.com/Group/Create it thinks it needs to go to the area which that doesn't exist and gives the 404 resource cannot be found.
Here is the default route in the RouteConfig.cs
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new[] { "TicketSystem.Controllers" }
);
Here is the route config for the area:
context.MapRoute(
"Group_default",
"{group}/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new[] { "TicketSystem.Areas.Group.Controllers" });
so the {group} is whatever group you are currently visiting and then it goes to the regular controller/action for that group. However for the default route it still seems to go to the area route instead no matter what.
I was thinking that there could be a fallback. So when it tries to go to the area and it can't find the correct controller/action it will check the default route next. If it still can't find anything it will give the 404 error resource cannot be found. Though I am not exactly sure how to do this.
So to make www.site.com/ to work and allow www.site.com/Home/Index to work.
The problem is, When you try to access /Home/Index The route engine does not know by "Home" , you meant the controller name or a groupName!
To solve this, you can create a custom route constraint which checks whether the group value in the request url is a valid controller name in your app. If yes, The request won't be handled by the area route registration definition.
public class GroupNameConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName,
RouteValueDictionary values, RouteDirection routeDirection)
{
var asm = Assembly.GetExecutingAssembly();
//Get all the controller names
var controllerTypes = (from t in asm.GetExportedTypes()
where typeof(IController).IsAssignableFrom(t)
select t.Name.Replace("Controller", ""));
var groupName = values["group"];
if (groupName != null)
{
if (controllerTypes.Any(x => x.Equals(groupName.ToString(),
StringComparison.OrdinalIgnoreCase)))
{
return false;
}
}
return true;
}
}
Register this constraint when you register your area route.
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Group_default",
"{group}/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new { anything = new GroupNameConstraint() }
);
}
This should work assuming you will never have a groupName same as your controller name (Ex : Home )

If Route doesn't exist -> go to other route using its default action?

So I'm having a little problem here with routing.
There are two parts to this web application:
  1. Brochure / Display Website
  2. Internal Site / Client Application
We wanted a way to release changes for the brochure without having to do a whole release of said Web application.
Visiting existing named views will take the user to a brochure page, however if it doesn't exist, it will act like they are a client and will redirect them to their company's login screen.
Global.asax:
//if view doesnt exist then url is a client and should be redirected
routes.MapRoute(
name: "Brochure",
url: "{id}",
defaults: new { controller = "brochure", action = "Brochure", id = "Index" },
namespaces: new[] { "Web.Areas.Brochure.Controllers" }
);
//This is home page
routes.MapRoute(
name: "HomeDefault",
url: "{client}/{action}",
defaults: new { controller = "home", action = "index" },
namespaces: new string[] { "Web.Controllers" }
);
Controller:
/// <summary> Check if the view exists in our brochure list </summary>
private bool ViewExists(string name) {
ViewEngineResult result = ViewEngines.Engines.FindView(ControllerContext, name, null);
return (result.View != null);
}
/// <summary> Generic action result routing for all pages.
/// If the view doesn't exist in the brochure area, then redirect to interal web
/// This way, even when we add new pages to the brochure, there is no need to re-compile & release the whole Web project. </summary>
public ActionResult Brochure(string id) {
if (ViewExists(id)) {
return View(id);
}
return RedirectToRoute("HomeDefault", new { client = id });
}
This code works fine up until we log in and go to the landing page. It seems to keep the Brochure action in the route and doesn't want to go to the subsequent controller which results in a 500 error.
e.g. 'domain/client/Brochure' when it needs to be: 'domain/client/Index'
Things tried but not worked:
Changing RedirectToRoute() to a RedirectToAction() - this results in a
finite loop of going back to the ActionResult Brochure(). So
changing controllers through that didn't work.
Create an ActionResult called Brochure() inside the 'HomeController'. It
doesn't even get hit.
Passed in namespaces for RedirectToRoute() as an attribute. I knew this would probably not work, but it was worth a try.
So the question is:
How can I get the route to act properly?
If you can restrict id to some subset of all values you can add that constraints to route (i.e. numbers only) to let default handle the rest.
routes.MapRoute(
name: "Brochure",
url: "{id}",
defaults: new { controller = "brochure", action = "Brochure", id = "Index" },
namespaces: new[] { "Web.Areas.Brochure.Controllers" }
constraints : new { category = #"\d+"}
);
If you can't statically determine restrictions - automatically redirecting in your BrochureController similar to your current code would work. The only problem with sample in the question is it hits the same route again and goes into infinite redirect loop - redirect to Url that does not match first rule:
// may need to remove defaults from second route
return RedirectToRoute("HomeDefault", new { client = id, action = "index" });
If standard constraints do not work and you must keep single segment in url - use custom constraints - implement IRouteConstraint and use it in first route. See Creating custom constraints.
There are several issues with your configuration. I can explain what is wrong with it, but I am not sure I can set you on the right track because you didn't provide the all of the URLs (at least not all of them from what I can tell).
Issues
Your Brouchure route, which has 1 optional URL segment named {id}, will match any URL that is 0 or 1 segments (such as / and /client). The fact that it matches your home page (and you have another route that is named HomeDefault that will never be given the chance to match the home page) leads me to believe this wasn't intended. You can make the {id} value required by removing the default value id = "Index".
The Brouchure route has a namespace that indicates it is probably in an Area. To properly register the area, you have to make the last line of that route ).DataTokens["area"] = "Brochure"; or alternatively put it into the /Areas/Brouchure/AreaRegistration.cs file, which will do that for you.
The only way to get to the HomeDefault route is to supply a 2 segment URL (such as /client/Index, which will take you to the Index method on the HomeController). The example URLs you have provided have 3 segments. Neither of the routes you have provided will match a URL with 3 segments, so if these URLs are not getting 404 errors they are obviously matching a route that you haven't provided in your question. In other words, you are looking for the problem in the wrong place.
If you provide your entire route configuration including all Area routes and AttributeRouting routes (including the line that registers them), as well as a complete description of what URL should go to what action method, I am sure you will get more helpful answers.
So the question is:
How can I get the route to act properly?
Unknown. Until you describe what properly is.
Related: Why map special routes first before common routes in asp.net mvc?
Two ways I could have solved this issue:
Way 1
I reviewed the redirect and just passed in an action in order to get a route that has 2 segments in the url. i.e. client/Index. The Index action now handles logins - going past a custom controller.
public class HomeController : CustomController
public ActionResult Brochure(string id, string action) {
if (ViewExists(id)) {
return View(id);
}
return RedirectToAction("Index", "Home", new { client = id, action = "Index" });
}
Way 2
(from #Alexei_Levenkov)
Create a custom Route constraint so the route will be ignored if the view cannot be found.
namespace Web.Contraints {
public class BrochureConstraint : IRouteConstraint {
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) {
//Create our 'fake' controllerContext as we cannot access ControllerContext here
HttpContextWrapper context = new HttpContextWrapper(HttpContext.Current);
RouteData routeData = new RouteData();
routeData.Values.Add("controller", "brochure");
ControllerContext controllerContext = new ControllerContext(new RequestContext(context, routeData), new BrochureController());
//Check if our view exists in the folder -> if so the route is valid - return true
ViewEngineResult result = ViewEngines.Engines.FindView(controllerContext, "~/Areas/Brochure/Views/Brochure/" + values["id"] + ".cshtml", null);
return result.View != null;
}
}
}
namespace Web {
public class MvcApplication : System.Web.HttpApplication {
//If view doesnt exist then url is a client so use the 'HomeDefault' route below
routes.MapRoute(
name: "Brochure",
url: "{id}",
defaults: new { controller = "brochure", action = "Brochure", id = "Index" },
namespaces: new[] { "Web.Areas.Brochure.Controllers" },
constraints: new { isBrochure = new BrochureConstraint() }
);
//This is home page for client
routes.MapRoute(
name: "HomeDefault",
url: "{client}/{action}",
defaults: new { controller = "home", action = "index" },
namespaces: new string[] { "Web.Controllers" }
);
}
}
I hope this helps someone else out there.

The C# MVC 3 Routing want to add hard code word also in url

The C# MVC 3 Routing i have a controller name Category.
There are 2 Sub Method of the Controller
1)Index
2)Detail
now my detail routing URl coming like this "Category/name" ok but i also want to Add "category/Name-for-all" hard code "-for-all"
want to add this hard code how ?
routes.MapRoute(
"categorie", // Route name
"Category/{id}/{no}", // URL with parameters
new { controller = "Category", action = "details", id = "id",no=UrlParameter.Optional } // Parameter defaults
);
routes.MapRoute(
"categories", // Route name
"Category/{id}/{action}", // URL with parameters
new { controller = "Category", action = "action", id = "id" } // Parameter defaults
);
html page
<div class="grid_3" >
<a class="companyanchor" href="/category/#Model.name/">#Model.Name</a>
</div>
Its hard to understand your question, as I understand you asking about hard coded part of url? Something like this:
routes.MapRoute(null,
"{category}/Page{page}", // Matches /Football/Page567
new { controller = "Product", action = "List" }, // Defaults
new { page = #"\d+" } // Constraints: page must be numerical
);
I have not worked with MVC recently, but isn't this the answer?
routes.MapRoute("categories", // Route name
"Category/{id}"-for-all/{action}", // URL with parameters
new { controller = "Category", action = "action", id = "id" } // Parameter defaults
);
... not sure where "Name" comes from in your example.

MVC3 Route/redirect problem

My application is multilingual and I wrote the following route in order to handle the languages:
routes.MapRoute(
"Default", // Route name
"{language}/{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index",
language = "pt", id = UrlParameter.Optional }, // Parameter defaults
new { language = #"(pt)|(es)|(en)" }
);
This works for domain.com and domain.com/pt/home/index. However, if I type domain.com/home/index it fails (404).
The desired behavior would be it being redirected to domain.com/pt/home/index (pt is the default language).
Whats the best way to achieve this? I've been reading a lot about routes and ActionFilters but nothing seems quite right.
i would suggest using custom route handler like following
public class LanguageRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
IRouteHandler handler = new MvcRouteHandler();
var vals = requestContext.RouteData.Values;
if(vals["language"] == null)
{
vals["language"] = "pt";
}
return handler.GetHttpHandler(requestContext);
}
}
and have another route without language route value and set its route handler (global.asax)
routes.MapRoute(
"Default2", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
).RouteHandler = new LanguageRouteHandler();
This will not redirect Home/index to pt/home/index yet it will provide language = "pt" to your index action method (and all others). if you want to redirect you can implement an actionfilter but redirecting will create problems with post requests. For example when you post a form to /home/index and suppose it is redirected by action filter, the redirected request will lose posted form data
You need two routes, the other one without the language, or add the language parameter at the end
Try this
routes.MapRoute(
"Default", // Route name
"{language}/{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }, // Parameter defaults
new { language = #"(pt)|(es)|(en)" }
);
and modify your action in this way
public ActionResult Index([DefaultValue("pt")] string language)
{
...
}

ASP.NET MVC 2 Routing with additional consistent parameters (not just controller & action)

Currently, I have URLs that look like this:
http://www.example.com/user/create
http://www.example.com/user/edit/1
But now, I have to support multiple organizations and their users. I need to have something like this:
http://www.example.com/org-name/user/create
http://www.example.com/org-name/user/edit/1
I was having trouble getting the routes to work just perfectly, so I had to add a token to the beginning of the organization name so that routing wouldn't confuse it with a controller/action pair. Not a huge deal but my URLs look like this now:
http://www.example.com/o/org-name/user/create
http://www.example.com/o/org-name/user/edit/1
That's fine. I can live with that.
Here's where I'm running into trouble:
When I generate URLs once I have an organization selected, it's not persisting the organization name. So when I'm here:
http://www.example.com/o/org-name
...and I use Url.Action("User", "Create") to generate a URL, it outputs:
/user/create
...rather than what I want:
/o/org-name/user/create
This is what my routes look like (in order):
routes.MapRouteLowercase(
"DefaultOrganization",
"{token}/{organization}/{controller}/{action}/{id}",
new { id = UrlParameter.Optional },
new { token = "o" }
);
routes.MapRouteLowercase(
"OrganizationDashboard",
"{token}/{organization}/{controller}",
new { controller = "Organization", action = "Dashboard" },
new { token = "o" }
);
routes.MapRouteLowercase(
"DefaultSansOrganization",
"{controller}/{action}/{id}",
new { controller = "Core", action="Dashboard", id = UrlParameter.Optional }
);
It's similar to this question ASP.NET MVC Custom Routing Long Custom Route not Clicking in my Head.
I have a feeling this is going to end up being obvious but it's Friday and it's not happening right now.
EDIT:
Womp's suggested worked but would this be the best way to automate this?
public static string ActionPrepend(this UrlHelper helper, string actionName, string controllerName)
{
string currentUrl = helper.RequestContext.RouteData.Values["url"] as string;
string actionUrl = string.Empty;
if (currentUrl != null)
{
Uri url = new Uri(currentUrl);
if (url.Segments.Length > 2 && url.Segments[1] == "o/")
actionUrl = string.Format("{0}{1}{2}{3}", url.Segments[0], url.Segments[1], url.Segments[2],
helper.Action(actionName, controllerName));
}
if(string.IsNullOrEmpty(actionUrl))
actionUrl = helper.Action(actionName, controllerName);
return actionUrl;
}
EDIT:
Fixed my routes to work rather than hacking it together. The final solution didn't need the stupid {token} in the URL. Maybe this'll help someone else:
routes.MapRouteLowercase(
"Organization",
"{organization}/{controller}/{action}/{id}",
new { controller = "Organization", action = "Dashboard", id = UrlParameter.Optional },
new { organization = #"^(?!User|Account|Report).*$" }
);
routes.MapRouteLowercase(
"Default",
"{controller}/{action}/{id}",
new { controller = "Core", action = "Dashboard", id = UrlParameter.Optional }
);
Url.Action uses route values to generate the actual URL's by querying the virtual path provider and attempting to match the most specific route. In the form that you are using, you are supplying values for the controller and the action, which is as deep as most simple websites go, hence the convenient form of the method. When Url.Action queries the routing system, it only has a "controller" and an "action" segment to match.
If you give the method the rest of the routing information it needs, it will properly match the route that you desire, and will return the correct URL. Try this:
Url.Action("User", "Create", new { token = "o", organization = "organization" })

Categories