Routeconstraint with Ninject and dbcontext - asp.net-mvc-3

I'm trying to build a constraint that checks against database. And I'm using Ninject, but for some reason it doesnt create a new instance of my repository when it fires.
global.asax.cs
// Content
routes.MapRoute(
"Content Language Route",
"{languageID}/List",
new { controller = "Content", action = "Index",
new { languageID = new LanguageRouteConstraint() },
new string[] { "MyProj.MVC.Controllers" }
);
.....
kernel.Bind<IContentRepository>().To<ContentRepository>();
Constraint
public class LanguageRouteConstraint : IRouteConstraint
{
#region IRouteConstraint Members
private readonly IContentRepository _contentRepository;
public LanguageRouteConstraint(IContentRepository contentRepository)
{
this._contentRepository = contentRepository;
}
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
if (routeDirection == RouteDirection.IncomingRequest)
{
string languageID = values["languageID"].ToString();
if (String.IsNullOrEmpty(languageID))
return false;
MyProj.MVC.Models.Language language = _contentRepository.GetLanguage(languageID);
return (language != null);
}
return false;
}
#endregion
}
Using Ninject for the repository works in the controller, but do I need to modify the route in gobal asa for it to make it work?

Solved it like this:
// Content
routes.MapRoute("Content Language Route",
"{languageID}/List",
new { controller = "Content", action = "Index",
new
{
languageID = new LanguageRouteConstraint(
DependencyResolver.Current.GetService<IContentRepository>())
},
new string[] { "MyProj.MVC.Controllers" }
);

Related

Remove part of Routing in MVC

I wanna change one of my routes in a EPiServer CMS, MVC.
From
http://myDomain.com/modules/EpiCase/About/Index
TO
http://myDomain.com/EpiCase/About/Index
I tried make a new route. When I try it I get 404 erro
routes.MapRoute(
name: "EPiCase_Default",
url: "EPiCase/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional });
Try deriving from System.Web.Routing.RouteBase. Here is a a sample implementation. I think you will need to modify this to fit your application.
public class AlternateRoute : RouteBase
{
private string[] alternateUrls;
public AlternateRoute(params string[] urls){alternateUrls = urls;}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
RouteData result = null;
string requestedURL =
httpContext.Request.AppRelativeCurrentExecutionFilePath;
if (alternateUrls.Contains(requestedURL, StringComparer.OrdinalIgnoreCase))
{
result = new RouteData(this, new MvcRouteHandler());
result.Values.Add("controller", "About");
result.Values.Add("action", "Index");
result.Values.Add("alternateUrl", requestedURL);
}
return result;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
VirtualPathData result = null;
if (values.ContainsKey("alternateUrl") &&
alternateUrls.Contains((string)values["alternateUrl"], StringComparer.OrdinalIgnoreCase))
{
result =
new VirtualPathData(
this,
new UrlHelper(requestContext).Content((string)values["alternateUrl"]).Substring(1)
);
}
return result;
}
}
Then add the desired route through constructor when adding AlternateRoute instance to routes.
routes.Add(new AlternateRoute("~/EpiCase/About/Index"));
You can achieve this with partial routing in EPiServer 7: http://joelabrahamsson.com/custom-routing-for-episerver-content/

How to generate a link to an HTTP POST action with Hyprlinkr?

I'm trying to use Hyprlinkr to generate URL to the HTTP Post action. My controller looks like this:
public class MyController : ApiController {
[HttpPost]
public void DoSomething([FromBody]SomeDto someDto) {
...
}
}
with this route:
routes.MapHttpRoute(
name: "MyRoute",
routeTemplate: "dosomething",
defaults: new { controller = "My", action = "DoSomething" });
I expect to get a simple URL: http://example.com/dosomething, but it does not work. I tried two methods:
1) routeLinker.GetUri(c => c.DoSomething(null)) - throws NullReferenceException
2) routeLinker.GetUri(c => c.DoSomething(new SomeDto())) - generates invalid URL:
http://example.com/dosomething?someDto=Namespace.SomeDto
Update:
Issue opened at github:
https://github.com/ploeh/Hyprlinkr/issues/17
I found a workaround, loosely based on Mark's answer. The idea is to go over every route parameter and remove those that have [FromBody] attribute applied to them. This way dispatcher does not need to be modified for every new controller or action.
public class BodyParametersRemover : IRouteDispatcher {
private readonly IRouteDispatcher _defaultDispatcher;
public BodyParametersRemover(String routeName) {
if (routeName == null) {
throw new ArgumentNullException("routeName");
}
_defaultDispatcher = new DefaultRouteDispatcher(routeName);
}
public Rouple Dispatch(
MethodCallExpression method,
IDictionary<string, object> routeValues) {
var routeKeysToRemove = new HashSet<string>();
foreach (var paramName in routeValues.Keys) {
var parameter = method
.Method
.GetParameters()
.FirstOrDefault(p => p.Name == paramName);
if (parameter != null) {
if (IsFromBodyParameter(parameter)) {
routeKeysToRemove.Add(paramName);
}
}
}
foreach (var routeKeyToRemove in routeKeysToRemove) {
routeValues.Remove(routeKeyToRemove);
}
return _defaultDispatcher.Dispatch(method, routeValues);
}
private Boolean IsFromBodyParameter(ParameterInfo parameter) {
var attributes = parameter.CustomAttributes;
return attributes.Any(
ct => ct.AttributeType == typeof (FromBodyAttribute));
}
}
The second option is the way to go:
routeLinker.GetUri(c => c.DoSomething(new SomeDto()))
However, when using a POST method, you'll need to remove the model part of the generated URL. You can do that with a custom route dispatcher:
public ModelFilterRouteDispatcher : IRouteDispatcher
{
private readonly IRouteDispatcher defaultDispatcher;
public ModelFilterRouteDispatcher()
{
this.defaultDispatcher = new DefaultRouteDispatcher("DefaultApi");
}
public Rouple Dispatch(
MethodCallExpression method,
IDictionary<string, object> routeValues)
{
if (method.Method.ReflectedType == typeof(MyController))
{
var rv = new Dictionary<string, object>(routeValues);
rv.Remove("someDto");
return new Rouple("MyRoute", rv);
}
return this.defaultDispatcher.Dispatch(method, routeValues);
}
}
Now pass that custom dispatcher into your RouteLinker instance.
Caveat: it's very late as I'm writing this and I haven't attempted to compile the above code, but I thought I'd rather throw an attempted answer here than have you wait several more days.
Dimitry's solution got me most of the way to where I wanted, however the routeName ctor param was a problem because StructureMap doesn't know what to put in there. Internally hyprlink is using UrlHelper to generate the URI, and that wants to know the route name to use
At that point, I see why URI generation is so tricky, because it is tied to the route names in the routing config and in order to support POST, we need to associate the method, with the correct routename and that is not known at dispatcher ctor time. Default hyprlinkr assumes there is only one route config named "DefaultRoute"
I changed Dimitry's code as follows, and adopted a convention based approach, where controller methods that start with "Get" are mapped to the route named "Get" and controller methods starting with "Add" are mapped to the route named "Add".
I wonder if there are better ways of associating a method with the proper named routeConfig?
public class RemoveFromBodyParamsRouteDispatcher : IRouteDispatcher
{
private static readonly ILog _log = LogManager.GetLogger(typeof (RemoveFromBodyParamsRouteDispatcher));
public Rouple Dispatch(MethodCallExpression method,
IDictionary<string, object> routeValues)
{
var methodName = method.Method.Name;
DefaultRouteDispatcher defaultDispatcher;
if (methodName.StartsWith("Get"))
defaultDispatcher = new DefaultRouteDispatcher("Get");
else if (methodName.StartsWith("Add"))
defaultDispatcher = new DefaultRouteDispatcher("Add");
else
throw new Exception("Unable to determine correct route name for method with name " + methodName);
_log.Debug("Dispatch methodName=" + methodName);
//make a copy of routeValues as contract says we should not modify
var routeValuesWithoutFromBody = new Dictionary<string, object>(routeValues);
var routeKeysToRemove = new HashSet<string>();
foreach (var paramName in routeValuesWithoutFromBody.Keys)
{
var parameter = method.Method
.GetParameters()
.FirstOrDefault(p => p.Name == paramName);
if (parameter != null)
if (IsFromBodyParameter(parameter))
{
_log.Debug("Dispatch: Removing paramName=" + paramName);
routeKeysToRemove.Add(paramName);
}
}
foreach (var routeKeyToRemove in routeKeysToRemove)
routeValuesWithoutFromBody.Remove(routeKeyToRemove);
return defaultDispatcher.Dispatch(method, routeValuesWithoutFromBody);
}
private static bool IsFromBodyParameter(ParameterInfo parameter)
{
//Apparently the "inherit" argument is ignored: http://msdn.microsoft.com/en-us/library/cwtf69s6(v=vs.100).aspx
const bool msdnSaysThisArgumentIsIgnored = true;
var attributes = parameter.GetCustomAttributes(msdnSaysThisArgumentIsIgnored);
return attributes.Any(ct => ct is FromBodyAttribute);
}
}

Why is the masterPath parameter in CreateView empty?

Writing a small proof of concept application and wondering why the masterPath parameter is empty:
in application_start:
ViewEngines.Engines.Add(new AlternateLocationViewEngine(
new string[] {
"~/Views/Shared/_Layout.cshtml", //Is this correct? Can/should i do that
"~/Views/Shared/{0}.cshtml",
"~/Plugins/Views/Shared/{0}.cshtml",
},
new string[] {
"~/Plugins/Views/{1}/{0}.cshtml",
"~/Plugins/{1}/{0}.chstml",
"~/Plugins/Views/Shared/{0}.cshtml"
}
));
public class AlternateLocationViewEngine : RazorViewEngine
{
public AlternateLocationViewEngine(string[] masterLocations, string[] viewLocations)
: base()
{
MasterLocationFormats = masterLocations;
ViewLocationFormats = viewLocations;
PartialViewLocationFormats = ViewLocationFormats;
}
protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
if (string.IsNullOrEmpty(masterPath))
{
masterPath = MasterLocationFormats.ElementAt(0);
}
var nameSpace = controllerContext.Controller.GetType().Namespace;
return base.CreateView(controllerContext, viewPath.Replace("%1", nameSpace), masterPath.Replace("%1", nameSpace));
}
}
As you see i 'm forced to check if masterPath is empty in method CreateView(). Why is this? Am i missing something fundamental?
My dev environment: ASP.NET MVC3, Razor, .NET4
The masterPath will only have a value when creating a ViewResult with a masterName.
protected internal ViewResult View(string viewName, string masterName);
Internally, the RazorView handles null masterPaths in it's constructor.
// where layoutPath is the masterPath arg from the RazorViewEngine's CreateView
LayoutPath = layoutPath ?? String.Empty;
When rendering the view, the RazorView will set the OverridenLayoutPath to the masterPath (if supplied).
// An overriden master layout might have been specified when the ViewActionResult got returned.
// We need to hold on to it so that we can set it on the inner page once it has executed.
webViewPage.OverridenLayoutPath = LayoutPath;
You do not need to specify the _Layout as one of the MasterLocationFormats. Below is the default behavior for the RazorViewEngine.
MasterLocationFormats = new[] {
"~/Views/{1}/{0}.cshtml",
"~/Views/{1}/{0}.vbhtml",
"~/Views/Shared/{0}.cshtml",
"~/Views/Shared/{0}.vbhtml"
};
You can checkout the source code for more inspiration.

MvcSiteMapProvider, DynamicNodeProviderBase and Globalization

I have the following class
public class MenuVeiculo
{
public string Nome { get; set; }
public string NomeEn { get; set; }
public Guid ID { get; set; }
}
As you can see, I have two properties, "Nome" and "NomeEn." Each one represents the name and the name in English.
Mvc.sitemap
<mvcSiteMapNode key="MenuVeiculo" dynamicNodeProvider="Semep.Extensibilidade.SiteMap.MenuVeiculoDynamicNodeProvider, Semep" title="Menu veiculo" action="Index" controller="Rental">
MenuVeiculoDynamicNodeProvider.cs
public class MenuVeiculoDynamicNodeProvider : DynamicNodeProviderBase
{
public override IEnumerable<DynamicNode> GetDynamicNodeCollection()
{
var context = DependencyResolver.Current.GetService<SemepContext>();
var listDB = (from p in context.MenusVeiculo
select new
{
p.Nome,
p.ID
});
const string keyFormat = "MenuVeiculo_{0}";
foreach (var menu in listDB.ToList())
{
var key = string.Format(keyFormat, menu.ID.ToString().ToUpper());
var root = new DynamicNode(key, menu.Nome)
{
Title = menu.Nome
,
Key = key
};
root.Attributes.Add("id", menu.ID.ToString());
root.RouteValues.Add("id", menu.ID);
yield return root;
}
}
#endregion
}
Question
My question is, how to work with multi-language and DynamicNodeProviderBase?
As you can see, there are two fields, and I'm only showing one.
A problem of "Thread.CurrentThread.CurrentCulture" is that the MvcSiteMapProvider caches the result, how to handle this:
Yes this is one of the shortcomings of MvcSiteMapProvider. In a project I've solved this by returning all the nodes, one for each localization, and using a custom VisibilityProvider to only show the correct localization.
You need to create an additional Route with lang parameter:
routes.MapRoute(
name: "Default_lang",
url: "{lang}/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
constraints: new { lang = #"^(en|ru)$" },
namespaces: new[] { "PNSoft.WebSite.Controllers" }
);
Then, in your mvc.sitemap you specify lang="..." parameter for the root node and for child nodes you need to set inheritedRouteParameters="lang" and then you can get lang from node RouteValues property:
public override IEnumerable<MvcSiteMapProvider.DynamicNode> GetDynamicNodeCollection(MvcSiteMapProvider.ISiteMapNode node)
{
var lang = (string)node.RouteValues["lang"];
...
}
Thats all!

.NET ROUTING and ajax sys undefined

Whenever .NET routing is included in my web.CONFIG i get a sys undefined error which prevents ajax from being loaded.
I'm using .net 3.5 w/ c#
any help would be much appreciated.
You need to use Route Constrains on your routes, it means that you must add a RouteValueDictionary in Route instance in property Contraints
The following example shows how use a virtual folder for indicate the UICulture.
e.g.:
RouteTable.Routes.Add(new Route("{locale}/{page}", new CultureRouter())
{
Constraints = new RouteValueDictionary() {
{ "locale", "[a-z]{2}-[a-z]{2}" } ,
{ "page", "([a-z0-9]*).aspx" }
}
});
RouteTable.Routes.Add(new Route("{folder}/{page}", new CultureRouter())
{
Constraints = new RouteValueDictionary() {
{ "page", "([a-z0-9]*).aspx" }
}
});
RouteTable.Routes.Add(new Route("{locale}/{folder}/{page}", new CultureRouter())
{
Constraints = new RouteValueDictionary() {
{ "locale", "[a-z]{2}-[a-z]{2}" } ,
{ "page", "([a-z0-9]*).aspx" }
}
});
In this case, this route evaluate a regular expression for locale key, and page key, and then you need to evaluate all keys in your IRouteHandler class
e.g:
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
StringBuilder virtualPath = new StringBuilder("~/Pages/");
if (requestContext.RouteData.Values.ContainsKey("locale"))
{
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(requestContext.RouteData.Values["locale"].ToString());
}
if (requestContext.RouteData.Values.ContainsKey("folder"))
{
virtualPath.AppendFormat("{0}/", requestContext.RouteData.Values["folder"].ToString());
}
if (requestContext.RouteData.Values.ContainsKey("page"))
{
virtualPath.Append(requestContext.RouteData.Values["page"].ToString());
}
IHttpHandler pageHandler = BuildManager.CreateInstanceFromVirtualPath(virtualPath.ToString(), typeof(Page)) as IHttpHandler;
return pageHandler;
}
I hope that this will help you.

Resources