配置 ClientContext 的解析方式

Configure how ClientContext is resolved

我正在开展一个项目,我们使用存储库模式抽象化对 SharePoint 存储数据的访问,如下(为简洁起见省略了详细信息):

    public interface IDocumentRepository { ... }
    public class SharePointDocumentRepository: IDocumentRepository
    {
        public SharePointDocumentRepository(Microsoft.SharePoint.Client.ClientContext clientContext)
        {
            ...
        }
    }

因此,为了能够在 ASP.Net MVC 应用程序中使用 IDocumentRepository 实例,在 UnityConfig.cs 中,容器配置如下:

    ...
    container.RegisterType<IDocumentRepository, SharePointDocumentRepository>();
    ...

问题是,在运行时,我得到以下异常:

The type ClientContext has multiple constructors of length 1. Unable to disambiguate.

为了解决这个问题,我需要注册如何解析 ClientContext。到目前为止,我已经尝试了以下方法,但没有成功:

  1. 配置构造函数的使用,其中有一个字符串参数
  2. 每当需要 ClientContext 时,将 Unity 配置为 return 特定实例

所以,我的问题是:有没有一种方法可以配置 Unity 来解析 ClientContext,或者我应该回过头来通过 IDocumentRepository 接口的 属性 注入它,只要我得到这个实例界面?注意:第二种方法将使 SharePoint 存储库无法在无法访问 HTTP 上下文的任何其他组件中使用,因此它不是一个可取的方法。

备注:创建一个没有 DI 的存储库实例可以按如下方式完成:

    ...
    [SharePointContextFilter]
    public ActionResult Index()
    {
        SharePointContext spContext = SharePointContextProvider.Current.GetSharePointContext(HttpContext);
        using(var clientContext = spContext.CreateUserClientContextForSPHost())
        {
            var documentRepository = new SharePointDocumentRepository(clientContext);
            ...
        }
        ...
        return View();
    }

注意,SharePointContextFilterAttributeSharePointContextProviderSharePointContext 是在从 "Apps for SharePoint" 项目模板创建项目时自动生成的。

您可以像这样将 ClientContext 的实例传递给构造函数:

var clientContext = new ClientContext();
container.RegisterInstance<ClientContext>("myContext", clientContext);

container.RegisterType<IDocumentRepository, SharePointDocumentRepository>(new InjectionConstructor(container.Resolve<ClientContext>("myContext")));

因此,您的控制器依赖于 IDocumentRepository,但这种依赖性需要基于某些只能从其自身的控制器操作内部获得的上下文来创建。正确吗?

如果是这种情况,那么你需要使用一个Factory.

在你的例子中,你的工厂应该是这样的:

public interface IDocumentRepositoryFactory
{
    IDocumentRepository Create(Context context);
}

public class SharePointDocumentRepositoryFactory : IDocumentRepositoryFactory
{
    public IDocumentRepository Create(Context context)
    {
        //Create the repository
        SharePointContext spContext = SharePointContextProvider.Current.GetSharePointContext(....);
        using(var clientContext = spContext.CreateUserClientContextForSPHost())
        {
            return new SharePointDocumentRepository(clientContext); 
        }
    }
}

请注意,这里我使用 Context 作为 Create 方法的输入。您应该将其替换为控制器需要的任何上下文,以便能够构建存储库(如果有)。

现在,更改控制器的构造函数以在构造函数中接受 IDocumentRepositoryFactory 并将其存储在字段中。

别忘了像这样注册工厂:

container.RegisterType<IDocumentRepositoryFactory, SharePointDocumentRepositoryFactory>();

然后在您的操作中,您可以像这样使用工厂创建存储库:

var repository = m_DocumentRepositoryFactory.Create(....);

其中 m_DocumentRepositoryFactory 是您在构造函数中用于存储工厂依赖项的字段的名称。

更新:

既然ClientContextclass是一次性的,那么ClientContext.[=24后returnSharePointDocumentRepository就没有意义了=]

我建议您改用隔离工厂。像这样:

public interface IDocumentRepositoryIsolationFactory
{
    void CreateAndUse(Context context, Action<IDocumentRepository> action);
}

public class SharePointDocumentRepositoryIsolationFactory : IDocumentRepositoryIsolationFactory
{
    public void CreateAndUse(Context context, Action<IDocumentRepository> action)
    {
        //Create the repository
        SharePointContext spContext = SharePointContextProvider.Current.GetSharePointContext(....);
        using(var clientContext = spContext.CreateUserClientContextForSPHost())
        {
            var repository = SharePointDocumentRepository(clientContext); 

            action(repository);
        }
    }
}

并在您的操作中像这样使用它:

m_IsolationFactory.CreateAndUse(... , (repository) =>
{
    //repository is a IDocumentRepository, use it here

});