在之前這篇 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());
}