I want to create a Mvc route that match every string, but all those ones that contains some values, for instance, I have this route:
context.MapRoute(
"Users_Bootstrap",
"{ulrPrefix}/{*catchall}",
new { controller = "Start", action = "Index" },
namespaces: new[] { "Fanapo.Web.Areas.Users.Controllers" },
constraints: new { }
);
I want this route doesn't match any string that urlPrefix parameter be Account or Admin or ...
Something like this: ulrPrefix NOT IN [Account, Admin, ...]
I think this should be solved using regular expresions, hope IRouteConstraint be the last option. Thanks.
Solved using route constrains, based on this answer
public class NotInRouteConstraint : IRouteConstraint
{
private readonly string _notInString;
public NotInRouteConstraint(string notInString)
{
_notInString = notInString;
}
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values,
RouteDirection routeDirection)
{
if (string.IsNullOrEmpty(_notInString))
return true;
var notList = _notInString.Split('|').Select(s => s.Trim().ToLower()).ToList();
var value = values[parameterName] as string;
if (!string.IsNullOrEmpty(value))
{
return !notList.Contains(value.ToLower());
}
return true;
}
}
The usage code:
//this is in another area
context.MapRoute(
"Users_Bootstrap",
"{ulrPrefix}/{*catchall}",
new { controller = "Start", action = "Index" },
constraints: new { ulrPrefix = new NotInRouteConstraint(RouteConfig.ReservedUrlPrefix.JoinStrings("|")) }
);
Hope there is another nicer solution. Thanks.
EDIT
A bit more faster:
public class NotInRouteConstraint : IRouteConstraint
{
private readonly IEnumerable<string> _notInString;
public NotInRouteConstraint(IEnumerable<string> notInString)
{
_notInString = notInString;
}
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values,
RouteDirection routeDirection)
{
if (_notInString == null)
return true;
var value = values[parameterName] as string;
if (!string.IsNullOrEmpty(value))
{
return !_notInString.Contains(value.ToLower());
}
return true;
}
}
AND
context.MapRoute(
"Users_Bootstrap",
"{ulrPrefix}/{*catchall}",
new { controller = "Start", action = "Index" },
constraints: new { ulrPrefix = new NotInRouteConstraint(RouteConfig.ReservedUrlPrefix.Select(s=>s.Trim().ToLower())) }
);
Related
How can I set a custom contract resolver in web api configuration? My code is relatively new and has no custom contract resolver till now.
I have added no other customization besides routing.
I tried in three different ways and none worked:
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
//attempt 1
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CustomContractResolver();
//attempt 2
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CustomContractResolver();
//attempt 3
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
ContractResolver = new CustomContractResolver()
};
}
The custom contract resolver code, breakpoint never reaches here when I'm debugging:
public class CustomContractResolver : CamelCasePropertyNamesContractResolver
{
protected override string ResolvePropertyName(string propertyName)
{
var regex = new Regex(#"([_])(\w)");
if (regex.IsMatch(propertyName))
{
var result = regex.Replace(propertyName.ToLower(), (match) => { return match.Groups[2].Value.ToUpper(); });
return result;
}
else
return base.ResolvePropertyName(propertyName);
}
}
Is there something that is missing?
Edit 1:
I'm using ASP.NET WebApi 5.2.1 AND MVC 5.2.7, JSON.NET (Newtonsoft.Json) v13.0.1 (and already tried the old v12)
My Global Asax is very simple as well:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register); //<- web api configuration
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes); //<- mvc configuration
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
The MVC RouteConfig class:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("{resource}.ashx/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
Edit 2
Here is some test web api controllers:
using System.Web.Http;
namespace Kronos.Web.Geolocalizacao.Controllers.Api
{
public class TestController : ApiController
{
[HttpGet]
public TestModel Obtain()
{
return new TestModel { CODE_IDENTIFICATION = 1, DEFAULT_DESCRIPTION = "TEST DAT THING" };
}
}
public class TestModel
{
public decimal CODE_IDENTIFICATION { get; set; }
public string DEFAULT_DESCRIPTION { get; set; }
}
}
Used the Tabbed Postman chrome addon to test
Postman tests
Your problem has nothing to do with how you are registering your global settings -- setting config.Formatters.JsonFormatter.SerializerSettings.ContractResolver is correct as per this question. Your problem is that Json.NET does not call ResolvePropertyName() when the contract resolver also has a NamingStrategy -- and your base class CamelCasePropertyNamesContractResolver does indeed have a naming strategy.
This can be verified by checking the current Json.NET reference source for DefaultContractResolver.SetPropertySettingsFromAttributes():
if (namingStrategy != null)
{
property.PropertyName = namingStrategy.GetPropertyName(mappedName, hasSpecifiedName);
}
else
{
property.PropertyName = ResolvePropertyName(mappedName);
}
Broken demo fiddle #1 here.
If I simply modify your CustomContractResolver to inherit from DefaultContractResolver (which has a null NamingStrategy by default), then it works:
public class CustomContractResolver : DefaultContractResolver
{
readonly NamingStrategy baseNamingStrategy = new CamelCaseNamingStrategy();
protected override string ResolvePropertyName(string propertyName)
{
var regex = new Regex(#"([_])(\w)");
if (regex.IsMatch(propertyName))
{
var result = regex.Replace(propertyName.ToLower(), (match) => { return match.Groups[2].Value.ToUpper(); });
return result;
}
else
return baseNamingStrategy.GetPropertyName(propertyName, false);
}
}
Fixed demo fiddle #2 here.
However, a cleaner solution would be to replace your custom contract resolver with a custom naming strategy:
public class CustomNamingStrategy : CamelCaseNamingStrategy
{
public CustomNamingStrategy() : base() { }
public CustomNamingStrategy(bool processDictionaryKeys, bool overrideSpecifiedNames) : base(processDictionaryKeys, overrideSpecifiedNames) { }
public CustomNamingStrategy(bool processDictionaryKeys, bool overrideSpecifiedNames, bool processExtensionDataNames) : base(processDictionaryKeys, overrideSpecifiedNames, processExtensionDataNames) { }
readonly Regex regex = new Regex(#"([_])(\w)");
protected override string ResolvePropertyName(string name)
{
if (regex.IsMatch(name))
{
var result = regex.Replace(name.ToLower(), (match) => { return match.Groups[2].Value.ToUpper(); });
return result;
}
return base.ResolvePropertyName(name);
}
}
And then configure it in settings like so:
settings.ContractResolver = new DefaultContractResolver
{
// Set the constructor parameters as per your preference. These values are consistent with CamelCasePropertyNamesContractResolver
NamingStrategy = new CustomNamingStrategy(processDictionaryKeys: true, overrideSpecifiedNames: true),
};
Demo fiddle #3 here.
I'm trying to build two routes only to action and to controller with id, keeping the default.
I have to access:
www.mysite.com/MyController/MyAction/{OptionalId}
www.mysite.com/MyController/{OptionalId}
www.mysite.com/MyActionFromHomeController
I was able to create routes to work with first and the third point, but not for the second. Current code:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "OnlyActionToHomeController",
url: "{action}",
defaults: new { controller = "Home" },
constraints: new { noConflictingControllerExists = new NoConflictingControllerExists() }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
public class NoConflictingControllerExists : IRouteConstraint
{
private static readonly Dictionary<string, bool> _cache = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
var path = httpContext.Request.Path;
if (path == "/" || String.IsNullOrEmpty(path))
return false;
if (_cache.ContainsKey(path))
return _cache[path];
IController ctrl;
try
{
var ctrlFactory = ControllerBuilder.Current.GetControllerFactory();
ctrl = ctrlFactory.CreateController(httpContext.Request.RequestContext, values["action"] as string);
}
catch
{
_cache.Add(path, true);
return true;
}
var res = ctrl == null;
_cache.Add(path, res);
return res;
}
}
I did!
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "OnlyController",
url: "{controller}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
constraints: new { id = #"\d+" } // define the id parameter needs to be integer
);
routes.MapRoute(
name: "OnlyActionToHomeController",
url: "{action}",
defaults: new { controller = "Home" },
constraints: new { noConflictingControllerExists = new NoConflictingControllerExists() }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
public class NoConflictingControllerExists : IRouteConstraint
{
private static readonly Dictionary<string, bool> _cache = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
var path = httpContext.Request.Path;
if (path == "/" || String.IsNullOrEmpty(path))
return false;
if (_cache.ContainsKey(path))
return _cache[path];
IController ctrl;
try
{
var ctrlFactory = ControllerBuilder.Current.GetControllerFactory();
ctrl = ctrlFactory.CreateController(httpContext.Request.RequestContext, values["action"] as string);
}
catch
{
_cache.Add(path, true);
return true;
}
var res = ctrl == null;
_cache.Add(path, res);
return res;
}
}
I would like to be able to create a succinct language-specific default URL for my website so that when someone browses to:
somesite.com
They get redirected to a language-culture page such as:
somesite.com/en-US/
somesite.com/sp-MX/
somesite.com/fr-FR/
Specifically, I do not want /Home/Index appended to the URLs:
somesite.com/en-US/Home/Index
somesite.com/sp-MX/Home/Index
somesite.com/fr-FR/Home/Index
I am committed to making this site using RouteLocalization.mvc
Dresel/RouteLocalization
Translating Your ASP.NET MVC Routes
And I would like to use MVC Attribute Routing to the extent feasible.
I am having trouble figuring out how to cause the Start() method redirect to a language-culture specific URL without the addition of something like "index".
Samples of what I have attempted follow:
using RouteLocalization.Mvc;
using RouteLocalization.Mvc.Extensions;
using RouteLocalization.Mvc.Setup;
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.Clear();
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes(Localization.LocalizationDirectRouteProvider);
const string en = "en-us";
ISet<string> acceptedCultures = new HashSet<string>() { en, "de", "fr", "es", "it" };
routes.Localization(configuration =>
{
configuration.DefaultCulture = en;
configuration.AcceptedCultures = acceptedCultures;
configuration.AttributeRouteProcessing = AttributeRouteProcessing.AddAsNeutralAndDefaultCultureRoute;
configuration.AddCultureAsRoutePrefix = true;
configuration.AddTranslationToSimiliarUrls = true;
}).TranslateInitialAttributeRoutes().Translate(localization =>
{
localization.AddRoutesTranslation();
});
CultureSensitiveHttpModule.GetCultureFromHttpContextDelegate = Localization.DetectCultureFromBrowserUserLanguages(acceptedCultures, en);
var defaultCulture = System.Threading.Thread.CurrentThread.CurrentUICulture.Name;
routes.MapRoute(
name: "DefaultLocalized",
url: "{culture}/{controller}/{action}/{id}",
constraints: new { culture = #"(\w{2})|(\w{2}-\w{2})" },
defaults: new { culture = defaultCulture, controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
And my home controller:
public class HomeController : Controller
{
[HttpGet]
[Route]
public RedirectToRouteResult Start()
{
return RedirectToAction("Home", new { culture = Thread.CurrentThread.CurrentCulture.Name });
}
[Route("Index", Name = "Home.Index")]
public ActionResult Index()
{
return View();
}
public ActionResult Contact()
{
return View();
}
public ActionResult About()
{
return View();
}
}
My Global.asax file:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
AreaRegistration.RegisterAllAreas();
}
}
Redirecting is a separate concern than routing. Since your goal of redirecting any URL to its localized counterpart is a cross-cutting concern your best bet is to make a global filter.
public class RedirectToUserLanguageFilter : IActionFilter
{
private readonly string defaultCulture;
private readonly IEnumerable<string> supportedCultures;
public RedirectToUserLanguageFilter(string defaultCulture, IEnumerable<string> supportedCultures)
{
if (string.IsNullOrEmpty(defaultCulture))
throw new ArgumentNullException("defaultCulture");
if (supportedCultures == null || !supportedCultures.Any())
throw new ArgumentNullException("supportedCultures");
this.defaultCulture = defaultCulture;
this.supportedCultures = supportedCultures;
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
var routeValues = filterContext.RequestContext.RouteData.Values;
// If there is no value for culture, redirect
if (routeValues != null && !routeValues.ContainsKey("culture"))
{
string culture = this.defaultCulture;
var userLanguages = filterContext.HttpContext.Request.UserLanguages;
if (userLanguages.Length > 0)
{
foreach (string language in userLanguages.SelectMany(x => x.Split(';')))
{
// Check whether language is supported before setting it.
if (supportedCultures.Contains(language))
{
culture = language;
break;
}
}
}
// Add the culture to the route values
routeValues.Add("culture", culture);
filterContext.Result = new RedirectToRouteResult(routeValues);
}
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
// Do nothing
}
}
Usage
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new RedirectToUserLanguageFilter("en", new string[] { "en", "de", "fr", "es", "it" }));
filters.Add(new HandleErrorAttribute());
}
}
Note also that your routing is misconfigured. The route setup is run one time per application startup, so setting the default culture to that of the current thread is meaningless. In fact, you should not be setting a default culture at all for your culture route because you want it to miss so your Default route will execute if there is no culture set.
routes.MapRoute(
name: "DefaultLocalized",
url: "{culture}/{controller}/{action}/{id}",
constraints: new { culture = #"(\w{2})|(\w{2}-\w{2})" },
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
I have an asp.net MVC 5 site with OWIN authentication. I would like to have two different log in pages, one for regular users and one for Admin users. The login redirect path is currently set in Start.cs when setting up UseCookieAuthentication. I am pretty sure I could parse the returnUrl and look for /admin/ in the path, but that seems really hacky and prone to errors (what if there is no returnUrl?). Is there a better way? I see stuff online about using an authentication filter, but not sure if that is workable when using OWIN.
#AntP is right, I had the same problem and I solved using Authentication Filter. (I am also using OWIN Authentication).
In my case I wanted the user to be redirected to a different Login page according to the area.
public class AreaAuthorizationAttribute : FilterAttribute, IAuthenticationFilter
{
string clientRole = "Client"; // can be taken from resource file or config file
string adminRole = "Admin"; // can be taken from resource file or config file
string currentArea = "";
public void OnAuthentication(AuthenticationContext context)
{
var area = context.RouteData.DataTokens["area"];
if (context.HttpContext.User.Identity.IsAuthenticated)
{
if (context.HttpContext.User.IsInRole(clientRole) && !(area.ToString().Equals("Client")))
{
context.Result = new HttpUnauthorizedResult();
currentArea = "Client";
}
if (context.HttpContext.User.IsInRole(adminRole) && !(area.ToString().Equals("Admin")))
{
context.Result = new HttpUnauthorizedResult();
currentArea = "Admin";
}
}
else
{
if (area.ToString().Equals("Client"))
{
context.Result = new HttpUnauthorizedResult();
currentArea = "Client";
} else if (area.ToString().Equals("Admin"))
{
context.Result = new HttpUnauthorizedResult();
currentArea = "Admin";
}
}
}
public void OnAuthenticationChallenge(AuthenticationChallengeContext context)
{
if (context.Result == null)
{
Debug.WriteLine("Context null");
context.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary(
new { area = "Client", controller = "Client", action = "Login", returnUrl = context.HttpContext.Request.RawUrl }));
}
if (context.Result is HttpUnauthorizedResult)
{
if ((currentArea.Equals("Client")))
{
context.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary(
new { area = "Client", controller = "Client", action = "Login", returnUrl = context.HttpContext.Request.RawUrl }));
}
else if(currentArea.Equals("Admin"))
{
context.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary(
new { area = "Admin", controller = "Admin", action = "Login", returnUrl = context.HttpContext.Request.RawUrl }));
}
}
}
}
Then I used my custom attribute as followes:
[AreaAuthorization]
public class AdminController : Controller //controller in Admin area
{
.... //actions
}
[AreaAuthorization]
public class ClientController : Controller //controller in Client area
{
.... //actions
}
This is my source (for custom authorize attributes): http://www.dotnetfunda.com/articles/show/2935/creating-custom-authentication-filter-in-aspnet-mvc
You could put a constraint in your routing rules. This constraint will check is the user is of type Admin and allow (or reject) the routing.
You need to set a IRouteConstraint class. For example:
public class IsAdmin : IRouteConstraint
{
public IsAdmin()
{
}
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
bool isAdmin = false;
// TO DO, detect if current user is Admin and return the value
// ...
return isAdmin;
}
}
Then in your routing rules you add that as a constraint
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "home", action = "index", id = UrlParameter.Optional },
constraints: new { controller = new Common.Constraints.IsAdmin() }
);
It is a rather simple approach. I have used to prevent non admin users from accessing anything on the /admin section of my website and it works like a charm.
I have created a sub-domain route class in RouteConfig.cs as:
public class SubdomainRoute : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
if (httpContext.Request == null || httpContext.Request.Url == null)
{
return null;
}
var host = httpContext.Request.Url.Host;
var index = host.IndexOf(".");
string[] segments = httpContext.Request.Url.PathAndQuery.TrimStart('/').Split('/');
if (index < 0)
{
return null;
}
var subdomain = host.Substring(0, index);
string[] blacklist = { "www", "yourdomain", "mail","localhost" };
if (blacklist.Contains(subdomain))
{
return null;
}
string controller = (segments.Length > 0) ? segments[0] : "Home";
if (controller == "")
controller = "Home";
string action = (segments.Length > 1) ? segments[1] : "Index";
var routeData = new RouteData(this, new MvcRouteHandler());
routeData.Values.Add("controller", controller);
routeData.Values.Add("action", action);
routeData.Values.Add("subdomain", subdomain);
return routeData;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
return null;
}
}
I updated RegisterRoutes function to :
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add(new SubdomainRoute());
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{category}/{subcategory}/{lowcategory}/{id}",
defaults: new { controller = "Home", action = "Index",category=UrlParameter.Optional,subcategory=UrlParameter.Optional,lowcategory=UrlParameter.Optional, id = UrlParameter.Optional }
);
}
My Index function is :
public string Index(string category, string subcategory, string lowcategory,int? id)
Now the link http://localhost:29808/Home/Index/mobiles/Htc/M8/3 is working fine but with subdomain its not working electronics.localhost:29808/Home/Index/mobiles/HTC/M8/3. (It shows null in category and subcategory). What else if have to include to make it work?