Configuring properties from config.json using services.Configure - c#

Following on from a StackOverflow question regarding Using IConfiguration globally in mvc6. A comment to the accepted answer suggests using
services.Configure<SomeOptions>(Configuration);
Now this works fine with the following code;
Class
public class SomeOptions
{
public string MyOption { get; set; }
}
config.json
{
"MyOption": "OptionValue"
}
Startup.cs
public Startup(IHostingEnvironment env)
{
Configuration = new Configuration()
.AddJsonFile("config.json")
.AddEnvironmentVariables();
}
public void ConfigureServices(IServiceCollection services)
{
services.Configure<SomeOptions>(Configuration);
}
However the config.json file doesn't have any really structure, and I would like it to look more like;
{
"SomeOptions": {
"MyOption": "OptionValue"
}
}
However this does not bind the values within the class. Is there anyway to allow this?

If you want to change the config.json structure you also need to change your class structure.
{
"SomeOptions": {
"MyOption": "OptionValue"
}
}
maps to something like
public class SomeOptions
{
public List<MyOption> MyOptions { get; set; }
}
public class MyOption
{
public string OptionValue { get; set; }
}

You can access specific value in config.json like:
Configuration.Get("SomeOptions:MyOption");
Which returns
"OptionValue"
So, your code will be
services.Configure<SomeOptions>(options =>
options.MyOption = Configuration.Get("SomeOptions:MyOption"));

services.Configure<SomeOptions>(Configuration.GetSubKey(nameof(SomeOptions)));
Should do it.

Related

Azure Function: Difference between AddOptions and Configure method of IServiceCollection

I am using Options Pattern within Azure Function. I created custom classes to represent the config data.
Below is my settings.json
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"ServiceBusConfigOptions:EventTypeTopic": "",
"ServiceBusConfigOptions:EventTypeTopicSubscription": "",
"ServiceBusConfigOptions:ConnectionString": "",
"ServiceBusConfigOptions:SendEmailTopic": "",
"ServiceBusConfigOptions:NotificationMonitoringTopic": "",
"EmailConfigOptions:FromAddress": "",
"EmailConfigOptions:Subject": "",
"EmailConfigOptions:MailingServiceAccount": "",
"EmailConfigOptions:EmailTemplateDefaultValue": "",
}
}
My EmailConfiOptions class:
public class EmailConfigOptions
{
public string FromAddress { get; set; }
public string Subject { get; set; }
public string MailingServiceAccount { get; set; }
public string EmailTemplateDefaultValue { get; set; }
}
ServiceBusConfigOptions class:
public class ServiceBusConfigOptions
{
public string ConnectionString { get; set; }
public string EventTypeTopic { get; set; }
public string EventTypeTopicSubscription { get; set; }
public string SendEmailTopic { get; set; }
public string NotificationMonitoringTopic { get; set; }
}
In the Startup.cs class I can register with both methods AddOptions and Configure(as shown in the below).
So basically which method should we use? AddOptions or Configure.
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddLogging();
builder.Services.AddOptions<ServiceBusConfigOptions>()
.Bind(builder.GetContext().Configuration.GetSection(nameof(ServiceBusConfigOptions)));
builder.Services.Configure<EmailConfigOptions>(builder.GetContext().Configuration.GetSection(nameof(EmailConfigOptions)));
builder.Services.AddSingleton<IValidateOptions<EmailConfigOptions>, EmailConfiOptionsValidator>();
builder.Services.AddSingleton<IValidateOptions<ServiceBusConfigOptions>, ServiceBusConfigOptionsValidator>();
}
}
The ConfigurationBuilder Class and the extension methods used to configure various files in your code.
In a ConfigurationBuilder we use Configure property to inject the settings of our custom or predefined services.
which method should we use? AddOptions or Configure.
We can use both Configure or AddOptions to inject the IOptions. But the proper way which follows to avoid conflicts.
If we are trying to inject the Custom IOptions in our Application, we need to use the Services.AddOptions(). to retrieve the exact IOptions value we need to use the Services.Configure< IOptions>()
public override void Configure(IFunctionsHostBuilder builder)
{
# Inject Options
builder.services.AddOptions();
# it inject the Connection String value of ServiceBusConfigOptions
builder.services.Configure<ServiceBusConfigOptions>(Configuration.GetSection("ConnectionString"));
...
}
Reference
Usage of IOptions in Configure Link 1 & 2
Usage of IOptions in AddOptions Link 1 & 2

Entity Framework Core, one-to-many relationship, collection always empty

I'm trying to do a simple one to many relationship in a InMemory database that is built up in runtime by http requests. I'm new to Entity Framework Core, so I'm unsure if the is a proper way of working with it.
ValuesController.cs
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
private readonly DataListContext _Context;
public ValuesController(DataListContext context)
{
_Context = context;
}
[HttpGet]
public ActionResult<IEnumerable<DataList>> Get()
{
// Datas keeps is value
Console.WriteLine("Root Count: " + _Context.RootDatas.Count());
if (_Context.RootDataLists.Count() > 0)
{
// InsideDatas is always
Console.WriteLine("Inside Count: " + _Context.RootDataLists.First().InsideDatas.Count);empty next request
}
return _Context.RootDataLists.ToList();
}
[HttpPost]
public void Post([FromBody] string value)
{
var data = new Data() { Id = Guid.NewGuid() };
_Context.RootDatas.Add(data);
var dataList = new DataList();
dataList.Name = value;
dataList.InsideDatas.Add(data);
_Context.RootDataLists.Add(dataList);
_Context.SaveChanges();
// InsideDatas has an element
Console.WriteLine("Inside Count: " + _Context.RootDataLists.First().InsideDatas.Count);
}
}
public class DataListContext : DbContext
{
public DbSet<DataList> RootDataLists { get; set; }
public DbSet<Data> RootDatas { get; set; }
public DataListContext(DbContextOptions<DataListContext> options) : base(options) { }
}
public class Data
{
[Key] public Guid Id { get; set; }
}
public class DataList
{
public DataList()
{
InsideDatas = new List<Data>();
}
[Key]
public string Name { get; set; }
public ICollection<Data> InsideDatas { get; set; }
}
Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DataListContext>(options => options.UseInMemoryDatabase(databaseName: "DataList"));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
}
}
My problem if that the InsideDatas list is always empty in the next call, but not RootDatas. The DbSet RootDatas in DataListContext is not really anything I would need in my application, but I though it would make the database keep the InsideDatas element. But no.
So I'm I using this all wrong? My use case is a server listing, built up in runtime by hosts posting it's presence to the listing service. My plan is to swap the InMemoryDatabase for redis to be able to scale this properly if needed. (This code is just a slimmed version of the actual one with he same problem)
You need to tell EF Core to load related entities. One way is through eager loading:
// notice the Include statement
_Context.RootDataLists.Include(x => x.InsideDatas).First().InsideDatas

How to map config in IConfigurationSection to a simple class

Using MVC .net Core and building a concrete config class within the startup class. My appsettings.json looks like this:
{
"myconfig": {
"other2": "tester,
"other": "tester",
"root": {
"inner": {
"someprop": "TEST VALUE"
}
}
}
}
I've represented this with a concrete class as follows:
public class TestConfig
{
public string other2 { get; set; }
public string other { get; set; }
public Inner1 root { get; set; }
}
public class Inner1
{
public Inner2 inner { get; set; }
}
public class Inner2
{
public string someprop { get; set; }
}
And I can easily map this by doing the follow:
var testConfig = config.GetSection("myconfig").Get<TestConfig>();
However.... what I don't like about the above is the need to make TestConfig more complex than it needs to be. Ideally, I'd like something like this:
public class PreciseConfig
{
[Attribute("root:inner:someprop")]
public string someprop { get; set; }
public string other { get; set; }
public string other2 { get; set; }
}
Where I don't have to have the nested objects within and can map directly to a lower property in this kind of way. Is this possible? Using .net Core 2.1.
Thanks for any pointers in advance!
P.s. I know I can create an instance of PreciseConfig myself and set properties using config.GetValue<string>("root:inner:someprop") BUT I don't want to have to set all my custom settings in this way if I can do them automatically using a serialization property or similar.
For the higher level config you get the configuration as normal with the top node.
Then use the path myconfig:root:inner to get the other desired section and Bind PreciseConfig from the previous step
var preciseConfig = config.GetSection("myconfig").Get<PreciseConfig>();
config.GetSection("myconfig:root:inner").Bind(preciseConfig);
Reference Configuration in ASP.NET Core : GetSection
Reference Configuration in ASP.NET Core : Bind to an object graph

MVC 6 Create View issues : There was an error creating the DBContext to get the model

Attempting to create a view for "Create" using the NavBar Model and the NavBarEntity shown below (in MVC6) receives this message...
There was an error running the selected code generator: There was an error creating the DBVContext instance to get the model... Value cannot be null... Parameter Name: connectionString
I picked this mode in View Wizard...
public class NavBarModel
{
public string ID { get; set; }
public List<LinkModel> Links { get; set; }
}
This DBContext class is shown here...
public class NavBarEntity : DbContext
{
public NavBarEntity()
{
ID = Guid.NewGuid().ToString();
}
[Key]
public string ID { get; set; }
public DbSet<List<LinkModel>> Links { get; set; }
}
And the LinkModel shown here..
public class LinkModel
{
public LinkModel()
{
ID = Guid.NewGuid().ToString();
}
[Key]
private string ID { get; set; }
public string HREF { get; set; }
public string Text { get; set; }
}
Configure Services looks like this...
var cfg2 = Configuration["Data Source=MyPC\\SQLEXPRESS;Initial Catalog=Dashboard;Integrated Security=True;Pooling=False"];
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(cfg))
.AddDbContext<NavBarEntity>(options =>
{
options.UseSqlServer(cfg2);
});
Question: What am I doing wrong?
Thanks for the help listed above..
For newbies to MVC6 and EF7, the method named ConfigureServices, must contain a json pointer to the appsetting.json. That method is found in the Startup.cs file.
This is the services configuration to match the code shown above. The string value in the brackets points to the json location...
var cfg2 = Configuration["Data:DashboardContext:ConnectionString"];
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<DashboardContext>(options =>
{
options.UseSqlServer(cfg2);
})
But, you must also put a value into appsettings.json like this:
"Data": {
"DefaultConnection": {
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=aspnet5-TestWebApplication1-d91c23e4-3565-476d-a7c0-45665bc0c367;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"DashboardContext": {
"ConnectionString": "Data Source= MYPC\\SQLEXPRESS;Initial Catalog=Dashboard;Integrated Security=True;Pooling=False"
}
},
The root cause of the Parameter Name: connectionString being null was that the appsettings.json has to be exactly as shown above. The json parsing routines must be able to locate the string name/value pair... Notice that these configurations fall under the "Data" name that contains other names. in this case "DefaultConnection" was there by default, and I added "DashboardContext" portion.
Also in MVC 6 you must change the connectionString type to IServiceProvider and NOT string as was done before...
public class DashboardContext : DbContext
{
public DashboardContext(IServiceProvider connectionString) : base (connectionString)
{}
public DbSet<NavBarEntity> NavBars { get; set; }
}
Alas: The Views created no problem...Yes!
The way you've tried to combine the DbContext and your entity isn't right. The DbContext should reference any entities you have as DbSets - entities should not inherit from it.
Your DbContext should look similar to this (EF6)
public class MyDbContext : DbContext
{
public MyDbContext(string connectionString)
: base(connectionString)
{ }
public DbSet<NavBarEntity> NavBars { get; set; }
// Other entities
}
The constructor takes the name of the connecting string entry that's defined in your web.config you want to use. There are other ways to do this though - see here
Then create your entities as a simple class (POCO):
public class NavBarEntity
{
public NavBarEntity()
{
ID = Guid.NewGuid().ToString();
}
[Key]
public string ID { get; set; }
// Other properties/columns here
}
EDIT
My original answer was based on EF6 rather than EF7. Here's how I would implement the context in EF7 for completeness:
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options)
: base(options)
{ }
public DbSet<NavBarEntity> NavBars { get; set; }
// Other entities
}

How to handle a hierarchy of properties in ASP.NET 5 AppSettings?

In ASP.NET 4 to organize settings, I am prefixing the setting key with a small word that indicates where this config is used (e.g. key="dms:url", "sms:fromNumber" .... etc).
In ASP.NET 5, the AppSettings configuration is mapped to a strongly typed class.
what is the property that i need to build for "dms:url"? How could map dashes & special chars to a C# property in ASP.NET 5?
You can organize your configuration file within a hierarchy in the config.json
{
"AppSettings": {
"SiteTitle": "PresentationDemo.Web",
"Dms": {
"Url": "http://google.com",
"MaxRetries": "5"
},
"Sms": {
"FromNumber": "5551234567",
"APIKey": "fhjkhededeudoiewueoi"
}
},
"Data": {
"DefaultConnection": {
"ConnectionString": "MyConnectionStringHere. Included to show you can use the same config file to process both strongly typed and directly referenced values"
}
}
}
We defined the AppSettings as a POCO class.
public class AppSettings
{
public AppSettings()
{
Dms = new Dms(); // need to instantiate (Configuration only sets properties not create the object)
Sms = new Sms(); // same
}
public string SiteTitle { get; set; }
public Dms Dms { get; set; }
public Sms Sms { get; set; }
}
public class Dms
{
public string Url { get; set; }
public int MaxRetries { get; set; }
}
public class Sms
{
public string FromNumber { get; set; }
public string ApiKey { get; set; }
}
We then load the configuration into an instance of IConfigurationSourceRoot and then set values of AppSettings using GetSubKey. The best practice would be to do this in ConfigureServices and add it to the DI Container.
public class Startup
{
public Startup(IHostingEnvironment env)
{
// Setup configuration sources.
var configuration = new Configuration()
.AddJsonFile("config.json")
.AddJsonFile($"config.{env.EnvironmentName}.json", optional: true);
}
public void ConfigureServices(IServiceCollection services)
{
// Add Application settings to the services container.
services.Configure<AppSettings>(Configuration.GetSubKey("AppSettings"));
//Notice we can also reference elements directly from Configuration using : notation
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
}
}
We can now provide access in a controller through the constructor. I set the setting values explicitly the constructor but you could use the entire IOptions
public class HomeController : Controller
{
private string _title;
private string _fromNumber;
private int _maxRetries;
public HomeController(IOptions<AppSettings> settings)
{
_title = settings.Options.SiteTitle;
_fromNumber = settings.Options.Sms.FromNumber;
_maxRetries = settings.Options.Dms.MaxRetries;
}
If you wanted to keep everything flat and use a pseudo hierarchy like you have been doing, you can, but ":" isn't a valid symbol for a variable name. You would need to use a valid symbol like "_" or "-" instead.

Categories

Resources