понедельник, 3 ноября 2014 г.

MVC, EF, AppContext рабочий пример архитектуры

Описанная архитектура взята из проекта Expresscoin.

1-Контроллеры обращаются к отдельным сервисам AppContext-а:
this.AppContext.OrderService.GetUserOrders()

Базовый контроллер имеет ссылку на AppContext, который живет в web приложении
AppContext создается в базовом контроллере,
причем в конструкторе используется код для предотвращения создания нескольких экземпляров AppContext
protected BaseApiController()
        {
            this.AppContext = AppContext.Current;
            if (this.AppContext == null)
            {
                this.disposeAppContext = true;
                this.AppContext = AppContext.Current = new AppContext();
            }
        }

2-статическое свойство AppContext.Current сохраняет или достает экземпляр AppContext из/в
HttpContextFactory.Current.Items
public static AppContext Current
        {
            get { return (AppContext)HttpContextFactory.Current.Items[HttpContextKey]; }
            set { HttpContextFactory.Current.Items[HttpContextKey] = value; }
        }
т.о. в ситуации, когда в одном и том же запросе будет создаваться несколько контроллеров (хотя я не вижу как так может произойти на практике) один и тот же экземпляр будет браться из HttpContext.Items

3-HttpContextFactory инкапсулирует в себе доступ к HttpContext.Current, но при этом имеет возможность подсунутьфэйковый HttpContext для тестовых целей:
public static class HttpContextFactory
    {
        private static HttpContextBase _context;
        public static HttpContextBase Current
        {
            get
            {
                if (_context != null)
                    return _context;

                if (HttpContext.Current == null)
                    throw new InvalidOperationException("HttpContext not available");

                return new HttpContextWrapper(HttpContext.Current);
            }
        }

        public static void SetCurrentContext(HttpContextBase context)
        {
            _context = context;
        }
    }

4-AppContext выставляет наружу сервисы
public ICurrencyService CurrencyService { get { return this.service; } }
public IOrderService OrderService { get { return this.service; } }

Все эти сервисы реализует класс MainService
public AppContext()
        {
            this.service = new MainService();
        }

5-Класс MainService живет в business layer и представляет собой partial класс. Каждая отдельная часть реализует отдельный интерфейс: ICurrencyServic,  IOrderServic.

6- Внутри класса MainService есть ссылка на DbContext (здесь MainDB), который создается в конструкторе
        public MainService()
        {
            this.dbContext = new MainDB();
        }
7-Класс MainService наследует абстрактный класс Disposable (живет в Tools)
public abstract class Disposable : IDisposable
    {
        ~Disposable()
        {
            // Finalizer calls Dispose(false)
            Dispose(false);
        }

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

        protected virtual void Dispose(bool disposing)
        {
        }
    }
это нужно для освобождения DbContex, в MainService:
protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.dbContext != null)
                {
                    this.dbContext.Dispose();
                    this.dbContext = null;
                }
            }
        }

В базовом контроллере, также реализуется Dispose:
protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.AppContext != null)
                {
                    if (this.disposeAppContext)
                    {
                        AppContext.Current = null;
                        this.AppContext.Dispose();
                    }
                    this.AppContext = null;
                }
            }
            base.Dispose(disposing);
        }