2009-07-19 25 views
44

Một vài ngày trước, tôi đã gặp vấn đề này với luồng ASP.Net. Tôi muốn có một đối tượng singleton cho mỗi yêu cầu web. Tôi thực sự cần điều này cho đơn vị công việc của tôi. Tôi muốn khởi tạo một đơn vị công việc cho mỗi yêu cầu web để bản đồ nhận dạng hợp lệ thông qua yêu cầu. Bằng cách này tôi có thể sử dụng một IoC để tiêm IUnitOfWork của riêng tôi vào các lớp kho lưu trữ của tôi một cách minh bạch, và tôi có thể sử dụng cùng một cá thể để truy vấn và sau đó cập nhật các thực thể của mình.Singleton Per Call Context (Yêu cầu Web) trong Unity

Vì tôi đang sử dụng Unity, tôi đã sử dụng nhầm PerThreadLifeTimeManager. Tôi sớm nhận ra rằng mô hình luồng ASP.Net không hỗ trợ những gì tôi muốn đạt được. Về cơ bản nó sử dụng một theadpool và tái chế chủ đề, và điều đó có nghĩa là tôi nhận được một UnitOfWork cho mỗi chủ đề !! Tuy nhiên, những gì tôi muốn là một đơn vị công việc cho mỗi yêu cầu web.

Một chút googling đã cho tôi this great post. Đó là chính xác những gì tôi muốn; ngoại trừ phần thống nhất khá dễ dàng để đạt được.

Đây là triển khai thực hiện của tôi cho PerCallContextLifeTimeManager cho sự hiệp nhất:

public class PerCallContextLifeTimeManager : LifetimeManager 
{ 
    private const string Key = "SingletonPerCallContext"; 

    public override object GetValue() 
    { 
     return CallContext.GetData(Key); 
    } 

    public override void SetValue(object newValue) 
    { 
     CallContext.SetData(Key, newValue); 
    } 

    public override void RemoveValue() 
    { 
    } 
} 

Và dĩ nhiên là tôi sử dụng để đăng ký đơn vị của tôi làm việc với một mã tương tự như sau:

unityContainer 
      .RegisterType<IUnitOfWork, MyDataContext>(
      new PerCallContextLifeTimeManager(), 
      new InjectionConstructor()); 

Hy vọng nó sẽ giúp tiết kiệm một ai đó một chút thời gian.

+0

Giải pháp tốt. Nếu tôi có thể, tôi khuyên bạn nên đổi tên thành "CallContextLifetimeManager" vì yêu cầu web có lẽ chỉ là một trong những ứng dụng tiềm năng. –

+0

Đúng, tôi đã cập nhật văn bản và mã để phản ánh điều đó. Cảm ơn. –

+0

+1 Rất hữu ích. – MrDustpan

Trả lời

25

giải pháp gọn gàng, nhưng mỗi trường hợp của LifetimeManager nên sử dụng một khóa duy nhất chứ không phải là một hằng số:

private string _key = string.Format("PerCallContextLifeTimeManager_{0}", Guid.NewGuid()); 

Nếu không nếu bạn có nhiều hơn một đối tượng đăng ký với PerCallContextLifeTimeManager, họ đang chia sẻ cùng một khóa để truy cập CallContext, và bạn sẽ không nhận được đối tượng mong đợi của bạn trở lại.

Cũng đáng thực hiện RemoveValue để đảm bảo các đối tượng được dọn dẹp:

public override void RemoveValue() 
{ 
    CallContext.FreeNamedDataSlot(_key); 
} 
+0

-1 CallContext được tạo lại trên mỗi yêu cầu. Chìa khóa không phải là duy nhất giữa các trường hợp khác nhau của mỗi yêu cầu singleton của bạn. –

+8

+1 Như một vấn đề của thực tế, nó đã được giống như sáueyed nói. Nếu bạn không chỉ định các khóa duy nhất thì tất cả các đối tượng được đăng ký theo một khóa duy nhất và mọi thứ sẽ bị rối tung lên. –

+0

-1 Xem câu trả lời của Steven Robbins và bình luận của Micah Zoltu về câu hỏi: dưới tải 'CallContext' không được lưu giữ cho một yêu cầu duy nhất. –

20

Trong khi điều này là gọi điện thoại tốt PerCallContextLifeTimeManager này, tôi khá chắc chắn đây là không "an toàn" để được coi là một ASP.Net Theo yêu cầu LifeTimeManager.

Nếu ASP.Net thực hiện hoán đổi luồng thì chỉ điều duy nhất được chuyển sang chuỗi mới thông qua CallContext là HttpContext hiện tại - bất kỳ thứ gì khác bạn lưu trữ trong CallContext sẽ bị mất. Điều này có nghĩa là dưới tải nặng mã ở trên có thể có kết quả không mong đợi - và tôi tưởng tượng nó sẽ là một nỗi đau thực sự để theo dõi lý do tại sao!

Các chỉ "an toàn" cách để làm điều này là với HttpContext.Current.Items, hoặc làm một cái gì đó như:

public class PerCallContextOrRequestLifeTimeManager : LifetimeManager 
{ 
    private string _key = string.Format("PerCallContextOrRequestLifeTimeManager_{0}", Guid.NewGuid()); 

    public override object GetValue() 
    { 
     if(HttpContext.Current != null) 
     return GetFromHttpContext(); 
     else 
     return GetFromCallContext(); 
    } 

    public override void SetValue(object newValue) 
    { 
     if(HttpContext.Current != null) 
     return SetInHttpContext(); 
     else 
     return SetInCallContext(); 
    } 

    public override void RemoveValue() 
    { 
    } 
} 

Điều này rõ ràng có nghĩa là dùng phụ thuộc vào hệ thống.Web :-(

Phần lớn biết thêm thông tin về vấn đề này có sẵn tại địa chỉ:

http://piers7.blogspot.com/2005/11/threadstatic-callcontext-and_02.html

3

Cám ơn sự đóng góp của bạn,

Nhưng câu hỏi đề xuất thực hiện có hai nhược điểm, một trong số đó là một lỗi nghiêm trọng như đã được Steven Robbins nêu trong số his answer và Micah Zoltu in a comment.

  1. Ngữ cảnh cuộc gọi là không t đảm bảo được bảo tồn bởi asp.net cho một yêu cầu duy nhất. Dưới tải, nó có thể chuyển sang một số khác, khiến việc triển khai đề xuất bị gián đoạn.
  2. Nó không xử lý việc giải phóng các phụ thuộc khi yêu cầu kết thúc.

Hiện tại, Unity.Mvc Nuget cung cấp gói PerRequestLifetimeManager để thực hiện công việc. Đừng quên đăng ký UnityPerRequestHttpModule liên quan của nó trong mã bootstrapping nếu không phụ thuộc phát hành cũng sẽ không được xử lý.

Sử dụng bootstrapping

DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule)); 

hoặc trong web.config system.webServer/modules

<add name="UnityPerRequestHttpModule" type="Microsoft.Practices.Unity.Mvc.UnityPerRequestHttpModule, Microsoft.Practices.Unity.Mvc" preCondition="managedHandler" /> 

Có vẻ thực hiện như hiện nay cũng thích hợp cho các hình thức web. Và nó thậm chí không phụ thuộc vào MVC. Thật không may, lắp ráp của nó không, nguyên nhân của một số lớp khác nó chứa.

Hãy coi chừng, trong trường hợp bạn sử dụng một số mô-đun http tùy chỉnh sử dụng các phụ thuộc đã giải quyết của bạn, chúng có thể đã được xử lý trong mô-đun EndRequest. Nó phụ thuộc vào thứ tự thực hiện mô-đun.

Các vấn đề liên quan