Using ASP.NET DI for SignalR with TinyIoC - c#

In a project, I'm using Nancy/TinyIoC for Dependency Injection. I had no problems thus far.
I added SignalR to my project and setup my hubs so that I'm injecting IHubContext into my hub.
I'm running into a problem that when TinyIoC tries to resolve one of its dependency trees, it runs into an ASP.NET type and cannot resolve such. How do I work around this? My first guess was to register the type within TinyIoC, but that seems tedious.
Here's what I have:
public class Startup
{
public void Configure(IApplicationBuilder builder)
{
// Register types from ASP.net
// Pass instances to UseNancy
var hubContext = builder.ApplicationServices.GetService<IHubContext<MessageSender>>();
builder
.UseCors(AllowAllOrigins)
.UseSignalR(HubRegistration.RouteRegistrations)
.UseOwin(x => x.UseNancy());
}
public virtual void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy(AllowAllOrigins,
builder =>
{
builder
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
});
});
services.AddSignalR();
}
}
public class MessageRepo : IRepository<Message>
{
private readonly IDatabase<Message> _database;
private readonly IValidator<Message> _messageValidator;
private readonly IMessageSender<Message> _hubContext;
public MessageRepo(IDatabase<Message> database, IValidator<Message> messageValidator, IMessageSender<Message> hubContext)
{
_database = database;
_messageValidator = messageValidator;
_hubContext = hubContext;
}
}
public class MessageSender : Hub, IMessageSender<Message>
{
public MessageSender(IHubContext<MessageSender> context)
{
_context = context;
}
}

Related

Injecting a dependency before startup

I have a type that I want to use in Startup of my .net core 3.1 app.
Here is the class I want to inject:
public class DbConnectionStringManager
{
public readonly string ConnectionStringForDefault;
public readonly string ConnectionStringForLogDb;
public DbConnectionStringManager(ConnectionStringProviderFactory factory)
{
var defaultConnectionStringProvider = factory.Create(ConnectionString.Default);
var logDbConnectionStringProvider = factory.Create(ConnectionString.LogDb);
ConnectionStringForDefault = defaultConnectionStringProvider.GetConnectionString();
ConnectionStringForLogDb = logDbConnectionStringProvider.GetConnectionString();
}
}
Another class I need to use
public class ConnectionStringProviderFactory
{
private readonly IConfiguration _configuration;
protected readonly AwsSecretProvider _awsSecretProvider;
public ConnectionStringProviderFactory(IConfiguration configuration, AwsSecretProvider awsSecretProvider)
{
_configuration = configuration;
_awsSecretProvider = awsSecretProvider;
}
public AbsConnectionStringProvider Create(ConnectionString connectionString)
=> connectionString switch
{
ConnectionString.Default => new DefaultConnectionStringProvider(_configuration, _awsSecretProvider),
ConnectionString.LogDb => new LogDbConnectionStringProvider(_configuration, _awsSecretProvider),
_ => throw new InvalidOperationException($"No ConnectionStringProvider created for requested source : {connectionString}"),
};
public enum ConnectionString
{
Default,
LogDb
}
}
And lastly
public class AwsSecretProvider
{
private GetSecretValueResponse _response = null;
public DatabaseSecret GetSecret(string secretName)
{
//Some code
}
}
I tried this at my Program.cs for injecting dependencies before startup
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder
.ConfigureLogging((hostingContext, config) => { config.ClearProviders(); })
.UseKestrel(options =>
{
options.AllowSynchronousIO = true;
options.Limits.MinRequestBodyDataRate = null;
})
.UseIISIntegration()
.ConfigureServices(serviceCollection =>
{
serviceCollection.AddSingleton<AwsSecretProvider>();
serviceCollection.AddSingleton<ConnectionStringProviderFactory>();
serviceCollection.AddSingleton<DbConnectionStringManager>();
})
.UseIIS()
.UseStartup<Startup>();
}));
}
When I run the app, I get the following error
System.InvalidOperationException: 'Unable to resolve service for type
'Hesapkurdu.Services.Encryption.Database.DbConnectionStringManager'
while attempting to activate 'Hesapkurdu.WebApi.Startup'.'
Based on the fact that it happens when trying to activate Startup, it looks like you are trying to inject DbConnectionStringManager directly into Startup constructor.
That wont work.
Only the following services can be injected into the Startup
constructor when using the Generic Host (IHostBuilder):
IWebHostEnvironment
IHostEnvironment
IConfiguration
Any service
registered with the DI container can be injected into the
Startup.Configure method:
For example
public void Configure(IApplicationBuilder app, DbConnectionStringManager connections) {
//...
}
Reference Dependency injection in ASP.NET Core - Services injected into Startup

Autofac Dependency Injection - Data Not Saving to Database

I am trying to implement Dependency Injection with Autofac. I'm trying to add a WebAPI interface to an MVC application. My goal is to create an application that communicates via API while creating an administration panel. I don't get any error message but data is not saved in database. I think it has something to do with the Register<EFUnitOfWork> or the Register<DbContext> field.
Business Layer -> AutofacBusinessModule
public class AutofacBusinessModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<ArticleManager>().As<IArticleService>();
builder.RegisterType<EfArticleDal>().As<IArticleDal>();
builder.RegisterType<CategoryManager>().As<ICategoryService>();
builder.RegisterType<EfCategoryDal>().As<ICategoryDal>();
builder.RegisterType<CommentManager>().As<ICommentService>();
builder.RegisterType<EfCommentDal>().As<ICommentDal>();
builder.RegisterType<RoleManager>().As<IRoleService>();
builder.RegisterType<EfRoleDal>().As<IRoleDal>();
builder.RegisterType<UserManager>().As<IUserService>();
builder.RegisterType<EfUserDal>().As<IUserDal>();
builder.RegisterType<ATKlogMSSqlContext>().InstancePerLifetimeScope();
builder.RegisterType<EfUnitOfWork>().As<IUnitOfWork>().AsSelf().SingleInstance();
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
builder.RegisterAssemblyTypes(assembly).AsImplementedInterfaces()
.EnableInterfaceInterceptors(new ProxyGenerationOptions()
{
Selector = new AspectInterceptorSelector()
}).SingleInstance();
}
}
Business Layer -> Article Manager
public class ArticleManager : IArticleService
{
private readonly IUnitOfWork _unitOfWork;
private readonly IMapper _mapper;
public ArticleManager(IUnitOfWork unitOfWork, IMapper mapper)
{
_unitOfWork = unitOfWork;
_mapper = mapper;
}
[ValidationAspect(typeof(ArticleAddDtoValidator))]
public async Task<IResult> Add(ArticleAddDto articleAddDto, string createdByName)
{
var mapping = _mapper.Map<Article>(articleAddDto);
mapping.CreatedByName = createdByName;
mapping.CreatedDate = DateTime.Now;
mapping.ModifiedByName = createdByName;
mapping.ModifiedDate = DateTime.Now;
mapping.Slug = SlugifyHelper.ConvertSlug(articleAddDto.Title);
mapping.UserId = 1;
await _unitOfWork.Articles.AddAsync(mapping);
await _unitOfWork.SaveAsync();
return new SuccessResult(String.Format(Messages.Article.ArticleAdded, articleAddDto.Title));
}
}
DataAccess Layer -> EFUnitOfWork
public class EfUnitOfWork : IUnitOfWork
{
private readonly ATKlogMSSqlContext _context;
private EfArticleDal _efArticleDal;
private EfCategoryDal _efCategoryDal;
private EfCommentDal _efCommentDal;
private EfRoleDal _efRoleDal;
private EfUserDal _efUserDal;
public EfUnitOfWork(ATKlogMSSqlContext context)
{
_context = context;
}
public async ValueTask DisposeAsync()
{
await _context.DisposeAsync();
}
public IArticleDal Articles => _efArticleDal ?? new EfArticleDal();
public ICategoryDal Categories => _efCategoryDal ?? new EfCategoryDal();
public ICommentDal Comments => _efCommentDal ?? new EfCommentDal();
public IRoleDal Roles => _efRoleDal ?? new EfRoleDal();
public IUserDal Users => _efUserDal ?? new EfUserDal();
public async Task<int> SaveAsync()
{
return await _context.SaveChangesAsync();
}
}
MVC -> Startup
public class Startup
{
// 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 https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews().AddRazorRuntimeCompilation().AddNewtonsoftJson(opt => opt.SerializerSettings.ReferenceLoopHandling =
Newtonsoft.Json.ReferenceLoopHandling.Ignore);
services.AddAutoMapper(typeof(ArticleProfile), typeof(CategoryProfile), typeof(CommentProfile), typeof(UserProfile));
}
// 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.UseStatusCodePages();
}
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapAreaControllerRoute(
name: "Admin",
areaName: "Admin",
pattern: "Admin/{controller=Home}/{action=Index}/{id?}"
);
endpoints.MapDefaultControllerRoute();
endpoints.MapControllers();
});
}
}
MVC Layer -> Program
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureContainer<ContainerBuilder>(builder =>
{
builder.RegisterModule(new AutofacBusinessModule());
})
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
}

Xunit Testing EFcore Repositories InMemory DB

I am trying to unit test the repositories, I am using InMemory option in EFCore . This is the method
[Fact]
public async Task GetCartsAsync_Returns_CartDetail()
{
ICartRepository sut = GetInMemoryCartRepository();
CartDetail cartdetail = new CartDetail()
{
CommercialServiceName = "AAA"
};
bool saved = await sut.SaveCartDetail(cartdetail);
//Assert
Assert.True(saved);
//Assert.Equal("AAA", CartDetail[0].CommercialServiceName);
//Assert.Equal("BBB", CartDetail[1].CommercialServiceName);
//Assert.Equal("ZZZ", CartDetail[2].CommercialServiceName);
}
private ICartRepository GetInMemoryCartRepository()
{
DbContextOptions<SostContext> options;
var builder = new DbContextOptionsBuilder<SostContext>();
builder.UseInMemoryDatabase($"database{Guid.NewGuid()}");
options = builder.Options;
SostContext personDataContext = new SostContext(options);
personDataContext.Database.EnsureDeleted();
personDataContext.Database.EnsureCreated();
return new CartRepository(personDataContext);
}
I am getting error which say
System.TypeLoadException : Method 'ApplyServices' in type
'Microsoft.EntityFrameworkCore.Infrastructure.Internal.InMemoryOptionsExtension' from assembly
'Microsoft.EntityFrameworkCore.InMemory, Version=1.0.1.0, Culture=neutral,
PublicKeyToken=adb9793829ddae60' does not have an implementation.
Microsoft.
EntityFrameworkCore.InMemoryDbContextOptionsExtensions.UseInMemoryDatabase(DbContextOptionsBuilder
optionsBuilder, String databaseName, Action`1 inMemoryOptionsAction)
Microsoft.EntityFrameworkCore.InMemoryDbContextOptionsExtensions.UseInMemoryDatabase[TContext]
(DbContextOptionsBuilder`1 optionsBuilder, String databaseName, Action`1 inMemoryOptionsAction)
My reference is from https://www.carlrippon.com/testing-ef-core-repositories-with-xunit-and-an-in-memory-db/
Please suggest me where i am going wrong with the current implementation . Thanks in Advance
I suggest reading the official Microsoft documentation about integration testing.
https://learn.microsoft.com/fr-fr/aspnet/core/test/integration-tests?view=aspnetcore-3.0
Secondly, I you start adding this kind of boilerplate to create your tests with the memory database you will stop doing it very soon.
For integration tests, you should be near to your development configuration.
Here my configuration files and a usage in my CustomerController :
Integration Startup File
Have all think about database creation and dependency injection
public class IntegrationStartup : Startup
{
public IntegrationStartup(IConfiguration configuration) : base(configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public override void ConfigureServices(IServiceCollection services)
{
services.AddEntityFrameworkInMemoryDatabase().BuildServiceProvider();
services.AddDbContext<StreetJobContext>(options =>
{
options.UseInMemoryDatabase("InMemoryAppDb");
});
//services.InjectServices();
//here you can set your ICartRepository DI configuration
services.AddMvc(option => option.EnableEndpointRouting = false)
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
.AddApplicationPart(Assembly.Load(new AssemblyName("StreetJob.WebApp")));
ConfigureAuthentication(services);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public override void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
var serviceScopeFactory = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>();
using (var serviceScope = serviceScopeFactory.CreateScope())
{
//Here you can add some data configuration
}
app.UseMvc();
}
The fake startup
it's quite similar to the one in the Microsoft documentation
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
protected override IWebHostBuilder CreateWebHostBuilder()
{
return WebHost.CreateDefaultBuilder(null)
.UseStartup<TStartup>();
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseSolutionRelativeContentRoot(Directory.GetCurrentDirectory());
builder.ConfigureAppConfiguration(config =>
{
config.AddConfiguration(new ConfigurationBuilder()
//custom setting file in the test project
.AddJsonFile($"integrationsettings.json")
.Build());
});
builder.ConfigureServices(services =>
{
});
}
}
The controller
public class CustomerControllerTest : IClassFixture<CustomWebApplicationFactory<IntegrationStartup>>
{
private readonly HttpClient _client;
private readonly CustomWebApplicationFactory<IntegrationStartup> _factory;
private readonly CustomerControllerInitialization _customerControllerInitialization;
public CustomerControllerTest(CustomWebApplicationFactory<IntegrationStartup> factory)
{
_factory = factory;
_client = _factory.CreateClient();
}
}
With this kind of setting, testing the integration tests are very similar to the development controller.
It's a quite good configuration for TDD Developers.

Singleton service and EF Core dbContext

The application uses ASP.NET Core 3. At the first call, a project class service is created.
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
string connection = Configuration.GetConnectionString("ConnectionDB");
services.AddDbContext<DataBaseContext>(options => options.UseSqlServer(connection), ServiceLifetime.Transient, ServiceLifetime.Singleton);
services.AddSingleton<Project>();
}
Project.cs
public class Project
{
private readonly DataBaseContext _dbContext;
public Project(DataBaseContext dbContext)
{
_dbContext = dbContext;
Init();
}
public async void Init()
{
await SomeMethod('text');
}
public async Task SomeMethod(string message)
{
_dbContext.Items.Add(message);
await _dbContext.SaveChangesAsync();
}
}
This is not entirely correct and I want to create a service when the application starts.
public void ConfigureServices(IServiceCollection services)
{
// AddDbContext
Project project = new Project(dbContext); // How to get dbcontext?
services.AddSingleton(typeof(Project), project);
}
How to pass dbcontext in this case?
UPDATE
Now in the Stratup class, I call the init () method of the project service.
Startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider)
{
Project project = serviceProvider.GetService<Project>();
project.Init();
// some code
}
Dunno why would you not use the automatic Dependecy Injection at your first code
Singleton are created upon app start. And as long as the init method is called inside the constructor it will run. So this code will work on your case already
public void ConfigureServices(IServiceCollection services)
{
string connection = Configuration.GetConnectionString("ConnectionDB");
services.AddDbContext<DataBaseContext>(options => options.UseSqlServer(connection), ServiceLifetime.Transient, ServiceLifetime.Singleton);
services.AddSingleton<Project>();
}
But anyway if you insist on instantiating the Project class then you can use this. Get the DBContext using ServiceProvider.
public void ConfigureServices(IServiceCollection services)
{
// AddDbContext
var sp = services.BuildServiceProvider();
var dbContext = sp.GetRequiredService<DbContext>();
Project project = new Project(dbContext);
services.AddSingleton(typeof(Project), project);
}

ASP.NET Core - Dependency Injection - IdentityServer4 DbContext

I've been trying to find what the best/preferred way is to have my RoleService obtain a ConfigurationDbContext (IdentityServer4).
I would really like to decouple so that my RoleService can be testable.
The only way I found get access to the ConfigurationDbContext is via creation of a public static IServiceProvider in Startup.cs:
public class Startup
{
private readonly IHostingEnvironment _environment;
// THIS IS THE PROPERTY I USED
public static IServiceProvider ServiceProvider { get; private set; }
public ConfigurationDbContext GetConfigurationDbContext()
{
return null;
}
public Startup(ILoggerFactory loggerFactory, IHostingEnvironment environment)
{
loggerFactory.AddConsole(LogLevel.Debug);
_environment = environment;
}
public void ConfigureServices(IServiceCollection services)
{
var connectionString = DbSettings.IdentityServerConnectionString;
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
// configure identity server with in-memory stores, keys, clients and scopes
var identityServerConfig = services.AddIdentityServer()
.AddConfigurationStore(builder =>
builder.UseSqlServer(connectionString, options =>
options.MigrationsAssembly(migrationsAssembly)))
.AddOperationalStore(builder =>
builder.UseSqlServer(connectionString, options =>
options.MigrationsAssembly(migrationsAssembly)))
.AddSigningCredential(new X509Certificate2(Path.Combine(_environment.ContentRootPath, "certs", "IdentityServer4Auth.pfx"), "test"));
identityServerConfig.Services.AddTransient<IResourceOwnerPasswordValidator, ActiveDirectoryPasswordValidator>();
identityServerConfig.Services.AddTransient<IProfileService, CustomProfileService>();
services.AddDbContext<ConfigurationDbContext>(options => options.UseSqlServer(connectionString));
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
ServiceProvider = app.ApplicationServices;
// other code emitted
}
}
And then utilizing this code in RoleService.cs:
public class RoleService : IRoleService
{
public async Task<ApiResource[]> GetApiResourcesByIds(int[] ids)
{
ApiResource[] result;
using (var serviceScope = Startup.ServiceProvider.GetService<IServiceScopeFactory>().CreateScope())
{
var context = serviceScope.ServiceProvider.GetRequiredService<ConfigurationDbContext>();
result =
context.ApiResources.Where(x => ids.Contains(x.Id))
.Include(x => x.Scopes)
.Include(x => x.UserClaims)
.ToArray();
return result;
}
}
}
Is this the best way to get a dependency in RoleService.cs?
Is there a way to abstract the serviceScope (since it is in a using statement, and probably IDisposable, I don't really know if there is a way to abstract obtaining a context?
Any other recommendations or best practices?
You can pass a dependency to the RoleService by creating a constructor with the desired dependency as a constructor parameter.
Depending on the lifetime of your RoleService (can't tell based on the provided code) you would pass either the ConfigurationDbContext directly to the RoleService if the service is already Scoped. Or you would pass the IServiceScopeFactory to create a scope manually in your service.
Something like:
private readonly IServiceScopeFactory _scopeFactory;
public RoleService(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public async Task<ApiResource[]> GetApiResourcesByIds(int[] ids)
{
using (var scope = _scopeFactory.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<ConfigurationDbContext>();
}
}
Since your method is thight coupled to the DbContext (which isn't necessary a bad thing) you would probably end up creating an in-memory ConfigurationDbContext.
The IdentityServer4.EntityFramework repository that you use has some examples for testing that here.

Categories

Resources