Skip to content

Instantly share code, notes, and snippets.

@DavidBoike
Last active March 4, 2021 01:35
Show Gist options
  • Save DavidBoike/7203662 to your computer and use it in GitHub Desktop.
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.
[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.
}
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;
}
}
@attentive
Copy link

Hey, this was a useful bit of code, just wanted to put out that the call to type.GetMembers in BuildControllerFields should be reflectType.GetMembers, I think …

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment