Azure Function: Difference between AddOptions and Configure method of IServiceCollection - c#

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

Related

How to get section from a configuration file

I want to transfer data from appsettings.json to an instance of MailSettings at runtime :
Here's the model :
public class MailSettings
{
public string Mail { get; set; }
public string DisplayName { get; set; }
public string Password { get; set; }
public string Host { get; set; }
public int Port { get; set; }
}
In program.cs, I try to configure the service with the following instruction:
builder.Services.Configure<MailSettings>(Configuration.GetSection("MailSettings"));
But I have the following problem:
Compiler Error CS0120 : An object reference is required for the
nonstatic field, method, or property
Configuration.GetSection(string)
If someone has a solution ...
try this.
builder.Services.Configure<MailSettings>(builder.Configuration.GetSection("MailSettings"));
1. If you want to get section in program.cs
Try
var settings = builder.Configuration.GetSection("MailSettings").Get<MailSettings>();
Read this answer to know more
Result:
in appsetings.json:
"MailSettings": {
"Mail": "a#a.com",
"DisplayName": "aa",
"Password": "123"
}
2. If you want to get section in Controller/class
Try
builder.Services.Configure<MailSettings>(builder.Configuration.GetSection("MailSettings"));
HomeController:
public class HomeController : Controller
{
public MailSettings MailSettings { get; }
public HomeController(IOptions<MailSettings> smtpConfig)
{
MailSettings = smtpConfig.Value;
}
}
Result:

Why am i getting argumentNullException when adding migration

I'm trying to create a migration in my web api for one-to-many db. Services are configured this way and the connection string is successfully received from launchsettings.json
var connectionStr = Environment.GetEnvironmentVariable("ApplicationDbContext");
services.AddDbContext<InsolationResultContext>(options =>
{
options.UseNpgsql(connectionStr, builder =>
{
builder.CommandTimeout(300);
builder.EnableRetryOnFailure(5, TimeSpan.FromSeconds(10), null);
});
});
This are models
public class DocumentInsolationResult
{
public string Id { get; set; }
public string UserId { get; set; }
public IEnumerable<InsolationResult> Elements { get; set; }
}
public class InsolationResult
{
public string UniqueId { get; set; }
public string Insolation { get; set; }
}
And DbContext
public class InsolationResultContext : DbContext
{
public DbSet<DocumentInsolationResult> DocumentInsolationResults { get; set; }
public DbSet<InsolationResult> InsolationResults { get; set; }
public InsolationResultContext(DbContextOptions<InsolationResultContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<DocumentInsolationResult>()
.HasMany(p => p.Elements)
.WithOne();
}
}
the connection string looks like this:
"environmentVariables": {
"ApplicationDbContext": "Host=192.168.1.***;Port=****;Database=***.******;Username=*****;Password=****",
"ASPNETCORE_ENVIRONMENT": "Development"
},
When trying to "Add-Migartion Init i always get "Value cannot be null. (Paramtere 'connectionString'). What am i doing wrong? Coulnd't actually find the answer on the internet
upd: i'me receiving the connection string from launchsetting, it's ok as i'm using the same way of getting connectionString on some other projects
upd2 hardcoding the connection string worked for me
If you use IConfiguration instead of Environment.GetEnvironmentVariable then it becomes a lot easier:
var connString = config.GetValue<string>("ApplicationDbContext");
It should be
"ConnectionStrings": {
"ApplicationDbContext": "Host=192.168.1.***;Port=****;Database=***.******;Username=*****;Password=****""
}
in appsettings.Development.json file.
Thus it is giving error "Value cannot be null. (Paramtere 'connectionString')."
And in your startup.cs file
services.AddDbContext<InsolationResultContext >(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("ApplicationDbContext")
});
Well the problem was really with the connection string. I couldn't create a migration when using connection string from appsettings or launchsettings. After hardcoding the connection string in stratup.cs i was able to create migration. And now after the db is initialized i'm using connection string from launchsettings. This might be some EF core tricky things.

POCO object array inside AppSettings.json in ASP.NET Core

This seems like it should be really simple, I have been searching SO and a lot of other places for an answer to this, everything I have found and tried does not work.
I have an appsettings.json file that looks like this
"Email": {
"Port": "25",
"Host": "localhost",
"EnableSSL": "false",
"Credentials": {
"Username": "fakeuser",
"Password": "fakepassword"
},
"SystemFromAddress": "testsender#localhost.com",
"SystemFromDisplayName": "Test Sender",
"EmailTemplateRootDirectory": "Email\\EmailTemplates",
"EmailTemplates": [
{
"TemplateKey": "ResetPassword",
"TemplatePath": "ResetPassword.cshtml"
},
{
"TemplateKey": "NewAccount",
"TemplatePath": "NewAccount.cshtml"
},
{
"TemplateKey": "VerifyEmail",
"TemplatePath": "VerifyEmail.cshtml"
}
]
}
There are several models (EmailOptions being the parent) that I am trying to bind to, the EmailOptions class is expecting to have it's EmailTemplates list populated from the EmailTemplates list in the appsettings.json as seen above.
The parent class is being populated by the appsettings.json file as expected, the Child List of Email Templates in this class is always coming up empty.
Here are the classes I am binding to.
public class EmailOptions
{
public int Port { get; set; }
public string Host { get; set; }
public bool EnableSSL { get; set; }
public EmailCredentials Credentials { get; set; }
public string SystemFromAddress { get; set; }
public string SystemFromDisplayName { get; set; }
public string EmailTemplateRootDirectory { get; set; }
public IEnumerable<EmailTemplate> EmailTemplates { get; set; } = new List<EmailTemplate>();
}
public class EmailTemplate
{
public string TemplateKey { get; set; }
public string TemplatePath { get; set; }
}
public class EmailCredentials
{
public string Username { get; set; }
public string Password { get; set; }
}
I am using the following call I am making in my startup class in ASP.NET Core.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddOptions();
services.Configure<EmailOptions>( _configuration.GetSection("Email" ));
...
For some reason the IEnumerable property in my EmailOptions is not being deserialized from the appsettings.json into my options - when I attempt to use it anywhere in my controllers - the list is always set to an empty array.
FWIW: I have this working in a console application where I have more control over setting up my options from the appsettings.json. Here is what I am doing in the console app, (I am leaving out the code where I set up the options with the DI container for brevity)
var emailSection = configuration.GetSection( "Email" );
var emailOptions = emailSection.Get<EmailOptions>();
emailOptions.EmailTemplates = configuration.GetSection( "Email:EmailTemplates" ).Get<List<EmailTemplate>>();
as expected - in the console application, I get my Email Templates because i have the ability to get the child list separately and add it to the options before handing it over to the DI container. I don't seem to have that flexibility in the ASP.NET Core IServiceCollection.Configure() extension method (so maybe use another method to do this? which one? After a couple hours of searching I am crying uncle and asking for help).
So how does one get this to work using the ASP.NET Core "IServiceCollection.Configure()" method? Is there a better way to do this?
Thank you Joe for pointing out what needed to happen!
I made the false assumption that the serializer would happily create it's list from the json and assign that list to my IEnumerable - rather - you need to make sure to use List if you intend to deserialize a list of json objects into your Options (and other concrete dotnet types where applicable).
so instead of this
IEnumerable<EmailTemplate> EmailTemplates { get; set; }
I should have had this...
List<EmailTemplate> EmailTemplates { get; set; }

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