AspNetCore.Docs icon indicating copy to clipboard operation
AspNetCore.Docs copied to clipboard

Shadow copying for ASP.NET Core + IIS feedback

Open Rick-Anderson opened this issue 2 years ago • 28 comments

Leave feedback for shadow copying for ASP.NET Core + IIS in this issue. @adityamandaleeka

What's new in 6.0 - Shadow copying in IIS

Rick-Anderson avatar Nov 04 '21 03:11 Rick-Anderson

The sample web.config in the documentation at https://docs.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-6.0?view=aspnetcore-6.0#additional-resources is incorrect.

"AspNetCoreModulev2" must have a capital V in V2 or you'll get the error:

HTTP Error 500.21 - Internal Server Error
Handler "aspNetCore" has a bad module "AspNetCoreModulev2" in its module list

That said, even after I correct that error, I just get a 500 error with no text. Tested with .NET 6 hosting bundle installed on Windows Server 2016 Datacenter. There is also no log file generated when uncommenting the debugFile and debugLevel settings.

UPDATE: I can also reproduce the 500 w/o an error message or logs being created on my locally installed IIS on Windows 11 with VS2022 installed. I installed .NET 6 hosting bundle after I reproduced it, just in case, still getting 500.

My entire web.config:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <location path="." inheritInChildApplications="false">
    <system.webServer>
      <handlers>
        <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
      </handlers>
      <aspNetCore processPath="dotnet" arguments=".\MyAppNameHere.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout">
        <handlerSettings>
          <handlerSetting name="experimentalEnableShadowCopy" value="true" />
          <handlerSetting name="shadowCopyDirectory" value="../ShadowCopy/" />
          <handlerSetting name="debugFile" value=".\logs\aspnetcore-debug.log" />
          <handlerSetting name="debugLevel" value="FILE,TRACE" />
        </handlerSettings>
      </aspNetCore>
    </system.webServer>
  </location>
</configuration>

ScottRFrost avatar Nov 10 '21 21:11 ScottRFrost

This feature has been a life-saver for us. Simply put, since moving our web application to windows server 2022 and IIS 10, the traditional <EnableMsDeployAppOffline>True</EnableMsDeployAppOffline> in our webdeploy publish profile simply doesnt work. I mean, I can see the app_offline.htm going into the application directory, but we still get the classic "cant deploy, file is locked".

We have not been able to deploy to production without creating significant downtime, i.e. turn off the app pool and website in IIS, wait about 2 minutes for the file locking to stop, then publish the app.

If this feature were no to make it into the full .net core it would be really disappointing. Put another way, this setting gives us developers another option for deployment, and its not like there is a huge array of choice, I can only speak from my personal perspective and that of our company, but this feature is the single most important feature of ,net core, as being able to deploy apps with zero downtime (or at least virtually zero) is more important than an other feaure.

It could be argued "well, you just need to fix the issue with EnableMsDeployAppOffline", but thats the point, this shadow copying option means we have another choice.

Please keep it in, from me, other devs that havent discovered the same issues as me and from all devs that enjoy a range of choices of deployment settings.

ps. thank you for this feature, it saved us.

Deeeej avatar Nov 30 '21 18:11 Deeeej

@Deeeej how did you get it to work? I can't get it to work even in the most trivial scenario of publishing a blank site. Can you share your web.config file with us?

ScottRFrost avatar Nov 30 '21 19:11 ScottRFrost

<handlerSettings>
	<handlerSetting name="experimentalEnableShadowCopy" value="true" />
	<handlerSetting name="shadowCopyDirectory" value="../ShadowCopyDirectory_site1/" />
</handlerSettings>

Are the required settings but you need to add the correct permissions to the 'shadowCopyDirectory', I gave it the same permissions as the wwwroot folder, but if you are having problems add "Everyone" to the security of the folder, with 'Full Control' temporarily to see if this helps. Remember this only works with .net 6.

Deeeej avatar Nov 30 '21 23:11 Deeeej

That got me pointed in the right direction @Deeeej ! Thank you!

Based on your comment, I manually created C:\inetpub\Shadow and gave IIS_IUSRS user full control of that folder.

However, I also had ANOTHER problem. The aspNetCore attributes processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" were not working for me for some reason, and I had to explicity write them out.

My new WORKING web.config:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <location path="." inheritInChildApplications="false">
    <system.webServer>
      <handlers>
        <remove name="aspNetCore"/>
        <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
      </handlers>
      <aspNetCore processPath="C:\Program Files\dotnet\dotnet.exe" arguments=".\MyAppNameHere.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout">
        <handlerSettings>
          <handlerSetting name="experimentalEnableShadowCopy" value="true" />
          <handlerSetting name="shadowCopyDirectory" value="../Shadow/"  />
        </handlerSettings>
      </aspNetCore>
    </system.webServer>
  </location>
</configuration>

A new folder named "0" was automatically created in the C:\inetpub\Shadow folder and it appears to be working as intended now. Presumably that folder is created so you an use name="shadowCopyDirectory" value="../Shadow/" on multiple sites, but I haven't tried it yet.

@Rick-Anderson - It may be worth adding something about manually creating the Shadow folder and assigning it proper permissions to the documentation, in case other users run into this same issue.

ScottRFrost avatar Dec 01 '21 15:12 ScottRFrost

This is a mission critical feature for my company, and we are using it on all of our production servers. I certainly hope this becomes a permanent .NET 7 feature and not just experimental.

With this feature I can now release updates throughout the day knowing that it will not cause any failed client transactions, timeouts, long delays, or unexpected disconnect errors. We have a load balancer for our web servers, and even with it, there are drainstop issues that occasionally cause failed client transactions during a deployment. We have opted to use the experimentalEnableShadowCopy instead and this has proven to be the most reliable, consistent, seamless, and simplest way to deploy updates.

alanhoman avatar Jan 07 '22 23:01 alanhoman

I also 100% agree with everything @Deeeej stated above.

alanhoman avatar Jan 07 '22 23:01 alanhoman

We are migrating to ASP.NET Core from MVC 5 and the stepping stone for us to get stuff working on ASP.NET Core 2.2 layered on top of .NET 4.8. Does anyone know if this ONLY works with .NET 6 application files, or only with the .NET 6 module support (which is what we installed our servers). We are in production with the first (tiny) web site using ASP.NET Core, and instantly ran into this problem deploying our code.

We have our own deployment pipeline, so we currently dump app_offline.htm into the root of the application folder and it seems to work, but I also discovered that also takes down regular .NET web sites (which do not need it as they are shadow copied already). I would much prefer to rely on shadow copies working, but if this is still experimental and may not become a functional feature, I might ignore it for now.

Long term, I would love to see this be supported and ideally the shadow copy directory would NOT need to be specified. It should just work out of the box like it does today with regular ASP.NET web sites. That feature has been extremely useful for us. All our web sites except our image server are load balanced, so we deploy them one at a time while they are out of rotation and warm them up, so having downtime is not a big issue (locked files however is). But we do have one site (still .NET 4.8 and soon to be .NET Core) for serving images that is not yet load balanced, and when it goes down for upgrades we do get production level errors. Shadow copies on that one help mitigate most of the errors we see in production.

I do plan to load balance that one shortly (need to move images to a shared NFS server) so it won't be a big issue in the long term, but even if we are able to take the sites offline, having the site files be updatable without needing to shut the site down is a huge win IMHO. So I hope this feature becomes a standard feature and not experimental.

kendallb avatar Jan 21 '22 23:01 kendallb

Currently rewriting and redeploying a 100+ domain project from .net 4.8 to .net 6. The shadow copy feature is a must! Without it this project would be unmanageable.

kbalint avatar Jan 24 '22 19:01 kbalint

100%. It needs to be a core part of .NET Core (pun intended :) ).

kendallb avatar Jan 24 '22 21:01 kendallb

This feature is such a relief to have back!

Few questions/observations with my testing:

  1. Can the numbered folders be named their source folder's name + "_shadow" or something? We could set it up so that each application has it's own shadow copy directory, but that adds another layer unnecessary developer interaction of making sure files and folders are in their correct places with correct permissions. A problem does occur when two applications point to the same shadow copy directory, where a website may display a different site:

    1. Create an IIS Server with 2 websites: Site1 & Site2
    2. Publish a test application to Site1
    3. Modify the Home/Index view and publish to Site2
    4. Recycle Site1 app pool
    5. Refresh Site1 in the web browser. Site1 now displays Site2.
  2. Can shadow copy be disabled when the environment is development?

    • I would wager most developers will elect to use runtime compilation for razor, so shadow copying would probably make things more confusing for devs migrating or getting started with .NET.
    • When enabled in development (using IIS pointing to the project's folder, not publish folder), folders are created for app pool start up and shutdown, but are never deleted. I tried giving "Everyone" and literally everyone Full Control of the ShadowCopyDirectory folder, but the folders are still never deleted on my local machine. In the log file (but nothing in Event Viewer), it produces the error:

[aspnetcorev2_inprocess.dll] Exception 'remove_all: Access is denied.: "C:\inetpub\wwwroot\ShadowCopyDirectory\1"' caught at D:\a_work\1\s\src\Servers\IIS\AspNetCoreModuleV2\InProcessRequestHandler\inprocessapplication.cpp:195

sthkn avatar Mar 10 '22 15:03 sthkn

Hi,I found this feature doesn't work. Here is my configuration in web.config

<system.webServer>
  <handlers>
    <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
  </handlers>
  <aspNetCore processPath="dotnet" arguments=".\WebApplication3.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" >
    <handlerSettings>
      <handlerSetting name="experimentalEnableShadowCopy" value="true" />
      <handlerSetting name="shadowCopyDirectory" value="../ShadowCopy/" />
    </handlerSettings>
  </aspNetCore>
</system.webServer>

After using the above configuration, A new folder named "0" was automatically created in the "ShadowCopy" folder. However,as soon as I try to overwrite the file “WebApplication3.dll” to the publish folder,the file is still locked

Levyrii avatar Mar 22 '22 14:03 Levyrii

@LevyTerritory what versions of asp.net core/IIS/Windows are you using?

HaoK avatar Mar 22 '22 14:03 HaoK

@LevyTerritory what versions of asp.net core/IIS/Windows are you using? asp.net core : 6.0.3 IIS : 10.0.19041.1586 Windows : Win10 Professional 21H1(19043.1586)

Levyrii avatar Mar 22 '22 15:03 Levyrii

What version of ANCM do you have under C:\Program Files\IIS\Asp.Net Core Module\V2 should have something that looks like a version for example 17.0.22077

HaoK avatar Mar 22 '22 15:03 HaoK

What version of ANCM do you have under C:\Program Files\IIS\Asp.Net Core Module\V2 should have something that looks like a version for example 17.0.22077 this version of ANCM is 16.0.22055

Levyrii avatar Mar 22 '22 15:03 Levyrii

@LevyTerritory can you outline exactly what your workflow is for when you are running into the dll being locked, i.e. VS f5 or what exactly are you doing in your dev scenario

HaoK avatar Mar 22 '22 20:03 HaoK

@HaoK I have found the cause of the problem. It need to set hostingModel="InProcess" Thank you for your help

Levyrii avatar Mar 23 '22 03:03 Levyrii

I'm going to chime in with the others who have said this feature is indispensable in light of normal deployment with app_offline still failing with locked files. If you have no way of fixing the locked file error, then please make this feature permanent.

That said, it is still a little wonky, causing a brief period immediately after deployment when the server can return a 503 error. So the ultimate solution it would seem would be to combine the app_offline deployment with the shadow copy.

I tried doing that, enabling both shadow copy and the <EnableMsDeployAppOffline> option in Preview.pubxml. It doesn't really help.

The problem is that the app_offline file is removed from the server before whatever "cutover" occurs to the new shadow-copied files, so there is still that brief period of time when the server can return 503 errors. If whatever script you're running could delay removal of app_offline until after the cutover occurs, I think we can make this thing seamless.

Another overall solution that could fix all these deployment problems? Improve the VS Publish feature to allow us to script actions at different stages of deployment. I'm sure the community could build a ton of useful actions for all the different scenarios that are causing problems.

Finally, one more thing that drives me absolutely batty: I have no ability to modify the app_offline.htm file that gets deployed with the <EnableMsDeployAppOffline> option. That one would be so simple for you all to add an option for in VS. Just let me set the file path to use for app_offline.htm during deployment -- maybe even let me vary the path by host environment name.

SpeednetGroup avatar Mar 27 '22 23:03 SpeednetGroup

We wrote our own deployment tool using SSH to copy just the changed files along with the app_offline.html file with a custom message. We only have one server not load balanced (image server) and yeah we get the 503 issues also during the transition. My plan is to load balance the image server also with a shared file sever for the images to avoid this issue :(

kendallb avatar Mar 27 '22 23:03 kendallb

If any file in the bin folder is changed, AspNetCoreModuleV2 will restart the site after a certain period of time. We would need the following:

  1. To have an option to set after what time the restart occurs.
  2. Have the option to turn off change tracking (and subsequent restarts) completely.

Point 2 is important to us for the following reason. We host sites from a network path repository. If there is a brief network outage, the module detects the change and restarts the sites. This behavior is inappropriate for us. We have IIS configured so that it does not monitor changes, i.e. ASP.NET Framework sites do not restart.

petr-horny-bsshop avatar Mar 28 '22 11:03 petr-horny-bsshop

#25499 mentioned by @Rick-Anderson indicated that this feature is considered production and no longer experimental in .NET 7! This is great news! Thank you @Rick-Anderson !

ScottRFrost avatar Apr 07 '22 15:04 ScottRFrost

Closing as Shadow copy is now supported.

@HaoK lots of great comments here to review.

Rick-Anderson avatar Apr 07 '22 18:04 Rick-Anderson

Is there any way to exclude some paths from shadow copy?

Nuklon avatar Apr 14 '22 12:04 Nuklon

Is there any way to exclude some paths from shadow copy?

Not currently, what is the situation where you feel this is necessary?

HaoK avatar Apr 14 '22 15:04 HaoK

Is there any way to exclude some paths from shadow copy?

Not currently, what is the situation where you feel this is necessary?

It's copying the entire folder here rather than just the assemblies (including wwwroot and others). When you run Umbraco CMS for example, it creates a lot of temp and cache files so there's a lot of files that get copied. Also any images/videos/etc you add to CMS are added to wwwroot. All in all, this is very slow as there are a lot of (small) files. My current Umbraco folder has more than 11k files and copying takes a few minutes, whereas if I could exclude the wwwroot, umbraco folder, and App_Plugins, it only takes 5 seconds or so. I don't really understand why some of these folders are copied as the current directory and ASPNETCORE_IIS_PHYSICAL_PATH seems to be set to the original folder.

Nuklon avatar Apr 15 '22 08:04 Nuklon

Another vote to allow certain paths to be excluded. A simple file uploader with ~5GB of files shouldn't be copied every time a binary changes. Or at least always exclude wwwroot. This folder is probably never used to store binaries and is pointless to copy.

RataplanNL avatar Jun 17 '22 08:06 RataplanNL

Part of our deploy process is to apply the app_offline.htm, wait 15 seconds, then start the copy of the new files. This worked perfectly from dotnet v2.2 through v5. With v6 we started seeing issue with file locking and have been struggling to find a work around. Today I found the shadowcopy solution and it seems to be working. Finally.

What's most important is that we have a reliable way to deploy new files and NEVER run into a file locking issue. I think most of us can settle for minimal downtime to spool down a process in IIS and release file locks but it needs to be reliable and predictable and work from here on out. We don't want to continue to have to change our deploy processes with newer versions of dotnet.

ctorx avatar Aug 11 '22 19:08 ctorx

Not sure if this is the right place for this, but we seem to be running into an issue with shadow copying using .NET 6.x, and I'm at a loss to diagnose it.

We have a Windows Server 2019 VM hosting (currently) 15 instances of an API, all based on identical code. They're all configured with separate AppPools, all set to "no managed code", and all configured for shadow-copying.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <location path="." inheritInChildApplications="false">
    <system.webServer>
      <handlers>
        <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
      </handlers>
      <aspNetCore processPath=".\Assembly.exe" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="inprocess">
        <handlerSettings>
          <handlerSetting name="experimentalEnableShadowCopy" value="true" />
          <handlerSetting name="shadowCopyDirectory" value="../shadow/api/" />
          <!-- Only enable handler logging if you encounter issues-->
          <!--<handlerSetting name="debugFile" value=".\logs\aspnetcore-debug.log" />-->
          <!--<handlerSetting name="debugLevel" value="FILE,TRACE" />-->
        </handlerSettings>
      </aspNetCore>
    </system.webServer>
  </location>
</configuration>

ANCM version is 16.0.22232.9.

Mostly this works fine. But for the last two months, when the server reboots to install updates, one AppPool seemingly chosen at random ends up with its shadow copy being corrupted. Specifically, the .deps.json file is filled with nulls, and the main assembly .dll is 0kb in size. There are three errors logged in the application event log by the ANCM:

1027: Unable to locate application dependencies. Ensure that the versions of Microsoft.NetCore.App and Microsoft.AspNetCore.App targeted by the application are installed.

1027: Could not find 'aspnetcorev2_inprocess.dll'. Exception message: A JSON parsing exception occurred in C:\inetpub\Instance\shadow\api\99\Assembly.deps.json, offset 0 (line 1, column 1): The document is empty. Error initializing the dependency resolver: An error occurred while parsing: C:\inetpub\Instance\shadow\api\99\Assembly.deps.json

1010: Failed to start application '/LM/W3SVC/9/ROOT/api', ErrorCode '0x8000ffff'.

After stopping the affected AppPool, deleting the shadow folder, and starting the AppPool again, everything springs back into life.

There don't appear to be any other relevant events in the event log around the time of the restart.

I don't know whether this is a bug in the .NET 6 shadow copying option, or a problem with the VM, or whether something else is interfering when the server restarts.

Has anyone else seen this, or have any suggestions on how to diagnose and/or fix it? (The debug file won't help, since the issue occurs before the application has a chance to start.)

RichardD2 avatar Nov 10 '22 09:11 RichardD2

We ran into a different issue. The shadow copy would often be missing DLLs that were in the original folder, causing runtime errors for missing assemblies. Not sure if it's related to what you're experiencing, but we had to disable the feature.

On Thu, Nov 10, 2022 at 1:22 AM Richard Deeming @.***> wrote:

Not sure if this is the right place for this, but we seem to be running into an issue with shadow copying using .NET 6.x, and I'm at a loss to diagnose it.

We have a Windows Server 2019 VM hosting (currently) 15 instances of an API, all based on identical code. They're all configured with separate AppPools, all set to "no managed code", and all configured for shadow-copying.

ANCM version is 16.0.22232.9.

Mostly this works fine. But for the last two months, when the server reboots to install updates, one AppPool seemingly chosen at random ends up with its shadow copy being corrupted. Specifically, the .deps.json file is filled with nulls, and the main assembly .dll is 0kb in size. There are three errors logged in the application event log by the ANCM:

1027: Unable to locate application dependencies. Ensure that the versions of Microsoft.NetCore.App and Microsoft.AspNetCore.App targeted by the application are installed.

1027: Could not find 'aspnetcorev2_inprocess.dll'. Exception message: A JSON parsing exception occurred in C:\inetpub\Instance\shadow\api\99\Assembly.deps.json, offset 0 (line 1, column 1): The document is empty. Error initializing the dependency resolver: An error occurred while parsing: C:\inetpub\Instance\shadow\api\99\Assembly.deps.json

1010: Failed to start application '/LM/W3SVC/9/ROOT/api', ErrorCode '0x8000ffff'.

After stopping the affected AppPool, deleting the shadow folder, and starting the AppPool again, everything springs back into life.

There don't appear to be any other relevant events in the event log around the time of the restart.

I don't know whether this is a bug in the .NET 6 shadow copying option, or a problem with the VM, or whether something else is interfering when the server restarts.

Has anyone else seen this, or have any suggestions on how to diagnose and/or fix it? (The debug file won't help, since the issue occurs before the application has a chance to start.)

— Reply to this email directly, view it on GitHub https://github.com/dotnet/AspNetCore.Docs/issues/23733#issuecomment-1309996000, or unsubscribe https://github.com/notifications/unsubscribe-auth/AACT5Z4PNHSB36HGUKCXFEDWHS5GRANCNFSM5HKO4FUA . You are receiving this because you commented.Message ID: @.***>

ctorx avatar Nov 10 '22 18:11 ctorx