Register a Component with IServiceCollection - .net-core

I want to build my NotificationLibrary so it feels like a real .NET Core component.
This is what I want in my Startup class
public void ConfigureServices(IServiceCollection services)
{
services.AddNotificationLibrary(options =>
{
options.ConfigSectionName = "notificationSetup";
});
}
So the next steps should be to configure the NotificationLibrary with the configuration from appsetting.json and register it in DI.
This is what I have so fare
namespace Microsoft.Extensions.DependencyInjection
{
public static class NotificationLibraryFactoryServiceCollectionExtensions
{
public static IServiceCollection AddNotificationLibrary(
this IServiceCollection services,
Action<NotificationLibraryOptions> configure)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
if (configure != null)
{
services.Configure(configure);
}
services.AddScoped<IMailFactory, MailFactory>(provider =>
{
var mailFactory = new MailFactory();
var configuration = provider.GetRequiredService<IConfiguration>();
//var setup = configuration. /* How to I get the section name? */
var config = new NotificationSetup();
mailFactory.Configure(config);
return mailFactory;
});
return services;
}
}
}
I am stocked getting the config section name, and I don't understand what services.Configure(configure); are doing.
I want to create a configration poco object and add it as a singleton to the service collection. Next step would be to call mailFactory.Configure(config); and return the mailFactory object.
I am close, but what am I missing?

Resolve the options registered with the service collection
public static IServiceCollection AddNotificationLibrary(
this IServiceCollection services,
Action<NotificationLibraryOptions> configure) {
if (services == null) {
throw new ArgumentNullException(nameof(services));
}
if (configure != null) {
services.Configure(configure);
}
services.AddScoped<IMailFactory, MailFactory>(provider => {
/* How to I get the section name? */
IOptions<NotificationLibraryOptions> options = provider.GetRequiredService<IOptions<NotificationLibraryOptions>>();
string sectionName = options.Value.ConfigSectionName;
var configuration = provider.GetRequiredService<IConfiguration>();
NotificationSetup config = configuration.GetSection(sectionName).Get<NotificationSetup>();
MailFactory mailFactory = new MailFactory();
mailFactory.Configure(config);
return mailFactory;
});
return services;
}

Related

How can I read request header from program.cs file in .netcore6 webapi

I have tenant id in request header. I want to connect database string based on the tenant id , how can i achieve this? I am using .netcore6 webapi. Thanks in advance.
You can achieve this requirement through Middleware. Here is the test result and sample.
My Test files and picutres
Test Method in HomeController
public IActionResult GetConnectionStringByTenantID()
{
bool testResult = false;
string msg = string.Empty;
try
{
testResult = _dbcontext.Database.CanConnect();
if (testResult)
{
msg = "connect sucessfully";
}
else
{
msg = "connect failed";
}
}
catch (Exception e)
{
msg = e.ToString();
throw;
}
return Ok(msg);
}
TenantMiddleware.cs
using WebApplication7.Models;
namespace WebApplication7
{
public class TenantMiddleware
{
private readonly RequestDelegate _next;
public TenantMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
// Read the tenant ID from the request header
var tenantId = context.Request.Headers["Tenant-Id"].FirstOrDefault();
// Get the connection string for the tenant
var connectionString = GetConnectionStringForTenant(tenantId);
// Create a Tenant object with the tenant ID and connection string
var tenant = new Tenant
{
Id = tenantId,
ConnectionString = connectionString
};
// Set the Tenant object in the context
context.Items["Tenant"] = tenant;
// Call the next middleware component in the pipeline
await _next(context);
}
private string GetConnectionStringForTenant(string tenantId)
{
// Implement logic to get the connection string for the tenant
// This can be from a configuration file or a database
// For example, you can have a dictionary of tenant IDs and connection strings
var connectionStrings = new Dictionary<string, string>
{
{ "tenant1", "Data Source=...My real test connectionstring..." },
{ "tenant2", "Server=server2;Database=database2;User Id=user2;Password=password2;" }
};
if (tenantId == null || tenantId.Equals(string.Empty))
{
tenantId = "tenant1";
}
// Return the connection string for the tenant ID
if (connectionStrings.TryGetValue(tenantId, out var connectionString))
{
return connectionString;
}
// If the tenant ID is not found, throw an exception
throw new ArgumentException($"Invalid tenant ID: {tenantId}");
}
}
}
MyDbContext.cs
using Microsoft.EntityFrameworkCore;
using WebApplication7.Models;
namespace WebApplication7
{
public class MyDbContext : DbContext
{
private readonly Tenant _tenant = null!;
public MyDbContext()
{
}
public MyDbContext(DbContextOptions<MyDbContext> options, IHttpContextAccessor httpContextAccessor)
: base(options)
{
_tenant = httpContextAccessor.HttpContext.Items["Tenant"] as Tenant;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(_tenant.ConnectionString);
}
}
}
Program.cs
using Microsoft.EntityFrameworkCore;
namespace WebApplication7
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpContextAccessor();
builder.Services.AddDbContext<MyDbContext>();
// Add services to the container.
builder.Services.AddControllersWithViews();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
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.UseHttpsRedirection();
app.UseStaticFiles();
app.UseMiddleware<TenantMiddleware>();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
}
}
}
Tenant.cs
namespace WebApplication7.Models
{
public class Tenant
{
public string? Id { get; set; }
public string? ConnectionString { get; set; }
}
}

.Net Core Dependency Injection without passing Dependency in Constructor

I am implementing custom [Authorize] attribute. Inside the OnAuthorization method in IdentityAuthorizeFilter Class, I need to have access to DBContext to perform Database checks. I can not pass the context in the constructor of the Class. How can I access DBContext inside this class ?
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<SecureContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("SecureContext")));
}
CustomAuthorize:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class IdentityAuthorizeAttribute : TypeFilterAttribute
{
public IdentityAuthorizeAttribute(string permissions)
: base(typeof(IdentityAuthorizeFilter))
{
Arguments = new object[] { permissions };
}
}
public class IdentityAuthorizeFilter : IAuthorizationFilter
{
public IdentityAuthorizeFilter(string permissions) => Permissions = permissions;
public string Permissions { get; set; }
[Authorize]
public void OnAuthorization(AuthorizationFilterContext context)
{
var claims = context.HttpContext.User.Claims.ToList();
var auth = context.HttpContext.User.Identity.IsAuthenticated;
//Access DB Context
if (!isAuthorized)
context.Result = new UnauthorizedResult();
}
}
Does this work?
var dbContext = context.HttpContext.RequestServices.GetRequiredService<SecureContext>();
I'm not sure if it will, but I did something similar using Microsoft's AddMicrosoftIdentityWebApp method like this:
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(
options =>
{
configuration.Bind("AzureAD", options);
options.Events ??= new OpenIdConnectEvents();
options.Events.OnTokenValidated += async tokenValidatedContext =>
{
var dbContext = tokenValidatedContext.HttpContext.RequestServices.GetRequiredService<dbContext>();
// Do stuff with db context here
};
});

Configure MassTransit for testing with WebApplicationFactory<Startup>

I have an ASP.NET Core web app and test setup using WebApplicationFactory to test my controller actions. I used RawRabbit before and it was easy enough for me to mock the IBusClient and add it to the DI container as a singleton. Within the WebApplicationFactory<TStartup>.CreateWebHostBuilder() I call this extension method to add my mocked IBusClient instance like so;
/// <summary>
/// Configures the service bus.
/// </summary>
/// <param name="webHostBuilder">The web host builder.</param>
/// <returns>A web host builder.</returns>
public static IWebHostBuilder ConfigureTestServiceBus(this IWebHostBuilder webHostBuilder)
{
webHostBuilder.ConfigureTestServices(services =>
{
services.AddSingleton<IBusClient, MY_MOCK_INSTANCE>
});
return webHostBuilder;
}
But there are gaps in RawRabbit right now that made me decide to move over to MassTransit. However, I am wondering if there's already a better way to register the IBus into my container without mocking it inside my test. Not sure if InMemoryTestFixture, BusTestFixture, or BusTestHarness is the solution to my problem. Not sure how to use them together and what they do.
By the way, in my ASP.NET Core app, I have a reusable extension method setup like the code below to hook me up to RabbitMQ on startup.
/// <summary>
/// Adds the service bus.
/// </summary>
/// <param name="services">The services.</param>
/// <param name="configurator">The configurator.</param>
/// <returns>A service collection.</returns>
public static IServiceCollection AddServiceBus(this IServiceCollection services, Action<IServiceCollectionConfigurator> configurator)
{
var rabbitMqConfig = new ConfigurationBuilder()
.AddJsonFile("/app/configs/service-bus.json", optional: false, reloadOnChange: true)
.Build();
// Setup DI for MassTransit.
services.AddMassTransit(x =>
{
configurator(x);
// Get the json configuration and use it to setup connection to RabbitMQ.
var rabbitMQConfig = rabbitMqConfig.GetSection(ServiceBusOptionsKey).Get<RabbitMQOptions>();
// Add bus to the container.
x.AddBus(provider => Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.Host(
new Uri(rabbitMQConfig.Host),
hostConfig =>
{
hostConfig.Username(rabbitMQConfig.Username);
hostConfig.Password(rabbitMQConfig.Password);
hostConfig.Heartbeat(rabbitMQConfig.Heartbeat);
});
cfg.ConfigureEndpoints(provider);
// Add Serilog logging.
cfg.UseSerilog();
}));
});
// Add the hosted service that starts and stops the BusControl.
services.AddSingleton<IMessageDataRepository, EncryptedMessageDataRepository>();
services.AddSingleton<IEndpointNameFormatter, EndpointNameFormatter>();
services.AddSingleton<IBus>(provider => provider.GetRequiredService<IBusControl>());
services.AddSingleton<IHostedService, BusHostedService>();
return services;
}
A MassTransit config defined during Startup could be replaced with a new configuration with custom WebApplicationFactory by removing services from MassTransit namespace, e.g.
public class CustomWebApplicationFactory : WebApplicationFactory<Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
var massTransitHostedService = services.FirstOrDefault(d => d.ServiceType == typeof(IHostedService) &&
d.ImplementationFactory != null &&
d.ImplementationFactory.Method.ReturnType == typeof(MassTransitHostedService)
);
services.Remove(massTransitHostedService);
var descriptors = services.Where(d =>
d.ServiceType.Namespace.Contains("MassTransit",StringComparison.OrdinalIgnoreCase))
.ToList();
foreach (var d in descriptors)
{
services.Remove(d);
}
services.AddMassTransitInMemoryTestHarness(x =>
{
//add your consumers (again)
});
});
}
}
Then your test could look like
public class TestClass : IClassFixture<CustomApplicationFactory>
{
private readonly CustomApplicationFactoryfactory;
public TestClass(CustomApplicationFactoryfactory)
{
this.factory = factory;
}
[Fact]
public async Task TestName()
{
CancellationToken cancellationToken = new CancellationTokenSource(5000).Token;
var harness = factory.Services.GetRequiredService<InMemoryTestHarness>();
await harness.Start();
var bus = factory.Services.GetRequiredService<IBusControl>();
try
{
await bus.Publish<MessageClass>(...some message...);
bool consumed = await harness.Consumed.Any<MessageClass>(cancellationToken);
//do your asserts
}
finally
{
await harness.Stop();
}
}
}
I ended up creating a method from within my WebApplicationFactory like so;
public void ConfigureTestServiceBus(Action<IServiceCollectionConfigurator> configurator)
{
this._configurator = configurator;
}
Giving me the ability to register test handlers from within my derived integration class constructor;
public Intg_GetCustomers(WebApplicationTestFactory<Startup> factory)
: base(factory)
{
factory.ConfigureTestServiceBus(c =>
{
c.AddConsumer<TestGetProductConsumer>();
});
}
This configurator gets used when I call my extension method to add an InMemory instance of MassTransit
public static IWebHostBuilder ConfigureTestServiceBus(this IWebHostBuilder webHostBuilder, Action<IServiceCollectionConfigurator> configurator)
{
return webHostBuilder
.ConfigureTestServices(services =>
{
// UseInMemoryServiceBus DI for MassTransit.
services.AddMassTransit(c =>
{
configurator?.Invoke(c);
// Add bus to the container.
c.AddBus(provider =>
{
var control = Bus.Factory.CreateUsingInMemory(cfg =>
{
cfg.ConfigureEndpoints(provider);
});
control.Start();
return control;
});
});
services.AddSingleton<IMessageDataRepository, InMemoryMessageDataRepository>();
});
}
You best bet is to use the InMemoryTestHarness, so that you can ensure your message contracts can be serialized, your consumers are configured properly, and that everything works as expected. While some might call this an integration test, it's really just doing a proper test. And it's extremely quick, since it's all in-memory.
You can see a unit test here, but a short example is also shown below.
[TestFixture]
public class When_a_consumer_is_being_tested
{
InMemoryTestHarness _harness;
ConsumerTestHarness<Testsumer> _consumer;
[OneTimeSetUp]
public async Task A_consumer_is_being_tested()
{
_harness = new InMemoryTestHarness();
_consumer = _harness.Consumer<Testsumer>();
await _harness.Start();
await _harness.InputQueueSendEndpoint.Send(new A());
}
[OneTimeTearDown]
public async Task Teardown()
{
await _harness.Stop();
}
[Test]
public void Should_have_called_the_consumer_method()
{
_consumer.Consumed.Select<A>().Any().ShouldBe(true);
}
class Testsumer :
IConsumer<A>
{
public async Task Consume(ConsumeContext<A> context)
{
await context.RespondAsync(new B());
}
}
class A
{
}
class B
{
}
}
In my case (I'm injecting everywhere IPublishEndpoint interface only) I have just simply registered another IPublishEndpoint in ConfigureTestServices method like so:
[TestClass]
public class TastyTests
{
private readonly WebApplicationFactory<Startup> factory;
private readonly InMemoryTestHarness harness = new();
public TastyTests()
{
factory = new WebApplicationFactory<Startup>().WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(services =>
{
services.AddSingleton<IPublishEndpoint>(serviceProvider =>
{
return harness.Bus;
});
});
});
}
[TestMethod]
public async Task Test()
{
await harness.Start();
try
{
var client = factory.CreateClient();
const string url = "/endpoint-that-publish-message";
var content = new StringContent("", Encoding.UTF8, "application/json");
var response = await client.PostAsync(url, content);
(await harness.Published.Any<IMessage>()).Should().BeTrue();
}
finally
{
await harness.Stop();
}
}
}

How to store list object in session variable using asp.net core. And how to fetch values from session variable from the View?

Using asp.net core how to create a session variable to store the list kind of objects and how to retrieve the values from the view
was trying
HttpContext.Session.SetString("Test", listObject);
.
First, you need to add more config at Startup class.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDistributedMemoryCache();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddSession(options => {
options.IdleTimeout = TimeSpan.FromSeconds(10);
options.Cookie.IsEssential = true;
});
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseSession();
app.UseMvc(routes =>
{
// Default Route
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
//Add the following extension methods to set and get serializable objects:
public static class SessionExtensions
{
public static T GetComplexData<T>(this ISession session, string key)
{
var data = session.GetString(key);
if (data == null)
{
return default(T);
}
return JsonConvert.DeserializeObject<T>(data);
}
public static void SetComplexData(this ISession session, string key, object value)
{
session.SetString(key, JsonConvert.SerializeObject(value));
}
}
public IActionResult Index()
{
List<BookingModel> data = new List<BookingModel>();
for (int i = 1; i < 10; i++)
{
BookingModel obj = new BookingModel
{
BookingId = i,
BookingRefNo = $"00{i}",
FullName = $"A{i}",
MobileNo = $"(00)-{i}",
Email = $"abc{i}#gmail.com"
};
data.Add(obj);
}
HttpContext.Session.SetComplexData("loggerUser", data);
return View();
}
public IActionResult Privacy()
{
List<BookingModel> data = HttpContext.Session.GetComplexData<List<BookingModel>>("loggerUser");
return View();
}
And you can visit this link to refer more: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/app-state?view=aspnetcore-2.2#session-state
Hope to help, my friend :))

How to properly inject dependencies with the built in Funq container?

I have a cached repository
public interface IRepository
{
void LogWebUsage(string html);
IEnumerable<ApiKey> GetApiKeys();
ApiKey GetApiKey(Guid key);
}
public class Repository : IRepository
{
private static readonly ILog Log = LogManager.GetLogger("API.Repository");
public IDbConnectionFactory DbFactory { get; set; }
public void LogWebUsage(string request)
{
Log.Debug(request);
}
public virtual IEnumerable<ApiKey> GetApiKeys()
{
List<ApiKey> result = null;
using (var db = DbFactory.OpenDbConnection())
{
result = db.SelectParam<ApiKey>(q => q.Active);
}
return result;
}
public ApiKey GetApiKey(Guid key)
{
ApiKey result = null;
using (var db = DbFactory.OpenDbConnection())
{
result = (db.SelectParam<ApiKey>(q => q.Id == key)).FirstOrDefault();
}
return result;
}
}
public class CachedRepository : Repository
{
public ICacheClient Cache { get; set; }
public override IEnumerable<ApiKey> GetApiKeys()
{
const string cacheKey = "GetApiKeys";
var result = Cache.Get<IEnumerable<ApiKey>>(cacheKey);
if (result == null)
{
result = base.GetApiKeys();
if (result.Any())
{
Cache.Add(cacheKey, result, TimeSpan.FromMinutes(30));
}
}
return result;
}
}
And I configure it like so.
//Register any dependencies you want injected into your services
container.Register<IDbConnectionFactory>(new OrmLiteConnectionFactory(ConfigUtils.GetConnectionString("DBConnstr"), true, SqlServerOrmLiteDialectProvider.Instance));
container.Register<ICacheClient>(new MemoryCacheClient());
container.Register<IRepository>(new CachedRepository());
container.RegisterAutoWired<CachedRepository>();
So what I was hoping for is that both the IDbConnectionFactory and ICacheClient would be injected at run-time, but they are null. How to you properly account for this type of dependency graph?
Thank you,
Stephen
Updated
After googling for a couple of hours I finally found a solution that works. Constructor injection though the config.
public class CachedRepository : Repository
{
private ICacheClient Cache { get; set; }
public CachedRepository(IDbConnectionFactory dbFactory, ICacheClient cache) : base(dbFactory)
{
Cache = cache;
}
public override IEnumerable<ApiKey> GetApiKeys()
{
const string cacheKey = "GetApiKeys";
var result = Cache.Get<IEnumerable<ApiKey>>(cacheKey);
if (result == null)
{
result = base.GetApiKeys();
if (result.Any())
{
Cache.Add(cacheKey, result, TimeSpan.FromMinutes(30));
}
}
return result;
}
}
Configuration
//Register any dependencies you want injected into your services
container.Register<IDbConnectionFactory>(c => new OrmLiteConnectionFactory(ConfigUtils.GetConnectionString("DBConnstr"), true, SqlServerOrmLiteDialectProvider.Instance));
container.Register<ICacheClient>(c => new MemoryCacheClient());
container.Register<IRepository>(c => new CachedRepository(c.Resolve<IDbConnectionFactory>(), c.Resolve<ICacheClient>()));
It works, but I'd still like to know how to wire up the property injection.
Take care,
Stephen... again
The APIs for AutoWiring in ServiceStack's Funq IOC are here:
Using Generic API:
container.RegisterAutoWired<MyType>();
container.RegisterAutoWiredAs<MyType,IMyType>();
Using Run-time typed API:
container.RegisterAutoWiredType(typeof(MyType));
container.RegisterAutoWiredType(typeof(MyType),typeof(IMyType));
container.RegisterAutoWiredTypes(typeof(MyType),typeof(MyType2),typeof(MyType3));
So basically you can use any of the above APIs to auto-wire your dependencies, e.g:
container.Register<IDbConnectionFactory>(c => new
OrmLiteConnectionFactory(ConfigUtils.GetConnectionString("DBConnstr"), true,
SqlServerDialect.Provider));
container.Register<ICacheClient>(c => new MemoryCacheClient());
container.RegisterAutoWiredAs<CachedRepository,IRepository>(); //auto-wired

Resources