I decided it was time for me to try cool things like the Onion architecture of the Repository + UnitOfWork, but I liked ASP.NET Identity quite a bit, which I would like to combine with such an application architecture. I didn’t like the examples that were found on the net. For example, http://metanit.com/sharp/mvc5/23.10.php the layer architecture is completely broken here, namely:

-in BLL layer is connected EntityFramework + Identity.EntityFramework -in DAL layer there is a bunch of business logic in the form of ApplicationRoleManager and ApplicationUserManager

etc...

Therefore, it was decided to make your bike. For a start, I took as an example a project that is generated when creating ASP.NET MVC with Identity already enabled and decided to repeat its functionality, while being divided into 3 layers. Here's what happened: https://github.com/dmitrievMV/OnionArchitectureWithASPNETIdentity

In DAL:

  • Entities.
  • EF context
  • Repository + UnitOfWork
  • UserStore

All implementations in the assembly are closed, except for UnitOfWork and UserStore. UserStore accepts UOW constructor and works with repositories. Only interfaces look outside. The database structure completely repeats the example from the small ones, except that the main keys are int.

In BLL: -ApplicationUserManager

In WEB -ApplicationSignInManager

Actually, please help, spend a little time, just inspect the code. Answer a couple of questions:

  • Did I implement the Repository + UnitOfWork correctly?
  • Is Dispose implemented correctly?
  • In what layer do you think ApplicationSignInManager should be, because it uses OWIN and works with ApplicationUserManager
  • Due to the fact that no layer other than DAL knows about the database and ORM, I decided to make connectionString in a constant, is this a normal approach?
  • Does it make sense to bring entities into a separate project and separate them from annotations for the database?
  • In the example in owin, the context puts the ef context like this: app.CreatePerOwinContext (ApplicationDbContext.Create); How much is this justified and is it worth doing so?
  • In the UserStore implementation for EF, it is very strange to behave with dispose and I have a feeling that some data is permanently stored in memory, is it?
  • How justified is such a bike?

    2 answers 2

    In general, your version is working. But if you want to get better code, I would recommend the following:

    1. OwinContext using OwinContext directly, remove all code using it. Instead, use any DI manager: Autofac , Ninject , Unity , etc. I chose Autofac because of the excellent documentation. In this case, you do not need to drag OWIN to other layers of the application, and it will be very easy to get dependencies by specifying parameters in the constructor. In OwinContext , you need to use the Service Locator anti-pattern for this:

       var manager = HttpContext.GetOwinContext().Get<ApplicationSignInManager>(); 
    2. In UnitOfWork you have a lot of excess. Ideally, it should not create the context / connection itself, but should receive it from the DI container in the constructor. And he should not implement IDisposable . Monitoring the context is best left to the DI manager. In Autofac my config for DAL with nhibernate is:

       builder.Register<ISessionFactory>(c => SessionFactory.Create()).SingleInstance(); builder.Register<IStatelessSession>(c => c.Resolve<ISessionFactory>().OpenStatelessSession()).InstancePerRequest(); builder.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerRequest(); builder.RegisterGeneric(typeof(BaseRepository<>)).As(typeof(IRepository<>)).InstancePerRequest(); 

      Here I first register singleton factory sessions. And then, using it, a new IStatelessSession object IStatelessSession an analogue of DbContext for each request. At the end of the request, the DI manager will complete it. And in this case, IDisposable not needed. Here I register my implementation of IUnitOfWork and the generic repository IRepository<> in a container. IUnitOfWork itself should only manage the transaction: start, roll back, fix no more.

    3. I left the ApplicationUserManager in the Web Api project itself because of the OWIN dependency. But I transferred all the logic of working with the database into my service for working with Asp Identity users - IdentityUserService . Config Autofac for ASP Identity:

       builder.RegisterType<ApplicationUserStore>().As<IUserStore<ApplicationUser>>().InstancePerRequest(); builder.RegisterType<ApplicationUserManager>().AsSelf().InstancePerRequest(); builder.Register<IDataProtectionProvider>(c => app.GetDataProtectionProvider()).InstancePerRequest(); builder.Register<IOAuthAuthorizationServerProvider>(c => new ApplicationOAuthProvider(Startup.PublicClientId)).SingleInstance(); 
    4. connectionString better to render in Web.Config

    5. Entities and interfaces are better to carry out in separate projects. This will allow you to use the same models in all layers of the application. There is no need to maintain a separate hierarchy of models in the DAL, in BOL and in the WebApi / View-layer. On the website http://metanit.com there is a section about a more successful Onion-architecture. It uses this approach.
    6. Data in EF will hang if you share the same context to the database between all requests. It is good practice to create a new context for each request. You can also disable object tracking using AsNoTracking() and speed up EF.
    7. I use generic repositories and generic services, as it eliminates a ton of generic code. Since I use the same model hierarchy in all layers, I do not break encapsulation when using IQueryable in business logic. The fact that IQueryable is used in the business layer is nothing bad, since this is the base class .Net which is essentially a Builder pattern or a Query Object. It gives more flexibility, since requests can be built dynamically in the business layer and not overload the repository with a bunch of similar methods. It also reduces the sample code, since you can write one basic query and then substitute different predicates and projections into it — in other words, use the maximum FP.
    • builder.RegisterGeneric(typeof(BaseRepository<>)).As(typeof(IRepository<>)) - how will you make concrete implementations of repositories with this approach? - Pavel Mayorov
    • Under paragraph 5, if entities and interfaces are brought into a separate project or individual projects, then it will be a multi-faceted architecture rather than an onion one. By the way, it is really easier to support, although it looks more complicated. - Pavel Mayorov
    • Using IQueryable forces the storage model to completely repeat the business logic model - there is no way to drastically change something in the DAL. For example, add deep versioning for all entities. Or dynamic dependency tracking. - Pavel Mayorov
    • If you register a generic for a specific entity, it will take precedence over generic generic. For example, for the IdentityUser model to declare a custom repository, do the following: builder.RegisterType<MyCustomIdentityRepository>().As<IRepository<IdentityUser>>().InstancePerRequest(); - Alexey Skvortsov
    • In the bulbous one, a separate Class Library is also created for entities and for domain interfaces - these two libraries form the Domain. In general, yes it turns out very similar to the multifaceted. The main plus is that you do not need to make several variations of the model: UserEntity, UserBO, UserDTO, etc., as the Domain Library can be connected to any higher layer. - Alexey Skvortsov
    1. Why do you use full dispose pattern so often? Your classes will never own uncontrollable resources - it means they don’t need finalizers in principle!

    2. Why does UserStore close the Unit of Work? Do not close what you did not create!

    3. Why are there two alternative ways to get dependencies in the controller? Obviously, only one of them will be used.

    4. What is not pleased with the standard UserStore?

    5. Generic Repository - antipattern. It simplifies nothing! The IQueryable<TObject> Get() method uncovers all encapsulation. As a result, you have the logic of working with the database will be "smeared" by layer of business logic.

      We must either abandon a separate DAL layer (more precisely, recognize that the EF library itself is a good DAL layer) - or abandon the "generalized" repositories and implement the logic of query construction in the layer that is intended for this.

    6. Why is the Connection string not config?

    7. What does the ApplicationSignInManager class do in App_Start ?!

    • 1. Dispose for the rolestore and userstore are made by analogy to the rolestore and userstore from the identity.entityframework library 2. Because it is also implemented in identity.entityframework 3. The controllers are almost unchanged from the start project 4. In that it is tied to its entity implementation in entityframework. The purpose of the DAL layer is to create an abstraction over orm / bd. 5. I do not have enough experience in this matter 6. Already in the config, I realized a mistake. 7. It is there because it uses Owin and I did not dare to render it into the BLL layer. This is one of the questions. - dmitrievMV
    • 4. Your own UserStore is also bound to EF. - Pavel Mayorov
    • he uses uow which should hide ef. just as you rightly noticed, my implementation of the repository somewhat suggests the possibility of working with data using IQueryable - dmitrievMV
    • @PavelMayorov, why is GenericRepositiry anti-pattern? - andrey.shedko