I've written simple server using Owin Self-hosting and WebApi:
namespace OwinSelfHostingTest
{
using System.Threading;
using System.Web.Http;
using Microsoft.Owin.Hosting;
using Owin;
public class Startup
{
public void Configuration(IAppBuilder builder)
{
var config = new HttpConfiguration();
config.Routes.MapHttpRoute(
"Default",
"{controller}/{id}",
new { id = RouteParameter.Optional }
);
builder.UseWebApi(config);
}
}
public class Server
{
private ManualResetEvent resetEvent = new ManualResetEvent(false);
private Thread thread;
private const string ADDRESS = "http://localhost:9000/";
public void Start()
{
this.thread = new Thread(() =>
{
using (var host = WebApp.Start<Startup>(ADDRESS))
{
resetEvent.WaitOne(Timeout.Infinite, true);
}
});
thread.Start();
}
public void Stop()
{
resetEvent.Set();
}
}
}
When there is exception in controller, then Owin returns XML response like this:
<Error>
<Message>An error has occurred.</Message>
<ExceptionMessage>Attempted to divide by zero.</ExceptionMessage>
<ExceptionType>System.DivideByZeroException</ExceptionType>
<StackTrace>
...
</StackTrace>
</Error>
But i want different output - so how can i override this?
You do so by creating an OWIN MiddleWare and hooking it into the pipeline:
public class CustomExceptionMiddleware : OwinMiddleware
{
public CustomExceptionMiddleware(OwinMiddleware next) : base(next)
{}
public override async Task Invoke(IOwinContext context)
{
try
{
await Next.Invoke(context);
}
catch(Exception ex)
{
// Custom stuff here
}
}
}
And hook it on startup:
public class Startup
{
public void Configuration(IAppBuilder builder)
{
var config = new HttpConfiguration();
config.Routes.MapHttpRoute(
"Default",
"{controller}/{id}",
new { id = RouteParameter.Optional }
);
builder.Use<CustomExceptionMiddleware>().UseWebApi(config);
}
}
That way any unhandled exception will be caught by your middleware and allow you to customize the output result.
An important thing to note: if the thing being hosted has an exception handling logic of it's own, as WebAPI does, exceptions will not propagate. This handler is meant for any exception which goes by unhandeled by the underlying service being hosted.
When you await a async method, an exception won't be thrown into the caller's context, but will be available by inspecting the task returned to the caller.
So, with this modification to the solution provided by #yuval-itzchakov you'll be able to capture underlying exceptions:
var subtask = Next.Invoke(context);
await subtask;
if (subtask.Exception != null)
{
// log subtask.Exception here
}
Related
I'm trying to set up global exception handling code in .NetCore 3.1 webpai
My goal is to log unhandled exception before the app exits, using log4net.
I tried following several tutorials, one using a filter, and several using middelware and when I'm done and test it the middleware never gets called when I throw an exception thusly.
I have a filter already (which is commented out for testing the middle ware in case they were interacting) which does work, but can't use IOC to load an instanve of ILogger
[HttpGet]
[Route( "/ThrowException" )]
public JqGridReturnCV ThrowException()
{
log.LogTrace( "AdStudentController::ThrowException() - in" );
throw new Exception( "This is a test Exception" );
log.LogTrace( "AdStudentController::ThrowException() - out" );
}
Here is my code for the middleware:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using log4net;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using StudentPortal4Api.Dto;
namespace StudentPortal4Api.Utilities
{
public class GlobalExceptionMiddleware
{
private readonly RequestDelegate next;
public readonly ILogger log;
public GlobalExceptionMiddleware( RequestDelegate _next, ILogger _log )
{
next = _next;
log = _log;
}
public async Task Invoke( HttpContext context )
{
try
{
await next( context );
}
catch ( Exception ex )
{
var response = context.Response;
response.ContentType = "application/json";
switch ( ex )
{
default:
// unhandled error
log.Log( LogLevel.Error, " GlobalException:" + ex.ToString() );
break;
}
throw;
}
}
}
public class ErrorDetails
{
public int StatusCode { get; set; }
public string Message { get; set; }
public override string ToString()
{
return JsonConvert.SerializeObject( this );
}
}
public static class ExceptionMiddlewareExtensions
{
public static void ConfigureExceptionHandler( this IApplicationBuilder app, ILogger logger )
{
app.UseExceptionHandler( appError =>
{
appError.Run( async context =>
{
context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
context.Response.ContentType = "application/json";
var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
if ( contextFeature != null )
{
logger.LogError( $"Something went wrong: {contextFeature.Error}" );
await context.Response.WriteAsync( new ErrorDetails()
{
StatusCode = context.Response.StatusCode,
Message = "Internal Server Error."
}.ToString() );
}
} );
} );
}
}
}
and here is my configure method , where I suspect I'm doing something wrong in registering it
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure( IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory, ILogger log )
{
//code removed for clarity
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
//Code removed for clarity
// global error handler
app.UseMiddleware<GlobalExceptionMiddleware>();
app.ConfigureExceptionHandler( log );
}
}
}
anyone see what I'm doing wrong?
You have a order issue source
change to:
app.UseAuthorization();
// global error handler
app.UseMiddleware<GlobalExceptionMiddleware>(); //custom Middleware Must be before endpoints and after auth.
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
You will not need the app.UseExceptionHandler.
I created a self hosted web api app, to run as a Windows Service, using TopShelf, and Autofac for dependency injection.
Here is my StartUp logic:
public class ApiShell : IApiShell
{
public void Start()
{
using (WebApp.Start<Startup>("http://localhost:9090"))
{
Console.WriteLine($"Web server running at 'http://localhost:9090'");
}
}
internal class Startup
{
//Configure Web API for Self-Host
public void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration();
GlobalConfiguration.Configuration
.EnableSwagger(c => c.SingleApiVersion("v1", "Swagger UI"))
.EnableSwaggerUi();
//default route
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional });
app.UseWebApi(config);
}
}
}
And I start the WebApp as follow:
public class HostService
{
//when windows service statrts
public void Start()
{
IoC.Container.Resolve<IApiShell>().Start(); //start web app
IoC.Container.Resolve<IActorSystemShell>().Start();
}
//when windows service stops
public void Stop()
{
IoC.Container.Resolve<IActorSystemShell>().Stop();
}
}
TopShelf configuration:
HostFactory.Run(x =>
{
x.Service<HostService>(s =>
{
s.ConstructUsing(name => new HostService());
s.WhenStarted(sn => sn.Start());
s.WhenStopped(sn => sn.Stop());
});
x.RunAsLocalSystem();
x.SetDescription("Sample Service");
x.SetDisplayName("Sample Service");
x.SetServiceName("Sample Service");
});
My controller:
public class PingController : ApiController
{
private IActorSystemShell _actorSystem;
public PingController(IActorSystemShell actorSystem)
{
_actorSystem = actorSystem;
}
[HttpGet]
public async Task<string> Ping()
{
var response = await _actorSystem.PingActor.Ask<PingMessages.Pong>(PingMessages.Ping.Instance(),
TimeSpan.FromSeconds(10));
return response.PongMessage;
}
}
I installed Swagger as well, but I can't reach my controller, using either of the following attempts:
http://localhost:9090/api/Ping
http://localhost:9090/swagger
What am I missing?
You can't just do this:
using (WebApp.Start<Startup>("http://localhost:9090"))
{
Console.WriteLine($"Web server running at 'http://localhost:9090'");
}
After the write line, there's no more statements left in the using, so the using will close, thus stopping the web app. This is one of those cases where even though the result of WebApp.Start is an IDisposable, you shouldn't use a using statement. Instead, do this:
public class ApiShell : IApiShell
{
_IDisposable _webApp;
public void Start()
{
_webApp = WebApp.Start<Startup>("http://localhost:9090");
Console.WriteLine($"Web server running at 'http://localhost:9090'");
}
public void Stop()
{
_webApp.Dispose();
}
}
public class HostService
{
public void Start()
{
IoC.Container.Resolve<IApiShell>().Start(); //start web app
}
public void Stop()
{
IoC.Container.Resolve<IApiShell>().Stop(); //stop web app
}
}
You haven't shown your dependency registration, but make sure that IApiShell is registered as a singleton so you're starting/stopping the same instance.
Note, if this were a traditional console app instead of a Windows service, you could do this:
using (WebApp.Start<Startup>("http://localhost:9090"))
{
Console.WriteLine($"Web server running at 'http://localhost:9090'");
Console.WriteLine("Press any key to exit.");
Console.ReadKey(true);
}
The ReadKey method would keep the using statement active and thus keep the web app from disposing.
I'm developping a web API with ASP.NET Core and I'm trying to implement a custom error handling middleware so I can throw standard exceptions that can be converted into a JSON response with the appropriate HTTP Status code.
For example if I do:
throw new NotFoundApiException("The object was not found");
I need it to be converted into:
StatusCode: 404
ContentType: application/json
ResponseBody: {"error": "The object was not found"}
Here is my middleware:
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
try {
await next(context);
} catch (ApiException ex) {
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, ApiException exception)
{
var result = JsonConvert.SerializeObject(new { error = exception.Message });
context.Response.ContentType = "application/json";
context.Response.StatusCode = exception.httpStatusCode;
return context.Response.WriteAsync(result);
}
}
Exceptions
public class ApiException : System.Exception
{
private int _httpStatusCode = (int)HttpStatusCode.InternalServerError;
public ApiException() { }
public ApiException(string message): base(message) { }
public int httpStatusCode {
get { return this._httpStatusCode; }
}
}
public class NotFoundApiException : ApiException
{
private int _httpStatusCode = (int)HttpStatusCode.BadRequest;
public NotFoundApiException() { }
public NotFoundApiException(string message): base(message) { }
}
Startup
public void Configure(/*...*/)
{
loggerFactory.AddConsole();
app.UseMiddleware<ErrorHandlingMiddleware>();
app.UseMvc();
}
Controller action
[HttpGet("object/{guid}")]
public WebMessage Get(Guid guid)
{
throw new NotFoundApiException(string.Format("The object {0} was not found", guid));
//...
I can see the request entering my registered middleware but the exception is not catched and simply thrown as usual.
I'm suspecting a race condition or something similar, I don't know very much about them async functions actually.
Has someone got an idea why my exception is not catched ?
edit By continuing the execution with VisualStudio I can see the expected behavior: I'm finally getting my response.
Seems like the Exception is not really catched by the middleware but somehow processed afterwards.
My solution to this problem was to remove app.UseDeveloperExceptionPage(); in Startup.cs
In my case, I found that app.UseMiddleware<ExceptionHandlingMiddleware>(); should be at the top of Configure() method.
You can try also Exception filters.
(of course, filters are not so flexible like as error handling middleware, which is better in general case, but - at least for me - filters are working fine without any issues)
That's what I'm using:
public class ExceptionGlobalFilter : ExceptionFilterAttribute
{
private readonly ILogger logger;
public ExceptionGlobalFilter(ILoggerFactory lf)
{
logger = lf.CreateLogger("ExceptionGlobalFilter");
}
public override void OnException(ExceptionContext context)
{
var customObject = new CustomObject(context.Exception);
//TODO: Add logs
if (context.Exception is BadRequestException)
{
context.Result = new BadRequestObjectResult(customObject);
}
else if (context.Exception is NotFoundException)
{
context.Result = new NotFoundObjectResult(customObject);
}
else
{
context.Result = new OkObjectResult(customObject);
}
base.OnException(context);
}
public override async Task OnExceptionAsync(ExceptionContext context)
{
await base.OnExceptionAsync(context);
return;
}
}
Startup.cs:
services.AddMvc(config =>
{
config.Filters.Add(typeof(ExceptionGlobalFilter));
});
More info:
Introduction to Error Handling in ASP.NET Core
Exception filters
Filters
MVC Issue #5594
ExceptionHandlerMiddleware.cs
In my case app.UseDeveloperExceptionPage(); was written in the Startup after the exception handler middleware. The fix was simply by moving the exception handler middleware to be after it.
#Pierre, I have met the same issue here when using Middleware as the global exception handler. The issue was caused by my mistake to wrote an "async void" method, I have throwed an exception in the method named "NewException":
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public async Task<IActionResult> Get()
{
NewException();
return Ok("<h1>Hi, Welcome!</h1>");
}
private async void NewException()
{
throw new InvalidOperationException("WTF");
}
The exception [InvalidOperationException("WTF")] will not be catching by the Middleware, if I change the code snippet to :
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public async Task<IActionResult> Get()
{
await NewException();
return Ok("<h1>Hi, Welcome!</h1>");
}
private async Task NewException()
{
throw new InvalidOperationException("WTF");
}
The exception Middleware will catch it. Hope this help.
In my C# Web API, I'm trying to add a global exception handler. I've been using a custom global ExceptionFilterAttribute to handle the exception and return a HttpResponseMessage:
public override void OnException(HttpActionExecutedContext context)
{
...
const string message = "An unhandled exception was raised by the Web API.";
var httpResponseMessage = new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = new StringContent(message),
ReasonPhrase = message
};
context.Response = httpResponseMessage;
}
This has worked fine for handling exceptions thrown at the controller level.
However, during development we had an error thrown from our OWIN startup file due to a database connection issue, however, a standard IIS exception was returned, instead of going through the global exception handler, and the full HTML was returned to our API consumer.
I've tried a few different approaches to catch exceptions thrown in my OWIN startup:
Custom ApiControllerActionInvoker:
public class CustomActionInvoker : ApiControllerActionInvoker
{
public override Task<HttpResponseMessage> InvokeActionAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
var result = base.InvokeActionAsync(actionContext, cancellationToken);
if (result.Exception != null && result.Exception.GetBaseException() != null)
{
...
}
return result;
}
}
Custom ExceptionHandler:
public class CustomExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
...
base.Handle(context);
}
public override bool ShouldHandle(ExceptionHandlerContext context)
{
return true;
}
}
Custom OwinMiddleware component:
public class CustomExceptionMiddleware : OwinMiddleware
{
public CustomExceptionMiddleware(OwinMiddleware next) : base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
try
{
await Next.Invoke(context);
}
catch (Exception ex)
{
...
}
}
}
And finally just using Application_Error:
protected void Application_Error(object sender, EventArgs e)
{
...
}
But nothing seems to catch the exception.
Does anyone know of a way to catch the exception and return a HttpResponseMessage? Or if any of the approaches I've already tried should have worked?
Any help much appreciated.
I have an application that does this correctly. In my case I wrote a middleware class that always returns a message telling the caller that the service is unavailable because there was an error during startup. This class is called FailedSetupMiddleware in my solution. The outline of it looks like this:
public class FailedSetupMiddleware
{
private readonly Exception _exception;
public FailedSetupMiddleware(Exception exception)
{
_exception = exception;
}
public Task Invoke(IOwinContext context, Func<Task> next)
{
var message = ""; // construct your message here
return context.Response.WriteAsync(message);
}
}
In my Configuration class I have a try...catch block that configures the OWIN pipeline with only the FailedSetupMiddleware in the case where an exception was thrown during configuration.
My OWIN startup class looks like this:
[assembly: OwinStartup(typeof(Startup))]
public class Startup
{
public void Configuration(IAppBuilder app)
{
try
{
//
// various app.Use() statements here to configure
// OWIN middleware
//
}
catch (Exception ex)
{
app.Use(new FailedSetupMiddleware(ex).Invoke);
}
}
}
I have a custom exception filter capable handle all the errors in the controller( just a common error handling mechanism) ,
public class ExceptionHandlingAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext actionExecutedContext)
{
var error = actionExecutedContext.Exception;
if (error is BussinessExcetion)
{
var exceptionBase = (BussinessExcetion)error;
var code = (HttpStatusCode)exceptionBase.HttpExceptionCode;
throw new HttpResponseException(new HttpResponseMessage(code)
{
Content = new StringContent(exceptionBase.Message),
ReasonPhrase = "Exception"
,
});
}
// Now log the error
/* Error logging */
LoggingFactory.GetLogger().LogError(string.Format("Exception:{0} ||Stack trace:{1}", error.Message, error.StackTrace), error);
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = new StringContent("An error occurred, contact the support team."),
ReasonPhrase = "Critical Exception"
});
}
}
I registered this filter in fillterConfig file
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new ExceptionHandlingAttribute());
filters.Add(new HandleErrorAttribute());
}
but i am getting an error
The given filter instance must implement one or more of the following filter interfaces: IAuthorizationFilter, IActionFilter, IResultFilter, IExceptionFilter
I know the ExceptionFilterAttribute already implimented IExceptionFilter filter. Why i am getting this error
In order for this to work, you need to implement System.Web.Http.Filters.ExceptionFilterAttribute.
public class NotImplExceptionFilterAttribute : ExceptionFilterAttribute
{
log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public override void OnException(HttpActionExecutedContext context)
{
RequestData requestData = new RequestData(context.Request);
log.Error("NotImplExceptionFilterAttribute", context.Exception);
context.Response = new HttpResponseMessage(HttpStatusCode.NotImplemented);
}
}
Then, in your WebApiConfig.cs, register the filter:
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{count}",
defaults: new { count = RouteParameter.Optional }
);
config.Filters.Add(new NotImplExceptionFilterAttribute());
}
And for anyone using NLog:
public class NlogExceptionFilter : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext filterContext)
{
Exception lastException = filterContext.Exception;
Logger logger = LogManager.GetLogger("");
logger.Fatal(lastException.Message, lastException);
}
}
And in the WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new NlogExceptionFilter());
...
}
make sure you also override the Async version of the filter
class myFilter : ExceptionFilterAttribute
{
...
public override async Task OnExceptionAsync(HttpActionExecutedContext ctx,
CancellationToken cancellationToken)
{
...
}
...
}
Doh! If you're like me, and were handling the exception in your controller, then you would not be triggering the filter. Inside your catch block, place a throw. Voila!