Hy, in my Global.asax I've this rule:
// Home
routes.MapRoute("Home",
"{lang}/",
new { lang = "ita", controller = "Home", action = "Index" },
new { lang = new LanguageRouteConstraint() }
);
And my LanguageRouteConstraint class:
public class LanguageRouteConstraint : IRouteConstraint
{
#region Membri di IRouteConstraint
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
if ((routeDirection == RouteDirection.IncomingRequest) && (parameterName.ToLower(CultureInfo.InvariantCulture) == "lang"))
{
try
{
string lang = Convert.ToString(values[parameterName]);
// Language check on db
Language currLang = new Language().Get(lang);
if (currLang != null)
{
// Here I'd like to "save (in session|querystring|....)" the id
return true;
}
}
catch
{
return false;
}
}
return false;
}
#endregion
}
And my controller
public class HomeController : Controller
{
public ActionResult Index(string lang)
{
// I would get the language ID without interrogating the data base
}
}
In HomeController-->Index method I would get the language ID without interrogating the data base because I have already done in LanguageRouteConstraint.
I'm sorry for my poor English
Thanks in advance.
You could do the following:
In the Match method insert the language ID in the RouteValueDictionary: values["lang"] = theLanguageId;
Turn your action's signature into something like ActionResult Index(int lang)
You can access the current session via httpContext on your language constraint
Language currLang = new Language().Get(lang);
if (currLang != null)
{
httpContext.Session["Lang"] = id
return true;
}
then in your controller you could use a property
public int Language { get return int.Parse(Session["Lang"].ToString()); }
Related
I am working on CMS and need to do dynamic routing. I tried a lot of things but I decided to use routing like below.
[Route("{leftPath:values(announcements|duyurular)}/{announceUrl?}")]
public ActionResult Announcements(string leftPath, string announceUrl)
{
ViewBag.Url = announceUrl;
return View();
}
Problem:
[Route("{leftPath:values(announcements|duyurular)}/{announceUrl?}")]
bold part is dynamic. For example, if I use only English for user interface langage, I want to 'announcements' instead of bolded part above. If I use English, Turkish and Spanish I want to 'announcements|duyurular|anuncios'.
If I do like below, I get error because Attributes are accepts constant string variables:
[Route("{leftPath:values(" + GetRouteValues() + ")}/{announceUrl?}")]
public ActionResult Announcements(string leftPath, string announceUrl)
{
ViewBag.Url = announceUrl;
return View();
}
Is there any way to do this or any suggestions.
I am sorry about this but I found my solution, I didn't try solutions enough.
This code is fixed my problem. (in RouteConfig class -> RegisterRoutes() function.):
routes.MapRoute(
name: "announcementRoute",
url: "{leftPath}/{announcementUrl}",
defaults: new { controller = "Home", action = "Announces", announcementUrl = UrlParameter.Optional },
constraints: new { leftPath = new ValuesConstraint(Tools.GetRouteValues("announcements")) }
);
constraints: new {
leftPath = new ValuesConstraint (Tools.GetRouteValues ( "announcements" ))
}
Bolded function returns string what i want: 'announcements' or 'announcements|duyurular|anuncios' based on language(s).
For the curious:
public class ValuesConstraint : IRouteConstraint
{
private readonly string[] validOptions;
public ValuesConstraint(string options)
{
validOptions = options.Split('|');
}
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
object value;
if (values.TryGetValue(parameterName, out value) && value != null)
{
return validOptions.Contains(value.ToString(), StringComparer.OrdinalIgnoreCase);
}
return false;
}
}
more: MSDN Resource
I created this Custom Route Class in ASP.NET MVC:
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);
}
}
And in Global.asax.cs:
public static void RegisterRoutes(RouteCollection routes) {
routes.MapRoute("myRoute2", "{controller}/{action}/{Id}",
new { controller = "home", action = "index", Id = UrlParameter.Optional }, new {
customConstriant=new UserAgentConstraint("IE")
}
}
The above code works prefectly, but when the user uses IE, I get a 404 Error. I want to redirect to a custom Page. I dont want to use a Custom Error in the Web.Config file because my error is only for use in IE. How can one do this?
Thanks in your advice.
a better way of doing this is using ActionFilter.
public class BrowserFilterAttribute : ActionFilterAttribute
{
public string [] _browserNames { get; set; }
public AssetIdFilterAttribute(params string [] browserNames)
{
_browserNames= browserNames;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//get browser name from somewhere
string currentBrowser = filterContext.HttpContext.Request.Browser.Browser;
if(_browserNames.Contains(currentBrowser))
filterContext.Result = new RedirectResult("your URL");
}
}
you can apply it in Controller level like this :
[BrowserFilter("IE","Opera","SomeOtherBrowser")]
public class BrowserAwareController() : Controller
{
}
hope this help.good luck.
I'm trying to setup a custom route in MVC to take a URL from another system in the following format:
../ABC/ABC01?Key=123&Group=456
The 01 after the second ABC is a step number this will change and the Key and Group parameters will change. I need to route this to one action in a controller with the step number key and group as paramters. I've attempted the following code however it throws an exception:
Code:
routes.MapRoute(
"OpenCase",
"ABC/ABC{stepNo}?Key={key}&Group={group}",
new {controller = "ABC1", action = "OpenCase"}
);
Exception:
`The route URL cannot start with a '/' or '~' character and it cannot contain a '?' character.`
You cannot include the query string in the route. Try with a route like this:
routes.MapRoute("OpenCase", "ABC/ABC{stepNo}",
new { controller = "ABC1", action = "OpenCase" });
Then, on your controller add a method like this:
public class ABC1 : Controller
{
public ActionResult OpenCase(string stepno, string key, string group)
{
// do stuff here
return View();
}
}
ASP.NET MVC will automatically map the query string parameters to the parameters in the method in the controller.
When defining routes, you cannot use a / at the beginning of the route:
routes.MapRoute("OpenCase",
"/ABC/{controller}/{key}/{group}", // Bad. Uses a / at the beginning
new { controller = "", action = "OpenCase" },
new { key = #"\d+", group = #"\d+" }
);
routes.MapRoute("OpenCase",
"ABC/{controller}/{key}/{group}", // Good. No / at the beginning
new { controller = "", action = "OpenCase" },
new { key = #"\d+", group = #"\d+" }
);
Try this:
routes.MapRoute("OpenCase",
"ABC/{controller}/{key}/{group}",
new { controller = "", action = "OpenCase" },
new { key = #"\d+", group = #"\d+" }
);
Then your action should look as follows:
public ActionResult OpenCase(int key, int group)
{
//do stuff here
}
It looks like you're putting together the stepNo and the "ABC" to get a controller that is ABC1. That's why I replaced that section of the URL with {controller}.
Since you also have a route that defines the 'key', and 'group', the above route will also catch your initial URL and send it to the action.
There is no reason to use routing based in querystring in new ASP.NET MVC project. It can be useful for old project that has been converted from classic ASP.NET project and you want to preserve URLs.
One solution can be attribute routing.
Another solution can be in writting custom routing by deriving from RouteBase:
public class MyOldClassicAspRouting : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
if (httpContext.Request.Headers == null) //for unittest
return null;
var queryString = httpContext.Request.QueryString;
//add your logic here based on querystring
RouteData routeData = new RouteData(this, new MvcRouteHandler());
routeData.Values.Add("controller", "...");
routeData.Values.Add("action", "...");
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
//Implement your formating Url formating here
return null;
}
}
And register your custom routing class
public static void RegisterRoutes(RouteCollection routes)
{
...
routes.Add(new MyOldClassicAspRouting ());
}
The query string arguments generally are specific of that controller and of that specific application logic.
So it will better if this isn't written in route rules, that are general.
You can embed detection of query string on action argument in the following way.
I think that is better to have one Controller for handling StepNo.
public class ABC : Controller
{
public ActionResult OpenCase(OpenCaseArguments arg)
{
// do stuff here
// use arg.StepNo, arg.Key and arg.Group as You need
return View();
}
}
public class OpenCaseArguments
{
private string _id;
public string id
{
get
{
return _id;
}
set
{
_id = value; // keep original value;
ParseQueryString(value);
}
}
public string StepNo { get; set; }
public string Key { get; set; }
public string Group { get; set; }
private void ParseQueryString(string qs)
{
var n = qs.IndexOf('?');
if (n < 0) return;
StepNo = qs.Substring(0, n); // extract the first part eg. {stepNo}
NameValueCollection parms = HttpUtility.ParseQueryString(qs.Substring(n + 1));
if (parms.Get("Key") != null) Key = parms.Get("Key");
if (parms.Get("Group") != null) Group = parms.Get("Group");
}
}
ModelBinder assign {id} value to the id field of OpenCaseArguments. The set method handle querystring split logic.
And keep routing this way. Note routing get your querystring in id argument.
routes.MapRoute(
"OpenCase",
"ABC/OpenCase/{id}",
new {controller = "ABC", action = "OpenCase"}
);
I have used this method for getting multiple fields key value on controller action.
I want to make the following:
when the url doesn't have an instID, i want to redirect to the "Instelling" action
in this controller, every method needs the instID.
[RequiredParameter(parameterName="instID", controllerToSend="Instelling")]
public ActionResult Index(int? instID) {
//if (!instID.HasValue) {
// return RedirectToAction("Index", "Instelling");
//}
var facts = _db.Instellingens.First(q => q.Inst_ID == instID).FacturatieGegevens;
return View(facts);
}
so this is in the controller.
the actionfilter:
namespace MVC2_NASTEST.Controllers {
public class RequiredParameterAttribute : ActionFilterAttribute {
public string parameterName { get; set; }
public string actionToSend { get; set; }
public string controllerToSend { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext) {
if (parameterName != string.Empty) {
if (filterContext.ActionParameters.ContainsKey(parameterName) && filterContext.ActionParameters[parameterName] != null) {
string s = "test";
//all is good
} else {
//de parameter ontbreekt. kijk of de controller en de action geset zijn.
if (actionToSend == string.Empty)
actionToSend = "Index";
if (controllerToSend == string.Empty) {
controllerToSend = filterContext.Controller.ToString();
controllerToSend = controllerToSend.Substring(controllerToSend.LastIndexOf(".") + 1);
controllerToSend = controllerToSend.Substring(0, controllerToSend.LastIndexOf("Controller"));
}
UrlHelper helper = new UrlHelper(filterContext.RequestContext);
string url = helper.Action(actionToSend, controllerToSend);
HttpContext.Current.Response.Redirect(url);
//filterContext.HttpContext.Response.Redirect(url, true);
}
}
base.OnActionExecuting(filterContext);
}
public override void OnActionExecuted(ActionExecutedContext filterContext) {
base.OnActionExecuted(filterContext);
}
}
}
the thing is: it does work, however, the action itself first gets executed, THEN the redirect happens. this is not what I wanted.
Perhaps i shouldnt use actionfilters but just add a route?
in this case, how would i redirect the route to another controller if the instID is missing?
Rather than creating an action filter (which runs just before the return of the action method), you could consider changing to an Authorization Filter which would allow you to redirect to an alternative controller & action
Something like this (pseudo code):
public class RequiredParameterAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
// read instID from QueryString
// if instId is null, return false, otherwise true
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.result = new RedirectToRouteResult( new { controller = "MyController" , action = "MyAction" } )
}
}
This was the first result on a question I asked on Google, so I'd like to propose a different answer. Instead of doing the redirect from the action assign a redirect to the filterContext.Result like this:
filterContext.Result = new RedirectResult(url);
If the result property of the filterContext is not null then the underlying action will not be executed. Because you're performing a redirect outside the context of the call you will still execute the action method.
Anyone understand why the following doesn't work?
What I want to do is copy current route data plus whatever I add via an anonymous object into new routedata when forming new links on the view.
For example if I have the parameter "page" as a non route path (i.e. so it overflows the route path and its injected into the method parameter if a querystring is present) e.g.
public ActionResult ChangePage(int? page) { }
and I want the View to know the updated page when building links using helpers. I thought the best way to do this is with the following:
public ActionResult ChangePage(int? page)
{
if(page.HasValue)
RouteData.Values.Add("Page", page);
ViewData.Model = GetData(page.HasValue ? page.Value : 1);
}
Then in the view markup I can render my next, preview, sort, showmore (any links relevant) with this overload:
public static class Helpers
{
public static string ActionLinkFromRouteData(this HtmlHelper helper, string linkText, string actionName, object values)
{
RouteValueDictionary routeValueDictionary = new RouteValueDictionary();
foreach(var routeValue in helper.ViewContext.RouteData.Values)
{
if(routeValue.Key != "controller" && routeValue.Key != "action")
{
routeValueDictionary[routeValue.Key] = routeValue;
}
}
foreach(var prop in GetProperties(values))
{
routeValueDictionary[prop.Name] = prop.Value;
}
return helper.ActionLink(linkText, actionName, routeValueDictionary;
}
private static IEnumerable<PropertyValue> GetProperties(object o)
{
if (o != null) {
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(o);
foreach (PropertyDescriptor prop in props) {
object val = prop.GetValue(o);
if (val != null) {
yield return new PropertyValue { Name = prop.Name, Value = val };
}
}
}
}
private sealed class PropertyValue
{
public string Name { get; set; }
public object Value { get; set; }
}
}
I have posted the code only to illustrate the point. This doesn't work and doesn't feel right... Pointers?
Pass the page info into ViewData?
PagedResultsInfo (or something) sounds like a class you could write too... we do.