Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

In-Process hosting Directory.GetCurrentDirectory() location #4206

Closed
epignosisx opened this Issue Nov 22, 2018 · 35 comments

Comments

Projects
None yet
@epignosisx
Copy link

epignosisx commented Nov 22, 2018

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’t think of a use case where I would care about it.

@jkotalik

This comment has been minimized.

Copy link
Member

jkotalik commented Nov 26, 2018

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: aspnet/IISIntegration#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

@shirhatti

This comment has been minimized.

Copy link
Member

shirhatti commented Nov 26, 2018

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.

@shirhatti shirhatti closed this Nov 26, 2018

@epignosisx

This comment has been minimized.

Copy link
Author

epignosisx commented Nov 26, 2018

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’s the case, I’m fine with that and I’m 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:

https://github.com/serilog/serilog-aspnetcore/blob/a2c1d9c437f524b1488e27d40c1b789322b9889b/samples/SimpleWebSample/Program.cs#L1-L54

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?

@epignosisx

This comment has been minimized.

@Tratcher

This comment has been minimized.

Copy link
Member

Tratcher commented Nov 26, 2018

We don't do static APIs like that anymore.

@epignosisx

This comment has been minimized.

Copy link
Author

epignosisx commented Nov 27, 2018

@Tratcher how can the sample code above would work then?

@Tratcher

This comment has been minimized.

Copy link
Member

Tratcher commented Nov 27, 2018

Re-opening for @shirhatti to address.

@Tratcher Tratcher reopened this Nov 27, 2018

@shirhatti shirhatti removed the discussion label Nov 28, 2018

@shirhatti

This comment has been minimized.

Copy link
Member

shirhatti commented Nov 28, 2018

  • We'll look into exposing ContentRoot via an environment variable into the 2.2.x Patch train.
  • For 3.0, we'll either have to expose it via a static 😢 or see if you we can get coreclr to allow us to set AppDomain.CurrentDomain.BaseDirectory ahead of time
@shirhatti

This comment has been minimized.

Copy link
Member

shirhatti commented Nov 28, 2018

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

@Tratcher

This comment has been minimized.

Copy link
Member

Tratcher commented Nov 28, 2018

Does in-proc support passing parameters into Main via the web.config?

@muratg muratg added this to the 3.0.0-preview2 milestone Nov 28, 2018

@epignosisx

This comment has been minimized.

Copy link
Author

epignosisx commented Nov 29, 2018

Nice to hear there are plans to expose an API or environment variable to enable this scenario. I’m fine with either one.

@shirhatti

This comment has been minimized.

Copy link
Member

shirhatti commented Nov 29, 2018

Does in-proc support passing parameters into Main via the web.config?

Nope

@jkotalik

This comment has been minimized.

Copy link
Member

jkotalik commented Nov 29, 2018

You can still define an environment variable in the web.config and read that in Main.

@epignosisx

This comment has been minimized.

Copy link
Author

epignosisx commented Nov 29, 2018

I would like my app to know where it is running without me having to add a config setting specifying where it is running.

@Tratcher

This comment has been minimized.

Copy link
Member

Tratcher commented Nov 29, 2018

Understood. We're discussing these as short term workarounds.

@jkotalik

This comment has been minimized.

Copy link
Member

jkotalik commented Dec 3, 2018

@epignosisx an easier workaround would be to set the CurrentDirectory to AppContext.BaseDirectory, ex:

public static void Main(string args[])
{
    CurrentDirectory.SetCurrentDirectory(AppContext.BaseDirectory);
}

Can you try that and see if that works?

@DamianEdwards

This comment has been minimized.

Copy link
Member

DamianEdwards commented Dec 5, 2018

Can we please ensure this behavior change with in-process hosting is documented. I can see this tripping up a lot of folks.

@jkotalik

This comment has been minimized.

Copy link
Member

jkotalik commented Dec 5, 2018

It is documented and we are considering changing the default in 3.0: #4369

@shapeh

This comment has been minimized.

Copy link

shapeh commented Dec 5, 2018

@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\inetsrv\appsettings.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)
            ;
    }
}

@danjohnso

This comment has been minimized.

Copy link

danjohnso commented Dec 5, 2018

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.

@danjohnso

This comment has been minimized.

Copy link

danjohnso commented Dec 6, 2018

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.

@molesinski

This comment has been minimized.

Copy link

molesinski commented Dec 6, 2018

@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.

@danjohnso

This comment has been minimized.

Copy link

danjohnso commented Dec 6, 2018

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.

@mac-bhaird

This comment has been minimized.

Copy link

mac-bhaird commented Dec 9, 2018

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.

@pakrym

This comment has been minimized.

Copy link
Member

pakrym commented Dec 10, 2018

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.

@searus

This comment has been minimized.

Copy link

searus commented Dec 12, 2018

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.

@jkotalik

This comment has been minimized.

Copy link
Member

jkotalik commented Dec 12, 2018

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.

@JipingWang

This comment has been minimized.

Copy link
Contributor

JipingWang commented Dec 18, 2018

@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 ...
        }

davepermen added a commit to davepermen/website that referenced this issue Jan 19, 2019

thiennn added a commit to simplcommerce/SimplCommerce that referenced this issue Mar 6, 2019

Fixed downloading invoice with DinkPdf and MySql seed data #725 (#728)
* Added AspnetCore directory helper see aspnet/AspNetCore#4206 (comment). Sets current directory in `SimplCommerce.WebHost.Program.cs` because we're unable to use `Directory.GetCurrentDirectory`

* Fixed: DinkPdf download invoice `Unable to load DLL 'libwkhtmltox' or one of its dependencies:`. Added CustomAssemblyLoadContext and load the dll in DinkPdf ConfigureServices.

* Fixed MySql seed data. Color Key/ Display arrays missing quotes.

* Fixed payment process not going through and showing last step. Changed amount to `decimal` and removed casting to int.

* Fixed: MySql Fashion product seed data bad datetime format. CreatedOn, UpdatedOn

* Fixed: Clicking `Do it!` to reset `MySql` SampleData to Phones or Fashion. Deletes the wishlist table.

* Made Stripe checkout Amount long for zero decimal. #726
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.