Last active
March 4, 2021 01:35
-
-
Save DavidBoike/7203662 to your computer and use it in GitHub Desktop.
Helps with custom "deep routes" within ASP.NET MVC of the form "parent/{parentId}/child/{childId}/deeperChild/{id}" by examining route data and inserting the values into properties or fields that match within the controller.
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
[RoutePrefix("Parent/{parentId:int}/ChildThings")] | |
[InjectRouteData] | |
public class ExampleController : Controller | |
{ | |
private int _parentId; | |
private ParentThing parentThing; | |
// Value is injected from the route by the InjectRouteDataAttribute and sets parentThing. | |
protected int parentId | |
{ | |
get { return this._parentId; } | |
set | |
{ | |
this._parentId = value; | |
this.parentThing = // Some expression to retrieve parentThing by its id | |
} | |
} | |
[Route("")] | |
public ActionResult Index() | |
{ | |
// Able to use parentId or parentThing from any action method | |
return View(); | |
} | |
[Route("{id:int}")] | |
public ActionResult Details(int id) | |
{ | |
return View(); | |
} | |
[Route("Edit/{id:int}")] | |
public ActionResult Edit(int id) | |
{ | |
return View(); | |
} | |
// etc. | |
} |
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 InjectRouteDataAttribute : ActionFilterAttribute | |
{ | |
// Dictionary to hold all of the reflected PropertyInfos and MemberInfos | |
private static Dictionary<Type, Dictionary<string, MemberInfo>> controllerReflectionInfo | |
= new Dictionary<Type, Dictionary<string, MemberInfo>>(); | |
public override void OnActionExecuting(ActionExecutingContext filterContext) | |
{ | |
base.OnActionExecuting(filterContext); | |
// Get the reflection info for this controller type | |
Dictionary<string, MemberInfo> controllerFields = GetControllerFields(filterContext.Controller.GetType()); | |
// Loop through route data values looking for reflected properties/fields that match on name | |
var routeData = filterContext.RouteData.Values; | |
foreach (string key in routeData.Keys) | |
{ | |
MemberInfo member = null; | |
if (controllerFields.TryGetValue(key, out member)) | |
{ | |
if (member is PropertyInfo) | |
{ | |
var pi = member as PropertyInfo; | |
object value = Convert.ChangeType(routeData[key], pi.PropertyType); | |
pi.SetValue(filterContext.Controller, value); | |
} | |
else if (member is FieldInfo) | |
{ | |
var fi = member as FieldInfo; | |
object value = Convert.ChangeType(routeData[key], fi.FieldType); | |
fi.SetValue(filterContext.Controller, value); | |
} | |
} | |
} | |
} | |
private Dictionary<string, MemberInfo> GetControllerFields(Type type) | |
{ | |
Dictionary<string, MemberInfo> result = null; | |
// Standard check/lock/check pattern to avoid race conditions | |
if (!controllerReflectionInfo.TryGetValue(type, out result)) | |
{ | |
lock (controllerReflectionInfo) | |
{ | |
if (!controllerReflectionInfo.TryGetValue(type, out result)) | |
{ | |
result = BuildControllerFields(type); | |
controllerReflectionInfo[type] = result; | |
} | |
} | |
} | |
return result; | |
} | |
private Dictionary<string, MemberInfo> BuildControllerFields(Type type) | |
{ | |
const BindingFlags flags = BindingFlags.SetField | |
| BindingFlags.SetProperty | |
| BindingFlags.Public | |
| BindingFlags.NonPublic | |
| BindingFlags.Instance | |
| BindingFlags.DeclaredOnly; | |
var results = new Dictionary<string, MemberInfo>(); | |
// We don't want properties and fields from all the way down in the MVC framework, | |
// so start at the current controller type and loop through base types until | |
// we leave the top-level controller's assembly. | |
for (var reflectType = type; reflectType.Assembly == type.Assembly; reflectType = reflectType.BaseType) | |
{ | |
var fieldsAndProperties = type.GetMembers(flags) | |
.Where(mi => mi is FieldInfo || mi is PropertyInfo) | |
// If something is overridden in a higher-level class we don't want it | |
// replaced by the base class version | |
.Where(mi => !results.ContainsKey(mi.Name)); | |
// Add each item found to the result dictionary | |
foreach (var member in fieldsAndProperties) | |
results.Add(member.Name, member); | |
} | |
return results; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hey, this was a useful bit of code, just wanted to put out that the call to
type.GetMembers
inBuildControllerFields
should bereflectType.GetMembers
, I think …