How can I use an app setting in a custom resolver? - c#

I'll try and explain what I want to do. I have a Server object, that contains a parameter with a date of when it was last "synced"
public class Server
{
public Guid Id { get; set; }
public DateTime LastSyncedAt { get; set; }
}
I want to map this to a ServerSummary object, which has a Status parameter.
public class ServerSummary
{
public Guid Id { get; set; }
public string Status { get; set; }
}
This status will be set by checking to see if the Server has been synced in the last X minutes, with X being stored in my appsettings.json file:
{
"SyncOffsetMinutes": "5"
}
I have a model class for this:
public class AppSettings
{
public int SyncOffsetMinutes { get; set; }
}
which is configured in my Startup.cs class:
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
...
services.Configure<AppSettings>(Configuration);
}
To resolve this Status property, I have this in my AutoMapper profile configuration:
public class MyAutoMapperProfileConfiguration : Profile
{
public MyAutoMapperProfileConfiguration()
{
CreateMap<Server, ServerSummary>()
.ForMember(d => d.Status, o => o.ResolveUsing<ServerSyncStatusResolver>());
}
}
In my custom resolver, I'm trying to use DI to inject my AppSettings so that I can use the app setting in my Resolve method:
public class ServerSyncStatusResolver : IValueResolver<Server, ServerSummary, string>
{
private AppSettings _appSettings;
public ServerSyncStatusResolver(AppSettings appSettings)
{
_appSettings = appSettings;
}
public string Resolve(Server source, ServerSummary destination, string member, ResolutionContext context)
{
return source.LastSyncedAt.AddMinutes(_appSettings.SyncOffsetMinutes) < DateTime.UtcNow ? "Offline" : "Online";
}
}
But when I actually do my mapping:
var servers = _dbContext.Servers.ToList();
var serverSummaries = Mapper.Map<List<Server>, List<ServerSummary>>(servers);
I get an error saying
No parameterless constructor defined for this object
Is there a way to inject my AppSettings file into the resolver? Am I doing it wrong?

I was very much wrong, see the update below
You can not use injections at this point, because the resolver instance is created at the time the mapper configuration is initialized.
The only possibility is to put AppSettings as a part of one of the objects to be mapped.
The only thing you can use is a static class and update its members from another places, but this is a very, very, very bad solution. I did not tell you, it's almost illegal =)
Update by #Lucian Bargaoanu from comments
You can find answer in documentation here

Related

How to exclude certain model from Swagger UI

I have a model that's being used by one of the other models, that is being accepted as a parameter to one of my controllers. So as a result, this model is being displayed in Swagger UI. This model is a nullable type and is optional and I want to hide it from my documentation.
public class A
{
public string SomeProperty { get; set; }
public B? ClassB {get; set; }
}
public class B
{
public int SomeIntProperty { get; set; }
public bool SomeBooleanProperty { get; set; }
}
in the controller method:
public async Task<ActionResult<SomeType>> GetSomeType(A modelA, CancellationToken token)
As is, this endpoint will accept a JSON document like:
{
"SomeProperty": "SomeValue"
}
And won't need B to be present. So as a result, I want to hide B from my Swagger schemas. How can I do that? I found some related questions/answers but all of them are about hiding properties, https://stackoverflow.com/a/48454933/16749442
Hiding all properties of a model results in empty model:
The only working and clean solution I found is, unfortunately, using reflection again.
SwaggerExcludeAttribute.cs
[AttributeUsage(AttributeTargets.Class)]
public class SwaggerExcludeAttribute : Attribute
{
}
SwaggerIgnoreModelFilter.cs
public class SwaggerIgnoreModelFilter : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
// Get all models that are decorated with SwaggerExcludeAttribute
// This will only work for models that are under current Assembly
var excludedTypes = GetTypesWithHelpAttribute(Assembly.GetExecutingAssembly());
// Loop through them
foreach (var _type in excludedTypes)
{
// Check if that type exists in SchemaRepository
if (context.SchemaRepository.TryLookupByType(_type, out _))
{
// If the type exists in SchemaRepository, check if name exists in the dictionary
if (swaggerDoc.Components.Schemas.ContainsKey(_type.Name))
{
// Remove the schema
swaggerDoc.Components.Schemas.Remove(_type.Name);
}
}
}
}
// Get all types in assembly that contains SwaggerExcludeAttribute
public static IEnumerable<Type> GetTypesWithHelpAttribute(Assembly assembly)
{
return assembly.GetTypes().Where(type => type.GetCustomAttributes(typeof(SwaggerExcludeAttribute), true).Length > 0);
}
}
Startup.cs
services.AddSwaggerGen(c =>
{
.....
c.DocumentFilter<SwaggerIgnoreModelFilter>();
......
});

How do I setup a service to receive some options in ASP.NET Core?

I'm just getting my head round setting up my own services to inject from Startup.cs in an ASP.NET Core 3.1+ project.
So far I created an interface and a class:
public interface IMyClass
{
public string SomeString { get; set; }
}
public class MyClass : IMyClass
{
public string SomeString { get; set; }
}
I added the service in startup using:
services.AddScoped<Services.IMyClass, Services.MyClass>();
Which works fine, except I want this class to have various strings it can return based on the current configuration and I have no idea how to pass those variables from appsettings.json to populate SomeString so I can then get it from in a controller.
I was trying this:
services.AddScoped<Services.IMyClass, Services.MyClass>(
Configuration.GetValue<string>("SomeAppSettingKey");
To pull a string from appsettings, but don't know how to populate a constructor in the class with the string. Do I need to add some sort of options class as a property in MyClass ?
There's lots of hints on how I can be adding some of these config settings but I'm just missing some info e.g.
services.AddTransient<IMyClass, MyClass>();
services.Configure<MyClass>(Configuration);
Makes syntactical sense to me but I'm still not sure how to populate the string in MyClass by passing the Configuration here.
Create a model to store the setting extracted from configuration
public class MyClassOptions {
public string SomeAppSettingKey { get; set; }
}
(Note the matching names)
Configure it at Startup
services.Configure<MyClassOptions>(Configuration);
(This assumes the key is in the root of the settings file)
Update the target class to inject the options configured
public class MyClass : IMyClass {
public MyClass (IOptions<MyClassOptions> options) {
SomeString = options.Value.SomeAppSettingKey;
}
public string SomeString { get; set; }
}
Reference Options pattern in ASP.NET Core

Custom IOptions map path

I currently have a Json config file that looks something like this :
{
"MySettings" " {
"SomeSetting" : "SomeValue"
}
}
In the perfect world, I would have a class that matches that same structure. But, I need to map it to a class that would look something like this :
public class MySettingsUpdated
{
public string MyRenamedSetting {get;set;}
}
I am already using a custom ConfigurationProvider to get data from a configuration file (for various reasons), and I -could- create the data in the expected path in there, but it would make my life much easier if I could decorate the new class with some type of attribute in order to specify where the data needs to come from.
Any way to do this?
static string data = #"
{
""class"": {
""property"" : ""some string!""
}
}";
class DTO
{
[JsonProperty("class")]
public Data Property { get; set; }
}
class Data
{
[JsonProperty("property")]
public string Value { get; set; }
}
static void Main(string[] args)
{
var result = JsonConvert.DeserializeObject<DTO>(data);
}
You can use the .Bind() method exposed on the IConfigurationSection interface.
In a class it could look like
public class SomeClassDoingWork
{
private MyConfigClass MyConf = new MyConfigClass();
public SomeClassDoingWork(IConfiguration config)
{
config.GetSection("MySettings").Bind(MyConf);
}
}

Howto inject the right instance of a single type into another class

I've got two typed HttpClients that get a configuration class injected:
public class MyConfig
{
prop string Prop { get; set; }
}
public class MyConfig2
{
prop string Prop { get; set; }
}
public class HttpClientService
{
public HttpClientCoreService(HttpClient client, IOptions<MyConfig> config)
{
}
}
public class HttpClientService2
{
public HttpClientCoreService2(HttpClient client, IOptions<MyConfig2> config)
{
}
}
As you can see the classes are basically identical, just the configuration isn't.
services.Configure<MyConfig>(x =>
{
x.Prop = "x";
}
services.Configure<MyConfig2(x =>
{
x.Prop = "y";
}
services.AddHttpClient<HttpClientService>();
services.AddHttpClient<HttpClientService2>();
I'd like to have just one config and one service but therefore I need some DI magic (if possible) to happen.
I somehow need to tell the DI which version of MyConfig should be injected for the specific instance of HttpClientService (this should be possible if I use named HttpClients).
Is this possible?

How to register class as interface but invoke a method?

Following this blog I have copied the code below and it works fine, but I have modified it to suit my needs by creating an interface for DatabaseSettings and UserSettings with the same suffix by adding I to the current name. But the issue is that it is trying to register the interface as an interface which is wrong?
Before making the changes settings variables only have two entries now that I have added the interfaces settings is picking up the interface (because of the suffix) so it now has for entries instead of adding them as entries I would like to use them with the corresponding class and still invoke .LoadSection(type)
public class SettingsModule : Module
{
private readonly string _configurationFilePath;
private readonly string _sectionNameSuffix;
public AeSettingsModule(string configurationFilePath, string sectionNameSuffix = "Settings")
{
_configurationFilePath = configurationFilePath;
_sectionNameSuffix = sectionNameSuffix;
}
protected override void Load(ContainerBuilder builder)
{
var settings = Assembly.Load(nameof(DataLayer))
.GetTypes()
.Where(t => t.Name.EndsWith(_sectionNameSuffix, StringComparison.InvariantCulture))
.ToList();
settings.ForEach(type =>
{
builder.Register(c => c.Resolve<ISettingsReader>().LoadSection(type))
.As(type)
.SingleInstance();
});
}
}
public class DatabaseSettings: IDatabaseSettings
{
public string ConnectionString { get; set; }
public int TimeoutSeconds { get; set; }
}
public interface IDatabaseSettings
{
string ConnectionString { get; set; }
int TimeoutSeconds { get; set; }
}
The error message I am getting is:
`System.MissingMethodException: 'Cannot create an instance of an interface.'`
Because I have changed the `Constructor injection from a class to an interface:
public UserService(IDatabaseSettings databaseSettings, IUserSettings userSettings)
{
...
}
Because I have added the interfaces and it has the same prefixes "Settings" it is picking up the interfaces which I don't wan, instead I would like to use it with the corresponding class?
I am trying to do this(but with the syntax above because I would like to invoke LoadSection too):
builder.RegisterType<DatabaseSettings>().As<IDatabaseSettings>();
I have found a solution through trial and error and the app works as expected but what I am not sure of is if it efficient, reliable etc.
I am posting merely to get feedback and if it is valid other might be able to use it.
var settings = Assembly.Load(nameof(DataLayer))
.GetTypes()
.Where(t => t.Name.EndsWith(_sectionNameSuffix, StringComparison.InvariantCulture ) && !t.IsInterface)
.ToList();
settings.ForEach(type =>
{
builder.Register(c => c.Resolve<ISettingsReader>().LoadSection(type))
.As(type.GetInterfaces())
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
});

Categories

Resources