configuration.getValue or configuration.getsection always returns null

14,309

Solution 1

When hosting in-process inside of IIS (or IIS Express), Directory.GetCurrentDirectory() will return a different path to that which is returned when running out-of-process. Up until ASP.NET Core 2.1, IIS-based hosting was always out-of-process, but ASP.NET Core 2.2 introduces the ability to run in-process (which is the default when creating a new project).

When running out-of-process, Directory.GetCurrentDirectory() will return the path to your ASP.NET Core application itself, whereas when running in-process, it will return the path to IIS (or IIS Express, e.g. "C:\Program Files\IIS Express").

From your question, the relevant code is this:

WebHost.CreateDefaultBuilder(args)
    .ConfigureAppConfiguration((hostingContext, config) =>
    {
        config.SetBasePath(Directory.GetCurrentDirectory());
    })

Here, before you make a call to SetBasePath, the IConfigurationBuilder has already been set up to use the correct path. Your call itself is overriding this path, setting it to e.g. "C:\Program Files\IIS Express". With this overridden base-path, your appsettings.json et al files are no longer found, as they do not live in e.g. "C:\Program Files\IIS Express", and so no configuration is loaded from these files.

The solution is simply to remove your call to ConfigureAppConfiguration so that the base-path does not get overridden. I realise you've already discovered this, but I wanted to make sure you had an explanation as to what was going wrong here.

Solution 2

Accessing the configuration in the Controller works a little different than in the Startup.cs. I did this a while back, just follow these steps:

Nuget: Microsoft.Extensions.Configuration.Binder

Put all the Configuration you want to access in your Controller into one Section, e.g. "EmailSettings":

appsettings.json

{
  "EmailSettings": {
    "EmailTemplatesPath": "EmailTemplates",
    "Email": "[email protected]"
  }
}

Then Create a Class in your Web API Project called EmailSettings.cs :

EmailSettings.cs

public class EmailSettings
{
    public string EmailTemplatesPath { get; set; }
    public string Email { get; set; }
}

Then bind your Config values to an Instance of the EmailSettings Class in Startup.cs and add that Object to the Dependency Injection Container:

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    ...
    EmailSettings emailSettings = new EmailSettings();
    Configuration.GetSection("EmailSettings").Bind(emailSettings);
    services.AddSingleton(emailSettings);
}

Now you can request your Configuration in the Api Controller by simply adding it to the Constructor like this:

Web API Controller

[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    EmailSettings _emailSettings;

    public ValuesController(EmailSettings emailSettings)
    {
        _emailSettings = emailSettings;
    }
....
}

Just tried this again in my current Project (.NET Core 2.2 Web Api) and it worked. I put a Breakpoint in the ValuesController Constructor and the _emailSettings Object contained the values from the appsettings.json file. You should be able to just Copy & Paste this! Have fun! :)

Solution 3

You made a simple mistake and forgot to add "Email" into "EmailSettings". Update your json as shown below and get the Email with config.GetSection("EmailSettings")["Email"];

"EmailSettings": {
    "EmailTemplatesPath": "EmailTemplates",
    "Email": "[email protected]"
  },

Hope this solves your problem.

Edit:

If you wanna get those values from appsettings to anything other than startup, you should load those config values into a settings class and then inject appropiate IOptions instance to the method constructor in which you want to use those settings. To do so, please see my answer here.

Solution 4

While others have solved this problem, if you want to leave your startup services code as is, just change your Web API controller to the following:

private readonly EmailSettings _emailSettings;

public EmailController(IOptions<EmailSettings> emailSettings)
{
    _emailSettings = emailSettings.Value;
}

The emphasis is on the .Value. That's why your code is returning null. I would also suggest changing program.cs back to it's default. And to use this in an action method, just do the following:

_email.settings.EmailTemplatesPath

One last thing - make sure your EmailSettings class structure is the exact same as your json.

Solution 5

Program.cs

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

        }

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

Startup.cs:

public partial class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IAntiforgery antiforgery)
        {
            app.UseMvcWithDefaultRoute();
        }
    }

Controller Class

enter image description here

You need to use Bind() method to get the value from section.

EmailSetting option = new EmailSetting();
            //You need to bind the section with data model, 
            //it will automatically map config key to property of model having same name
            config.GetSection("EmailSettings").Bind(option);

            //Alternative to this you can read nested key using below syntax
            var emailTemplatesPath = config["EmailSettings:EmailTemplatesPath"];
            var emailInEmailSettings = config["EmailSettings:Email"];
            // If email key is not nested then you can access it as below
            var email = config.GetValue<string>("EmailOutside");

Email Setting Model:- (Property name should match with config Key)

public class EmailSetting
    {
        public string EmailTemplatesPath { get; set; }
        public string Email { get; set; }
    }

Appsetting.json:-

    {
       "AllowedHosts": "*",
        "EmailSettings": {
            "EmailTemplatesPath": "EmailTemplates",
            "Email": "[email protected]"
          },
          "EmailOutside": "[email protected]"
        }
Share:
14,309
Naveed Butt
Author by

Naveed Butt

Experienced coder, debugger and problem solver. **#SOreadytohelp**

Updated on June 22, 2022

Comments

  • Naveed Butt
    Naveed Butt almost 2 years

    I am new to .Net Core and trying to get a value from the appsettings.json file but I have missed something very basic. Please let me know what I have done wrong... Here is the code...

    Program.cs

     WebHost.CreateDefaultBuilder(args)
     .ConfigureAppConfiguration((hostingContext, config) =>
     {
          config.SetBasePath(Directory.GetCurrentDirectory());
     })
    

    Startup.cs

    public IConfiguration Configuration { get; private set; }
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }
    public void ConfigureServices(IServiceCollection services)
    {
        services.Configure<EmailSettings>(Configuration.GetSection("EmailSettings"));
    }
    

    Web API Controller

    private readonly IConfiguration config;
    
    public EmailController(IConfiguration configuration)
    {
        if (configuration != null)
        {
            config = configuration;
        }
    }
    

    Action Method

    var emailTemplatesRelativePath = config.GetSection("EmailSettings");
    var email = config.GetValue<string>("Email");
    

    Both the above lines are returning null values for both GetSection and GetValue

    appsettings.json

    {
      "Logging": {
        "LogLevel": {
          "Default": "Trace",
          "Microsoft": "Information"
        }
      },
      "ConnectionStrings": {
        "FCRContext": "server=xxx;database=xxx;user id=xxx;password=xxx"
      },
      "AllowedHosts": "*",
      "EmailSettings": {
        "EmailTemplatesPath": "EmailTemplates"
      },
      "Email": "[email protected]"
    }
    
    • Felix
      Felix over 5 years
      You usually initialize IConfiguration in Startup.cs; not in Program.cs. If you are getting null the way you describe, something is wrong there. Please show Startup.cs
    • Naveed Butt
      Naveed Butt over 5 years
      docs.microsoft.com/en-us/aspnet/core/fundamentals/configurat‌​ion/… I am following this. I tried Options Pattern as well, but it didn't work...
    • Naveed Butt
      Naveed Butt over 5 years
      .Net Core 2.2 and using IIS Express for now..
  • Naveed Butt
    Naveed Butt over 5 years
    Those are two separate statements suggesting that none of them are returning any value.
  • Naveed Butt
    Naveed Butt over 5 years
    No, The two code lines are there to show that both the GetSection and the GetValue are returning no values.
  • Naveed Butt
    Naveed Butt over 5 years
    This config["EmailSettings:EmailTemplatesPath"] still returns null.
  • Naveed Butt
    Naveed Butt over 5 years
    and according to this docs.microsoft.com/en-us/dotnet/api/… Bind is not available in .Net Core 2.2
  • Ashish Mishra
    Ashish Mishra over 5 years
    Please try to create simple MVC project with one controller to read config file. I have updated answer and added the code sample in it. try to keep this dummy project very simple, do not add any middle ware in pipeline except MVC.
  • Naveed Butt
    Naveed Butt over 5 years
    Thanks. That helped. I created an empty application and that was returning the configuration values correctly. Then I compared the two program.cs, the problem was this line that I had to .ConfigureAppConfiguration((hostingContext, { config.SetBasePath(Directory.GetCurrentDirectory()); })
  • Naveed Butt
    Naveed Butt over 5 years
    Don't know why that is the problem. The MSDN says that I need to add that... docs.microsoft.com/en-us/aspnet/core/fundamentals/configurat‌​ion/…
  • Robert Bantele
    Robert Bantele over 5 years
    Updated to work in Controllers. I tested it myself, just give it a try :)
  • Ashish Mishra
    Ashish Mishra over 5 years
    I tried with and without ConfigureAppConfiguration().In both cases code were working fine.
  • Hasan
    Hasan over 5 years
    Can you see my answer on this link and get those values from appsetting.json via settings class and inject it to the controller with IOptions. stackoverflow.com/a/53904527/5198054
  • Ensunder
    Ensunder about 5 years
    Are there any implications when deploying this to 1) older versions of IIS or 2) latest versions of IIS? If we have "in-process" set for our project, do we just never use config.SetBasePath even though its in the project when it's created? If so, I guess its just MS hasn't updated the template for it.