Skip to content

Instantly share code, notes, and snippets.

@jmarnold
Created October 20, 2010 18:40
Show Gist options
  • Save jmarnold/637033 to your computer and use it in GitHub Desktop.
Save jmarnold/637033 to your computer and use it in GitHub Desktop.
Simple reusable create entity action call example
// metadata for finding models
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public abstract class DecoratorAttribute : Attribute
{
private readonly Type _entityType;
protected DecoratorAttribute(Type entityType)
{
_entityType = entityType;
}
public Type EntityType
{
get { return _entityType; }
}
}
public class InputForCreateAttribute : DecoratorAttribute
{
public InputForCreateAttribute(Type entityType)
: base(entityType)
{
}
}
// the actual reusable action call
public class CreateEntityAction<TEntity, TInput, TBuilder>
where TBuilder : IEntityBuilder<TEntity, TInput>
{
private readonly IUnitOfWork _unitOfWork;
private readonly TBuilder _builder;
public CreateEntityAction(IUnitOfWork unitOfWork, TBuilder builder)
{
_unitOfWork = unitOfWork;
_builder = builder;
}
public JsonResponse Execute(TInput inputModel)
{
var entity = _builder.Build(inputModel);
_unitOfWork.Insert(entity);
_unitOfWork.Commit();
return new JsonResponse(); // obviously make this more meaningful
}
}
// Usually goes somewhere shared
public class RootEntities
{
private static readonly TypePool Types;
private static readonly CompositeFilter<Type> TypeFilters;
static RootEntities()
{
Types = new TypePool();
Types.AddAssembly(typeof(EntityMarker).Assembly);
TypeFilters = new CompositeFilter<Type>();
TypeFilters.Includes += (type => type.Namespace.StartsWith(typeof(EntityMarker).Namespace));
TypeFilters.Excludes += (type => type == typeof(EntityMarker));
TypeFilters.Excludes += (type => type.IsAbstract);
TypeFilters.Excludes += (type => type.IsInterface);
}
public static IEnumerable<Type> All { get { return Types.TypesMatching(TypeFilters.Matches); } }
}
// Marks your domain namespace
public class EntityMarker { }
public class Note
{
protected Note() {}
public Note(string body, User author)
{
Body = body;
Author = author;
}
public int NoteId { get; protected set; }
public string Body { get; protected set; }
public User Author { get; protected set; }
}
public class User
{
protected User() { }
public User(string username)
{
Username = username;
}
public int UserId { get; protected set; }
public string Username { get; protected set; }
}
public interface IEntityBuilder<TEntity, TInput>
{
TEntity Build(TInput inputModel);
}
// Marks the builders namespace
public class EntityBuilderMarker { }
// Could make a quick dsl but seems like overkill (and obviously you could refactor this and the RootEntities stuff)
public class EntityBuilders
{
private static readonly TypePool Types;
private static readonly CompositeFilter<Type> TypeFilters;
static EntityBuilders()
{
Types = new TypePool();
Types.AddAssembly(typeof(EntityBuilderMarker).Assembly);
TypeFilters = new CompositeFilter<Type>();
TypeFilters.Includes += (type => type.Namespace.StartsWith(typeof(EntityBuilderMarker).Namespace));
TypeFilters.Excludes += (type => type == typeof(EntityBuilderMarker));
TypeFilters.Excludes += (type => type.IsAbstract);
TypeFilters.Excludes += (type => type.IsInterface);
TypeFilters.Excludes += (type => !type.ImplementsInterfaceTemplate(typeof(IEntityBuilder<,>)));
}
public static IEnumerable<Type> All { get { return Types.TypesMatching(TypeFilters.Matches); } }
public static Type FindBuilderFor(Type entityType, Type inputModelType)
{
foreach (var type in All)
{
var builderInterface = type.FindInterfaceThatCloses(typeof (IEntityBuilder<,>));
if (builderInterface == null)
{
continue;
}
if (typeof (IEntityBuilder<,>).MakeGenericType(entityType, inputModelType).Equals(builderInterface))
{
return builderInterface;
}
}
return null;
}
}
// wire it up all up in fubu (just missing a url policy)
public class CreateEntitiesActionSource : IActionSource
{
public IEnumerable<ActionCall> FindActions(TypePool types)
{
var actionCalls = new List<ActionCall>();
RootEntities
.All
.Each(entityType =>
{
var inputModelType = entityType.GetModelType<InputForCreateAttribute>();
if (inputModelType == null)
{
return;
}
var builderType = EntityBuilders.FindBuilderFor(entityType, inputModelType);
if (builderType == null)
{
return;
}
var createActionType = typeof(CreateEntityAction<,,>).MakeGenericType(entityType,
inputModelType,
builderType);
actionCalls.Add(new ActionCall(createActionType,
createActionType.GetMethod("Execute", BindingFlags.Instance | BindingFlags.Public)));
});
return actionCalls;
}
}
public static class TypeExtensions
{
public static Type GetModelType<TAttribute>(this Type entityType)
where TAttribute : DecoratorAttribute
{
return ModelTypes
.All
.Where(type => type.HasAttribute<TAttribute>()
&& type.GetAttribute<TAttribute>().EntityType == entityType)
.FirstOrDefault();
}
}
// could refactor to reuse the root entities and entity builders stuff
public class ModelTypes
{
private static readonly TypePool Types;
private static readonly CompositeFilter<Type> TypeFilters;
static ModelTypes()
{
Types = new TypePool();
Types.AddAssembly(typeof(ModelMarker).Assembly);
TypeFilters = new CompositeFilter<Type>();
TypeFilters.Includes += (type => type.Namespace.StartsWith(typeof(ModelMarker).Namespace));
TypeFilters.Excludes += (type => type == typeof(ModelMarker));
}
public static IEnumerable<Type> All { get { return Types.TypesMatching(TypeFilters.Matches); } }
}
// Marks the root models namespace
public class ModelMarker { }
// sample input
[InputForCreate(typeof(Note))]
public class CreateNoteInputModel
{
public string Note { get; set; }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment