In the
last article, I gave an example of a factory for obtaining IQuery implementations, but did not explain the mechanism of its work

_queryFactory.GetQuery<Product>() .Where(Product.ActiveRule) .OrderBy(x => x.Id) .Paged(0, 10) // 10 // ElasticSearch, : _queryFactory.GetQuery<Product, FullTextSpecification>() .Where(new FullTextSpecification(«»)) .All() // EF Dapper _queryFactory.GetQuery<Product, DictionarySpecification, DapperQuery>() .Where(new DictionarySpecification (someDirctionary)) .All()
In this article I want to share the technique of registering the necessary components of the assembly according to the agreements. Now I have a code base at hand with a different implementation of CQRS, so the examples will be different. It does not matter: the basic idea remains unchanged.
Suppose you have an interface where
ListParams is a specification coming from the frontend
public interface IListOperation<TDto> { ListResult<TDto> List(ListParams listParam); }
TaskRid application developers from writing controllers, projections and services.
DecisionCreate a base class for the List operation:
public class ListOperationBase<TEntity, TDto> : IListOperation<TDto> where TEntity: IEntity where TDto: IHaveId { protected readonly IDbContext DbContext ; public ListOperationBase(IDbContext dbContext ) { if (dbContext == null) throw new ArgumentNullException(nameof(dbContext)); DbContext = dataStore; } public virtual ListResult<TDto> List(ListParam listParam) { var data = AddProjectionBusinessLogic(AddEntityBusinessLogic(DataStore .GetAll<TEntity>()) .ProjectTo<TDto>()) .Filter(listParam); return new ListResult<TDto>() { Data = data .Paging(listParam) .ToList(), TotalCount = data.Count() }; } protected virtual IQueryable<TEntity> AddEntityBusinessLogic(IQueryable<TEntity> queryable) => queryable; protected virtual IQueryable<TDto> AddProjectionBusinessLogic(IQueryable<TDto> queryable) => queryable; }
The
ProjectTo method is an
AutoMapper feature that allows you to build projections on agreements. Eliminates the need to raise the entire
Entity in memory, while allowing you not to write dull
Select constructions of the form
Query.Select(x => { Name = x.Name, ParentUrl = x.Parent.Url, Foo = x.Foo })
The virtual methods
AddEntityBusinessLogic and
AddProjectionBusinessLogic allow you to add filtering conditions before and after creating a projection.
')
Now for fast prototyping we can use
ListOperationBase <TEntity, TDto> and for real implementations, we need to create real operations with the correct logic. To do this, at the start of the application you need to register everything that is in the assembly under the agreements. In my case, the modular architecture is used and this is the module load code. For monolithic applications, you will need to make a list of assemblies from which you want to load types.
var types = GetType().Assembly.GetTypes(); var operations = types .Where(t.IsClass && !t.IsAbstract && t.ImplementsOpenGenericInterface(typeof(IListOperation<>))); foreach (var operation in operations) { var definitions = operation.GetInterfaces().Where(i => i.ImplementsOpenGenericInterface(typeof (IListOperation<>))); foreach (var definition in definitions) { Container.Register(definition, operation); }
You need only one controller for all Crud operations. You can find the implementation of ControllerSelector for Generic WebApi controllers at:
github.com/hightechtoday/costeffectivecode/blob/master/src/CostEffectiveCode.WebApi2/WebApi/Infrastructure/RuntimeScaffoldingHttpControllerSelector. public ListResult<TListDto> List(ListParam loadParams) => (_container.ResolveAll<IListOperation<TListDto>>().SingleOrDefault() ?? new ListOperationBase<TEntity,TListDto>(DataStore)) .List(loadParams);
Transferring the container to the controller is of course an idea of ​​itself (
ServiceLocator ) and in fact it is much better to wrap the call in a factory method (as is done in the
QueryFactory example). Another weak point is what to do if 2
IListOperation implementations with the same types are registered. There is no definitive answer to this question: it all depends on the specifics of your application and system requirements.
As a result, we got a system for rapid prototyping, which saves the programmer from writing controllers and registering services in a container. All you need to do is add the entity, DTO and describe the mapping. In the case of using
AutoMapper , you should definitely add the
Mapper.AssertConfigurationIsValid (); It will help you learn about errors if you have to change Entity or Dto. By the way, by analogy with the registration of operations, you can automate the creation of mappings under agreements for cases when all mappings are obvious. However, in real life, several lines have to be added to the mapping quite often, so I prefer to do it manually, the benefit is just a couple of lines.
Steps- Add SomeEntity: IEntity
- Add SomeEntityListDto
- Register the mapping SomeEntity -> SomeEntityListDto
- Automaton we get the method / SomeEntity / List
- We add business logic to SomeEntityListOperation <SomeEntity, SomeEntityListDto>
- The / SomeEntity / List method starts using a new implementation with “correct” business logic.
Mapping can be omitted if the Entity can be transferred to the presentation layer / serialized smoothly "as is".