redirect in Application_BeginRequest on MVC - asp.net-mvc

I've a multi-language site. I'm trying to change to english (including the URL) when the site doesn't have the requested language. I want to do that by redirecting to the same page but changing the url's language. So I added a resource with the key "_Language" which get the language code if it is active. For example if the resource file doesn't exists or it does but it isn't ready, it will fallback into some other language. In the global.asax I have this code:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"DefaultLang", // Route name
"{language}/{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }, // Parameter defaults
new { language = "^[a-z]{2}$" } // Get language
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Cuenta", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
public static string Language
{
get
{
var currentContext = new HttpContextWrapper(HttpContext.Current);
var routeData = RouteTable.Routes.GetRouteData(currentContext);
if (routeData != null)
{
var lang = (string)routeData.Values["language"];
if (lang != null && lang.Length == 2)
return lang.ToLower();
}
return "es";
}
}
protected void Application_BeginRequest(object sender, EventArgs e)
{
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(MvcApplication.Language);
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(MvcApplication.Language);
if (Texts._Language != MvcApplication.Language) // Detect fallback.
{
Response.Redirect(Request.Url.AbsoluteUri.Replace("/" + MvcApplication.Language + "/", "/en/"));
Response.End();
return;
}
}
My problem is that for some reason each time the user is redirected by the BeginRequest the response its a 404. I've compared the url with the one when the language is set (from the beginning) in english and they are the same! Why is this happening?

How stupid! I was actually doing this with a POST request =(

Related

How to enforce Url route values in MVC Pipeline in a multi tenant app

I have the following default route
url: "{tenant}/{controller}/{action}/{id}",
defaults: new { tenant="not_set", controller = "Home", action = "Index", id = UrlParameter.Optional },
constraints: new { tenant= new TenantConstraint()}
I have user1 who should only have access to tenant1/{Controller}/{action}.
If the user tried to hack the url to tenantNoAccess/{Controller}/{action}, i want him to be automatically redirected to tenant1/{Controller}/{action}.
I can do this in an ActionFilter, but I don't want the request to go that far ahead in the mvc pipeline.
I want to do something before the request tries to MapRequestHandler().
I was thinking maybe in Application_PostAuthenticateRequest()?...
I have the following in the event
protected void Application_AuthenticateRequest(object o, EventArgs args)
{
var context = HttpContext.Current;
var httpContext = new HttpContextWrapper(HttpContext.Current);
var user = context.User;
var request = context.Request;
var requestUrl = request.Url;
var reqContext = request.RequestContext;
if (user.Identity.IsAuthenticated)
{
var usr = (System.Security.Claims.ClaimsPrincipal)user;
string correctTenant = GetCorrectTenantForUser(usr);
if (!reqContext.RouteData.Values["tenant"].Equals(correctTenant))
{
//stuck here. How to change the URL without referencing a 3rd party Url rewrite module?
}
else
{
//tenant matches
return;
}
}
else
{
//
}
}
In RouteConfig you could add a routehandler.
routes.MapRoute(name: "Default",url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional} ).RouteHandler = new LandingPageRouteHandler();
In the routehandler you can do the checks you need and set the appropriate controller, action, .....
public class LandingPageRouteHandler : MvcRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext Context)
{
if ( <perform checks)
{
Context.RouteData.Values["controller"] = <whateveryouwant>;
Context.RouteData.Values["action"] = <whateveryouwant>;
Context.RouteData.Values["id"] = <whateveryouwant>;
}
return base.GetHttpHandler(Context);
}
}

How to set cookie value as default value for MapRoute parameter?

Introduction:
I develop multilingual web application. Admin can create new languages (this information is stored in database). So languages are not hardcoded somewhere in code. The user's preferred language is stored in browser cookie.
Now i want to configure url routes from this:
www.host.com/home/about
to this:
www.host.com/{lang}/home/about
RouteConfig.cs looks like:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.LowercaseUrls = true;
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
foreach (Route r in routes)
{
r.Url = "{lang}/" + r.Url;
if (r.Defaults == null)
r.Defaults = new RouteValueDictionary();
r.Defaults.Add("lang", ServiceLocalization.GetLanguageFromBrowserCookie());
}
}
}
Parameter {lang} is added to every request url. I need to set default value from cookie. Service method GetLanguageFromBrowserCookie() uses HttpContext.Current.Request.Cookies object to access cookies sent by the client.
But HttpContext.Current.Request object is not accessible at this stage of request-handling pipeline. And i get this error: "Request is not available in this context"
Is there a way to fetch MapRoute parameter with cookie value?
Ok. I figured out how to do this. We need to create custom RouteHandler to process request url with extra logic.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.LowercaseUrls = true;
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
).RouteHandler = new CustomRouteHandler();
}
}
public class CustomRouteHandler : MvcRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
var route = (Route)requestContext.RouteData.Route;
if (!route.Url.Contains("{lang}"))
route.Url = "{lang}/" + route.Url;
if (route.Defaults == null)
{
route.Defaults = new RouteValueDictionary();
route.Defaults.Add("lang", ServiceLocalization.GetLanguageFromBrowserCookie().CodeName);
}
else
{
route.Defaults["lang"] = ServiceLocalization.GetLanguageFromBrowserCookie().CodeName;
}
return base.GetHttpHandler(requestContext);
}
}
I create CustomRouteHandler and pass it to Default route. In GetHttpHandler method we can access current HttpRequest and get any data from request.

MVC custom URL routing

I have a route configured like
routes.MapRoute(
name: "Default",
url: "TEST/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
It works fine to redirect to respective controller and actions.
I want to add another redirection on TEST so that if somebody uses www.mysite.com/TEST, it should redirect www.mysite.com/Test/Home instead of giving 403- Forbidden: Access is denied error.
I'm trying like this but could not achieve it.
routes.MapRoute(
name: "AnotherDefault",
url: "TEST",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Basically, what I'm trying to do is to redirect from www.mysite.com or www.mysite.com/TEST to www.mysite.com/TEST/Home
To add to the confusion, I also had a physical folder TEST in my application root. Just wondering if keeping another web.config in there would solve? I tried but of no luck
Please advise what i'm missing here. Thanks
After some experiment I have found that the physical folder TEST is causing redirection rule to fail. I changed my route to TEST1 in URL instead of TEST, it worked. But, I can't rename TEST folder. Please advise
Please set the property RouteExistingFiles to true above the Route configurations
public static void RegisterRoutes(RouteCollection routes)
{
routes.RouteExistingFiles = true;
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "TEST/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
This should allow you to keep the name of folder and also the route name to be "TEST". Let me know how it works out for you
Keep using the first route:
routes.MapRoute(
name: "Default",
url: "TEST/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
and add this in your Web.config:
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"></modules>
<handlers>
<remove name="UrlRoutingHandler"/>
</handlers>
</system.webServer>
We Can manage url routing by validate from database.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// here i pass my parameter like wwww.abc.com/country-state-city
routes.MapLocalizedRoute("SeoFriendlyUrl",
"{SeoFriendlyName}",
new { controller = "Company", action = "Index" },
new[] { "MigrationTest.Controllers" });
// it is default
routes.MapRoute( name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
public static class LocalizedRouteExtensionMethod
{
public static Route MapLocalizedRoute(this RouteCollection routes, string name, string url, object defaults, string[] namespaces)
{
return MapLocalizedRoute(routes, name, url, defaults, null /* constraints */, namespaces);
}
public static Route MapLocalizedRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces)
{
if (routes == null)
{
throw new ArgumentNullException("routes");
}
if (url == null)
{
throw new ArgumentNullException("url");
}
var route = new clsRouteData(url, new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(defaults),
Constraints = new RouteValueDictionary(constraints),
DataTokens = new RouteValueDictionary()
};
if ((namespaces != null) && (namespaces.Length > 0))
{
route.DataTokens["Namespaces"] = namespaces;
}
routes.Add(name, route);
return route;
}
}
public class clsRouteData : Route
{
public clsRouteData(string url, IRouteHandler routeHandler)
: base(url, routeHandler)
{
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
RouteData data = base.GetRouteData(httpContext);
if (data != null)
{
var SeoFriendliyName = data.Values["SeoFriendlyName"] as string;
if (SeoFriendliyName=="india-raj-jaipur")
{
data.Values["controller"] = "City";
data.Values["action"] = "Index";
// Can be send parameter
//data.Values["Id"] = Resutls.Id;
}
}
else
{
data.Values["controller"] = "Norecord";
data.Values["action"] = "Index";
// Can be send parameter
//data.Values["Id"] = Resutls.Id;
}
return data;
}
}

Paging and routing in ASP.Net MVC

I am following Martijn Boland's 'Paging with ASP.NET MVC'. And while helpful it has raised a couple of issues I don't understand.
Martijn says:
Internally, the pager uses
RouteTable.Routes.GetVirtualPath() to
render the url’s so the page url’s can
be configured via routing to create
nice looking url’s like for example
‘/Categories/Shoes/Page/1′ instead of
‘/Paging/ViewByCategory?name=Shoes&page=1′.
This is the is what he is talking about:
private string GeneratePageLink(string linkText, int pageNumber)
{
var pageLinkValueDictionary = new RouteValueDictionary(this.linkWithoutPageValuesDictionary);
pageLinkValueDictionary.Add("page", pageNumber);
//var virtualPathData = this.viewContext.RouteData.Route.GetVirtualPath(this.viewContext, pageLinkValueDictionary);
var virtualPathData = RouteTable.Routes.GetVirtualPath(this.viewContext.RequestContext, pageLinkValueDictionary);
if (virtualPathData != null)
{
string linkFormat = "{1}";
return String.Format(linkFormat, virtualPathData.VirtualPath, linkText);
}
else
{
return null;
}
}
How does this work? When I use it virtualPathData.VirtualPath just brings back a url representing the first route in my routing table with a 'page' param on the end rather then a url representing the current context.
Also what would the routing look like to change this ‘/Paging/ViewByCategory?name=Shoes&page=1′ to this ‘/Categories/Shoes/Page/1′ ?
I assume You have Paging controller and this controller has ViewByCategory action.
ViewByCategory looks like:
public ActionResult ViewByCategory(string categoryName, int? page)
{
....
}
Routing will look like
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"RouteByCategory",
"Categories/{categoryName}/Page/{page}",
new { controller = "Paging", action = "ViewByCategory" }
);
routes.MapRoute(
"RouteByCategoryFirstPage",
"Categories/{categoryName}",
new { controller = "Paging", action = "ViewByCategory", page = 1 }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
}
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
}
GeneratePageLink will return link in ‘/Categories/Shoes/Page/1′ format, because it is first matching route pattern in routing table.

Maintain the querystring in all pages in mvc

I need to maintain the querystring in all pages in my asp.net mvc(C#) application.
For ex.:
I will call a page www.example.com?Preview=True. The querystring should be maintained whatever the page i click in www.example.com. i.e. When i click About us page in www.example.com, the url should be www.example.com/AboutUs?Preview=True
How can i achieve this? Whats the best place to do this common operation.?
Maybe you need a custom route?:
public class PreviewRoute : System.Web.Routing.Route
{
...
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
var preview = System.Web.HttpContext.Current.Session["Preview"];
if (!values.ContainsKey("Preview"))
values.Add("Preview", preview);
var path = base.GetVirtualPath(requestContext, values);
return path;
}
}
}
Set Session["Preview"] at any time and you will get all your urls with ?Preview=True:
System.Web.HttpContext.Current.Session.Add("Preview", true);
UPDATED:
Use this route in the Global.asax.cs:
routes.Add("Default",
new PreviewRoute("{controller}/{action}/{id}", new MvcRouteHandler()) {
Defaults = new RouteValueDictionary(
new { controller = "Home", action = "Index", id = "" }
)
}
);
instead of:
routes.MapRouteLowercase(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
Also you can try this extension:
public static class CustomRouteExtensions
{
public static void MapPreviewRoute(this RouteCollection routes, string name, string url, object defaults) {
routes.MapPreviewRoute(name, url, defaults, null);
}
public static void MapPreviewRoute(this RouteCollection routes, string name, string url, object defaults, object constraints) {
if (routes == null) {
throw new ArgumentNullException("routes");
}
if (url == null) {
throw new ArgumentNullException("url");
}
var route = new PreviewRoute(url, new MvcRouteHandler()) {
Defaults = new RouteValueDictionary(defaults),
Constraints = new RouteValueDictionary(constraints)
};
if (String.IsNullOrEmpty(name)) {
routes.Add(route);
}
else {
routes.Add(name, route);
}
}
}
In Global.asax.cs:
routes.MapPreviewRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
You could create a view Helper that appends the existing query string onto any links you create with your new helper.
This may help
You might be better storing this information in session.
An excellent direction from #eu-ge-ne.
I have used the idea of custom route from #eu-ge-ne to add the route value to every url and used a basecontroller to handle the Preview key in session.
if ((requestContext.HttpContext.Request.QueryString != null &&
requestContext.HttpContext.Request.QueryString["Preview"] != null &&
requestContext.HttpContext.Request.QueryString["Preview"].ToString() =="True") ||
(requestContext.HttpContext.Request.UrlReferrer != null &&
requestContext.HttpContext.Request.UrlReferrer.ToString().Contains("Preview=True")))
{
//Add the preview key to session
}
else
{
//Remove the preview key to session
}
I have used the above code in the Initialize method of the base controller. This way the preview key will in session if the querystring has Preview, else it removes from the session.
Thanks to #eu-ge-ne once again.

Resources