Passing arguments to Middleware class - c#

I have a piece of middleware I'm trying to build that will check if a user has a particular key and if they can receive a authentication token and if so impersonate a windows user. That particular portion we were able to get to run. However I'm having trouble passing IConfiguration to said middleware. Whenever I run the application I get the following error:
A suitable constructor for type LocalDevelopmentImpersonation.LocalDevelopmentImpersonation' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor.
namespace LocalDevelopmentImpersonation
{
public sealed class LocalDevelopmentImpersonation : IMiddleware
{
private readonly RequestDelegate _Next;
private readonly SafeAccessTokenHandle _SafeAccessTokenHandle;
private readonly bool _IsLocal;
public LocalDevelopmentImpersonation(RequestDelegate next, IConfiguration configuration)
{
this._SafeAccessTokenHandle = LocalDevelopmentImpersonationSetup.GetSafeAccessTokenHandle(out var receivedSafeAccessTokenSuccessfully);
this._Next = next;
this._IsLocal = LocalDevelopmentImpersonationSetup.IsLocal(receivedSafeAccessTokenSuccessfully, configuration);
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
if (this._IsLocal)
{
await WindowsIdentity.RunImpersonated(this._SafeAccessTokenHandle, async () =>
{
await this._Next.Invoke(context);
});
}
await this._Next.Invoke(context);
}
}
Methods to use in Program.cs on the instance we created of WebApplication.
namespace LocalDevelopmentImpersonation.ExtensionMethods
{
public static class LocalDevelopmentImpersonationExtensions
{
public static IApplicationBuilder UseLocalDevelopmentImpersonation(this IApplicationBuilder app)
{
return app.UseMiddleware<LocalDevelopmentImpersonation>();
}
public static IApplicationBuilder UseLocalDevelopmentImpersonation(this IApplicationBuilder app, IConfiguration configuration)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
return app.UseMiddleware<LocalDevelopmentImpersonation>(Options.Create(configuration));
}
}
}
Calling instance
var app = builder.Build();
app.UseLocalDevelopmentImpersonation(configuration); //Instance of ConfigurationManager that implements IConfiguration

It isn't possible to pass objects to the factory-activated middleware with UseMiddleware,If you do want to pass argumentss to your middleware,try with Middleware activated by convention.
You could check the doc for more details

Related

Unable to resolve service for type while attempting to activate function

I have an azure function with below structure
Startup.cs
[assembly: FunctionsStartup(typeof(Edos.DatabaseInit.Startup))]
namespace Edos.DatabaseInit;
public class Startup
{
private readonly IConfiguration configuration;
public Startup(IConfiguration configuration)
{
this.configuration = configuration;
}
public void ConfigureServices(IServiceCollection collection)
{
collection.AddInfrastructure(this.configuration);
}
}
Function1.cs
public class CosmosInit
{
private readonly IMessagingService _messagingService;
public CosmosInit(IMessagingService messagingService)
{
_messagingService = messagingService;
}
[FunctionName("CosmosInit")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
await _messagingService.PushToTopic("topic", "message");
log.LogInformation("C# HTTP trigger function processed a request.");
return new OkObjectResult("");
}
}
And in infrastructure project
DependencyInjection.cs
public static class DependencyInjection
{
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
{
var serviceBusConnectionString = Environment.GetEnvironmentVariable("ServiceBusConnectionString");
if (string.IsNullOrEmpty(serviceBusConnectionString))
{
throw new InvalidOperationException(
"Please specify a valid ServiceBusConnectionString in the Azure Functions Settings or your local.settings.json file.");
}
//using AMQP as transport
services.AddSingleton((s) => {
return new ServiceBusClient(serviceBusConnectionString, new ServiceBusClientOptions() { TransportType = ServiceBusTransportType.AmqpWebSockets });
});
services.AddScoped<IMessagingService, MessagingService>();
return services;
}
}
MessagingService.cs
public class MessagingService: IMessagingService
{
private readonly ServiceBusClient _serviceBusClient;
public MessagingService(ServiceBusClient serviceBusClient)
{
_serviceBusClient = serviceBusClient;
}
// the sender used to publish messages to the topic
public async Task<int> PushToTopic(string topic, string serviceMessage)
{
var sender = _serviceBusClient.CreateSender(topic);
var message = new ServiceBusMessage(serviceMessage);
await sender.SendMessageAsync(message);
return 1;
}
}
IMessagingService.cs
public interface IMessagingService
{
Task<int> PushToTopic(string topic, string message);
}
As you can see I have the service bus client is injected into the MessageService.
And in the Function startup as well I have added the infrastructure collection.
Thus I am getting the PushToTopic method within the function as well.
But whenever I am trying to call the function it triggers the below error
Microsoft.Extensions.DependencyInjection.Abstractions: Unable to resolve service for type 'DataFoundation.Infrastructure.Services.IMessagingService' while attempting to activate 'DatabaseInit.CosmosInit'.
Please share your comments.. It took my whole day..
Your Startup class should extend FunctionsStartup class. And you should be overriding its Configure method instead.
Like so:
[assembly: FunctionsStartup(typeof(Edos.DatabaseInit.Startup))]
namespace Edos.DatabaseInit;
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
var configuration = builder.GetContext().Configuration;
builder.Services.AddInfrastructure(configuration);
}
}

Dependency Injection in Worker Service to classes other than Controllers by IOptions

I know this is a repeated question , went through answers and dont know whats happening here. In this problem we need to transfer the values from appsettings.json to another class other than Controllers here its ServiceSettings.cs.
This is a sample 'hello world' like program, here we need transfer values from appsettings.json to plugins.
This is folder architecture
appsettings.json
"Application": {
"TimerInterval": 10000,
"LogLevel": "Debug"
}
I created a class based upon this app setting in class library-
ApplicationSettings.cs
public class ApplicationSettings
{
public int TimerInterval { get; set; }
public string LogLevel { get; set; }
}
I tried push data from appsettings via the last line code
services.Configure<ApplicationSettings>(hostContext.Configuration.GetSection("Application"));
Program.cs
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseWindowsService()
.ConfigureServices((hostContext, services) =>
{
services.AddSingleton<IConfiguration>(hostContext.Configuration);
// Service Settings Injected here
services.AddOptions<ServiceSettings>();
services.AddHostedService<Worker>();
services.Configure<ApplicationSettings>(hostContext.Configuration.GetSection("Application"));
// for configure application
});
}
Here during start method of the worker class i need to get values from ServiceSettings() which always returns null value.
Worker.cs(Re Edited)
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
private readonly IConfiguration _configuration;
private ServiceSettings _settings;
public Worker(ILogger<Worker> logger, IConfiguration config)
{
_logger = logger;
_configuration = config;
}
public override Task StartAsync(CancellationToken cancellationToken)
{
Console.WriteLine("Start Asynch Method");
// Load Settings From Configuration Files
_settings = new ServiceSettings();
_settings.Load();
_logger.LogInformation("Settings: {setting}", _settings.TimerInterval);
return base.StartAsync(cancellationToken);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var values = _configuration.GetSection("DataSources").Get<List<DataSource>>();
while (!stoppingToken.IsCancellationRequested) {
await Task.Delay(Convert.ToInt32(_configuration["Application:TimerInterval"]), stoppingToken);
}
}
}
The service settings values are provided below which receives the null value
ServiceSettings.cs
public class ServiceSettings
{
private readonly IOptions<ApplicationSettings> _appSettings;
public ServiceSettings(IOptions<ApplicationSettings> appSettings)
{
_appSettings = appSettings;
}
public int TimerInterval { get; set; }
public string LogLevel { get; set; }
public void Load()
{
// Error is shown here
try { TimerInterval = Convert.ToInt32(_appSettings.Value.TimerInterval); }
catch { TimerInterval = 60; }
try
// Here too
{ LogLevel = Convert.ToString(_appSettings.Value.LogLevel).ToLower(); }
catch { LogLevel = "info"; }
}
}
I am pretty new to worker service, What i miss here? kindly guide me with the resources Thank you all.
This appears to be a design issue.
First lets fix the composition root. Avoid injecting IConfiguration. It can be seen as a code smell as IConfiguration should ideally be used in startup.
public class Program {
public static void Main(string[] args) {
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseWindowsService()
.ConfigureServices((hostContext, services) => {
IConfiguration config = hostContext.Configuration;
// parse settings
ApplicationSettings appSettings = config
.GetSection("Application").Get<ApplicationSettings>();
//set defaults.
if(appSettings.TimerInterval == 0)
appSettings.TimerInterval = 60;
if(string.IsNullOrWhiteSpace(appSettings.LogLevel))
appSettings.LogLevel = "Debug";
services.AddSingleton(appSettings); //<-- register settings run-time data
services.AddHostedService<Worker>();
});
}
Note how the settings are extracted from configuration and added to the service collection.
Since there is already a strongly defined type (ApplicationSettings) There really is no need for the ServiceSettings based on what was shown in the original question.
Update the worker to explicitly depend on the actual object required.
public class Worker : BackgroundService {
private readonly ILogger<Worker> _logger;
private readonly ApplicationSettings settings;
public Worker(ILogger<Worker> logger, ApplicationSettings settings) {
_logger = logger;
this.settings = settings; //<-- settings injected.
}
public override Task StartAsync(CancellationToken cancellationToken) {
Console.WriteLine("Start Asynch Method");
_logger.LogInformation("Settings: {setting}", settings.TimerInterval);
return base.StartAsync(cancellationToken);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
while (!stoppingToken.IsCancellationRequested) {
await Task.Delay(settings.TimerInterval), stoppingToken);
}
}
}
You always have to get instances from your service collection. You typically do this by injecting them in class constructor.
// WRONG
// Worker.cs
_settings = new ServiceSettings();
This code does not compile because your ServiceSettings class has a constructor that requires one parameter but no parameter is given.
how should your class know anything about the options stored in service collection without any reference?
Then it seems to make no sense to have two classes with the same data ServiceSettings and ApplicationSettings are the same. If you need the application settings in a service inject IOptions<ApplicationSettings> that's all. If you need separate settings classes, provide them as IOption<MyOtherSectionSettings>.
In the end, it could look like so:
public class Worker {
private readonly ApplicationSettings _settings;
private readonly ILogger<Worker> _logger;
public Worker(IOptions<ApplicationSettings> settingsAccessor, ILogger<Worker> logger) {
_settings = settingsAccessor.Value;
_logger = logger;
}
public override Task StartAsync(CancellationToken cancellationToken) {
Console.WriteLine("Start Asynch Method");
_logger.LogInformation("Settings: {setting}", _settings.TimerInterval);
return base.StartAsync(cancellationToken);
}
}
Note that reading settingsAccessor.Value is the place where the framework really tries to access the configuration and so here we should think about error conditions (if not validated before).

IMemoryCache does not save data at application startup

I created a fresh ASP.NET Core Web API project. Here is ConfigureServices in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddMemoryCache();
var serviceProvider = services.BuildServiceProvider();
var cache = serviceProvider.GetService<IMemoryCache>();
cache.Set("key1", "value1");
//_cahce.Count is 1
}
As you see I add an item to IMemoryCache. Here is my controller:
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
private readonly IMemoryCache _cache;
public ValuesController(IMemoryCache cache)
{
_cache = cache;
}
[HttpGet("{key}")]
public ActionResult<string> Get(string key)
{
//_cahce.Count is 0
if(!_cache.TryGetValue(key, out var value))
{
return NotFound($"The value with the {key} is not found");
}
return value + "";
}
}
When I request https://localhost:5001/api/values/key1, the cache is empty and I receive a not found response.
In short, the cache instance you're setting the value in is not the same as the one that is later being retrieved. You cannot do stuff like while the web host is being built (i.e. in ConfigureServices/Configure. If you need to do something on startup, you need to do it after the web host is built, in Program.cs:
public class Program
{
public static void Main(string[] args)
{
var host = CreateWebHostBuilder(args).Build();
var cache = host.Services.GetRequiredService<IMemoryCache>();
cache.Set("key1", "value1");
host.Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
}
As #selloape saids, if you manullay call BuildServicesProvider, you are creating a new provider, that will be not used in your controllers.
You can use a hosted service to intialize your cache
public class InitializeCacheService : IHostedService
{
private readonly IServiceProvider _serviceProvider;
public InitializeCacheService (IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public Task StartAsync(CancellationToken cancellationToken)
{
using (var scope = _serviceProvider.CreateScope())
{
var cache = _serviceProvider.GetService<IMemoryCache>();
cache.Set("key1", "value1");
}
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
Add it in your ConfigureServices
services.AddHostedService<InitializeCacheService>();
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.2

How to pass dependencies into a controller from a custom middleware?

I have a custom middleware from which I want to add a scoped dependency.
public class MyMiddleware {
private readonly RequestDelegate _next;
public MyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext,
IOptionsSnapshot<ApiClientHttpSettings> settings,
IServiceCollection services)
{
services.AddScoped<ICustomer>(new Customer());
await _next(httpContext);
}
}
So that I can get it inside controllers:
public class CustomerController : ControllerBase
{
public ControllerBase(ICustomer customer)
{
}
}
But in the middleware IServiceCollection cannot be resolved.
I want to do this because there is a lot of logic to resolve the DI involved.
I can also try to do inside ConfigureServices but then I wont get access to IOptionsSnapshot<SupplyApiClientHttpSettings> settings I need for every request.
Any pointer to right direction is really appreciated.
I can also try to do inside ConfigureServices, but then I wont get access to IOptionsSnapshot<SupplyApiClientHttpSettings> settings I need for every request.
Here is how you can get access to IOptionsSnapshot inside a custom service. The full source is here in GitHub.
Create your settings class.
public class SupplyApiClientHttpSettings
{
public string SomeValue { get; set; }
}
Add a value for it in configuration (e.g. in appsettings.json).
{
"someValue": "Value from appsettings"
}
Define your service and inject IOptionsSnapshot into it.
public class CustomerService
{
private readonly SupplyApiClientHttpSettings settings;
public CustomerService(IOptionsSnapshot<SupplyApiClientHttpSettings> options)
{
this.settings = options.Value;
}
public Customer GetCustomer()
{
return new Customer
{
SomeValue = settings.SomeValue
};
}
}
Wire together your configuration, options, and service in Startup.
public class Startup
{
IConfiguration Configuration;
public Startup()
{
Configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.Configure<SupplyApiClientHttpSettings>(Configuration);
services.AddScoped<CustomerService>();
services.AddMvc();
}
public void Configure(IApplicationBuilder app)
{
app.UseMvcWithDefaultRoute();
}
}
Inject the service into your controller. Use the service to get the customer with the up to date options snapshot.
public class CustomerController : Controller
{
private readonly CustomerService customerService;
public CustomerController(CustomerService customerService)
{
this.customerService = customerService;
}
public IActionResult Index()
{
return Json(customerService.GetCustomer());
}
}
Here is the full source in GitHub.
The answer was quite simple and close. Following is just what I had to do :
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<ICustomer>(provider => {
var settings = Configuration.GetSection("ApiClientHttpSettings").Get<ApiClientHttpSettings>();
return new Customer(settings.Name, settings.Age);
});
}
The above ticks all the boxes for me :
New instance for each request
Able to read updated config at the time of request
Create instance according to custom logic

Configure Simple Injector to support resolving ASP.NET Core middleware dependencies?

How can I configure SimpleInjector to resolve LogMiddleware's Invoke method dependency, like IMessageService ?
As I know, Asp.net core uses HttpContext.RequestServices (IServiceProvider) to resolve dependencies, I set SimpleInjector container to HttpContext.RequestServices property but didn't work. I want to change ServiceProvider dynamically because each tenant should have a container.
public class LogMiddleware
{
RequestDelegate next;
private readonly ILogger log;
public LogMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)
{
this.next = next;
this.log = loggerFactory.CreateLogger<LogMiddleware>();
}
public async Task Invoke(HttpContext context, IMessageService messageService)
{
await context.Response.WriteAsync(
messageService.Format(context.Request.Host.Value));
}
}
public interface IMessageService
{
string Format(string message);
}
public class DefaultMessageService : IMessageService
{
public string Format(string message)
{
return "Path:" + message;
}
}
You can use your LogMiddleware class as follows:
applicationBuilder.Use(async (context, next) => {
var middleware = new LogMiddleware(
request => next(),
applicationBuilder.ApplicationServices.GetService<ILoggerFactory>());
await middleware.Invoke(context, container.GetInstance<IMessageService>());
});
I however advise you to change your middleware class a little bit. Move the runtime data (the next() delegate) out of the constructor (since components should not require runtime data during construction), and move the IMessageService dependency into the constructor. And replace the ILoggerFactory with a ILogger dependency, since an injection constructor should do no more than store its incoming dependencies (or replace ILogger with your own application-specific logger abstraction instead).
Your middleware class will then look as follows:
public sealed class LogMiddleware
{
private readonly IMessageService messageService;
private readonly ILogger log;
public LogMiddleware(IMessageService messageService, ILogger log) {
this.messageService = messageService;
this.log = log;
}
public async Task Invoke(HttpContext context, Func<Task> next) {
await context.Response.WriteAsync(
messageService.Format(context.Request.Host.Value));
await next();
}
}
This allows you to use your middleware as follows:
var factory = applicationBuilder.ApplicationServices.GetService<ILoggerFactory>();
applicationBuilder.Use(async (context, next) => {
var middleware = new LogMiddleware(
container.GetInstance<IMessageService>(),
factory.CreateLogger<LogMiddleware>());
await middleware.Invoke(context, next);
});
Or in case you registered the ILogger (or your custom logging abstraction) in Simple Injector, you can do the following:
applicationBuilder.Use(async (context, next) => {
var middleware = container.GetInstance<LogMiddleware>();
await middleware.Invoke(context, next);
});
There is two problem with your code.
Your "DefaultMessageService" does not implement interface "IMessageService".
It should be like this.
public class DefaultMessageService : IMessageService
{
public string Format(string message)
{
return "Path:" + message;
}
}
You have to register DefaultMessageService in ConfigureService in Startup.cs.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
services.AddSingleton<IMessageService>(new DefaultMessageService());
}

Categories