Hi folks. I was reading the documentation on the new IIS in-process hosting option and kind of disappointing about this point:
Directory.GetCurrentDirectory() returns the worker directory of the process started by IIS rather than the application directory (for example, C:\Windows\System32\inetsrv for w3wp.exe).
Source: https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/aspnet-core-module?view=aspnetcore-2.2
Any code that was expecting the location to point to the application folder will be up for a big surprise. This might also be a problem for libraries relying on the current behavior.
This difference is going to trip up developers trying to evaluate the new model. Even worse if they develop in one hosting model but deploy to another another hosting model.
The w3wp process path feels very useless in my mind. I can鈥檛 think of a use case where I would care about it.
Sorry for not getting to this sooner, holidays in the states.
@epignosisx , appreciate the feedback. We are aware of this issue and attempted a fix (see: https://github.com/aspnet/IISIntegration/pull/1437), however making this fix is risky.
To make Directory.GetCurrentDirectory() work, we need to set the current working for the entire w3wp.exe process to your application directory. After changing the current directory, if IIS tries to load a module lazily or another IIS module relies on the current working directory being C:\Windows\System32\inetsrv, that will start failing, which may or may not be recoverable.
The way we host ANCM In-Process is similar to System.Web (inside of the w3wp process). System.Web had Directory.GetCurrentDirectory() as C:\Windows\System32\inetsrv too.
As a work around, we set the ContentRoot to be the application directory in Configuration. Anywhere that you can resolve IConfiguration, you can use IConfiguration[HostDefaults.ContentRootKey]
to obtain the application directory.
If this issue affects many users, we may consider investigating more into resolutions.
cc @muratg @shirhatti
As @jkotalik alluded to, we will not revisit this unless it proves to be a pit of failure.
This is no different from dotnet run --project /path/to/project
from a different directory OR no different from System.Web like Justin mentioned.
I definitely did not expect this to be such a hard issue to maintain interoperability with IIS. However, what would happen if as the first line of my Program.cs, this is done:
public class Program
{
public static int Main(string[] args)
{
Directory.SetCurrentDirectory(...);
}
}
Would this work in the context of my app without interfering with IIS? Because, if that鈥檚 the case, I鈥檓 fine with that and I鈥檓 guessing a lot of users will be fine with that too, that is, they do not care about deep integration with IOS other than the basic hosting.
Secondly, if I need to get real application directory, how can I do it when IConfiguration[HostDefaults.ContentRootKey]
is not available? For example, we are following something similar to pattern to set up our logging:
namespace SimpleWebSample
{
public class Program
{
public static IConfiguration Configuration { get; } = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory()) // <----- How do we make this work?
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true)
.AddEnvironmentVariables()
.Build();
public static int Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(Configuration)
.Enrich.FromLogContext()
.WriteTo.Console()
.CreateLogger();
try
{
Log.Information("Getting the motors running...");
BuildWebHost(args).Run();
return 0;
}
catch (Exception ex)
{
Log.Fatal(ex, "Host terminated unexpectedly");
return 1;
}
finally
{
Log.CloseAndFlush();
}
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseConfiguration(Configuration)
.UseSerilog()
.Build();
}
}
Notice in the creation of the configuration this line: SetBasePath(Directory.GetCurrentDirectory())
, how would this work with in-process hosting before any WebHost has been created?
A top level API as simple as this one would be needed:
To enable the example above.
We don't do static APIs like that anymore.
@tratcher how can the sample code above would work then?
Re-opening for @shirhatti to address.
The only workaround at the moment, is to Pinvoke directly and read the applicationPath yourself directly from iis config. See https://github.com/aspnet/AspNetCore/blob/master/src/IISIntegration/src/Microsoft.AspNetCore.Server.IIS/WebHostBuilderIISExtensions.cs#L35-L38
Does in-proc support passing parameters into Main via the web.config?
Nice to hear there are plans to expose an API or environment variable to enable this scenario. I鈥檓 fine with either one.
Does in-proc support passing parameters into Main via the web.config?
Nope
You can still define an environment variable in the web.config and read that in Main.
I would like my app to know where it is running without me having to add a config setting specifying where it is running.
Understood. We're discussing these as short term workarounds.
@epignosisx an easier workaround would be to set the CurrentDirectory to AppContext.BaseDirectory, ex:
c#
public static void Main(string args[])
{
CurrentDirectory.SetCurrentDirectory(AppContext.BaseDirectory);
}
Can you try that and see if that works?
Can we please ensure this behavior change with in-process hosting is documented. I can see this tripping up a lot of folks.
It is documented and we are considering changing the default in 3.0: https://github.com/aspnet/AspNetCore/pull/4369
@jkotalik
For ASP.NET Core 2.2 just released, what is the solution for InProcess?
I tried proposed solution AppContext.BaseDirectory
but it does not work - basically it fails at reading appsettings.json because it looks for the file here: c:\windows\system32\inetsrvappsettings.json
This is my Program.cs
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using System;
namespace XYZ
{
public static class Program
{
public static IConfiguration Configuration { get; } = new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables()
.Build();
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseConfiguration(Configuration)
;
}
}
This has another side effect I don't see mentioned yet. If debugging from Visual Studio, AppContext.BaseDirectory is the $(ProjectDir)/bin/netcoreapp2.2 folder, not $(ProjectDir) like it used to be when you used Directory.GetCurrentDirectory(). This also means things relative paths to wwwroot don't work without guessing if you need to move up directories.
Can you check what the value of AppContext.BaseDirectory is? Can try is to call Directory.SetCurrentDirectory(AppContext.BaseDirectory);
at the beginning of Program.Main().
@jkotalik yes, value is C:Code{Solution}{Project}binDebug\netcoreapp2.2\
.UseContentRoot() gets set off of that value which means when I go to try and read my webpack manifest that lives at C:Code{Solution}{Project}\wwwroot\manifest.json, I get an exception because it is looking here C:Code{Solution}{Project}binDebug\netcoreapp2.2\wwwroot\manifest.json
@jkotalik, neither Directory.GetCurrentDirectory()
nor AppContext.BaseDirectory
yields productive results for us in ASP.NET Core 2.2 with in-process.
This is a mess for us and a breaking change.
AppContext.BaseDirectory does not work in debug mode for us; we have a method that reads from appsettings.json
in Startup.cs
but appsettings.json
is not copied into the binDebug\netcoreapp2.2 directory.
To reproduce
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory()) // will look for appsettings.json here c:\windows\system32\inetsrv\appsettings.json = project fails
.SetBasePath(AppContext.BaseDirectory) // look in \bin\Debug\netcoreapp2.2 folder but file is not there, so project fails
.SetBasePath(HostDefaults.ContentRootKey) // just produces "contentRoot" string
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
IConfigurationRoot configuration = builder.Build();
We can't really upgrade to 2.2 at the moment.
Directory.GetCurrentDirectory()
used to work fine in ASP.NET Core 2.1-2.1.5
Is there another method we can use to get the right path no matter if we are in debug mode, production mode, inprocess/outofprocess, server or local machine?
We ran into this plus a few others. This upgrade wasn't as smooth. I know this isn't a great solution but we have specific json files we load on startup. We used AppContext.BaseDirectory then have a helper method that recursively looks at the parent directory until we find the file we need. Then use that path. It worked for us but not sure about your setup. Not a great solution but once a fix is made we will remove this code.
@shapeh you always have the option of using OutOfProcess. We intentionally made InProcess opt-in for existing applications so we could work out some of these issues before moving apps.
@Tratcher - I see. We were/are just eager to go with InProcess because of the announced 4x performance gains in 2.2.
I found a work-around using IHostingEnvironment
via DI and then doing something like:
.SetBasePath(hostingEnvironment.ContentRootPath)
ContentRootPath
seems to be getting the value I want but I need to do additional testing.
This actually breaks all scenarios requiring pre building configuration before creating WebHost. For us this breaks because we wire up Serilog logging based on configuration in Program.cs
. Same goes for Azure Key Vault configuration, which usually requires pre building configuration in order to read connection details.
Based on what @shirhatti suggested we used native method approach to extract IIS application path with a helper class and it seems to solve the problem for now.
Gist of helper class: https://gist.github.com/molesinski/aa8433f61bd20141e443d4fed4b33753
Then we just use:
.SetBasePath(IISHelper.GetContentRoot() ?? Directory.GetCurrentDirectory())
Using just SetBasePath for configuration and UseContentRoot for the WebBuilder helps with startup, but any time you make a relative call to File.Read or other IO classes, it is going to go back to looking in the netcoreapp directory.
For local debugging, if I combine @molesinski suggestion and @jkotalik suggestion and put this line first thing in Program.Main, I get the same behavior as 2.1 OutOfProcess:
Directory.SetCurrentDirectory(IISHelper.GetContentRoot() ?? AppContext.BaseDirectory);
Need to try some tests today and see if that causes any weird side effects in a deployed environment, but now I can at least reasonably debug other issues we may run into with 2.2 and InProcess mode.
@danjohnso you should always assume the current application directory comes from IHostingEnvironment.ContentRootPath
that you should inject into services that perform IO related operations like File.Read. This is what .SetBasePath(...)
sets. For InProcess ANCM Directory.GetCurrentDirectory()
will be defaulting to IIS folder.
Instead of calling SetCurrentDirectory, I was injecting ContentRoot as:
new WebHostBuilder()
.UseKestrel()
.UseIIS()
.UseIISIntegration()
.UseContentRoot(IISHelper.GetContentRoot() ?? AppContext.BaseDirectory)
While that worked for somethings, calls like this would still look in AppContext.BaseDirectory (inetsrv):
File.OpenRead("wwwroot/dist/manifest.json")
I think the only way to get the old behavior back right now is to call SetDirectory up front and continue using GetCurrentDirectory for setting configuration base path and the content root.
Used @danjohnso solution above, early tests are promising.
It seems to me that reading local files for configurations that can be updated via CD tooling is a pretty common use case.
We've added a helper to set current directory to correct value in 2.2 (https://github.com/aspnet/Docs/blob/master/aspnetcore/host-and-deploy/aspnet-core-module/samples_snapshot/2.x/CurrentDirectoryHelpers.cs) just call CurrentDirectoryHelpers.SetCurrentDirectory()
as the first line of your Program.cs
. We are also going to start setting current directory to correct value by default in 3.0.
I have just spent a long time trying to identify why my application wasn't running in Azure.
I deployed a second application into a new directory in my App Service and it was failing. Presumably because the application in the new folder was resolving to the default application folder in the app service.
The solution is to update to use out of process and redeploy.
I don't know if this is documented somewhere, but it has taken me a long time to find this issue and join the dots.
See the "Availability in Azure App Service" here: https://blogs.msdn.microsoft.com/webdev/2018/12/04/asp-net-core-2-2-available-today/. ANCMV2 (which is required for In-Process) is still rolling out across Azure, so for now you do need to continue using Out-Of-Process.
@shapeh saved my day, thanks.
IHostingEnvironment via DI
public Startup(IConfiguration configuration, IHostingEnvironment hostingEnvironment)
{
Configuration = configuration;
this.hostingEnvironment = hostingEnvironment;
Init();
}
void Init()
{
var dataDirConfig = $"{hostingEnvironment.ContentRootPath}/../data";
dataDir = Path.GetFullPath(dataDirConfig);
//read my data files from dataDir ...
}
I solved with DI:
public Startup(IConfiguration configuration, IHostingEnvironment hostingEnvironment)
{
Environment.CurrentDirectory = env.ContentRootPath;
...
}
Most helpful comment
We've added a helper to set current directory to correct value in 2.2 (https://github.com/aspnet/Docs/blob/master/aspnetcore/host-and-deploy/aspnet-core-module/samples_snapshot/2.x/CurrentDirectoryHelpers.cs) just call
CurrentDirectoryHelpers.SetCurrentDirectory()
as the first line of yourProgram.cs
. We are also going to start setting current directory to correct value by default in 3.0.