Skip to content

Instantly share code, notes, and snippets.

@CallumVass
Last active January 2, 2016 11:59
Show Gist options
  • Save CallumVass/8300400 to your computer and use it in GitHub Desktop.
Save CallumVass/8300400 to your computer and use it in GitHub Desktop.
Hacky way to load the correct service for before/after save operations to provide additional validation / business logic to entities for Breeze JS
public class SalesAppContextProvider : EFContextProvider<SalesAppContext>
{
private readonly ServiceFactory _serviceFactory;
public SalesAppContextProvider(ServiceFactory serviceFactory)
{
_serviceFactory = serviceFactory;
}
protected override bool BeforeSaveEntity(EntityInfo entityInfo)
{
var entityType = entityInfo.Entity.GetType();
var service = _serviceFactory.GetServiceFor(entityType);
switch (entityInfo.EntityState)
{
case EntityState.Added:
return service.BeforeSaveEntityAdded(entityInfo.Entity);
case EntityState.Modified:
return service.BeforeSaveEntityModified(entityInfo.Entity);
case EntityState.Deleted:
return service.BeforeSaveEntityDeleted(entityInfo.Entity);
default:
return true;
}
}
protected override void AfterSaveEntities(Dictionary<Type, List<EntityInfo>> saveMap, List<KeyMapping> keyMappings)
{
foreach (var map in saveMap)
{
var entityType = map.Key;
var service = _serviceFactory.GetServiceFor(entityType);
foreach (var entityInfo in map.Value)
{
switch (entityInfo.EntityState)
{
case EntityState.Added:
service.AfterSaveEntityAdded(entityInfo.Entity);
break;
case EntityState.Modified:
service.AfterSaveEntityModified(entityInfo.Entity);
break;
case EntityState.Deleted:
service.AfterSaveEntityDeleted(entityInfo.Entity);
break;
}
}
}
base.AfterSaveEntities(saveMap, keyMappings);
}
}
public class CustomerService : IService
{
private readonly IMailService _mailService;
public CustomerService(IMailServce mailService) {
_mailService = mailService;
}
public bool BeforeSaveEntityAdded(object entity)
{
return true;
}
public bool BeforeSaveEntityModified(object entity)
{
return true;
}
public bool BeforeSaveEntityDeleted(object entity)
{
return true;
}
public void AfterSaveEntityAdded(object entity)
{
var customer = entity as Customer;
_mailService.NewCustomerNotification(customer);
}
public void AfterSaveEntityModified(object entity)
{
}
public void AfterSaveEntityDeleted(object entity)
{
}
}
public class ServiceFactory
{
private readonly IMailService _mailService;
public ServiceFactory(IMailService mailService) {
_mailService = mailService;
}
private readonly Dictionary<Type, int> _typeDictionary = new Dictionary<Type, int>
{
{ typeof(Collection), 0 },
{ typeof(Customer), 1 }
};
public IService GetServiceFor(Type entityType)
{
switch (_typeDictionary[entityType])
{
case 0:
return new CollectionService();
case 1:
return new CustomerService(_mailService);
default:
throw new ArgumentException("entityType");
}
}
}
@CallumVass
Copy link
Author

I guess the bit that felt "hacky" to me was the type dictionary, and having to add a new one in each time I add a new service for an Entity. Also, the new'ing up of the concrete implementation rather than in the IoC container, which might be better perhaps for handling any dependencies the services may have? But yes, overall I'm quite pleased with it. Thanks for your comment, it's really appreciated! I will take those points on board and re-factor a bit more! :)

@wardbell
Copy link

wardbell commented Jan 7, 2014

Yes, for sure you'd inject rather than new up the factory. You could get fancy and use MEF to have type-specific services self-register. But you're doing the most important thing which is factoring the business logic so that it isn't all lumped in a gigantic before/after interceptor and your factoring is both rational and flexible.

I've done such factoring myself in DocCode but I like your approach better and will probably steal it in a future DocCode update (I'll credit you and this gist).

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