最近几个月我一直在使用Windows服务工作,而且事实证明,Windows服务是调试,部署,更新和维护的重要因素。获取服务设置,调试和更新的过程是一项重要的工作,必须进行广泛的文档记录和/或自动化。在构建服务的大多数项目中,人们最终争先恐后地争取用于管理的正确“流程”。另一方面,Web应用程序的部署和维护是常见的,并且现在已经很好理解,因为我们一直在处理Web应用程序。Web Tools中内置了大量基础架构和工具,如Visual Studio,以促进流程。相比之下,Windows服务或任何自托管的东西似乎令人费解。

事实上,在最近的一篇博文中,我提到在最近的一个项目中,我一直在Windows服务中使用自托管SignalR,因为该应用程序实际上是一个“服务”,也需要发送大量的消息通过SignalR。但实际情况是,它也可能是一个IIS应用程序,其中包含在后台运行的服务组件。无论你是哪种方式,它都是带有内置Web服务器的Windows服务,或运行服务应用程序的IIS应用程序,它们都不遵循标准的服务或Web应用程序模板。

我个人更喜欢Web应用程序。在IIS内部运行我获得了IIS平台的所有好处,包括服务生命周期管理(崩溃和重启),受控关闭,整个安全基础设施,包括简单的证书支持,代码的热插拔以及从中直接发布到IIS的能力在Visual Studio中轻松完成。

由于这些好处,我们开始从自托管服务转移到ASP.NET Web应用程序。

ASP.NET即服务的缺失链接:自动加载

过去我曾想在ASP.NET中运行“类似服务”的应用程序,因为当你考虑它时,远程控制Web应用程序要容易得多。服务被锁定在启动/停止操作中,但如果您在Web应用程序内部托管,则可以编写自己的票证并从任何位置控制它。事实上,差不多10年前,我构建了一个在ASP.NET内部运行的后台调度应用程序,它运行良好,并且它仍在运行,正在完成它的工作。

现在,在IIS内部运行应用程序作为服务的棘手部分是如何启动IIS和ASP.NET,以便即使在重置应用程序池后您的“服务”仍然存在。7年前,我通过使用网络监视器(我自己的West Wind Web Monitor应用程序)伪造它。我正在运行以监视我的各种网站的正常运行时间,并让监视器每隔20秒ping我的“服务”以有效地保持ASP。 NET存活或在重新加载后重新启动它。我使用了一个简单的调度程序类,它还包含一些“自我重新加载”的逻辑Hacky肯定,但它可靠地工作。

幸运的是,一旦使用应用程序初始化模块启动应用程序池,就可以更轻松,更集成地让IIS启动ASP.NET。应用程序初始化模块基本上允许您打开应用程序池和站点/ IIS应用程序上的预加载,这实际上是在启动应用程序池后通过IIS管道发出请求。这意味着您的ASP.NET应用程序会立即生效,Application_Start会被激活,以确保您的应用始终保持运行状态。所有其他功能,如应用程序池回收和空闲时间后自动关闭仍然有效,但IIS将始终立即重新启动应用程序。

应用程序初始化入门

从IIS 8开始,Application Initialization是IIS功能集的一部分。对于IIS 7和7.5,可通过Web Platform Installer 单独下载使用IIS 8应用程序初始化是Windows或Windows Server Role Manager中的可选安装组件:

Windows功能

这是一个可选组件,因此请确保明确选择它。

应用程序初始化的IIS配置

需要在应用程序池和IIS应用程序级别上应用初始化。从IIS 8开始,可以通过IIS管理控制台进行这些设置。

从应用程序池开始:
AppPools

在这里,你需要设置两种自动开始其始终设置,并应设置为AlwaysRunning的STARTMODE。两者都必须设置 - 默认情况下,Start Automatically标志设置为true,并控制应用程序池本身的启动,而启动应用程序则需要Always Running标志。如果没有设置后一个标志,则站点设置无效。

现在,在站点/应用程序级别,您可以指定站点是否应预加载:

SiteConfig

Preload Enabled标志设置为true。

此时,ASP.NET应用程序应自动加载。如果你想要的只是让你的网站自动启动,这就是预加载网站所需的全部内容。

如果您想要更多地控制加载过程,可以在web.config文件中添加一些设置,以便在应用程序启动时显示静态页面。如果启动速度很慢,这可能很有用,因此,当用户摆弄拇指时,您可以显示静态HTML页面,而不是显示空白屏幕:

  < system.webServer >
    < applicationInitialization remapManagedRequestsTo = “ Startup.htm ”  
                                skipManagedModules = “ true ” >
      < add initializationPage = “ ping.ashx ” />
    </ applicationInitialization >
  </ system.webServer >

这允许您指定要在空运行中执行的页面。IIS基本上伪造请求并将其直接推送到IIS管道而不会访问网络。您指定一个页面,IIS将伪造对该页面的请求,在这种情况下ping.ashx只返回一个简单的OK字符串 - 即。快速的管道请求。应用程序池重新启动后立即运行此请求,当此请求正在运行且您的应用程序正在预热时,IIS可以显示备用静态页面 - 上面的Startup.htm。因此,当您点击网站上的链接时,您可以选择显示某种静态状态页面,而不是向用户显示空的加载页面,“我们会马上回来”。我不确定这是不是一个好主意,因为在某些情况下这可能会非常具有破坏性。我个人认为我更喜欢让人们等待,但至少得到他们应该回来的回应而不是随机页面。但是如果你需要它就在那里。

请注意,web.config内容是可选的。如果您不提供IIS,则会访问默认站点链接(/),即使在该请求结束时没有匹配的请求,它仍将通过IIS管道触发请求。理想情况下,您希望确保使用默认页面命中ASP.NET端点,或者通过指定initializationPage以确保ASP.NET实际受到攻击,因为IIS可能仅针对静态页面触发非托管请求(取决于您的方式)管道已配置)。

AppDomain重启怎么

除了IIS级别的完整工作进程回收之外,ASP.NET还必须处理AppDomain关闭,这可能由于各种原因而发生:

  • 文件在BIN文件夹中更新
  • Web部署到您的站点
  • web.config已更改
  • 硬应用程序崩溃

这些操作不会导致工作进程重新启动,但它们确实会导致ASP.NET卸载当前的AppDomain并启动新的AppDomain。由于上述功能仅适用于应用程序池重新启动,因此AppDomain重新启动也可能导致“ASP.NET服务”在后台停止处理。

为了使应用程序在AppDomain上循环运行,您可以在Application_End事件中使用简单的ping:

protected void Application_End()
{
    var client = new WebClient ();
    var url = App .AdminConfiguration.MonitorHostUrl + “ping.aspx” ;
    client.DownloadString(URL);
    Trace .WriteLine(“应用程序关闭Ping:” + url);
}

它会在管道关闭的最后将任何ASP.NET URL激活到当前站点,从而确保站点立即重新启动。

ApplicationHost.config中的手动配置

上面的UI对应于以下ApplicationHost.config设置。如果您使用的是IIS 7,则这些标志没有UI,因此您必须手动编辑它们。

将Application Initialization组件安装到IIS时,它应该将模块自动配置为ApplicationHost.config对我来说不幸的是,墨菲先生对我来说是最好的形式,模块注册没有发生,我不得不手动添加它。

< globalModules >
  < add name = “ ApplicationInitializationModule ”
        image = “ %windir% System32  inetsrv  warmup.dll ” />
</ globalModules >

很可能你不需要添加它,但如果事情不起作用,那么检查模块是否实际注册是值得的。

接下来,您需要配置ApplicationPool和Web站点。以下是ApplicationHost.config中的两个相关条目。

< system.applicationHost >
  < applicationPools >
    < 添加名称= “ 西风西风Web连接          AUTOSTART = STARTMODE = AlwaysRunning 
           managedRuntimeVersion = “ V4.0 ”
           managedPipelineMode = “ 集成” >
      < processModel identityType = “ LocalSystem ”
                     setProfileEnvironment = “ true ” />
    </ add >
  </ applicationPools >

  < sites >
    < site name = “ 默认网站” id = “ 1 ” >      
      < application path = “/ MPa.Workflow.WebQueueMessageManager ”
                     applicationPool = “ West Wind West Wind Web ConnectionpreloadEnabled = true > 
        < virtualDirectory path = “ / ”
                           physicalPath = “ C: Clients  ... ” />
      </ application >        
    </ site >
  </ sites >
</ system.applicationHost >
在应用程序池上,确保将autoStart和startMode标志分别设置为true和AlwaysRunning。在站点上,确保将preloadEnabled标志设置为true。

这就是你应该需要的。您仍然可以设置上述web.config设置。

ASP.NET即服务?

在我目前正在处理的特定应用程序中,我们有一个队列管理器,它作为独立服务运行,轮询数据库队列并选择作业并在多个线程上处理它们。该服务可以启动任意数量的线程,并在IIS运行时自己将这些线程保持活动状态。这些线程是新创建的线程,因此它们完全位于IIS线程池之外。为了使这项服务能够工作,它所需要的只是一个长时间运行的引用,它可以在应用程序的整个生命周期内保持活动状态。

在这个特定的应用程序中,有两个组件在后台运行在自己的线程上:一个调度程序,它运行各种计划任务并处理诸如拾取电子邮件以发送到IIS范围之外的事件和QueueManager。

以下是global.asax中的内容:

公共类Global :System.Web。HttpApplication { private static ApplicationScheduler scheduler; 私有静态ServiceLauncher 发射器; protected void Application_Start(object sender,EventArgs e) { // ping服务并确保它保持活动 scheduler = new ApplicationScheduler () { CheckFrequency = 600000 }; scheduler.Start(); launcher = new ServiceLauncher (); launcher.Start(); //注册所以关闭是受控的 HostingEnvironment .RegisterObject(启动器); }

}

通过将这些对象保持为在启动时仅设置一次的静态实例,它们可以在应用程序的生命周期中存活。除了我可以删除Windows服务接口(OnStart,OnStop,OnResume等)所需的各种覆盖之外,这些类中的代码与Windows服务代码基本相同。否则行为和操作非常相似。

在这个应用程序中,ASP.NET有两个目的:它充当SignalR的主机,并提供允许远程管理“服务”的管理界面。我可以通过非常轻松地关闭ApplicationScheduler来远程启动和停止服务。我也可以通过SignalR服务直接通过几个Web请求或(如我们现在所做)直接从队列中提取统计信息。

使用ASP.NET注册后台对象

另请注意HostingEnvironment.RegisterObject()的使用此函数向ASP.NET注册一个对象,让它知道它是一个后台任务,如果AppDomain关闭,应该通知它。RegisterObject()需要一个带有Stop()方法的接口,该方法被触发并允许代码响应关闭请求。以下是启动器上IRegisteredObject :: Stop()方法的样子:

public void Stop(bool immediate = false 
{
    LogManager .Current.LogInfo(“QueueManager Controller Stopped。” );

    Controller.StopProcessing();
    Controller.Dispose();
    Thread .Sleep(1500); //给一些背景线程
 
    HostingEnvironment .UnregisterObject(this );
}

实现IRegisterObject应该有助于AppDomain关闭的可靠性。感谢Justin Van Patten在推特上向我指出这一点。

RegisterObject()不是必需的,但我强烈建议在AppDomain关闭时,在后台处理所有干净关闭的任何对象控件上实现它。

测试出来

我仍然处于这个特定服务的测试阶段,看看是否有任何副作用。但到目前为止看起来并不像。通过大约50行代码,我能够将Windows服务启动替换为Web启动 - 其他一切都按原样工作。值得一提的是SignalR 2.0的oWin托管,因为新的基于oWin的托管不需要代码更改,只需要几个配置文件设置和汇编指令,指向SignalR启动类。甜!

与自托管相比,似乎SignalR在IIS内运行速度明显更快。由于预加载,启动感觉更快。

启动和停止“服务”

由于应用程序作为Web服务器运行,因此可以轻松地使用Web界面来启动和停止在服务内部运行的服务。对于我们的队列管理器,SignalR服务和前端监控应用程序有一个用于切换队列的播放和停止按钮。

如果您想要更多的管理控制并使其更像Windows服务,您还可以从命令行显式停止应用程序池,这相当于停止和重新启动服务。

要从命令行启动和停止,您可以使用IIS appCmd工具。停止:

>%windir% system32 inetsrv appcmd stop apppool /apppool.name:"Weblog“

并开始

>%windir% system32 inetsrv appcmd start apppool /apppool.name:"Weblog“

请注意,当您明确强制AppPool停止在UI(在ApplicationPools页面上使用Start / Stop)或通过命令行工具运行时,应用程序池将不会立即自动重新启动。您必须手动启动它。

有什么不喜欢的?

在IIS中运行后台服务肯定有很多好处,但是...... ASP.NET应用程序在内存占用方面确实有更多的开销,启动时间稍微慢一些,但通常对于服务器应用程序来说这不是什么大问题。如果应用程序稳定,则服务应该启动并无限期地保持运行。很多时候,这种服务接口可以简单地附加到现有的Web应用程序,或者如果需要将可伸缩性卸载到它自己的Web服务器上。

更容易使用

但这里的最终好处是使用Web应用程序而不是服务更容易。在开发过程中,我可以简单地通过点击网站上的页面来关闭自动启动功能并通过IIS按需启动服务。如果我想关闭IISRESET -stop将足够轻松地关闭服务。然后我可以在任何我想要的地方附加一个调试器,这就像任何其他ASP.NET应用程序一样。是的,你最终会在后台线程上进行调试,但是Visual Studio处理得很好,如果你留在一个线程上,这与调试任何其他代码没什么不同。

摘要

使用ASP.NET运行后台服务操作可能不是一个超常见的场景,但它可能应该是构建服务时仔细考虑的事情。许多应用程序具有类似于服务的功能以及Application Initialization模块的自动启动功能,因此很容易将此功能构建到ASP.NET中。特别是当与SignalR的通知功能结合使用时,创建丰富的服务变得非常非常容易,这些服务也可以轻松地将其状态传达给外界。

无论是现有的应用程序需要一些后台处理来安排相关任务,还是只是创建一个单独的站点来托管您的服务,这很容易做到,您可以利用您已经用于其他Web项目的相同工具链。如果你有很多服务项目,值得考虑......给它一些思考......