【问题标题】:Proper use of [Import] attribute in MEF在 MEF 中正确使用 [Import] 属性
【发布时间】:2012-05-28 20:16:02
【问题描述】:

我正在学习 MEF,我想创建一个简单的示例(应用程序)来看看它是如何工作的。于是我想到了一个简单的翻译器。我创建了一个包含四个项目(DLL 文件)的解决方案:

合同
网络
必应翻译
谷歌翻译

Contracts 包含ITranslate 接口。顾名思义,它只包含合约(接口),因此出口商和进口商都可以使用它。

public interface ITranslator
{
    string Translate(string text);
}

BingTranslatorGoogleTranslator 都是本合同的出口商。他们都执行此合同并提供(导出)不同的翻译服务(一个来自 Bing,另一个来自 Google)。

[Export(typeof(ITranslator))]
public class GoogleTranslator: ITranslator
{
    public string Translate(string text)
    {
        // Here, I would connect to Google translate and do the work.
        return "Translated by Google Translator";
    }
}

BingTranslator 是:

[Export(typeof(ITranslator))]
public class BingTranslator : ITranslator
{
    public string Translate(string text)
    {
        return "Translated by Bing";
    }
}

现在,在我的 Web 项目中,我只想从用户那里获取文本,使用其中一位翻译器(Bing 和 Google)进行翻译,然后将结果返回给用户。因此,在我的 Web 应用程序中,我依赖于翻译。因此,我以这种方式创建了一个控制器:

public class GeneralController : Controller
{
    [Import]
    public ITranslator Translator { get; set; }

    public JsonResult Translate(string text)
    {
        return Json(new
        {
            source = text,
            translation = Translator.Translate(text)
        });
    }
}

最后一块拼图应该是将这些组件(部分)粘合在一起(从较小的部分组成整首歌曲)。所以,在 Web 项目的Application_Start 中,我有:

        var parts = new AggregateCatalog
            (
                new DirectoryCatalog(Server.MapPath("/parts")), 
                new DirectoryCatalog(Server.MapPath("/bin"))
            );
        var composer = new CompositionContainer(parts);
        composer.ComposeParts();

其中/parts 是我放置 GoogleTranslator.dllBingTranslator.dll 文件的文件夹(导出器位于这些文件中),并且在 @987654330 @ 文件夹 我只有包含导入器的 Web.dll 文件。但是,我的问题是,MEF 没有使用所需的翻译器填充GeneralControllerTranslator 属性。我在这个网站上阅读了几乎所有与 MEF 相关的问题,但我无法弄清楚我的示例有什么问题。谁能告诉我我在这里错过了什么?

【问题讨论】:

    标签: c# asp.net-mvc-3 model-view-controller mef extensibility


    【解决方案1】:

    MEF 只会在它自己构建的对象上填充导入。在 ASP.NET MVC 的情况下,创建控制器对象的是 ASP.NET。它无法识别[Import] 属性,因此您会看到缺少依赖项。

    要让 MEF 构造控制器,您必须执行以下操作:

    1. [Export] 标记控制器类本身。
    2. 实现包装 MEF 容器的 IDependencyResolver 实现。您可以通过向 MEF 容器询问匹配的导出来实现 GetService。您可以使用 AttributedModelServices.GetContractName 从请求的类型生成 MEF 合同字符串。
    3. 通过在Application_Start 中调用DependencyResolver.SetResolver 来注册该解析器。

    您可能还需要用[PartCreationPolicy(CreationPolicy.NonShared)] 标记大部分导出的部分,以防止在多个请求中同时重用同一个实例。否则,保存在 MEF 部分中的任何状态都将受到竞争条件的影响。

    编辑:这个blog post 是整个过程的一个很好的例子。

    edit2:可能还有其他问题。 MEF 容器将保存对其创建的任何IDisposable 对象的引用,以便在释放容器本身时释放这些对象。但是,这不适用于具有“每个请求”生命周期的对象!对于实现IDisposable 的任何服务,您实际上都会发生内存泄漏。

    使用像AutoFac 这样的替代方案可能更容易,它有一个用于ASP.NET MVC integration 的NuGet 包,并且支持per-request lifetimes

    【讨论】:

    • +1。 MEF 并非设计为 DI 框架,因此将其用于 DI 非常复杂 - 最初是为 VS 插件开发的。所有 DI 框架都通过传递类型实例来支持服务定位。
    • @Aliostad:事实证明这并不是真正的问题,因为 MEF 合约实际上是字符串,您可以从类型中自己生成。我会更新我的答案。
    • MEF 很棒,但是将非语义属性添加到根本不提供服务的类型中确实很愚蠢。我的意思是,如果我的控制器不提供(导出)任何服务怎么办?我应该总是用[Export] 属性来装饰它吗?如果是这样,我更喜欢使用另一种方法。
    • @Namati:控制器处理 HTTP 请求,这就是它提供的 服务
    【解决方案2】:

    正如@Aliostad 所提到的,您确实需要在控制器创建期间/之后运行组合初始化代码才能使其工作 - 仅将其放在 global.asax 文件中是行不通的。

    但是,您还需要使用 [ImportMany] 而不仅仅是 [Import],因为在您的示例中,您可以使用您发现的二进制文件中的任意数量的 ITranslator 实现。关键是,如果您有许多 ITranslator,但要将它们导入到单个实例中,您可能会从 MEF 获得异常,因为它不知道您真正想要的实现。

    所以你改用:

    [ImportMany]
    public IEnumerable<ITranslator> Translator { get; set; }
    

    快速示例:

    http://dotnetbyexample.blogspot.co.uk/2010/04/very-basic-mef-sample-using-importmany.html

    【讨论】:

      【解决方案3】:

      好的,你需要做的是(没有规定性能,这只是为了看到它工作)

      public class GeneralController : Controller
      {
          [Import]
          public ITranslator Translator { get; set; }
      
          public JsonResult Translate(string text)
          {
              var container = new CompositionContainer(
              new DirectoryCatalog(Path.Combine(HttpRuntime.BinDirectory, "Plugins")));
              CompositionBatch compositionBatch = new CompositionBatch();
              compositionBatch.AddPart(this);
              Container.Compose(compositionBatch);
      
              return Json(new
              {
                  source = text,
                  translation = Translator.Translate(text)
              });
          }
      }
      

      我不是 MEF 方面的专家,坦率地说,我使用它的目的是什么我使用 DI 容器而不是 MEF。

      MEF 是必要的——据我所知。在您的情况下,您需要主动组合您需要 MEFed 的内容,即 您的控制器所以你的控制器工厂需要编写你的控制器实例。

      由于我很少在我的 MVC 应用程序中使用 MEFed 组件,因此我为那些需要 MEF 的操作设置了一个过滤器(而不是在我的控制器工厂中使用 MEF 处理我的所有控制器):

      public class InitialisePluginsAttribute : ActionFilterAttribute
      {
          public override void OnActionExecuting(ActionExecutingContext filterContext)
          {
              CompositionBatch compositionBatch = new CompositionBatch();
              compositionBatch.AddPart(filterContext.Controller);
              UniversalCompositionContainer.Current.Container.Compose(
                  compositionBatch);
              base.OnActionExecuting(filterContext);
          }
      }
      

      这里的UniversalCompositionContainer.Current.Container 是一个使用我的目录目录初始化的单例容器。


      我对 MEF 的个人看法

      MEF,虽然不是 DI 框架,但它做了很多。因此,与 DI 有很大的重叠,如果您已经使用 DI 框架,它们必然会发生冲突

      MEF 在运行时加载 DLL 方面非常强大,尤其是当您拥有 WPF 应用程序时,您可能正在加载/卸载插件并期望其他一切都按原样工作,添加/删除功能。

      对于 Web 应用程序,这没有多大意义,因为您真的不应该在工作的 Web 应用程序中删除 DLL。因此,它的用途非常有限。

      我将在 ASP.NET MVC 中写一篇关于插件的文章,并将使用链接更新这篇文章。

      【讨论】:

      • 感谢您回答@Aliostad,但坦率地说,我不明白我应该怎么做才能使[Import] 在我的Translator 属性上工作。
      • @SaeedNamati 好的,我已经更新以演示如何使用它。
      • 嗯,这是您的观点的对应部分 - MEF 是 .NET 的一部分,它本身就是一个非常好的 DI 框架。在大多数情况下,使用另一种技术是不合理的,只是引入了另一种技术而没有收益(即维护价值为负)。刚刚使用 MEF 完成了一个 18 个月的项目;)工作得很好。
      • 好吧,我的车也飞不起来。问题不在于您是否可以列出它不支持的花哨功能,而在于它是否足够好。我只在有显着好处时添加额外的技术。 MEF 是 .NET 的一部分 - 所以它就在那里。还有什么...问题不是“MEF 能不能做这做那”,而是“这个和那个值得在技术堆栈中添加另一个项目”。
      • @TomTom func 注入并不花哨,至少对我而言。我的代码功能强大。此外,MEF 依赖注入要求您在 DI 时主动组合对象,这一切都发生在后台,我不必为它编写代码。我使用 MEF,但仅用于加载程序集。
      猜你喜欢
      • 2017-10-05
      • 1970-01-01
      • 1970-01-01
      • 2011-02-09
      • 1970-01-01
      • 1970-01-01
      • 2012-03-31
      • 2021-07-24
      • 2011-04-01
      相关资源
      最近更新 更多