Automapper mapping validation during application startup in .net core? - c#

When I was using Automapper v6 (I'm using .net core), I had this command to validate configuration :
configuration.AssertConfigurationIsValid();
But now, after moving to the latest version, I don't have this since my config is exactly (docs):
private void ConfigureServices(IServiceCollection services)
{
services.AddAutoMapper(typeof(AppSettingsMappingProfile)); //marker type
}
However, I still want to validate all mappings at startup .
The docs says that I need to do this :
var configuration = new MapperConfiguration(cfg =>
cfg.CreateMap<Source, Destination>());
configuration.AssertConfigurationIsValid();
But I don't have it since I'm using profiles with this command :
services.AddAutoMapper(typeof(AppSettingsMappingProfile));
Question:
How can I still make AutoMapper scan for validation at startup?

If you look at the source code for AddAutoMapper, you will see that it registers IConfigurationProvider as singleton. This means you can safely have it in your Configure method and do the validation there:
public void Configure(IConfigurationProvider pr)
{
pr.AssertConfigurationIsValid();
}

Following these steps should work:
Add AutoMapper.Extensions.Microsoft.DependencyInjection NuGet
package
services.AddAutoMapper(typeof(...)) within
ConfigureServices(...).
Add IMapper mapper as parameter to Configure(...) method
mapper.ConfigurationProvider.AssertConfigurationIsValid(); within
Configure(...)
Example (omitting namespace inclusion)
public void ConfigureServices(IServiceCollection services)
{
services.AddAutoMapper(typeof(Startup));
}
public void Configure(IApplicationBuilder app, IMapper mapper)
{
mapper.ConfigurationProvider.AssertConfigurationIsValid();
}

Related

Implementing Dependency Injection from a new file

I`ve been working on an ASP.NET Core 5 MVC project and it is working as expected, the only issue i'm having right now is the size of the Startup.cs file, i'm using the Microsoft.Extensions.DependencyInjection a lot, and it's very good!, but as i mentioned it is getting very crowded with those "services.AddTransient, Scoped or Singleton", is there a way to create my own class to add those services and call it from the Startup.cs?.
So far i've been trying to make a static class with an "Inject" method that will return an IServiceCollection, but it is not working, i've been searching on google for some examples but it looks like this is not a "thing".
Let me share some sample code:
using FluentValidation;
using Microsoft.Extensions.DependencyInjection;
using Models;
namespace MyFirstAzureWebApp
{
public class Injections
{
private static readonly IServiceCollection _services;
public static IServiceCollection Inject()
{
_services.AddTransient<IValidator<Customer>, CustomerValidator>();
_services.AddTransient<IValidator<Requirement, RequirementValidator>();
_services.AddApplicationInsightsTelemetry(Configuration["APPINSIGHTS_CONNECTIONSTRING"]);
_services
.AddFluentEmail("noreply#myownmail.com")
.AddRazorRenderer()
.AddSmtpSender("smtp.myownmail.com",445);
return _services;
}
}
}
And at the Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews()
.AddFluentValidation(opt =>
{
opt.DisableDataAnnotationsValidation = true;
opt.ValidatorOptions.LanguageManager.Culture = new CultureInfo("es");
});
//Dependency Injetions call
services = Injections.Inject();
}
I hope this information is enougth to bring some light over my problem.
Thank you very much!
yes you absolutely can do that. I use it all the time to keep my code nice and clean. You can easily do it with Extension methods:
public class Injections
{
public static IServiceCollection RegisterServices(this IServiceCollection services) => services
.AddTransient<IValidator<Customer>, CustomerValidator>()
.AddTransient<IValidator<Requirement, RequirementValidator>();
public static IServiceCollection AddOtherServices(this IServiceCollection services, IConfiguration configuration) => services
.AddApplicationInsightsTelemetry(configuration["APPINSIGHTS_CONNECTIONSTRING"])
.AddFluentEmail("noreply#myownmail.com")
.AddRazorRenderer()
.AddSmtpSender("smtp.myownmail.com",445);
}
Then in your Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews()
.AddFluentValidation(opt =>
{
opt.DisableDataAnnotationsValidation = true;
opt.ValidatorOptions.LanguageManager.Culture = new CultureInfo("es");
});
//Dependency Injetions call
services.RegisterServices();
services.AddOtherServices(Configuration);
}
This is quite a common thing (because yes - it does get crowded!).
One approach is to write extension methods on IServiceCollection grouping bits of functionality together.
For example, you might create a file called DatabaseServices.cs, which adds entity framework or Dapper or whatever.
// DatabaseServices.cs
public static class DatabaseServices
{
public static IServiceCollection AddDatabases(this IServiceCollection services)
{
// Set up Entity Framework
services.AddDbContext<MyContext>(/* configure EF */);
// Do other stuff related to databases.
// Return the service collection to allow chaining of calls.
return services
}
}
Then in Startup.cs you can just do:
// Startup.cs
services.AddDatabases();
Create other files to add logging, configuration, services, HTTP clients, etc. etc.

Dependency Injection - Multiple projects

I'm creating a wep api and this is the current structure:
API - The Web API (.net core web api project)
DAL - DbContext and Entities (.net core class library)
DTO - Data Transfert Objects - The classes I send to the client without sensible data (.net core class library)
REPO - Contains de Interfaces and Repositories (.net core class library)
For information I had everything on the same project and decided to split into multiple class libraries.
What I've done until now:
Added the references beetween each project
Update usings
Changed namespaces names to the correct ones
Solution as 0 errors
I think that my problem is related to dependency injection because when I try to access a controller from postman or from the browser this error happens:
InvalidOperationException: Error while validating the service descriptor 'ServiceType: FootballManager.REPO.ILeagueRepository Lifetime: Scoped ImplementationType: FootballManager.REPO.LeagueRepository': Unable to resolve service for type 'FootballManager.DAL.FootballManagerAPIContext' while attempting to activate 'FootballManager.REPO.LeagueRepository'.
My Startup.cs looks like this:
using FootballManager.REPO;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace FootballManager.API
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddDefaultPolicy(
builder =>
{
builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader();
});
});
services.AddControllers().AddNewtonsoftJson(options =>
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
services.AddScoped<ILeagueRepository, LeagueRepository>();
services.AddScoped<IMatchRepository, MatchRepository>();
services.AddScoped<IPlayerRepository, PlayerRepository>();
services.AddScoped<IRefereeRepository, RefereeRepository>();
services.AddScoped<ITeamRepository, TeamRepository>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseCors();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
This is my controller code where I do the injection:
public class LeaguesController : ControllerBase
{
private readonly ILeagueRepository _repo;
public LeaguesController(ILeagueRepository repo)
{
_repo = repo;
}
[HttpGet]
public async Task<ActionResult<IEnumerable<LeagueDto>>> GetLeagues()
{
return await _repo.GetAll();
}
}
For my DbContext connection I did directly on the DAL project like this (I dont think that the problem is here):
public partial class FootballManagerAPIContext : DbContext
{
public FootballManagerAPIContext()
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer(#"Server =.\SQLEXPRESS; Database = FootballManagerAPI; Trusted_Connection = True;");
}
}
}
After hours on the web and stackoverflow I still can't find any working solution...
How can I solve this error and why I'm having this? Thank you.
You never instantiate your DbContext - the error is very explicit about that;
Unable to resolve service for type 'FootballManager.DAL.FootballManagerAPIContext'
You also need to register the DbContext you need in the startup including configuration
I cant add comments to you question so I leave this here:
Maybe its a stupid question but, maybe you forgot it:
Does LeagueRepository inherit from ILeagueRepository?
I think this will help you.
Check out this video in which i explain how to implement dependency injection using autofac.
https://studio.youtube.com/video/XvklkAj7qPg/edit
Also i sugest that you should use disposable database connection, connect and disconnect in every method. So do not use dependency injection for db context.
Check if you registered the db context.
services.AddDbContext(options => options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));
In the services configuration i can't see it.
Thanks,
Liviu

How to inject dependencies inside an ASP.NET Core Health Check

I'm trying to use the new ASP.NET Code 2.2 Healthchecks feature.
In this link on the .net blog, it shows an example:
public void ConfigureServices(IServiceCollection services)
{
//...
services
.AddHealthChecks()
.AddCheck(new SqlConnectionHealthCheck("MyDatabase", Configuration["ConnectionStrings:DefaultConnection"]));
//...
}
public void Configure(IApplicationBuilder app)
{
app.UseHealthChecks("/healthz");
}
I can add custom checks that implement the Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck interface. But since I need to provide to the AddCheck method an instance instead of a type, and it needs to run inside the ConfigureServices method, I can't inject any dependency in my custom checker.
Is there any way to workaround this?
As of .NET Core 3.0, the registration is simpler and boils down to this
public void ConfigureServices(IServiceCollection services)
{
services.AddHealthChecks();
services.AddSingleton<SomeDependency>();
services.AddCheck<SomeHealthCheck>("mycheck");
}
Note that you no longer have the singleton vs transient conflict as you use what the engine needs to use.
The name of the check is mandatory, therefore you have to pick up one.
While the accepted asnwer seems no longer to work.
Short Answer
How to inject dependencies inside an ASP.NET Core Health Check.
If we register our services in a correct order, then SomeDependency will be available for injection into the SomeHealthCheck constructor, and SomeHealthCheck will run as part of the health check feature.
public void ConfigureServices(IServiceCollection services)
{
services.AddHealthChecks();
services.AddSingleton<SomeDependency>();
// register the custom health check
// after AddHealthChecks and after SomeDependency
services.AddSingleton<IHealthCheck, SomeHealthCheck>();
}
More Details
A comment in the Health Check samples states that:
All IHealthCheck services will be available to the health check service and middleware. We recommend registering all health checks as Singleton services.
Full Sample
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
public class SomeDependency
{
public string GetMessage() => "Hello from SomeDependency";
}
public class SomeHealthCheck : IHealthCheck
{
public string Name => nameof(SomeHealthCheck);
private readonly SomeDependency someDependency;
public SomeHealthCheck(SomeDependency someDependency)
{
this.someDependency = someDependency;
}
public Task<HealthCheckResult> CheckHealthAsync(
CancellationToken cancellationToken = default(CancellationToken))
{
var message = this.someDependency.GetMessage();
var result = new HealthCheckResult(HealthCheckStatus.Failed, null, null, null);
return Task.FromResult(result);
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddHealthChecks();
services.AddSingleton<SomeDependency>();
services.AddSingleton<IHealthCheck, SomeHealthCheck>();
}
public void Configure(IApplicationBuilder app)
{
app.UseHealthChecks("/healthz");
app.Run(async (context) => await context.Response.WriteAsync("Hello World!"));
}
}
This sample is also available on GitHub here.
In addition to Shaun's answer: there is an open pull-request which will allow to inject services with any lifetime (transient and scoped) into health checks. This will probably land in the 2.2 release.
When you can use transient and scoped services in health checks, you should register them using a transient lifestyle.
Dependency injection for health checks in asp.net core works exactly as it works for any other registered service that is added through ServiceProvider.
This means creating your health check as
public class Foo : IHealthCheck {
private ILogger<Foo> _log;
public Foo(ILogger<Foo> log) {
_log = log; // log is injected through the DI mechanisms
}
}
And registering (using the new 6 style here):
builder.AddHealthChecks().AddHealthCheck<Foo>();
So this also means that you can inject the IServiceProvider itself and utilise it internally should the need for getting further required services or werid use cases be there.
I am very curious why this is not explicitly stated in the documentation and there are no examples for this, as it is not "obvious". But it clearly follows the classical pattern of everything in the asp.net core land.
I was struggling with this in my ASP.NET Core 3.1 Web API as I followed the typical DI approach described above by calling:
services.AddHealthChecks();
services.AddSingleton<IHealthCheck, MyHealthCheck1>();
services.AddSingleton<IHealthCheck, MyHealthCheck2>();
Unfortunately, it seems in ASP.NET Core 3.1 that does not actually work as my IHealthCheck implementations were not being called.
Instead, I had to do the following in Startup.ConfigureServices():
services.AddHealthChecks()
.AddCheck<MyHealthCheck1>("My1-check",
HealthStatus.Unhealthy,
new string[] { "tag1" })
.AddCheck<MyHealthCheck2>("My2-check",
HealthStatus.Unhealthy,
new string[] { "tag2" });
Then in Startup.Configure(), I also called MapHealthChecks() as follows:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHealthChecks("/hc");
});

Automapper Profiles not being loaded in Startup?

I'm using:
AutoMapper 6.1.1
AutoMapper.Extensions.Microsoft.DependencyInjection
3.0.1
It seems my profiles are not being loaded, every time I call the mapper.map I get AutoMapper.AutoMapperMappingException: 'Missing type map configuration or unsupported mapping.'
Here my Startup.cs class ConfigureServices method
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
//register automapper
services.AddAutoMapper();
.
.
}
In another project called xxxMappings I have my mapping profiles.
Example class
public class StatusMappingProfile : Profile
{
public StatusMappingProfile()
{
CreateMap<Status, StatusDTO>()
.ForMember(t => t.Id, s => s.MapFrom(d => d.Id))
.ForMember(t => t.Title, s => s.MapFrom(d => d.Name))
.ForMember(t => t.Color, s => s.MapFrom(d => d.Color));
}
public override string ProfileName
{
get { return this.GetType().Name; }
}
}
And call the map this way in a service class
public StatusDTO GetById(int statusId)
{
var status = statusRepository.GetById(statusId);
return mapper.Map<Status, StatusDTO>(status); //map exception here
}
status has values after calling statusRepository.GetById
For my Profile classes, if instead of inherit from Profile I inherit from MapperConfigurationExpression I got a unit test like the one below saying the mapping is good.
[Fact]
public void TestStatusMapping()
{
var mappingProfile = new StatusMappingProfile();
var config = new MapperConfiguration(mappingProfile);
var mapper = new AutoMapper.Mapper(config);
(mapper as IMapper).ConfigurationProvider.AssertConfigurationIsValid();
}
My guess is that my mappings are not being initialized.
How can I check that? Am I missing something?
I saw an overload for AddAutoMapper() method
services.AddAutoMapper(params Assembly[] assemblies)
Should I pass all the assemblies in my xxxMappings project. How can I do that?
I figure it out. Since my mappings are in a different project, I did two things
From my API project (where Startup.cs is located, added a reference to my xxxMapprings project)
in ConfigureServices I used the overload AddAutoMapper that gets an Assembly:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
//register automapper
services.AddAutoMapper(Assembly.GetAssembly(typeof(StatusMappingProfile))); //If you have other mapping profiles defined, that profiles will be loaded too.
Another solution for fixing the issue when we have mapping profiles in different projects in a solution is:
services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
1. Create AutoMapperProfile inherit Profile Class
public class AutoMapperProfile : Profile
{
public AutoMapperProfile()
{
ConfigureMappings();
}
private void ConfigureMappings()
{
// DriverModel and Driver mapping classes
CreateMap<DriverModel, Driver>().ReverseMap();
}
}
2. Register this profile in Configuration service
public void ConfigureServices(IServiceCollection services)
{
services.AddAutoMapper(typeof(AutoMapperProfile));
}

Unable to find Use.RunTimePageInfo() method in startup.cs file in asp.net core

I'm following Scott Allen's Asp.Net core Pluralsight course in Ubuntu 16.04 .Net Core 1.0.0 framework. I'm unable to find the app.UseRuntimeInfoPage method in Configure method in StartUp.cs file, even though I have included Microsoft.AspNetCore.Diagnostics. Does the framework have limitations for the non-windows operating systems in terms of the features provided?
StartUp.cs code from Scott Allen's course
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using OdeToFood.Services;
namespace OdeToFood
{
public class Startup
{
public Startup()
{
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json");
Configuration = builder.Build();
}
public IConfiguration Configuration { get; set; }
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton(provider => Configuration);
services.AddSingleton<IGreeter, Greeter>();
}
// This method gets called by the runtime.
// Use this method to configure the HTTP request pipeline.
public void Configure(
IApplicationBuilder app,
IHostingEnvironment environment,
IGreeter greeter)
{
app.UseIISPlatformHandler();
if (environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRuntimeInfoPage("/info");
app.UseFileServer();
app.UseMvcWithDefaultRoute();
app.Run(async (context) =>
{
var greeting = greeter.GetGreeting();
await context.Response.WriteAsync(greeting);
});
}
// Entry point for the application.
public static void Main(string[] args) => WebApplication.Run<Startup>(args);
}
}
This feature was removed some time ago.
https://github.com/aspnet/Home/issues/1632
Also, it seems like it is scheduled to come back at an undetermined moment in time.
https://github.com/aspnet/Diagnostics/issues/280
So for now you can remove it from your startup.cs; or add the code and create your own version of it from this commit:
https://github.com/aspnet/Diagnostics/commit/af19899927516718bdc05507612dcc17901fb937
I do not provide a code sample because the code is in the commit mentioned above.
UPDATE:
It seems like issue #280 has been updated to state that the feature will not be brought back at all.

Categories