Last active
February 1, 2023 14:17
-
-
Save mariosparica-q/47f32349dccc06536e726c18094111a9 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<projectName> | |
└───Controllers | |
└───<controllerName>Controller.cs | |
└───Views | |
└───<controllerName> | |
└───<actionName> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<projectName> | |
└───Controllers | |
└───<sectionName> | |
└───<controllerName>Controller.cs | |
└───Views | |
└───<sectionName> | |
└───<controllerName> | |
└───<actionName> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<projectName> | |
└───Areas | |
└───<areaName> | |
└───Controllers | |
└───<controllerName>Controller.cs | |
└───Views | |
└───<controllerName> | |
└───<actionName> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
routes.MapRoute( | |
name: "Default", | |
url: "{section}/{controller}/{action}/{id}", | |
defaults: new { section = "Home", controller = "Index", action = "Index", id = UrlParameter.Optional } | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class CustomViewEngine : RazorViewEngine { } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public CustomViewEngine() : base() | |
{ | |
ViewLocationFormats = new string[] { | |
"~/Views/%1/{1}/{0}.cshtml", | |
"~/Views/%1/{1}/{0}.vbhtml", | |
"~/Views/Shared/{0}.cshtml", | |
"~/Views/Shared/{0}.vbhtml", | |
}; | |
MasterLocationFormats = new string[] { | |
"~/Views/%1/{1}/{0}.cshtml", | |
"~/Views/%1/{1}/{0}.vbhtml", | |
"~/Views/Shared/{0}.cshtml", | |
"~/Views/Shared/{0}.vbhtml", | |
}; | |
PartialViewLocationFormats = new string[] { | |
"~/Views/%1/{1}/{0}.cshtml", | |
"~/Views/%1/{1}/{0}.vbhtml", | |
"~/Views/Shared/{0}.cshtml", | |
"~/Views/Shared/{0}.vbhtml", | |
}; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath) | |
{ | |
var section = controllerContext.RouteData.Values["section"]?.ToString() ?? string.Empty; | |
return base.CreatePartialView(controllerContext, partialPath.Replace("%1", section)); | |
} | |
protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath) | |
{ | |
var section = controllerContext.RouteData.Values["section"]?.ToString() ?? string.Empty; | |
return base.CreateView(controllerContext, viewPath.Replace("%1", section), masterPath.Replace("%1", section)); | |
} | |
protected override bool FileExists(ControllerContext controllerContext, string virtualPath) | |
{ | |
var section = controllerContext.RouteData.Values["section"]?.ToString() ?? string.Empty; | |
return base.FileExists(controllerContext, virtualPath.Replace("%1", section)); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
ViewEngines.Engines.Clear(); | |
ViewEngines.Engines.Add(new CustomViewEngine()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
routes.MapRoute( | |
name: "Home", | |
url: "", | |
defaults: new { section = "Home", controller = "Index", action = "Index", id = UrlParameter.Optional }, | |
namespaces: new string[] { "MVC.Controllers.Home" } | |
); | |
routes.MapRoute( | |
name: "Admin", | |
url: "Admin/{controller}/{action}/{id}", | |
defaults: new { section = "Admin", controller = "Index", action = "Index", id = UrlParameter.Optional }, | |
namespaces: new string[] { "MVC.Controllers.Admin" } | |
); | |
routes.MapRoute( | |
name: "Dashboards", | |
url: "Dashboards/{controller}/{action}/{id}", | |
defaults: new { section = "Dashboards", controller = "Index", action = "Index", id = UrlParameter.Optional }, | |
namespaces: new string[] { "MVC.Controllers.Dashboards" } | |
); | |
routes.MapRoute( | |
name: "Reports", | |
url: "Reports/{controller}/{action}/{id}", | |
defaults: new { section = "Reports", controller = "Index", action = "Index", id = UrlParameter.Optional }, | |
namespaces: new string[] { "MVC.Controllers.Reports" } | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class CustomControllerFactory : DefaultControllerFactory { } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class CustomControllerFactory : DefaultControllerFactory | |
{ | |
private const string _namespacePrefix = "MVC.Controllers."; | |
// if there are cases where namespaces and section names do not match neatly, use a dictionary to map sections to specific namespaces | |
private Dictionary<string, string> _namespaceMap = new Dictionary<string, string>(); | |
protected override Type GetControllerType(RequestContext requestContext, string controllerName) | |
{ | |
// get section name from route | |
var sectionName = requestContext.RouteData.Values["section"].ToString(); | |
// section not provided, cannot search for controllers | |
if (string.IsNullOrEmpty(sectionName)) return null; | |
// get requested namespace by either finding it in the provided map, or appending the section to the prefix | |
var requestedNamespace = _namespaceMap.ContainsKey(sectionName) | |
? _namespaceMap[sectionName] | |
: $"{_namespacePrefix}{sectionName}"; | |
// get all valid matching controllers | |
var controllersFound = GetControllers(controllerName, requestedNamespace); | |
// no valid matches | |
if (controllersFound.Count == 0) return null; | |
// multiple controllers found | |
if (controllersFound.Count > 1) | |
throw CreateAmbiguousControllerException( | |
requestContext.RouteData.Route, | |
controllerName, | |
controllersFound | |
); | |
// return the single found controller | |
return controllersFound.First(); | |
} | |
// get all controller types within the executing assembly that match the given controller name and namespace | |
private List<Type> GetControllers(string controllerName, string requestedNamespace) => | |
Assembly.GetExecutingAssembly() | |
.GetTypes() | |
.Where(i => IsControllerType(i) | |
&& IsNamespaceMatch(requestedNamespace, i.Namespace) | |
&& String.Equals(i.Name, $"{controllerName}Controller", StringComparison.OrdinalIgnoreCase)) | |
.ToList(); | |
// these functions are defined as "internal" in the MVC source code, so we need to extract them here. | |
#region Extracted functions | |
// determine if type is a controller type | |
// original source: ControllerTypeCache.IsControllerType(Type t) | |
private bool IsControllerType(Type t) => true | |
&& t != null && t.IsPublic && !t.IsAbstract | |
&& typeof(IController).IsAssignableFrom(t) | |
&& t.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase); | |
// check if the target namespace is equal to the requested namespace | |
// if the requested namespace is wildcarded, check if the target namespace is equal or under the requested namespace | |
// original source: IsNamespaceMatch.IsControllerType(string requestedNamespace, string targetNamespace) | |
private bool IsNamespaceMatch(string requestedNamespace, string targetNamespace) | |
{ | |
// looking for exact namespace match | |
if (!requestedNamespace.EndsWith(".*", StringComparison.OrdinalIgnoreCase)) | |
return String.Equals(requestedNamespace, targetNamespace, StringComparison.OrdinalIgnoreCase); | |
// looking for exact or sub-namespace match | |
requestedNamespace = requestedNamespace.Substring(0, requestedNamespace.Length - ".*".Length); | |
if (!targetNamespace.StartsWith(requestedNamespace, StringComparison.OrdinalIgnoreCase)) | |
return false; | |
// exact match | |
if (requestedNamespace.Length == targetNamespace.Length) | |
return true; | |
// good prefix match, e.g. requestedNamespace = "Foo.Bar" and targetNamespace = "Foo.Bar.Baz" | |
if (targetNamespace[requestedNamespace.Length] == '.') | |
return true; | |
// bad prefix match, e.g. requestedNamespace = "Foo.Bar" and targetNamespace = "Foo.Bar2" | |
return false; | |
} | |
// create an exception for finding an ambiguous controller match | |
// original source: DefaultControllerFactory.CreateAmbiguousControllerException(RouteBase route, string controllerName, ICollection<Type> matchingTypes) | |
private InvalidOperationException CreateAmbiguousControllerException(RouteBase route, string controllerName, ICollection<Type> matchingTypes) | |
{ | |
var errorFormats = new | |
{ | |
WithoutRoute = "Multiple types were found that match the controller named '{0}'. This can happen if the route that services this request does not specify namespaces to search for a controller that matches the request. If this is the case, register this route by calling an overload of the 'MapRoute' method that takes a 'namespaces' parameter.{2}{2}The request for '{0}' has found the following matching controllers:{1}", | |
WithRoute = "Multiple types were found that match the controller named '{0}'. This can happen if the route that services this request ('{1}') does not specify namespaces to search for a controller that matches the request. If this is the case, register this route by calling an overload of the 'MapRoute' method that takes a 'namespaces' parameter.{3}{3}The request for '{0}' has found the following matching controllers:{2}" | |
}; | |
string typeList = String.Join(Environment.NewLine, matchingTypes.Select(i => i.FullName)); | |
string errorText; | |
if (route is Route castRoute) | |
errorText = String.Format(errorFormats.WithRoute, controllerName, castRoute.Url, typeList, Environment.NewLine); | |
else | |
errorText = String.Format(errorFormats.WithoutRoute, controllerName, typeList, Environment.NewLine); | |
return new InvalidOperationException(errorText); | |
} | |
#endregion | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
ControllerBuilder.Current.SetControllerFactory(typeof(CustomControllerFactory)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment