C#WCF插件的设计和实现
我想得到一些建议。 我正在开发一个系统,它将在运行时加载插件并要求它们通过WCF端点可用。
我将有一个仅用于配置的MVC 3 Web应用程序,以及将加载不同插件的类库(核心)。
我会很感激如何解决这个问题。 我想加载插件,然后能够创建一个在IIS 7中注册的WCF端点,以访问该插件。
提前致谢 :)
使用Darko动态IIS托管的WCF服务工作的衍生产品,您可以实现您想要的function。 让我们从我们可能想要托管的示例服务开始,我们将其称为IMessageBroker
,它的合同很简单:
[ServiceContract] public interface IMessageBroker { [OperationContract] string Send(string message); }
我们将此合同用于服务和MEF出口/import。 我们还将定义一些额外的元数据:
public interface IMessageBrokerMetadata { public string Name { get; } public string Channel { get; } }
由于这是一个简单的项目,我会欺骗并使用一个简单的静态类来管理用于组成部分的MEF CompositionContainer
:
public static class MEF { private static CompositionContainer container; private static bool initialised; public static void Initialise() { var catalog = new DirectoryCatalog("bin"); container = new CompositionContainer(catalog); initialised = true; } public static CompositionContainer Container { get { if (!initialised) Initialise(); return container; } } }
为了能够动态生成WCF服务,我们需要创建一个ServiceHostFactory,它可以访问我们的组合容器来访问我们的类型,所以你可以这样做:
public class MEFServiceHostFactory : ServiceHostFactory { public override ServiceHostBase CreateServiceHost(string constructorString, System.Uri[] baseAddresses) { var serviceType = MEF.Container .GetExports() .Where(l => l.Metadata.Name == constructorString) .Select(l => l.Value.GetType()) .Single(); var host = new ServiceHost(serviceType, baseAddresses); foreach (var contract in serviceType.GetInterfaces()) { var attr = contract.GetCustomAttributes(typeof(ServiceContractAttribute), true).FirstOrDefault(); if (attr != null) host.AddServiceEndpoint(contract, new BasicHttpBinding(), ""); } var metadata = host.Description.Behaviors .OfType() .FirstOrDefault(); if (metadata == null) { metadata = new ServiceMetadataBehavior(); metadata.HttpGetEnabled = true; host.Description.Behaviors.Add(metadata); } else { metadata.HttpGetEnabled = true; } return host; } }
实质上, constructorString
参数用于传入我们想要的特定服务的元数据名称。 接下来,我们需要处理这些服务的定位。 我们现在需要的是VirtualPathProvider
,我们可以通过VirtualFile
动态创建实例。 提供者看起来像:
public class ServiceVirtualPathProvider : VirtualPathProvider { private bool IsServiceCall(string virtualPath) { virtualPath = VirtualPathUtility.ToAppRelative(virtualPath); return (virtualPath.ToLower().StartsWith("~/services/")); } public override VirtualFile GetFile(string virtualPath) { return IsServiceCall(virtualPath) ? new ServiceFile(virtualPath) : Previous.GetFile(virtualPath); } public override bool FileExists(string virtualPath) { if (IsServiceCall(virtualPath)) return true; return Previous.FileExists(virtualPath); } public override System.Web.Caching.CacheDependency GetCacheDependency(string virtualPath, System.Collections.IEnumerable virtualPathDependencies, DateTime utcStart) { return IsServiceCall(virtualPath) ? null : Previous.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart); } }
我们正在做的是将对/Services/
任何调用映射到我们的MEF派生端点。 该服务需要一个虚拟文件,这是我们将它们联系在一起的地方:
public class ServiceFile : VirtualFile { public ServiceFile(string virtualPath) : base(virtualPath) { } public string GetName(string virtualPath) { string filename = virtualPath.Substring(virtualPath.LastIndexOf("/") + 1); filename = filename.Substring(0, filename.LastIndexOf(".")); return filename; } public override Stream Open() { var stream = new MemoryStream(); var writer = new StreamWriter(stream); writer.Write("<%@ ServiceHost Language=\"C#\" Debug=\"true\" Service=\"" + GetName(VirtualPath) + "\" Factory=\"Core.MEFServiceHostFactory, Core\" %>"); writer.Flush(); stream.Position = 0; return stream; } }
虚拟文件将从虚拟路径中分解元数据名称,其中/Services/SampleMessageBroker.svc
– > SampleMessageBroker
。 然后,我们生成一些标记,表示使用Service="SampleMessageBroker"
的.svc
文件的标记。 该参数将传递给MEFServiceHostFactory
,我们可以在其中选择端点。 因此,给出一个样本端点:
[Export(typeof(IMessageBroker)), ExportMetadata("Name", "SampleMessageBroker"), ExportMetadata("Channel", "Greetings")] public class SampleMessageBroker : IMessagerBroker { public string Send(string message) { return "Hello! " + message; } }
我们现在可以在/Services/SampleMessageBroker.svc
中动态访问它。 您可能想要做的是提供静态服务,该服务允许您集成哪些端点可用,并将其反馈给您的消费客户端。
哦,不要忘记连接虚拟路径提供程序:
HostingEnvironment.RegisterVirtualPathProvider(new ServiceVirtualPathProvider());