Custom Routing in ASP .NET Core Areas - asp.net-mvc

I love the areas feature in ASP .NET Core.
For example, the admin section resides inside ~/Areas/Admin folder which has it's own Views, Controllers and Models inside it. An example route for the admin area is /domain.com/admin/users/manage
Say I have another area called Customers which can contain all customer related stuff. The customer area will have function related to CRUD operations only. So I need the route for customer area be like domain.com/customer/create. But as far as I know the only way to achieve this is through placing the views inside the common Views folder.
Is there any way to organize the customer related stuff in the Areas folder and access it through the url I mentioned above?

I find the easiest way is using attribute route, you have to enable it first inside RouteConfig.cs for ASP.NET MVC. In ASP.NET core, i am not sure you have to enable it or not, i belive it is inside Startup.cs?
public static void RegisterRoutes(RouteCollection routes)
{
//routes.AppendTrailingSlash = false;
//routes.LowercaseUrls = true;
routes.MapMvcAttributeRoutes();
....
}
Now in your customer area controller
[RouteArea("Customer", AreaPrefix = "")]
public class UsersController : Controller
{
//Customer/Create
[Route("Customer/Create")]
public ActionResult Create()
{
}
}
RouteArea is the Area name, setting the prefix to null so it won't show up in the url as Customer/Customer/Create, Route is the Controller Action Name, in this case the result will be Customer/Create

Related

How do I make MVC look into the Views folder within my area first before looking into the area-less root's Views folder?

I am using ASP.NET MVC 5.2.6 on .NET framework version 4.5.2.
I have an area named Admin in which I have the following:
namespace MyCode.Controllers
{
[RoutePrefix("admin/article")]
public class ArticleController : Controller
{
[Route("new")]
public ActionResult New()
{
return View();
}
}
}
I also have a view named New.cshtml in the Admin area's Views\Article folder.
Areas\Admin\Views\Article\New.cshtml
However, when I run my application, MVC is only looking in the Views folder within my area-less root. From my past experience, it starts to look in the Views folder within the area.
Is it because the new attribute based routing that MVC doesn't know that I am inside the Admin area?
I know I could and I don't want to be providing the full path to each view because that's a pain in the neck. My question isn't, where do I go from here now? It is, as the title says, the following:
Is there a way to tell it to look for views in the area first?
Holy smokes!
I finally found out the solution to my problem. Here's what you must do if you face this problem.
Make sure that the controllers in your areas reside in a namespace different from the namespace of the controllers in your area-less root. This should be so even if the controller names are unique. It doesn't matter that you use attribute routing or not.
So, if you have an area-less root like so:
Root
namespace MyCode.Controllers
{
public class HomeController : Controller
{
}
}
Foo Area
namespace MyCode.Controllers
{
[RoutePrefix("whatever/it/doesnt/matter")]
public class FooController : Controller
{
}
}
You will have the same problem. Change it like the following so that the controllers of the area reside in a namespace separate from that of the root, and the problem will go away.
Foo Area
namespace MyCode.Areas.Foo.Controllers // or anything else other
// than the namespace where root controllers are
{
[RoutePrefix("whatever/it/doesnt/matter")]
public class FooController : Controller
{
}
}
This is most likely a bug in the ASP.NET MVC source.
PS:
Even RouteDebugger told me there wasn't a problem with my route and it was able to resolve my path localhost:<port>/Admin/Article/New to the correct controller and action, yet ASP.NET MVC couldn't resolve my controller and action and reported a 404.
This deleted answer from #lawphotog has a link to a video that helped me solve my problem.

ASP.NET MVC Core - replace controllers using custom class library

I have core product with some MVC controller.
I want to have ability to overwrite some actions specific to the client using custom class library (Let's say MyProduct.MVC.Custom.dll).
In previous versions of MVC it was possible using routes.MapRoute( namespaces: new[] { "MyProduct.MVC.Custom" }
In current MVC we do not have such option and I receiving the following error message:
AmbiguousActionException: Multiple actions matched. The following actions matched route data and had all constraints satisfied:
MyProduct.MVC.Custom.Controllers.HomeController.About
MyProduct.MVC.Controllers.HomeController.About
Microsoft.AspNet.Mvc.Infrastructure.DefaultActionSelector.SelectAsync(RouteContext context)
Google suggest me to use Areas, but I want to replace the controller's action with the same URL.
Is it possible to achieve this is ASP.NET MVC core?
I finally manage how to do it.
It can be achieved using the Route attribute with Order=0
[Route("[controller]/[action]",Name ="Replacement", Order = 0)]
public IActionResult About()
{
ViewData["Message"] = "This is About from Custom controller!!!!!!!!!!!!!!";
return View();
}

ASP.NET MVC Areas or Defined Routes?

I'd like to achieve the following (with ASP.NET MVC 3):
A controller called "apps" with the following actions:
/apps/my
/apps/agency
/apps/new
Within the last action I really want some sub-actions, e.g.:
/apps/new/product
/apps/new/tariff
I could write the New() action to take some kind of parameter to say which view I should render (i.e. product or tariff) but that feels a bit dirty.
What I really want is separate action methods for product and tariff.
What's the best way to go about this?
I think I could use Areas but this seems overkill for what I want - is the solution just to write a custom route?
Many thanks!
Sam
You could use Areas but for this small amount I agree that it is probably overkill. I'd say making the New action take in a parameter is fine for what you need. It may get more complicated if you want to pass more information in but its still do able. If you want to keep the code clean(er) you can have the action do all the complicated bits in separate private methods.
However, doing it with a custom route way (and with a separate controller as well) ...
Global.asax.cs
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute("newControllerRoute",
"apps/new/{action}",
new {controller = "NewApps"});
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
The add a controller (NewAppsController).
public class NewAppsController : Controller
{
public ActionResult Product()
{
/* used as example */
return Content("NewApps controller - Product");
}
public ActionResult Tariff()
{
/* used as example */
return Content("NewApps controller - Tariff");
}
}
Hope this helps.
If you have two distinct things that you want to be able to add then that is two distinct actions on your controller. It sounds like you are just wanting to control your URL schema so I think some sort custom routing is the answer.
Why not have separate controllers for tariffs and products? That will lead to a natural URL schema.

Multilingual URLs with ASP.NET MVC

I’m working out the concepts for a new project where I need to support for multilingual URL’s. Ideally all URL’s need to be in the native language of the user. So we don’t want to use domain.com/en/contact and domain.com/es/contact but we like domain.com/contact and domain.com/contactar (contactar is Spanish for contact). Internally both should be routed to the same ContactController class.
This could be handled by adding multiple static routes to Global.asax.cs for each language but we’d like to make this very dynamic and would like the user of the system to be able to change the translation of the URL’s through the content management system. So we need some kind of dynamic mapping from URL’s to controllers and actions.
By looking at the source code of MVC3 I figured out that the ProcessRequestInit method of MvcHandler is responsible for determining which controller to create. It simply looks in the RouteData to get the name of the controller. One way to override the default MVC routing would be to create a simple default route that uses a custom RouteHandler. This RouteHandler forces MVC to use my own custom subclassed version of MvcHandler that overrides the ProcessRequestInit method. This overridden method insert my own dynamically found controller and action into the RouteData before calling back to the original ProcessRequestInit.
I’ve tried this:
Global.asax.cs
routes.Add(
new Route("{*url}", new MultilingualRouteHandler())
{
Defaults = new RouteValueDictionary(new { controller = "Default", action = "Default" })
}
);
MultilingualRouteHandler.cs
public class MultilingualRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new MultilingualMVCHandler(requestContext);
}
}
MultilingualMvcHandler.cs
public class MultilingualMVCHandler : MvcHandler
{
public MultilingualMVCHandler(RequestContext context) : base(context)
{
}
protected override void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory)
{
if (RequestContext.RouteData.Values.ContainsKey("controller"))
{
RequestContext.RouteData.Values.Remove("controller");
}
if (RequestContext.RouteData.Values.ContainsKey("action"))
{
RequestContext.RouteData.Values.Remove("action");
}
RequestContext.RouteData.Values.Add("controller", "Product");
RequestContext.RouteData.Values.Add("action", "Index");
base.ProcessRequestInit(httpContext, out controller, out factory);
}
}
In this handler I hardcoded the controller and action for testing purposes to some fixed values but it’s not difficult to make this dynamic. It works but the only problem is that I had to modify the source code of ASP.NET MVC3 to get it working. The problem is that the ProcessRequestInit method of MvcHandler is private and thus cannot be overridden. I’ve modified the source code and changed it to protected virtual which allows me to override it.
This is all great but possibly not the best solution. It’s cumbersome that I would always need to distribute my own version of System.Web.Mvc.dll. It would be much better that it would work with the RTM version.
Am I missing any other possibilities of hooking into ASP.NET MVC that would allow me to dynamically determine the controller and action to launch, depending on the URL? One other way I thought of is to build the RouteCollection dynamically on *Application_Start* but I think that will make it more difficult to change it on the fly.
I would appreciate any tips of hooks that I’ve not yet found.
This is fairly old now, nut just in case anyone else is looking for something similar...
Unless I'm completely misunderstanding what you want to do, it's pretty simple really.
Step 1: Add a new route to global.ascx.cs containing a reference to your personal routing engine
routes.Add(new MyProject.Routing.ContentRoutingEngine());
Make sure that it is in the right place in the list of routes so that other routing engines can catch stuff before it if required, or continue the route search if your engine doesn't handle a particular route. I put it after the ignores, but before the MVC default routes.
Step 2: Create the Content Routing Engine, making sure that it inherites from System.Web.Routing.RouteBase abstract class, and overrides the GetRouteData and GetVirtualPath methods as required e.g.
public class ContentRoutingEngine : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var routeHandler = new MvcRouteHandler();
var currentRoute = new Route("{controller}/{action}", routeHandler);
var routeData = new RouteData(currentRoute, routeHandler);
// set your values dynamically here
routeData.Values["controller"] = "Home" ;
// or
routeData.Values.Add("action", "Index");
// return the route, or null to have it passed to the next routing engine in the list
return routeData;
}
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;
}
}
and that should do it. You can change routes as dynamically as you wish within your engine, and no changes to MVC source required. Let the standard MVC RouteHandler actually invoke the controller.
Postscript: Obviously the code above is not production standard - it's written to make it as obvious as possible what's going on.
If you are allowing modification of urls through your CMS, then you will have to keep all old versions of the urls so that you can 301 redirect to the new ones.
The best bet for this will be to put the url tokens eg "contactar" in the db along with its corresponding controller.
query that, and create your routes out of that.
create a route that will handle the 301s
I think that most elegant solution would be using some action filter combined with custom ActionInvoker. That way, you could invoke an action that has specific filters applied. Something like ActionName attribute, only capable to accept multiple values (names).
Edit: Take a look at ActionMethodSelectorAttribute, meybe you don't need a custom ActionInvoker after all.

ASP.NET MVC, Reeling in Unroutable Page Nesting

The concept of routes is nothing new, and it works great for the concept of {area}/{controller}/{action}/{parameter}, but few sites are standalone UI interaction.
Websites often need parts of themselves that aren't really dedicated to taking data, but presenting it. For instance one of the sites I am working on has a large part of itself dedicated to user interaction (which the MVC system solves expertly. A Membership area, a place to manage information, a way to purchase items, etc.) - but it also needs a part that functions more like an old-fashioned website, where you're simply looking at pages like a folder structure.
one solution I have found is to try a custom view engine. This worked, but I quick found myself lost in a convoluted routing scheme. Another I guess I could go with is to just have an IgnoreRoute and put files in the ignored folder like normal html/aspx, but I'd really rather have the option of using Controllers so that there is a chance I can have data returned from a database, etc in the future.
So let me show you my current scenario...
Areas
Membership
Rules
Controllers
HomeController
FileView(string folder, string file)
Views
Home
General
Customize
Content
yyy.cshtml
xxx.cshtml
#Html.Partial("Content/yyy.cshtml")
xxx.cshtml
xxx.cshtml
etc. The Rules area is basically setup to function like a normal /folder/file/ structure. So here is my Controller for it..
public class HomeController : Controller
{
//
// GET: /Information/Home/
public ActionResult Index()
{
return View();
}
// **************************************
// URL: /Rules/{controller}/{folder}/{file}
// **************************************
public ViewResult FileView(string folder, string filename)
{
return View(String.Format("{0}/{1}", folder, filename));
}
}
Now, if I have a category, I simply have a lightweight controller that inherits from that Area's HomeController, like this...
public class GeneralController : Rules.Controllers.HomeController
{
// **************************************
// URL: /Rules/General/Customize/{id}
// **************************************
public ViewResult Customize(string id)
{
return FileView("Customize", id);
}
}
So then, for each folder in the 'sub' controller, I have a single Route that takes in the name of the file.
This works, but I feel it's excessively clunky. Can anyone suggest a better alternative? There are just too many pages, and too much nesting, to have a full ActionResult for each one. I also want to maintain clean urls.
Perhaps you can use a catch-all route for the Membership area, route it to a controller (MembershipController?) and have that controller just render the view that is catched by the route, like this:
public class MembershipController : Controller
{
public ActionResult Index(string pageTitle)
{
return View(pageTitle);
}
}
And the route:
routes.MapRoute(
"Membership",
"Membership/{*pageTitle}",
new {controller = "Membership", action = "Index", pageTitle = "NotFound"});
Of course, in the controller you should check whether the view exists or not, but this one should get you moving. Although I don't see why you want to have MVC in front of this when you just want to display (static?) content.

Resources