asp.net mvc dynamic area selection based on route parameter value - asp.net-mvc-3

I am working on an application surrounding sporting events. There are different types of events like a soccer tournament and a tennis tournament. Based on the type of tournament I want to have the requests proccessed by a different area. But the events and their tournament type is something that is configurable by users of the application and stored in the database.
Currrently I have this proof of concept:
public class SoccerTournamentAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "SoccerTournament";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
var soccerTournaments = new string[] { "championsleague", "worldcup" };
foreach (var tournament in soccerTournaments)
{
context.MapRoute(
string.Format("SoccerTournament_default{0}", tournament),
string.Format("{0}/{{controller}}/{{action}}/{{id}}", tournament),
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new[] { "Mvc3AreaTest1.Areas.SoccerTournament.Controllers" }
);
}
}
}
and it works only I want soccerTournaments to come from the database (not a problem) but I also want it to work ask soon as a new event/tournament type record is added to the database and that doesn't work in this case.
How can I make the area selection dynamic instead of hard coded into routes?

Area registration only occurs at the application start, so any tournaments added after startup will not be captured until a re-start.
To have a dynamic routing scheme for your tournaments, you must redefine your area route and add a RouteConstraint.
Redefine your route as follows:
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"SoccerTournament_default",
"{tournament}/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new { tournament = new MustBeTournamentName() },
new string[] { "Mvc3AreaTest1.Areas.SoccerTournament.Controllers" }
);
}
Than, you can create the MustBeTournamentName RouteConstraint to be similar to the RouteConstraint in the answer to this question: Asp.Net Custom Routing and custom routing and add category before controller

Related

Whats wrong with my routes and actions?

I recently asked a question based on how to create pages based on the content table which contains the following: Title and Content. I followed the steps, to my understanding, in the answer that was given.
I created a route like so:
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.MapRoute(
"ContentManagement",
"{title}",
new { controller = "ContentManagement", action = "Index", title = "{title}" }
);
}
I am assuming I can do routes like this? where I can set up multiple routes? I am also assuming I can pass the title to to the controller action like I have done?
I then created the model:
namespace LocApp.Models
{
public class ContentManagement
{
public int id { get; set; }
[Required]
public string title { get; set; }
public string content { get; set; }
}
}
from that I created a controller with an index action that looks as such:
public ViewResult Index(string title)
{
using (var db = new LocAppContext())
{
var content = (from c in db.Contents
where c.title == title
select c).ToList();
return View(content);
}
}
So then I created some content with the title of "bla" so when I visit site.com/bla I get an error that it cant find "bla/"
Can some one tell me what I am doing wrong? I would also, if you are familiar with the default layout of a asp.net mvc project with the tabs at the top, create a set of tabs that lead to the pages, based on the title in the database
The main issue is that when you are using the title, the routing engine is matching it to the first route and trying to find a controller by that title. We have implemented something similar and found that by explicitly defining what controllers are valid for the default route, it then processed request appropriately. I gave an example of the controllers that we allow to fit our default route below (Home, Help and Error).
You probably also want to prevent people from giving the content the same TITLE as your root level controllers as that would blow this up pretty well.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new {controller = "Home", action = "Index", id = UrlParameter.Optional},
new {controller = "Home|Error|Help"},
new[] {"UI_WWW.Controllers"});
routes.MapRoute(
"ContentManagement",
"{title}",
new {controller = "ContentManagement", action = "Index"});
}
}

Routing with areas and different parameters, misunderstanding

I have been confused all day, i have a routing in area and it looks like this.
public class AdminAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "admin";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRouteLowercase(null, "Account/{action}",
new {controller = "Account"},
new {action = #"LogOff|LogOn|Create|Update|Delete|List"},
new[] {"WebUI.Areas.Admin.Controllers"});
context.MapRouteLowercase( //this works
"AdminUpdateCategoryView",
"admin/{controller}/{action}/{cid}",
new {area = "admin", controller = "Main", action = "UpdateCategory", cid = ""},
new {cid = #"\d+"},
new[] {"WebUI.Areas.Admin.Controllers"}
);
context.MapRouteLowercase(//this not works
"AdminCategoryListView",
"admin/Main/{action}/{page}",
new { action = "Category", page = "1" },
new {page = #"\d+"},
new[] {"WebUI.Areas.Admin.Controllers"}
);
context.MapRouteLowercase(
"Admin_Default", // Route name
"admin/{controller}/{action}/{id}", // URL with parameters
new {controller = "Category", action = "Index", id = UrlParameter.Optional} // Parameter defaults
);
}
}
I have wrote what works and what not, but if change between them the one that doesn't work, works and the other that works, don't work?
example:
first case-> /admin/main/updatecategory/1 --> works
/admin/main/category/1 --> not works
result: /admin/main/category/1?page=1
second case-> /admin/main/category/1 --> works
/admin/main/updatecategory/1 --> not works
result: /admin/main/updatecategory/1?cid=1
Here is my controller actions:
public ActionResult Category(int? page)
{
int pageIndex = page.HasValue ? page.Value : 1;
return View("Category", CategoryViewModelFactory(pageIndex));
}
public ActionResult CreateCategory()
{
return View();
}
public ActionResult UpdateCategory(int cid)
{
return View();
}
public ActionResult DeleteCategory(int? cid)
{
return View();
}
What is this problem and how to solve it?
I'm totally confused, Routing in ASP.MVC3 is e-logical.
Help?!
When routes are searched, the first one that matches your URL is used. AdminUpdateCategoryView will match any admin controller, and action. You provide a default cid of "", but that shouldn't matter because you're requiring that cid be a number below that. AdminCategoryListView will match any url that enters main. Because you provide a default page of 1, it doesn't even matter if no page is provided.
So if AdminCategoryListView is on top: every single route in admin/main will use this route.
If AdminUpdateCategoryView is on top every route in admin that reaches this route and has a numerical cid value parameter will use it.
I'd recommend putting AdminCategoryListView on top because it's the more specific route. Either remove page="1" (depends on if you want to provide a default), or replace {action} with "category" so your other routes don't use this route. Also you should provide a default controller of main, otherwise it will assume the controller you're currently using is the correct one.
context.MapRouteLowercase(
"AdminCategoryListView",
"admin/Main/category/{page}",
new { action = "Category", controller = "Main" },
new {page = #"\d+"},
new[] {"WebUI.Areas.Admin.Controllers"}
);
//Put AdminUpdateCategoryView here

ASP.NET MVC Areas routing not working with default route

I am trying to separate my MVC project into multiple areas. So i have 3 areas 1) crm 2)services 3) Web. I want PublicWeb to be my default one. that means it should be accessed like www.mysitename.com/mycontroller/myaction( no area name inbetween) and other two to be accessed with the area name (www.mysitename.com/crm/mycontroller/myaction). What routing/ Area configuration i should have ? I tried AreaRegistration.RegisterAllAreas(); and it works only for my default one (web). When i access the other 2, it threw 404 error.
I tried to register indidually like the below one
var area2reg = new crmAreaRegistration();
var area2context = new AreaRegistrationContext(area2reg.AreaName, RouteTable.Routes);
area2reg.RegisterArea(area2context);
var area1reg = new webAreaRegistration();
var area1context = new AreaRegistrationContext(area1reg.AreaName, RouteTable.Routes);
area1reg.RegisterArea(area1context);
Then my publicweb works. But when i access my forum it threw this error,
Multiple types were found that match the controller named 'home'. This can happen if the route that services this request ('crm/{controller}/{action}/{id}') does not specify namespaces to search for a controller that matches the request. If this is the case, register this route by calling an overload of the 'MapRoute' method that takes a 'namespaces' parameter.
My RegisterArea function for web is this
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"web_default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
and the one for crm is this
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"crm_default",
"crm/{controller}/{action}/{id}",
new { controller = "home", action = "Index", id = UrlParameter.Optional }
);
}
}
How do i handle this ?
From what I can see the area routes look fine. Did you update the default route in your Global.asax to send requests to the web area?
Something like:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}",
new { area = "web", controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
I think Jonathan S's solution is definitely worth a try, but you might consider a different approach. That would be to put your web files in the default locations. The routing engine would not look in the Area's for those files when no Area is part of the request.

ASP.NET MVC3 Route Mapping reduction help

I am trying to play with with is possible with routes in my ASP.NET MVC3 application and try reduce some of my mapping code. I am using trying to us a common UserController/View accross my application across a number of different entities. For example, you have Stores and Companies, and each has their own set of users. Is there any way to reduce the following two routes:
routes.MapRoute(
"StoreUsers", // Route name
"Store/Details/{entityID}/User/Index", // URL with parameters
new { controller = "User", action = "StoreIndex"} // Parameter defaults
);
routes.MapRoute(
"CompanyUsers", // Route name
"Company/Details/{entityID}/User/Index", // URL with parameters
new { controller = "User", action = "CompanyIndex"} // Parameter defaults
);
To something which resembles this?
routes.MapRoute(
"EntityUsers", // Route name
"{entity}/Details/{entityID}/User/Index", // URL with parameters
new { controller = "User", action = entity + "Index"} // Parameter defaults
new { entity = "(Store|Company)" } //Parameter constraints
);
and have the {action} parameter (and {action} default) set to: {entity} + "Index" so it can be used for entity entity which matches the constraints.
I am only reducing 2 routes to 1 here, but my real issue involves more then just these two entities, and if I can get this to work, I can use this for other controllers that have to mimic the same functionality and other actions as well (Create, Edit, etc).
Thanks
I figured the answer had to be out there and I was just not searching for the right things, i scoured StackOverflow for a bit and was able to find this question which helped me develop a solution:
asp.net mvc complex routing for tree path
I could set up a route to look like this:
routes.MapRoute(
"EntityUsers", // Route name
"{entity}/Details/{entityID}/{controller}/{subaction}/{id}", // URL with parameters
new {controller = "User", subaction = "Index", id = UrlParameter.Optional}, // Parameter defaults
new {entity = "(Lender|Dealer)", controller="User"}
).RouteHandler = new UserRouteHandler();
and the UserRouteHandler class looks as follows:
public class UserRouteHandler : IRouteHandler {
public IHttpHandler GetHttpHandler(RequestContext requestContext) {
string entity = requestContext.RouteData.Values["entity"] as string;
string subaction = requestContext.RouteData.Values["subaction"] as string;
if (entity != null && subaction != null)
{
requestContext.RouteData.Values["action"] = entity + subaction;
}
return new MvcHandler(requestContext);
}
}
In the end, I was way over complicating the issue and don't need this, but its good to know you can have this flexibility

RouteHandler vs ControllerFactory

new to asp.net mvc (using v3 + razor) and am wondering how to best solve a problem with creating dynamic routes based on a database. Essentially, the main site navigation will be entered into a database and I want to load them up as routes. i.e. - Load Category list from database, then append the routes to the routing engine if possible...
mysite.com/cars
mysite.com/televisions
mysite.com/computers
etc....
Each category after the slash comes from the db, but, there are regular entries like /about and /contactus that will not be in the database and have been statically entered in the global.asax... my question is:
For the dynamic database URLs should I use a custom RouteHandler or pehaps create a ControllerFactory that will match and handle the requests for the entries loaded from the database. Is it possible to have the DefaultControllerFactory handle the routing if my RouteHandler or CustomControllerFactory don't find the route in the list from the database? Thanks for any help, very first project with this so I'm not sure what the best route is ;) no pun intended...
Update:
Tried using a route constraint that pulls from the database but it conflicts with the default route now... here is my custom constraint and routes:
public class CategoryListConstraint : IRouteConstraint
{
public CategoryListConstraint()
{
var repo = new Repository<Topic>();
var cats = repo.All();
var values = new List<string>();
foreach (var c in cats)
{
values.Add(c.URI.Replace("/", "").Replace("?", ""));
}
this._values = values.ToArray<string>();
}
private string[] _values;
public bool Match(HttpContextBase httpContext,
Route route,
string parameterName,
RouteValueDictionary values,
RouteDirection routeDirection)
{
// Get the value called "parameterName" from the
// RouteValueDictionary called "value"
string value = values[parameterName].ToString();
// Return true is the list of allowed values contains
// this value.
return _values.Contains(value);
}
}
and here are the routes:
Routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Categories",
"{category}/{*values}",
new { controller = "Category", action = "List" },
new CategoryListConstraint()
);
Routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
The home page www.mysite.com loads using the Default route. All the URLs that match the constraint list are loaded by the category route... but if I have the www.mysite.com/admin or www.mysite.com/aboutus these are getting picked up by the Categories route even though the values are not in the constraint list. Confused...
What about something like this?
Categories controller:
public ActionResult List(string category)
{
var products = _repo.Get(category); // however you are getting your data
return View(products);
}
Routes
routers.MapRoute(
"About",
"About",
new { controller = "Home", action = "About" });
//... other static routes
routes.MapRoute(
"CategoriesList",
"{id}",
new { controller = "Categories", action = "List" },
new { id = #"\w+" });
The incoming URL is tested against each Route rule to see if it matches - and if a Route rule matches then that rule (and its associated RouteHandler) is the one that is used to process the request (and all subsequent rules are ignored). This means that you want to typically structure your routing Rules in a "most specific to least specific" order
source
Found the exact solution I was looking for. Code is below. I managed to avoid using Controller Factories or implementing a custom IRouteHandler by using extending the RouteBase class which worked perfectly and allows me to pass control down to the default mvc route is something specific isn't hit. BTW - constraints ended up not working properly as the broke the controllers associated with the default route (although the default route was getting hit)
public class CustomRoutingEngine : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var routeHandler = new MvcRouteHandler();
var currentRoute = new Route("{controller}/{*URI}", routeHandler);
var routeData = new RouteData(currentRoute, routeHandler);
// implement caching here
var list = GetConstraintList();
// set your values dynamically here
routeData.Values["controller"] = "Category";
// or
routeData.Values.Add("action", "List");
// return the route, or null to have it passed to the next routing engine in the list
var url = Util.StripSlashOnFrontAndBack(httpContext.Request.Path.ToLower()).Split('/')[0];
if (list.Contains(url))
return routeData;
return null; // have another route handle the routing
}
protected List<string> GetConstraintList()
{
using (var repo = new RavenRepository<Topic>())
{
var tops = repo.Query().Where(x => x.Hidden == false).ToList()
.Select(x=>x.Name.ToLower());
List<string> list = new List<string>();
list.AddRange(tops);
repo.Dispose();
return list ?? new List<string>();
}
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
//implement this to return url's for routes, or null to just pass it on
return null;
}
}
Then my register routes method looks like so:
Routes.Clear();
// Set Defaults
Routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
AreaRegistration.RegisterAllAreas();
routes.Add(new App.Helpers.CustomRoutingEngine());
Routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);

Resources