【WCF】自動加載WCF Library (IIS Host)

在之前這篇 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 上進行處理:


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()); 
  }



發佈了143 篇原創文章 · 獲贊 10 · 訪問量 127萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章