configuration.getValue or configuration.getsection always returns null

Viewed 16131

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": "aa@aa.com"
}
6 Answers

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": "aa@aa.com"
  }
}

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! :)

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.

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": "aa@aa.com"
  },

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.

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": "aa@aa.com"
          },
          "EmailOutside": "aa@aa.com"
        }

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.

You can do something like this

services.Configure<EmailSettings>(Configuration.GetSection("EmailSettings"));
services.AddTransient(p => p.GetRequiredService<IOptions<EmailSettings>>().Value);

and initialize EmailSettings object via constructor DI.

Related