I'm having problems with the routings in my MVC project not working...
I want all my views in the Views > Shared folder like this:
Error.cshtml (default)
Index.cshtml (default)
Overview.cshtml (custom that I made)
Recordings.cshtml (custom that I made)
I've then created one shared controller to handle all views like this:
public class SharedController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Error()
{
return View();
}
public ActionResult Overview()
{
return View();
}
public ActionResult Recordings()
{
return View();
}
}
My RouteConfig.cs looks like this:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// Map to specific pages under Shared controller:
routes.MapRoute("SharedPages", "{action}/{id}",
new { controller = "Shared", action = #"Overview|Recordings", id = UrlParameter.Optional });
// Use the default rout for all other pages:
routes.MapRoute("Default", "{controller}/{action}/{id}",
new { controller = "Shared", action = "Index", id = UrlParameter.Optional }
);
// Show the Error page for anything else (404):
routes.MapRoute("Error", "{*url}",
new { controller = "Shared", action = "Error" }
);
}
I want the routing to work like this:
://(url)/ (root - no action specified) --> Shared/Index.cshtml
://(url)/Index --> Shared/Index.cshtml
://(url)/Overview --> Shared/Overview.cshtml
://(url)/Recordings --> Shared/Recordings.cshtml
://(url)/whatever (or if an error occurs) --> Shared/Error.cshtml
But it's not working as expected. If I go to ://(url)/ (root), I get a HTTP 404 - The resource cannot be found. If I go to for example ://(url)/Overview, it's working fine.
How can I make it work like I want?
The order of how you map route is important and first matched route wins. That means that even if there is no resource there one it matches the route it will use it.
public static void RegisterRoutes(RouteCollection routes)
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// Use specific rout for all other pages:
routes.MapRoute("WhateverA", "WhateverA/{action}/{id}",
new { controller = "WhateverA", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute("WhateverB", "WhateverB/{action}/{id}",
new { controller = "WhateverB", action = "Index", id = UrlParameter.Optional }
);
// Map to specific pages under Shared controller:
routes.MapRoute("RootPages", "{action}/{id}",
new { controller = "Shared", action = "Index", id = UrlParameter.Optional });
// Show the Error page for anything else (404):
routes.MapRoute("Error", "{*url}",
new { controller = "Shared", action = "Error" }
);
}
The problem with the Default and SharedPages routes is that they conflict with each other. You may need to provide specific routes for other controllers if they exist. Other wise the other option is to use Attribute Routing for the other controllers and convention-based routing for your root routes and error
public static void RegisterRoutes(RouteCollection routes)
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
//Attribute routing
routes.MapMvcAttributeRoutes();
// Map to specific pages under Shared controller:
routes.MapRoute("RootPages", "{action}/{id}",
new { controller = "Shared", action = "Index", id = UrlParameter.Optional });
// Show the Error page for anything else (404):
routes.MapRoute("Error", "{*url}",
new { controller = "Shared", action = "Error" }
);
}
With controllers decorated accordingly
[RoutePrefix("WhatEver")]
public class WhatEverController : Controller {
//GET whatever
[HttpGet]
[Route("")]
public ActionResult Index() { ... }
}
Related
I have a site with controllers and two other areas with the respective controllers for each. One of the controllers within the area has a language contraint code say en. By default it works perfectly fine. But when I try to use the Route specification in the controllers it is building the routes in misleading way.
The RouteConfig.cs file looks like below
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
routes.LowercaseUrls = true;
AreaRegistration.RegisterAllAreas();
routes.MapRoute(
name: "DefaultWithLanguageAndOrg",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "MyProj.Website.WebApp.Controllers" }
);
}
Part of Area registration file looks like below:-
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Test_default",
"{lang}/Test/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new { lang = new LanguageRouteConstraint() },
new[] { "MyProj.Website.WebApp.Areas.Test.Controllers" }
);
}
And controller looks like below:-
[RouteArea("Test")]
[RoutePrefix("certificate")]
public class CertificationsController : Controller
{
[Route("Home")]
public ActionResult Home()
{
return View();
}
}
My expecation is to have the URL structure like site/en/Test/certificate/Home but I'm not able to add the prefix en before RouteArea.
Note:-
Tried adding en into the RouteArea like [RouteArea("en/Test")] it executes the action but expects the views folder to be moved inside en. That is not a proper solution, other routes without the Route specification will not work.
Tried adding Area and language contraint within the RoutePrefix like [RoutePrefix("{lang}/Test/certificate/Home")], it executes the action but not renders the view. It searches the view in the path like ~/Views/Certifications/Home.cshtml where Area Test is missing, it should be like ~/Test/Views/Certifications/Home.cshtml. And this format as well [RoutePrefix("en/{area}/certificate")] no luck.
You can override the View() of the Controller
like -
protected override ViewResult View(string ViewName, string masterName, object model)
{
return PrepareView(ViewName, masterName, model);
}
private ViewResult PrepareView(string ViewName, string masterName, object model)
{
renderview = base.View("~/Views/Shared/" + ViewName + ".cshtml", masterName, model);
return base.View(ViewName, masterName, model);
}
This is just a example code, similar to this you can override the path using your languagecode.
How do I map unmatched routes to the index action for that controller?
I'm using a client side router for routes like /Home/foo
routes.MapRoute(
name: "Test",
url: "{controller}/{*.}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
This currently results in a 404.
Your route that you used is correct, the problem is the orders of the routes that need to be added in write format:
for example if you have some routes like:
routes.MapRoute(
name: "PreTest",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Test",
url: "{controller}/{*.}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
So it is always matched with first route PreTest. Check your routes order. It is work like a dictionary that ordered. Check this for more information.
I would create an AuthorizeAttribute to handle your case. Then you can decorate your controller with that attribute.
Here's a small example to redirect your action base on a value in the route:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class RedirectAttribute:ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if(filterContext.Controller.ControllerContext.RouteData.Values.ContainsValue("Foo"))
{
//Redirect to the login for example
UrlHelper urlHelper = new UrlHelper(filterContext.HttpContext.Request.RequestContext);
string url = urlHelper.Action("actionName", "controllerName");
filterContext.Result = new RedirectResult(redirectUrl);
}
}
}
Here's how to use it in a controller:
[Redirect]
public class MyCustomController : AsyncController
{
public ActionResult Index()
{
return View();
}
public ActionResult Foo()
{
//It will redirect
return View();
}
}
I have a website in MVC4 that I am developing that requires some custom routing. It is a simple website with a few pages. For example:
/index
/plan
/investing
... etc.. a few others
Through an admin panel the site administrator can create "branded" sites, that basically mirror the above content, but swap out a few things like branded company name, logo etc. Once created, the URLs would look like
/{personalizedurl}/index
/{personalizedurl}/plan
/{personalizedurl}/investing
... etc... (exact same pages as the non branded pages.
I am validating the personalized urls with an action filter attribute on the controller method and returning a 404 if not found in the database.
Here is an example of one of my actions:
[ValidatePersonalizedUrl]
[ActionName("plan")]
public ActionResult Plan(string url)
{
return View("Plan", GetSite(url));
}
Easy-peasy so far and works pretty well with the following routes:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Admin",
url: "Admin/{action}/{id}",
defaults: new { controller = "Admin", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{action}",
defaults: new { controller = "Default", action = "Index" }
);
routes.MapRoute(
"Branded", // Route name
"{url}/{action}", // URL with parameters
new { controller = "Default", action = "Index" } // Parameter defaults
);
/*
routes.MapRoute(
"BrandedHome", // Route name
"{url}/", // URL with parameters
new { controller = "Default", action = "Index" } // Parameter defaults
);
*/
}
The problem I currently have is with the bottom commented out route. I'd like to be able to go to /{personalizedurl}/ and have it find the correct action (Index action in default controller). right now with the bottom line commented out, I get a 404 because it thinks its an action and its not found. When I un-comment it, the index pages, work however the individual actions do not /plan for example because it thinks its a pUrl and can't find it in the database.
Anyway, sorry for the long question. Any help or suggestions on how to set this up would be greatly appreciated.
James
The problem is that MVC will use the first matching url and since the second route is:
routes.MapRoute(
name: "Default",
url: "{action}",
defaults: new { controller = "Default", action = "Index" }
);
and that matches your /{personalizedurl}/ it will route to Default/{action}.
What you want gets a bit tricky! I assume the personalizing is to be dynamic, not some static list of branded companies and you wouldn't want to recompile and deploy every time you add/remove a new one.
I think you will need to handle this in the controller, it won't work well in routing; unless it is a static list of personalized companies. You will need the ability to check if the first part is one of your actions and to check if it is a valid company, I will give you an example with simple string arrays. I believe you will be building the array by query some sort of data store for your personalized companies. I also have created a quick view model called PersonalizedViewModel that takes a string for the name.
Your routing will be simplified:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Admin",
url: "Admin/{action}/{id}",
defaults: new { controller = "Admin", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{url}/{action}",
defaults: new { controller = "Default", action = "Index", url = UrlParameter.Optional }
);
}
Here is the view model my example uses:
public class PersonalizedViewModel
{
public string Name { get; private set; }
public PersonalizedViewModel(string name)
{
Name = name;
}
}
And the Default controller:
public class DefaultController : Controller
{
private static readonly IEnumerable<string> personalizedSites = new[] { "companyA", "companyB" };
private static readonly IEnumerable<string> actions = new[] { "index", "plan", "investing", "etc" };
public ActionResult Index(string url)
{
string view;
PersonalizedViewModel viewModel;
if (string.IsNullOrWhiteSpace(url) || actions.Any(a => a.Equals(url, StringComparison.CurrentCultureIgnoreCase)))
{
view = url;
viewModel = new PersonalizedViewModel("Default");
}
else if (personalizedSites.Any(s => s.Equals(url, StringComparison.CurrentCultureIgnoreCase)))
{
view = "index";
viewModel = new PersonalizedViewModel(url);
}
else
{
return View("Error404");
}
return View(view, viewModel);
}
public ActionResult Plan(string url)
{
PersonalizedViewModel viewModel;
if (string.IsNullOrWhiteSpace(url))
{
viewModel = new PersonalizedViewModel("Default");
}
else if (personalizedSites.Any(s => s.Equals(url, StringComparison.CurrentCultureIgnoreCase)))
{
viewModel = new PersonalizedViewModel(url);
}
else
{
return View("Error404");
}
return View(viewModel);
}
}
I have Login action method in my home controller like this
[HttpGet]
public ActionResult Login()
{
return View();
}
I am having this Action method as start page of my application, however I want to re-write it like this
www.abc.com/MySite/security/login
I write this attribute after [HttpGet]
[Route("MySite/security/Login")]
Now the problem is,when I am running the application,its giving me error
The resource cannot be found.
This is my RoutConfig
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Default" ,
url: "{controller}/{action}/{id}" ,
defaults: new { controller = "Home" , action = "Login" , id = UrlParameter.Optional }
);
}
How can I fix this issue,Also I am having same name method with HttpPost attribute,should I have to write Rout Attribute on it as well?
This should do the work:
[RoutePrefix("MySite/Security")]
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpGet]
[HttpPost]
[Route("Login")]
public ActionResult Login()
{
return View("~/Views/Home/Index.cshtml");
}
}
EDITED:
There is one way, but I'm not sure if it's the best way. You need to create another controller called DefaultController like this:
public class DefaultController : Controller
{
//
// GET: /Default/
public ActionResult Index()
{
return RedirectToAction("Login","Home");
}
}
In your RouteConfig.cs, change the 'Default' route with this:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Default", action = "Index", id = UrlParameter.Optional }
);
This should do the job. I'm still trying to find other better ways.
First, you should add custom route on the top of a default route, since you have 2 action methods with different HTTP protocols and want to make custom routing with same action name.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
// custom route
routes.MapRoute(
name: "Login",
url: "MySite/{controller}/{action}/{id}",
defaults: new { controller = "Security", action = "Login", id = UrlParameter.Optional }
);
// default route
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home" , action = "Index" , id = UrlParameter.Optional }
);
}
Note that your controller with Login method should be named SecurityController, then you can set attribute routing like this code:
// set all default prefix to /Security path
[RoutePrefix("Security")]
public class SecurityController : Controller
{
[Route("Login")]
public ActionResult Login()
{
return View();
}
}
Additionally, make sure you already registered the route in Global.asax file.
Any improvements & suggestions welcome.
In my project there is an action
public ActionResult Lead(int leadId)
{
return View();
}
and in the View an ActionLink was created like this
#Html.ActionLink("Old Link", "Lead", "Home", new { leadId = 7 }, null)
But after some time, to make clean URL, I have changed the name of parameter of that action
public ActionResult Lead(int id)
{
return View();
}
And ActionLink change accordingly
#Html.ActionLink("New Link", "Lead", "Home", new { id = 5 }, null)
But old link was shared in multiple social network sites. Whenever anyone clicks on that old link, he is redirect to the page www.xyx.com/Home/Lead?leadId=7
But now in my application, no such URL exists.
To handle this problem, I was thinking of overloading, but MVC action doesn't support overloading.
I have created another Action with same name with extra parameter, and redirect to new action, but it doesn't work.
public ActionResult Lead(int leadId, int extra=0)
{
return RedirectToAction("Lead", "Home", new { id = leadId });
}
I have found one link to handle such situation, but It is not working in my case.
ASP.NET MVC ambiguous action methods
One possibility to handle this would be to write a custom route:
public class MyRoute : Route
{
public MyRoute() : base(
"Home/Lead/{id}",
new RouteValueDictionary(new
{
controller = "Home",
action = "Lead",
id = UrlParameter.Optional,
}),
new MvcRouteHandler()
)
{
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var rd = base.GetRouteData(httpContext);
if (rd == null)
{
return null;
}
var leadId = httpContext.Request.QueryString["leadid"];
if (!string.IsNullOrEmpty(leadId))
{
rd.Values["id"] = leadId;
}
return rd;
}
}
that you will register before the default one:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add(new MyRoute());
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
and now you could only have a single action:
public ActionResult Lead(int id)
{
return View();
}
Now both the following urls will work as expected:
www.xyx.com/Home/Lead/7
www.xyx.com/Home/Lead?leadId=7