Global exception handling in ASP.NET 5 - asp.net

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.

Related

Why doesn't middleware execute after mapping controllers?

I am building the most basic of an ASP.NET Core Web API, and after a lot of experimenting on my own, I found that running any middleware (custom, app.run, app.use) after the .MapControllers() method doesn't work at all.
internal class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseEndpoints(endpoint =>
{
endpoint.MapControllers();
});
}
}
I'm asking here because the documentation didn't clarify: how does the MapControllers() method affect other middleware in the pipeline?
I found that running any middleware (custom, app.run, app.use) after
the .MapControllers() method doesn't work at all.
The issue might relate the middleware order, using the MapController method, it already find the endpoint and return the response, so it will not invoke the next middleware.
You can refer the following code: in this sample, by using the MapControllser method, it can't match the /map1, so we can access it using https://localhost:44379/map1 (change the port to yours).
The result like this:
More detail information, see ASP.NET Core Middleware.

No service for type 'CommanderContext' has been registered

I am using .NET 5.0 and HotChoclate framework to create a GraphQL API.
Below is my ConfigureServices Method
public void ConfigureServices(IServiceCollection services)
{
services.AddPooledDbContextFactory<CommanderContext>(options =>
{
options.UseSqlServer(_configuration.GetConnectionString("default"));
});
services.AddGraphQLServer()
.AddQueryType<Query>()
.AddMutationType<Mutation>()
.AddFiltering()
.AddSorting()
.AddProjections();
}
To resolve the concurrency issues. I am using AddPooledDbContextFactory() method.
https://chillicream.com/docs/hotchocolate/integrations/entity-framework.
Below is my Configure Method
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
InitializeDatabase(app);
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGraphQL();
});
}
private void InitializeDatabase(IApplicationBuilder app)
{
using var scope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope();
scope.ServiceProvider.GetRequiredService<CommanderContext>().Database.Migrate(); //Here I get the exception.
}
Since the application is in development, I want the migrations to be automatically applied to the database.
But when I try and get instance of my context class in InitializeDatabase() method, I get the above exception.
Now I have a little understanding that instead of getting a single instance my context class I am getting back a pool of context class.
My Question is: How to I automatically apply the migrations to my database.
Any help is appreciated.
Thanks
AddPooledDbContextFactory method registers IDbContextFactory<TContext> instead the DbContext itself, that's why you can't use GetRequiredService<TContext>. Instead, you should retrieve the factory, use it to obtain TContext instance, do your work and then Dispose it (which in fact will return it to the pool). You can't rely on DI to do that for you because it is not resolved (thus not maintained) by the DI system, but from another abstraction (called factory pattern).
Something like this:
using var context = scope.ServiceProvider
.GetRequiredService<IDbContextFactory<CommanderContext>>()
.CreateDbContext();
context.Database.Migrate();

Why does my HomeController not get instantiated when I register an IHostedService

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.

Hangfire recurring job at a specific time ASP MVC

Ive been recently using hangfire to process lengthy jobs enabling me to return API calls more efficiently within an ASP MVC Core application.
i have implimented this by adding the following to startup.cs
public class Startup
{
public IConfigurationRoot Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddCors(options => options.AddPolicy("AllowAll", p => p.AllowAnyOrigin()));
services.AddHangfire(configuration => configuration.UseSqlServerStorage(Configuration.GetConnectionString("DefaultConnection")));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseCors("AllowAll");
app.UseMvc();
app.UseHangfireDashboard();
app.UseHangfireServer();
}
and then calling this within an action on a controller
using (new BackgroundJobServer())
{
formData.DateCreated = DateTime.Now;
formData.Source = "Web";
formData.StatusItem = _context.Status.FirstOrDefault(x => x.Default == true);
_context.Lead.Add(formData);
_context.SaveChanges();
}
Now i have a need to send a an email at 1am every day with the status of records from the database.
Im slightly confused on the implementation from the following perspective:
-Where do i implement the background job?
-Do i put the call in a method, if so how is this called/
-I cannot find BackgroundJob.AddOrUpdate, which i understand is used for the recurring tasks
-The schedule method takes a timespan object, but all examples are using CRON.
I have all the code to create the email, i just need help scheduling it
Thanks for your help in advance.

Unable to run my asp.net mvc web application after installing HangFire

I am working on an asp.net MVC-5 web application, and using nuget i installed the hangfire tool:-
Install-Package Hangfire
but when i run my application i got this exception:-
The following errors occurred while attempting to load the app.
- No assembly found containing an OwinStartupAttribute.
- No assembly found containing a Startup or [AssemblyName].Startup class.
To disable OWIN startup discovery, add the appSetting owin:AutomaticAppStartup with a value of "false" in your web.config.
To specify the OWIN startup Assembly, Class, or Method, add the appSetting owin:AppStartup with the fully qualified startup class or configuration method name in your web.config.
second question. if i got the above error fix, how i can call an action method on predefined intervals using hangfire. currently i am defining this inside my glabal.asax as follow:-
static void ScheduleTaskTrigger()
{
HttpRuntime.Cache.Add("ScheduledTaskTrigger",
string.Empty,
null,
Cache.NoAbsoluteExpiration,
TimeSpan.FromMinutes(60)),
CacheItemPriority.NotRemovable,
new CacheItemRemovedCallback(PerformScheduledTasks));
}
static void PerformScheduledTasks(string key, Object value, CacheItemRemovedReason reason)
{
//Your TODO
HomeController h = new HomeController();
var c = h.ScanServer("12345", "allscan");
ScheduleTaskTrigger();
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
ScheduleTaskTrigger();
}
----EDIT----------
now after adding the startup.css class , i defined the following inside my global.asax :-
HomeController h = new HomeController();
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
// ScheduleTaskTrigger();
RecurringJob.AddOrUpdate(() => h.ScanServer("12345","allscan"), Cron.Minutely);
}
mainly to call an action method named "ScanServer" under the Home controller. now the ScanServer is an async task which have the following defenition :-
public async Task<ActionResult> ScanServer(string tokenfromTMS, string FQDN)
{
so my global.asax is raising this error :-
Async methods are not supported. Please make them synchronous before using them in background.
It seems that your OWIN startUp class is missing, So create a class with name Startup:
public class Startup
{
public void Configuration(IAppBuilder app)
{
//..codes
}
}
For your second question, if you want to call a method, for example each hour you can use RecurringJob:
RecurringJob.AddOrUpdate(() => CallMethod(), Cron.Hourly);

Resources