MVC custom routing with validation - c#

Masters,
In my project I have specified many different routes something like follows.
routes.MapRoute(
name: "StudentMerit",
url: "Student-Merit",
defaults: new { controller = "StudentMerit", action = "GetData"});
routes.MapRoute(
name: "StudentResults",
url: "Student-Results",
defaults: new { controller = "StudentResults", action = "GetData"});
As you can see url is hyphen separated and i am not much worried about action name
All this routes can be rewritten if i can tell MVC that Replace Hyphen with nothing in
URL and it will be my controller.
Something like,
routes.MapRoute(
name: "AllInOne",
url: "{Cont-roller}",
defaults: new { controller = {Cont-roller}.replace("-",""), action = "GetData"});
Is there any way to do this?
Please help.
Thanks in advance.

Create your own RouteHandler. I don't know if this is the best solution though.
public class RemoveDashRouteHandler : MvcRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
requestContext.RouteData.Values["controller"] = ((string)requestContext.RouteData.Values["controller"]).Replace("-", String.Empty);
return base.GetHttpHandler(requestContext);
}
}
Usage
routes.MapRoute(
name: "AllInOne",
url: "{controller}",
defaults: new { controller = "Default", action = "GetData" }
).RouteHandler = new RemoveDashRouteHandler();
Edit for alternative solution
I found a better solution (in my opinion) by sub-classing Route then overriding GetRouteData. It's better since Route's responsibility is to generate the RouteData while MvcRouteHandler's responsibility is to get the IHttpHandler.
public class RemoveDashRoute : Route
{
private const string ControllerKey = "controller";
public RemoveDashRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints = null, RouteValueDictionary dataTokens = null, IRouteHandler routeHandler = null)
: base(url, defaults, constraints ?? new RouteValueDictionary(), dataTokens ?? new RouteValueDictionary(), routeHandler ?? new MvcRouteHandler())
{
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var routeData = base.GetRouteData(httpContext);
if (routeData != null && routeData.Values.ContainsKey(ControllerKey))
{
routeData.Values[ControllerKey] = ((string)routeData.Values[ControllerKey]).Replace("-", String.Empty);
}
return routeData;
}
}
Usage
routes.Add("AllInOne", new RemoveDashRoute(
url: "{controller}",
defaults: new RouteValueDictionary(new { controller = "Home", action = "GetData" }))
);

Related

ASP.net customized routing with Action/Id

I've been trying to change the routing for my website for the past few hours and I just can't find out what ASP.net wants from me!
This is my default routing:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{Id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional, }
);
It successfully brings up Home/Index whenever I go to these URLs:
/
/Home
/Home/Index
However, I have an action called "K" like below which receives a parameter "name":
public ActionResult K(string name)
I want to define a route that will redirect me to /Home/K/{name} with this template:
website.com/K/{name}
I tried this route below but It doesn't work:
routes.MapRoute(
"OnlyK",
"K/{id}",
new { controller = "Home", action = "K", id = UrlParameter.Optional }
);
Also even without this route config, if I go to website.com/Home/K/something It will not recognize "something" as id (controller parameter == null)!
What am I doing wrong?
The url website.com/Home/K/something results in a null value for argument name in the K action method ActionResult K(string name) because the argument names don't match.
This method is being served via the the route Default, which
declares a parameter with name id (via {controller}/{action}/{id}), whereas action method K has an argument named name.
You can solve this by overruling the argument name on the action method, via the BindAttribute so that both match.
ActionResult K([Bind(Prefix ="id")] string name)
To do a redirect from website.com/K/{name} to website.com/Home/K/{name} you can set up a custom IRouteHandler that takes care of this redirect.
Register a route that handles any request that match route K/{id} and redirect these to a route with a wellknown name (here: K).
Because of the argument name mismatch discussed about we'll use {id} instead of {name} in our routes.
routes.Add(new Route("K/{id}", new RedirectRouteHandler("K")));
Define this route K as below.
routes.MapRoute(
name: "K",
url: "Home/K/{id}",
defaults: new { controller = "Home", action = "K", id = UrlParameter.Optional }
);
The RouteHandler and RedirectHandler doing the redirect.
The HttpResponse class has a RedirectToRoute method that can deal with route names and route values, without having to construct urls ourselves.
class RedirectRouteHandler : IRouteHandler
{
private readonly string _routeName;
public RedirectRouteHandler(string routeName)
{
_routeName = routeName;
}
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new RedirectHandler(this._routeName, requestContext.RouteData.Values);
}
}
class RedirectHandler : IHttpHandler
{
private readonly string _routeName;
private readonly RouteValueDictionary _routeValues;
public RedirectHandler(string routeName, RouteValueDictionary routeValues)
{
this._routeName = routeName;
this._routeValues = routeValues;
}
public bool IsReusable { return false; }
public void ProcessRequest(HttpContext context)
{
context.Response.RedirectToRoute(this._routeName, this._routeValues);
}
}
Note that the order of the registered routes matters; RegisterRoutes looks like.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add(new Route("K/{id}", new RedirectRouteHandler("K")));
routes.MapRoute(
name: "K",
url: "Home/K/{id}",
defaults: new { controller = "Home", action = "K", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}

Asp.Net MVC 5 route not getting hit

I have this in my RouteConfig:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "StoreSearch",
url: "{storeName}",
defaults: new { controller = "Search", action = "Store" }
);
Basically I have a 2nd route so if I type in localhost:9000/facebook there is no such controller by the name of facebook so my 2nd router should pickup facebook as the storeName and hit my Search controller's Store action. But at the moment I get 404.
Any ideas how do I fix this?
If you have specific set of stores or a way to verify them you can add a constraint to the route.
To add a generic constraint with match predicate
public class ServerRouteConstraint : IRouteConstraint
{
private readonly Func<Uri, bool> _predicate;
public ServerRouteConstraint(Func<Uri, bool> predicate)
{
this._predicate = predicate;
}
public bool Match(HttpContextBase httpContext, Route route, string parameterName,
RouteValueDictionary values, RouteDirection routeDirection)
{
return this._predicate(httpContext.Request.Url);
}
}
and then in the route add a paramater
routes.MapRoute(
name: "StoreSearch",
url: "{storeName}",
defaults: new { controller = "Search", action = "Store" }, constraints:
//this could be new { controller ="pattern" }
new {
serverRoute = new ServerRouteConstraint(url =>
{
//this will check that the route starts with a specific string in this case Settings
return url.PathAndQuery.StartsWith("/Settings",
StringComparison.InvariantCultureIgnoreCase);
})
});
For examples check: https://www.c-sharpcorner.com/UploadFile/dacca2/route-constraints-in-mvc/
Also, routes should be added from most specific to most general.
You should place any custom route above the Default route.
Just swap the order and you good to go
//before default route
routes.MapRoute(
name: "StoreSearch",
url: "{storeName}",
defaults: new { controller = "Search", action = "Store" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

Modifying the routing asp mvc

In my ASP MVC application, I need to show the URL path like this:
domain.com/viewName
I have the route config in the follow way:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.RouteExistingFiles = false;
routes.IgnoreRoute("{*allfiles}", new { allfiles = #".*\.(css|js|gif|jpg|png)" });
routes.MapRoute(
"Theme",
"theme/{theme}/{page}",
new { controller = "Theme", action = "Index", page = UrlParameter.Optional },
new[] { "Tenant.Controllers" }
);
routes.MapRoute(
"ThemeSlider",
"theme/slider/{id}",
new { controller = "Theme", action = "Slider" },
new[] { "Tenant.Controllers" }
);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new[] { "Tenant.Controllers" }
);
routes.MapRoute(
"Default_tenant",
"{page}",
new { controller = "Home", action = "Index", page = UrlParameter.Optional },
new[] { "Tenant.Controllers" }
);
routes.MapMvcAttributeRoutes();
}
But it does not works, I have to specify the page parameter in the follow way: domain.com/?page=viewName. How should be the route config that leaves the follow routes:
domain.com.co/controller/action/
domain.com.co/view
Any sugestion ?? Thanks in advance
You mean to get rid of the controller? This sort of thing will work, but beware of conflicts between routes in different controllers:
routes.MapRoute(
name: "HomeRoute",
url: "{action}",
defaults: new {controller = "Home", action = "Index"},
constraints: new { homeControllerConstraint = new HomeControllerConstraint() }
);
Now every action in the home controller maps to Domain.com/Action instead of Domain.com/Home/Action.
Here is some reflective code to efficiently work out whether a route is in the home controller, for instance.
internal class HomeControllerConstraint : IRouteConstraint
{
private static readonly Lazy<HashSet<string>> homeMethods = new Lazy<HashSet<string>>(CreateHomeMethods);
private static HashSet<string> CreateHomeMethods()
{
return typeof(HomeController).GetMethods().Select(e => e.Name.ToLower()).ToSet();
}
public static bool Exists(string name)
{
return homeMethods.Value.Contains(name.Trim().ToLower());
}
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
return homeMethods.Value.Contains(values["action"].ToString().ToLower());
}
}

ASP.NET MVC 5 Routing Areas

I have a problem with routing in ASP.NET MVC 5. I create two Areas "Public" and "Admin" and I set Public/NewsController/Index default site in url localhost/Example.WebUI but now I can't use localhost/Example.WebUI/Admin...
My code with T4MVC:
AdminAreaRegistration.cs
public class AdminAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Admin";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Admin_default",
"Admin/{controller}/{action}/{id}",
new { area = MVC.Admin, controller = MVC.Admin.Hierarchy.Name, action = MVC.Admin.Hierarchy.ActionNames.Index, id = UrlParameter.Optional }
);
}
}
PublicAreaRegistration.cs
public class PublicAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Public";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Public_default",
"{controller}/{action}/{id}",
new { controller = MVC.Public.News.Name, action = MVC.Public.News.ActionNames.Index, id = UrlParameter.Optional }
);
}
}
RegistrationRoutes.cs
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { area = MVC.Public.Name, controller = MVC.Public.News.Name, action = MVC.Public.News.ActionNames.Index, id = UrlParameter.Optional },
namespaces: new[] { "Example.WebIU.Areas.Public.Controllers" }
);
routes.MapRoute(
name: "MyRoute",
url: "{controller}/{action}/{id}",
defaults: new { controller = MVC.Admin.Hierarchy.Name, action = MVC.Admin.Hierarchy.ActionNames.Index, id = UrlParameter.Optional },
namespaces: new[] { "Example.WebIU.Areas.Admin.Controllers" }
);
}
And Global.asax
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AutoMapper.Mapper.Initialize(cfg => cfg.AddProfile<AutomapperProfile>());
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
}
Can you help me?
The issue is that all of your route URLs are exactly the same except for the Admin area. MVC will always use the first matching route and ignore every route that follows, so it is important to both configure the routes in the right order, and to ensure that the route doesn't match any URLs except for the ones it should. This problem and possible solutions for it are best described in Why map special routes first before common routes in asp.net mvc?.
The simplest fix is to use a literal segment in the URL to ensure it only matches a specific set of URLs.
PublicAreaRegistration.cs
public class PublicAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Public";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Public_default",
"Public/{action}/{id}",
new { controller = MVC.Public.News.Name, action = MVC.Public.News.ActionNames.Index, id = UrlParameter.Optional }
);
}
}
RegisterRoutes.cs
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
name: "MyRoute",
url: "MyRoute/{action}/{id}",
defaults: new { controller = MVC.Admin.Hierarchy.Name, action = MVC.Admin.Hierarchy.ActionNames.Index, id = UrlParameter.Optional },
namespaces: new[] { "Example.WebIU.Areas.Admin.Controllers" }
).DataTokens["area"] = "Admin";
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { area = MVC.Public.Name, controller = MVC.Public.News.Name, action = MVC.Public.News.ActionNames.Index, id = UrlParameter.Optional },
namespaces: new[] { "Example.WebIU.Areas.Public.Controllers" }
);
}
Note you also have MyRoute registered in the wrong order. This route must be placed before Default in order for it to have any effect at all. As previously mentioned, you also need a way to ensure that not all 3 segment URLs will match. The above shows the use of a literal segment to do this, but you could do more advanced matching with a Regex route constraint or a Custom route constraint.
Finally, you are missing the .DataTokens["area"] = "Admin" needed to set the route to a specific area (this is needed if you don't define the route inside of the AdminAreaRegistration class).

How to await a service call in routes.MapRoute in MVC5

I am doing a massive overhaul on a custom CMS that I am writing in MVC5. This custom CMS has "pages" that have "urls" stored in a database. For instance if a user requested /stackoverflow in the browser and there was a page in the database with /stackoverflow listed as the url, then I am serving the database content at the specificed CoreCms/Index Controller and view with the database content as a model property. The idea is that I can use this single controller/view to serve up any page in the database.
The rewrite that I am working uses dependency injection and async calls only in the service layer. It seems that I am having a bit of trouble with the RouteConfig RegisterRoutes static method.
Here's some code...
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("FileBrowser/{pathInfo}");
routes.MapRoute(
name: "CmsRoutes",
url: "{*permalink}",
defaults: new { controller = "CmsCorePage", action = "Index" },
constraints: new { url = new CmsCoreRouting() }
);
routes.MapRoute(
name: "ArticlesCategoryRoute",
url: "Articles/{categoryURL}",
defaults: new { controller = "CmsCoreArticles", action = "Index", categoryURL = UrlParameter.Optional }
);
routes.MapRoute(
name: "ArticlesPostsRoute",
url: "Articles/{categoryURL}/{postURL}",
defaults: new { controller = "CmsCoreArticles", action = "ArticlePost", categoryURL = UrlParameter.Optional, postURL = UrlParameter.Optional }
);
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
Note that I am rewriting old code and the constraints: new { url = new CmsCoreRouting() } is code that I intend on altering here I believe.
For reference, here is the CmsCoreRouting class...
public class CmsCoreRouting : IRouteConstraint
{
private ICoreCmsServices _coreSvc;
public CmsCoreRouting()
{
}
public CmsCoreRouting(ICoreCmsServices coreSvc)
{
_coreSvc = coreSvc;
}
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
if (httpContext?.Request?.FilePath == null) { return false; }
string myURL = httpContext.Request.FilePath;
if (myURL.StartsWith("/"))
{
myURL = myURL.Substring(1, myURL.Length - 1);
}
myURL = myURL.ToLower();
var siteId = CoreCms.Core.Settings.CoreCmsSettings.SiteId;
var cmsPage = AsyncUtility.RunSync(() => _coreSvc.PageService.FindBySiteAndUrlAsync(siteId, myURL));
if (cmsPage != null)
{
return true;
}
var cmsArticle = AsyncUtility.RunSync(() => _coreSvc.ArticleService.FindCategoryBySiteAndUrlAsync(siteId, myURL));
if (cmsArticle != null)
{
return true;
}
return false;
}
}
Problem: When MVC starts, it calls the parameterless constructor on CmsCoreRouting (because I told it to in url = new CmsCoreRouting()) but I am not sure how to both use AutoFac's DI so that I don't have to pass around my own instances of the service and repository and DbContext on this RegisterRoutes function.
Any help on this would be great. I would like to do this "right".
Just ask the resolver (AutoFac) to create it for you:
routes.MapRoute(
name: "CmsRoutes",
url: "{*permalink}",
defaults: new { controller = "CmsCorePage", action = "Index" },
constraints: new { url = DependencyResolver.Current.GetService<CmsCoreRouting>() }
);

Categories