24

Tôi có một ứng dụng web có nhiều thành phần được đăng ký sử dụng .LifestylePerWebRequest(), bây giờ tôi đã quyết định triển khai Quartz.NET, một thư viện lập lịch công việc .NET, thực hiện trong các chuỗi riêng biệt chứ không phải chuỗi Yêu cầu.Castle.Windsor lối sống tùy thuộc vào ngữ cảnh?

Như vậy, HttpContext.Current sản lượng null. Các dịch vụ, kho lưu trữ của tôi và IDbConnection đã được lập trình cho đến nay bằng cách sử dụng .LifestylePerWebRequest() vì nó giúp việc xử lý chúng dễ dàng hơn khi các yêu cầu kết thúc.

Bây giờ tôi muốn sử dụng các thành phần này trong cả hai trường hợp, trong khi yêu cầu web tôi muốn chúng không bị ảnh hưởng và trong ngữ cảnh không yêu cầu tôi muốn chúng sử dụng một Phong cách sống khác nhau, tôi có thể xử lý bản thân mình, nhưng làm thế nào tôi nên đi về nó để lựa chọn một lối sống cho các thành phần dựa trên bối cảnh hiện tại?

Hiện nay tôi đăng ký dịch vụ (ví dụ), như thế này:

container.Register(
    AllTypes 
     .FromAssemblyContaining<EmailService>() 
     .Where(t => t.Name.EndsWith("Service")) 
     .WithService.Select(IoC.SelectByInterfaceConvention) 
     .LifestylePerWebRequest() 
); 

tôi con tôi nên sử dụng một số loại phương pháp khuyến nông nhưng tôi chỉ không nhìn thấy nó ..

+0

Có một câu hỏi liên quan đến việc chạy mã trong nền của ứng dụng ASP.NET. Câu hỏi là về thùng chứa Simple Injector DI, nhưng câu trả lời vẫn có thể thú vị với bạn: http://stackoverflow.com/a/11059491/264697. – Steven

Trả lời

21

Bạn nên sử dụng Hybrid Lifestyle từ castleprojectcontrib.

Lối sống lai là sự kết hợp giữa hai lối sống cơ bản: lối sống chính và lối sống phụ. Lối sống lai đầu tiên cố gắng sử dụng lối sống chính; nếu nó không có sẵn vì một lý do nào đó, nó sử dụng lối sống phụ. Điều này thường được sử dụng với PerWebRequest làm lối sống chính: nếu bối cảnh HTTP có sẵn, nó được sử dụng như phạm vi cho cá thể thành phần; nếu không lối sống thứ cấp được sử dụng.

+7

Có sẵn cho Windsor 3 thông qua NuGet: http://nuget.org/packages/Castle.Windsor.Lifestyles/0.2.0-alpha1 –

+0

Tôi đã viết một bài đăng hiển thị một trường hợp sử dụng khi sử dụng Castle Windsor với SignalR. Nó thực sự tiện dụng! Cảm ơn. http: //www.leniel.net/2013/01/signalr-ondisconnected-task-and-dependency-injection-with-castle.windsor-hybrid-lifestyle.html –

+0

URL không phải phiên bản cụ thể cho NuGet - https://www.nuget.org/ gói/Castle.Windsor.Lifestyles/ – PandaWood

1

tôi don không biết những gì xảy ra đằng sau hậu trường trong .LifestylePerWebRequest(); nhưng đây là những gì tôi làm cho các trường hợp "Ngữ cảnh theo yêu cầu":

Kiểm tra HttpContext cho phiên và nếu tồn tại kéo ngữ cảnh từ .Items. Nếu nó không tồn tại kéo bối cảnh của bạn từ System.Threading.Thread.CurrentContext.

Hy vọng điều này sẽ hữu ích.

3

Gần đây tôi đã gặp vấn đề tương tự - tôi muốn có thể chạy mã khởi tạo dựa trên vùng chứa của mình trong khởi động Ứng dụng, khi HttpContext.Request chưa tồn tại. Tôi đã không tìm thấy bất kỳ cách nào để làm điều đó, vì vậy tôi đã sửa đổi nguồn của PerWebRequestLifestyleModule để cho phép tôi làm những gì tôi muốn. Thật không may là dường như không thể thực hiện thay đổi này mà không cần biên dịch lại Windsor - tôi đã hy vọng tôi có thể thực hiện nó theo cách mở rộng để tôi có thể tiếp tục sử dụng phân phối chính của Windsor.

Dù sao, để thực hiện công việc này, tôi đã sửa đổi chức năng GetScope của PerWebRequestLifestyleModule để nếu nó KHÔNG chạy trong HttpContext (hoặc nếu HttpContext.Request ném một ngoại lệ, giống như trong Application_Start) thì nó sẽ tìm kiếm Phạm vi bắt đầu từ vùng chứa thay thế. Điều này cho phép tôi sử dụng vùng chứa của mình trong Application_Start bằng cách sử dụng mã sau:

using (var scope = container.BeginScope()) 
{ 
    // LifestylePerWebRequest components will now be scoped to this explicit scope instead 
    // _container.Resolve<...>() 

} 

Không cần phải lo lắng về việc xử lý rõ ràng mọi thứ, vì chúng sẽ được xử lý khi phạm vi là.

Tôi đã đặt mã đầy đủ cho mô-đun bên dưới. Tôi phải trộn lẫn một vài thứ khác trong lớp này để nó hoạt động, nhưng về cơ bản thì nó giống nhau.

public class PerWebRequestLifestyleModule : IHttpModule 
{ 
    private const string key = "castle.per-web-request-lifestyle-cache"; 
    private static bool allowDefaultScopeOutOfHttpContext = true; 
    private static bool initialized; 

    public void Dispose() 
    { 
    } 

    public void Init(HttpApplication context) 
    { 
     initialized = true; 
     context.EndRequest += Application_EndRequest; 
    } 

    protected void Application_EndRequest(Object sender, EventArgs e) 
    { 
     var application = (HttpApplication)sender; 
     var scope = GetScope(application.Context, createIfNotPresent: false); 
     if (scope != null) 
     { 
      scope.Dispose(); 
     } 
    } 

    private static bool IsRequestAvailable() 
    { 
     if (HttpContext.Current == null) 
     { 
      return false; 
     } 

     try 
     { 
      if (HttpContext.Current.Request == null) 
      { 
       return false; 
      } 
      return true; 
     } 
     catch (HttpException) 
     { 
      return false; 
     } 
    } 

    internal static ILifetimeScope GetScope() 
    { 
     var context = HttpContext.Current; 
     if (initialized) 
     { 
      return GetScope(context, createIfNotPresent: true); 
     } 
     else if (allowDefaultScopeOutOfHttpContext && !IsRequestAvailable()) 
     { 
      // We're not running within a Http Request. If the option has been set to allow a normal scope to 
      // be used in this situation, we'll use that instead 
      ILifetimeScope scope = CallContextLifetimeScope.ObtainCurrentScope(); 
      if (scope == null) 
      { 
       throw new InvalidOperationException("Not running within a Http Request, and no Scope was manually created. Either run from within a request, or call container.BeginScope()"); 
      } 
      return scope; 
     } 
     else if (context == null) 
     { 
      throw new InvalidOperationException(
        "HttpContext.Current is null. PerWebRequestLifestyle can only be used in ASP.Net"); 
     } 
     else 
     { 
      EnsureInitialized(); 
      return GetScope(context, createIfNotPresent: true); 
     } 
    } 

    /// <summary> 
    /// Returns current request's scope and detaches it from the request context. 
    /// Does not throw if scope or context not present. To be used for disposing of the context. 
    /// </summary> 
    /// <returns></returns> 
    internal static ILifetimeScope YieldScope() 
    { 
     var context = HttpContext.Current; 
     if (context == null) 
     { 
      return null; 
     } 
     var scope = GetScope(context, createIfNotPresent: true); 
     if (scope != null) 
     { 
      context.Items.Remove(key); 
     } 
     return scope; 
    } 

    private static void EnsureInitialized() 
    { 
     if (initialized) 
     { 
      return; 
     } 
     var message = new StringBuilder(); 
     message.AppendLine("Looks like you forgot to register the http module " + typeof(PerWebRequestLifestyleModule).FullName); 
     message.AppendLine("To fix this add"); 
     message.AppendLine("<add name=\"PerRequestLifestyle\" type=\"Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModule, Castle.Windsor\" />"); 
     message.AppendLine("to the <httpModules> section on your web.config."); 
     if (HttpRuntime.UsingIntegratedPipeline) 
     { 
      message.AppendLine(
       "Windsor also detected you're running IIS in Integrated Pipeline mode. This means that you also need to add the module to the <modules> section under <system.webServer>."); 
     } 
     else 
     { 
      message.AppendLine(
       "If you plan running on IIS in Integrated Pipeline mode, you also need to add the module to the <modules> section under <system.webServer>."); 
     } 
#if !DOTNET35 
     message.AppendLine("Alternatively make sure you have " + PerWebRequestLifestyleModuleRegistration.MicrosoftWebInfrastructureDll + 
          " assembly in your GAC (it is installed by ASP.NET MVC3 or WebMatrix) and Windsor will be able to register the module automatically without having to add anything to the config file."); 
#endif 
     throw new ComponentResolutionException(message.ToString()); 
    } 

    private static ILifetimeScope GetScope(HttpContext context, bool createIfNotPresent) 
    { 
     var candidates = (ILifetimeScope)context.Items[key]; 
     if (candidates == null && createIfNotPresent) 
     { 
      candidates = new DefaultLifetimeScope(new ScopeCache()); 
      context.Items[key] = candidates; 
     } 
     return candidates; 
    } 
} 
+0

bạn có thể muốn xem câu trả lời tôi sẽ đăng trong vài phút :) – bevacqua

2

Ok, tôi đã tìm ra một cách rất rõ ràng để thực hiện việc này!

Trước hết, chúng tôi cần triển khai IHandlerSelector, điều này có thể chọn trình xử lý dựa trên ý kiến ​​của chúng tôi về vấn đề hoặc vẫn trung lập (bằng cách trả lại null, có nghĩa là "không có ý kiến").

/// <summary> 
/// Emits an opinion about a component's lifestyle only if there are exactly two available handlers and one of them has a PerWebRequest lifestyle. 
/// </summary> 
public class LifestyleSelector : IHandlerSelector 
{ 
    public bool HasOpinionAbout(string key, Type service) 
    { 
     return service != typeof(object); // for some reason, Castle passes typeof(object) if the service type is null. 
    } 

    public IHandler SelectHandler(string key, Type service, IHandler[] handlers) 
    { 
     if (handlers.Length == 2 && handlers.Any(x => x.ComponentModel.LifestyleType == LifestyleType.PerWebRequest)) 
     { 
      if (HttpContext.Current == null) 
      { 
       return handlers.Single(x => x.ComponentModel.LifestyleType != LifestyleType.PerWebRequest); 
      } 
      else 
      { 
       return handlers.Single(x => x.ComponentModel.LifestyleType == LifestyleType.PerWebRequest); 
      } 
     } 
     return null; // we don't have an opinion in this case. 
    } 
} 

Tôi đã đưa ra ý kiến ​​rất hạn chế về mục đích. Tôi sẽ chỉ có ý kiến ​​nếu có chính xác hai người xử lý và một trong số họ có lối sống PerWebRequest; nghĩa là người kia là có thể là thay thế không phải HttpContext.

Chúng tôi cần đăng ký bộ chọn này với Castle. Tôi làm như vậy trước khi tôi bắt đầu đăng ký bất kỳ thành phần khác:

container.Kernel.AddHandlerSelector(new LifestyleSelector()); 

Cuối cùng Tôi ước gì có bất kỳ đầu mối như thế nào tôi có thể sao chép đăng ký của tôi để tránh tình trạng này:

container.Register(
    AllTypes 
     .FromAssemblyContaining<EmailService>() 
     .Where(t => t.Name.EndsWith("Service")) 
     .WithService.Select(IoC.SelectByInterfaceConvention) 
     .LifestylePerWebRequest() 
); 

container.Register(
    AllTypes 
     .FromAssemblyContaining<EmailService>() 
     .Where(t => t.Name.EndsWith("Service")) 
     .WithService.Select(IoC.SelectByInterfaceConvention) 
     .LifestylePerThread() 
); 

Nếu bạn có thể tìm ra một cách sao chép đăng ký, thay đổi lối sống và đăng ký cả hai (bằng cách sử dụng container.Register hoặc IRegistration.Register), vui lòng đăng câu trả lời ở đây! :)

Cập nhật: Trong thử nghiệm, tôi cần phải đặt tên duy nhất việc đăng ký giống hệt nhau, tôi đã làm như vậy như thế này:

.NamedRandomly() 


    public static ComponentRegistration<T> NamedRandomly<T>(this ComponentRegistration<T> registration) where T : class 
    { 
     string name = registration.Implementation.FullName; 
     string random = "{0}{{{1}}}".FormatWith(name, Guid.NewGuid()); 
     return registration.Named(random); 
    } 

    public static BasedOnDescriptor NamedRandomly(this BasedOnDescriptor registration) 
    { 
     return registration.Configure(x => x.NamedRandomly()); 
    } 
+0

Thú vị. Tôi có lẽ sẽ gắn bó với cách tiếp cận của tôi vì nó tiết kiệm phải đăng ký tất cả mọi thứ hai lần, và hy vọng rằng tôi có thể tìm thấy một cách dễ dàng hơn để tích hợp nó. Nếu bạn muốn sử dụng cách tiếp cận của bạn để làm bất cứ điều gì trong Application_Start, bạn sẽ cần phải sửa đổi bộ chọn xử lý của bạn để nó cũng bắt trường hợp HttpRequest.Current là hiện tại nhưng HttpRequest.Current.Request là không. – Richard

6

Không sử dụng cùng các thành phần. Trong thực tế, trong hầu hết các kịch bản tôi đã nhìn thấy "xử lý nền" thậm chí không có ý nghĩa để được trong quá trình web để bắt đầu với.

Xây dựng dựa trên các nhận xét.

Xử lý nền thu nhập trong đường dẫn web đang ảnh hưởng đến kiến ​​trúc của bạn để tiết kiệm một vài đô la trên phiên bản EC2. Tôi sẽ mạnh mẽ đề nghị suy nghĩ về điều này một lần nữa, nhưng tôi digress.

Báo cáo của tôi vẫn đứng, ngay cả khi bạn đặt cả hai thành phần trong quy trình web, chúng là hai thành phần khác nhau được sử dụng trong hai ngữ cảnh khác nhau và phải được xử lý như vậy.

+0

Chăm sóc để làm mờ? – bevacqua

+0

trên phần nào ?? –

+0

Khi "sử dụng lại các thành phần trong các ngữ cảnh khác nhau thậm chí không có ý nghĩa"; Tôi chạy các dịch vụ của tôi trong ứng dụng web bởi vì máy chủ tôi sẽ triển khai để không cho phép tôi lưu trữ các dịch vụ Windows. – bevacqua