How to handle Custom Errors With .Net Core 2.2 MVC - asp.net-core-mvc

I saw on youtube how to handle custom errors but it was with web.config and in dotnet core 2.2 it does not have this file or I'm not finding it through visual studio 2019.

ASP.NET Core doesn't use Web.config, unless you're hosting in IIS, and then only for minimal IIS module configuration. Custom error handling is done via middleware configuration in your Startup.Configure method. This is actually covered in the default project template, though, so it's odd that you don't have something included by default to at least work from. Regardless, you're looking at something like:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
}
That will generally route any global uncaught exception to a general /Error endpoint, which could be either a controller action or Razor Page. More likely than not, you'll want a little more flexibility, and you will also want to not expose an actual "error" URL in the browser, so you'll probably sub UseExceptionHandler with UseStatusCodePagesWithReExecute:
app.UseStatusCodePagesWithReExecute("/StatusCode","?code={0}");
That will keep the URL, without redirecting and load up a /StatusCode endpoint while passing the specific status code (404, 400, 500, etc.), allowing you to return custom messaging per error type.
All of this and more is in the documentation.

Related

Azure AD in ASP.NET Core MVC web application causes CORB for JavaScript requests

Integration of Azure AD into a ASP.NET Core MVC web application causes Cross-Origin Read Blocking for requests made by JavaScript frontend to ASP.NET controllers.
I am writing an ASP.NET Core MVC application that requires users to login using a Microsoft work account. This works. I added the following code to Startup.cs:
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddAzureAd(options => Configuration.Bind("AzureAd", options))
.AddCookie(options =>
{
options.LoginPath = "/Account/SignIn/";
options.AccessDeniedPath = "/Account/AccessDenied/";
});
I am also using Telerik UI widgets for the applications user interface. Because of this, there are multiple places where I use JavaScript to make requests to my ASP.NET controllers. Ex:
function onEvent(e) {
$.post("Controller/Foo", function (data) {
...
});
}
This works great when I'm running and debugging locally using IIS Express but when I deploy the application to our server running IIS I start getting warnings about Cross-Origin Read Blocking in my browsers development tools and none of my javascript functions that make requests to my ASP.NET controllers receive data.
Here is a screenshot of the warnings:
If anyone happens to know how to approach this problem I would be very grateful; I'm new to all of this and I have no idea where to start with this particular problem.
My current thinking is either I need to figure out how to handle the 'Access-Control-Allow-Origin' header in javascript or that there is something that needs to be done in Azure.
The first thing I did was enable CORS in my application but that just allows other domains to make cross-origin requests from my application, which isn't what's happening here.
If anyone comes across this issue with CORB in Chrome and dotnet core please note that in my case I was using the ResponseCache attribute. On the first non cached request it worked and subsequent ones failed with CORB error.
Once I removed the attribute the error went away.
This does not solve the issue but gives one some direction as to where the issue might be.
I did not bother with the response cache as it's no longer needed.
Below was the offending code
[ResponseCache(Duration = 15, Location = ResponseCacheLocation.Any, VaryByQueryKeys = new[] {"key", "type", "identifier", "app"})]

Exceptions in ASP.NET Web API custom exception handler never reach top level when CORS is enabled

I have created a custom Web API global exception handler like this:
public class MyGlobalExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
// here I handle them all, no matter sync or not
}
public override Task HandleAsync(ExceptionHandlerContext context,
CancellationToken cancellationToken)
{
// not needed, but I left it to debug and find out why it never reaches Handle() method
return base.HandleAsync(context, cancellationToken);
}
public override bool ShouldHandle(ExceptionHandlerContext context)
{
// not needed, but I left it to debug and find out why it never reaches Handle() method
return context.CatchBlock.IsTopLevel;
}
}
I'm registering it in my Global.asax Application_Start:
GlobalConfiguration.Configuration.Services.Replace(typeof(IExceptionHandler),
new MyGlobalExceptionHandler());
Everything worked fine, no matter where I threw exceptions - inside controller methods or in my custom attributes above controller methods, and no matter if I call it from AJAX requests or directly from browser.
But then one day I needed CORS support for my AJAX requests. I enabled CORS globally as described in the article Enabling Cross-Origin Requests in ASP.NET Web API
var cors = new EnableCorsAttribute("*", "*", "*"); // * is just for debugging purpose
config.EnableCors(cors);
At first everything seemed OK, CORS worked as expected, the server responded to OPTIONS request.
But the problems started when I wanted to check my authentication. Suddenly the exception was swallowed and I got an empty JSON {} response instead of my custom JSON formatted exception which I create in the Handle() method of my MyGlobalExceptionHandler.
While debugging, I was surprised to find that now for AJAX requests ShouldHandle() method is called only with IsTopLevel = false, thus the exception never bubbles up and never reaches my Handle() method. As soon as I disable CORS, everything works fine again (except cross-domain requests, of course).
Why IsTopLevel is never true when I enable CORS? How should I fix this?
One more side effect is as follows. If CORS is disabled, then if I throw any exception inside Handle() method, it reaches the Application_Error handler in Global.asax. But if I enable CORS and throw exceptions in my handler methods, these exceptions never reach Application_Error.
UPDATED WITH MORE DETAILS:
It seems, I found exactly when this is happening.
If I throw an exception in a controller method when CORS is enabled, then CORS doesn't kick in at all and does not send the Access-Control-Allow-Origin header. When the browser doesn't receive the header, it immediately disrupts the request and this disruption seems to affect also the exception handler - it never reaches ShouldHandle() method with IsTopLevel = true.
Chrome and Mozilla act like this even when I run AJAX request from a local html file to my websiet on IIS Express localhost.
But the situation is different on IE 11. When I open the html file there, it first asks me permissions to enable scripts. After I agree, then IE 11 ignores the fact that there are no CORS headers present and it doesn't disrupt the request, thus my exception handler receives IsTopLevel = true and is able to return a customised error response.
I guess, this should be fixed in the Web API core - even if I throw exceptions, CORS should still be able to kick in and send its headers, so the browser accepts the response. I have created a minimal test case application and I'll send it to the ASP.NET Team on CodePlex.
Link to the test project. (the project zip file will be marked, just click Download and ignore all the other files in that folder)
I have found the source of confusion.
It seems, WebAPI by default is using this exception handler:
https://aspnetwebstack.codeplex.com/SourceControl/latest#src/System.Web.Http/ExceptionHandling/DefaultExceptionHandler.cs
and it has major differences from the suggested exception handling in this article:
http://www.asp.net/web-api/overview/web-api-routing-and-actions/web-api-global-error-handling
see chapter "Appendix: Base Class Details", where the code of default exception base class is given.
In the example it checks for IsOutermostCatchBlock (which now seems to be moved to exceptionContext.CatchBlock.IsTopLevel) to see if it's time to handle the exception. When CORS is enabled, such approach won't work for the reasons I described above. ASP.NET team said, this behavior is by design and they won't change anything.
I hope someone experienced will write an up-to-date article with instructions for doing exception handling the right way with and without CORS.
Currently I see two ways to work around this in my code:
don't inherit my custom exception handler from System.Web.Http.ExceptionHandling.ExceptionHandler but implement IExceptionHandler directly
if inheriting from System.Web.Http.ExceptionHandling.ExceptionHandler, override
ShouldHandle method and return true always because CatchBlock.IsTopLevel might never have true value.
I tried both apporaches and they seem to work fine.

Any reason this WebAPI call works in dev but will not work on Win2012? "Unable to locate a controller for ..." error

I am stuck trying to find the reason this happens. I have a WebAPI service in my module. A specific call to a public entry point (the default path) works fine on my dev machine (Win7, IIS 7.5) but doesn't on the production server (Win2012, IIS8). The DNN installations are clones. Here is the call:
/DesktopModules/DNNEurope/LocalizationEditor/API?tabid=1&moduleid=4
The Win2012 installation replies with "Unable to locate a controller for ..." and then naming the path and namespace this controller is in. Note the routing should work just fine as it works fine in dev.
There is one quirk to note, here. I'm supplying the tabid and moduleid through the querystring, rather than through headers as is the practice when you're doing json exchanges. That is because this call is supposed to be consumed elsewhere. Again, keep in mind this works fine locally.
The route definition is:
mapRouteManager.MapHttpRoute("DNNEurope/LocalizationEditor", "Default", "", New With {.Controller = "Localization", .Action = "ListObjects"}, New String() {"DNNEurope.Modules.LocalizationEditor.Services"})
and the method:
<HttpGet()>
<AllowAnonymous()>
Public Function ListObjects() As HttpResponseMessage
Any ideas?
Kiran's remark put me on the right track. In the project there was a reference to the Mvc library that was not on the server. Strangely enough for the asp.net application this is not a problem but for WebAPI this is. Even though any of the methods in the Mvc library were no longer used as I switched to WebAPI at one point during the project. So locally this Mvc library was still found, but not in production.

Introducing MVC into ASP.Net - 404 error

I have an existing ASP.Net Application, into which I am attempting to introduce MVC2.
Hopefully I can remember the steps, but what I did was the following:
Created a dummy MVC2 project.
Compared and merged .csproj, resulting in the Add Item commands showing MVC2 items.
Compared and merged the web.config
Compared and merged the global.asax.cs
Added Models, Views and Controllers directories
Added HostController with Index action and Index.aspx (no logic)
Amended route to make /Host/Index the default
So now, when I access the application via the root address http://localhost/MyApp it all works.
But, when I access http://localhost/MyApp/Host/Index I get a 404 error. I get the same result for any of the Controller/Actions I created. The only way I can get them to appear is to use the defaults in the routing configuration. I installed Phill Haack's route debugger and it's doing nothing. Obviously there's some problem with my routing, but I can't figure it out.
Any ideas what I've missed?
Bah... turns out that this is to do with IIS 5.1 and MVC routing.
I resolved it by using the following Routes in my application (note the .aspx extensions).
routes.MapRoute("Root", "", new { controller = "Host", action = "Index" });
routes.MapRoute("Default", "{controller}/{action}.aspx", new { controller = "Host", action = "Index" });
Means I can't have clean routes, but at least it works.

Http Modules are called on every request when using mvc/routing module

I am developing a http module that hooks into the FormsAuthentication Module through the Authenticate event.
While debugging i noticed that the module (and all other modules registered) gets hit every single time the client requests a resource (also when it requests images, stylesheets, javascript files (etc.)).
This happens both when running on a IIS 7 server in integrated pipeline mode, and debugging through the webdev server (in non- integrated pipeline mode)
As i am developing a website with a lot images which usually wont be cached by the client browser it will hit the modules a lot of unnessecary times.
I am using MVC and its routing mechanishm (System.Web.Routing.UrlRoutingModule).
When creating a new website the runAllManagedModulesForAllRequests attribute for the IIS 7 (system.webServer) section is per default set to true in the web.config, which as the name indicates make it call all modules for every single request.
If i set the runAllManagedModulesForAllRequests attribute to false, no modules will get called.
It seems that the reason for this is because of the routing module or mvc (dont know excactly why), which causes that the asp.net (aspx) handler never gets called and therefore the events and the modules never gets called (one time only like supposed).
I tested this by trying to call "mydomain.com/Default.aspx" instead of just "mydomain.com/" and correctly it calls the modules only once like it is supposed.
How do i fix this so it only calls the modules once when the page is requested and not also when all other resources are requested ?
Is there some way i can register that all requests should fire the asp.net (aspx) handler, except requests for specific filetype extensions ?
Of course that wont fix the problem if i choose to go with urls like /content/images/myimage123 for the images (without the extension). But i cant think of any other way to fix it.
Is there a better way to solve this problem ?
I have tried to set up an ignoreRoute like this routes.IgnoreRoute("content/{*pathInfo}"); where the content folder contains all the images, javascripts and stylesheets in seperat subfolders, but it doesnt seem to change anything.
I can see there a many different possibilites when setting up a handler but I cant seem to figure out how it should be possible to setup one that will make it possible to use the routing module and have urls like /blog/post123 and not call the modules when requesting images, javascripts and stylesheets (etc.).
Hope anyone out there can help me ?
Martin
The problem seems to be the routing module.
The solution is to move images, css, js to a subdomain, or you can probably register which filetypes/extensions the routing module should ignore.
The following code is what I use in every MVC Application in order to avoid the overhead caused by the routing system on serving static files, javascript, css, etc:
public static void RegisterRoutes(RouteCollection routes)
{
routes.RouteExistingFiles = false;
routes.LowercaseUrls = true;
routes.AppendTrailingSlash = true;
routes.IgnoreRoute("Content/{*pathInfo}");
routes.IgnoreRoute("Scripts/{*pathInfo}");
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("{*favicon}", new { favicon = #"(.*/)?favicon.ico(/.*)?" });
/* ... */
}

Resources