知乎周源微信_每周源代码24-可扩展性版本-.NET中的插件,提供程序,属性,插件和模块...

本文探讨了xUnit.NET的源代码组织,其如何利用属性、扩展方法和插件机制实现可扩展性。还介绍了Miguel Castro的性感可扩展性模式,包括提供者、插件和模块的概念。最后,文章提到了Windows Live Writer的插件模型,展示了如何通过API和自定义UI扩展其功能。
摘要由CSDN通过智能技术生成
知乎周源微信

知乎周源微信

I've been getting more and more interested in how folks extend their applications using plugins and things. In my new ongoing quest to read source code to be a better developer, Dear Reader, I present to you twenty-fourth in a infinite number of posts of "The Weekly Source Code."

我对人们如何使用插件和事物扩展其应用程序越来越感兴趣。 在我不断追求阅读源代码以成为更好的开发人员的过程中,亲爱的读者,我每周源代码的无数帖子中向您展示了第二十四位

There's a lot of ways to "extend" an application or framework. Probably dozens of patterns. You can learn about the Gang of Four Patterns using C# over here.

有很多方法可以“扩展”应用程序或框架。 大概有几十种模式。 您可以在此处使用C#了解有关四种模式帮派

I was looking and three chunks of code this week that extend things or are extensible. The first was xUnit.NET, the new Unit Testing Framework on the block (until I finally go File|New Project and bang out "HanselTest2000" ;) ) and the second was some source that Miguel Castro gave out at the March 2008 CINNUG meeting called Sexy Extensibility Patterns. Miguel has a Code Generation/Data Mapping tool called CodeBreeze that uses this patterns. It was on DNRTV a while back. The third was the plugin design of Windows Live Writer and the WLW SDK.

我当时在看,这周有三部分代码可以扩展或扩展。 第一个是xUnit.NET ,这个新的单元测试框架(直到我终于进入File | New Project并敲出“ HanselTest2000”;)),第二个是Miguel Castro2008年3月的CINNUG会议上提供的一些资料。性感的可扩展性模式。 Miguel有一个使用此模式的称为CodeBreeze代码生成/数据映射工具前段时间DNRTV上。 第三是Windows Live WriterWLW SDK的插件设计。

This post isn't trying to be an exhaustive list of anything, it's just some cool code that's got me thinking about interesting was to extend stuff. I like these three examples because each has more than one way to extend it.

这篇文章并不是要详尽地列出任何内容,它只是一些很酷的代码,让我考虑到有趣的是扩展内容。 我喜欢这三个示例,因为每个示例都有多种扩展方法

Extending software means adding functionality that wasn't there to start with. There's MANY ways to do it though. For example, adding a script engine like VBA or PowerShell and hosting script would be one way. Making a public scripting-friendly API is a twist on that theme. Hosting AddIns or Plugins via deriving from base classes, implementing interfaces or sourcing events. using System.AddIn, is another.  Using a Dependency Injection Container is a more advanced and powerful way to extend applications.

扩展软件意味着添加一开始就没有的功能。 虽然有很多方法可以做到。 例如,添加脚本引擎(如VBA或PowerShell)和托管脚本将是一种方法。 制作一个对脚本友好的公共API是该主题的一个转折点。 通过从基类派生,实现接口或获取事件来托管AddIns或插件。 使用System.AddIn ,是另一个。 使用依赖注入容器是扩展应用程序的更高级,更强大的方法。

xUnit.NET (xUnit.NET)

image

xUnit.NET is a .NET Unit Testing Framework from Brad Wilson and Jim Newkirk (formerly of NUnit fame). Initial reaction to their framework was a resounding "meh" as folks asked "seriously, do we NEED another Unit Testing Framework?" but they soldiered on, and just like the little Mock Framework that could, they're starting to get the respect they deserve. It's got an MSBuild task, Resharper and TestDriven.NET Test Runner support, and most importantly, the framework has some interesting extensibility points.

xUnit.NETBrad WilsonJim Newkirk (以前是NUnit的成名人物)的.NET单元测试框架。 人们对其框架的最初React是响亮的“ meh ”,就像人们问的“严重时,我们需要另一个单元测试框架吗?” 但是他们坚持不懈,就像可以使用的小Mock框架一样,他们开始获得应有的尊重。 它具有MSBuild任务,Resharper和TestDriven.NET Test Runner支持,最重要的是,该框架具有一些有趣的可扩展性点。

The xUnit.NET source code is exceedingly tidy. I mean that as a total complement, like when you visit someone's house and you find yourself asking "who is your decorator?" while simultaneously realizing that they are just THAT tidy and organized.

xUnit.NET源代码非常整洁。 我的意思是说,作为一个整体,就像您拜访某人的房子时,您发现自己在问“谁是您的装饰员?” 同时意识到他们只是整齐有序。

ASIDE: Not enough people use "Solution Folders" in Visual Studio. Seriously, folks, just right-click and "Add | New Solution Folder," start dragging things around and bask in the tidiness.

旁白:没有足够的人在Visual Studio中使用“解决方案文件夹”。 认真地说,人们只需单击鼠标右键,然后单击“添加|新解决方案文件夹”,就可以开始拖拉东西并享受整洁的感觉。

They've separately any controversial (my word) static extension methods into separate projects, xunitext and xunitext35 that includes .NET 3.5-specific features. So, certainly Extension Methods are a coarse extensibility point for developers "downstream" from your framework.

他们将任何有争议的(我的话)静态扩展方法分别放入了单独的项目xunitext和xunitext35中,这些项目包括.NET 3.5特定的功能。 因此,对于从您的框架“下游”的开发人员来说,扩展方法无疑是一个粗略的可扩展性点。

They use Attributes as MAJOR way to extend their framework. For example, say you want something to happen before and/or after a test.

他们使用属性作为主要方式来扩展其框架。 例如,假设您希望在测试之前和/或之后发生某些事情。

namespace XunitExt
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class TraceAttribute : BeforeAfterTestAttribute
public override void Before(MethodInfo methodUnderTest)
{
Trace.WriteLine(String.Format("Before : {0}.{1}", methodUnderTest.DeclaringType.FullName, methodUnderTest.Name));
}

public override void After(MethodInfo methodUnderTest)
{
Trace.WriteLine(String.Format("After : {0}.{1}", methodUnderTest.DeclaringType.FullName, methodUnderTest.Name));
}
}
}

You just derive a new attribute from BeforeAfterTestAttribute, and put it on a test. The [Fact] attribute is a standard one. They use it instead of [Test]:

您只需从BeforeAfterTestAttribute派生一个新属性,然后将其放在测试中。 [事实]属性是标准属性。 他们使用它代替[Test]:

[Fact]
[Trace]
public void CanSearchForSubstrings()
{
Assert.Contains("wor", "Hello, world!");
}

Now, my code in Before() and After() above will execute for this test.

现在,我在上面的Before()和After()中的代码将对此测试执行。

If you've got your own Test Framework but you want to move towards using xUnit.NET, they've got a [RunWith] attribute, where you can make your own Test Runner and say, for example, "run this test with nUnit." You can use it two ways, either as [RunWith(typeof(MyRunner)] or with a custom attribute like:

如果您拥有自己的测试框架,但是想使用xUnit.NET,则它们具有[RunWith]属性,您可以在其中创建自己的测试运行器,并说“例如,使用nUnit运行测试”。 。” 您可以通过两种方式使用它,既可以作为[RunWith(typeof(MyRunner)],也可以具有以下自定义属性:

public class RunWithNUnitAttribute : RunWithAttribute
{
public RunWithNUnitAttribute()
: base(typeof(NUnitCommand)) {}
}

...where nUnitCommand implements a very clean interface called ITestCommand that "describes the ability to executes all the tests in a test class."

...其中nUnitCommand实现了一个非常干净的名为ITestCommand的接口,该接口“描述了在测试类中执行所有测试的能力”。

public interface ITestClassCommand
{
object ObjectUnderTest { get; }
ITypeInfo TypeUnderTest { get; set; }
int ChooseNextTest(ICollection testsLeftToRun);
Exception ClassFinish();
Exception ClassStart();
IEnumerable EnumerateTestCommands(IMethodInfo testMethod);
IEnumerable EnumerateTestMethods();
bool IsTestMethod(IMethodInfo testMethod);
}

I think this is a very elegant interface. The framework "eats its own dogfood" also, which means that the xUnit.NET guys have factored things appropriately (as they should have) such that they are using their own extensibility points. Using your own framework to build your stuff is the only way you'll know if you've got a great framework and it's the best way to find out what's NOT working.

我认为这是一个非常优雅的界面。 该框架也“吃掉了自己的狗粮”,这意味着xUnit.NET家伙已经适当地考虑了因素(如他们应该那样),以便他们使用自己的可扩展性点。 使用自己的框架来构建自己的东西是知道自己是否拥有出色框架的唯一方法,并且这是找出不起作用的最佳方法。

The NUnitCommand implementation then uses the NUnit SDK/API to run test. This allows you to move over to xUnit.NET, if you like, from NUnit or another test framework a little at a time, while running all the tests under the same runner.

然后,NUnitCommand实现使用NUnit SDK / API来运行测试。 这样,您就可以一次从NUnit或另一个测试框架移至xUnit.NET(如果需要),同时在同一运行程序下运行所有​​测试。

Another way to extend your code is through the use of existing well-known interfaces like IComparer<T>, for example. They're a test framework, so they're full of Asserts like Assert.Equal and Assert.Contains. The Assert.InRange has an overload, seen below, than can take an IComparer as an optional parameter.

扩展代码的另一种方法是使用现有的众所周知的接口,例如IComparer <T>。 它们是一个测试框架,因此充满了Asserts.Equal和Assert.Contains之类的Asserts。 Assert.InRange具有一个重载,如下所示,可以将IComparer作为可选参数。

public void InRange<T>(T actual,
T low,
T high)
{
Assert.InRange(actual, low, high);
}
public void InRange<T>(T actual,
T low,
T high,
IComparer comparer)
{
Assert.InRange(actual, low, high, comparer);
}

This might seem obvious to some, but it's thoughtfully obvious and a clean extensibility point. And thoughtfully obvious is easy to say and hard to do. I'm thinking that Moq and xUnit.NET just may be the new peas and carrots - they go together nicely and each have the similar goals.

在某些人看来,这似乎很明显,但在思想上却很明显,并且是一个明确的扩展点。 显而易见,很容易说出来并且很难做到。 我认为MoqxUnit.NET可能只是新的豌豆和胡萝卜-它们很好地结合在一起,并且各自都有相似的目标。

性感的可扩展性模式 (Sexy Extensibility Patterns)

You know someone likes their job and coding when they describe a pattern as "sexy" and not just "handsome" or "sassy." Miguel includes his slide deck and code in both C# and VB up on the CINNUG website.

您知道当某人将模式描述为“性感”而不只是“帅气”或“厚颜无耻”时,他们会喜欢他们的工作和编码。 Miguel在CINNUG网站上以C#和VB形式包含了他的幻灯片和代码

Miguel calls out three main extensibility types that he uses (from his presentation):

Miguel(从演示中)指出了他使用的三种主要可扩展性类型:

  • Providers - Allow abstraction for data and behavior

    提供者-允许抽象数据和行为
  • Plugins- Adding new behavior

    插件-添加新行为
  • Modules - Centralize plug-in functionality and enforce manageable standards

    模块-集中插件功能并实施可管理的标准

Providers are based on the Strategy Design Pattern and look like this. You'll see stuff like this all throughout ASP.NET 2.0. A controller or context class needs a way (hence "strategy" to do something). Classically an instance of a class that provides the needed strategy is passed in, but nowadays it could come from an IOC framework, or just a config file.

提供者基于“战略设计模式” ,看起来像这样。 在整个ASP.NET 2.0中,您都会看到类似的东西。 控制器或上下文类需要一种方法(因此需要“策略”来做某事)。 通常,会传入提供所需策略的类的实例,但如今它可能来自IOC框架,也可能来自配置文件。

They're often very simple and they're easy to write. Make an interface that provides something, usually data:

它们通常非常简单,而且易于编写。 创建一个提供某些内容(通常是数据)的接口:

namespace Core
{
public interface IDataProvider
{
string GetSource();
string GetData(string source);
void LogData(string data);
}
}

Then use it. This is a simple example, but usually this would be buried in a ProviderFactory that would hide the config and activation, and even hold the new instance:

然后使用它。 这是一个简单的示例,但是通常它会被埋在ProviderFactory中,后者会隐藏配置和激活,甚至保存新实例:

public void ProcessData()
{
string s_Provider = ConfigurationManager.AppSettings["dataProvider"];

Object o = Activator.CreateInstance(Type.GetType(s_Provider));

IDataProvider o_Provider = o as IDataProvider;
string s_Source = o_Provider.GetSource();

string s_Data = o_Provider.GetData(s_Source);

if (s_Data != "")
{
o_Provider.LogData(s_Data);
}
}

Aside: Personally I find the quasi-Hungarian naming in Miguel's source off-putting, but I know him well enough to say that. ;)

旁白:我个人认为Miguel的来源中的准匈牙利名称令人反感,但我对他的了解足以使他这么说。 ;)

So, Providers provide stuff, and Plug-Ins add functionality, like:

因此,提供者提供东西,而插件添加功能,例如:

public class ArchiveProcessing : IPostLogPlugin
{
void IPostLogPlugin.PerformProcess(string data)
{
// take data and archive it
}
}
public class ArchiveProcessing : IPostLogPlugin
{
void IPostLogPlugin.PerformProcess(string data)
{
// take data and archive it
}
}

You might have a bunch of plugins added to your config file and then call them synchronously, in this example, perhaps as a postProcessing step to a provider:

您可能在配置文件中添加了一堆插件,然后在本示例中将它们同步调用,也许作为提供者的后处理步骤:

object section =  ConfigurationManager.GetSection("postProcessing");
List<plugininfo> o_Plugins = section as List ;
foreach (PluginInfo o_PluginInfo in o_Plugins)
{
object o = Activator.CreateInstance(Type.GetType(o_PluginInfo.PluginType));
IPostLogPlugin o_Plugin = o as IPostLogPlugin;
o_Plugin.PerformProcess(s_Data);
}

Modules, in Miguel's parlance, are like Filters that get involved in a process and various points (like HttpModules) and change functionality. First you define some events:

用Miguel的话来说,模块就像过滤器一样,它参与了流程和各个方面(例如HttpModules)并更改了功能。 首先,您定义一些事件:

public delegate void AcmeModuleDelegate<t>(T e);
public class ModuleEvents
{
public AcmeModuleDelegate<checkdatasourceeventargs> CheckDataSource { get; set; }
public AcmeModuleDelegate<preprocessdataeventargs> PreProcessData { get; set; }
public AcmeModuleDelegate<postprocessdataeventargs> PostProcessData { get; set; }
}

You might add a bunch of modules to your application and have them listen in at these three or more "events." Then you need to ask yourself, do these things fire in order and more importantly, can a module cancel the process? To make this happen, modules would have to each consciously respect the cancel boolean and that's not really enforceable.

您可以在应用程序中添加一堆模块,并让它们在这三个或更多“事件”中侦听。 然后您需要问自己,这些事情是否按顺序触发,更重要的是,模块可以取消该过程吗? 为了实现这一点,模块必须各自自觉地遵守cancel布尔值,并且它实际上不是可执行的。

A Module is passed a ModuleEvents in this example so they can hook up to the shared delegate. This is called a multi-cast delegate because if I call it, EVERYONE gets called:

在此示例中,向模块传递了ModuleEvents,以便它们可以连接到共享委托。 这被称为多播委托,因为如果我调用它,那么所有人都会被调用:

public interface IAcmeModule
{
void Initialize(ModuleEvents events);
}

public class ArchiveModule : IAcmeModule
{
void IAcmeModule.Initialize(ModuleEvents events)
{
events.PostProcessData += events_PostProcessData;
}

void events_PostProcessData(PostProcessDataEventArgs e)
{
// perform archive functionality with processed data
}
}

You'd call the multicast-delagate like this

你会这样称呼多播延迟

CheckDataSourceEventArgs o_EventArgs =  new CheckDataSourceEventArgs(s_Source);
o_FilterEvents.CheckDataSource.Invoke(o_EventArgs);

Or, you could spin through the delegates and invoke them yourself, checking the cancel flag and stopping the whole thing yourself. Miguel's got a nice sample app and PPT with lots more code that illustrates this point well.

或者,您可以遍历委托并自己调用它们,检查cancel标志并自己停止整个事情。 Miguel有一个不错的示例应用程序和PPT,其中有很多代码很好地说明了这一点。

Then you can take all these concepts and put them together into a single extensible app with providers, plugins, modules that all work together. I like to keep all my Interfaces separated in another assembly and version them slowly, only when the contract changes. 

然后,您可以采用所有这些概念并将它们与提供程序,插件和模块一起使用的单个可扩展应用程序组合在一起。 我喜欢将所有接口保持在另一个程序集中,并仅在合同更改时才慢慢对其进行版本控制。

Windows Live编写器 (Windows Live Writer)

Windows Live Writer is what I use to post to my blog. I'm typing in it right now. If you're using an admin web page to post to your blog, stop. Go download it now, I'll wait here. Ok, it's got three kinds of extensibility (from MSDN):

Windows Live Writer是我用来发布到博客的工具。 我正在输入。 如果您使用的是管理员的网页张贴到您的博客停止 立即下载,我将在这里等待。 好的,它具有三种可扩展性(来自MSDN ):

  • Application API, for launching Writer to create new posts or "Blog This" items for links, snippets, images, and feed items.

    应用程序API,用于启动Writer来创建新帖子或“ Blog This”项,以用于链接,摘要,图像和feed项目。
  • Content Source Plugin API, for extending the capabilities of Writer to insert, edit, and publish new types of content.

    Content Source Plugin API,用于扩展Writer的功能,以插入,编辑和发布新类型的内容。
  • Provider Customization API, for customizing both the capabilities of Writer as well as adding new capabilities to the Writer user interface.

    提供程序自定义API,用于自定义Writer的功能以及向Writer用户界面添加新功能。

Let's handle the last bullet, first, the Provider Customization API. Extensibility can also mean extending an application without using code at all. Check out how to extend the Windows Live Writer UI using only an XML file for DasBlog. No code was required. Sometimes, just a thoughtful XML file provides the extensibility points you need.

让我们处理最后一个项目符号,首先是提供商自定义API。 可扩展性也意味着无需使用代码即可扩展应用程序。 了解如何仅使用DasBlog的XML文件扩展Windows Live Writer UI 。 不需要代码。 有时,只有一个周到的XML文件可以提供您所需的可扩展性点。

The Content Source Plugin API is a slick thing, letting you add your own "Insert" commands directly to WLW. There's 85 plugins at the time of this writing in the gallery.

Content Source Plugin API的用法很巧妙,您可以将自己的“ Insert”命令直接添加到WLW。 在撰写本文时,库中有85个插件

This time last year I took Travis's CueCat and make a CueCat Windows Live Writer plugin so I could more quickly post my Monthly Reading List (which tragically, ended up being yearly out of laziness. Note to self: Self, post a monthly reading list.)

去年的这个时候,我带了Travis的CueCat并制作了CueCat Windows Live Writer插件,这样我可以更快地发布我的每月阅读列表(可悲的是,最终由于懒惰而每年出现。自我提醒自我,发布每月阅读列表。 )

Anyway, I wrote an article for Coding4Fun and blogged as well. The Plugin looked like this:

无论如何,我为Coding4Fun写了一篇文章,同时也写了博客。 该插件如下所示:

Windows Live Writer has a cool model for Plugins. There's the potential for adding lots of functionality. You're adding a link and picture to the main UI, an undefined number of forms, plus you'll need storage, and you'll want to insert HTML into the main Editor window. This could potentially be a complex plugin model, but Joe Cheng and the others on the team made it, IMHO, very easy.

Windows Live Writer有一个很棒的插件模型。 有可能添加许多功能。 您要在主UI中添加链接和图片,数量不确定的表单,此外还需要存储,并且要将HTML插入主Editor窗口。 这可能是一个复杂的插件模型,但是Joe Cheng和团队中的其他人使它变得非常简单,恕我直言。

How does the plugin model for WLW enable all this? The code for a plugin speaks volumes:

WLW的插件模型如何实现所有这些功能? 插件的代码可以说明问题:

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using WindowsLive.Writer.Api;

namespace AmazonLiveWriterPlugin
{
[WriterPlugin("605EEA63-B74B-4e9d-A290-F5E9E8229FC1", "Amazon Links with CueCat",
ImagePath = "Images.CueCat.png",
PublisherUrl = "http://www.hanselman.com",
Description = "Amazon Links with a CueCat.")]
[InsertableContentSource("Amazon Links")]
public class Plugin : ContentSource
{
public override System.Windows.Forms.DialogResult CreateContent(System.Windows.Forms.IWin32Window dialogOwner, ref string newContent)
{
using(InsertForm form = new InsertForm())
{
form.AmazonAssociatesID = this.Options[AMAZONASSOCIATESID];
form.AmazonWebServicesID = this.Options[AMAZONWEBSERVICESID];
DialogResult result = form.ShowDialog();
if (result == DialogResult.OK)
{
this.Options[AMAZONASSOCIATESID] = form.AmazonAssociatesID;
this.Options[AMAZONWEBSERVICESID] = form.AmazonWebServicesID;
Product p = Decoder.Decode(form.CueCatData);
AmazonBook book = AmazonBookPopulator.CreateAmazonProduct(p, form.AmazonWebServicesID);
string associatesId = form.AmazonAssociatesID.Trim();
string builtAmazonUrl = removed for cleanliness";
newContent = string.Format(builtAmazonUrl, book.ID, associatesId, book.Title, book.Author);
}
return result;
}
}
}
}

Their plugin model uses a combination of things. There's attributes on the class that give details on where images are (embedded as resources), text, URLs and a GUID for uniqueness. There's a base class that makes a "Hello World" plugin a one-line affair. And there's a clean way to show any WinForm, show it and tell WLW what the result was. The HTML is returned by reference in newContent while the return value was a standard DialogResult. If you need storage for state, they pass in a simple dictionary-like Interface to your plugin when you're initialized. As a plugin, you don't need to sweat storage, or how you appear in the list of plugins, you just focus on your main dialog.

他们的插件模型使用了多种组合。 该类上的属性提供有关图像位置(作为资源嵌入),文本,URL和唯一性的GUID的详细信息。 有一个基类使“ Hello World”插件成为一站式事务。 有一种干净的方法可以显示任何WinForm,将其显示并告诉WLW结果是什么。 HTML是通过newContent中的引用返回的,而返回值是标准的DialogResult。 如果您需要存储状态,则在初始化时,它们会向您的插件传递一个类似于字典的简单接口。 作为一个插件,您无需花太多精力,也不用出现在插件列表中,您只需关注主对话框即可。

Do you have any cool examples of elegant, cool, convenient, clever extensibility mechanisms?

您有优雅,酷,方便,聪明的可扩展性机制的出色示例吗?

翻译自: https://www.hanselman.com/blog/the-weekly-source-code-24-extensibility-edition-plugins-providers-attributes-addins-and-modules-in-net

知乎周源微信

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值