Injectable ApplicationConfig service - c#

I want a service I can inject - or in my example get with GetService - that contains settings from my appsettings.json file.
The appsettings.json fragment looks like this:
"ExternalInterfaces": [
{
"Name": "name1",
"BaseUrl": "https://www.baseurl1.svc"
},
{
"Name": "name2",
"BaseUrl": "https://www.baseurl2.svc"
}
]
To do this I have the following interfaces:
using System.Collections.Generic;
namespace Infrastructure.Settings
{
public interface IExternalInterfaceSettingsCollection
{
IReadOnlyCollection<IExternalInterfaceSettings> Settings { get; set; }
}
}
namespace Infrastructure.Settings
{
public interface IExternalInterfaceSettings
{
string Name { get; set; }
string BaseUrl { get; set; }
}
}
and the following corresponding classes:
using System.Collections.Generic;
namespace Infrastructure.Settings
{
public class ExternalInterfaceSettingsCollection : IExternalInterfaceSettingsCollection
{
public IReadOnlyCollection<IExternalInterfaceSettings> Settings { get; set; }
}
}
namespace Infrastructure.Settings
{
public class ExternalInterfaceSettings : IExternalInterfaceSettings
{
const string DefaultName = "newExternalInterface";
const string DefaultBaseUrl = "";
public string Name { get; set; } = DefaultName;
public string BaseUrl { get; set; } = DefaultBaseUrl;
}
}
And in my Startup.cs I have this (definitely gets called with no exceptions):
services.Configure<IExternalInterfaceSettingsCollection>(settings => _configuration.GetSection("ExternalInterfaces").Bind(settings));
and this is then consumed as follows:
var externalInterfaceConfiguration = app.ApplicationServices.GetService<ExternalInterfaceSettingsCollection>();
var Setting1BaseUrl = externalInterfaceConfiguration.Settings
.SingleOrDefault(s => s.Name == "name1")?.BaseUrl;
However, in the last 3 lines, externalInterfaceConfiguration is always null.
I'm clearly missing something, but I can't see what. Any clues?

You've registered IExternalInterfaceSettings, but you're attempting to retrieve ExternalInterfaceSettings. There's no such service in the collection, so the result is null (since you used GetService<T>). If you had used GetRequiredService<T> then an exception would have been thrown telling you as much.
Then, the options pattern is not meant to bind to interfaces. The whole idea is that you're binding to a POCO that represents a specific set of settings. If you want to use an interface, I suppose that's your prerogative, but it's not going to be applicable to the options configuration. In other words, you need the following instead:
services.Configure<ExternalInterfaceSettings>(Configuration.GetSection("ExternalInterfaces"));
(Note, the action overload with Bind is superfluous. You can just pass the config section directly.)
With that, you'll be able to request something like IOptions<ExternalInterfaceSettings>, but you still cannot get ExternalInterfaceSettings directly from the service collection. If you want that functionality, you'll need to add an additional service registration (which can utilize an interface, this time):
services.AddScoped<IExternalInterfaceSettings, ExternalInterfaceSettings>(p =>
p.GetRequiredService<IOptions<ExternalInterfaceSettings>>().Value);

Related

Multiple instances of class with different parameters based on configuration file

So I have a simple configuration class PubsubSettings:
public class PubSubSettings
{
public string ProjectId { get; set; }
public string TopicId { get; set; }
public int PartnerId { get; set; }
public string SubscriptionId { get; set; }
}
I have previously only had one of these configured in my appsettings.json but now I want to be able to handle an arbitrary number of them.
I have another class, PubSub, that I usually inject an IOptions<PubSubSettings> into. And this, in turn, gets injected into my Worker class.
services.Configure<PubSubSettings>(configuration.GetSection(nameof(PubSubSettings)));
...
services.AddHostedService<Worker>();
So, what I want to do now, is add a new Worker as a hosted service for each entry in my AppSettings PubSubSettings section and inject the relevant IOptions<PubSubSettings> into each of these (along with the standard ILogger).
So in essence, I'd like this config block:
"PubsubSettings": [
{
"ProjectId": "project1",
"TopicId": "topic",
"PartnerId": 1,
"SubscriptionId": "sub1"
},
{
"ProjectId": "project2",
"TopicId": "topic2",
"PartnerId": 2,
"SubscriptionId": "sub2"
}
]
To end up with two hosted services being created, one with the first set of options and the other with the second.
I've seen a few questions looking for similar things but nothing I could find quite lined up with this so I'm a bit stumped. Any ideas?
The solution is Dotnet 5.
So from what I've been able to find, there's no way to do this out-of-the box.
However, This can be done manually using a combination of ActivatorUtilities and Configuration.Bind().
private void CreateWorkers(IServiceCollection services, IConfigurationRoot configuration)
{
List<PubSubSettings> pubsubSettings = new();
configuration.Bind(nameof(PubSubSettings), pubsubSettings);
foreach (PubSubSettings setting in pubsubSettings)
{
services.AddSingleton<IHostedService>(s => ActivatorUtilities.CreateInstance<Worker>(s, ActivatorUtilities.CreateInstance<PubSub.PubSub>(s, setting)));
}
}
Essentially, you can use Bind to get the configuration objects from the JSON. Then you can manually construct the Worker for the call to AddHostedService using CreateInstance.
Two calls are needed in this case, one to generate the PubSub for the worker (in which we pass the setting parameter) and the other to generate the Worker itself.
ActivatorUtilities essentially injects everything you need for the object except the parameters you've provided.
We need to use .AddSingleton<IHostedService> because of the way that the framework checks for dupes with AddHostedService().
Maybe you could try creating a class only for the object and let the PubSubSettings class only for the array:
public class PubSubSettings
{
public PubSubObject[] PubSubs { get; set; }
}
public class PubSubObject
{
public string ProjectId { get; set; }
public string TopicId { get; set; }
public int PartnerId { get; set; }
public string SubscriptionId { get; set; }
}
Then in the startup class you should use Bind to get the current value of the array to create a Worker for each PubSub:
PubSubSettings settings = new PubSubSettings();
Configuration.GetSection(nameof(PubSubSettings)).Bind(settings);
...
foreach(PubSubObject item in settings.PubSubs)
{
services.AddHostedService<Worker>();
}
Then in the PubSub class you need to search the PartnerId inside the Array.
Or you could follow the approach described in the section Named options support using IConfigureNamedOptions in the Microsoft docs: Options pattern in ASP.NET Core

How to have different names for my API DTO's (based on the generator) with Nswag?

I have a .NET 5.0 ASP.NET Core project and I am using Nswag to generate an API client.
Let's say I have the following API model:
public class GetFooListResponseModel
{
public string Bar { get; set; }
}
What I would like is 2 things. Let's start with the basic one.
How do I make the generated API Client's model name a different one than the one in my project? For example, I want the generated typescript model to be called Foo instead of GetFooListResponseModel.
Could I make them have different names based on the client it is generating? For example, for my C# Client I am completely fine with the existing model name, but the typescript one needs to be changed. If this is not possible it's no big deal, but it would be nice.
Thank you very much!
You can use SchemaNameGenerator to customise the model name. You can refer shadowsheep's answer . To generate completely new Model name you can use CustomeAttribute with SchemaNameGenerator.
public class ClientModelAttribute : Attribute
{
public string Name { get; set; }
public ClientModelAttribute(string name)
{
Name = name;
}
}
internal class CustomSchemaNameGenerator : ISchemaNameGenerator
{
public string Generate(Type type)
{
var attrs = type.GetCustomAttributes(typeof(ClientModelAttribute),true);
foreach (var attr in attrs)
{
if(attr is ClientModelAttribute)
{
ClientModelAttribute clientModel = attr as ClientModelAttribute;
return clientModel.Name;
}
}
return type.FullName;
}
}
Model Class with CustomAttribute
[ClientModel("Foo")]
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string Summary { get; set; }
}
update ConfigureServices
services.AddSwaggerDocument(cfg => { cfg.SchemaNameGenerator = new CustomSchemaNameGenerator(); });
swagger.json
"paths": {
"/WeatherForecast": {
"get": {
"tags": [
"WeatherForecast"
],
"operationId": "WeatherForecast_Get",
"responses": {
"200": {
"x-nullable": false,
"description": "",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/Foo"
}
}
}
}
}
}
Sometimes, the best solution is the simplest.
Try creating a windows/message asking the user what they would like their module name to be that would trigger once the program starts?
You can simply create a injectable service called 'FooService' for communicating with backend services using HTTP Request. Specify the model/class (you can create any name you want) being returned from your API in getFooListResponse(). The naming not necessary must be the same with GetFooListResponseModel, as long the attributes and its data type of the model/class are same.
foo.service.ts
#Injectable()
export class FooService
{
constructor(
protected http: HttpClient
) {}
getFooListResponse() {
const endpointUrl = "myEndpointUrl";
return this.http.get<Foo>(endpointUrl);
}
}
foo.model.ts
export class Foo {
public Bar: string;
}

Dependency Injection with options pattern

I'm trying to load some settings from the appsettings file and I've got a small issue with the way lists are loaded when using the options pattern. Suppose I've got the following classes (for loading the settings):
public class Application {
public string Name { get; set; } = "";
public IEnumerable<string> Roles { get; set; } = new[] {""};
public Application ToApplicationWithoutPass() =>
new Application {
Name = Name,
Username = Username,
Roles = Roles.ToList()
};
}
public class Applications {
public IEnumerable<Application> AppList { get; set; } = new List<Application>();
}
And here is what the settings that are defined on the appsetings file look like:
"Applications": {
"AppList": [
{
"Name": "SraWebuserAdmin",
"Roles": [ "SraEntitiesWriters", "SraEntitiesReaders", "SraEntitiesLoginAccess" ]
},
...
Here are the entries from the DI setup which is done on the ConfigureServices method:
services.Configure<Applications>(options => Configuration.GetSection("Applications").Bind(options));
services.AddScoped<IApplicationAccessVerifier, ApplicationAccessVerifier>();
And, finally, here's the constructor of the ApplicationAccessVerifier class:
public ApplicationAccessVerifier(IOptionsSnapshot<Applications> applicationOptions) {
_applicationOptions = applicationOptions;
}
Now, the question: if I don't initialize the AppList property,
public class Applications {
public IEnumerable<Application> AppList { get; set; }
}
then the settings are loaded correctly.
However, if I initialized it like I've shown (making sure the filed wrapper by the property is initialized with an empty list), then the settings won't be copied to the AppList.
I find this strange since simple properties (ex.: Name on the Application class) aren't affected by the same issue.
Can anyone tell me why this happens or point me to an official documentation about it?

How to bind IConfiguration to class having parameters in constructor

I am using standard configuration pattern for ASP.NET Core applications and I can not bind configuration to my class as it has construtor with parameters.
In appsettings.json I included desired config:
"MyServiceConfig": {
"Identity": {
"Version": "1.0",
"ComplicatedUri": {
"Scheme": "http",
"Authority": "localhost",
"Path": "SuperService"
}
}
},
My config class and it's dependencies look like that:
public class MyServiceConfig
{
public MyIdentity Identity { get; set; }
}
public class MyIdentity
{
public string IdentityName { get; set; }
public string Version { get; set; }
public MyComplicatedUri ComplicatedProperty { get; set; }
public MyIdentity(string version, MyComplicatedUri complicatedProperty)
{
Version = version;
ComplicatedProperty = complicatedProperty;
IdentityName = complicatedProperty.Path;
}
}
public class MyComplicatedUri
{
public string Scheme { get; set; }
public string Authority { get; set; }
public string Path { get; set; }
}
I have already tried code like that:
private MyServiceConfig GetMyConfig(IConfiguration configuration)
{
var config = new MyServiceConfig();
configuration.GetSection("MyServiceConfig").Bind(config);
return config;
}
It throws exception:
'Cannot create instance of type 'MyIdentity' because it is missing
a public parameterless constructor.'
That behaviour can make sense in some cases but in that particular one not so much. Mappings could be straightforward - by property names which have public setters or by constructor parameter names.
Another idea would be adding converter in AddJsonOptions in Startup class for my types - IConfiguration.Bind could infer how to construct it but I also tried that with no success.
Have you encoutered similar problems and found some reasonable solution to that?
Edit: Adding parameterless constructor will work of course, but sometimes I need to deal with some classes from external packages I'd like to use as parts of my config class so let's assume we can not modify them. I'd like to avoid adding new types for mapping only as well. Ideally I'd like to force ASP.NET Core engine to use existing constructor with parameters and by parameter name map with json properties - which currently is not working.
You should just add a default constructor in MyIdentity class.
.bind() binds the configuration into the object using the default constructor.
So, add the required default constructor in your MyIdentity class and it will be fine.
public MyIdentity(){}
Also, you can use Options.
In ConfigureServices, add the following:
services.AddOptions();
services.ConfigureOptions<MyServiceConfig>();
and then use dependency injection to initialize it.
In addition, use your own JsonConverter

configuration.GetValue list returns null

I am trying to read a list from appsettings.json file using the GetValue<T> method:
var builder = new ConfigurationBuilder().SetBasePath(System.AppContext.BaseDirectory)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
IConfigurationRoot configuration = builder.Build();
var rr = configuration.GetValue<IList<ConnectionSettings>>("Connections");
public class ConnectionSettings
{
public string Name { get; set; }
public string Host { get; set; }
public string Account { get; set; }
public string Password { get; set; }
}
and my appsettings.json
{
"Connections": [
{
"Name": "",
"Host": "192.168.1.5",
"Account": "74687",
"Password": "asdsdadsq"
},
{
"Name": "",
"Host": "127.0.0.1",
"Account": "45654",
"Password": "asdasads"
}
]
}
Problem is that I always get null and I dont understand why.
According to the documentation for GetValue<>, it gets the value of a (single) key and converts it to the specified type. Unfortunately, it doesn't throw an error if the value can't be converted, which is the situation you're running into.
I believe that Get<> would be preferable in your situation.
var rr = configuration.GetSection("Connections").Get<IList<ConnectionSettings>>();
According to Get<>'s documentation, it:
Attempts to bind the configuration instance to a new instance of type T. If this configuration section has a value, that will be used. Otherwise binding by matching property names against configuration keys recursively.
This allows you to get the value directly or, if it can't find the property, it looks for nested objects that contain a matching property.
An alternative would be as #AthanasiosKataras says; use Bind<>. This is helpful when you might have a sparse configuration in which you want to overlay some values with either default or calculated values.
I have spotted the following issue on GitHub: GetValue<T> not working with lists
Long story short: It is by design.
So you can try this:
var result = new List<ConnectionSettings>();
var rr = configuration.GetSection("Connections").Bind(result);
Configuration.Get<T> is a better option when you have nested configuration using non-primitive structure like list or array.
{
"Email": {
"ToEmails": [
"test1#test.com",
"test2#test.com",
"test3#test.com"
]
}
List<string> emailTo = _config.GetSection("Email:ToEmails").Get<List<string>>()
foreach (string email in emailTo)
{
sendGridMessage.AddTo(new EmailAddress(email));
}
OR use Bind()
public static class ConfigurationRootExtentions
{
public static List<T> GetListValue<T>(this IConfigurationRoot configurationRoot, string section)
{
var result = new List<T>();
configurationRoot.GetSection(section).Bind(result);
return result;
}
}
Ref[1]: https://blog.bitscry.com/2017/11/14/reading-lists-from-appsettings-json/
Ref[2]: https://github.com/aspnet/Configuration/issues/451
If you develop a project from scratch i.e using the VS template then set the following properties for appsettings.json (Right-click on appsettings.json--> click on Properties ). After that update following property value.
Copy to output Directory: Copy always
Now, you can run the application and get value using GetValue method.
After that, You can reverse the same setting as pervious
Copy to output Directory: Copy if newer
It might be useful for you.
GetValue<type>("key") does not work for complex types.
Let's say you have the following appsettings.json structure:
{
"ArrayOfObjectsWithinObjects": [
{
"foo0": "bar",
"foo1": 1,
"foo2": false,
"anotherObject": {
"foo3.1": "3.1",
"foo3.2": "3.2"
}
},
{
"foo0": "bar",
"foo1": 1,
"foo2": false,
"anotherObject": {
"foo3.1": "3.1",
"foo3.2": "3.2"
}
}
...
]
}
and C# the appropriate classes
public class Foo
{
public string foo0 { get; set;}
public int foo1 { get; set;}
public bool foo2 { get; set;}
public AnotherObject anotherObject { get; set;}
}
public class AnotherObject
{
public string foo3.1 { get; set; }
public string foo3.2 { get; set; }
}
You should be able to:
//using static System.Console;
//using System.Linq;
//using Microsoft.Extensions.Configuration;
var _listOfFoos = _config.GetSection("ArrayOfObjects")
.GetChildren()
.ToList()
.Select( x => new Foo
{
x.GetValue<string>("")
x.GetValue<int>("")
x.GetValue<bool>("")
x.GetSection("anotherObject").Get<AnotherObject>()
});
WriteLine(_listOfFoos.anotherObject.foo3.1);
Note that foo3.1 is an invalid c# syntax and was used for didactic purposes.

Categories