Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dotnet Core Multiple Startup Classes with In-Process Hosting

I have a dotnet core v.2.1 application that utilizes the "startup-class-by-environment-name-convention" to use different Startup classes for different environment, e.g. development, staging and production. The Program.Main.CreateWebHost method looks similar to this:

public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
    var startupAssembly = Assembly.GetExecutingAssembly();
    var webHostBuilder = WebHost.CreateDefaultBuilder(args)
                                .UseStartup(startupAssembly.FullName);
    return webHostBuilder;
}

However, after upgrading to dotnet core v.2.2 (and the startup-switching still works great) I wanted to try out the in-process hosting capabilities. When switching to the in-process hosting model, and running locally with Visual Studio 2017 updated and IIS Express, I get this error running the application:

HTTP Error 500.30 - ANCM In-Process Start Failure

Common causes of this issue:

  • The application failed to start
  • The application started but then stopped
  • The application started but threw an exception during startup

Troubleshooting steps:

  • Check the system event log for error messages
  • Enable logging the application process' stdout messages
  • Attach a debugger to the application process and inspect

For more information visit: https://go.microsoft.com/fwlink/?LinkID=2028265

I checked all the logs, and all I could find was this:

<Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
  <System>
    <Provider Name="IIS Express AspNetCore Module V2" /> 
    <EventID Qualifiers="0">1007</EventID> 
    <Level>2</Level> 
    <Task>0</Task> 
    <Keywords>0x80000000000000</Keywords> 
    <TimeCreated SystemTime="2018-12-14T10:37:48.327935100Z" /> 
    <EventRecordID>3693</EventRecordID> 
    <Channel>Application</Channel> 
    <Computer>[whatever]</Computer> 
    <Security /> 
  </System>
  <EventData>
    <Data>Application '/LM/W3SVC/2/ROOT' with physical root '[whatever again]' failed to load clr and managed application. CLR worker thread exited prematurely</Data> 
    <Data>Process Id: 29836.</Data> 
    <Data>File Version: 12.2.18316.0. Description: IIS ASP.NET Core Module V2 Request Handler. Commit: ce8cf65589734f82b0536c543aba5bd60d0a5a98</Data> 
  </EventData>
</Event>

I read all the dotnet core in-hosting migrate 2.1 -> 2.2 whatever MSDN articles I could find, and tried a bunch of different set-ups, but could find no solution apart from using the default:

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
           .UseStartup<Startup>();

... Which will not do - I want to use the startup-switching together with the in-process hosting. Does anyone know how to achieve this, or have any suggestions on how to proceed with the troubleshooting?

EDIT: I got the answer I needed from @cilerler. For the sake of completeness, here's what was going on in my case:

The loading of my custom configuration files failed because this process depended on a call to Directory.GetCurrentDirectory(), the result of which changes when switching to in-process hosting. Here's my original code for that part (shortened for brevity):

var basePath = $"{Directory.GetCurrentDirectory()}\\ConfigurationFiles";
builder.SetBasePath(basePath);

builder.AddJsonFile("some.config.json", false, true);
builder.AddJsonFile($"some.config.{context.HostingEnvironment.EnvironmentName}.json", true, true);

The crucial part is - again - the call to GetCurrentDirectory(), so, to fix the issue, I changed the above as per the recommendation in the accepted answer, to:

var currentDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var basePath = $"{currentDirectory}\\ConfigurationFiles";
builder.SetBasePath(basePath);

For further details, see the accepted answer ;)

like image 791
Oskar Lindberg Avatar asked Dec 14 '18 16:12

Oskar Lindberg


People also ask

Can we have more than one startup class in .NET Core?

No, each environment could only run one startup class. We couldn't run multiple startup class at same environment.

Which method of startup class sets up the application's request processing pipeline?

The Configure method is used to specify how the app responds to HTTP requests. The request pipeline is configured by adding middleware components to an IApplicationBuilder instance.

Can Cs have multiple startups?

Multiple Startup ClassesAt runtime, the appropriate Startup class is selected when the app defines separate Startup classes for various environments. The class whose name matches the current environment is prioritized.

What does webhost CreateDefaultBuilder () do?

CreateDefaultBuilder performs the following tasks: Configures Kestrel server as the web server using the app's hosting configuration providers. For the Kestrel server's default options, see Configure options for the ASP.NET Core Kestrel web server. Sets the content root to the path returned by Directory.


1 Answers

According to aspnet-core-module article it says

GetCurrentDirectory returns the worker directory of the process started by IIS rather than the app's directory (for example, C:\Windows\System32\inetsrv for w3wp.exe).

which means config loader will not be able to find appsettings.* files, or any other files such as custom config files, that depend on a GetCurrentDirectory call. In order to solve it in your Program.cs right after public static void Main(string[] args) { add the following line

Directory.SetCurrentDirectory(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));

Also, in project file (e.g. MyProject.csproj) make sure that you have the following lines and appsettings.* exists in output folder.

<ItemGroup>
  <Content Update="appsettings.json">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </Content>
  <Content Update="appsettings.Development.json">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </Content>
  <Content Update="appsettings.Production.json">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </Content>
</ItemGroup>
like image 198
cilerler Avatar answered Oct 18 '22 13:10

cilerler