2015-01-31 3 views
2

Я пытаюсь создать новый проект, и я добавил новый класс MembershipService, который требует HttpContext быть переданы в это конструктор.MVC5 Ninject связывания и HttpContext

В предыдущем проекте я использовал код

private static void RegisterServices(IKernel kernel) 
    { 
     kernel.Bind<IMembershipService>() 
      .To<MembershipService>() 
      .InRequestScope() 
      .WithConstructorArgument("context", HttpContext.Current); 
     .... 
    } 

Однако в новом проекте я использую Ninject модулей, и после некоторого поиска на StackOverflow и Google, я придумал код ниже: общественного класса ServiceHandlerModule: NinjectModule {

public override void Load() 
    { 

     Bind<IMembershipService>() 
      .To<MembershipService>() 
      .WithConstructorArgument("context", ninjectContext=> HttpContext.Current); 


     this.Kernel.Bind(x => 
     { 
      x.FromAssemblyContaining(typeof(NinjectWebCommon)) 
       .SelectAllClasses() 
       .Where(t => t != typeof(MembershipService)) 
       .BindDefaultInterface(); 
     }); 
     this.Kernel.Bind(x => 
     { 
      x.FromAssemblyContaining<BrandServiceHandler>() 
       .SelectAllClasses() 
       .Where(t => t != typeof(MembershipService)) 
       .BindDefaultInterface(); 
     }); 

    } 
} 

Однако я получаю ошибку ниже:

Описание: Необработанное исключение произошло во время выполнения текущего веб-запроса. Просмотрите трассировку стека для получения дополнительной информации о ошибке и ее возникновении в коде.

Сведения об исключении: Ninject.ActivationException: Ошибка активации строка Нет соответствующих привязок, и тип не является самопереключаемым. Путь активации:

5) Инъекция строки зависимостей в параметр имени файла конструктора типа HttpRequest

4) Инъекция зависимости HttpRequest в запрос параметра конструктора типа HttpContext

3) Инъекции зависимости HttpContext в параметре httpContext от конструктор типа MembershipService

2) Инъекция зависимости IMembershipService в параметр membershi pService конструктора типа HomeController

1) Запрос HomeController

Может кто-то указать, где я неправильно?

Спасибо, Джон

+0

Из сообщения об исключении кажется, что параметр называется 'httpContext' ... поэтому попробуйте его с помощью' .WithConstructorArgument («httpContext», ninjectContext => HttpContext.Current); ' – nemesv

ответ

9

Стивен был прав насчет HttpContext быть значение времени выполнения. Его значения даже не заполняются во время составления заявки.

Это имеет смысл, если вы думаете об этом, потому что приложение должно быть инициализировано вне любого индивидуального контекста пользователя.

Однако решение Стивена перевело проблему на другую услугу. В конце концов, класс, который реализует IUserContext, по-прежнему должен принимать HttpContext в качестве зависимости.

Решение состоит в использовании Abstract Factory, чтобы обеспечить возможность доступа к экземпляру HttpContext во время выполнения, а не при подключении фабрики.

Важно: HttpContext не абстракция, поэтому она не может быть выгружена или издевались. Чтобы гарантировать, что мы имеем дело с абстракцией, Microsoft предоставила абстрактный класс HttpContextBase и конкретный тип HttpContextWrapper по умолчанию. HttpContextBase имеет тот же интерфейс, что и HttpContext. Вы всегда должны использовать HttpContextBase как абстрактный ссылочный тип в своих сервисах, а не HttpContext.

С этими 2 вещи в виду, вы можете создать фабрику для HttpContext следующим образом:

public interface IHttpContextFactory 
{ 
    HttpContextBase Create(); 
} 

public class HttpContextFactory 
    : IHttpContextFactory 
{ 
    public HttpContextBase Create() 
    { 
     return new HttpContextWrapper(HttpContext.Current); 
    } 
} 

Ваш MembershipService может быть изменен, чтобы принять в своем конструкторе в IHttpContextFactory:

public class MembershipService : IMembershipService 
{ 
    private readonly IHttpContextFactory httpContextFactory; 

    // This is called at application startup, but note that it 
    // does nothing except get our service(s) ready for runtime. 
    // It does not actually use the service. 
    public MembershipService(IHttpContextFactory httpContextFactory) 
    { 
     if (httpContextFactory == null) 
      throw new ArgumentNullException("httpContextFactory"); 
     this.httpContextFactory = httpContextFactory; 
    } 

    // Make sure this is not called from any service constructor 
    // that is called at application startup. 
    public void DoSomething() 
    { 
     HttpContextBase httpContext = this.httpContextFactory.Create(); 

     // Do something with HttpContext (at runtime) 
    } 
} 

И вам нужно только ввести HttpContextFactory во время композиции.

kernel.Bind<IHttpContextFactory>() 
    .To<HttpContextFactory>(); 

kernel.Bind<IMembershipService>() 
    .To<MembershipService>(); 

Это само по себе может не решить всю проблему.Вы должны убедиться, что остальная часть вашего приложения не пытается использовать HttpContext, прежде чем она будет готова. В терминах DI это означает, что вы не можете использовать HttpContext в любом конструкторе типов, которые состоят в начале приложения или в любых членах службы, которые вызывает один из этих конструкторов. Чтобы решить эту проблему, вам может потребоваться создать дополнительные абстрактные заводы, чтобы эти службы не вызывали членов IMembershipService до тех пор, пока не будет готова HttpContext.

Для получения дополнительных сведений о том, как это сделать, см. this answer.

Решение Стивена также предполагало создание Facade около HttpContext. Хотя это не помогает решить проблему, я согласен с тем, что это может быть хорошей идеей, если ваши MembershipService (и, возможно, другие службы) используют только небольшое количество членов HttpContext. Как правило, этот шаблон состоит в том, чтобы упростить работу с сложным объектом (например, сгладить его до нескольких членов, которые могут быть глубоко вложены в свою иерархию). Но вам действительно нужно взвесить дополнительное обслуживание добавления другого типа против сложности использования HttpContext в вашем приложении (или стоимости замены его части), чтобы принять это решение.

+0

Принцип замены Лискова Принцип не в том, чтобы высмеивать HttpContext. На самом деле базовые классы никогда не могут нарушать LSP; их подтипы, однако, будут нарушать LSP, если они не соответствуют контракту базового класса. Таким образом, реальная проблема с HttpContext заключается в том, что ПОТРЕБИТЕЛЬ «HttpContext» будет нарушать как принцип инверсии зависимостей, так и принцип разграничения интерфейса, поскольку HttpContext не является абстракцией, и потребитель вынужден зависеть от методов, которые он не использует. – Steven

+1

Спасибо за разъяснение. Я удалил часть из LSP из моего ответа. – NightOwl888

5

Я добавил новый класс MembershipService, который требует HttpContext , который будет передан в это конструктор.

Здесь вы ошибаетесь. HttpContext - это значение времени выполнения, но ваш граф объекта должен состоять только из зависимостей времени компиляции или конфигурации. Все остальное, значения времени выполнения должны либо передаваться через вызовы методов, либо должны отображаться как свойства из служб, которые вводятся.

Не следуя этому руководству, сделать сложнее составление и тестирование графиков объектов. Тестирование вашего корня композиции - хороший пример, поскольку HttpContext.Current недоступен при запуске внутри рамки тестирования.

Поэтому не допускайте, чтобы этот MembershipService получил зависимость конструктора от HttpContext. Вместо этого добавьте службу, которая предоставляет свойство HttpContext в качестве свойства, поскольку это позволяет запросить этот контекст после того, как граф объекта является конструктором.

Но, возможно, даже лучше скрыть HttpContext за абстракцией, специфичной для приложения. HttpContext - это не абстракция; это большой и уродливый API, который делает ваш код намного сложнее проверить и гораздо сложнее понять. Вместо этого можно создать очень узкие/ориентированные интерфейсы, например, интерфейс, как это:

public interface IUserContext 
{ 
    User CurrentUser { get; } 
} 

Теперь ваш MembershipService может зависеть от IUserContext, что выставляет на User объект через свойство. Теперь вы можете создать реализацию AspNetUserContext, которая использует внутреннее устройство HttpContext.Current, когда вызывается свойство CurrentUser. Это приводит к значительно более чистым, более удобному коду.

Вот возможная реализация:

public class AspNetUserContext : IUserContext 
{ 
    public User CurrentUser 
    { 
     // Do not inject HttpContext in the ctor, but use it 
     // here in this property 
     get { return new User(HttpContext.Current.User); } 
    } 
} 
+0

Спасибо, это была огромная помощь. –

2

Я согласен со Стивеном, однако, вы можете также:

kernel.Bind<HttpContext>().ToMethod(c => HttpContext.Current); 
Смежные вопросы