Aspnetcore: Enable web.config transforms for AspNetCore projects in VS

Created on 1 May 2017  ·  91Comments  ·  Source: dotnet/aspnetcore

Let me prefix this by saying that I'm no fan of web.config but experience of publishing AspNetCore apps to IIS is really poor.

The configuration system of AspNetCore revolves around ASPNETCORE_ENVIRONMENT variable based on which different configs can be loaded and applied.

Setting this variable for specific deployments is a nightmare and there seems to be a lot of confusion how to go about this:

The crux of the problem seems to boil down to 2 problems:

1) web.config transforms as we know them are not supported in ASP.NET Core projects in VS
2) the only reasonable way to change ASPNETCORE_ENVIRONMENT is using web.config

Setting a global ASPNETCORE_ENVIRONMENT is not an option as that sets it for every site on a single server. web.config used to be self contained configuration. This dependency on a global env variable is no good in IIS use case.

All in all, I believe the IIS publish story should be of concern as many dev such as myself who are transitioning to AspNetCore want to use their existing infrastructure for deployments and take it step by step. Currently this story is overly complicated and not complete.

Another example where I need web.config transforms: https://github.com/aspnet/Home/issues/1701#issuecomment-298273962

Most helpful comment

Totally agree with the first post. It's by design pretty flexible but at the end it's turned out to be very hard to automate. For example I have an app which I publish to IIS on another server. Also it runs on local IIS (via folder publish). So I set Development environment in web.config. But then I need to change it onto Production or whatever during publishing. Pretty common task I guess. I believe it's impossible, right? I have to manually edit web.config on remote servers.

Ideally setting Environment should be supported via "dotnet publish" (for any publish target):

dotnet publish ... -environment=Production

dotnet publish could search for web.%Enviroment%.config (Web.Production.config, Web.Development.config) files and merge (transform) it with web.config. At the end we'd have correct value of ASPNETCORE_ENVIRONMENT:

    <environmentVariables>
        <environmentVariable name="ASPNETCORE_ENVIRONMENT" value="value passed to publish" />
    </environmentVariables>         

All 91 comments

FWIW, I agree. We just need to be clear that it's for IIS settings. The other thing is the fact that we've moved away from having a default web.config in the template so that would need to be figured out.

/cc @sayedihashimi

Is this just for IIS though? I think the crux of the issue is the use of ASPNETCORE_ENVIRONMENT variable fullstop to let a website know which environment it is in.

I think a pretty common scenario is for various staging/QA environments to be hosted on the same box in the same IIS instance. There might be 3 teams all with their own "environments" that they can deploy to to test their code (Say deploy a particular branch), but they are all using the same box.

If we take some example code for how appsettings.json is switched per environment :

public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) .AddEnvironmentVariables(); Configuration = builder.Build(); }
The scenario above is not going to work because the environment variable is system wide. It could be IIS, nginix etc sitting infront of it and the swapping of configuration is not going to work.

Most times I have seen the above come up, suggestions have come up to use Docker, or use something like Puppet/Chef to manage app configuration so there is no appsettings.{env}.json, but I think that makes the path from full framework MVC that much harder.

@mindingdata You can actually control the logic that determines where the environment comes from in your Program.cs. The environment variable is just one way to declare that (the main way). If you want to do something else based on some magic file or time of day then call UseEnvironment(environmentName) on the WebHostBuidler

Just to clarify, this is absolutely just for IIS config. Current JSON config system for app settings is quite good and I fully recommend it.

To muddy things further: I find that when I use web.config to try to override the system environment setting, it simply doesn't work. When my system environment setting is set to "Development" and I try to set it to "Production" using the web.config (as below), the application still thinks it is running in Development. (ASP.NET Core app running in IIS).

<environmentVariables>
      <environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Production" />
</environmentVariables>

I think this is the right place. I build a N-tier demo and I can't seem to figure out how to inject IHostingEnvironment in to my data access layer project. That project is where I access my database and the only way I got the connection string to that layer was to override the OnConfiguring method with a magic string of the config file.

I also looked at slow-cheetah, but that seems like going around the current method of either "Debug, Release" or "Development, Production" depending on if you are using the VS method or the environment variables.

There must be information that I'm missing. MS can't want everyone to build single tier apps.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
           var builder = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json");

            var config = builder.Build();
            var connstr = config.GetConnectionString("connStr");            
            optionsBuilder.UseSqlServer(connstr);
        }

so has there been any progress on this?

we should be able to set the env in the config for an app without resorting to magic

Yeah I'd like some progress on this too. Currently have no idea how I'm supposed to publish seperate projects to the same box using different environment variables.

I ended coming up with a fairly simple solution. In the main appsettings.json file, I specify my desired environment:

{
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "ActiveEnvironment": "Development"
}

In Program.CS, I then build an IConfiguration, and set the environment name to the value of the 'ActiveEnvironment', before loading the environment specific configuration file.

public static void Main(string[] args)
{
    WebHost.CreateDefaultBuilder()
    .ConfigureAppConfiguration((hostingContext, config) =>
    {
        // Get the environment from our hostContext.
        var env = hostingContext.HostingEnvironment;
        config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);

        // Build an initial configuration.
        IConfiguration Configuration = config.Build();

        // Set the environment name.
        env.EnvironmentName = Configuration.GetSection("ActiveEnvironment").Value;

        // Load the configuration file for our specific environment.
        config.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: false, reloadOnChange: true)
        .AddEnvironmentVariables();
    })
    .UseStartup<Startup>()
    .Build()
    .Run();
}

Obviously, when publishing to different environments, the 'ActiveEnvironment' needs to be changed accordingly.

@DaleMckeown I was going to do something similar but I had a question about your implementation. I see you are using the Microsoft.AspNetCore.WebHost to create the default configuration. Does this approach still apply for a console application with no web components?

@Swazimodo I haven't tried this solution int the context of a console application, so I'm not sure. Best bet its to see if there is an alternative way to setting the environment variables for console applications.

We took care of this in a different way. We setup our build agent to take
the environment specific settings file and overwrite the root settings file
with it. Our build makes a separate zip file for each environment that we
deploy to.

On Thu, Oct 26, 2017 at 12:48 PM, Dale Mckeown notifications@github.com
wrote:

@Swazimodo https://github.com/swazimodo I haven't tried this solution
int the context of a console application, so I'm not sure. Best bet its to
see if there is an alternative way to setting the environment variables for
console applications.


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/aspnet/Home/issues/2019#issuecomment-339728441, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAx4bT20JSb0XSzNBIAiiubq9mTVKbW5ks5swLf6gaJpZM4NMx25
.

It seems all solutions require some kind of post build/publish tweaking? I ended up dropping an empty text file in the app root and pass that in as environment. Did I miss anything?

Totally agree with the first post. It's by design pretty flexible but at the end it's turned out to be very hard to automate. For example I have an app which I publish to IIS on another server. Also it runs on local IIS (via folder publish). So I set Development environment in web.config. But then I need to change it onto Production or whatever during publishing. Pretty common task I guess. I believe it's impossible, right? I have to manually edit web.config on remote servers.

Ideally setting Environment should be supported via "dotnet publish" (for any publish target):

dotnet publish ... -environment=Production

dotnet publish could search for web.%Enviroment%.config (Web.Production.config, Web.Development.config) files and merge (transform) it with web.config. At the end we'd have correct value of ASPNETCORE_ENVIRONMENT:

    <environmentVariables>
        <environmentVariable name="ASPNETCORE_ENVIRONMENT" value="value passed to publish" />
    </environmentVariables>         

@evil-shrike have a look at https://github.com/nil4/dotnet-transform-xdt for build/publish time config transforms.

A use case, perhaps. I need logs to be written to .\App_Data because my application runner has read/write access to everything under .\App_Data, but can only read everything else under .\ . I use web deploy, and would like to be able to deploy a web.config like this one with the logs folder under App_Data:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <handlers>
      <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" />
    </handlers>
    <aspNetCore processPath=".\<assembly>.exe" stdoutLogEnabled="true" stdoutLogFile=".\App_Data\logs\stdout" />
  </system.webServer>
</configuration>

You might ask, why not just put those settings in a web.config file instead of web.release.config? Well processPath=".\<executable>.exe" is not where the executable lives when I'm developing locally against IIS Express. So I get the good ol' "HTTP Error 502.5 - Process Failure" error.

We automate our builds using Jenkins and our existing way of doing vars configuration per environment in NetFramework projects is through web.config transforms. So we thought we will be able to do it in dotnet as well.

It turns out that msbuild is still compatible with aspnet core csproj for doing web.config transforms!

We can add the TransformXml publishing task in the Asp.Net Core csproj:

<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v15.0\Web\Microsoft.Web.Publishing.Tasks.dll" />

Add the specific transform targets:

<Target Name="ConfigDev">
    <TransformXml Source="web.config" Transform="web.dev.config" Destination="web.config" /></Target>

Do some transformations, for example on the ASPNETCORE_ENVIRONMENT variable in the web.dev.config:

<system.webServer>
    <aspNetCore processPath="dotnet" arguments=".\Test.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" >
      <environmentVariables>
        <environmentVariable xdt:Locator="Match(name)" name="ASPNETCORE_ENVIRONMENT" value="dev" xdt:Transform="SetAttributes" />
      </environmentVariables>
    </aspNetCore>
</system.webServer>

Transform web.config using msbuild before running publish:

msbuild Test.csproj /t:ConfigDev

"Let me prefix this by saying that I'm no fan of web.config but experience of publishing AspNetCore apps to IIS is really poor."

I couldn't agree more with this thread.

After the simplicity of config transformations in regular ASP.Net, this whole dependency on a machine-wide ASPNETCORE_ENVIRONMENT variable stinks.

Even Microsoft's own examples say we can use, say, an _appsettings.Production.json_ file, just by adding this line...

            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);

...but it pays no attention to the NAME of the configuration we've chosen. So even when I have chosen my Production configuration, it'll still try to open appsettings.development.json. And no, I don't want to have to manually edit the ASPNETCORE_ENVIRONMENT variable myself each time I publish to a different environment. That's just asking for trouble.

I've yet to see a simple, working example of using _appsettings.XXXX.json_ files.
I can't wait for this stuff to finally be finished. (Sigh...)

Yes, please fix this!!

I have just deployed my first production dotnet core site, and now face the issue that I can't easily have a staging instance on the same server because I don't know how to publish my app onto the same box without it picking up the production settings.

I think I'm going to try @DaleMckeown solution for now, I think it will work but it does feel like a workaround rather than a solution. Environment variables are great but ONLY if you have a 1-1 ratio of servers to environments, and outside of enterprise I doubt that happens often.

@willapp My suggestion is definitely a workaround. But it does work - I've had multiple versions of the same app running on the same server for a while now and haven't had any issues.

Just remember to change the 'ActiveEnvironment' setting before you publish and you should be fine.

I am facing exactly what dkent600 mentioned:

<environmentVariables>
      <environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Production" />
</environmentVariables>

does not work on production server. The application keeps using "appsettings.Development.json" values, which, ultimately defeats the benefits of xml transformation when publishing.

I will try the workaround suggested by DaleMckeown.

the only reasonable way to change ASPNETCORE_ENVIRONMENT is using web.config

Not really. If you need to run multiple instances of the same site on the same machine, here's a much simpler way

  • MyCoreSite

    • bin

    • logs

  • env.Whatever

Point your IIS app to bin and in program.cs

.UseEnvironment(ReadEnvExtensionFromParentFolder())

That's it. You can have as many sites as you want, deploy into bin without worrying about transform, and even switch environment by renaming env.Whenver.

I upvote this. I have URL rewrites configured for production server that do not work in the test environment. I have not found a way for publish to select the right web.config file for deployment, I have to copy it manually.

Is there any progress on this? It has been 13 months since the initial report, 2.1.0 is here now, and things are still pretty poor with traditional IIS hosting. Things work wonderfully if you're using Azure, but that's not currently an option for a large portion of the sites I'm hosting.

It seems that for traditional IIS hosting it would be beneficial to set the desired environment on the command line (as @evil-shrike laid out above), and also within any .pubxml publish profiles. As @davidfowl said, it would need to be clear that this would just work using web.config and IIS, but that surely still covers a substantial part of the install base.

At the moment I have a very brittle solution using Microsoft.DotNet.Xdt.Tools and web.config translation, but it requires a build configuration per environment to get working and has just broken after updating to v2.1.0.

It'd be great to hear if there are any plans in this area and, if so, what they are? If there are no plans, or they are a long way off, then surely there's a better interim solution than the brittle/semi-manual ways we are currently forced to employ? Especially for deploying to QA and staging, I'd really like to be able to just press a button in VS and know the right thing is happening, and not have to depend on head knowledge and manual processes.

We don't have any plans currently but this tool https://github.com/nil4/xdt-samples/ by @nil4 looks pretty good.

@svallis have you tried it?

Is the main thing people want to set the environment?

@davidfowl Yeah, my current solution is using the tool you linked, but for a project with multiple destinations to publish to it requires a build configuration per environment. You need the different build configurations in order to have something to latch onto in the csproj setup, and for my purposes it's generally a 1 to 1 relationship with your environments. For example:

  • Build configuration: Debug = Environment: Development
  • Build configuration: Staging = Environment: Staging
  • Build configuration: Release = Environment: Production

For latter two, I then have the corresponding web.{Build configuration}.config and appsettings.{Environment}.json, and the web.{Build configuration}.config is literally just a XDT to tell the web.config to end up with the appropriate ASPNETCORE_ENVIRONMENT environment variable. So for example, continuing the example above, web.Release.config looks like this:

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <system.webServer>
    <aspNetCore>
      <environmentVariables>
        <environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Production" xdt:Transform="SetAttributes" xdt:Locator="Match(name)" />
      </environmentVariables>
    </aspNetCore>
  </system.webServer>
</configuration>

This actually all works pretty well from the command line. Running dotnet publish -c Staging or whatever produces a folder with the correct output and the web.config transformed as required. It does feel like it could be simplified though. If there was an -iisenvironment switch on dotnet publish which did this for us, and then that was exposed as a simple text box in VS publishing, it would remove the need for managing build configurations, XDTs, etc manually for traditional IIS hosting situations.

From within VS it currently seems problematic, though. I won't go into the details yet as I'm still trying to pin down where my issues are exactly, as we're still working through the conversion from v2.0.0 to v2.1.0 and it's possible the problems I'm having are unrelated to this. We're currently unable to publish from within VS at all though, and even when it did work on v2.0.0 we had to manually select the correct build configuration before publishing. We've reverted to publishing via the command line for the time being, but this is far from ideal. Ignore this, the problems I'm having in VS seem unrelated to this, so I'll create a new issue for that if I can reproduce it in a test repo.

@davidfowl From my perspective, Ideally, I'd like to set the environment as part of the publish profile when using Web Deploy.

I'd second that, an option on publishing would be ideal. I'm not even using the workaround described by @svallis as I prefer to publish via the VS IDE and not command line.

@willapp The solution I'm using does work in Visual Studio, but I have other issues with the solution I'm testing with that are causing issues in VS (https://github.com/aspnet/Home/issues/3190). Sorry if I didn't make that clear. It's all setup using tools in the csproj, which both the command line and VS should respect.

Finally got around to trying the tool suggested above by @davidfowl and it works! 👍

Just followed the instructions here https://github.com/nil4/dotnet-transform-xdt under the "Project-level tool" section (since I'm not on 2.1.0 yet). I had to create a default Web.config myself as I didn't have it (just copied it from the one VS generates when you deploy), then created a Web.Debug.config with a transform to set ASPNETCORE_ENVIRONMENT to Development, and hey presto! It deploys the transformed config and the site now starts up correctly.

Would be nice if the support for this was made official, but for now this is doing the job.

@davidfowl The link you pointed requires publishing using the command-line. I would like to publish using the VS GUI tool. I do not want my developers to need to install cli tools for publishing the project. There is no point of being able to specify ASPNETCORE_ENVIRONMENT in web.config if transforms are not supported. Seeing how many upvotes this issue has, it should go to the backlog.

@orobert91 VS is just a GUI wrapped around the command line tools. I'm successfully using https://github.com/nil4/xdt-samples/ to transform the web.config during publish from within VS. If you configure it correctly for your project, then it will transform correctly both from the command line and within your IDE.

@svallis Indeed, misread the docs. I have solved my problem with simple copy task:

  <Target Name="CopyFiles" AfterTargets="Publish">  
      <Copy SourceFiles="web.$(Configuration).config" DestinationFiles="web.config" />  
  </Target>   

There is an msbuild property that publish honors for Environment name -

$(EnvironmentName)

(https://github.com/aspnet/websdk/blob/d7d73e75918ec3168bd3e5d519d0decc04675faf/src/Publish/Microsoft.NET.Sdk.Publish.Targets/netstandard1.0/TransformTargets/Microsoft.NET.Sdk.Publish.TransformFiles.targets#L79 ).

Right now, when this property is set, all we do is create appsettings.$(EnvironmentName).json and add the connection string info to that file (if requested).

Going forward, When this property is set during publish, we can also update the published web.config to have the following entry as well:

<environmentVariables>
      <environmentVariable name="ASPNETCORE_ENVIRONMENT" value="$(EnvironmentName)" />
</environmentVariables>

Would this work for most of the scenarios mentioned here?

For the scenarios outside of setting the environmentVariable, we will look at the feasibility of porting the web.config transforms (Xdt) for core projects.

I've come up with a solution that seems to cover the scenarios I require - I wanted to post it here in the event it is helpful for anyone else.

In my case I've got my development environment locally and both my staging and production environments on a remote server (same server). I was able to pull the application url from the IApplicationBuilder and use that in a switch statement to set the EnvironmentName value.

It seems much less "workaround-ish" than some of the other solutions offered but certainly comes down to preference.

In Startup.cs:

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            env.DetectAndSet(app);

            ... other config method stuff here ...
        }

The method above is an extension I put together based on the properties collection within the IApplicationBuilder. The extension method (and class) look like so:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Hosting.Server.Features;
using System.Linq;

namespace MyRootNamespace.Common.Extensions
{
    public static class IHostingEnvironmentExtensions
    {
        /// <summary>
        /// Detects the current hosting url and sets the <see cref="IHostingEnvironment.EnvironmentName"/> accordingly.
        /// </summary>
        /// <param name="env">The <see cref="IHostingEnvironment"/> to set.</param>
        /// <param name="app">The <see cref="IApplicationBuilder"/> used to retrieve the current app url.</param>
        public static void DetectAndSet(this IHostingEnvironment env, IApplicationBuilder app)
        {
            var _appUrl = string.Empty;

            try
            {
                _appUrl = ((FeatureCollection)app.Properties["server.Features"]).Get<IServerAddressesFeature>()?.Addresses?.FirstOrDefault();
            }
            catch { }

            switch (_appUrl)
            {
                case "https://www.myurl.com":
                case "http://www.myurl.com":
                    env.EnvironmentName = EnvironmentName.Production;
                    break;
                case "https://staging.myurl.com":
                case "http://staging.myurl.com":
                    env.EnvironmentName = EnvironmentName.Staging;
                    break;
                default:
                    env.EnvironmentName = EnvironmentName.Development;
                    break;
            }
        }
    }
}

I hope this can be of some use to folks (in those cases where a web.config transform is not desirable).

Cheers!

I'm marking this issue as a discussion because no features/fixes related to this are planned at this time. If people still have specific feature requests, please file issues for those and we will prioritize.

I wasn't really satisfied with the workarounds I saw on this thread, so I took a different approach. I stumbled on this thread Stack Overflow: https://stackoverflow.com/questions/31049152/publish-to-iis-setting-environment-variable/36836533#36836533. It talks about modifying ApplicationHost.config on the server as a place to store the ASPNETCORE_ENVIRONMENT variable.

Our internal IIS servers are managed by our operations team, so the devs in our organization don't create those sites anyway. The operations team uses a PowerShell script to create these sites. I just asked them to modify that script so that it also makes the relevant changes to ApplicationHost.config at the time the site is provisioned. Now I can just deploy there and not worry about managing the .NET Core environment. Worked well for our situation.

I would think that in a true DevOps environment you'd probably be using Azure anyway, which makes it very easy to set the environment variable.

With the latest Sdk, you can just pass the msbuild property $(EnvironmentName) & tooling will take care of setting the ASPNETCORE_ENVIRONMENT to this value.

for e.g: If EnvironmentName is set to staging, then web.config will have the following entry:

      <aspNetCore processPath="dotnet" arguments=".\WebApplication242.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout">
        <environmentVariables>
          <environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Staging" />
        </environmentVariables>
      </aspNetCore>

PR with the fix:
https://github.com/aspnet/websdk/pull/377

@vijayrkn Looks like a promising start. Will this eventually have support built into VS WebDeploy/publish profiles etc?

Right now we don't have a UI in VS to make this Environment name selection but if this this added to the project file or the pubxml, both publish from VS and commandline will honor it.

We will be looking at adding UI support for this as well.

@vijayrkn This is only a solution for those publishing with a web.config file - is that correct?

@vijayrkn Yeah, getting support into the pubxml format would be the moment where this would become useful for our use case. At that point we could drop all the XML transform stuff and just specify the appropriate environment strings in each pubxml. UI support could come at a later date, but that'd just be the icing on the cake.

@challamzinniagroup - No, you don't need a web.config for this functionality.
All you need to do is add the following to the pubxml file and publish tooling will take care of the rest.

<EnvironmentName>YourCustomEnvironment</EnvironmentName>

@challamzinniagroup , @vijayrkn

Unless you have some configuration that can only be done in web.config and must be transformed between environment, otherwise, leave web.config alone. With 2 lines of code, you can read environment variables from anywhere. And you don't need any tweaking in publish profile, or published code, just list of matching appsetting.XYZ.json

@ThisNoName

You mean like database connection strings, service endpoints, credentials, or any number of other things that would change from dev->staging->production.

If you are running multiple instances on the same environment (very common outside of cloud), you absolutely need the flexibility of web.config transforms. It's pretty ridiculous that Microsoft didn't cater for this requirement out of the box with dotnet core, they just assumed everyone was hosting in Azure and had a 1-1 relationship between instance and environment.

Environment variables actually suck outside of cloud, since you end up with configuration separate from your app, making it harder to manage deployments full stop.

The major change here is that those secrets should never be stored in source control and instead, should be configured on the environment itself. Transforms flies in the face of good practice because it assumes that you understand the deployment target at publish time which isn't always the case (especially when you have multiple environments). Do you really need to re-deploy when you change a configuration setting for one of those N deployments?

Now the unfortunate part is that with IIS, environment variables need to be set up in the configuration file so that the app pool can see them. I don't think we ever intended for people to setup machine wide configuration on target machines (though I've seen people do that).

@willapp Why don't you use appsettings.json?

@willapp

M$ has the perfect solution, but most people stuck with the concept of web.config transform.

Here's one way you can have multiple sites side by side. You just need to throw your environment value anywhere outside IIS binary directory, and read/set it from program.cs

  • IISRoot

    • MySiteDEV

    • bin



      • appsettings.json


      • appsettings.DEV.json


      • appsettings.PROD.json


      • web.config



    • logs

    • env.DEV

    • MySitePROD

    • bin



      • appsettings.json


      • appsettings.DEV.json


      • appsettings.PROD.json


      • web.config



    • logs

    • env.PROD

@davidfowl

Are you telling people it's unfortunate that ASP.NET core can't work well with IIS? As Microsoft .NET Core Architect?

The paradigm shift here with anything Core is multi-platform. IIS is clearly a Windows utility - and so some growing pains should be expected. Workarounds, in a case such as this, should be expected and accepted as things move forward. To have made intentional design choices in Core to support a Windows feature would have been counter-intuitive imo.

Edit to add:
This being a thought on whether Core should have better support out-of-the-box for IIS. The discussion of using environment variables being a good alternative notwithstanding...

@davidfowl

And this is exactly the difference between enterprise architecture and SME. I work for a FTSE100 and yes, separating deployment from code makes sense, but I also run several sites independently and it's just easier and more cost-effective to have a single server that I host all my sites on. Using Web Deploy + Transforms is a tried-and-tested mechanism for delivery code changes, it just works. I'm not saying there aren't better alternatives in some cases, but Microsoft should have continued support for transforms knowing that most users want familiarity when migrating from .net framework to core.

I have my solution now from earlier in this thread, so I'm happy enough. I just think they dropped the ball a bit not including this as standard.

@willapp

I think transform is based on the idea of compile from source and publish separately for each environment. I'm glad M$ dropped it because the alternative is so much more elegant.

With the new setup, you only publish your code once using Release config. All running instances are identical binary copies of the original. Deployment should just be simple file sync to the next environment DEV=>TEST=>Staging=>PROD.

Are you telling people it's unfortunate that ASP.NET core can't work well with IIS? As Microsoft .NET Core Architect?

I'm saying that people don't immediately think of the fact that environment variables used like this are supposed to be per process instead of per machine. Historically on windows people identify environment variables as machine wide instead of per process and that has caused confusion. IIS 10 added support for adding per app pool environment variables. The ASP.NET Core module also has support for setting environment variables per process.

Still this capability has been there. I think there's mostly been a philosophical shift in thinking here. We don't want to do anything that makes it easier to specify secrets at development time.

@challamzinniagroup good summary

This being a thought on whether Core should have better support out-of-the-box for IIS. The discussion of using environment variables being a good alternative notwithstanding...

Right. The workflow for configuring environment specific information should still be on the specific environment. The question is how do you get it there. Is it part of deployment?

All of that said, I believe we are adding support for this in tooling in 2.2.

cc: @vijayrkn

@davidfowl

When you say "Historically on windows people identify environment variables as machine wide instead of per process", do you mean Windows environment variables, as in command line SET?

Because my understanding of "environment" inside .NET core context, is just a variable. By default, it reads ASPNETCORE_ENVIRONMENT from system or web.config. But you can overwrite and get it from any source by any convention.

Is there anything inherently wrong with that approach? Because if Microsoft .NET team's official position is don't do it, we would have to seriously reconsider. But you would end up telling people to run .NET core under IIS, but unfortunately, no easy way to config those apps. Hope that's not the case.

Thanks.

@ThisNoName

When you say "Historically on windows people identify environment variables as machine wide instead of per process", do you mean Windows environment variables, as in command line SET?

Yes, this is exactly what this thread is all about. (See here). As David says, these can be per process, but in my experience most of the documentation on dotnet core assumes you set them at machine level, hence we have this problem where you can't easily deploy multiple app instances to the same server, as they will pick up the same environment values so you can't distinguish between dev/staging/prod.

The fact that IIS10 supports setting them per app-pool is great...if you have Server 2016 available. I'm running 2012 so this isn't an option. Again, my issue isn't that Microsoft are trying to move things in a better direction, but dropping 'legacy' support for something that a lot of people took for granted in deploying ASP.NET applications feels like a mistake to me. By all means introduce new patterns and encourage people to adopt them, but don't remove the legacy behaviour until you're confident that few people need it. Otherwise all you're doing is putting barriers up for customers who want dotnet core but find the migration path too painful.

@willapp

That's just the default out of the box setting. You can read and set environment whichever way you want. At least that's my understanding, unless someone point out any serious flaw in this approach?

   public class Program   {
        public static void Main(string[] args)   {
            CreateWebHostBuilder(args).Build().Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args)   {
               return WebHost.CreateDefaultBuilder(args)
                .UseEnvironment(ReadEnvFromWherever())
                .UseStartup<Startup>()
        }

        private string ReadEnvFromWherever() { 
              // Get and return whatever by your own convention
              return "DEV";
        }
    }

@ThisNoName

The serious flaw is, how do you make the "ReadEnvFromWherever" method return a different value when deploying using Web Publish from VSTS, based on your Solution Configuration? I suppose you could use preprocessor directives (#if RELEASE ...) but that feels pretty dirty.

In the "old world" (ASP.NET full framework) you create a publish profile, and part of that is selecting a solution configuration to build. Based on that configuration it then applied a config transform which allowed you to specify different configuration based on whether you were publishing to dev/staging/prod.

I accept that this workflow doesn't fit into a modern CI pipeline, where you take a single build package and push it through each environment - in this case you want something like the VSTS release pipeline to inject settings at deployment time, or you prime your server instance with the right environment variables in which case you just drop the package onto the box and it will auto-magically work. Again though, I think a lot of SMEs and one-man-bands aren't in this place, and want a process of deploying from Visual Studio that produces the result they want.

Just to add that this is also important for deploying small projects to shared hosting platforms that run on IIS and traditionally offered ASP.NET hosting. They don't have any facility to set environment variables per website through their hosting control panels, and so the only realistic approach is to use transforms and the web.config.

It sound like this is all going to have a decent solution in the next release though, and with built in VS support following a little further down the line. Hopefully that will support people with existing workflows that rely on this functionality until hosting providers and legacy systems can be upgraded or improved.

@willapp

By convention. If you go by example I posted earlier, you can read env.DEV from parent folder and use file extension as your environment variable. But you can go by any convention, like name your app root as MySiteDEV, MySitePROD, etc. and parse folder name to get environment name.

If your code vary by environment, you can read environment value from IHostingEnvironment .EnvironmentName

This is no difference from .NET core reading ASPNETCORE_ENVIRONMENT from system variable or web.config.

@ThisNoName

But how do you get _env.DEV_ into the parent folder, other than copying it up manually? I see what you're trying to say, but I think you are missing the point here: there used to be an out-of-the-box solution that just worked. Now there isn't because tooling support for it wasn't considered as part of the dotnet core deployment process.

I get _why_ it was removed (because there are better alternatives if you have a decent CI process), but think it was pretty short-sighted when a lot of migrating users would still want it.

@willapp

You copy it once during initial setup and all future deployment go into bin folder. No recompile, no publish, no transform. That's the way it should be, takes extra 3 lines of code, so what? It's M$. Up till 3 months ago, you can't even call Active Directory in .NET Core.

@ThisNoName Do you expect to be taken seriously with this M$ stuff. You do realise it's 2018, right?

Your suggestion doesn't make any sense in the context of a web deploy to a shared hosting platform. I'm not even sure what you're arguing for or against, the functionality is being added into the next release of ASP.NET Core so that an environment variable can be specified in the publish profile, no need for any transforms or anything. There's also no need for non-standard approaches with folder names, which may or may not be under your control.

@willapp , @svallis
The solution I posted a little further up works in some cases. You can pull the Url and set environment based off that. In any hosting environment you have full control of your Urls. I realize this is still "hackish", and a workaround - but it should generally work most of the time. In the case of my hosting company, the Url I was receiving was a localhost:port base ("127.0.0.1:7777") - so it turned out to not be ideal for me. But I also have the ability to set root folder paths on my various environments as I have them set up as subdomains - and pulling root app path is pretty simple as well:

        public static string DetectAndSet(this IHostingEnvironment env, ILogger logger)
        {
            switch (env.ContentRootPath)
            {
                case "h:\\root\\home\\www\\api":
                    env.EnvironmentName = EnvironmentName.Production;
                    break;
                case "h:\\root\\home\\www\\api-staging":
                    env.EnvironmentName = EnvironmentName.Staging;
                    break;
                default:
                    env.EnvironmentName = EnvironmentName.Development;
                    break;
            }

            logger.LogInformation($"Application root path discovered as '{env.ContentRootPath}'. Environment set to '{env.EnvironmentName}'");

            return env.EnvironmentName;
        }

The choice of whether or not environment should be determined within code, at deploy-time, or pre-determined directly in the environment ahead of time is not the discussion I am trying to tackle - I am simply offering a reasonable fix for those SME/individuals who have limited hosting control and need a fix. In my case this is a code-once-and-forget - exactly what I need.

Further downstream I can easily pull config values from an appsettings.json file without any need of transforms by simple including:

"{configName}.{environmentName}": "{value}"

in the settings file and having 3 entries per config - one for each environment. I only have a very small number of configs in this file and so the duplication is not a big deal.

Hope this helps!

@challamzinniagroup It's easier to use relative folder path and name by convention.

@svallis The whole concept is to publish once and deploy at binary level. So even if you can set variable per publish profile, that's not the way to go.

@ThisNoName For you, perhaps it is easier - but don't assume such things for others. In my case I have settings in place to clear the destination directories prior to publish/copy - and so for your solution I would have to re-upload each environment file manually, every time. That is definitely _not_ easier.

@challamzinniagroup Agree. Every situation is different. Bottom line is that environment is just a variable, you can control it anyway you see fit, instead of waiting Microsoft to deliver the next best thing. And whatever they come up, is just another convention based solution anyway -- reading some value from system or web.config and set environment.

BTW, you can still use convention. There's no saying that appsettings have to use single middle word and Staging/Production have no special meanings. You can easily do something like this and pass in the folder name to match.

appsettings.api.json
appsettings.api-staging.json

With convention based solution, you can spin up new instances without having to recompile and/or republish.

@vijayrkn could you please elaborate more on using pubxml?
I upgraded my SDK to 2.1.302 (x64) changed my pubxml but cannot see any difference on web.config published.

@wggley - The fix is not available in 2.1.302. It should be available in the next upcoming release.

You can try the preview version of the cli containing the fix from here - https://dotnetcli.blob.core.windows.net/dotnet/Sdk/release/2.1.4xx/dotnet-sdk-latest-win-x64.exe

Let me know if you run into any issues with the above preview version.

Thanks @vijayrkn I'll wait for next upcoming release and try to use on a new publish.
I would like to point that @frankhoffy solution worked for me like a charm:
https://stackoverflow.com/questions/31049152/publish-to-iis-setting-environment-variable/36836533#36836533
Look for first answer (most voted answer).

Web config transform support during publish is now available in 2.2 preview1 https://www.microsoft.com/net/download/dotnet-core/2.2

@vijayrkn is there a basic sample somewhere? It would be great to point people to the hello world equivalent using a basic transform that sets ASPNETCORE_ENVIRONMENT.

To set ASPNETCORE_ENVIRONMENT, users can set the msbuild property '$(EnvironmentName)'. I will add a sample that shows how to set other env variables using web.config transforms.

Here is a sample repo - https://github.com/vijayrkn/webconfigtransform

Readme has details on how this works:
https://github.com/vijayrkn/webconfigtransform/blob/master/README.md

Hope this helps!

@Eilon - We can close this issue. This is fixed and available in the latest preview.

@vijayrkn It's not clear how to configure the Publish settings to set the environment from the Publish settings dialog? What actually can be changed in this dialog to distinguish a Staging site from a Production site, if both sites are being deployed to the same server?

This aspect of .NET core seems very messy. It needs to be made much easier to specify the environment on a per-folder basis. A whole-server environment variable cannot work for our situation at all. Not everyone can run their staging sites on different servers to their production sites.

The setting is per application.

Sorry but that doesn't really answer my question. Perhaps I'm misunderstanding something.

There's some pseudo documentation here https://github.com/vijayrkn/webconfigtransform/blob/master/README.md (@vijayrkn we need to get this into the real docs).

@vijayrkn How much support is there for this in the publish dialog?

Currently the publish dialog does not have any support for setting the publish environments but the environments can be set manually in the publish profile or specific transforms can be applied.

@nmg196 - If you want to just set the 'ASPNETCORE_ENVIRONMENT' in the web.config, all you have to do is add the environmentname to the publishprofile.

example:
https://github.com/vijayrkn/webconfigtransform/blob/master/Properties/PublishProfiles/FolderProfile.pubxml#L18

For advanced scenarios (other than setting the ASPNETCORE_ENVIRONMENT environment variable) web.config transforms can be applied as mentioned in the documentation above.

@davidfowl - Angelos has an item to get the web.config transform support documented.

@vijayrkn How to use in CI/CD of TFS? I couldn't use the dotnet publish with p:CustomTransformFileName in TFS

You can pass this msbuild property to dotnet publish.

You can refer to this sample here - https://github.com/vijayrkn/webconfigtransform/blob/master/publish.cmd

This passes the CustomTransformFileName property to dotnet publish

dotnet publish /p:Configuration=Release /p:EnvironmentName=Staging /p:CustomTransformFileName=custom.transform

@vijayrkn
I have read your documentation and cannot get the web.config to transform. I've tried basically all of the commands you have in the readme.md and none of them result in the transformation being performed.

I have however been able to write a powershell script which uses Microsoft.Web.XmlTransform.dll to perform the transformation separate from the publish operation.

My question is what version of msbuild and dotnet are you using?
I am using:
dotnet --version 2.1.104
dotnet msbuild -version 15.6.84.34536

Thanks

@stephenarsenault1
You need to install any of the builds from here for the feature to light up - https://www.microsoft.com/net/download/dotnet-core/2.2

Latest - https://www.microsoft.com/net/download/thank-you/dotnet-sdk-2.2.100-preview3-windows-x64-installer

@davidfowl wrote:

There's some pseudo documentation here https://github.com/vijayrkn/webconfigtransform/blob/master/README.md (@vijayrkn we need to get this into the real docs).

@vijayrkn How much support is there for this in the publish dialog?

Do you know whether this was ever added to the real docs, if so do you have the link please ?

We've moved over to using <EnvironmentName> in our publish profiles now. It would be nice to have some UI on top of this so that it is more visible, though. I presume this will come at a later date?

I think it is a breaking change to enable by default web.config transforms on dotnet publish. After installing .NET Core SDK 2.2.101 it broke our CI/CD scenario. Our pipeline configured to create published artifact using dotnet publish only once for any release (build once rule), transformation applied automatically on real deployment (performed by Octopus). I could not found any mention in documentation about this change. Spent a bit of time trying to figure out what the reason for doubled configuration blocks in web.config after deployment. Apparently, transformation was applied twice: by dotnet publish and by release management tool.

After disabling web.config transformations with <IsTransformWebConfigDisabled>true</IsTransformWebConfigDisabled> setting, TransformWebConfig also skipped (along with TransformXml), which results in %LAUNCHER_PATH% and %LAUNCHER_ARGS% variables not being replaced during publish process. Is there a setting to control these steps independently?

Does this work in a console app with the App.Config file?

Can we see an example?

@dmitryshunkov you can set $(RunXdt) to false to just skip running the custom Xml tranform.

@auserx - if it is an sdk style project, then it should work.

Was this page helpful?
0 / 5 - 0 ratings