Skip to content

Instantly share code, notes, and snippets.

@Scooletz
Created June 18, 2011 11:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Scooletz/1033019 to your computer and use it in GitHub Desktop.
Save Scooletz/1033019 to your computer and use it in GitHub Desktop.
CRUD action provider
/// <summary>
/// The class representing an action info, with its <see cref="ActionMethodSelectorAttribute"/> retrieved.
/// </summary>
/// <remarks>
/// For performance optimization, cache this class instances.
/// </remarks>
public sealed class ActionInfo
{
private readonly ActionMethodSelectorAttribute[] _attributes;
public ActionInfo(MethodInfo methodInfo)
{
MethodInfo = methodInfo;
_attributes =
MethodInfo.GetCustomAttributes(typeof (ActionMethodSelectorAttribute), true).Cast
<ActionMethodSelectorAttribute>().ToArray();
}
public MethodInfo MethodInfo { get; private set; }
public bool IsValid(ControllerContext ctx)
{
for (var i = 0; i < _attributes.Length; i++)
{
if (!_attributes[i].IsValidForRequest(ctx, MethodInfo))
return false;
}
return true;
}
}
/// <summary>
/// A class containing actions provided for any controller being <see cref="IEntityController{TEntity}"/>.
/// </summary>
/// <remarks>
/// The action implementations relies on a binder automatically updating the passed entity, for instance:
/// <see cref="Edit(System.Web.Mvc.ControllerContext,TEntity)"/> simply redirects the user to the index action,
/// as the update onto an entity was made by the model binder. Additionally, a transaction is handled by a global action filter.
/// </remarks>
public static class CrudActions<TEntity, TId>
{
[HttpGet]
public static ActionResult Add(ControllerContext ctx, TEntity entity)
{
return ctx.CreateViewResult(entity);
}
[HttpPost]
public static ActionResult Add(ControllerContext ctx, TEntity entity, ISession session)
{
session.Save(entity);
return ctx.CreateViewResult(entity);
}
[HttpGet]
public static ActionResult Details(ControllerContext ctx, TId id, ISession session)
{
var entity = session.Get(typeof(TEntity), id);
if (entity == null)
return ctx.RedirectToAction("Index");
return ctx.CreateViewResult(entity);
}
[HttpGet]
public static ActionResult Edit(ControllerContext ctx, TId id, ISession session)
{
var entity = session.Get(typeof(TEntity), id);
if (entity == null)
return ctx.RedirectToAction("Index");
return ctx.CreateViewResult(entity);
}
[HttpPost]
public static ActionResult Edit(ControllerContext ctx, TEntity entity)
{
// post
// nothing to do, entity is updated
return ctx.RedirectToAction("Index");
}
}
/// <summary>
/// The CRUD action provider, using <see cref="CrudActions{TEntity,TId}"/> to provide basic CRUD operations.
/// </summary>
public class DetachedActionProvider : IActionProvider
{
private readonly ISessionFactory _factory;
private static readonly ActionInfo[] EmptyInfos = new ActionInfo[0];
private readonly ReaderWriterCache<TypeNameKey, ActionInfo[]> _controllerWithActionNameToActions;
public DetachedActionProvider(ISessionFactory factory)
{
_factory = factory;
_controllerWithActionNameToActions = new ReaderWriterCache<TypeNameKey, ActionInfo[]>();
}
public ActionDescriptor FindAction(ControllerContext ctx, ControllerDescriptor descriptor, string action)
{
var actions = _controllerWithActionNameToActions.FetchOrCreate(new TypeNameKey(descriptor.ControllerType, action),
GetActions);
for (var i = 0; i < actions.Length; i++)
{
var a = actions[i];
if (a.IsValid(ctx))
return ReflectedMvc.DescriptorCreator(a.MethodInfo, action, descriptor);
}
return null;
}
private ActionInfo[] GetActions(TypeNameKey controllerTypeWithActionName)
{
var controller = controllerTypeWithActionName.Type;
var actionName = controllerTypeWithActionName.Name;
var entityType = TryFindEntityType(controller);
if (entityType == null)
return EmptyInfos;
var metadata = _factory.GetClassMetadata(entityType);
if (metadata.IsInherited)
return EmptyInfos;
var detachedActions = typeof(CrudActions<,>).MakeGenericType(entityType, metadata.IdentifierType.ReturnedClass);
return detachedActions
.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(m => StringComparer.InvariantCultureIgnoreCase.Compare(m.Name, actionName) == 0)
.Select(m => new ActionInfo(m))
.ToArray();
}
private static Type TryFindEntityType(Type controllerType)
{
var entityControllers = controllerType.GetInterfaces()
.Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEntityController<>));
if (entityControllers.Count() > 1)
{
throw new InvalidOperationException("That's insane to edit two types of entities in one controller");
}
var i = entityControllers.FirstOrDefault();
return i == null ? null : i.GetGenericArguments()[0];
}
}
/// <summary>
/// The markup interface, providing information about which entity type is covered by this controller.
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public interface IEntityController<TEntity>
{
}
/// <summary>
/// Here you can find a plenty of nasty code, which was needed because of MVC internalization policy.
/// </summary>
public static class ReflectedMvc
{
public readonly static Func<MethodInfo, string, ControllerDescriptor, ReflectedActionDescriptor> DescriptorCreator;
static ReflectedMvc( )
{
DescriptorCreator = CreateDescriptorCreator();
}
private static Func<MethodInfo, string, ControllerDescriptor, ReflectedActionDescriptor> CreateDescriptorCreator()
{
var ctor = typeof(ReflectedActionDescriptor).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null,
new[]
{
typeof (MethodInfo),
typeof (string),
typeof (ControllerDescriptor),
typeof (bool)
},
null);
var p1 = Expression.Parameter(typeof(MethodInfo));
var p2 = Expression.Parameter(typeof(string));
var p3 = Expression.Parameter(typeof(ControllerDescriptor));
var p4 = Expression.Constant(false, typeof(bool));
return Expression.Lambda<Func<MethodInfo, string, ControllerDescriptor, ReflectedActionDescriptor>>(
Expression.New(ctor, p1, p2, p3, p4), p1, p2, p3)
.Compile();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment