AspNet Core Shared Settings

Sometimes there are app settings which you need to share across multiple projects within the same Visual Studio solution (e.g., database connections). You can always keep multiple appsettings.json files synchronized…but it’s a pain, and when you forget to do a manual sync, you can create hard-to-diagnose problems for yourself.

It turns out there’s a way you can simply share a common file. You can read all about this on Andrew Lock’s blog. I’m indebted to him for coming up with his clever approach, and doing 95% of the work needed to implement it. Thanx, Andrew!

My own contribution is just to organize things a little differently, via an extension method that you can drop into any AspNet Core project, and to build on the default CreateWebHostBuilder() method defined in AspNet Core, rather than duplicate its functionality (that way, when the AspNet Core team changes how CreateWebHostBuilder() works, you may not have to update your own code to mimic their changes). I also added in logic to support environment-specific (e.g., ‘development’) configuration files, similar to how that’s done in the AspNet Core source code.

Here’s the extension method:

public static class WebHostBuilderSharedSettings
{
    public static IWebHostBuilder AddSharedSettings( this IWebHostBuilder builder, string sharedFileName )
    {
        if( builder == null || String.IsNullOrEmpty( sharedFileName ) )
            return builder;

        // modify the config files being used
        builder.ConfigureAppConfiguration( ( hostingContext, config ) =>
        {
            var fileStub = Path.GetFileNameWithoutExtension( sharedFileName );
            var fileExt = Path.GetExtension( sharedFileName );

            var fileNames = new List<string>
            {
                sharedFileName,
                $"{fileStub}.{hostingContext.HostingEnvironment.EnvironmentName}{fileExt}"
            };

            var sharedConfigs = new List<JsonConfigurationSource>();

            // first settings files are the ones in the shared folder
            // that get found when run via dotnet run
            var parentDir = Directory.GetParent( hostingContext.HostingEnvironment.ContentRootPath );

            foreach( var fileName in fileNames )
            {
                var filePath = Path.Combine( parentDir.FullName, fileName );

                if( File.Exists( filePath ) )
                {
                    sharedConfigs.Add( new JsonConfigurationSource()
                    {
                        Path = filePath,
                        Optional = true,
                        ReloadOnChange = true
                    } );
                }
            }

            // second settings files are the linked shared settings files found when
            // the site is published
            foreach( var fileName in fileNames )
            {
                var filePath = Path.Combine( hostingContext.HostingEnvironment.ContentRootPath, fileName );

                if( File.Exists( filePath ) )
                {
                    sharedConfigs.Add( new JsonConfigurationSource()
                    {
                        Path = filePath,
                        Optional = true,
                        ReloadOnChange = true
                    } );
                }
            }

            // create the file providers, since we didn't specify one explicitly
            sharedConfigs.ForEach( x => x.ResolveFileProvider() );

            if( config.Sources.Count > 0 )
            {
                for( var idx = 0; idx < sharedConfigs.Count; idx++ )
                {
                    config.Sources.Insert( idx, sharedConfigs[ idx ] );
                }
            }
            else sharedConfigs.ForEach( x => { config.Add( x ); } );

            // all other setting files (e.g., appsettings.json) appear afterwards
        } );

        return builder;
    }
}

This is reasonably straightforward. First we check to ensure that we were given a valid IWebHostBuilder and shared file name object, doing nothing if we weren’t. Then we call the IWebHostBuilder’s ConfigureAppConfiguration() method to insert new json configuration objects derived from the shared json file.

There are up to four new json configuration objects added, representing two different files which may exist in each of two places (Andrew’s post explains why the files need to be in two places; they’re linked, so you don’t have to sync them manually).

One minor deviation from Andrew’s code involves how I look for the shared json file that sits “above” the individual projects in the Visual Studio solution. His approach involved using Path.Combine() to go up a level (e.g., “<some long path to the Content Root folder>\..\<shared json file>”). I could not get this to work on my system (I suspect Net Core doesn’t handle the ‘..’ correctly), so I simply created a DirectoryInfo object via Directory.GetParent().

Also, I don’t separately include the shared folder name in defining the path to the shared json file. In my VS solution that “directory” seems to be virtual (i.e., it doesn’t exist in the file system — VS appears to simply keep track of which file outside of the projects belong to which virtual directory, but all the actual files are simply stored in the solution directory). Your mileage may vary.

The subtlest problem I had coding this was solved by line 59: if you don’t specify a file provider for the configuration build system to use, you have to call ResolveFileProvider() on the JsonConfigurationSource object or the config info from the file won’t be included in the final configuration object.

You’ll notice that I insert the shared configuration objects if the default builder has already created some entries (e.g., from a traditional appsettings.json file). This was simply to duplicate Andrew’s approach. You could simply call AddJsonFile() on the config object if you were willing to have the shared files come after, and therefore override, the config files set up by the default builder. This way, the defaults will override entries with the same path/name from the shared files. Again, that’s simply a matter of preference.

Enjoy!

7 thoughts on “AspNet Core Shared Settings”

  1. Do we need .FullName or not? You do add .FullName to the parent dir:
    Path.Combine(parentDir.FullName, fileName);
    but not here:
    Path.Combine(hostingContext.HostingEnvironment.ContentRootPath, fileName);

    1. Hi Bruce,

      The full path to the containing directory is contained in ContentRootPath, so it isn’t necessary to add it separately.

  2. Sorry!! I can’t read your post.. Your header-dog-rollover occupies 80% of the screen without exaggerating

  3. Sorry, David, I forget to get rid of the sticky setting for anything other than the desktop. I rarely access the site on my phone or tablet so… :)

    Let me know if it’s still a problem.

  4. Hey, thanks for the post! Was looking for something like that – want to share settings across projects in nuget.
    Though, I want to raise the same issue – even on 24″ monitor it’s hard to read the posts because of header :D The dog is nice though ;)

  5. Thanx, Yurii. He was a great dog.

    I’ve eliminated the stickiness of the header on all displays so posts should be easier to read now. Let me know if it doesn’t work. Thanx for the feedback!

  6. Jan Narkiewicz

    I read Andrew’s article and thought “extension method” then I followed your link. Wahoo… great minds think alike.

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Archives
Categories