I had the following Middleware, the main idea is that check for every request (throught View or Ajax) if the user is not blocked in the database.
internal class UserMiddleware
{
private readonly RequestDelegate next;
private readonly IUsuariosRepository userRepository;
public UserMiddleware(RequestDelegate next, IUsuariosRepository userRepository)
{
this.next = next ?? throw new ArgumentNullException(nameof(next));
this.userRepository = userRepository ?? throw new ArgumentNullException(nameof(userRepository));
}
public async Task Invoke(HttpContext httpContext)
{
string guid = httpContext.User.Claims?.FirstOrDefault(x => x.Type.Equals("theClaimGuid"))?.Value;
bool isBlocked = await userRepository.CheckUserBlockByGUID(guid);
if (isBlocked)
{
await httpContext.Response.WriteAsync("You are blocked.");
return;
}
await next(httpContext);
}
}
The call to the database with the context is made in the CheckUserBlockByGUID method:
public class UsuariosRepository : IUsuariosRepository
{
private readonly nContext _context;
private readonly IWebHostEnvironment _hostingEnvironment;
public UsuariosRepository(nContext context, IWebHostEnvironment hostingEnvironment)
{
_context = context;
_hostingEnvironment = hostingEnvironment;
}
public async Task<bool> CheckUserBlockByGUID(string guid) {
bool isOK = await _context.Usuarios.Where(u => u.Guid == guid && u.Deleted == 0 && u.blocked == 0).AnyAsync();
return !isOK;
}
...
and the Middleware is called inside Startup.cs / Config as:
app.UseAuthentication();
app.UseAuthorization();
app.UseMiddleware<UserMiddleware>();
All my calls to the database are used with async / await but with this one, I get the following error:
A second operation started on this context before a previous operation
completed. This is usually caused by different threads using the same
instance of DbContext. For more information on how to avoid threading
issues with DbContext, see
https://go.microsoft.com/fwlink/?linkid=2097913.
I understand that I'm calling without waiting for the close operation of the other one. But I can't find that call. I don't know even if the error is this one or the problem is another one that I'm not seeing right now.
Any help or tip will be preciated.
Also please ask me in order to clarify this question or update it.
Thanks in advance and sorry for my english.
Related
I am trying to use HttpContextAccessor on my custom class(BLL class) and while i succesfully initializes the ContextAccessor meantime HttpContext itself is null.
Code in program.cs
builder.Services.AddSingleton<IUserPermissionConfig, UserPermisionConfig>();
builder.Services.AddHttpContextAccessor();
var app = builder.Build();
var setUserPermission = app.Services.GetRequiredService<IUserPermissionConfig>();
setUserPermission.SetUserPermissionsSession(UserConstants.SYSTEM_ID);
UserPermisionConfig component code
private readonly IHttpContextAccessor _httpContextAccessor;
public UserPermisionConfig( IHttpContextAccessor httpContextAccessor)
{
_permisionServiceClient = new PermissionServiceClient();
_httpContextAccessor = httpContextAccessor ?? throw new Exception("Http context accessor is null.");
}
public async Task SetUserPermissionsSession(int systemId)
{
string userName = _httpContextAccessor.HttpContext.
User.Identity.Name; //here httpcontext is alway null
UserPermissionsModel userPermissionModel = await GetUserPermission(systemId, userName);
_httpContextAccessor.HttpContext.Session.Set(UserConstants.SESSION_USER_PERMISSIOS, ByteArrayExtensions.ToByteArray(userPermissionModel));
}
Any help ?
I think you could check this document
just as mentioned in the document:
HttpContext isn't thread-safe. Reading or writing properties of the HttpContext outside of processing a request can result in a NullReferenceException.
In Asp.net if you call httpcontext directly in Application_Start() method in global.asax.cs,you would get the similar error
If your codes was in your HttpModule in Asp.net ,check this document to learn how to migrate module codes to middleware
And I tried as below:
public class MyMiddleware
{
private readonly RequestDelegate _next;
private readonly IUserPermisionConfig _config;
public MyMiddleware(RequestDelegate next, IUserPermisionConfig config)
{
_next = next;
_config = config;
}
public async Task InvokeAsync(HttpContext context)
{
_config.SetUserPermissionsSession(5);
await _next(context);
}
}
public interface IUserPermisionConfig
{
Task SetUserPermissionsSession(int systemId);
}
public class UserPermisionConfig : IUserPermisionConfig
{
private readonly IHttpContextAccessor _accessor;
public UserPermisionConfig(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
public Task SetUserPermissionsSession(int systemId)
{
var httpcontext = _accessor.HttpContext;
var user = httpcontext.User.Identity?.Name;
httpcontext.Session.SetString("Somekey", systemId.ToString());
return Task.CompletedTask;
}
}
Regist the services:
builder.Services.AddHttpContextAccessor();
builder.Services.AddTransient<IUserPermisionConfig, UserPermisionConfig>();
// The two following services are required to use session in asp.net core
builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession();
call the middleware in the pipeline :
.....
app.UseSession();
app.UseMiddleware<MyMiddleware>();
.....
it works well in my case:
Ok, it looks like you are trying to access the HTTP context directly from Program.cs. This is not how it works.
The HTTP context is only avialable during an incoming HTTP request.
Your IUserPermissionConfig service must be called from the ASP.net pipeline somewhere (controller, filter, middleware).
I was working with SignalR, and created timer that will execute following code where I need to get the number of unread messages from database
[Route("api/[controller]")]
[ApiController]
public class PorukeHubController : ControllerBase
{
private readonly IHubContext<PorukeHub> _hub;
private readonly TimerManager _timer;
private DnevnikContext _context { get; set; }
public PorukeHubController(IHubContext<PorukeHub> hub,
TimerManager timer, DnevnikContext context)
{
_hub = hub;
_timer = timer;
_context = context;
}
[HttpGet]
public IActionResult Get()
{
var currentUserId = 1;
if (!_timer.IsTimerStarted)
_timer.PrepareTimer(() =>
{
var unreadMessages = _context.Messages
.Where(p => p.RecieverID == currentUserId && p.isRead == false)
.Count();
_hub.Clients.All.SendAsync("checkForMessages", unreadMessages);
});
return Ok(new { Message = "Request Completed" });
}
Unfortunately, I get the following error when trying to access _context:
System.ObjectDisposedException: 'Cannot access a disposed context
instance. A common cause of this error is disposing a context instance
that was resolved from dependency injection and then later trying to
use the same context instance elsewhere in your application. This may
occur if you are calling 'Dispose' on the context instance, or
wrapping it in a using statement. If you are using dependency
injection, you should let the dependency injection container take care
of disposing context instances. ObjectDisposed_ObjectName_Name'
I'm very confused on what steps should I take to solve this, I'm not yet familiar that much with DI too
Any help would be appreciated
You should turn your method into an async method and await the database call.
public async Task<IActionResult> Get()
{
...
var unreadMessages = await _context.Messages.Where(p => p.RecieverID == currentUserId && p.isRead == false).Count();
...
}
A DbContext only lives for a short period of time and its lifetime ends at the end of the web request for which it is created.
The delegate, however, that you use to initialize the TimerManager stores a reference to that DbContext likely for the duration of the application (assuming that TimerManager is a Singleton). But since that DbContext is disposed of soon after you initialized the TimerManager it becomes unusable.
In general, you should prevent moving injected dependencies from thread to thread (except in case those threads are part of a sequentially executed asynchronous operation), because the consuming code (i.e. your controller) doesn't know whether or not it is safe to do so.
This means that, instead of reusing the same DbContext, the timer should get its own instance and, preferable, get a fresh DbContext instance every time the timer is triggered. You should see each timer tick as a new request, just like every call to a controller is a new (web) request. And the general rule of thumb is that each new request gets its own container scope.
What this means is that, instead of reusing the same DbContext instance, you should wrap each tick in a container scope / IServiceScope and resolve the DbContext from that scope.
For instance:
private readonly TimerManager _timer;
private readonly IServiceProvider _provider;
public PorukeHubController(TimerManager timer, IServiceProvider provider)
{
_timer = timer;
_provider = provider;
}
[HttpGet]
public IActionResult Get()
{
var currentUserId = 1;
if (!_timer.IsTimerStarted)
_timer.PrepareTimer(() =>
{
using (var scope = this.provider.CreateScope())
{
var sp = scope.ServiceProvider;
var context = sp.GetRequiredService<DnevnikContext>();
var hub = sp.GetRequiredService<IHubContext<PorukeHub>>();
var unreadMessages = context.Messages
.Where(p => p.RecieverID == currentUserId && p.isRead == false)
.Count();
hub.Clients.All.SendAsync("checkForMessages", unreadMessages);
}
});
return Ok(new { Message = "Request Completed" });
}
Although the code above fixes the initial problem of letting the DbContext go out of scope, it poses a new problem, which is more fundamental in nature, which is that application components shouldn't depend on the (IServiceProvider) DI Container. This is called the Service Locator anti-pattern.
This means that you shouldn't do this type of initialization inside your controllers, but instead move this to the application startup path; a.k.a. the Composition Root.
This can be done, for instance, by introducing a new abstraction:
private readonly IMessageInitializer _initializer;
public PorukeHubController(IMessageInitializer initializer)
{
__initializer = _initializer;
}
[HttpGet]
public IActionResult Get()
{
_messageInitializer.Initialize();
return Ok(new { Message = "Request Completed" });
}
In this code example the new IMessageInitializer hides the complexity of initialization of the timer, the querying of the database and the calling of the hub from the controller.
Inside your Composition Root you can now define an implementation with the original code:
public class ScheduledMessageInitializer : IMessageInitializer
{
private readonly TimerManager _timer;
private readonly IServiceProvider _provider;
public ScheduledMessageInitializer(
TimerManager timer, IServiceProvider provider)
{
_timer = timer;
_provider = provider;
}
public void Initialize()
{
var currentUserId = 1;
if (!_timer.IsTimerStarted)
_timer.PrepareTimer(() => {
using (var scope = this.provider.CreateScope())
{
var sp = scope.ServiceProvider;
var context = sp.GetRequiredService<DnevnikContext>();
var hub = sp.GetRequiredService<IHubContext<PorukeHub>>();
var unreadMessages = context.Messages
.Where(p => p.RecieverID == currentUserId && p.isRead == false)
.Count();
hub.Clients.All.SendAsync("checkForMessages", unreadMessages);
}
});
}
}
This class can be registered as follows:
// This assumes that TimerManager is a singleton as well.
services.AddSingleton<IMessageInitializer, ScheduledMessageInitializer>();
This still poses a (smaller) design issue, which is that with DI you should strive to keep the application's business logic out of the Composition Root. It should only contain the required infrastructural code for the application to execute. The querying of the database and sending it to the hub can be considered business logic; not infrastructure.
That would mean that one last refactoring is in place: you should extract that logic out of the ScheduledMessageInitializer and place it in a new application component:
public class UnreadMessageChecker
{
private readonly DnevnikContext _context;
private readonly IHubContext<PorukeHub> _hub;
public UnreadMessageChecker(DnevnikContext context, IHubContext<PorukeHub> hub)
{
_context = context;
_hub = hub;
}
public async Task CheckAsync()
{
var unreadMessages = context.Messages
.Where(p => p.RecieverID == currentUserId && p.isRead == false)
.Count();
// I noticed how you called SendAsync. You should await it, otherwise
// you'll get into the same trouble as where you started out with:
// with an ObjectDisposedExcetion.
await hub.Clients.All.SendAsync("checkForMessages", unreadMessages);
}
}
services.AddTransient<UnreadMessageChecker>();
This new component can be resolved from the ScheduledMessageInitializer, which reduces it to the following:
public class ScheduledMessageInitializer : IMessageInitializer
{
private readonly TimerManager _timer;
private readonly IServiceProvider _provider;
public ScheduledMessageInitializer(
TimerManager timer, IServiceProvider provider)
{
_timer = timer;
_provider = provider;
}
public void Initialize()
{
var currentUserId = 1;
if (!_timer.IsTimerStarted)
_timer.PrepareTimer(async () =>
{
using (var scope = this.provider.CreateScope())
{
var checker = scope.ServiceProvider
.GetRequiredService<UnreadMessageChecker>();
await checker.CheckAsync();
}
});
}
}
There might still be other issues with your code. For instance, it seems weird to me that you have a currentUserId (which is runtime data, changing on each request), while using it to initialize the timer with; unless that timer isn't a Singleton. But if the timer isn't a singleton, that would mean that would be initializing an endless number of timers, which likely isn't a good idea as well.
Another issue is that, if the TimerManager is indeed singleton, there might be a race condition while initializing. Is the TimerManager thread-safe? What would happen when it gets initialized twice simultaneously? Would that cause problems. I, unfortunately, can't answer this.
I am trying to implement .NET Core 3 app.UseExceptionHandler for an API project, but I have a generic logger per API controller that I would like to pass into the error method. If, for instance, the error happens in my WorkingController, I would like to have ILogger<WorkingController> be the logging entity. I have found that using the built-in ExceptionHandler, I lose some of the context of the request, and I would like to capture this context if possible.
Here's what all of my API methods used to look like before:
[Route("api/working")]
[ApiController]
public class WorkingController
{
private readonly ILogger<WorkingController> _logger;
public WorkingController(ILogger<WorkingController> logger)
{
_logger = logger;
}
[Route("workingRoute")]
public IActionResult SomeMethod()
{
_logger.LogInformation("Starting SomeMethod");
try
{
// doing some stuff here
}
catch(Exception ex)
{
_logger.LogError(ex, "Something happened");
return Problem();
}
return Ok();
}
}
I tried setting up a BaseErrorController from which other Controllers could inherit:
[ApiController]
public abstract class BaseErrorController<T> : ControllerBase
{
protected readonly ILogger<T> Logger;
public BaseErrorController(ILogger<T> logger)
{
Logger = logger;
}
[AllowAnonymous]
[Route("/error")]
public IActionResult Error()
{
var context = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
if (context != null)
{
var ex = context.Error;
Logger.LogError(ex, $"{context.Path} failed - {ex.Message}");
return Problem(
detail: context.Error.StackTrace,
title: context.Error.Message);
}
return Problem();
}
}
And now my former WorkingController looks like this, which is arguably a lot cleaner (and less code):
[Route("api/working")]
[ApiController]
public class WorkingController : BaseErrorController<WorkingController>
{
public WorkingController(ILogger<WorkingController> logger) : base(logger) { }
[Route("workingRoute")]
public IActionResult SomeMethod()
{
Logger.LogInformation("Starting SomeMethod");
// doing some stuff here
return Ok();
}
}
In Startup.cs, I'm registering this all with app.UseExceptionHandler("/error"), and it seems to work OK . . . except now in my logs, I see the following error (because I have more than one controller implementing this base controller):
An exception was thrown attempting to execute the error handler.
Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware
Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints.
Matches: Namespace.Controllers.WorkingController.Error (Namespace)
Namespace.Controllers.AnotherController.Error (Namespace)
at Microsoft.AspNetCore.Routing.Matching.DefaultEndpointSelector.ReportAmbiguity(CandidateState[] candidateState)
at Microsoft.AspNetCore.Routing.Matching.DefaultEndpointSelector.ProcessFinalCandidates(HttpContext httpContext, CandidateState[] candidateState)
at Microsoft.AspNetCore.Routing.Matching.DefaultEndpointSelector.Select(HttpContext httpContext, CandidateState[] candidateState)
at Microsoft.AspNetCore.Routing.Matching.DfaMatcher.MatchAsync(HttpContext httpContext)
at Microsoft.AspNetCore.Routing.Matching.DataSourceDependentMatcher.MatchAsync(HttpContext httpContext)
at Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.Invoke(HttpContext httpContext)
at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.HandleException(HttpContext context, ExceptionDispatchInfo edi)
Does anybody have an idea what might be the solution here? Is there an overload of the ExceptionHandler that might be what I'm looking for? Is this solution too boutique, and I should go back to what I was doing before? Help me, Stack Overflow. Thank you!
Unfortunately, there is simply no built-in way to do this cleanly. You could find a package to help here (I haven't looked), or you could want to re-write some parts of ASP.NET Core, but I wouldn't really want to do that.
There is another way of doing this, which depending on which version you like more, is recommended/recommended against, but I'm pro of the former. Instead of throwing/catching exceptions at the Controller level, I treat Controllers as the dumbest thing possible, so they just call some service and that's it.
If you want to know where an exception was thrown, or you specifically want the exception to go uncaught, a strategy my team follows is to create custom exceptions. You could then leave these uncaught (and the HTTP500 will be returned to the caller) or you could have a custom Middleware and define there what should happen.
The following is an example, written entirely here so there may be some changes needed, and it's solely to demonstrate a possible approach, not a working demo.
Given some exceptions valid to your domain:
public class UserNotFoundException : Exception { public Guid UserId { get; set; } }
public class InvalidModelException : Exception { }
And an exception handler:
public class MyCustomExceptionHandlerMiddleware
{
private readonly ILogger<MyCustomExceptionHandlerMiddleware> _logger;
public MyCustomExceptionHandlerMiddleware(ILogger<MyCustomExceptionHandlerMiddleware> logger)
{
_logger = logger;
}
public async Task Invoke(RequestDelegate next)
{
try
{
await next(); // without this, the request doesn't execute any further
}
catch (UserNotFoundException userNotFound)
{
_logger.LogError(userNotFound, "The user was not found");
// manipulate the response here, possibly return HTTP404
}
catch (Exception ex)
{
_logger.LogError(ex, "Something really bad happened");
// manipulate the response here
}
}
}
You could have something like this:
public class UsersService : IUsersService
{
private readonly ILogger<UsersService> _logger;
private readonly UsersContext _context;
// assume UsersContext is an EntityFramework context or some database service
public UsersService(ILogger<UsersService> logger, UsersContext context)
{
_logger = logger;
_context = context;
}
public async Task<User> GetUserAsync(Guid userId)
{
try
{
if (userId == Guid.Empty)
{
throw new InvalidModelException();
}
var user = await _context.FindUserAsync(userId);
if (user == null)
{
throw new UserNotFoundException(userId);
}
return user;
}
catch (InvalidModelException invalidModel)
{
_logger.LogWarning("The received user id is empty");
return null;
}
}
}
And its corresponding controller:
public class UsersController : ControllerBase
{
private readonly IUsersService _usersService;
public UsersController(IUsersService usersService)
{
_usersService = usersService;
}
[HttpGet("userId:guid")]
public async Task<IActionResult> GetUser(Guid userId)
{
var user = await _usersService.GetUserAsync(userId);
if (user == null)
{
return BadRequest();
}
return Ok(user);
}
}
Again, this is just an example to demonstrate how you could approach this, normally you'd do input validation in a more consistent way.
A user can trigger a long-running job by sending a request to an ASP.NET Core controller. Currently, the controller executes the job and then sends a 200 OK response. The problem is that the client has to wait rather long for the response.
This is why I am currently trying to process the job in a background task. I am using an IBackgroundTaskQueue where all jobs are stored and an IHostedService that processes the jobs whenever a new one is enqueued. It is similar to the code in the Microsoft documentation.
But the job does need access to the database and therefore the user has to authenticate using Active Directory. Hence, I need access to the HttpContext.User property in the background task. Unfortunately, the HttpContext is disposed when the response is sent and before the processing of the job begins.
Demonstration
public class Job
{
public Job(string message)
{
Message = message;
}
public string Message { get; }
}
The controller enqueues a new job in the task queue.
[HttpGet]
public IActionResult EnqueueJob()
{
var job = new Job("Hello World");
this.taskQueue.QueueBackgroundWorkItem(job);
return Accepted();
}
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private ConcurrentQueue<Job> jobs = new ConcurrentQueue<Job>();
private SemaphoreSlim signal = new SemaphoreSlim(0);
public void QueueBackgroundWorkItem(Job job)
{
jobs.Enqueue(job);
signal.Release();
}
public async Task<Job> DequeueAsync(CancellationToken cancellationToken)
{
await signal.WaitAsync(cancellationToken);
jobs.TryDequeue(out var job);
return job;
}
}
The IHostedService creates a new JobRunner for each job it dequeues. I'm using a IServiceScopeFactory here to have dependency injection available. JobRunner also has a lot more dependencies in the real code.
public class JobRunnerService : BackgroundService
{
private readonly IServiceScopeFactory serviceScopeFactory;
private readonly IBackgroundTaskQueue taskQueue;
public JobRunnerService(IServiceScopeFactory serviceScopeFactory, IBackgroundTaskQueue taskQueue)
{
this.serviceScopeFactory = serviceScopeFactory;
this.taskQueue = taskQueue;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (stoppingToken.IsCancellationRequested == false)
{
var job = await taskQueue.DequeueAsync(stoppingToken);
using (var scope = serviceScopeFactory.CreateScope())
{
var serviceProvider = scope.ServiceProvider;
var runner = serviceProvider.GetRequiredService<JobRunner>();
runner.Run(job);
}
}
}
}
public class JobRunner
{
private readonly ILogger<JobRunner> logger;
private readonly IIdentityProvider identityProvider;
public JobRunner(ILogger<JobRunner> logger, IIdentityProvider identityProvider)
{
this.logger = logger;
this.identityProvider= identityProvider;
}
public void Run(Job job)
{
var principal = identityProvider.GetUserName();
logger.LogInformation($"{principal} started a new job. Message: {job.Message}");
}
}
public class IdentityProvider : IIdentityProvider
{
private readonly IHttpContextAccessor httpContextAccessor;
public IdentityProvider(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public string GetUserName()
=> httpContextAccessor.HttpContext.User.Identity.Name; // throws NullReferenceException
}
Now, when sending a request, a NullReferenceException is thrown in JobRunner.Run() because httpContextAccessor.HttpContext is null.
What I've tried
I haven't had a good idea yet how to approach this problem. I know that it would be possible to copy the necessary information from the HttpContext, but don't know how to make them available to dependency injection services.
I thought that maybe I could create a new IServiceProvider that uses the services of an old one, but replaces the implementation for IHttpContextAccesor, but it does not seem to be possible.
How can I use the HttpContext in the background task although the response has been completed?
I have a Controller, which has 2 functions and a middleware.
My middleware
try
{
var request = httpContext.Request;
if (/* some logic here */)
{
await _next(httpContext);
// Some logic here
var someReturnValue = someFunction();
// Need to call the Controller Method here
// call SaveStaging(someReturnValue);
}
else
{
await _next(httpContext);
}
}
catch (Exception ex)
{
await _next(httpContext);
}
In my Controller
[Authorize(AuthenticationSchemes = "APIKeyAuthentication")]
[LogAction]
[ApiController]
[Route("api/[controller]/[action]")]
public class MyController : ControllerBase
{
private IHttpContextAccessor _accessor;
public MyController(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
[HttpPost]
public async Task<ActionResult<ServiceResult>> PostData([FromBody] DataObj request)
{
ServiceResult result = new ServiceResult();
result.isSuccessful = true;
return Ok(result);
}
public async Task SaveStaging(int logNumber)
{
}
}
So, initially, user will call the PostData endpoint. In this method, it will return that it accepts the data posted by user.
Then in the middleware, I will need to do some things, and finally, I would like to call the SaveStaging method in the Controller. I want this SaveStaging method to be only accessible for the middleware, and should not be called directly by the user.
So my question:
How to call this method from the middleware?
How to limit SaveStaging to be accessible only to the middleware?
You should consider wrapping SaveStaging into a service because it appears to be a cross-cutting concern. Beyond that, it's likely wise to implement a layered architecture as a matter of refactoring.
Since this is .NET Core, I would recommend fully embracing dependency injection and injecting the service into your middleware.
public interface IStagingService
{
Task Save(int logNumber);
}
public class MyController : ControllerBase
{
private readonly IStagingService _service;
private readonly IHttpContextAccessor _accessor;
public MyController(IStagingService service, IHttpContextAccessor accessor)
{
_service = service ?? throw new ArgumentNullException(nameof(service));
_accessor = accessor ?? throw new ArgumentNullException(nameof(accessor));;
}
//....
}
public class MyMiddleware
{
private readonly RequestDelegate _next;
private readonly IStagingService _service;
public RequestCultureMiddleware(RequestDelegate next, IStagingService service)
{
_next = next;
_service = service ?? throw new ArgumentNullException(nameof(service));
}
public async Task InvokeAsync(HttpContext context)
{
// ...
}
In the beginning, your service's will likely start out small and simple. In the event the app grows, the complexity of this layer will likely increase, spawning the need for some facade services (i.e. services composed of services) and maybe some providers for thing like data access.