A
A
andea232016-02-17 20:25:51
ASP.NET
andea23, 2016-02-17 20:25:51

How to implement Asp.Net Identity 2.0 authorization on Onion architecture?

Good afternoon!
How to transfer Asp.Net Identity 2.0 authorization in asp.net mvc 5 to Onion architecture?

Answer the question

In order to leave comments, you need to log in

1 answer(s)
V
Valery Abakumov, 2016-02-26
@Valeriy1991

Good afternoon!
So far I've found a working version that is being tested very well.
1. Create several projects:
- DAL.Abstract - interfaces of Generic-repository, specialized repositories and UnitOfWork'a;
- DAL.Real - contains implementations of interfaces from DAL.Abstract;
- DAL.Fakes - fake implementations for unit tests;
- BL.Abstract - business service interfaces;
- BL.Real - here implementations of business service interfaces;
- BL.Fakes - fake implementations of auxiliary services (EmailService, SmsService from Identity);
- BL.Tests - business logic unit tests;
- Core.Models - domain models (code-first).
2. Because Since I use the UnitOfWork template, I had to abandon the ready-made implementation from the Identity.EntityFramework library due to the fact that it contains ready-made classes User, Role, Claims, etc., and I have to drag this library into all projects where you need to use at least User. And after it, the EntityFramework library is dragged into all projects ... I don’t like it, because, in my opinion, the MVC layer should not know about implementation particulars (that is, about EntityFramework).
Therefore, the easiest option is to make a bicycle ... um, your own implementation of all sorts of IUserStore, IPasswordStore and other interfaces from Microsoft.AspNet.Identity.Core (well, and accordingly, create your own User, Role, UserRole, Claims, ExternalLogin classes), which could work with UnitOfWork'om necessary to me.
3. Writing a test:

[TestFixture]
    public class UserServiceTests
    {
        private FakeEmailService _emaiService;
        private FakeSmsService _smsService;
        private IUserServiceFactory _userServiceFactory;
        private IUserService _userSrv;
        private IAuthenticationManager _authManager;
        private IUserRepository _userRepository;

        [SetUp]
        public void PreInit()
        {
            var authManagerMock = new Mock<IAuthenticationManager>();
            _authManager = authManagerMock.Object;

            _userRepository = new FakeUserRepository();

            _smsService = new FakeSmsService();
            _emaiService = new FakeEmailService();
            _userServiceFactory = new UserServiceFactory(
                new FakeUnitOfWorkFactory(_userRepository),
                _emaiService, _smsService, _authManager);

            _userSrv = _userServiceFactory.Create();
            // Заполняем хранилище пользователем "[email protected]"
            RegisterExistUser();
        }
        private RegisterResult RegisterExistUser()
        {
            var model = new RegisterNewUserViewModel(ConstForTest.Users.ExistUser.Email, ConstForTest.Users.ExistUser.Password);
            return RegisterUser(model);
        }
        // Регистрация пользователя "[email protected]"
        private RegisterResult RegisterValidUser()
        {
            var model = new RegisterNewUserViewModel(ConstForTest.Users.ValidUser.Email, ConstForTest.Users.ValidUser.Password);
            return RegisterUser(model);
        }

        [Category(ConstForTest.Categories.UserServiceTests.RegisterNewUser)]
        [Test]
        public void RegisterNewUser_ValidEmailAndPassword_AfterRegistrationEmailDoesNotConfirmed()
        {
            var email = ConstForTest.Users.ValidUser.Email;

            // Act
            RegisterValidUser();

            var foundUser = _userSrv.GetUser(email);
            Assert.IsNotNull(foundUser);
            Assert.IsFalse(foundUser.EmailConfirmed);
        }
    }

4. We write the logic of business services. Here is an example of user registration:
public class UserService : IUserService
    {
        private readonly IUnitOfWorkFactory _unitFactory;
        private readonly IUserIdentityManagerFactory _userManagerFactory;
        private readonly ISignInManagerFactory _signInManagerFactory;

        public UserService(IUnitOfWorkFactory unitFactory, 
            IUserIdentityManagerFactory userManagerFactory,
            ISignInManagerFactory signInManagerFactory)
        {
            _unitFactory = unitFactory;
            _userManagerFactory = userManagerFactory;
            _signInManagerFactory = signInManagerFactory;
        }

        public RegisterResult RegisterNewUser(RegisterNewUserViewModel model)
        {
            using (var unit = _unitFactory.Create())
            {
                RegisterResult result = new RegisterResult();
                IdentityResult identityResult;
                try
                {
                        var username = model.Email;
                        var manager = CreateUserManager(unit.UserRepository);
                        identityResult = manager.Create(new User(username), model.Password);
                        if (identityResult.Succeeded)
                        {
                            var createdUser = unit.UserRepository.GetByUsernameQuery(username);
                            manager.AddToRole(createdUser.Id, Roles.Client.ToString());

                            var confirmEmailCode = manager.GenerateEmailConfirmationToken(createdUser.Id);
                            result.ConfirmEmailCode = confirmEmailCode;
                            manager.SendEmail(createdUser.Id, Const.EmailSubjects.Account.ConfirmEmail,
                                "<Тут содержимое  письма для подтверждения е-майл пользователя>");
                        }
                        unit.Commit();
                }
                catch (Exception ex)
                {
                    identityResult = IdentityResult.Failed("В процессе регистрации возникла ошибка. Попробуйте выполнить операцию еще раз.");
                    unit.Rollback();
                }
                result.IdentityResult = identityResult;
                return result;
            }
        }
        
        // ... другие методы бизнес-сервиса
        
    }

The CreateUserManager(unit.UserRepository) method creates an instance of the UserManager class from the Microsoft.AspNet.Identity library), passing it a repository for working with users (yes, since we have Identity, we need to move the repository for users to a separate IUserRepository interface) :
private UserManager<User, Guid> CreateUserManager(IUserRepository userRepository)
        {
            return _userManagerFactory.Create(userRepository);
        }

The UnitOfWork creation factory looks like this:
public interface IUnitOfWorkFactory
    {
        IUnitOfWork Create();
        IUnitOfWork Create(IsolationLevel level);
    }    
    public class EfUnitOfWorkFactory : IUnitOfWorkFactory
    {
        public IUnitOfWork Create()
        {
            return Create(IsolationLevel.ReadCommitted);
        }

        public IUnitOfWork Create(IsolationLevel level)
        {
            var uow = new EfUnitOfWork(level);
            return uow;
        }
    }

UnitOfWork itself:
public interface IUnitOfWork : IDisposable, IUnitOfWorkRepositories
    {
        void SaveChanges();
        void Commit();
        void Rollback();
    }
    public interface IUnitOfWorkRepositories
    {
        IUserRepository UserRepository { get; }
    }
    public class EfUnitOfWork : IUnitOfWork
    {
        private bool _isDisposed = false;
        private DbContext _db;
        private DbContextTransaction _transaction;

        public EfUnitOfWork(IsolationLevel level = IsolationLevel.ReadCommitted)
        {
            _db = new MyApplicationDbContext();
            _transaction = _db.Database.BeginTransaction(level);
        }

        #region IDisposable

        protected virtual void Dispose(bool disposing)
        {
            if (!_isDisposed)
            {
                if (disposing)
                {
                    if (_transaction != null)
                    {
                        _transaction.Rollback();
                        _transaction.Dispose();
                        _transaction = null;
                    }
                    _db.Dispose();
                    _db = null;
                }
            }
            _isDisposed = true;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion

        #region IUnitOfWork

        public void SaveChanges()
        {
            _db.SaveChanges();
        }

        public void Commit()
        {
            SaveChanges();
            _transaction.Commit();
        }

        public void Rollback()
        {
            _transaction.Rollback();
        }

        #endregion

        private IUserRepository _userRepo;

        public IUserRepository UserRepository
        {
            get { return _userRepo ?? (_userRepo = new EfUserRepository(_db)); }
        }
    }

As a result, we get, in my opinion, a well-tested architecture. At the same time, you can write dozens of tests that check the functionality of the business service for working with users, in isolation from MVC, EntityFramework, and other specific implementations.
PS
1. I will be glad to comments from experienced colleagues regarding this implementation. 2. Materials of a series of articles
are taken as a basis 3. I adhere to the concept that no Repository should know about SaveChanges, and even more so about Commit and Rollback. This is the prerogative of UnitOfWork, and the Repository should only implement CRUD operations. Therefore, the SaveChanges method is moved to the IUnitOfWork interface.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question