I put external in brackets, while it is not completely true. Here's the explanation:
I've got a MVC ASP.NET application (one solution) that consists of the following projects:
- BusinessLogic
- WebApi (pure WebApi2 controllers, returning json-serialized data based on (1))
- Frontend (MVC, Razor views, etc)
Frontend depends on webapi project and Frontend's RouteConfig registers routes for itself and for webapi project:
//register frontend routes, default code
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
//calls webapi project to register its routes:
//var config = GlobalConfiguration.Configuration;
//... call webApi project
config.Routes.MapHttpRoute(
name: "AppDefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "AppCustomApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
In this configuration I can deploy and run just the Frontend and access functionality as following:
- http://server/application/someview -- to access frontend
- http://server/application/api/someapimethod -- to access api (Here is the question I asked here: ASP.NET MVC, Web API: working in multiple deployments )
Now I've got an issue registering exceptions happened in WebApi project. Error and Exception handling works perfectly in FrontEndProject:
- If there is non-handled exception in code (throw new ArgumentException("hello, there"))
- If there is http error if I access just wrong address http://server/application/somefakeaddress
- If there is problem, for example, parsing parameter http://server/application/someview?parameter=unparseable_parameter
These exceptions and errors I can catch via various ways (currently I utilize custom implementation of HttpModule)
Now, here is an issue. Exception and error handling is not working for calls to WebApi project. (http://server/application/api/...). In browser I receive correct reply, for example trying to call non-existent method results in: In debug output:
AuditModule: 2016-05-04 14:15:45,201 [56] INFO AuditModule [(null)] - Request::begin
iisexpress.exe Information: 0 : Request, Method=GET, Url=http://localhost:52258/api/controller_name/fake_method, Message='http://localhost:52258/api/controller_name/fake_method'
iisexpress.exe Information: 0 : Message='controller_name', Operation=DefaultHttpControllerSelector.SelectController
iisexpress.exe Information: 0 : Message='Application.Controllers.controller_nameController', Operation=DefaultHttpControllerActivator.Create
iisexpress.exe Information: 0 : Message='Application.Controllers.controller_nameController', Operation=HttpControllerDescriptor.CreateController
iisexpress.exe Information: 0 : Message='Will use same 'JsonMediaTypeFormatter' formatter', Operation=JsonMediaTypeFormatter.GetPerRequestFormatterInstance
iisexpress.exe Information: 0 : Message='Selected formatter='JsonMediaTypeFormatter', content-type='application/json; charset=utf-8'', Operation=DefaultContentNegotiator.Negotiate
iisexpress.exe Warning: 0 : Message='UserMessage='No HTTP resource was found that matches the request URI 'http://localhost:52258/api/controller_name/fake_method'.', MessageDetail='No action was found on the controller 'controller_name' that matches the request.'', Operation=ApiControllerActionSelector.SelectAction, Status=404 (NotFound), Exception=System.Web.Http.HttpResponseException: Processing of the HTTP request resulted in an exception. Please see the HTTP response returned by the 'Response' property of this exception for deta
at System.Web.Http.Controllers.ApiControllerActionSelector.ActionSelectorCacheItem.SelectAction(HttpControllerContext controllerContext)
at System.Web.Http.Controllers.ApiControllerActionSelector.SelectAction(HttpControllerContext controllerContext)
at System.Web.Http.Tracing.Tracers.HttpActionSelectorTracer.<>c__DisplayClass2.<System.Web.Http.Controllers.IHttpActionSelector.SelectAction>b__0()
at System.Web.Http.Tracing.ITraceWriterExtensions.TraceBeginEnd(ITraceWriter traceWriter, HttpRequestMessage request, String category, TraceLevel level, String operatorName, String operationName, Action`1 beginTrace, Action execute, Action`1 endTrace, Action`1 errorTrace)
iisexpress.exe Warning: 0 : Message='UserMessage='No HTTP resource was found that matches the request URI 'http://localhost:52258/api/controller_name/fake_method'.', MessageDetail='No action was found on the controller 'controller_name' that matches the request.'', Operation=controller_nameController.ExecuteAsync, Status=404 (NotFound), Exception=System.Web.Http.HttpResponseException: Processing of the HTTP request resulted in an exception. Please see the HTTP response returned by the 'Response' property of this exception for deta
at System.Web.Http.Controllers.ApiControllerActionSelector.ActionSelectorCacheItem.SelectAction(HttpControllerContext controllerContext)
at System.Web.Http.Controllers.ApiControllerActionSelector.SelectAction(HttpControllerContext controllerContext)
at System.Web.Http.Tracing.Tracers.HttpActionSelectorTracer.<>c__DisplayClass2.<System.Web.Http.Controllers.IHttpActionSelector.SelectAction>b__0()
at System.Web.Http.Tracing.ITraceWriterExtensions.TraceBeginEnd(ITraceWriter traceWriter, HttpRequestMessage request, String category, TraceLevel level, String operatorName, String operationName, Action`1 beginTrace, Action execute, Action`1 endTrace, Action`1 errorTrace)
at System.Web.Http.Tracing.Tracers.HttpActionSelectorTracer.System.Web.Http.Controllers.IHttpActionSelector.SelectAction(HttpControllerContext controllerContext)
at System.Web.Http.ApiController.ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)
at System.Web.Http.Tracing.Tracers.HttpControllerTracer.<ExecuteAsyncCore>d__5.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at System.Web.Http.Tracing.ITraceWriterExtensions.<TraceBeginEndAsyncCore>d__18`1.MoveNext()
iisexpress.exe Information: 0 : Response, Status=404 (NotFound), Method=GET, Url=http://localhost:52258/api/controller_name/fake_method, Message='Content-type='application/json; charset=utf-8', content-length=unknown'
iisexpress.exe Information: 0 : Operation=JsonMediaTypeFormatter.WriteToStreamAsync
iisexpress.exe Information: 0 : Operation=controller_nameController.Dispose
AuditModule: 2016-05-04 14:15:45,236 [56] INFO AuditModule [(null)] - Request::end
The thread 0x6398 has exited with code 0 (0x0).
and in browser (with correct http response code, 404 in this case):
{
"Message": "No HTTP resource was found that matches the request URI 'http://localhost:52258/api/controller_name/fake_method'.",
"MessageDetail": "No action was found on the controller 'controller_name' that matches the request."
}
I tried:
- Same instance of IHttpModule implementation does not catch; it catches BeginRequest and EndRequest, but not exceptions (neither in-code, nor http)
- Exception filters and handlers do not catch whenever technique I'm using to register filters
- Replacing in services via config.Services.Replace()
The initial problem is coming from requirement to leave fine audit trail of everything happened in application -- all successful and failed requests that happened both in frontend and in API. I'm using Log4net to log these (and other) events.
Hence is my question -- can anyone suggest another way to register all exceptions (in-code, http) in this configuration or point out, where the problem might come from?
Thanks!
upd 1: I am able to catch in-code (when I throw an exception in a WebApi method) via standard: GlobalConfiguration.Configuration.Filters.Add(new CustomExceptionFilterAttribute());
upd 2: If I do the following:
public class MyExceptionFilter : IExceptionFilter {...}
GlobalConfiguration.Configuration.Filters.Add(new MyExceptionFilter ());
Then MyExceptionFilter is not called. However, impl of ExceptionFilterAttribute gets called.
Then what I'm left with is how to catch http (wrong path into WebApi) and framework (unparseable parameters and so) errors and exceptions.
upd3: Solved. Here is steps of my solution. Unfortunately, solution is a bit fragile and requires certain sequence of init steps, not too error prone:
In Application_Start, before registering any routes:
GlobalConfiguration.Configuration.Services.Add(typeof(IExceptionLogger), new AuditExceptionLogger());
Register Frontend routes
- Register WebApi routes
Register 404 handler and create Error controller
RouteTable.Routes.MapHttpRoute( name: "Error404", routeTemplate: "{*url}", defaults: new { controller = "Error", action = "Handle404" } );
Implement DefaultHttpControllerSelector and ApiControllerActionSelector, catching exceptions there and replace by
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector), new HttpNotFoundAwareDefaultHttpControllerSelector(GlobalConfiguration.Configuration)); GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpActionSelector), new HttpNotFoundAwareControllerActionSelector());
And I have not yet designed CustomError handling -- I want to show proper error page to invalid Frontend requests