寫在前面:
IIS是Windows平台非常關鍵的組件,它是微軟自帶的Web服務器,可以很方便的幫助我們運行起一個網站,WebApi等服務,提供給外部來訪問。即使它被很多java或者ruby的同學各種鄙視,被.Net平台的同學們吐槽性能不好,不夠靈活,部署受限等等,它依然在默默的幫助我們非常輕松的構建起一個Web應用。在.Net Core中微軟提供了更為強大的Web服務器 Kestrel ,它 是一個跨平台ASP.NET Core 的 web 服務器基於libuv,跨平台的異步 I/O 庫。它可以單獨使用來host一個web服務,也可以與反向代理服務器(如 IIS、Nginx 或 Apache)結合使用。 反向代理服務器接收到來自 Internet 的 HTTP 請求,並在進行一些初步處理后將這些請求轉發到 Kestrel。
那么今天我們來聊一聊另外的兩種可以self host的解決方案:
第一種方式:Owin
Owin 是 Open Web Interface for .NET 的簡稱,從字面意思解釋可以看出OWIN是針對.NET平台的開放Web接口。那Web接口是誰和誰之間的接口呢?是Web應用程序與Web服務器之間的 接口,OWIN就是.NET Web應用程序與Web服務器之間的接口。為什么需要這樣一個接口呢?因為.NET Web應用程序是運行於Web服務器之中的,.NET Web應用程序需要通過Web服務器接收用戶的請求,並且通過Web服務器將響應內容發送用戶。如果沒有這樣一個接口,.NET Web應用程序就要依賴於所運行的具體Web服務器,比如ASP.NET應用程序要依賴於IIS。有了這個接口,ASP.NET應用程序只需依賴這個抽象接口,不用關心所運行的Web服務器。所以我們可以得出下面的結論:
OWIN的作用就是通過引入一組抽象接口,解耦了.NET Web應用程序與Web服務器,再次體現了接口的重要性。
而我們知道在軟件開發中,每次解耦都是一次很大的進步。
更近一層我們可以理解為:OWIN是對ASP.NET Runtime的抽象。它將應用與服務器解耦, 使得便攜式 .NET Web 應用以及跨平台的願望成為現實, 標准的 OWIN 應用可以在任何OWIN 兼容的服務器上運行,不再依賴與 Windows 和 IIS,我們更可以不用裝一大堆笨重的IDE(如 visual studio)來開發web應用程序,也不再那么的依賴於IIS去Host我們的程序。 我們可以用下面的一張圖來表示它究竟可以做什么:
具體使用如下:
新建EventsController 繼承自:System.Web.Http.ApiController
public class EventsController : ApiController { [Authorize] [Route("events")] public IEnumerable<Event> Get() { return GetAllEventsFromRepo(); } [Route("events/{id}")] public Event GetById(Guid id) { return GetAllEventsFromRepo().First(x => x.EventId == id); } [Route("events")] public IEnumerable<Event> GetByType(string type) { return GetAllEventsFromRepo().Where(x => x.EventType.Equals(type, StringComparison.InvariantCultureIgnoreCase)); } [Route("events")] public HttpResponseMessage Post(Event @event) { if (@event == null) { return new HttpResponseMessage(HttpStatusCode.BadRequest); } return new HttpResponseMessage(HttpStatusCode.Created); } private IEnumerable<Event> GetAllEventsFromRepo() { return new List<Event> { new Event { EventId = Guid.Parse("45D80D13-D5A2-48D7-8353-CBB4C0EAABF5"), Timestamp = DateTime.Parse("2014-06-30T01:37:41.0660548"), EventType = "SearchView" }, new Event { EventId = Guid.Parse("83F9262F-28F1-4703-AB1A-8CFD9E8249C9"), Timestamp = DateTime.Parse("2014-06-30T01:37:52.2618864"), EventType = "DetailsView" }, new Event { EventId = Guid.Parse("3E83A96B-2A0C-49B1-9959-26DF23F83AEB"), Timestamp = DateTime.Parse("2014-06-30T01:38:00.8518952"), EventType = "SearchView" } }; } }
然后新建一個Startup.cs的class,我們可以看到這里體現了Middleware(中間件)的思想,即插即用,熟悉.Net Core的同學的對它並不陌生。
public class Startup { public void Configuration(IAppBuilder app) { var config = new HttpConfiguration(); config.MapHttpAttributeRoutes(); app.UseWebApi(config); var builder = new ContainerBuilder(); builder.RegisterApiControllers(typeof(EventsController).Assembly); var container = builder.Build(); app.UseAutofacMiddleware(container); app.UseAutofacWebApi(config); } }
上面代碼中的ContainerBuilder 是Autofac提供的功能,它可以讓我們動態的注冊Controller到容器中,還有一個非常重要的東西就是 HttpConfiguration,它用來表示 HttpServer 實例的配置。
然后我們只需要下面一句代碼就可以讓我們API 工作起來了:
WebApp.Start<TestStartup>("http://localhost:51502")
這樣通過 http://localhost:51502 地址就可以訪問我們的服務了,非常的簡單。
第二種方式:通過進程直接調用iisexpress.exe
iisexpress.exe我們很熟悉,它是windows平台自帶的IIS 的運行文件,默認路徑在: C:\Program Files\IIS Express 目錄下,我們可以在代碼中創建進程運行起這個exe就可以了。具體代碼如下:
public class IISExpress : IDisposable { /// <summary> /// Stores whether this instance has been disposed. /// </summary> private bool _isDisposed; /// <summary> /// Stores the IIS Express process. /// </summary> private Process _process; /// <summary> /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// </summary> public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// <summary> /// Starts IIS Express using the specified directory path and port. /// </summary> /// <param name="directoryPath"> /// The directory path. /// </param> /// <param name="port"> /// The port. /// </param> /// <param name="address"> /// The address. /// </param> public void Start(string directoryPath, int port, Uri address) { if (_process != null) { throw new InvalidOperationException("The IISExpress process is already running."); } if (address != null) { try { var request = (HttpWebRequest)WebRequest.Create(address); var webResponse = (HttpWebResponse)request.GetResponse(); if (webResponse.StatusCode == HttpStatusCode.OK) { return; } } catch (Exception ex) { Trace.WriteLine(ex); } } var iisExpressPath = DetermineIisExpressPath(); var arguments = string.Format(CultureInfo.InvariantCulture, "/path:\"{0}\" /port:{1}", directoryPath, port); var info = new ProcessStartInfo(iisExpressPath) { WindowStyle = ProcessWindowStyle.Hidden, ErrorDialog = true, LoadUserProfile = true, CreateNoWindow = false, UseShellExecute = false, Arguments = arguments }; var startThread = new Thread(() => StartIisExpress(info)) { IsBackground = true }; startThread.Start(); } /// <summary> /// Releases unmanaged and - optionally - managed resources. /// </summary> /// <param name="disposing"> /// <c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources. /// </param> protected virtual void Dispose(bool disposing) { if (_isDisposed) { return; } if (disposing) { if (_process != null) { // Free managed resources if (_process.HasExited == false) { SendStopMessageToProcess(_process.Id); _process.Close(); } _process.Dispose(); } } // Free native resources if there are any _isDisposed = true; } /// <summary> /// Determines the IIS express path. /// </summary> /// <returns> /// A <see cref="String" /> instance. /// </returns> private static string DetermineIisExpressPath() { string iisExpressPath; if (Environment.Is64BitOperatingSystem) { iisExpressPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86); } else { iisExpressPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); } iisExpressPath = Path.Combine(iisExpressPath, @"C:\Program Files\IIS Express\iisexpress.exe"); return iisExpressPath; } /// <summary> /// The send stop message to process. /// </summary> /// <param name="processId"> /// The process id. /// </param> private static void SendStopMessageToProcess(int processId) { try { for (var ptr = NativeMethods.GetTopWindow(IntPtr.Zero); ptr != IntPtr.Zero; ptr = NativeMethods.GetWindow(ptr, 2)) { uint num; NativeMethods.GetWindowThreadProcessId(ptr, out num); if (processId == num) { var handle = new HandleRef(null, ptr); NativeMethods.PostMessage(handle, 0x12, IntPtr.Zero, IntPtr.Zero); return; } } } catch (ArgumentException) { } } /// <summary> /// Starts the IIS express. /// </summary> /// <param name="info"> /// The info. /// </param> [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Required here to ensure that the instance is disposed.")] private void StartIisExpress(ProcessStartInfo info) { try { _process = Process.Start(info); _process.WaitForExit(); } catch (Exception) { Dispose(); } } /// <summary> /// The native methods. /// </summary> private static class NativeMethods { /// <summary> /// The get top window. /// </summary> /// <param name="hWnd"> /// The h wnd. /// </param> /// <returns> /// The <see cref="IntPtr"/>. /// </returns> [DllImport("user32.dll", SetLastError = true)] internal static extern IntPtr GetTopWindow(IntPtr hWnd); /// <summary> /// The get window. /// </summary> /// <param name="hWnd"> /// The h wnd. /// </param> /// <param name="uCmd"> /// The u cmd. /// </param> /// <returns> /// The <see cref="IntPtr"/>. /// </returns> [DllImport("user32.dll", SetLastError = true)] internal static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd); /// <summary> /// The get window thread process id. /// </summary> /// <param name="hwnd"> /// The hwnd. /// </param> /// <param name="lpdwProcessId"> /// The lpdw process id. /// </param> /// <returns> /// The <see cref="uint"/>. /// </returns> [DllImport("user32.dll", SetLastError = true)] internal static extern uint GetWindowThreadProcessId(IntPtr hwnd, out uint lpdwProcessId); /// <summary> /// The post message. /// </summary> /// <param name="hWnd"> /// The h wnd. /// </param> /// <param name="Msg"> /// The msg. /// </param> /// <param name="wParam"> /// The w param. /// </param> /// <param name="lParam"> /// The l param. /// </param> /// <returns> /// The <see cref="bool"/>. /// </returns> [return: MarshalAs(UnmanagedType.Bool)] [DllImport("user32.dll", SetLastError = true)] internal static extern bool PostMessage(HandleRef hWnd, uint Msg, IntPtr wParam, IntPtr lParam); }
代碼比較簡單,大家都能看得懂,我們只需要指定需要host的文件目錄,訪問端口,以及公開Uri地址就可以了,這樣就能調用起IIS的服務,幫助我們host服務。
寫在最后:
可能不僅限於這兩種方式,我只是把我最近使用到的兩種方式分享給出來,如果大家有更好的方式,歡迎交流分享。