I want to set up a route that will match on any url, with a constraint. This is what I have tried:
routes.MapRouteLowercase("CatchAll Content Validation", "{*url}",
new { controller = "Content", action = "LoadContent" },
new { url = new ContentURLConstraint(), }
);
and for testing purposes I have the following simple constraint:
public class ContentURLConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
var s = (string)values["url"];
return s.Contains("comm");
}
}
If the constraint is satisfied I when expect to pass the full url to the controll action LoadContent
public ActionResult LoadContent(string url)
{
return Content(url);
}
I am expecting, when I load a page, for s in the Match function to contain the entire url, so that I can check it, but instead it is null. What might I be missing?
You get null when there is no url in the route, i.e. host itself: http://localhost:64222.
When there is a url, e.g.: http://localhost:64222/one/two, your url parameter will hold a value: one/two.
You can modify your constraint to something like:
public class ContentURLConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values routeDirection)
{
var url = values[parameterName] as string;
if (!string.IsNullOrEmpty(url))
{
return url.Contains("comm");
}
return false; // or true, depending on what you need.
}
}
What might I be missing?
Could be that you are missing that url constraint does not contain host, port, schema and query string. If you need it - you can get it from httpContext.Request.
Related
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()}
I am trying to see if I can change Mvc routing to use a a ticket based system where the route is an guid string to an external dictionary of values. So I setup a new route like so:
routes.Add(new Route("{ticketid}", new Shared.TicketRouteHandler()));
And created a new route handler:
public class TicketRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new TicketHttpHandler(requestContext);
}
}
And a new HttpHandler class. In the ProcessRequest method, I read the values for the controller and action out of the ticket, and place them into the route data collection:
string ticketidString = this.Request.RouteData.GetRequiredString("ticketid");
var ticket = Shared.Ticket.GetTicket(ticketId);
foreach (var item in ticket)
{
Request.RouteData.Values[item.Key] = item.Value;
}
The controller is executed like normal from there.
In the controller I have it looking for a ticketid parameter and if not found it creates one, and does a RedirecToAction, populating the new Ticket with the controller / action values:
public ActionResult Index(Guid? ticketid)
{
var ticket = Shared.Ticket.GetOrCreateTicket(ticketid);
if (ticketid == null)
{
//push the new values into the ticket dictionary instead of the route
string controller = "Home";
string action = "Index";
ticket.SetRoute(controller, action);
return RedirectToAction(action, controller, new { ticketid = ticket.Id });
}
return View();
}
This is all working fine, the problem is the URL that is created from the redirect action looks like this:
http://localhost:49952/df3b9f26-6c1c-42eb-8d0d-178e03b7e6f6?action=Index&controller=Home
Why is ?action=Index&controller=Home getting attached to the url?
If I remove the extra pieces and refresh the page, everything loads perfectly from the ticket collection.
I imagine that it's happening after the HttpHandler code is finished.
What can I do to stop this behavior?
The RedirectToAction method makes a new request and changes the URL in the browser:
This part is changing the URL:
return RedirectToAction(action, controller, new { ticketid = ticket.Id });
1) Return View doesn't make a new requests, it just renders the view without changing URLs in the browser's address bar.
2) Return RedirectToAction makes a new requests and URL in the browser's address bar is updated with the generated URL by MVC.
3) Return Redirect also makes a new requests and URL in the browser's address bar is updated, but you have to specify the full URL to redirect
4) Between RedirectToAction and Redirect, best practice is to use RedirectToAction for anything dealing with your application actions/controllers. If you use Redirect and provide the URL, you'll need to modify those URLs manually when you change the route table.
5) RedirectToRoute redirects to the specifies route defined in the the Route table.
But, View doesn't, try to adapt your code to return a View instead! Or, in the worst case, make something to handle the URL from the Response!
After digging around the mvc4 source, I think I found a solution:
Derive a new type from RedirectToRouteResult, override ExecuteResult and modify the url generation to only return the ticket string:
public class TicketRedirectResult : RedirectToRouteResult
{
public override void ExecuteResult(ControllerContext context)
{
string destinationUrl = UrlHelper.GenerateUrl(
RouteName,
null /* actionName */,
null /* controllerName */,
//Only return the ticket id, not the entire dictionary
new RouteValueDictionary(new { ticketid = RouteValues["ticketid"] }),
Routes,
context.RequestContext, false /* includeImplicitMvcValues */);
// snip other code
}
}
Then in your controller override RedirectToAction to return an instance of the new derived type:
protected override RedirectToRouteResult RedirectToAction(string actionName, string controllerName, RouteValueDictionary routeValues)
{
RouteValueDictionary mergedRouteValues;
if (RouteData == null)
{
mergedRouteValues = MergeRouteValues(actionName, controllerName, null, routeValues, includeImplicitMvcValues: true);
}
else
{
mergedRouteValues = MergeRouteValues(actionName, controllerName, RouteData.Values, routeValues, includeImplicitMvcValues: true);
}
//Only change here
return new TicketRedirectResult(mergedRouteValues);
}
Now the redirects only populates the ticket portion in the url.
Given a route name, can I get the default action name and controller name?
routes.MapRoute(
name: "MonthlyCalendar",
url: "FMB/Calendar/{jamaatName}/{year}/{month}",
defaults: new { controller = "FMB", action = "MonthlyCalendar",
jamaatName = UrlParameter.Optional,year = UrlParameter.Optional,
month=UrlParameter.Optional}
);
No, of course that you cannot get controller and action name from just a route name. A route mapping might look like this: {controller}/{action}/{id}. That's the only thing you could get if you only have a route name.
If on the other hand you have a full url such as http://example.com/Home/Index then this is an entirely different story. Now you can get the controller=Home and action=Index from the RouteData.
UPDATE:
It seems that you are attempting to extract the default values for the controller and action from your routing configuration given the route name. This can easily be done by storing the route name when registering it in the DataTokens property:
RouteTable.Routes.MapRoute(
"foo",
"{controller}/{action}",
new { controller = "Home", action = "Index" }
).DataTokens["__RouteName"] = "foo";
and then:
var route = RouteTable.Routes.OfType<Route>().FirstOrDefault(x =>
x.DataTokens.ContainsKey("__RouteName") &&
(string)x.DataTokens["__RouteName"] == "foo"
);
if (route != null)
{
var defaultController = route.Defaults["controller"];
var defaultAction = route.Defaults["action"];
}
The reason why you need to store the route name in the DataTokens is because this
information is not available in the Route instance.
UPDATE:
At some point MS added an indexer to RouteCollection to lookup by name, so the above is no longer necessary:
((System.Web.Routing.Route)Url.RouteCollection["MyRouteName"]).Defaults
You should probably check first to make sure it exists to avoid null exception.
I've created this extension method that adds the "active" css class to a route link. This method gets the default action and controller from the route based on route name, if an action and controller is not provided in the RouteValueDictionary.
You can get the default action from the route in the RouteTable, however you need to cast the return value as Route instead of RouteBase.
Example:
var action = (RouteTable.Routes["Default"] as Route).Defaults["action"] as string;
Full Extension Method:
public static MvcHtmlString MenuRouteLink(this HtmlHelper htmlHelper, string linkText, string routeName, RouteValueDictionary routeValues,
IDictionary<string, object> htmlAttributes)
{
string currentAction = htmlHelper.ViewContext.RouteData.GetRequiredString("action");
string currentController = htmlHelper.ViewContext.RouteData.GetRequiredString("controller");
var route = RouteTable.Routes[routeName] as Route;
if (route != null)
{
routeValues = routeValues ?? new RouteValueDictionary();
string routeAction = (routeValues["action"] as string ?? route.Defaults["action"] as string) ?? string.Empty;
string routeController = (routeValues["controller"] as string ?? route.Defaults["controller"] as string) ?? string.Empty;
if (routeAction.Equals(currentAction, StringComparison.OrdinalIgnoreCase) && routeController.Equals(currentController, StringComparison.OrdinalIgnoreCase))
{
htmlAttributes = htmlAttributes ?? new Dictionary<string, object>();
var currentCssClass = htmlAttributes["class"] as string ?? string.Empty;
htmlAttributes["class"] = string.Concat(currentCssClass, currentCssClass.Length > 0 ? " " : string.Empty, "active");
}
}
return htmlHelper.RouteLink(linkText, routeName, routeValues, htmlAttributes);
}
You can get the RouteData that is supposed to serve your request by reading ControllerContext.RouteData. RouteData object has Values which in turn has Keys that you can examine and apply to Values to get all the fragments of the route.
Normally, you can loop through RouteCollection (this depends in which context you are located, eg. ViewContext, or using HtmlHelper object) and extract each item and cast it to Route object. After that call Route.GetRouteData(YourHttpContextBaseHere) which return RouteData or null. If it returns an object, that Route served your request and examine Defaults property.
For example, lets say you are building an HTML helper and you have access to HtmlHelper object. THe following code shows you what you could do:
var defaults = (RouteValueDictionary) null;
foreach (var routeBase in html.RouteCollection)
{
var route = (Route) routeBase;
var routeData = route.GetRouteData(html.ViewContext.HttpContext);
if (routeData != null)
{
defaults = route.Defaults;
break;
}
}
// Do something with defaults object
You could look into System.Web.Routing.RouteTable.Routes. It contains all routes in system. But you don't find default action and controller if they are not specified
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 }
);
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.