2015-10-17 23 views
1

我有一個MVC應用程序,它使用Unity作爲其IoC容器,並使用PerRequestLifetimeManager在我的應用程序中定義了多個服務。PerRequestLifetimeManager只能在HTTP請求的上下文中使用

container.RegisterType<IFileService, FileService>();

一切正常,只是當我試圖推出我的解決方案自動執行任務(如的SharePoint TimerJobs),在不同的時間間隔開始。

對於這一點,我在一個單獨的項目中定義的ServiceLocator -Type類ContainerManager,但這基本上是這樣的:

public static object Resolve(string typeName) 
    { 
     var type = Type.GetType(typeName); 
     return Resolve(type); 
    } 

    public static object Resolve(Type type) 
    { 
     object result = DependencyResolver.Current.GetService(type); 
     return result; 
    } 

    public static T Resolve<T>() where T : class 
    { 
     object result = DependencyResolver.Current.GetService<T>(); 
     return (T)result; 
    } 

    public static object Resolve(string typeName) 
    { 
     var type = Type.GetType(typeName); 
     return Resolve(type); 
    } 

    public static object Resolve(Type type) 
    { 
     object result = DependencyResolver.Current.GetService(type); 
     return result; 
    } 

    public static T Resolve<T>() where T : class 
    { 
     object result = DependencyResolver.Current.GetService<T>(); 
     return (T)result; 
    } 

而且在我的「任務管理器」我做了以下內容:

var unitOfWork = ContainerManager.Resolve<IFileService>(); 

手動啓動時(當源自HttpRequest時),現在這個工作。但是,這通過我的後臺線程啓動時不起作用。

我已經試過直接(沒有我的ServiceLocator)呼籲團結,但後來我得到異常:PerRequestLifetimeManager can only be used in the context of an HTTP request

這就是我如何創建我的任務:

private ITask CreateTask() 
    { 
     ITask task = null; 
     if (IsEnabled) 
     { 
      var type = System.Type.GetType(Type); 
      if (type != null) 
      { 
       object instance = ContainerManager.Resolve(type); 
       if (instance == null) 
       { 
        // Not resolved 
        instance = ContainerManager.ResolveUnregistered(type); 
       } 

       task = instance as ITask; 
      } 
     } 

     return task; 
    } 

我缺少什麼?

+0

我試圖理解你的問題。當你說「但是,通過我的後臺線程啓動時,這不起作用」,你是什麼意思?你有例外嗎?所以,每個HTTP請求都不會執行TimerJobs,它們是按照某個時間表執行的,對嗎?你希望他們使用他們自己的'FileService'實例,對嗎? –

+0

@YacoubMassad沒錯。當我創建一個要在我的控制器中運行的新任務(源自HTTP請求)時,Unity能夠解決依賴關係。但是,當我的後臺計時器執行它時,統一不會解析依賴關係,並在調用ServiceLocator之後返回「null」。 – SeToY

+0

當你說任務,你的意思是[這個類](https://msdn.microsoft.com/en-us/library/system.threading.tasks.task(v = vs.110).aspx)從TPL ?我雖然你正在使用SharePoint的其他類型的任務?你能提供一些關於你如何創建任務的代碼嗎? –

回答

5

您正在使用Serivice位置,其中is considered an anti-pattern

話雖如此,這裏是一個直接回答你的問題:爲您解決問題

一種方法是使用named registrations

讓說,你正在使用的PerRequestLifetimeManager壽命經理像註冊IServiceService這個:

container.RegisterType<IService, Service>(new PerRequestLifetimeManager()); 

您還可以爲相同的類型添加另一個註冊,但使用不同的生存期管理器。然而,這與之前的註冊之間的區分,你必須給它一個名字是這樣的:

container.RegisterType<IService, Service>("transient_service", new TransientLifetimeManager()); 

在這裏,我註冊IServiceService和使用瞬態壽命經理。我給這個註冊的名字是"transient_service",但是你可以在這裏使用任何名字。現在

,從你的後臺線程,你可以找到這樣的這項服務:

var service = container.Resolve<IService>("transient_service"); 

我假設在這裏,你可以訪問容器(你是通過服務定位器做)。您可能需要更新服務定位器以使其能夠按名稱查找服務。

UPDATE:

這裏是另一種解決方案:

您可以創建一個充當PerRequestLifetimeManager壽命經理如果在當前線程的HttpContext的定製一生的經理,這將回退到如果沒有,則爲TransientLifetimeManager

這裏是一輩子怎麼這麼經理會是什麼樣子:

public class PerRequestOrTransientLifeTimeManager : LifetimeManager 
{ 
    private readonly PerRequestLifetimeManager m_PerRequestLifetimeManager = new PerRequestLifetimeManager(); 
    private readonly TransientLifetimeManager m_TransientLifetimeManager = new TransientLifetimeManager(); 

    private LifetimeManager GetAppropriateLifetimeManager() 
    { 
     if (System.Web.HttpContext.Current == null) 
      return m_TransientLifetimeManager; 

     return m_PerRequestLifetimeManager; 
    } 

    public override object GetValue() 
    { 
     return GetAppropriateLifetimeManager().GetValue(); 
    } 

    public override void SetValue(object newValue) 
    { 
     GetAppropriateLifetimeManager().SetValue(newValue); 
    } 

    public override void RemoveValue() 
    { 
     GetAppropriateLifetimeManager().RemoveValue(); 
    } 
} 

您需要修改註冊使用這樣一輩子經理。

+1

謝謝你的回答。但是這對構造函數注入依賴不起作用,是嗎? – SeToY

+0

你能解釋一下你試圖從後臺線程創建的對象圖,以及這樣的圖是如何使用構造器注入的? –

+0

我的一個任務可能被稱爲'CleanupTask',它清理磁盤上的文件(因此需要一個'IFileService'),因此所說的'CleanupTask'的構造函數有一個IFileService參數。 – SeToY

2

我建議你爲Web環境和後臺環境分別配置2個不同的容器。因此,對於您的Web環境,您可以控制每個請求的生命週期,並且在後臺任務中可以爲每個線程執行一次。

當你正在使用的服務定位器,你可以有2個定位器,像WebServiceLocator.Resolve <>和BackgroundServiceLocator.Resolve <>

+0

這是一個很好的解決方案。 +1。唯一可能的問題是我們需要維護兩個可能相同的組合根。 –

+0

@YacoubMassad容器配置本身不應該導致重複,因爲你可以將生命管理者分解出來並決定在哪一種情況下使用。如果重複來自使用一個服務定位器或另一個服務定位器的代碼,那麼最好的做法是通過注入工廠或甚至注入IContainer來替換服務定位器。然後,您只需在每個環境的入口點引用一個容器或另一個容器。不知道這是否有道理。 –

+0

我認爲你是對的。您可以使配置容器的代碼將生命週期管理器類型作爲輸入。並且每個應用程序都將使用不同的終身管理器來使用此類容器構建代碼 –