Andrew Lock | .NET Escapades

Andrew Lock

in ASP.NET Core .NET Core 3.0 .NET Core ~ 6 min read.

IHostingEnvironment vs IHostEnvironment - obsolete types in .NET Core 3.0
Upgrading to ASP.NET Core 3.0 - Part 2

In this post I describe the differences between various ASP.NET Core types that have been marked as obsolete in .NET Core 3.0. I describe why things have changed, where the replacement types are, and when you should use them.

ASP.NET Core merges with the generic host

ASP.NET Core 2.1 introduced the GenericHost as a way of building non-HTTP apps using the Microsoft.Extensions.* primitives for configuration, dependency injection, and logging. While this was a really nice idea, the hosting abstractions introduced were fundamentally incompatible with the HTTP hosting infrastructure used by ASP.NET Core. This led to various namespace clashes and incompatibilities that mostly caused me to avoid using the generic host.

In ASP.NET Core 3.0 a big effort went into converting the web hosting infrastructure to be compatible with the generic host. Instead of having duplicate abstractions - one for ASP.NET Core and one for the generic host - the ASP.NET Core web hosting infrastructure could run on top of the generic host as an IHostedService.

This isn't the whole story though. ASP.NET Core 3 doesn't force you to convert to the new generic-host-based infrastructure immediately when upgrading from 2.x to 3.0. You can continue to use the WebHostBuilder instead of HostBuilder if you wish. The migration documentation implies it's required, but in reality it's optional at this stage if you need or want to keep using it for some reason.

I'd suggest converting to HostBuilder as part of your upgrade if possible. I suspect the WebHostBuilder will be removed completely at some point, even though it hasn't been marked [Obsolete] yet.

As part of the re-platforming on top of the generic host, some of the types that were duplicated previously have been marked obsolete, and new types have been introduced. The best example of this is IHostingEnvironment.

IHostingEnvironment vs IHostEnvironment vs IWebHostEnviornment

IHostingEnvironment is one of the most annoying interfaces in .NET Core 2.x, because it exists in two different namespaces, Microsoft.AspNetCore.Hosting and Microsoft.Extensions.Hosting. These are slightly different and are incompatible - one does not inherit from the other.

namespace Microsoft.AspNetCore.Hosting
{
    public interface IHostingEnvironment
    {
        string EnvironmentName { get; set; }
        string ApplicationName { get; set; }
        string WebRootPath { get; set; }
        IFileProvider WebRootFileProvider { get; set; }
        string ContentRootPath { get; set; }
        IFileProvider ContentRootFileProvider { get; set; }
    }
}

namespace Microsoft.Extensions.Hosting
{
    public interface IHostingEnvironment
    {
        string EnvironmentName { get; set; }
        string ApplicationName { get; set; }
        string ContentRootPath { get; set; }
        IFileProvider ContentRootFileProvider { get; set; }
    }
}

The reason there are two is basically historical - the AspNetCore version existed, and the Extensions version was introduced with the generic host in ASP.NET Core 2.1. The Extensions version has no notion of the wwwroot folder for serving static files (as it's for hosting non-HTTP services), so it lacks the WebRootFileProvider and WebRootPath properties.

A separate abstraction was necessary for backwards-compatibility reasons. But one of the really annoying consequences of this was the inability to write extension methods that worked for both the generic-host and for ASP.NET Core.

In ASP.NET Core 3.0, both of these interfaces are marked obsolete. You can still use them, but you'll get warnings at build time. Instead, two new interfaces have been introduced: IHostEnvironment and IWebHostEnvironment. While they are still in separate namespaces, they now have different names, and one inherits from the other!

namespace Microsoft.Extensions.Hosting
{
    public interface IHostEnvironment
    {
        string EnvironmentName { get; set; }
        string ApplicationName { get; set; }
        string ContentRootPath { get; set; }
        IFileProvider ContentRootFileProvider { get; set; }
    }
}

namespace Microsoft.AspNetCore.Hosting
{
    public interface IWebHostEnvironment : IHostEnvironment
    {
        string WebRootPath { get; set; }
        IFileProvider WebRootFileProvider { get; set; }
    }
}

This hierarchy makes much more sense, avoids duplication, and means methods that can accept the generic-host version of the host environment abstraction (IHostEnvironment) will now work with the web version too (IWebHostEnvironment). Under the hood, the implementations of IHostEnvironment and IWebHostEnvironment are still the same - they just implement the new interfaces in addition to the old ones. For example, the ASP.NET Core implementation:

namespace Microsoft.AspNetCore.Hosting
{
    internal class HostingEnvironment : IHostingEnvironment, Extensions.Hosting.IHostingEnvironment, IWebHostEnvironment
    {
        public string EnvironmentName { get; set; } = Extensions.Hosting.Environments.Production;
        public string ApplicationName { get; set; }
        public string WebRootPath { get; set; }
        public IFileProvider WebRootFileProvider { get; set; }
        public string ContentRootPath { get; set; }
        public IFileProvider ContentRootFileProvider { get; set; }
    }
}

So which interface should you use? The short answer is "use IHostEnvironment wherever possible", but the details may vary…

If you're building ASP.NET Core 3.0 apps

Use IHostEnvironment where possible, and use IWebHostEnvironment when you need access to the WebRootPath or WebRootFileProvider properties.

If you're building a library to be used with the generic host and .NET Core 3.0

Use IHostEnvironment. Your library will still work with ASP.NET Core 3.0 apps.

If you're building a library to be used with ASP.NET Core 3.0 apps

As before, it's best to use IHostEnvironment as then your library can potentially be used by other generic host applications, not just ASP.NET Core applications. However, if you need access to the extra properties on IWebHostEnvironment then you'll have to update your library to target netcoreapp3.0 instead of netstandard2.0 and add a <FrameworkReference> element, as described in my previous post.

If you're building a library to be used with both ASP.NET Core 2.x and 3.0

This is a pain. You basically have two choices:

  • Continue to use the Microsoft.AspNetCore version of IHostingEnvironment. It will work in both 2.x and 3.0 apps without any issues, you'll just likely have to stop using it in later versions.
  • Use #ifdef to conditionally compile using the IHostEnvironment in ASP.NET Core 3.0 and IHostingEnvironment in ASP.NET Core 2.0.

IApplicationLifetime vs IHostApplicationLifetime

A very similar issue of namespace clashes is present for the IApplicationLifetime interface. As with the previous example, this exists in both Microsoft.Extensions.Hosting and Microsoft.AspNetCore.Hosting. In this case however, the interface in both cases is identical:

// identical to Microsoft.AspNetCore.Hosting definition
namespace Microsoft.Extensions.Hosting
{
    public interface IApplicationLifetime
    {
        CancellationToken ApplicationStarted { get; }
        CancellationToken ApplicationStopped { get; }
        CancellationToken ApplicationStopping { get; }
        void StopApplication();
    }
}

As you might expect by now, this duplication was a symptom of backwards-compatibility. .NET Core 3.0 introduces a new interface, IHostApplicationLifetime that is defined only in the Microsoft.Extensions.Hosting namespace, but is available both in the generic host and ASP.NET Core apps:

namespace Microsoft.Extensions.Hosting
{
    public interface IHostApplicationLifetime
    {
        CancellationToken ApplicationStarted { get; }
        CancellationToken ApplicationStopping { get; }
        CancellationToken ApplicationStopped { get; }
        void StopApplication();
    }
}

Again, this interface is identical to the previous version, and the .NET Core 3.0 implementation implements both versions as ApplicationLifetime. As I discussed in my previous post on the startup process, the ApplicationLifetime type plays a key role in generic-host startup and shutdown. Interestingly, there is no real equivalent in Microsoft.AspNetCore.Hosting - the Extensions version handles it all. The only implementation in the AspNetCore namespace is a simple wrapper type that delegates to the ApplicationLifetime added as part of the generic host:

namespace Microsoft.AspNetCore.Hosting
{
    internal class GenericWebHostApplicationLifetime : IApplicationLifetime
    {
        private readonly IHostApplicationLifetime _applicationLifetime;
        public GenericWebHostApplicationLifetime(IHostApplicationLifetime applicationLifetime)
        {
            _applicationLifetime = applicationLifetime;
        }

        public CancellationToken ApplicationStarted => _applicationLifetime.ApplicationStarted;
        public CancellationToken ApplicationStopping => _applicationLifetime.ApplicationStopping;
        public CancellationToken ApplicationStopped => _applicationLifetime.ApplicationStopped;
        public void StopApplication() => _applicationLifetime.StopApplication();
    }
}

The decision of which interface to use is, thankfully, much easier for application lifetime rather than hosting environment:

If you're building .NET Core 3.0, or ASP.NET Core 3.0 apps or libraries

Use IHostApplicationLifetime. It only requires a reference to Microsoft.Extensions.Hosting.Abstractions, and is usable in all applications

If you're building a library to be used with both ASP.NET Core 2.x and 3.0

Now you're stuck again:

  • Use the Microsoft.Extensions version of IApplicationLifetime. It will work in both 2.x and 3.0 apps without any issues, you'll just likely have to stop using it in later versions.
  • Use #ifdef to conditionally compile using the IHostApplicationLifetime in ASP.NET Core 3.0 and IApplicationLifetime in ASP.NET Core 2.0.

Luckily IApplicationLifetime is generally used much less often than IHostingEnvironment, so you probably won't have too much difficulty with this one.

IWebHost vs IHost

One thing that may surprise you is that the IWebHost interface hasn't been updated to inherit from IHost in ASP.NET Core 3.0. Similarly IWebHostBuilder doesn't inherit from IHostBulider. They are still completely separate interfaces - one for ASP.NET Core, and one for the generic host.

Luckily, that doesn't matter. Now that ASP.NET Core 3.0 has been rebuilt to use the generic host abstractions, you get the best of both worlds. You can write methods that use the generic host IHostBuilder abstractions and share them between your ASP.NET Core and generic host apps. If you need to do something ASP.NET Core specific, you can still use the IWebHostBuilder interface.

For example, consider the two extension methods below, one for IHostBuilder, and one for IWebHostBuilder:

public static class ExampleExtensions
{
    public static IHostBuilder DoSomethingGeneric(this IHostBuilder builder)
    {
        // ... add generic host configuration
        return builder;
    }

    public static IWebHostBuilder DoSomethingWeb(this IWebHostBuilder builder)
    {
        // ... add web host configuration
        return builder;
    }
}

One of the methods does some sort of configuration on the generic host (maybe it registers some services with DI for example), and the other does some configuration on the IWebHostBuilder. Perhaps it sets some defaults for the Kestrel server for example.

If you create a brand-new ASP.NET Core 3.0 application, your Program.cs will look something like this:

public class Program
{
    public static void Main(string[] args) => CreateHostBuilder(args).Build().Run();

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder
                    .UseStartup<Startup>();
            });
}

You can add calls to both your extension methods by adding one call on the generic IHostBuilder, and the other inside ConfigureWebHostDefaults(), on the IWebHostBuilder:

public class Program
{
    public static void Main(string[] args) => CreateHostBuilder(args).Build().Run();

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .DoSomethingGeneric() // IHostBuilder extension method
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder
                    .DoSomethingWeb() // IWebHostBuilder extension method
                    .UseStartup<Startup>();
            });
}

The fact you can make calls on both builder types in ASP.NET Core 3.0 means you can now build libraries that rely solely on generic-host abstractions, and can reuse them in ASP.NET Core apps. You can then layer on the ASP.NET Core-specific behaviour on top, without having to duplicate methods like you did in 2.x.

Summary

In this post I discussed some of the types that have been made obsolete in ASP.NET Core 3.0, where they've moved to, and why. If you're updating an application to ASP.NET Core 3.0 you don't have to replace them, as they will still behave the same for now. But they'll be replaced in a future version, so it makes sense to update them if you can. In some cases it also makes it easier to share code between your apps, so it's worth looking in to.

Loading comments powered by Disqus, please wait…
Andrew Lock | .Net Escapades

Stay up to the date with the latest posts!

Oops! Check your details and try again.
Thanks! Check your email for confirmation.