.Net Self Hosting 的幾種方式

寫在前面:

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。java

那麼今天咱們來聊一聊另外的兩種能夠self host的解決方案:git

 第一種方式: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服務器。因此咱們能夠得出下面的結論:github

OWIN的做用就是經過引入一組抽象接口,解耦了.NET Web應用程序與Web服務器,再次體現了接口的重要性。web

而咱們知道在軟件開發中,每次解耦都是一次很大的進步。express

更近一層咱們能夠理解爲:OWIN是對ASP.NET Runtime的抽象。它將應用與服務器解耦, 使得便攜式 .NET Web 應用以及跨平臺的願望成爲現實, 標準的 OWIN 應用能夠在任何OWIN 兼容的服務器上運行,再也不依賴與 Windows 和 IIS,咱們更能夠不用裝一大堆笨重的IDE(如 visual studio)來開發web應用程序,也再也不那麼的依賴於IIS去Host咱們的程序。 咱們能夠用下面的一張圖來表示它究竟能夠作什麼:windows

 具體使用以下:ruby

新建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的同窗的對它並不陌生。 app

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服務。

寫在最後:

可能不只限於這兩種方式,我只是把我最近使用到的兩種方式分享給出來,若是你們有更好的方式,歡迎交流分享。 

相關文章
相關標籤/搜索