Proper way to use Azure Keyvault with Dependency Injection - c#

Information
I am building an Azure hosted protected API. I am targeting .Net 5 and using the MVC pattern. Inside this API, I have two DAL (data access layer) classes, which are added to the service container via the Startup.cs file. Both of these classes are Transient and expect a connection string in their constructors.
Problem
I want to move the connection strings from the appsetting.json file of the API to the Azure key vault, but I only want to have to pull down the connection strings once from the Azure key vault and use them as static values to initialize any instances of the DAL classes. I've tried calling the respective method of the SecretClient class called GetSecretAsync and I use the constructor of SecretClient(Uri, ManagedIdentityCredential). This all works when I call this after Startup.cs has ran, but when I try to do it inside of the Startup.cs it seems to hang for some unknown reason. I know that the calls do work later in the pipeline as I've tested them in the API as well as in the Azure Portal. This is what I've been trying to do via code:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
//For Production, set the properties of the KeyVault
Secrets.SetProps(new KeyVaultHelper(Configuration)).GetAwaiter().GetResult();
//Get the AzureAd section
try
{
//Instead of storing the AzureAdBinding info in appsettings, I created a
//json string which deserializes into a Dictionary which is used to
//create the ConfigurationSection object
Dictionary<string, string> azureadkvps = JsonConvert.DeserializeObject<Dictionary<string, string>>(Secrets.AzureAdBinding);
//Turn it into an IConfigurationSection
IConfigurationSection azureconfigsection = new ConfigurationBuilder().AddInMemoryCollection(azureadkvps).Build().GetSection("AzureAd");
//Bind authentication to Configuration AzureAd section
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(azureconfigsection);
}
catch { }
Secrets.cs
/// <summary>
/// Static class to hold values of keys pulled from Azure key vault
/// </summary>
public static class Secrets
{
private const string MOBILE_APP_ID = "xxxxxxxxxx";
private const string B_DB_CONN = "xxxxxxxxxx";
private const string MOBILE_ADMIN_ROLE = "xxxxxxxxxx";
private const string MOBILE_API_SCOPES = "xxxxxxxxxx";
private const string MOBILE_COMMON_ROLE = "xxxxxxxxxx";
private const string BL_DB_CONN = "xxxxxxxxxx";
private const string AZUREAD_BINDING = "xxxxxxxxxx";
public static string BLConn { get; private set; } = null;
public static string BConn { get; private set; } = null;
public static string MobileAdminRole { get; private set; } = null;
public static string MobileUserRole { get; private set; } = null;
public static string MobileApiScopes { get; private set; } = null;
public static string MobileClientID { get; private set; } = null;
public static string AzureAdBinding { get; private set; } = null;
public async static Task SetProps(IKeyVaultHelp keyvault)
{
Task<string>[] secretretrievaltasks = new Task<string>[7];
secretretrievaltasks[0] = keyvault.GetSecretAsync(MOBILE_APP_ID);
secretretrievaltasks[1] = keyvault.GetSecretAsync(B_DB_CONN);
secretretrievaltasks[2] = keyvault.GetSecretAsync(MOBILE_ADMIN_ROLE);
secretretrievaltasks[3] = keyvault.GetSecretAsync(MOBILE_API_SCOPES);
secretretrievaltasks[4] = keyvault.GetSecretAsync(MOBILE_COMMON_ROLE);
secretretrievaltasks[5] = keyvault.GetSecretAsync(BL_DB_CONN);
secretretrievaltasks[6] = keyvault.GetSecretAsync(AZUREAD_BINDING);
await Task.WhenAll(secretretrievaltasks).ConfigureAwait(false);
BLConn = secretretrievaltasks[5].Result;
BConn = secretretrievaltasks[1].Result;
MobileAdminRole = secretretrievaltasks[2].Result;
MobileUserRole = secretretrievaltasks[4].Result;
MobileApiScopes = secretretrievaltasks[3].Result;
MobileClientID = secretretrievaltasks[0].Result;
AzureAdBinding = secretretrievaltasks[6].Result;
}
}
KeyVaultHelper.cs
public class KeyVaultHelper : IKeyVaultHelp
{
private readonly IConfiguration _config;
public KeyVaultHelper(IConfiguration config)
{
_config = config;
}
public string GetSecret(string secretkey)
{
string kvuri = $"https://{_config.GetValue<string>("KVN")}.vault.azure.net";
var secretclient = new SecretClient(new Uri(kvuri), new ManagedIdentityCredential("xxxxxxxxxxxxxxxxxxxx"));
return secretclient.GetSecret(secretkey).Value.Value;
}
public async Task<string> GetSecretAsync(string secretkey)
{
string kvuri = $"https://{_config.GetValue<string>("KVN")}.vault.azure.net";
var secretclient = new SecretClient(new Uri(kvuri), new ManagedIdentityCredential());
return (await secretclient.GetSecretAsync(secretkey).ConfigureAwait(false)).Value.Value;
}
}
Perhaps I am going about this all wrong, but I'd like to be able to pull these values before adding the DAL classes to the service container so that the static values of the Secrets.cs hold the correct connection strings which will be used to construct the DAL objects.
I understand there is a way to add a Configuration file in Azure which can be built in Program.cs via the following, but I was hoping to just pull the values directly from KeyVault and use them throughout the lifetime of the application. Any direction or advice is appreciated.
webBuilder.ConfigureAppConfiguration((hostingContext, config) =>
{
var settings = config.Build();
config.AddAzureAppConfiguration(options =>
{
options.Connect(settings["ConnectionStrings:AppConfig"])
.ConfigureKeyVault(kv =>
{
kv.SetCredential(new DefaultAzureCredential());
});
});
})
.UseStartup<Startup>());
}

Thanks Camilo Terevinto, your comments are converting to an answer. So that it may helps to other community members to find this correct solution.
Instead in the Startup, use await with async Task Main in Program class where you already have the IConfiguration configured.
use AddAzureAppConfiguration if you're using app services, or
put the secrets in environment variables if you're running on VMs/containers
To populate the static values before the call to CreateHostBuilder(args).Build().Run(), you should call the async or await method.

Related

.NET 6 and DataProtection/Encryption

Using a razor pages app and .NET 6, how does this look for encrypting/decrypting data? I did a bit of research and apparently AES-CBC encryption isn't recommended, and this was the alternative I found (AES_256_GCM). Also, is there a clean way to turn this into a library that can be reused in other versions of .NET (large environment, it'll take time to upgrade everything)?
It's close to other posts about using it in .NET core, but some minor (yet "it won't work without it") tweaks:
Program.cs file:
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;
using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddDataProtection();
builder.Services.AddDataProtection()
.UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration()
{
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_GCM,
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
});
builder.Services.AddSingleton<CipherService>();
var app = builder.Build();
Class for the cipher:
using Microsoft.AspNetCore.DataProtection;
namespace Encryption.BusinessLogic
{
public class CipherService
{
private readonly IDataProtectionProvider _dataProtectionProvider;
private const string Key = "my-very-long-key-of-no-exact-size";
public CipherService(IDataProtectionProvider dataProtectionProvider)
{
_dataProtectionProvider = dataProtectionProvider;
}
public string Encrypt(string input)
{
var protector = _dataProtectionProvider.CreateProtector(Key);
return protector.Protect(input);
}
public string Decrypt(string cipherText)
{
var protector = _dataProtectionProvider.CreateProtector(Key);
return protector.Unprotect(cipherText);
}
}
}
Code behind on the index page:
private readonly ILogger<IndexModel> _logger;
private readonly IDataProtectionProvider _dataProtectionProvider;
[BindProperty]
public string InputText { get; set; }
[BindProperty]
public string Enc { get; set; }
public IndexModel(ILogger<IndexModel> logger, IDataProtectionProvider dataProtectionProvider)
{
_logger = logger;
_dataProtectionProvider = dataProtectionProvider;
}
public void OnGet()
{
}
public void OnPost()
{
CipherService cipher = new CipherService(_dataProtectionProvider);
Enc = cipher.Encrypt(InputText);
}
I've taken a different approach by using this article, https://code-maze.com/data-protection-aspnet-core/. I've made a minor change. On the controller: I've added the following declaration to deal with Route Constraints. So far it's working for me. This is for .Net Core.
[Route("Controller/Action/{id:regex(^[[a-zA-Z0-9_-]]*$)}")] // matches alpha, number, plus _ and +
public async Task<IActionResult> EditPermission(string id)
{
`var decryptedId = Convert.ToInt32(_dataProtector.Unprotect(id));
}

dependency injection multiple interface for the same storage (with different connectionString)

I want to add two or more(depends on how many azure storage container i want to add to my app) services in Startup.cs
My appsettings.json:
"AzureBlobStorageConfiguration": {
"Storages": {
"Storage1": {
"StorageName": "Storage1",
"ConnString": "connString",
"AzureBlobContainerName": "containerName"
},
"Storage2": {
"StorageName": "Storage2",
"ConnString": "connString",
"AzureBlobContainerName": "containerName"
},
"Storage3": {
"StorageName": "Storage3",
"ConnString": "connString",
"AzureBlobContainerName": "containerName"
}
}
Next in Startup.cs im adding service with method:
public static IServiceCollection AddAzureStorage1(this IServiceCollection services, IConfiguration configuration)
{
var options = new ABlobStorageConfigurationOptionsDTO();
configuration.GetSection("AzureBlobStorageConfiguration").GetSection("Storages").GetSection("Storage1").Bind(options);
services.AddTransient<IAzureBlobStorage1, AzureBlobStorage1>(isp =>
{
var client = new BlobServiceClient(options.ConnString);
var container = client.GetBlobContainerClient(options.AzureBlobContainerName);
var containerName = options.AzureBlobContainerName;
var storageName = options.StorageName;
return new AzureBlobStorage1(container, containerName, storageName);
}
);
return services;
}
My IAzureBlobStorage1 looks like:
public interface IAzureBlobStorage1
{
string AzureBlobContainerName { get; }
string StorageName { get; }
public Task<Stream> DownloadStreamAsyns(string fileName);
public Task Upload(string fileId, Stream stream);
}
and AzureBlobStorage1 :
public class AzureBlobStorage1 : IAzureBlobStorage1
{
private BlobContainerClient _client;
private string _containerName;
private string _storageName;
public string StorageName => _storageName;
public string AzureBlobContainerName => _containerName;
public AzureBlobStorage1(BlobContainerClient client, string containerName, string storageName)
{
_client = client;
_containerName = containerName;
_storageName = storageName;
}
public async Task<Stream> DownloadStreamAsyns(string fileName)
{
return await _client.GetBlobClient(fileName).OpenReadAsync();
}
public async Task Upload(string fileId, Stream stream)
{
await _client.GetBlobClient(fileId).UploadAsync(stream);
}
}
After this i can injection interface in my constructor controller class :
public Controller(IAzureBlobStorage1 azureStorage)
{
_azureStorage1 = azureStorage;
}
But if i want to add many storages (i have 3 in appsetings.json) i have to:
Create interface IAzureBlobStorage2 (looking the same like IAzureBlobStorage1 - only name change)
Create class AzureBlobStorage2 (looking the same like AzureBlobStorage1 - only name change)
copy-paste method with changed class names
public static IServiceCollection AddAzureStorage2(this IServiceCollection services, IConfiguration configuration)
{
var options = new ABlobStorageConfigurationOptionsDTO();
configuration.GetSection("AzureBlobStorageConfiguration").GetSection("Storages").GetSection("Storage2").Bind(options);
services.AddTransient<IAzureBlobStorage2, AzureBlobStorage2>(isp =>
{
var client = new BlobServiceClient(options.ConnString);
var container = client.GetBlobContainerClient(options.AzureBlobContainerName);
var containerName = options.AzureBlobContainerName;
var storageName = options.StorageName;
return new AzureBlobStorage2(container, containerName, storageName);
}
);
return services;
}
Now i can get it in controller by
public Controller(IAzureBlobStorage2 azureStorage)
{
_azureStorage2 = azureStorage;
}
If i want add my third storage i need to copy-paste third time my code.
For me this solution looks very bad and im thinking how i can resolve it and make my code clean.
Unsure if this is a best practice or not, but you could design a named service provider, maybe? Either that, or you could just a generic parameter to differentiate them, but that generic parameter wouldn't mean much except as a way to differentiate..
Anyways, here's a really basic implementation using some kind of named provider?:
public interface INamedService {
string Identifier { get; }
}
public interface IAzureBlobStorage : INamedService
{
string AzureBlobContainerName { get; }
string StorageName { get; }
Task<Stream> DownloadStreamAsyns(string fileName);
Task Upload(string fileId, Stream stream);
}
public class NamedServiceProvider<T>
where T : INamedService
{
readonly IReadOnlyDictionary<string, T> Instances;
public NamedServiceProvider(
IEnumerable<T> instances)
{
Instances = instances?.ToDictionary(x => x.Identifier) ??
throw new ArgumentNullException(nameof(instances));
}
public bool TryGetInstance(string identifier, out T instance) {
return Instances.TryGetValue(identifier, out instance);
}
}
public class AzureBlobStorage : IAzureBlobStorage
{
public string Identifier { get; }
private BlobContainerClient _client;
private string _containerName;
private string _storageName;
public string StorageName => _storageName;
public string AzureBlobContainerName => _containerName;
public AzureBlobStorage(string identifier, BlobContainerClient client, string containerName, string storageName)
{
Identifier = identifier;
_client = client;
_containerName = containerName;
_storageName = storageName;
}
public async Task<Stream> DownloadStreamAsyns(string fileName)
{
return await _client.GetBlobClient(fileName).OpenReadAsync();
}
public async Task Upload(string fileId, Stream stream)
{
await _client.GetBlobClient(fileId).UploadAsync(stream);
}
}
And then the static extension method:
public static IServiceCollection AddAzureStorage(
this IServiceCollection services,
IConfiguration configuration,
string identifier)
{
var options = new ABlobStorageConfigurationOptionsDTO();
configuration
.GetSection("AzureBlobStorageConfiguration")
.GetSection("Storages")
.GetSection(identifier)
.Bind(options);
return services
.TryAddTransient<NamedServiceProvider<IAzureBlobStorage>>()
.AddTransient<IAzureBlobStorage, AzureBlobStorage>(isp =>
{
var client = new BlobServiceClient(options.ConnString);
var container = client.GetBlobContainerClient(options.AzureBlobContainerName);
var containerName = options.AzureBlobContainerName;
var storageName = options.StorageName;
return new AzureBlobStorage(identifier, container, containerName, storageName);
});
}
Then you could call use it like so:
public Controller(NamedServiceProvider<IAzureBlobStorage> azureStorage)
{
_ = azureStorage ?? throw new ArgumentNullException(nameof(azureStorage));
_azureStorage2 = azureStorage.TryGetInstance("Storage2", out var instance) ? instance : throw new Exception("Something about the identifier not being found??");
}
I coded this outside of an intellisense environment, so sorry if there are any smaller mispellings or bugs. There may be a better way to do this, but this seemed at least somewhat ok-ish? Oh, and I only changed what I had to in order to make it work generically. I didn't want to touch any other logic..

Adding multiple endpoint routes to .net core console app from appsettings.json

I'm using .net core 3.1 to build a console app that acts as an event handler API.
The app captures changes to a database and directs those changes to other APIs, in real-time. Updates to "customer" go to "customerAPI", "product" goes to "productAPI" and so on. This means that I have an appsettings.Local.json that looks like this:
"DBConnectionStrings": {
"DefaultConnection": "AccountEndpoint=(my account)",
"SourceDatabaseName": "MyDB",
"SourceContainerName": "MySource",
"LeasesContainerName": "MyLease",
"PartitionKey": "/id"
},
"EndpointAPIStrings": {
"Endpoint1": {
"EndpointUrl": "https://localhost:7777",
"Username": "myusername1",
"Password": "mypassword1",
"Endpoint2": {
"EndpointUrl": "https://localhost:8888",
"Username": "myusername2",
"Password": "mypassword2",
"Endpoint3": {
"EndpointUrl": "https://localhost:9999",
"Username": "myusername3",
"Password": "mypassword3"
...
}
I am currently using a crappy method of declaring them as EnvironmentVariables to get them from my Main where the configuration is built to my CallAPI Task.
Main:
public static async Task Main(string[] args)
{
...
IConfiguration configuration = BuildConfiguration(environmentName);
CosmosClient cosmosClient = BuildCosmosClient(configuration);
Environment.SetEnvironmentVariable("EndpointUrl", configuration["EndpointAPIStrings:Endpoint1:EndpointURL"]);
Environment.SetEnvironmentVariable("Username", configuration["EndpointAPIStrings:Endpoint1:Username"]);
Environment.SetEnvironmentVariable("Password", configuration["EndpointAPIStrings:Endpoint1:Password"]);
...
}
Delegate function:
...
if (entityType == "myproduct")
{
var entity = "products";
var result = await Task.Run(() => CallAPIAsync(entity, item));
}
...
Task CallAPI:
public static async Task<HttpResponseMessage> CallAPIAsync(string entity, ProcessedItem item)
{
using (var client = new HttpClient())
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
var endpointUrl = Environment.GetEnvironmentVariable("EndpointUrl");
var uri = new Uri($"{endpointUrl}/api/{entity}/{item.Id}/propagate");
string username = Environment.GetEnvironmentVariable("Username");
string password = Environment.GetEnvironmentVariable("Password");
...
}
}
This obviously only works for the first endpoint and ignores the others.
How can I refactor this to get the values into my CallAPI Task for all EndpointAPIStrings?
I've done this in a Windows Service .net Core 3.1 app, pretty similar. Essentially when you call your IHostBuilder function in program.cs
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseWindowsService()
.ConfigureLogging(loggerFactory => loggerFactory.AddEventLog())
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
});
You get access to your configuration variables from appsettings.json by default. Which can then be accessed in your main startup or execute function:
private readonly ILogger<Worker> _logger;
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly IConfiguration _config;
public Worker(ILogger<Worker> logger, IServiceScopeFactory serviceScopeFactory, IConfiguration config)
{
_logger = logger;
_serviceScopeFactory = serviceScopeFactory;
_config = config;
}
And then in your main or execute function:
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// Must be a scoped process in order to run correctly
using var scope = _serviceScopeFactory.CreateScope();
// Start timer and begin log
var startTime = DateTime.UtcNow;
var env = _config.GetValue<string>("ENV");
var stageTable = _config.GetValue<string>("StageTable");
var prevTable = _config.GetValue<string>("PrevTable");
var mainTable = _config.GetValue<string>("MainTable");
var sqlConnectionString = _config.GetValue<string>("SqlConnString_" + env);
var excelConnectionString = _config.GetValue<string>("ExcelConnectionString1") +
_config.GetValue<string>("ExcelFilePath_" + env) +
_config.GetValue<string>("ExcelFileName") +
_config.GetValue<string>("ExcelConnectionString2");
With an appsettings.json like:
"ENV": "LOCAL",
"StageTable": "Staging",
"PrevTable": "Previous",
"MainTable": "Courses",
You can create a class for it and read the values into that class. Also changing it to a list in the JSON would be good. Steps I would do:
Change 'EndpointAPIStrings' into an array:
{
"EndpointAPIStrings":[
{
"Id":"Endpoint1",
"EndpointUrl":"https://localhost:7777",
"Username":"myusername1",
"Password":"mypassword1"
},
{
"Id":"Endpoint2",
"EndpointUrl":"https://localhost:8888",
"Username":"myusername2",
"Password":"mypassword2"
},
{
"Id":"Endpoint3",
"EndpointUrl":"https://localhost:9999",
"Username":"myusername3",
"Password":"mypassword3"
}
]
}
Create a C# class defining the objects in the JSON array:
public sealed class EndPoint {
public string Id { get; set; }
public string EndPointUrl { get; set; }
public string Username { get; set; }
public string Password { get; set; }
}
Change data retrieval from the configuration:
IConfiguration configuration = BuildConfiguration(environmentName);
CosmosClient cosmosClient = BuildCosmosClient(configuration);
List<EndPoint> endPoints = configuration.GetSection("EndPointAPIStrings").Get<List<EndPoint>>();
Now you have all your endpoints in the endPoints variable. You can remove and add properties into the JSON how you like and the only thing you need to do is change the class accordingly. Please note that you need the same names in the JSON and in the C# class in order to get a successful mapping.

How to add Bing Spellcheck to LuisRecognizerMiddleware?

Ok so this is how my LUIS app is configured in my bot.
On the LUIS website I can add Bing Spell Check to correct common spelling mistakes and have a better intent and entity match.
All that is required is that a BING API key needs to be added to the LUIS query string. But where do I configure that in the LuisRecognizerMiddleware?
I'm not even sure if that's the right place. But I guess it does put together the URI.
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddBot<MyBot>(options =>
{
options.CredentialProvider = new ConfigurationCredentialProvider(_configuration);
options.Middleware.Add(new CatchExceptionMiddleware<Exception>(async (context, exception) =>
{
await context.TraceActivity("MyBotException", exception);
await context.SendActivity("Sorry, it looks like something went wrong!");
}));
IStorage dataStore = new MemoryStorage();
options.Middleware.Add(new ConversationState<MyBotConversationState>(dataStore));
// Add LUIS recognizer as middleware
// see https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-howto-v4-luis?view=azure-bot-service-4.0&tabs=cs
(string modelId, string subscriptionKey, Uri url) = GetLuisConfiguration(_configuration);
LuisModel luisModel = new LuisModel(modelId, subscriptionKey, url);
options.Middleware.Add(new LuisRecognizerMiddleware(luisModel));
});
}
private static (string modelId, string subscriptionKey, Uri url) GetLuisConfiguration(IConfiguration configuration)
{
string modelId = configuration.GetSection("Luis-ModelId")?.Value;
string subscriptionKey = configuration.GetSection("Luis-SubscriptionId")?.Value;
string url = configuration.GetSection("Luis-Url")?.Value;
Uri baseUri = new Uri(url);
return (modelId, subscriptionKey, baseUri);
}
All I get so far is...
GET https://westeurope.api.cognitive.microsoft.com/luis/v2.0/apps/?subscription-key=&q=test234&log=True HTTP/1.1
What I expect is something among those lines (copied from the LUIS web portal)
GET https://westeurope.api.cognitive.microsoft.com/luis/v2.0/apps/?subscription-key=&spellCheck=true&bing-spell-check-subscription-key=&verbose=true&timezoneOffset=0&q=test234
I just had a quick glimpse at the source code and figured ILuisOptions is what I am looking for. There was no concrete implementation to that. It's "roll your own" I guess...
public class MyLuisOptions : ILuisOptions
{
public bool? Log { get; set; }
public bool? SpellCheck { get; set; }
public bool? Staging { get; set; }
public double? TimezoneOffset { get; set; }
public bool? Verbose { get; set; }
public string BingSpellCheckSubscriptionKey { get; set; }
}
...and of course you have to pass this along to the LuisRecognizerMiddleware.
options.Middleware.Add(new LuisRecognizerMiddleware(luisModel, new LuisRecognizerOptions { Verbose = true }, new MyLuisOptions { SpellCheck = true, BingSpellCheckSubscriptionKey = "test123" }));

Read a value from appsettings.json in 1.0.0-rc1-final

In one of my concrete class. I have the method.
public class Call : ICall
{
......
public Task<HttpResponseMessage> GetHttpResponseMessageFromDeviceAndDataService()
{
var client = new HttpClient();
var uri = new Uri("http://localhost:30151");
var response = GetAsyncHttpResponseMessage(client, uri);
return response;
}
Now I put the url into appsettings.json.
{
"AppSettings": {
"uri": "http://localhost:30151"
}
}
And I created a Startup.cs
public class Startup
{
public IConfiguration Configuration { get; set; }
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json");
Configuration = builder.Build();
}
}
and now I get stuck.
EDIT
By the way, I don't have a controller, it is a console application.
The preferred way to read configuration from appSettings.json is using dependency injection and the built or (or 3rd party) IoC container. All you need is to pass the configuration section to the Configure method.
public class AppSettings
{
public int NoRooms { get; set; }
public string Uri { get; set; }
}
services.Configure<AppSettings>(Configuration.GetSection("appsettings"));
This way you don't have to manually set the values or initialize the AppSettings class.
And use it in your service:
public class Call : ICall
{
private readonly AppSettings appSettings;
public Call(IOptions<AppSettings> appSettings)
{
this.appSettings = appSetings.Value;
}
public Task<HttpResponseMessage>GetHttpResponseMessageFromDeviceAndDataService()
{
var client = new HttpClient();
var uri = new Uri(appSettings.Uri);
var response = GetAsyncHttpResponseMessage(client, uri);
return response;
}
}
The IoC Container can also be used in a console application, you just got to bootstrap it yourself. The ServiceCollection class has no dependencies and can be instantiated normally and when you are done configuring, convert it to an IServiceProvider and resolve your main class with it and it would resolve all other dependencies.
public class Program
{
public static void Main(string[] args)
{
var configurationBuilder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json");
var configuration = configurationBuilder.Build()
.ReloadOnChanged("appsettings.json");
var services = new ServiceCollection();
services.Configure<AppSettings>(configuration.GetSection("appsettings"));
services.AddTransient<ICall, Call>();
// add other services
// after configuring, build the IoC container
IServiceProvider provider = services.BuildServiceProvider();
Program program = provider.GetService<Program>();
// run the application, in a console application we got to wait synchronously
program.Wait();
}
private readonly ICall callService;
// your programs main entry point
public Program(ICall callService)
{
this.callService = callService;
}
public async Task Run()
{
HttpResponseMessage result = await call.GetHttpResponseMessageFromDeviceAndDataService();
// do something with the result
}
}
Create a static class
public static class AppSettings
{
public static IConfiguration Configuration { get; set; }
public static T Get<T>(string key)
{
if (Configuration == null)
{
var builder = new ConfigurationBuilder().AddJsonFile("appsettings.json");
var configuration = builder.Build();
Configuration = configuration.GetSection("AppSettings");
}
return (T)Convert.ChangeType(Configuration[key], typeof(T));
}
}
then access the settings anywhere you want like
var uri = AppSettings.Get<string>("uri");
var rooms = AppSettings.Get<int>("noRooms");
appsettings.json example
{
"AppSettings": {
"uri": "http://localhost:30151",
"noRooms": 100
}
}
You can access data from the IConfigurationRoot as following:
Configuration["AppSettings:uri"]
Like suggested in the comment I would put the information in a seperate class for that info and pass it into the DI container.
the class
public class AppSettings {
public string Uri { get; set; }
}
DI
public void ConfigureServices(IServiceCollection services)
{
services.Configure<AppSettings>(new AppSettings() { Uri = Configuration["AppSettings:uri"] });
// ...
}
Controller
public class DemoController
{
public HomeController(IOptions<AppSettings> settings)
{
//do something with it
}
}

Categories

Resources