在之前这篇 Blog (
【WCF】自动加载WCF Library) 中介绍了如何在一个desktop应用中自动加载 WCF Library 的简单实现。后来我就想到如果能部署到 IIS 上,用 IIS Host 实现不就更方便嘛。正好最近学习 ASP.NET MVC 碰到这个类:
VirtualPathProvider 类 (它提供了一组方法,使 Web 应用程序可以从虚拟文件系统中检索资源。) ,一下子豁然开朗:通过这个类,可以将 svc 请求通过动态生成 .svc 服务文件的方式动态加载 WCF Library 提供服务。其中最关键的是:通过 VirtualPathProvider.GetFile 来返回
虚拟的 WCF 服务文件。
先上图:
上传 assembly
自动加载并迁移到 help 页面:
上传 dll 先放在一边,通过反射分析,我会将 url 跳转到类似: http://localhost/WcfLibs/WcfServiceLibrary1.Service1.svc 这个路径上。
作为规则:WcfServiceLibrary1 是上传的 dll 的名称,也是 Service1 的 namespace,而只有请求路径是指向 WcfLibs 这个目录上的,
才会进入我自定义的 VirtualPathProvider 上进行处理:
2. 映射虚拟WCF服务文件(WcfVirtualFile.cs)
在 WCF IIS Host 的时候,.net 是通过对 .svc (markup)文件映射到WCF Host 上进行处理的。
3. 提供自动加载的 WCF Host Service Factory (WcfVirtualServiceHostFactory.cs)
constructorString 是唯一的信息来源( 来自 Wcf 服务文件中 Markup 中的 "Service"),结合之前的 Constants.AbsolutePath ,利用 Assembly.LoadFile(LoadFrom) 来动态加载 Wcf Library Assembly。
最后,别忘记要在 Global.asax 的 Application_Start 里对自定义的 VirtualPathProvider 注册。
先上图:

上传 assembly

自动加载并迁移到 help 页面:

上传 dll 先放在一边,通过反射分析,我会将 url 跳转到类似: http://localhost/WcfLibs/WcfServiceLibrary1.Service1.svc 这个路径上。
作为规则:WcfServiceLibrary1 是上传的 dll 的名称,也是 Service1 的 namespace,而只有请求路径是指向 WcfLibs 这个目录上的,
才会进入我自定义的 VirtualPathProvider 上进行处理:
1. WCF虚拟路径解析(WcfVirtualPathProvider.cs)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Hosting;
using System.Web.Caching;
using System.Collections;
namespace DynamicWcfApp
{
public class WcfVirtualPathProvider : VirtualPathProvider
{
public override bool FileExists(string virtualPath)
{
var appRelativeVirtualPath = ToAppRelativeVirtualPath(virtualPath);
if (IsVirtualFile(appRelativeVirtualPath))
{
return true;
}
else
{
return Previous.FileExists(virtualPath);
}
}
public override System.Web.Hosting.VirtualFile GetFile(string virtualPath)
{
var appRelativeVirtualPath = ToAppRelativeVirtualPath(virtualPath);
if (IsVirtualFile(appRelativeVirtualPath))
{
var servicePath = VirtualPathUtility.MakeRelative(Constants.VirtualWcfDirectoryName + "/", virtualPath);
if (servicePath.EndsWith(".svc"))
servicePath = servicePath.Substring(0, servicePath.IndexOf(".svc"));
// check
if (!servicePath.Contains("."))
return Previous.GetFile(virtualPath);
var assemblyLocation = System.IO.Path.Combine(Constants.AbsolutePath, servicePath.Split('.')[0] + ".dll");
if (!System.IO.File.Exists(assemblyLocation))
return Previous.GetFile(virtualPath);
return new WcfVirtualFile(virtualPath, servicePath, typeof(WcfVirtualServiceHostFactory).FullName);
}
else
{
return Previous.GetFile(virtualPath);
}
}
public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart)
{
var appRelativeVirtualPath = ToAppRelativeVirtualPath(virtualPath);
if (IsVirtualFile(appRelativeVirtualPath) || IsVirtualDirectory(appRelativeVirtualPath))
{
return null;
}
else
{
return Previous.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
}
}
private bool IsVirtualFile(string appRelativeVirtualPath)
{
if (appRelativeVirtualPath.StartsWith(Constants.VirtualWcfDirectoryName + "/", StringComparison.OrdinalIgnoreCase))
{
return true;
}
return false;
}
private bool IsVirtualDirectory(string appRelativeVirtualPath)
{
return appRelativeVirtualPath.Equals(Constants.VirtualWcfDirectoryName, StringComparison.OrdinalIgnoreCase);
}
private string ToAppRelativeVirtualPath(string virtualPath)
{
var appRelativeVirtualPath = VirtualPathUtility.ToAppRelative(virtualPath);
if (!appRelativeVirtualPath.StartsWith("~/"))
{
throw new HttpException("Unexpectedly does not start with ~.");
}
return appRelativeVirtualPath;
}
}
public class Constants
{
public static readonly string VirtualWcfDirectoryName = "~/WcfLibs";
public static readonly string AbsolutePath = HttpContext.Current.Server.MapPath("~/WcfLibs");
}
}
另外提醒各位看官注意:在 Provider 里
无法直接使用 HttpContext.Current.Server.MapPath 来寻找绝对路径(HttpContext.Current 是 null),因此提前用 Constants 在启动时保存了绝对路径的信息。
2. 映射虚拟WCF服务文件(WcfVirtualFile.cs)
在 WCF IIS Host 的时候,.net 是通过对 .svc (markup)文件映射到WCF Host 上进行处理的。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Hosting;
using System.IO;
using System.Globalization;
using System.Web;
namespace DynamicWcfApp
{
public class WcfVirtualFile : VirtualFile
{
private string _service;
private string _factory;
public WcfVirtualFile(string vp, string service, string factory)
: base(vp)
{
_service = service;
_factory = factory;
}
public override Stream Open()
{
var ms = new MemoryStream();
var tw = new StreamWriter(ms);
tw.Write(string.Format(CultureInfo.InvariantCulture,
"<%@ServiceHost language=c# Debug=\"true\" Service=\"{0}\" Factory=\"{1}\"%>",
HttpUtility.HtmlEncode(_service), HttpUtility.HtmlEncode(_factory)));
tw.Flush();
ms.Position = 0;
return ms;
}
}
}
3. 提供自动加载的 WCF Host Service Factory (WcfVirtualServiceHostFactory.cs)
constructorString 是唯一的信息来源( 来自 Wcf 服务文件中 Markup 中的 "Service"),结合之前的 Constants.AbsolutePath ,利用 Assembly.LoadFile(LoadFrom) 来动态加载 Wcf Library Assembly。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Reflection;
using System.ServiceModel.Activation;
using System.ServiceModel;
using System.ServiceModel.Description;
namespace DynamicWcfApp
{
public class WcfVirtualServiceHostFactory : ServiceHostFactory
{
public override System.ServiceModel.ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)
{
var assmblyName = constructorString.Split('.')[0] + ".dll";
var serviceName = constructorString;
assmblyName = System.IO.Path.Combine(Constants.AbsolutePath, assmblyName);
var assembly = Assembly.LoadFile(assmblyName);
var serviceType = assembly.GetType(serviceName);
var host = new ServiceHost(serviceType, baseAddresses);
foreach (var iface in serviceType.GetInterfaces())
{
var attr = (ServiceContractAttribute)Attribute.GetCustomAttribute(iface, typeof(ServiceContractAttribute));
if (attr != null)
host.AddServiceEndpoint(iface, new BasicHttpBinding(), "");
}
var metadataBehavior = new ServiceMetadataBehavior();
metadataBehavior.HttpGetEnabled = true;
host.Description.Behaviors.Add(metadataBehavior);
return host;
}
}
}
最后,别忘记要在 Global.asax 的 Application_Start 里对自定义的 VirtualPathProvider 注册。
HostingEnvironment.RegisterVirtualPathProvider(new WcfVirtualPathProvider());
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
HostingEnvironment.RegisterVirtualPathProvider(new WcfVirtualPathProvider());
}