有没有办法让VS2010发布向导在发布网站时复制App_offline.htm文件?

14

看了Hanselman "你正在做错", 我开始使用VS2010的Web Publish功能。

我真正缺少的是,有时在网站发布时会出现错误,因为该功能未将app_offline.htm文件复制到服务器上。

我不想开始使用MSDeploy脚本,因为我有几个网站,希望保持简单。

也许有一个简单的调整可以告诉向导复制然后删除该文件。


当我从VS2010进行发布时,它可以很好地复制app_offline.htm文件,然后在完成后将其删除。 - Chev
@Chevex:您能否评论一下您是如何做到的?文件放在哪里?您使用了哪些选项? - Eduardo Molteni
3
只有在选择“发布之前删除所有文件”时,才会推送app_offline。进行替换发布时,不会推送app_offline。 - Josh
太棒了!Josh说对了。我没有注意到,但他是正确的。勾选“删除现有文件”框,它就会执行这个操作。然而,请记住,当你不需要时删除现有文件会严重降低发布速度。另外,请记住,此选项确实会按照字面意思删除目标位置的所有内容。 - Chev
这就是为什么我本地发布(选择删除)并使用Beyond Compare仅推送更改的文件。在推送过程中忽略任何错误。如果您正确进行推送,用户在更新期间应该只有几秒钟的“停机”窗口。在生产环境中,我们通过FTP到暂存区,然后在本地复制到机器上。对于一个项目,我们使用批处理文件来推送app_offline,执行复制,然后删除该文件。 OP说他不想使用脚本,但是值得花费精力来拥有定制和有效的部署解决方案。 - Josh
显示剩余6条评论
3个回答

2
这个问题是App_Offline in MSBuild Remote Web Deploy的重复,但我已经在下面贴出了答案。MSDeploy v3直接支持app_offline,但您需要在两端都安装MSDeploy v3。我们尚未更新VS Web Publish体验以利用此功能,但我们将在以后的更新中进行更新。
我最近在http://sedodream.com/2012/01/08/HowToTakeYourWebAppOfflineDuringPublishing.aspx上撰写了这篇文章。它比应该简单得多,我正在为后续版本简化它。无论如何,我已将所有内容粘贴在此处供您参考。
我收到一封客户电子邮件,询问他们如何在从Visual Studio发布时将其Web应用程序/站点下线。将您的网站离线的简单方法是在网站根目录中放置一个名为app_offline.htm的文件。有关更多信息,请阅读ScottGu的文章,在下面的资源部分中提供了链接。不幸的是,Web Deploy本身不支持此功能。如果您希望Web Deploy(也称为MSDeploy)本地支持此功能,请在http://aspnet.uservoice.com/forums/41199-general/suggestions/2499911-take-my-site-app-offline-during-publishing上进行投票。
由于Web Deploy不支持此功能,这将会更加困难,并且需要我们执行以下步骤:
  1. 发布 app_offline.htm
  2. 发布应用程序,并确保app_offline.htm包含在要发布的有效负载中
  3. 删除app_offline.htm
1将在发布过程开始之前使应用程序离线。 2将确保当我们发布时,app_offline.htm不会被删除(因此保持应用程序离线)。 3将删除app_offline.htm并使站点恢复在线状态。
现在我们知道了需要做什么,让我们看一下实现。首先是容易的部分。在您的Web应用程序项目(WAP)中创建一个名为app_offline-template.htm的文件。这将成为您目标服务器上的app_offline.htm文件。如果您将其留空,则用户将收到一条通用消息,说明该应用程序已离线,但最好的方法是在该文件中放置静态HTML(没有ASP.NET标记),让用户知道站点将会恢复,并提供其他您认为对用户有关联的信息。添加此文件时,您应该在属性网格中将Build Action更改为None。这将确保此文件本身不被发布/打包。由于文件以.htm结尾,它默认会被发布。请参见下面的图像。
现在是困难的部分。对于Web应用程序项目,我们有一个钩子来进行发布/打包过程,我们称之为“wpp.targets”。如果要扩展您的发布/打包过程,您可以在与项目文件本身相同的文件夹中创建一个名为{ProjectName}.wpp.targets的文件。这是我创建的文件,您可以将内容复制粘贴到wpp.targets文件中。我将解释重要部分,但希望发布整个文件以方便您阅读。注意:您可以从我的github存储库中获取此文件的最新版本,链接在下面的资源部分中。
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="InitalizeAppOffline">
    <!-- 
    This property needs to be declared inside of target because this is imported before
    the MSDeployPath property is defined as well as others -->
    <PropertyGroup>
      <MSDeployExe Condition=" '$(MSDeployExe)'=='' ">$(MSDeployPath)msdeploy.exe</MSDeployExe>
    </PropertyGroup>    
  </Target>

  <PropertyGroup>
    <PublishAppOfflineToDest>
      InitalizeAppOffline;
    </PublishAppOfflineToDest>
  </PropertyGroup>

  <!--
    %msdeploy% 
      -verb:sync 
      -source:contentPath="C:\path\to\app_offline-template.htm" 
      -dest:contentPath="Default Web Site/AppOfflineDemo/app_offline.htm"
  -->

  <!--***********************************************************************
  Make sure app_offline-template.htm gets published as app_offline.htm
  ***************************************************************************-->
  <Target Name="PublishAppOfflineToDest" 
          BeforeTargets="MSDeployPublish" 
          DependsOnTargets="$(PublishAppOfflineToDest)">
    <ItemGroup>
      <_AoPubAppOfflineSourceProviderSetting Include="contentPath">
        <Path>$(MSBuildProjectDirectory)\app_offline-template.htm</Path>
        <EncryptPassword>$(DeployEncryptKey)</EncryptPassword>
        <WebServerAppHostConfigDirectory>$(_MSDeploySourceWebServerAppHostConfigDirectory)</WebServerAppHostConfigDirectory>
        <WebServerManifest>$(_MSDeploySourceWebServerManifest)</WebServerManifest>
        <WebServerDirectory>$(_MSDeploySourceWebServerDirectory)</WebServerDirectory>
      </_AoPubAppOfflineSourceProviderSetting>

      <_AoPubAppOfflineDestProviderSetting Include="contentPath">
        <Path>"$(DeployIisAppPath)/app_offline.htm"</Path>
        <ComputerName>$(_PublishMsDeployServiceUrl)</ComputerName>
        <UserName>$(UserName)</UserName>
        <Password>$(Password)</Password>
        <EncryptPassword>$(DeployEncryptKey)</EncryptPassword>
        <IncludeAcls>False</IncludeAcls>
        <AuthType>$(AuthType)</AuthType>
        <WebServerAppHostConfigDirectory>$(_MSDeployDestinationWebServerAppHostConfigDirectory)</WebServerAppHostConfigDirectory>
        <WebServerManifest>$(_MSDeployDestinationWebServerManifest)</WebServerManifest>
        <WebServerDirectory>$(_MSDeployDestinationWebServerDirectory)</WebServerDirectory>
      </_AoPubAppOfflineDestProviderSetting>
    </ItemGroup>

    <MSdeploy
          MSDeployVersionsToTry="$(_MSDeployVersionsToTry)"
          Verb="sync"
          Source="@(_AoPubAppOfflineSourceProviderSetting)"
          Destination="@(_AoPubAppOfflineDestProviderSetting)"
          EnableRule="DoNotDeleteRule"
          AllowUntrusted="$(AllowUntrustedCertificate)"
          RetryAttempts="$(RetryAttemptsForDeployment)"
          SimpleSetParameterItems="@(_AoArchivePublishSetParam)"
          ExePath="$(MSDeployPath)" />
  </Target>

  <!--***********************************************************************
  Make sure app_offline-template.htm gets published as app_offline.htm
  ***************************************************************************-->
  <!-- We need to create a replace rule for app_offline-template.htm->app_offline.htm for when the app get's published -->
  <ItemGroup>
    <!-- Make sure not to include this file if a package is being created, so condition this on publishing -->
    <FilesForPackagingFromProject Include="app_offline-template.htm" Condition=" '$(DeployTarget)'=='MSDeployPublish' ">
      <DestinationRelativePath>app_offline.htm</DestinationRelativePath>
    </FilesForPackagingFromProject>

    <!-- This will prevent app_offline-template.htm from being published -->
    <MsDeploySkipRules Include="SkipAppOfflineTemplate">
      <ObjectName>filePath</ObjectName>
      <AbsolutePath>app_offline-template.htm</AbsolutePath>
    </MsDeploySkipRules>
  </ItemGroup>

  <!--***********************************************************************
  When publish is completed we need to delete the app_offline.htm
  ***************************************************************************-->
  <Target Name="DeleteAppOffline" AfterTargets="MSDeployPublish">
    <!--
    %msdeploy% 
      -verb:delete 
      -dest:contentPath="{IIS-Path}/app_offline.htm",computerName="...",username="...",password="..."
    -->
    <Message Text="************************************************************************" />
    <Message Text="Calling MSDeploy to delete the app_offline.htm file" Importance="high" />
    <Message Text="************************************************************************" />

    <ItemGroup>
      <_AoDeleteAppOfflineDestProviderSetting Include="contentPath">
        <Path>$(DeployIisAppPath)/app_offline.htm</Path>
        <ComputerName>$(_PublishMsDeployServiceUrl)</ComputerName>
        <UserName>$(UserName)</UserName>
        <Password>$(Password)</Password>
        <EncryptPassword>$(DeployEncryptKey)</EncryptPassword>
        <AuthType>$(AuthType)</AuthType>
        <WebServerAppHostConfigDirectory>$(_MSDeployDestinationWebServerAppHostConfigDirectory)</WebServerAppHostConfigDirectory>
        <WebServerManifest>$(_MSDeployDestinationWebServerManifest)</WebServerManifest>
        <WebServerDirectory>$(_MSDeployDestinationWebServerDirectory)</WebServerDirectory>
      </_AoDeleteAppOfflineDestProviderSetting>
    </ItemGroup>

    <!-- 
    We cannot use the MSDeploy/VSMSDeploy tasks for delete so we have to call msdeploy.exe directly.
    When they support delete we can just pass in @(_AoDeleteAppOfflineDestProviderSetting) as the dest
    -->
    <PropertyGroup>
      <_Cmd>"$(MSDeployExe)" -verb:delete -dest:contentPath="%(_AoDeleteAppOfflineDestProviderSetting.Path)"</_Cmd>
      <_Cmd Condition=" '%(_AoDeleteAppOfflineDestProviderSetting.ComputerName)' != '' ">$(_Cmd),computerName="%(_AoDeleteAppOfflineDestProviderSetting.ComputerName)"</_Cmd>
      <_Cmd Condition=" '%(_AoDeleteAppOfflineDestProviderSetting.UserName)' != '' ">$(_Cmd),username="%(_AoDeleteAppOfflineDestProviderSetting.UserName)"</_Cmd>
      <_Cmd Condition=" '%(_AoDeleteAppOfflineDestProviderSetting.Password)' != ''">$(_Cmd),password=$(Password)</_Cmd>
      <_Cmd Condition=" '%(_AoDeleteAppOfflineDestProviderSetting.AuthType)' != ''">$(_Cmd),authType="%(_AoDeleteAppOfflineDestProviderSetting.AuthType)"</_Cmd>
    </PropertyGroup>

    <Exec Command="$(_Cmd)"/>
  </Target>  
</Project>

1 发布 app_offline.htm

实现 #1 的方法包含在目标 PublishAppOfflineToDest 中。我们需要执行的 msdeploy.exe 命令是:

msdeploy.exe 
    -source:contentPath='C:\Data\Personal\My Repo\sayed-samples\AppOfflineDemo01\AppOfflineDemo01\app_offline-template.htm' 
    -dest:contentPath='"Default Web Site/AppOfflineDemo/app_offline.htm"',UserName='sayedha',Password='password-here',ComputerName='computername-here',IncludeAcls='False',AuthType='NTLM' -verb:sync -enableRule:DoNotDeleteRule

为了实现这一点,我将利用MSDeploy任务。在PublishAppOfflineToDest目标中,您可以看到通过为源和目标创建项目来实现此目的。
2.发布应用程序,并确保app_offline.htm包含在要发布的负载中。
这部分是通过片段完成的。
<!--***********************************************************************
Make sure app_offline-template.htm gets published as app_offline.htm
***************************************************************************-->
<!-- We need to create a replace rule for app_offline-template.htm->app_offline.htm for when the app get's published -->
<ItemGroup>
  <!-- Make sure not to include this file if a package is being created, so condition this on publishing -->
  <FilesForPackagingFromProject Include="app_offline-template.htm" Condition=" '$(DeployTarget)'=='MSDeployPublish' ">
    <DestinationRelativePath>app_offline.htm</DestinationRelativePath>
  </FilesForPackagingFromProject>

  <!-- This will prevent app_offline-template.htm from being published -->
  <MsDeploySkipRules Include="SkipAppOfflineTemplate">
    <ObjectName>filePath</ObjectName>
    <AbsolutePath>app_offline-template.htm</AbsolutePath>
  </MsDeploySkipRules>
</ItemGroup>

这里的FilesForPackagingFromProject项目值将把您的app_offline-template.htm转换为在发布处理的文件夹中的app_offline.htm。此外,它还有一个条件,只会在发布时而不是打包时发生。我们不希望app_offline-template.htm被打包(但如果确实被打包了也没什么大不了的)。

MsDeploySkiprules元素将确保app_offline-template.htm本身不会被发布。这可能不是必需的,但也不会有害。

3 删除app_offline.htm

现在我们已经发布了应用程序,我们需要从目标Web应用程序中删除app_offline.htm文件。msdeploy.exe命令如下:

%msdeploy% -verb:delete -dest:contentPath="{IIS-Path}/app_offline.htm",computerName="...",username="...",password="..."

这是在DeleteAppOffline目标中实现的。因为我已经包含了AfterTargets=”MSDeployPublish”属性,所以此目标将在发布后自动执行。在该目标中,您可以看到我直接构建了msdeploy.exe命令,看起来像MSDeploy任务不支持删除动词。

如果您尝试此操作,请告诉我是否遇到任何问题。我正在考虑从中创建一个Nuget包,以便您可以安装该包。那需要一些工作,请告诉我您是否感兴趣。

资源

  1. 我的AppOffline wpp.targets文件的最新版本
  2. ScottGu关于app_offline.htm的博客

感谢您详细的回答,很高兴知道您正在开发一个简化版本! - Eduardo Molteni
看起来这个几乎可以工作,但是我收到了ERROR_USER_NOT_ADMIN 401未经授权的错误。通常这意味着凭据不正确。查看它正在使用的命令,它没有将密码传递给msdeploy。我该怎么办? - James Hancock

2

只有在选择“在发布之前删除所有文件”时才会推送app_offline.htm文件。在进行替换发布时,它不会推送app_offline.htm文件。


这是一个不错的开始,因为它表明该工具支持推送文件。如果我们能找到一种方法,在您仅复制新文件的常见选项下使其正常工作,那就太好了... - Eduardo Molteni

1

Web Deploy(MSDeploy)v3 {{link1:支持在部署期间添加app_offline.htm文件}},但它对您的目的有两个限制:

  • 它使用自己的app_offline.htm模板(它支持自定义模板,但不起作用
  • Web发布管道(基于Web Deploy的MSBuild堆栈)不支持向MSDeploy传递自定义规则,这排除了使用WPP。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接