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);
}
}