Why does my HomeController not get instantiated when I register an IHostedService - asp.net-core-3.0

I've been scratching my head trying to get this to work but nothing I try seems to.
To summarise, I'm trying to:
1) register as a singleton, one of my services which is started by an IHostedService wrapper
2) have my HomeController instantiated on app startup with the above service injected
I started with the following, and attempted various versions of it to no avail:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddSingleton<IRunnerA, RunnerA>();
services.AddSingleton<IRunnable>
(
services => services.GetRequiredService<IRunnerA>()
// services => services.GetService<IRunnerA>() does not work either
);
services.AddSingleton<IHostedService, RunnableWrapper>(); // the IHostedService wrapper
}
While RunnableWrapper does kick off on app startup (i.e., its StartAsync is invoked), my HomeController below never gets instantiated:
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private IRunnerA _runnerA;
public HomeController
(
ILogger<HomeController> logger,
IRunnerA runnerA
)
{
// Never reaches here while debugging, given the above services config.
}
Question: Why isn't my HomeController getting instantiated?
===
Further info:
Among the many things I attempted, I also tried what was suggested here: https://stackoverflow.com/a/52398431, but I still get the same behaviour.
Observations:
Weirdly enough, if I remove the line services.AddSingleton<IHostedService, RunnableWrapper>();, my HomeController does get instantiated on app startup.
If I return from my RunnableWrapper's public async Task StartAsync(CancellationToken cancellationToken) method without performing a long running operation inside it (which I thought was the whole point of a background service), my HomeController does get instantiated.

It seems like the problem lies with starting a long running task in the IHostedService when it's registered inside ConfigureServices within Startup.
For whatever reason, it appears to block ApplicationStarted.
As per this section, https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.0&tabs=visual-studio#ihostedservice-interface, I removed the line services.AddSingleton<IHostedService, RunnableWrapper>();, and updated CreateHostBuilder to the following:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.ConfigureServices(services =>
{
services.AddHostedService<RunnableWrapper>();
});
Now things work as expected.
I'm not sure why StartAsync in the IHostedService is called before application start by default; if it has a long running task then it just seems to block.

Related

Scope in Middleware and Blazor Component

I'm working on a server-side Blazor application and ran into some problems regarding a scoped service.
For simplicity's sake I have re-created my issue using the default Blazor template (the one with the counter).
I have a service "CounterService", which initializes a counter to 1 and exposes this counter together with a method to increment it. Really basic:
public class CounterService
{
public int Counter { get; private set; }
public CounterService()
{
Counter = 1;
}
public void IncrementCounter()
{
Counter++;
}
}
I have then registered this counter in my Startup.cs as a scoped service: `services.AddScoped()
Then I have a custom ASP.NET middleware, in this case a "CounterInitializerMiddleware".
public class CounterInitializerMiddleware
{
public CounterInitializerMiddleware(RequestDelegate next)
{
_next = next;
}
public RequestDelegate _next { get; }
public async Task Invoke(HttpContext context, CounterService counterService)
{
Console.WriteLine($"CounterInitializer invoked from request path: {context.Request.Path.Value}");
counterService.IncrementCounter();
counterService.IncrementCounter();
await _next(context);
}
}
public static class MiddlewareExtensions
{
public static IApplicationBuilder UseCounterInitializer(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<CounterInitializerMiddleware>();
}
}
Basically, its a middle layer to increment the counter so that it starts at 3 rather than 1 when I get the service injected to my component(s). I register it in my Configure-method in Startup.cs: `app.UseCounterInitializer();
This middleware-layer is invoked 4 times when I start up my application (note that it has RenderMode set to ServerPreRendered): At the page-load request and at the _blazor-requests:
CounterInitializer invoked from request path: /counter
CounterInitializer invoked from request path: /_blazor/disconnect
CounterInitializer invoked from request path: /_blazor/negotiate
CounterInitializer invoked from request path: /_blazor
The scoped service is injected, and all seems good.
Then, if I have a component with the CounterService injected, it seems the scopes get messed up.
If I look at the OnInitialized-method, this is called twice. Once during the pre-render and once during normal render.
At the pre-render execution, the CounterService has Counter set to 3 as expected, since it has been through the CounterInitializerMiddleware. However, during render execution, the CounterService is spawned fresh. So it seems the scope of the normal render and the scope(s) of the requests going through the middleware are different. I thought the scope of the components would be bound to the "_blazor"-signalR connection which is processed my the middleware.
Anyone who can figure out what is going on and help me understand how to accomplish what I'm trying to do?
Best,
Mathias
EDIT: Just to clarify. My real use-case is something entirely different, and the Counter-example is just a simplified case showcasing the issue and is more easily reproducible (I hope).
I've hit the same problem and needed a quick workaround.
The workaround is to get the service from the HttpContext, which is an anti-pattern but better than nothing.
class YourClass
{
private readonly SomeMiddlewareScopedService _service;
public YourClass(SomeMiddlewareScopedServiceservice)
{
_service = service;
}
}
The workaround:
class YourClass
{
private readonly SomeMiddlewareScopedService _service;
public YourClass(IHttpContextAccessor contextAccessor)
{
_service= (SomeMiddlewareScopedService)contextAccessor.HttpContext.RequestServices.GetService(typeof(SomeMiddlewareScopedService));
}
}
Don't forget to add to your builder:
builder.Services.AddHttpContextAccessor();

What role do the Build() and Run() function play in an ASP.Net Core web application?

I am really new for programming started to development on .NET core Web API development.
This is the piece of code :
namespace time_api
{
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
}
I have spent more hours to understand this code piece. I understand that it is calling a static method CreateWebHostBuilder. But I really do not understand what are those .Build() & .Run().
It is like a method(CreateWebHostBuilder) calling another method and another method ?
What is the meaning of having => here ?
Please help me to understand
Ok, here is what is happening in a nutshell.
You're calling your own static method that constructs an IWebHostBuilder which is an interface whose purpose is to allow you to configure an ASP.Net hosting environment.
You're then calling an instance method, Build, on the IWebHostBuilder object that your static function returned.
The Build function takes the configuration, extensions, and so on that is stored on the IWebHostBuilder implementation and uses it to construct the IWebHost instance that will actually be running listening for HTTP requests to handle.
You're finally calling the Run method on the IWebHost that the IWebHostBuilder.Build method returned.
This is the method that actually listens for HTTP requests, maps the requests to one of your controllers, instantiates that controller along with any dependencies, and invokes the action methods.
This method "blocks", or does not return, until something causes your program to exit (i.e. you hit Ctrl+C).
The => after the CreateWebHostBuilder(string[] args) declaration is a new-ish way of declaring a method body called an "expression-bodied member." In this case it is equivalent to the following
public static IWebHostBuilder CreateWebHostBuilder(string[] args) {
return WebHost.CreateDefaultBuilder(args).UseStartup<Startup>();
}
The same symbol => can be used to create a "lambda", or anonymous function, in a case where a delegate or expression is expected.

Log4Net Default behavior recording arguments

I have a few methods that take in a Json (Jarray) as a parameter in my payload. So the method signature may look like this:
[HttpPut("{id:int}/data")]
public async Task<IActionResult> Put([FromBody] JArray jsonData, int id)
Log4Net records the payload in the log file. I'm trying to turn this behavior off and I was wondering if there was a way to do that?
I've played around with some configuration for the minimum logging but that doesn't seem to matter.
Maybe you can fulfill your desired behavior playing with the ASP.NET Core logging filtering.
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureLogging((hostingContext, logging) =>
{
// Next line filters every "Microsoft" related message to be logged on any implementation of Microsoft Logging Providers.
logging.AddFilter("Microsoft", LogLevel.None);
})
.UseStartup<Startup>();
}
Just change the Microsoft text in order to filter the most fine-grained elements to get your desired behavior in place.

Global exception handling in ASP.NET 5

How can I attach my own logging logic to an ASP.NET 5 application to handle each exception thrown in the business logic and lower layers?
I tried with own ILoggerProvider implementation and loggerfactory.AddProvider(new LoggerProvider(Configuration)) in Startup.cs. But it seems that it intercepts inner ASP.NET stuff, and not my thrown exceptions in lower layers.
Worked it out, by using two options:
1) ILoggerProvider
Implement your own ILoggerProvider and ILogger from the namespace Microsoft.Framework.Logging Then attach it to the MVC Framework in Startup.cs add following code:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerfactory)
{
loggerfactory.AddProvider(new YourCustomProvider());
}
But this above option, seems to only call the Write function of the ILogger on MVC specific events, routing related and so on, it wasn't called when I threw exceptions on my lower layers, so the second option worked out:
2) Global Filter
Register your own ActionFilterAttribute implementation in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().Configure<MvcOptions>(options =>
{
options.Filters.Add(new YourCustomFilter());
});
}
It's important, that the custom filter class implements the IExceptionFilter interace:
public class YourCustomFilter : ActionFilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext context)
{
///logic...
}
}
(EDIT:)
And then in the Startup class we add the filter:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.Filters.Add(new YourCustomFilter());
});
}
If you want a really global exception trap framework, not just at the controller level, take a look at one of my open source projects. I plan to make it into a Nuget Package soon after the holidays. I'm also planning to update my blog showing why I developed it.
The open source project is on github:
https://github.com/aspnet-plus/AspNet.Plus.Infrastructure
Take a look at the sample for usage.

DbContext gets disposed in ActionFilterAttribute, not reloaded on subsequent requests

I'm trying to inject a UOW container into a WebApi 2 actionfilter attribute
public class VerifyApiToken : ActionFilterAttribute
{
[Inject]
public IUOW Uow { get; set; }
public override void OnActionExecuting(HttpActionContext actionContext)
{
//get and save data to any repository in the uow container
base.OnActionExecuting(actionContext);
}
The UOW container gets injected like its supposed to and on the first request everything works fine. On subsequent request EF throws an exception saying that the DbContext has been disposed.
All the dependancies are bound in request scope, so its normal that the underlying dbcontext gets disposed. When using constructor injection in WebApi controllers everything works, resources are recreated on each request, why are they not recreated when trying to use Property injection in ActionFilterAttributes and how could this be resolved?
The IFilterProvider I'm using:
public IEnumerable<FilterInfo> GetFilters(HttpConfiguration configuration, HttpActionDescriptor actionDescriptor)
{
IEnumerable<FilterInfo> controllerFilters = actionDescriptor.ControllerDescriptor.GetFilters().Select(instance => new FilterInfo(instance, FilterScope.Controller));
IEnumerable<FilterInfo> actionFilters = actionDescriptor.GetFilters().Select(instance => new FilterInfo(instance, FilterScope.Action));
IEnumerable<FilterInfo> filters = controllerFilters.Concat(actionFilters);
foreach (FilterInfo filter in filters)
{
_kernel.Inject(filter.Instance);
}
return filters;
}
The "Inject" method description says "Injects the specified instance, without managing its lifecycle". So I take it that my VerifyApiToken attribute is injected once per App lifecycle (basically Application_Start) and thus on the following requests the same instance is used (with a disposed DbContext of course).
Is it even possible to configure Ninject to use a new IUOW container for each request in ActionFilterAttributes?
Filters are cached and reused by the WebApi Framework. You should thus not inject any dependency in request scope; use a factory instead.
For more information see the documentation
I was able to follow qujck's hint and instead inject a Func to the actionfilter.
It is required the following binding:
kernel.Bind<Func<IUOW>>().ToMethod(m => () => m.Kernel.Get<IUOW>());

Resources