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);
}
}
Related
I've a strange behavior of configured route in Route Config in MVC5 app.
So, actually I try to do pretty much easy thing - change default action of a route.
To do that I've changed the defaults of a route, instead of having action = "Index", I've changed it to my required action AddUser (Look at the code below).
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "ManageUser",
url: "{controller}/{action}/{id}",
defaults: new { controller = "ManageUser", action = "AddUser", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
And I've next controller:
[Authorize(Roles ="Admin")]
public class ManageUserController : Controller
{
// GET: ManageUser/AddUser
[HttpGet]
public ActionResult AddUser()
{
return View();
}
}
So, now I expect that whenever user goes by URL: ManageUser/ he'll be redirected to defined default action but it doesn't happen, I just have 404 error. I can fix it simply by adding Index action and then redirect to AddUser, but it doesn't seem right to me. Could somebody help me understand what I've done wrong?
I think, your config should look like this:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"ManageUser",
"ManageUser/{action}/{id}",
new { controller = "ManageUser", action = "AddUser", id = UrlParameter.Optional }
);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
The first parameter is the name of the route. Second is the URL, which match URLs that start with ManageUser, and allows for other actions in your ManageUser controller. As you can see, it will default to the AddUser action.
And if you want to call AddUser with paramater, you must call it by full url ManageUser/AddUser/1
This question already has answers here:
Routing in ASP.NET MVC, showing username in URL
(2 answers)
Closed 4 years ago.
I have an Asp.Net MVC project whereby we allow our users to have public profiles.
I would like to improve the url, so that it is more friendly, and shorter.
The existing code is as follows -
public class ProfileController : Controller
{
private readonly IUserProfileService _userProfileService;
public ProfileController(IUserProfileService userProfileService)
{
this._userProfileService = userProfileService;
}
public ActionResult Index(string id)
{
//Get users profile from the database using the id
var viewModel = _userProfileService.Get(id);
return View(viewModel);
}
}
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
//Required for the route prefix attributes to work!
routes.MapMvcAttributeRoutes();
routes.MapRoute(
"ProfileUrlIndexActionRemoval",
"Profile/{id}",
new { controller = "Profile", action = "Index" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
The aforementioned code allows the following url to work (based on the default MVC routing) - www.mydomain.com/profile/john-doe
What routing do I need to implement, in order to allow the following url to work instead - www.mydomain.com/john-doe
Thanks.
This is a little tricky as you want the friendly URL in the root of the site while not conflicting with any other routes.
That would mean that if you have any other routes like About or Contact you would need to make sure that are in the route table before the friendly route to avoid route conflicts.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
//Required for the route prefix attributes to work!
routes.MapMvcAttributeRoutes();
routes.MapRoute(
"ProfileUrlIndexActionRemoval",
"Profile/{id}",
new { controller = "Profile", action = "Index" }
);
routes.MapRoute(
name: "Home",
url: "Home/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "About",
url: "About/{action}/{id}",
defaults: new { controller = "About", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Contact",
url: "Contact/{action}/{id}",
defaults: new { controller = "Contact", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
"Default_Frieldly",
"{*id}",
new { controller = "Profile", action = "Index" }
);
}
}
And finally because the default route will capture all unmatched routes, you will need to take not found profiles into account.
public class ProfileController : Controller {
//...code removed for brevity
public ActionResult Index(string id) {
//Get users profile from the database using the id
var viewModel = _userProfileService.Get(id);
if(viewModel == null) {
return NotFound();
}
return View(viewModel);
}
}
By having the profile controller prefix in the original URL it made it unique so as to avoid route conflicts, but in wanting the root friendly URL, while not impossible, you see the hoops needed to jump through in order to get the desired behavior.
This is how I would do it. Register a route that matches any string after the root slash.
Note that this severely limits the routes you can use for the application since not everything matching /{id} may actually be a user ID, which is why applications will typically prefix the route with /profile or /p.
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
name: "UserIdRoute",
url: "{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
public ActionResult Index(string id)
{
//Get users profile from the database using the id
var viewModel = _userProfileService.Get(id);
return View();
}
I am making a website that displays a bunch of articles. It is built off a MVC I want to be able to search for articles by day and then by an ID number via url.
If the domain name is website.com, I'm trying to figure out how to make routes so that website.com/yyyymmdd hits a specific controller+method and displays all the articles for that day and website.com/yyyymmdd/111 searches that day's articles for article #111.
The issue I'm having is that all the tutorials for routing within an MVC assume I will specify the controller and method in the URL. They show something like:
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" });
}
If anybody could suggest a way to automatically make "website.com/yyyymmdd/111" use a specified controller and method, that'd be amazing. Thanks in advance!
EDIT:
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
"Today",
"{date}/{id}",
new { controller = "Articles", action = "Index", date = DateTime.Today.ToString("yyyyMMdd"), id = UrlParameter.Optional });
}
I've done this as my route and my method is as follows:
public ActionResult ArticlesByDate(string date, int id)
{
if(id > 1)
{
return View(Contact());
}
else
{
return View(About());
}
}
I hope I am understanding your guys' suggestions, but this is giving me a "Resource can not be found" error when i try to navigate to: "http://localhost:52159/20160908/2"
One way this can be solved by using the date as a parameter to the action.
Your action would look like
public ActionResult Index(string articleDate,string id) ...
The your route definition would be something like
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
"Default",
"{controller}/{action}/{articleDate}/{id}",
new { controller = "Home", action = "Index",articleDate=DateTime.Today.ToString("yyyyMMdd"), id = "" });
}
Then you can take it from there
Second route for default mvc routing mechanism.
First route is for articles.Controller and action value is static. You can change date and id values for website.com/20160911/111 , website.com/20160912/112 etc.
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
name: "Articles",
url: "{date}/{id}",
defaults: new { controller = "Articles", action = "Info"});
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional });
}
public class ArticleController : Controller {
public ActionResult Info(string date, int id){
return View();
}
}
you cant redirect two view in an action. Use one view and partial views within.
for example
Info.cshtml
#if(id > 1)
{
#Html.Partial("_Contact")
}
else
{
#Html.Partial("_About")
}
I'm using ASP.NET MVC 4 with C#. I'm using areas and it's named like "Admin"
Here is my route config;
public static class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(name: "PageBySlug",
url: "{slug}",
defaults: new {controller = "Home", action = "RenderPage"},
constraints: new {slug = ".+"});
routes.MapRoute(name: "Default",
url: "{controller}/{action}/{id}",
defaults: new {controller = "Home", action = "Index", id = UrlParameter.Optional},
namespaces: new[] { "Web.Frontend.Controllers.Controllers" });
}
}
I generated frontend page links like; "products/apple-iphone"
So I want to call them like this.
But the error is: The code can't get the controller / action method.
I used frontend page links like;
#Html.ActionLink(linkItem.Title, "RenderPage", routeValues: new {controller = "Home", slug = linkItem.PageSlug})
#Html.RouteLink(linkItem.Title, routeName: "PageBySlug", routeValues: new { controller = "Home", action = "RenderPage", slug = linkItem.PageSlug })
#linkItem.Title
#linkItem.Title
They are rendering url links like; http://localhost:1231/products/apple-iphone
It's like what I want. But when I click any link, asp.net mvc gives me this error:
Server Error in '/' Application.
The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.
Requested URL: /products/apple-iphone
Version Information: Microsoft .NET Framework Version:4.0.30319; ASP.NET Version:4.6.1069.1
Here is my controller;
namespace Web.Frontend.Controllers
{
public class HomeController : BaseFrontendController
{
public ActionResult Index()
{
return View();
}
public ActionResult RenderPage(string slug)
{
return View();
}
}
}
So how can I catch every link request like this combined slug and turn my coded view ?
The problem is, When you request products/iphone, the routing engine don't know whether you meant the slug "products/iphone" or the controller "products" and action method "iphone".
You can write a custom route constraint to take care of this. This constraint will check whether the slug part of the urls is a valid controller or not, if yes,the controller action will be executed.
public class SlugConstraint : 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 slug = values["slug"];
if (slug != null)
{
if (controllerTypes.Any(x => x.Equals(slug.ToString(),
StringComparison.OrdinalIgnoreCase)))
{
return false;
}
else
{
var c = slug.ToString().Split('/');
if (c.Any())
{
var firstPart = c[0];
if (controllerTypes.Any(x => x.Equals(firstPart,
StringComparison.OrdinalIgnoreCase)))
{
return false;
}
}
}
return true;
}
return false;
}
}
Now use this route constraint when you register your custom route definition for the slug. make sure you use {*slug} in the route pattern. The * indicates it is anything(Ex : "a/b/c")(Variable number of url segments- more like a catch all)
routes.MapRoute(name: "PageBySlug",
url: "{*slug}",
defaults: new { controller = "Home", action = "RenderPage" },
constraints: new { slug = new SlugConstraint() }
, namespaces: new string[] { "Web.Frontend.Controllers.Controllers" });
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
, new string[] { "Web.Frontend.Controllers.Controllers" });
you can provide only this type of link
#linkItem.Title
Because Routetable find your route using Route name provided by you. so controller name and action name is not necessary.
I have a blogging system that I'm building and I can't seem to get ASP.NET MVC to understand my route.
the route I need is /blogs/student/firstname-lastname so /blogs/student/john-doe, which routes to a blogs area, student controller's index action, which takes a string name parameter.
Here is my route
routes.MapRoute(
name: "StudentBlogs",
url: "blogs/student/{name}",
defaults: new { controller = "Student", action="Index"}
);
My controller action
public ActionResult Index(string name)
{
string[] nameparts = name.Split(new char[]{'-'});
string firstName = nameparts[0];
string lastName = nameparts[1];
if (nameparts.Length == 2 && name != null)
{
// load students blog from database
}
return RedirectToAction("Index", "Index", new { area = "Blogs" });
}
But it won't seem to resolve...it works fine with /blogs/student/?name=firstname-lastname, but not using the route I want, which is /blogs/student/firstname-lastname. Any advice on how to fix this would be greatly appreciated.
My RouteConfig
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "StudentBlogs",
url: "blogs/student/{name}",
defaults: new { controller = "Student", action = "Index"},
constraints: new { name = #"[a-zA-Z-]+" },
namespaces: new string[] { "IAUCollege.Areas.Blogs.Controllers" }
);
routes.MapRoute(
name: "Sitemap",
url :"sitemap.xml",
defaults: new { controller = "XmlSiteMap", action = "Index", page = 0}
);
//CmsRoute is moved to Gloabal.asax
// campus maps route
routes.MapRoute(
name: "CampusMaps",
url: "locations/campusmaps",
defaults: new { controller = "CampusMaps", action = "Index", id = UrlParameter.Optional }
);
// core route
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
// error routes
routes.MapRoute(
name: "Error",
url: "Error/{status}",
defaults: new { controller = "Error", action = "Error404", status = UrlParameter.Optional }
);
// Add our route registration for MvcSiteMapProvider sitemaps
MvcSiteMapProvider.Web.Mvc.XmlSiteMapController.RegisterRoutes(routes);
}
}
You have to declare custom routes before the default routes. Otherwise it will be mapping to {controller}/{action}/{id}.
Global.asax typically looks like this:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
If you created an Area named Blogs, there is a corresponding BlogsAreaRegistration.cs file that looks like this:
public class BlogsAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Blogs";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Admin_default",
"Blogs/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
}
Hyphens are sometimes treated like forward slashes in routes. When you are using the route blogs/students/john-doe, my guess is that it is matching the Area pattern above using blogs/students/john/doe, which would result in a 404. Add your custom route to the BlogsAreaRegistration.cs file above the default routes.
Try adding the parameter to the route:
routes.MapRoute(
name: "StudentBlogs",
url: "blogs/student/{name}",
defaults: new { controller = "Student", action="Index", name = UrlParameter.Optional}
);
Try adding a constraint for the name parameter:
routes.MapRoute(
name: "StudentBlogs",
url: "blogs/student/{name}",
defaults: new { controller = "Student", action="Index" },
constraints: new { name = #"[a-zA-Z-]+" }
);
Dashes are a bit weird in MVC at times.. because they are used to resolve underscores. I will remove this answer if this doesn't work (although.. it should).
This has the added benefit of failing to match the route if a URL such as /blogs/student/12387 is used.
EDIT:
If you have controllers with the same name.. you need to include namespaces in both of your routes in each area. It doesn't matter where the controllers are.. even if in separate areas.
Try adding the appropriate namespace to each of the routes that deal with the Student controller. Somewhat like this:
routes.MapRoute(
name: "StudentBlogs",
url: "blogs/student/{name}",
defaults: new { controller = "Student", action="Index" },
namespaces: new string[] { "Website.Areas.Blogs.Controllers" }
);
..and perhaps Website.Areas.Admin.Controllers for the one in the admin area.