Dependency Injection - Multiple projects - c#

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

Related

System Aggregate Exception using .Net

I am working on an mvc .net application. When I run dotnet build the solution is built normally with no errors. However, when I run the solution to display the api using swagger, it throws a system aggregated exception and the run fails. The exception is being thrown at a certain part in my Program.cs file.
The Program.cs file looks something like this:
using Example.Api.Data;
using Example.Services;
using Example.Services.Interfaces;
using Example.Core;
using Example.Core.Interfaces;
using Example.Core.Repositories;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.Swagger;
using Swashbuckle.AspNetCore.SwaggerUI;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddAutoMapper(typeof(StartupBase));
builder.Services.AddControllersWithViews();
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
builder.Services.AddTransient<IApplicantService, ApplicantService>();
builder.Services.AddTransient<IApplicantSurveyChoicesService, ApplicantSurveyChoicesService>();
builder.Services.AddTransient<IApplicantSurveyService, ApplicantSurveyService>();
builder.Services.AddTransient<IChoiceService,ChoiceService>();
//I basically did add transient for everyone of my services
builder.Services.AddSwaggerGen();
var app = builder.Build(); //this is where the exception is being thrown
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
/*app.UseSwagger();
app.UseSwaggerUI();*/
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "v1");
options.RoutePrefix = string.Empty;
});
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapRazorPages();
app.Run();
It throws the following exception: 'System.AggregateException' in Microsoft.Extensions.DependencyInjection.dll
Message=Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Example.Services.Interfaces.IApplicantService Lifetime: Transient ImplementationType: Example.Services.ApplicantService': Unable to resolve service for type 'Example.Core.Interfaces.IUnitOfWork' while attempting to activate 'Example.Services.ApplicantService'.)
and the error appears for every single time I call AddTransient
I am fairly new to the dotnet framework and I am still a beginner and would appreciate your help!
The IUnitOfWork file consists of the following:
public interface IUnitOfWork : IDisposable
{
IApplicantRepository Applicants { get; }
IApplicantSurveyChoicesRepository ApplicantSurveyChoices { get; }
IApplicantSurveyRepository ApplicantSurveys { get; }
IChoiceRepository Choices{ get; }
Task<int> CommitAsync();
}
The UnitOfWork class is just an implementation of the interface as such:
public UnitOfWork(DbContext context)
{
this._context = context;
}
public UnitOfWork(DbContext context, DbContext context1, IHostingEnvironment _environment, IHttpContextAccessor httpContextAccessor)
{
this._context = context;
this._environment = _environment;
_httpContextAccessor = httpContextAccessor;
}
public IApplicantRepository Applicants => _applicantRepository = _applicantRepository ?? new ApplicantRepository(_context, Config, _httpContextAccessor);
public IApplicantSurveyChoicesRepository ApplicantSurveyChoices => _applicantsurveychoicesrepository = _applicantsurveychoicesrepository ?? new ApplicantSurveyChoicesRepository(_context, Config, _httpContextAccessor);
public IApplicantSurveyRepository ApplicantSurveys => _applicantsurveysrepository = _applicantsurveysrepository ?? new ApplicantSurveyRepository(_context, Config, _httpContextAccessor);
public IChoiceRepository Choices => _choicerepository = _choicerepository ?? new ChoiceRepository(_context, Config, _httpContextAccessor);
public async Task<int> CommitAsync()
{
return await _context.SaveChangesAsync();
}
public void Dispose()
{
_context.Dispose();
}
I believe swagger is running through your controller endpoints and will activate some services while some services remain just registered but not resolved. So in my theory the error will also happen when you use the logic of the ApplicantService.
The error says it can't resolve the implementation of IUnitOfWork while trying to activate ApplicantService. So I would guess, you have missed a registration from the class UnitOfWork, or the interface referenced in the class UnitOfWork. But that would be the place where I would look.
Singelton, Scope and Transient should not have any relevance at this stage.
I figured out the issue. One of my controllers was not properly routed which led to the exception being thrown!
Thank you everyone for your help!
EDIT!
I also added an empty constructor to each of my services so that the DI can inject into them!
Hope this was of any help to anyone!

Automapper mapping validation during application startup in .net core?

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();
}

Problem registering object and interface on the startup file more specifically in the ConfigureServices method, the app does not execute

Well, I have created an application to start on ASP net core 3.1 from scratch, I have created an API application and I have already created some layers to have better control on my application, However, when I created my object with its interface and registered them in the startup file on this way:
services.AddScoped<IMySpaceService, MySpaceService>();
I have gotten this error when I run the application:
Unhandled exception. System.AggregateException: Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: MySpaceService.Services.Interfaces.IMySpaceService Lifetime: Scoped ImplementationType:
this is my code:
public class MySpaceService: IMySpaceService
{
private IMySpaceRepository _mySpaceRepository;
public MySpaceService(IMySpaceRepository mySpaceRepository)
{
_mySpaceRepository = mySpaceRepository;
}
public IList<MySpaceDto> getSpaces()
{
List<MySpaceDto> spaces = new List<MySpaceDto>();
var data = _mySpaceRepository.getSpaces();
foreach (var item in data)
{
SpaceDto spaceDto = new SpaceDto();
spaceDto.Identification = item.Identification;
spaceDto.Name = item.Name;
spaces.Add(spaceDto);
}
return spaces;
}
}
My startup code:
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.AddControllers();
services.AddScoped<IMySpaceService, MySpaceService>();
services.AddScoped<IMySpaceRepository, MySpaceRepository>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
Any Ideas about it?.
Your MySpaceService has only one constructor with parameter IMySpaceRepository. You need to register your repository as well:
services.AddScoped<IMySpaceRepository, MySpaceRepository>();
services.AddScoped<IMySpaceService, MySpaceService>();
Well, definitely the problem was that I had not registered yet a dependency, however, the dependency that I hadn't registered was "Dbcontext" and I am calling it from my repository class on the constructor. Therefore, I have to say that your comments helped me to solve my problem because finally, it was a problem with the dependency that didn't register.
I had to do this on my startup file:
services.AddDbContext<ExampleContext>(
options => options.UseMySql("Server=localhost;port=3306;Database=exampleDB;User=UserRegistered;Password=*******", mySqlOptions => mySqlOptions
.ServerVersion(new ServerVersion(new Version(8, 0, 18), ServerType.MySql))));

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");
});

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