Maintain the querystring in all pages in mvc - asp.net-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.

Related

MVC 5 culture and redirect query string loses value

this seems like any other question about culture or routing, but I can't figure out why this is happening.
When I try to access an [Authorize] element without being authenticated, I'm redirected to login page with a ReturnUrl param in query string. That's perfect. (boilerplate app)
Now my problem is: if I do the same with localisation within url (from routeConfig) ie: url: "{lang}/{controller}/{action}", I'm being redirected a first time with this parameter, but after been passed by the base controller that redirect me to a localized route. After that, ReturnUrl is null while login page (.cshtml) is loaded...
I've loooked into lots of articles, and tried numerous tricks/options and maybe on the way I did something that's breaks everything, so here are some code:
First RouteConfig :
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "LocalizedDefault",
url: "{lang}/{controller}/{action}",
defaults: new { lang = UrlParameter.Optional, controller = "Home", action = "Index" } ,
constraints: new { lang = "fr-CA|en-US" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
In BaseController:
protected override IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
{
string cultureName = RouteData.Values["lang"] as string;
// Attempt to read the culture cookie from Request
if (cultureName == null)
{
cultureName = Request.UserLanguages != null &&
Request.UserLanguages.Length > 0 ?
Request.UserLanguages[0] :
null; // obtain it from HTTP header AcceptLanguages
}
// Validate culture name
cultureName = CultureHelper.GetImplementedCulture(StartupMVC.SupportedLangs, cultureName);
if (RouteData.Values["lang"] as string != cultureName)
{
// Force a valid culture in the URL
RouteData.Values["lang"] = cultureName.ToLowerInvariant(); // lower case too
// Redirect user
//Response.SuppressFormsAuthenticationRedirect = false;
****HERE****
if I comment this line, I keep returnUrl but my route
is like {controller}/{action}/{id} => So default language
Response.RedirectToRoute(RouteData.Values);
otherwise route ok {lang}/{controller}/{action}/{id} but no ReturnUrl
}
SetCurrentCultureOnThread(cultureName);
return base.BeginExecuteCore(callback, state);
}
and a bit of Login.cshtml:
#using (Html.BeginForm("Login", "Account", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
...
}
I hope this is silly and there is some obvious solution you could point out to me.
Otherwise, does anyone understand this? and maybe help me to fix it?
PS : if I not being clear enough or you need some more info, please comment what you need! :)
Edit : from NightOwl888's comment, I'd like to add some more questions:
_I kwow there is at least 3 major ways of handling localization (locale in query string, in route, translating the url), but is there any documented or commonly used Best Practices regarding localization and culture?
_How does the .NET framework handles the redirect to login and how does it handles the query string? (since we can not register route with query string values)
Added QueryStrings to end of Routes:
if (cultureOnUrl.ToLower() != culture.ToLower())
{
try
{
var routes = new RouteValueDictionary(filterContext.RouteData.Values);
foreach (string key in filterContext.HttpContext.Request.QueryString.Keys)
{
routes[key] = filterContext.HttpContext.Request.QueryString[key];
}
if (!routes.ContainsKey("lang"))
routes.Add("lang", culture);
else
routes["lang"] = culture;
filterContext.HttpContext.Response.RedirectToRoute("LocalizedDefault",routes);
}
catch (Exception ex)
{
}
return;
}
Specifically tell MVC which route to use. So instead of this:
Response.RedirectToRoute(RouteData.Values);
do this:
Response.RedirectToRoute("LocalizedDefault", RouteData.Values);

MVC Custom Routing Subdomain

I'm trying to build a "Tenant" Subdomain route that attaches to a MVC Area. In this case I have an Area called "Tenant" which has two controllers; Public and Admin. My custom Route is used to grab the Subdomain if it matches then route them to the proper Controller-Action-Area.
The base of this project came from the following
http://www.matrichard.com/post/asp.net-mvc-5-routing-with-subdomain
The problem I'm having is in the custom Subdomain Route. When I hit the Public/Index Route, the routeData is returning null and I see the following error. Although if the route is /admin it returns the correct routeData.
Server Error in '/' Application.
The matched route does not include a 'controller' route value, which is required.
It also seems to be always matching using RouteDebugger tool, is this a clue to my problem?
Examples Routes:
controller=Public action=Index, area=Tenant
http://tenant1.mydomain.com:8080/
http://tenant1.mydomain.com:8080/logon
controller=Admin action=Index, area=Tenant
http://tenant1.mydomain.com:8080/admin
http://tenant1.mydomain.com:8080/admin/edit
--
SubdomainRouteP.cs
public class SubdomainRouteP : Route
{
public string Domain { get; set; }
public SubdomainRouteP(string domain, string url, RouteValueDictionary defaults): this(domain, url, defaults, new MvcRouteHandler())
{
}
public SubdomainRouteP(string domain, string url, object defaults): this(domain, url, new RouteValueDictionary(defaults), new MvcRouteHandler())
{
}
public SubdomainRouteP(string domain, string url, object defaults, IRouteHandler routeHandler): this(domain, url, new RouteValueDictionary(defaults), routeHandler)
{
}
public SubdomainRouteP(string domain, string url, RouteValueDictionary defaults, IRouteHandler routeHandler): base(url, defaults, routeHandler)
{
this.Domain = domain;
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
//
// routeData object returns null in some cases
//
var routeData = base.GetRouteData(httpContext);
var subdomain = httpContext.Request.Url.Host.Split('.').First();
string[] blacklist = { "www", "mydomain", "localhost" };
// This will ignore anything that is not a client tenant prefix
if (blacklist.Contains(subdomain))
{
return null; // Continue to the next route
}
// Why is this NULL?
if (routeData == null)
{
routeData = new RouteData(this, new MvcRouteHandler());
}
routeData.DataTokens["Area"] = "Tenant";
routeData.DataTokens["UseNamespaceFallback"] = bool.FalseString;
routeData.Values.Add("subdomain", subdomain);
// IMPORTANT: Always return null if there is no match.
// This tells .NET routing to check the next route that is registered.
return routeData;
}
}
RouteConfig.cs
routes.Add("Admin_Subdomain", new SubdomainRouteP(
"{client}.mydomain.com", //of course this should represent the real intent…like I said throwaway demo project in local IIS
"admin/{action}/{id}",
new { controller = "Admin", action = "Index", id = UrlParameter.Optional }));
routes.Add("Public_Subdomain", new SubdomainRouteP(
"{client}.mydomain.com", //of course this should represent the real intent…like I said throwaway demo project in local IIS
"{controller}/{action}/{id}",
new { controller = "Public", action = "Index", id = UrlParameter.Optional }));
// This is the MVC default Route
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
The Url below gives me the following results from RouteDebugger. During test 1 and 2 the route still matches /admin.
Failed Test 1: http://tenant.mydomain.com/
Failed Test 2: http://tenant.mydomain.com/logon
Successful 3: http://tenant.mydomain.com/admin
Matches Url Defaults
True admin/{action}/{id} controller = Admin, action = Index
True {controller}/{action}/{id} controller = Public, action = Index
The post that you linked to has a bug: When a constraint or the URL does not match, the base.GetRouteData method will return null. In this case, adding the subdomain name to the route dictionary will obviously throw an exception. There should be a null guard clause before that line.
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var routeData = base.GetRouteData(httpContext);
if (routeData != null)
{
routeData.Values.Add("client", httpContext.Request.Url.Host.Split('.').First());
}
return routeData;
}
As should be the case with your route. You need to ensure you return null in the case where the base class returns null (which indicates either the URL or a constraint did not match, and we need to skip processing this route).
Also, I am not sure if it makes any difference than adding the data directly to the DataTokens, but the MVC framework has an IRouteWithArea that can be implemented to configure the Area the route applies to.
public class SubdomainRouteP : Route, IRouteWithArea
{
public string Area { get; private set; }
public SubdomainRouteP(string area, string url, RouteValueDictionary defaults): this(area, url, defaults, new MvcRouteHandler())
{
}
public SubdomainRouteP(string area, string url, object defaults): this(area, url, new RouteValueDictionary(defaults), new MvcRouteHandler())
{
}
public SubdomainRouteP(string area, string url, object defaults, IRouteHandler routeHandler): this(area, url, new RouteValueDictionary(defaults), routeHandler)
{
}
public SubdomainRouteP(string area, string url, RouteValueDictionary defaults, IRouteHandler routeHandler): base(url, defaults, routeHandler)
{
this.Area = area;
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var routeData = base.GetRouteData(httpContext);
// This will ignore anything where the URL or a constraint doesn't match
// in the call to base.GetRouteData().
if (routeData != null)
{
var subdomain = httpContext.Request.Url.Host.Split('.').First();
string[] blacklist = { "www", "mydomain", "localhost" };
// This will ignore anything that is not a client tenant prefix
if (blacklist.Contains(subdomain))
{
return null; // Continue to the next route
}
routeData.DataTokens["UseNamespaceFallback"] = bool.FalseString;
routeData.Values.Add("subdomain", subdomain);
}
// IMPORTANT: Always return null if there is no match.
// This tells .NET routing to check the next route that is registered.
return routeData;
}
}
I can't figure out what you are trying to do with the domain parameter. The URL will most likely return something for domain. So, it seems like you should have a constraint in the first "{controller}/{action}/{id}" route or you will never have a case that will pass through to the default route. Or, you could use an explicit segment in the URL so you can differentiate it (the same way you did with your admin route).
routes.Add("Admin_Subdomain", new SubdomainRouteP(
"Tenant",
"admin/{action}/{id}",
new { controller = "Admin", action = "Index", id = UrlParameter.Optional }));
routes.Add("Public_Subdomain", new SubdomainRouteP(
"Tenant",
"public/{action}/{id}",
new { controller = "Public", action = "Index", id = UrlParameter.Optional }));
// This is the MVC default Route
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
Another option would be to add another constructor parameter to pass in an explicit list of valid domains to check against.

Adding Data annotation for a value that is got from query string

I need to add data annotation for the web api service in the {ID} mentioned in below url to accept a max length of 7, else I should throw a custom exception. I donot have any model class for the same to use maxlength attribute. Appreciate your help.
http://xyz.com/{ID}
What you could try to do is the following, assuming from the url you have in the question:
routes.MapRoute(
"Default", // Route name
"{id}", // URL with parameters
new { controller = "Home", action = "Index" }, // Parameter defaults,
new { id = #"\d{4}" } //Constraint
);
routes.MapRoute(
"DefaultError", // Route name
"{id}", // URL with parameters
new { controller = "Home", action = "ThrowError" }, // Parameter defaults,
new { id = #"\d{5,}" } //Constraint
);
and have a ThrowError(int id) method in your controller which throws the error you want or redirects to an errorpage.
Why not include a handler in your WebAPI method?
public SomeModel Get(int id)
{
if (id != null && id <= 7)
return something();
throw new HttpResponseException(HttpStatusCode.{BadRequest|Forbidden|SomethingElse});
}

redirect in Application_BeginRequest on 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 =(

Can i use a route constraint here?

If i have the following URL:
/someurl
And i have two domain names:
us.foo.com
au.foo.com
I want this to 200 (match):
us.foo.com/someurl
But this to 404 (not match):
au.foo.com/someurl
The route looks like this:
RouteTable.Routes.MapRoute(
"xyz route",
"someurl",
new { controller = "X", action = "Y" }
);
I'm guessing because there are no route values, i can't constraint the URL based on the host? Is that correct?
If so, how can i do this, other than the following (ugly) in the action:
if (cantViewThisUrlInThisDomain)
return new HttpNotFoundResult();
Anyone got any ideas?
I guess i'm kind of looking for a way to constraint a route via it's domain, not the route token, if that makes sense.
You could write a custom route:
public class MyRoute : Route
{
public MyRoute(string url, object defaults)
: base(url, new RouteValueDictionary(defaults), new MvcRouteHandler())
{ }
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var url = httpContext.Request.Url;
if (!IsAllowedUrl(url))
{
return null;
}
return base.GetRouteData(httpContext);
}
private bool IsAllowedUrl(Uri url)
{
// TODO: parse the url and decide whether you should allow
// it or not
throw new NotImplementedException();
}
}
and then register it in the RegisterRoutes method in Global.asax:
routes.Add(
"xyz route",
new MyRoute(
"{someurl}",
new { controller = "Home", action = "Index" }
)
);

Resources