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

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()

Related

MVC IgnoreRoute /?_escaped_fragment_= to continue Reverse Proxy with IIS ARR

Technical Information
AngularJS single page app
Umbraco 7.3.0 website, extended to register routes via Umbraco.Core.ApplicationEventHandler in a separate class library
Scenario
I have an AngularJS single page app (SPA) that I'm trying to pre-render via an external PhantomJS service.
I want MVC's route handler to ignore the route /?_escaped_fragment_={fragment}, so the request can be handled directly by ASP.NET and thus passed on to IIS to proxy the request.
In Theory
Umbraco is built on ASP.NET MVC.
Routes are configurable via System.Web.Routing.RouteCollection class.
When extending Umbraco with custom routes, any routes configured via the System.Web.Routing.RouteTable will take precedence over Umbraco routes, thus never being handled by Umbraco**
Possible methods for my scenario
public void Ignore(string url) or
public void Ignore(string url, object constraints)
**I could be wrong. As far as I'm aware, custom routing takes precedence as it's done before the Umbraco routes are registered. However I'm unsure whether telling MVC to ignore a route would also prevent Umbraco from handling that route.
In Practise
I have attempted to ignore the routes with the following:
Attempt one:
routes.Ignore("?_escaped_fragment_={*pathInfo}");
This throws an error: The route URL cannot start with a '/' or '~' character and it cannot contain a '?' character.
Attempt two:
routes.Ignore("{*escapedfragment}", new { escapedfragment = #".*\?_escaped_fragment_=\/(.*)" });
This didn't result in an error, however Umbraco still picked up the request and handed me back my root page. Regex validation on Regexr.
Questions
Can MVC actually ignore a route based on its query string?
Is my knowledge of Umbraco's routing correct?
Is my regex correct?
Or am I missing something?
The built-in routing behavior doesn't take the query string into consideration. However, routing is extensible and can be based on query string if needed.
The simplest solution is to make a custom RouteBase subclass that can detect your query string, and then use the StopRoutingHandler to ensure the route doesn't function.
public class IgnoreQueryStringKeyRoute : RouteBase
{
private readonly string queryStringKey;
public IgnoreQueryStringKeyRoute(string queryStringKey)
{
if (string.IsNullOrWhiteSpace(queryStringKey))
throw new ArgumentNullException("queryStringKey is required");
this.queryStringKey = queryStringKey;
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
if (httpContext.Request.QueryString.AllKeys.Any(x => x == queryStringKey))
{
return new RouteData(this, new StopRoutingHandler());
}
// Tell MVC this route did not match
return null;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
// Tell MVC this route did not match
return null;
}
}
Usage
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// This route should go first
routes.Add(
name: "IgnoreQuery",
item: new IgnoreQueryStringKeyRoute("_escaped_fragment_"));
// Any other routes should be registered after...
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}

Attribute routing constraints with no parameters

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()}

Override all GET requests ASP.NET MVC

What is the best way to override all GET requests in .NET MVC and pipe them to a single controller's action?
I want only POST requests to go through the standard pipeline, e.g.
GET /Eating/Apples -> /GlobalProcessor/Index
POST /Eating/Apples -> /Eating/Apples
If .NET filters are your answer, then how would I accomplish that without using RedirectToAction(), as I need to maintain the URL structure. Meaning,
GET /Eating/Apples
Would be processed by /GlobalProcessor/Index but appear to the client as /Eating/Apples
And if you are wondering why -- it is for a dynamic AJAX processing backend that I'm implementing.
You could create a route that matches everything and then have an IRouteConstraint that matches when the request method is GET:
routes.MapRoute("Get",
"{*path}",
new {controller = "GlobalProcessor", action = "Index" },
new {isGet = new IsGetRequestConstraint()} );
With IsGetRequestConstraint being:
public class IsGetRequestConstraint: IRouteConstraint
{
public bool Match ( HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection )
{
return httpContext.Request.Method == "GET";
}
}
You could try adding something like this in your routes config
routes.MapRoute(
"GlobalProcessorThingy",
"{*url}",
new { controller = "GlobalProcessor", action = "Index" }
);
I tailored an answer from this SO question

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.

How to route a .aspx page in asp.net mvc 3 project?

I have a .aspx page in the following path:
Areas/Management/Views/Ticket/Report.aspx
I want to route that to the following path in my browser:
http://localhost/Reports/Tickets
How can i do that?
I try this:
routes.MapRoute(
"Tickets", // Route name
"Areas/Management/Views/Ticket/Report.aspx", // Original URL
new { controller = "Reports", action = "Tickets" } // New URL
);
But i got the 404 error.
What i'm doing wrong?
Obs: I put that before the Default route.
If you are trying to utilise web forms in a MVC project then I would move your .aspx out of the views folder, as it isn't really a view, so something like WebForms/Tickets/Report.aspx.
In web forms you map a route by calling the MapPageRoute method.
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapPageRoute("Tickets", "Reports/Tickets", "~/WebForms/Tickets/Report.aspx");
routes.MapRoute("Default", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = UrlParameter.Optional });
You'll need to put that before the default MVC route.
Solved! So, we need to add a route contraint to the webforms route to ensure that it only catches on incoming routes, not outgoing route generation.
Add the following class to your project (either in a new file or the bottom of global.asax.cs):
public class MyCustomConstaint : IRouteConstraint{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection){
return routeDirection == RouteDirection.IncomingRequest;
}
}
Then change the Tickets route to the following:
routes.MapPageRoute(
"Tickets",
"Reports/Tickets",
"~/WebForms/Reports/Tickets.aspx",
true, null,
new RouteValueDictionary { { "outgoing", new MyCustomConstaint() } }
);
you are doing it opposite. this maps your url Areas/Management/Views/Ticket/Report.aspx to { controller = "Reports", action = "Tickets" }
what u should do instead is
set the url as Reports/Tickets
EDIT:- you can create a routeHandler just for routing to this .aspx page.. like this.
public class ASPXRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return BuildManager.CreateInstanceFromVirtualPath("~/Areas/Management/Views/Ticket/Report.aspx", typeof(Page)) as Page;
}
}
then u can add ur route to the existing routes table using
Route customRoute = new Route("Reports/Ticket",null, new ASPXRouteHandler());
routes.Add(customRoute);
if you leave the default routing when you create the asp.net project
public class ReportsController : Controller
{
public ActionResult Ticket()
{
return View();
}
}
this should do the trick.
The routing in asp.net mvc means that you don't link directly to .aspx but to Actions (methods) that in turn return an appropriate view (.aspx)

Categories

Resources