Attribute routing constraints with no parameters - c#

I would like to create attribute routing in MVC 5 application with one simple constraint:
public class UserAgentConstraint : IRouteConstraint
{
private string _requiredUserAgent;
public UserAgentConstraint(string requiredUserAgent)
{
_requiredUserAgent = requiredUserAgent;
}
public bool Match(HttpContextBase httpContext, Route route,
string parameterName, RouteValueDictionary values,
RouteDirection routeDirection)
{
return httpContext.Request.UserAgent != null &&
httpContext.Request.UserAgent.Contains(_requiredUserAgent);
}
}
In MVC 4 i was able to register it in this way:
routes.MapRoute("ChromeRoute", "{*catchall}",
new { controller = "Home", action = "Index" },
new { customConstraint = new UserAgentConstraint("Chrome") });
How can i achieve same result using only attribute routing?
I know that i can register constraint in this way:
var constraintsResolver = new DefaultInlineConstraintResolver();
constraintsResolver.ConstraintMap.Add("UserAgent", typeof(UserAgentConstraint));
But how to add it to my route pattern? Something like this:
[Route("Home/Index:UserAgent(Chrome)")]
public ActionResult Index() {}
doesnt work

Review aspnetmvc source code,It can be helpful , try to review
RouteContraintAttribute , AreaAttribute and RouteAttribute
For example RouteConstraintAttribute:
https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNet.Mvc.Core/RouteConstraintAttribute.cs
I'm trying to figure out how to do that.

Attribute routing allows the following constraints to be applied and is used via this format {parameter:constraint} eg [Route("Index/{id:int}")]. So your attribute should be [Route("Home/{Index:UserAgent(Chrome)}")]
{x:alpha}
{x:bool}
{x:datetime}
{x:decimal}
{x:double}
{x:float}
{x:guid}
{x:int}
{x:length()}
{x:long}
{x:max()}
{x:maxlength()}
{x:min()}
{x:minlength()}
{x:range()} {x:regex()}

Related

ASP.NET MVC Page-URL and Controller/Action Routing

I have problems building an ASP.NET MVC page which allows two sorts of routing.
I have a database where all pages are stored with an url-path like: /Site1/Site2/Site3
i tried to use an IRouteConstraint in my first route, to check wether the requested
site is a site from my database (permalink).
In the second case, i want to use the default asp.net mvc {controller}/{action} functionality, for providing simple acces from an *.cshtml.
Now i don't know if this is the best way. Furthermore i have the problem, how to root with the IRouteContraint.
Does anyone have any experiance with this?
I'm using asp.net mvc 5.
Problem solved, final solution:
Adding this two routes:
routes.MapRoute(
"FriendlyUrlRoute",
"{*FriendlyUrl}"
).RouteHandler = new FriendlyUrlRouteHandler();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Page", action = "Load", id = UrlParameter.Optional },
namespaces: controllerNamespaces.ToArray()
);
My own Route-Handler:
public class FriendlyUrlRouteHandler : System.Web.Mvc.MvcRouteHandler
{
protected override IHttpHandler GetHttpHandler(System.Web.Routing.RequestContext requestContext)
{
var friendlyUrl = (string)requestContext.RouteData.Values["FriendlyUrl"];
WebPageObject page = null;
if (!string.IsNullOrEmpty(friendlyUrl))
{
page = PageManager.Singleton.GetPage(friendlyUrl);
}
if (page == null)
{
page = PageManager.Singleton.GetStartPage();
}
// Request valid Controller and Action-Name
string controllerName = String.IsNullOrEmpty(page.ControllerName) ? "Page" : page.ControllerName;
string actionName = String.IsNullOrEmpty(page.ActionName) ? "Load" : page.ActionName;
requestContext.RouteData.Values["controller"] = controllerName;
requestContext.RouteData.Values["action"] = actionName;
requestContext.RouteData.Values["id"] = page;
return base.GetHttpHandler(requestContext);
}
}
You can use attribute routing which is in MVC 5 and combine attribute routing with convention-based routing to check the condition that you want on controller class or action methods.
And you could make the constraint yourself to use it on the action methods like this:
public class ValuesConstraint : IRouteConstraint
{
private readonly string[] validOptions;
public ValuesConstraint(string options)
{
validOptions = options.Split('|');
}
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
object value;
if (values.TryGetValue(parameterName, out value) && value != null)
{
return validOptions.Contains(value.ToString(), StringComparer.OrdinalIgnoreCase);
}
return false;
}
}
To use attribute routing you just need to call MapMvcAttributeRoutes during configuration and call the normal convention routing afterwards. also you should add your constraint before map the attributes, like the code below:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
var constraintsResolver = new DefaultInlineConstraintResolver();
constraintsResolver.ConstraintMap.Add("values", typeof(ValuesConstraint));
routes.MapMvcAttributeRoutes(constraintsResolver);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
Now on your controller class you can check the route and decide what to do with different urls like below:
for example: // /mysite/Site1 and /mysite/Site2 but not /mysite/Site3
[Route("mysite/{site:values(Site1|Site2)}")]
public ActionResult Show(string site)
{
return Content("from my database " + site);
}
And you could do all kind of checking just on you controller class as well.
I hope this gives you a bit of clue to achieve the thing that you want.

Is it possible for two areas to share the same route and still both be reachable?

I have two areas that register routes as shown below:
"Website" area:
context.MapRoute(
"Landing Controllers",
"{controller}/{action}",
new { controller = "Home", action = "Index" }
);
"Mobile" area:
context.MapRoute(
"Mobile Defaults",
"{controller}/{action}",
new { controller = "MobileHome", action = "Index" },
new { controller = "MobileHome", action = "Index" }
);
By default, one or the other of these routes would be consistently taken when trying to go to the root URL /. But suppose we decorated our controller actions with a custom AuthorizeAttribute, where the OnAuthorization method is overridden to redirect the user to the correct controller when appropriate, as below. (Idea taken from a great blog post.)
public class MobileRedirectAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
var result = // Logic to generate the ActionResult that conditionally
// takes us to the other route goes here.
filterContext.Result = result;
}
}
I've tried using a new RedirectResult and RedirectToRouteResult, neither of which work as I'd like because of the routing conflict. Is there a way to set AuthorizationContext.Result to a value that would take us to the action that we're not currently executing? (As a last resort, I can just prefix the mobile route with some sort of namespacing variable, but I'd like to avoid going down that road just yet.)
My question can probably also be summarized by having a look at Wikipedia's desktop/mobile routing. Their two sites, http://en.m.wikipedia.org/wiki/Main_Page and http://en.wikipedia.org/wiki/Main_Page also share identical routes, but, depending on which mode you're in, return very different results.
Would it be possible to set up Wikipedia's routing in an MVC project where each environment (mobile/desktop) is registered in its own area?
A colleague led me to a promising solution using a custom IRouteConstraint.
public class HelloWorldConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route,
string parameterName, RouteValueDictionary values,
RouteDirection routeDirection)
{
// Determine whether to accept the route for this request.
var browser = BrowserDetector.Parse(httpContext.Request.UserAgent);
if (browser == BrowserPlatform.Mobile)
{
return true;
}
return false;
}
}
And my route declaration now looks like the below, where the route constraint is attached to a route parameter chosen at random.
context.MapRouteLowercase(
"Mobile Defaults",
"{controller}/{action}",
new { controller = "MobileHome", action = "Index" },
// In this case, it's not so much necessary to attach the constraint to
// a particular route parameter as it is important to be able to inspect
// the HttpContextBase provided by the IRouteConstraint.
new {
controller = new HelloWorldConstraint()
}
);
Not with standard MVC Routing. You can probably do with attribute routing, available in either MVC 5 or via the nuget package, AttributeRouting.

ASP.NET Webforms routing, check input types

Say I have a route declaration similar to this in Global.asax:
RouteTable.Routes.MapPageRoute("Products", "products/{productno}/{color}", "~/mypage.aspx");
How do I configure the route so it only intercepts the request if {productno} is a valid Guid and {color} is an integer value?
OK url: /products/2C764E60-1D62-4DDF-B93E-524E9DB079AC/123
Invalid url: /products/xxx/123
The invalid urls will be picked up by another rule/route or just ignored completely.
You can write your own RouteConstraint by implementing a matching rule. For example, here's one that makes sure a route parameter is a valid date:
public class DateTimeRouteConstraint : IRouteConstraint
{
public bool Match(System.Web.HttpContextBase httpContext, Route route,
string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
DateTime dateTime;
return DateTime.TryParse(values[parameterName] as string, out dateTime);
}
}
And then you can enforce it by changing the route definition (this is for MVC 2.0):
routes.MapRoute(
"Edit",
"Edit/{effectiveDate}",
new { controller = "Edit", action = "Index" },
new { effectiveDate = new Namespace.Mvc.DateTimeRouteConstraint() }
);
Here are a few more resources:
How can I create a route constraint of type System.Guid?
http://prideparrot.com/blog/archive/2012/3/creating_custom_route_constraints_in_asp_net_mvc
Short of creating your own RouteConstraint the standard routing system supports RegEx route constraints in the standard syntax. Something like:
string guidRegex = #"^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$";
string intRegex = #"^([0-9]|[1-9][0-9]|[1-9][0-9][0-9])$";
routes.MapRoute(
"Products",
"products/{productno}/{color}",
new { controller = "Products", action = "Index" },
new { productno = guidRegex, color = intRegex }
);

ASP.NET Routing - Route Constraint with database lookup

I'm just starting out with C# and ASP.NET and have the following questions. I am working with code adapted from a couple different tutorials playing with Northwind and have gotten this far. The list of acceptable categories is currently hard coded in a string but I would like to look up the CategoryName in the database to verify that it exists.
Obviously the purpose of this is to ensure that users don't just type:
www.domain.com/Categories/AnyUrlWillWork and return a valid page.
Also does anyone have an tips of how they are dealing with capitalization issues since the routing is case sensitive? For example Categories/beverages should forward to Categories/Beverages ?
Thanks in advance for any assistance, and glad to be joining Stack Overflow.
//Complex contraint class
public class EchoConstraint : IRouteConstraint
{
public readonly string[] ValidMessages = { "Beverages", "Produce", "Confections", "Seafood" };
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
string message = values["CategoryName"] as string;
return ValidMessages.Contains(message);
}
}
//Routes
RouteTable.Routes.MapPageRoute(
"Category Route", // Route name
"Categories/{CategoryName}", // Url pattern
"~/ShowProductsInCategory.aspx", // Physical file
true,
new RouteValueDictionary
{{"CategoryName", "Beverages"}}, //Sets default value if none is provided in URL
new RouteValueDictionary
{{"CategoryName", new EchoConstraint()}}
);
Is this MVC? If so, why not route to a function, which will check the category name against your data store and redirect to an error page if such category doesn't exist?
public ActionResult Index(string catName)
{
if (string.IsNullOrEmpty(catName) || !MyCategoriesDataStore.Exists(catName))
return RedirectToAction("CategoryError");
// Load category data to be used from View
return View();
}
A WebForms solution would be:
public class EchoConstraint : IRouteConstraint
{
private IRepository rep;
public EchoConstraint(IRepository r) { rep = r; }
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
return rep.GetCategory(message) == 0;
}
}
.
.
.
new RouteValueDictionary
{{"CategoryName", new EchoConstraint(myDataAccessRepo)}}
);
Where you pass an object of class implementing IRepository with your data access logic (using NHibernate, EntityFramework or your own DAL implementation). You need to return a bool value, and this is what I did.

ASP.NET MVC Route based on Web Browser/Device (e.g. iPhone)

Is it possible, from within ASP.NET MVC, to route to different controllers or actions based on the accessing device/browser?
I'm thinking of setting up alternative actions and views for some parts of my website in case it is accessed from the iPhone, to optimize display and functionality of it. I don't want to create a completely separate project for the iPhone though as the majority of the site is fine on any device.
Any idea on how to do this?
Mix: Mobile Web Sites with ASP.NET MVC and the Mobile Browser Definition File
Don't know if the above helps as I havn't watched it yet.
And this one;
How Would I Change ASP.NET MVC Views Based on Device Type?
You can create a route constraint class:
public class UserAgentConstraint : IRouteConstraint
{
private readonly string _requiredUserAgent;
public UserAgentConstraint(string agentParam)
{
_requiredUserAgent = agentParam;
}
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
return httpContext.Request.UserAgent != null &&
httpContext.Request.UserAgent.ToLowerInvariant().Contains(_requiredUserAgent);
}
}
And then enforce the constraint to one of the routes like so:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new {id = RouteParameter.Optional},
constraints: new {customConstraint = new UserAgentConstraint("Chrome")},
namespaces: new[] {"MyNamespace.MVC"}
);
You could then create another route pointing to a controller with the same name in another namespace with a different or no constraint.
Best bet would be a custom action filter.
All you have to do is inherit from ActionMethodSelectorAttribute, and override the IsValidRequest class.
public class [IphoneRequest] : ActionMethodSelectorAttribute
{
public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo)
{
// return true/false if device is iphone
Then in your controller
[IphoneRequest]
public ActionResult Index()

Categories

Resources