MVC 3 Route Constraints that check the POST - c#

So I have a method that accepts some JSON data and binds the data to some variables. The route only has the method name in it, nothing else.
Is it possible to have a route constraint that looks at the POST data and checks some variables to determine whether it is the correct route?
The methods:
public ActionResult GetDataV1(string userId)
{
// Do stuff one way
}
public ActionResult GetDataV2(string userId)
{
// Do stuff another way
}
The routes:
routes.MapRoute(
"API GetData V1",
"GetData",
new { controller = "API", action = "GetDataV1" },
new { version = new APIVersionRoutingConstraint("1") }
);
routes.MapRoute(
"API GetData V2",
"GetData",
new { controller = "API", action = "GetDataV2" },
new { version = new APIVersionRoutingConstraint("2") }
);
The client would post { "version":"1", "userId":"mrjohn" } to /GetData and would get response from GetDataV1. The APIVersionRoutingConstraint would make sure the right method is called depending on the version variable.
Would it be good practice to try to deserialise the request stream inside the constraint? Maybe it would be better to put the version in the URL like /GetData/1 and the other variables in the JSON body?

Rather than trying to check the version as a route constraint, why not go to one Action that then checks the version to execute the appropriate work?
I haven't tested this, but the idea is:
public ActionResult GetData(string userId, int version)
{
switch(version)
{
case 1:
return GetDataV1(userId);
case 2:
return GetDataV2(userId);
// You could always add more cases here as you get more versions,
// and still only have the one route
default:
// Probably more appropriate to return a result that contains error info,
// but you get the idea
throw new ArgumentOutOfRangeException("version");
}
}
// Made these private since they are called from the public action
private ActionResult GetDataV1(string userId)
{
// Do stuff one way
}
private ActionResult GetDataV2(string userId)
{
// Do stuff another way
}
And then you only need the one route
routes.MapRoute(
"GetData",
"GetData",
new { controller = "API", action = "GetData" },
new { version = "\d+" } // Require a numeric version
);

Made the APIVersionRoutingConstraint like this:
public class APIVersionRoutingConstraint : IRouteConstraint
{
private string _versionNumber;
public APIVersionRoutingConstraint(string versionNumber)
{
_versionNumber = versionNumber;
}
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
string version = null;
if (httpContext.Request.ContentType.Contains("application/json"))
{
string body = new StreamReader(httpContext.Request.InputStream).ReadToEnd();
httpContext.Request.InputStream.Position = 0;
var vals = new JavaScriptSerializer().DeserializeObject(body) as Dictionary<string, object>;
if (vals.ContainsKey("version"))
version = vals["version"].ToString();
}
// Must have version to pass constraint
return !string.IsNullOrEmpty(version) && version == _versionNumber;
}
}
It's not super-efficient I guess, but it gets the job done. Only the route that matches the version in the POST body gets used.

Related

How to use a custom route getting value before {controller}/{action}/{id}

I need to have a variable before every controller.
I want something like that:
www.mysite.com/SYSTEMVARIABLE/Controller/Action
Example:
www.mysite.com/internalevent/Inscricao/Index
www.mysite.com/externalevent/Inscricao/Index
And i need to get the "internalevent" or "externalevent"
Example of values array
At this example, i need the [0] value of array, be the SYSTEMVARIABLE, not "Inscricao (Controller)"
So, how can i get the "SYSTEMVARIABLE" value and work with this ?
I have the following now (not working...)
routes.MapRoute(
name: "Default",
url: "{sistema}/{controller}/{action}/{id}",
defaults: new { controller = "Inscricao", action = "Index", id = UrlParameter.Optional },
constraints: new { sistema = new SistemaRouteConstraint() }
);
I need to get the value from "{sistema}", and i need to do some logic on this value.
Below is the class i want to get it, but ALWAYS the "{sistema}" only gets me the controller value (controller come twice on RouteValueDictionary)
public class SistemaRouteConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
object sistema;
if(values.TryGetValue(parameterName, out sistema) && values != null)
{
var NomeSistema = values["sistema"].ToString();
using (Entities db = new Entities())
{
/*some logic here*/
}
}
return true;
}
}

How mark a .NET Web API Regex Route Parameter as optional [duplicate]

I'm trying to write a route with a nullable int in it. It should be possible to go to both /profile/ but also /profile/\d+.
routes.MapRoute("ProfileDetails", "profile/{userId}",
new {controller = "Profile",
action = "Details",
userId = UrlParameter.Optional},
new {userId = #"\d+"});
As you can see, I say that userId is optional but also that it should match the regular expression \d+. This does not work and I see why.
But how would I construct a route that matches just /profile/ but also /profile/ followed by a number?
The simplest way would be to just add another route without the userId parameter, so you have a fallback:
routes.MapRoute("ProfileDetails", "profile/{userId}",
new {controller = "Profile",
action = "Details",
userId = UrlParameter.Optional},
new {userId = #"\d+"});
routes.MapRoute("Profile", "profile",
new {controller = "Profile",
action = "Details"});
As far as I know, the only other way you can do this would be with a custom constraint. So your route would become:
routes.MapRoute("ProfileDetails", "profile/{userId}",
new {controller = "Profile",
action = "Details",
userId = UrlParameter.Optional},
new {userId = new NullableConstraint());
And the custom constraint code will look like this:
using System;
using System.Web;
using System.Web.Routing;
using System.Web.Mvc;
namespace YourNamespace
{
public class NullableConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
if (routeDirection == RouteDirection.IncomingRequest && parameterName == "userId")
{
// If the userId param is empty (weird way of checking, I know)
if (values["userId"] == UrlParameter.Optional)
return true;
// If the userId param is an int
int id;
if (Int32.TryParse(values["userId"].ToString(), out id))
return true;
}
return false;
}
}
}
I don't know that NullableConstraint is the best name here, but that's up to you!
It's possible something changed since this question was answered but I was able to change this:
routes.MapPageRoute(
null,
"projects/{operation}/{id}",
"~/Projects/ProjectWizard.aspx",
true,
new RouteValueDictionary(new
{
operation = "new",
id = UrlParameter.Optional
}),
new RouteValueDictionary(new
{
id = new NullableExpressionConstraint(#"\d+")
})
);
With this:
routes.MapPageRoute(
null,
"projects/{operation}/{id}",
"~/Projects/ProjectWizard.aspx",
true,
new RouteValueDictionary(new
{
operation = "new",
id = UrlParameter.Optional
}),
new RouteValueDictionary(new
{
id = #"\d*"
})
);
Simply using the * instead of the + in the regular expression accomplished the same task. The route still fired if the parameter was not included, but if included it would only fire if the value was a valid integer. Otherwise it would fail.
ASP.NET MVC 3 has solved this problem, and as Alex Ford brought out, you can use \d* instead of writing a custom constraint. If your pattern is more complicated, like looking for a year with \d{4}, just make sure your pattern matches what you want as well as an empty string, like (\d{4})? or \d{4}|^$. Whatever makes you happy.
If you are still using ASP.NET MVC 2 and want to use Mark Bell's example or NYCChris' example, please be aware that the route will match as long as the URL parameter contains a match to your pattern. This means that the pattern \d+ will match parameters like abc123def. To avoid this, wrap the pattern with ^( and )$ either when defining your routes or in the custom constraint. (If you look at System.Web.Routing.Route.ProcessConstraint in Reflector, you'll see that it does this for you when using the built in constraint. It also sets the CultureInvariant, Compiled, and IgnoreCase options.)
Since I already wrote my own custom constraint with the default behavior mentioned above before realizing I didn't have to use it, I'll leave it here:
public class OptionalConstraint : IRouteConstraint
{
public OptionalConstraint(Regex regex)
{
this.Regex = regex;
}
public OptionalConstraint(string pattern) :
this(new Regex("^(" + pattern + ")$",
RegexOptions.CultureInvariant |
RegexOptions.Compiled |
RegexOptions.IgnoreCase)) { }
public Regex Regex { get; set; }
public bool Match(HttpContextBase httpContext,
Route route,
string parameterName,
RouteValueDictionary values,
RouteDirection routeDirection)
{
if(routeDirection == RouteDirection.IncomingRequest)
{
object value = values[parameterName];
if(value == UrlParameter.Optional)
return true;
if(this.Regex.IsMatch(value.ToString()))
return true;
}
return false;
}
}
And here's an example route:
routes.MapRoute("PostsByDate",
"{year}/{month}",
new { controller = "Posts",
action = "ByDate",
month = UrlParameter.Optional },
new { year = #"\d{4}",
month = new OptionalConstraint(#"\d\d") });
should your regex be \d*?
Thanks to Mark Bell for this answer, it helped me quite a bit.
I'm wondering why you hard coded the check for "userId" in the constraint? I slightly rewrote your class like to user the parameterName parameter, and it seems to be working just fine.
Am I missing anything by doing it this way?
public class OptionalRegExConstraint : IRouteConstraint
{
private readonly Regex _regEx;
public OptionalRegExConstraint(string matchExpression=null)
{
if (!string.IsNullOrEmpty(matchExpression))
_regEx = new Regex(matchExpression);
}
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
if (routeDirection == RouteDirection.IncomingRequest)
{
if (values[parameterName] == UrlParameter.Optional) return true;
return _regEx != null && _regEx.Match(values[parameterName].ToString()).Success;
}
return false;
}
}
I needed to validate a few things with more than just a RegEx but was still getting an issue similar to this. My approach was to write a constraint wrapper for any custom route constraints I may already have:
public class OptionalRouteConstraint : IRouteConstraint
{
public IRouteConstraint Constraint { get; set; }
public bool Match
(
HttpContextBase httpContext,
Route route,
string parameterName,
RouteValueDictionary values,
RouteDirection routeDirection
)
{
var value = values[parameterName];
if (value != UrlParameter.Optional)
{
return Constraint.Match(httpContext, route, parameterName, values, routeDirection);
}
else
{
return true;
}
}
}
And then, in constraints under a route in RouteConfig.cs, it would look like this:
defaults: new {
//... other params
userid = UrlParameter.Optional
}
constraints: new
{
//... other constraints
userid = new OptionalRouteConstraint { Constraint = new UserIdConstraint() }
}

How to fall back from a custom MvcRouteHandler to next one in Route Table

I have a custom MvcRouteHandler which checks database if a Url exists and pairs it with some controller action and Id.
However if this route handler can not find a matching pair in database, I'd like MVC to keep try with other defined route handlers in route table.
How can I do that?
Update: (Example code added)
routes.MapRoute(
name: "FriendlyRoute",
url: "{FriendlyUrl}").RouteHandler = new FriendlyRouteHandler();
FriendlyRouteHandler is:
public class FriendlyRouteHandler : MvcRouteHandler
{
private TancanDbContext db = new MyDbContext();
protected override IHttpHandler GetHttpHandler(System.Web.Routing.RequestContext requestContext)
{
if (requestContext.RouteData.Values["FriendlyUrl"] != null)
{
string friendlyUrl = requestContext.RouteData.Values["FriendlyUrl"].ToString();
//Here, you would look up the URL Record in your database, then assign the values to Route Data
//using "where urlRecord.Url == friendlyUrl"
try
{
UrlRecord urlRecord = db.UrlRecords.Single(u => u.URL == friendlyUrl);
//Now, we can assign the values to routeData
if (urlRecord != null)
{
requestContext.RouteData.Values["controller"] = urlRecord.Controller;
requestContext.RouteData.Values["action"] = urlRecord.Action;
if(urlRecord.EntityId != null)
requestContext.RouteData.Values["id"] = urlRecord.ObjectId;
}
}
else
{
//Here, I want to redirect to next RouteHandler in route Table
requestContext.RouteData.Values["controller"] = friendlyUrl;
}
}
catch (Exception ex)
{
//throw;
//Here too, I want to redirect to next RouteHandler in route Table
requestContext.RouteData.Values["controller"] = friendlyUrl;
}
}
return base.GetHttpHandler(requestContext);
}
}
After adding this line it seems to work:
requestContext.RouteData.Values["controller"] = friendlyUrl;
Am I lucky or this is right way to do ? Do I need to use IRouteConstraint somewhere?
By the way, my influence was this article by Adam Riddick.
You want to use a custom Constraint, not a custom Handler for this.
routes.MapRoute(
name: "example",
url: "{friendly}",
defaults: new { controller = "FriendlyController", action = "Display" },
constraints: new { friendly = new FriendlyUrlConstraint() }
);
and then the constraint becomes:
public class FriendlyUrlConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
var friendlyUrl = values[parameterName];
// Your logic to return true/false here
}
}

Change URL Routing by overriding GenericPathRoute.cs in Plugin nopCommerce 3.3

I am trying to create a plugin which will override the TopicsDetails.cshtml page. I added a route like this:
routes.MapRoute("Nop.Plugin.Other.CustomTopic.ViewCustomTopic", "{SeName}",
new { controller = "CustomTopic", action = "TopicDetails", SeName = UrlParameter.Optional },
new[] { "Nop.Plugin.Other.CustomTopic.Controllers" });
This is getting all the {SeName} to my CustomTopicController .Even the products SeName.
If I add this instead of the older one:
routes.MapRoute("Nop.Plugin.Other.CustomTopic.ViewCustomTopic",
new { controller = "CustomTopic", action = "TopicDetails" },
new[] { "Nop.Plugin.Other.CustomTopic.Controllers" });
I get an error because the TopicDetails(int itemId) Action receives an integer which is not provided as we know that GenericPathRoutes.cs Provides that integer.
How can I override the Rules of GenericPathRoutes.cs to do it so that only the topic SeName would hit my Controller or is there other way to do that kind of work or is it even possible to do?
Recently i wrote an article which shows how to override localized route and i used the TopicDetails view as an example. There article is here.
Inshort, your route provider shall look like this;
public class RouteProvider : IRouteProvider
{
private const string NAMESPACES = "Nop.Plugin.Misc.Custom.Controllers";
private const string CONTROLLER = "MiscCustom";
public void RegisterRoutes(RouteCollection routes)
{
//Public Override
routes.MapGenericPathRoute("Plugin.Misc.Custom.GenericUrl",
"{generic_se_name}",
new { controller = "Common", action = "GenericUrl" },
new[] { NAMESPACES });
}
public int Priority
{
get { return Int32.Max; }
}
}
I am not sure whether i understand your question
can you try it by adding
id = UrlParameter.Optional
here any id parameter is optional it wont throw that error

How to Restrict Browser

I build a site in mvc3 , i want to restrict my site on firefox .
i mean to say that when anyone open my site on firefox it open correctly but when anyone opens it on chrome or IE it give an customze error . I am using c# with mvc3
You could write a global action filter which will test the User-Agent HTTP request header:
public class FireFoxOnlyAttribute : ActionFilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
var userAgent = filterContext.HttpContext.Request.Headers["User-Agent"];
if (!IsFirefox(userAgent))
{
filterContext.Result = new ViewResult
{
ViewName = "~/Views/Shared/Unauthorized.cshtml"
};
}
}
private bool IsFirefox(string userAgent)
{
// up to you to implement this method. You could use
// regular expressions or simple IndexOf method or whatever you like
throw new NotImplementedException();
}
}
and then register this filter in Global.asax:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new FireFoxOnlyAttribute());
}
You are looking for the user-agent of the user connected to your website, which may be retrieved via this call in your controller:
Request.UserAgent
Not that I agree with such a pattern, though.
This is a simple javascript function you may add to your code and perform the actions against.
function detect_browser() {
var agt=navigator.userAgent.toLowerCase();
if (agt.indexOf("firefox") != -1) return true;
else{
window.location="";//Here within quotes write the location of your error page.
}
}
On main page you may call the function on page load event. Though this practice is not recommended.
You could test the Request.UserAgent as part of a constraint on the route.
For example, you could define a route constraint routine as follows:
public class UserAgentConstraint : IRouteConstraint
{
private 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.Contains(requiredUserAgent);
}
}
Then add the following constraint to a route:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }, //Parameter defaults
new { customConstraint = new UserAgentConstraint("Firefox") } //Constraint
);

Categories

Resources