Asp show error

I have a fairly simple website that I am playing with using ASP.NET Core. I am running the application from the command line and the website is returning static files but I keep getting 500 error...

Update for beta8:
In beta8 Microsoft changed the name to UseDeveloperExceptionPage. So if you want to use the ErrorPage, call:

app.UseDeveloperExceptionPage();

Here is the link to the related Github issue.
The ErrorPageOptions are the same as in beta6/7.


You can use

app.UseErrorPage(ErrorPageOptions.ShowAll)

until beta5 of Asp.Net Mvc.


As of beta6, ErrorPageOptions.ShowAll has been removed. You can now use the version without parameters

app.UseErrorPage();

or create an ErrorPageOptions object and specify how many lines around the error you want to display by setting SourceCodeLineCount.

app.UseErrorPage(new ErrorPageOptions() {SourceCodeLineCount = 100});

Additional Information

They removed multiple properties of ErrorPageOptions in this commit.

Before:

public class ErrorPageOptions
{
    private bool _defaultVisibility;

    private bool? _showExceptionDetails;
    private bool? _showSourceCode;
    private bool? _showQuery;
    private bool? _showCookies;
    private bool? _showHeaders;
    private bool? _showEnvironment;
    ...
}

After:

public class ErrorPageOptions
{
    public int SourceCodeLineCount { get; set; }
    public IFileProvider FileProvider { get; set; }
    ...
}

So now you can only set how many lines of source code are printed.

Your web application is throwing some sort of error or invalid response. Do you know how to troubleshoot IIS or ASP.NET errors on your servers? Luckily, Windows and ASP.NET provide several different logs where failed requests are logged beyond simple IIS logs.

Get to Know the 4 Different IIS Logs

You are probably familiar with normal IIS logs, but there are some other places to look if you are looking for more detailed error messages or can’t find anything in your IIS log file.

1. Standard IIS Logs

Standard IIS logs will include every single web request that flows through your IIS site. Via IIS Manager, you can verify that your IIS logs are enabled and where they are being written to.

iis logs settings

You should find your logs in folders that are named by your W3SVC site ID numbers.

By default, each logged request in your IIS log will include several key fields including the URL, querystring, and error codes via the status, substatus, and win32 status. These status codes can help identify the actual error in more detail.

#Fields: date time s-ip cs-method cs-uri-stem cs-uri-query s-port cs-username c-ip cs(User-Agent) cs(Referer) sc-status sc-substatus sc-win32-status time-taken 2016-09-13 21:45:10 ::1 GET /webapp2 - 80 - ::1 Mozilla/5.0 - 500 0 0 5502 2016-09-13 21:45:10 ::1 GET /favicon.ico - 80 - ::1 Mozilla/5.0 http://localhost/webapp2 404 0 2 4 

The “sc-status” and “sc-substatus” fields are the standard HTTP status code of 200 for OK, 404, 500 for errors, etc.

The “sc-win32-status” can provide more details that you won’t know unless you lookup the code. They are basic Win32 error codes.

By the way, if you are using Retrace, you can also use it to query across all of your IIS logs as part of it’s built in log management functionality.

2. HTTPERR Is Your IIS Error Log

Every single web request should show in your IIS log. If it doesn’t, it is possible that the request never made it to IIS, or IIS wasn’t running. Also, be sure that your IIS logging is enabled.

Incoming requests to your server first route through HTTP.SYS before being handed to IIS. These type of errors get logged in HTTPERR. Common errors are 400 Bad Request, timeouts, 503 Service Unavailable, and similar types of issues. The built-in error messages and error codes from HTTP.SYS are usually very detailed.

3. Look for ASP.NET Exceptions in Windows Event Viewer

By default, ASP.NET will log unhandled 500 level exceptions to the Windows Application EventLog. This is handled by the ASP.NET Health Monitoring feature. You can control settings for it via system.web/healthMonitoring in your web.config file.

Very few people realize that the number of errors written to the Application EventLog is rate limited. So, you may not find your error! By default, it will only log the same type of error once a minute. You can also disable writing any errors to the Application EventLog.

iis error logs in eventlog

Can’t find your exception? Depending on if you are using WebForms, MVC, Web API, WCF, or other frameworks, you may have issues with ASP.NET not writing any errors at all to ASP.NET due to compatibility issues with the health monitoring feature.

4. Failed Request Tracing for Advanced IIS Error Logs

Failed request tracing (FRT) is probably one of the least used features in IIS. It provides robust IIS logging and works as a great IIS error log. FRT is enabled in IIS Manager and can be configured via rules for all requests, slow requests, or just certain response status codes.

The only problem with FRT is it is insanely detailed. It tracks every detail and every step of the IIS pipeline. You can spend a lot of time trying to decipher a single request.

5. Make ASP.NET Show the Full Exception — Temporarily

If you can reproduce the problem, you could modify your ASP.NET web.config to see exceptions. Typically server side exceptions are disabled from being visible within your application for important security reasons. Instead, you will see a yellow screen of death (YSOD) or your own custom error page. You can modify your application config files to make exceptions visible.

asp net error ysod

ASP.NET

You could use remote desktop to access the server and set customErrors to “RemoteOnly” in your web.config so you can see the full exception via “localhost” on the server. This would ensure that no users would see the full exceptions but you would be able to.

If you are OK with the fact that your users may now see a full exception page, you could set customErrors to “Off.”

.NET Core

With .NET Core, how error handling works has been completely changed from previous versions of ASP.NET. You now need to use the DeveloperExceptionPage in your middleware.

6. Using a .NET Profiler to Find ASP.NET Exceptions

.NET Profilers like Prefix (which is free!) can collect every single exception that is thrown in your code even if they are hidden in your code. Prefix is a free ASP.NET profiler that is designed to run on your workstation to help you optimize your code as you write it. Prefix can also show you your SQL queries, HTTP calls, and much, much more.

profiled asp.net iis error log

Get Proactive About Tracking Application Errors

Trying to reproduce an error in production or chasing down IIS logs/IIS error logs is not fun. Odds are, there are probably many more errors going on that you aren’t even aware of. When a customer contacts you and says your site is throwing errors, you better have an easy to see them! Tracking application errors is one of the most important things every development team should do.

You’re sending an HTTP request to your ASP.NET server, and out of the blue, it returns a 500 error. Now what? The error code doesn’t say too much by itself. Did a database request fail? What was the exact database query? Was there an exception? What kind of exception? On which line of code did it happen? And what were the local variables? Wouldn’t it be great if you could see the failure as if you’re breaking on an exception while debugging in Visual Studio?

In this article, we’re going to see how to get the most information about the failed request. This includes the error code, the type of failure, the method where the exception occurred, the exception type, exception stack trace, local variables, and the exact line of code where the exception happened. Not all failed requests are caused by an exception in code, but 500 errors do. We’ll focus mostly on failures caused by exceptions.

Table of Contents:

  • Web request error example
  • Dealing with failures in Azure App Service vs a Machine with IIS
  • Using Fiddler and Postman to debug
  • Use development exception pages to see the exception
  • Middleware to capture request failures
  • See Failures in native ASP.NET logs
  • Use Error Monitoring tools like Raygun and Application Insights
  • Break on exceptions with Cloud Explorer
  • Use Microsoft’s Snapshot Collector
  • Capture Dumps with ProcDump
  • OzCode’s Production Debugger
  • Summary

Web Request Error Example

Let’s say you’re building a simple ASP.NET Web API application and you’ve got the following controller:

public class CalculatorController : Controller
{
    [HttpGet]
    public int Divide(int a, int b)
    {
        var result = DivideInternal(a, b);
        return result;
    }
    
    private int DivideInternal(int a, int b)
    {
        return a/ b;
    }
}

When you call GET https://localhost:5000/Calculator/Divide?a=6&b=2 you’ll get the result 3. Makes sense. But what happens when you call GET https://localhost:5000/Calculator/Divide?a=8&b=0? In this case, a DivideByZeroException will be thrown and the server will return a 500 error.

In this simple case, it’s obvious what happened. But usually, you’ll need to investigate and find the root cause of the problem. This article will show many techniques to get the most information from the failure and find the core issue.

Dealing with failures in Azure App Service vs a Machine with IIS

In Azure, there are 2 ways to deploy an ASP.NET application: On a Virtual Machine with IIS or on an Azure App Service. In AWS, or another cloud provider, a virtual machine is the only option.

An App Service is a managed virtual machine. That means Azure takes care of everything: OS updates, security issues, environment settings, SDK updates, and so on. The tradeoff is that you’re much less flexible. A dedicated virtual machine means you can do whatever you want. If you want to run a Mongo DB database instance, you can. If you want to run a PHP server, not a problem.

The big difference for us is the ability to run IIS Manager (InetMgr) and to Remote-Desktop (RDP) to the machine. With an Azure App Service, you can’t do either. There was an option to install an InetMgr client and connect remotely. But it’s not supported anymore after App Services moved to Windows Server 2016.

You have more options in a virtual machine, but that doesn’t mean an App Service can’t be debugged. We have quite a lot of methods to detect and investigate failures in an App Service as well, as you’re about to see.

One useful feature of having the ability to RDP to a virtual machine is the ability to run Windows Event Viewer. Windows has a built-in logging system called Windows Event Log. Whenever there’s a failed request, it’s logged automatically. To inspect it, open Event Viewer by typing “Event Viewer” in the start menu or using the command eventvwr from the command prompt. Here’s how you’ll see the DivideByZeroException error from before:

Event Viewer showing failed ASP.NET request

Using Fiddler and Postman to debug

Before diving deep into various techniques to get failure details, let’s talk about a couple of useful tools to debug web requests. One of them is Fiddler and the other one is Postman.

Fiddler allows you to monitor all HTTP requests on a machine. You can see the request URL, headers, query parameters, body, and so on. You can also replay requests and manipulate them. This is very useful to debug and reproduce request errors. Instead of re-running whatever scenario you need to send the same request, if it’s even possible, you can just replay it with Fiddler. This gives you the time to set up debugging tools on the server. Or to check that the bug no longer reproduces after you’ve applied some fix.

Fiddler replaying ASP.NET requests

Postman is a very popular tool to send HTTP requests and inspect the result. It has a ton of features, like test automation, import/export, collaboration, and others. What you can do is to copy the request from Fiddler and paste to Postman. Now, save it to your Postman session and debug from there. Sending from Postman is much more convenient than Fiddler’s Replay feature. You can create collaborative workspaces, and share your Postman sessions with other team members. And you can automate it and convert requests to tests.

Use Developer Exception pages to see the exception

ASP.NET can show a special Developer Exception Page when a request fails. It looks like this:

Developer Exception Page

You can see the call stack and even the actual lines that led to the exception.

By default, this page shows only during development, which is configured in Startup.cs (if you’re using ASP.NET Core):

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

This code checks if the environment variable ASPNETCORE_ENVIRONMENT equals to Development (which it doesn’t when publishing to the cloud). But you can change it however you want. It’s probably not a great idea to show this exception page to your customers, but it can be helpful in QA and Staging environments.

Middleware to capture request-failure logs

ASP.NET Core’s middleware system is great for exception handling. You can easily create code that’s triggered when a request fails with an exception. It’s as simple as this:

public class ErrorLoggingMiddleware
{
    private readonly RequestDelegate _next;

    public ErrorLoggingMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception e)
        {
            // Use your logging framework here to log the exception 
            throw;
        }
    }
}

In the catch clause you can use your favorite logging framework to log to a file, database, or wherever you want. Make sure to register the middleware:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }
    app.UseMiddleware<ErrorLoggingMiddleware>();

Check out this post for more explanation on the ErrorLoggingMiddleware.

See Failures in Native Logs

Enabling logs that show request failures is somewhat elusive in Azure Portal and in the documentation. If you’re looking in the App Service logs and Log stream, then you’re in the wrong place. Instead, you’ll need to use the Core module stdout log.

To enable it, modify the web.config file in your application’s wwwroot. If you’re using Azure App Service, you can use Kudu tools. Go to your App Service resource, Advanced Tools blade and click on Go. It will launch Kudu tools, which means it opens a browser tab with the URL https://[yoursite].scm.azurewebsites.net. From there, choose Debug Console | CMD, and navigate to site | wwwroot where you’ll find web.config. Click on the pencil icon to edit it.

Azure Kudu tools

Set stdoutLogEnabled to true and stdoutLogFile to the path you want your log files to be. It can be something like .logsstdout and the log file will look like stdout_20191227125405_7832.log. In an Azure App Service, you can set it to \?%home%LogFilesstdout. Make sure the folder exists. If it doesn’t, create it manually or this won’t work.

This will create log files in the chosen location for each failed request. Each such log will write the exception type, exception message, and the stack trace. Like this:

stdout logs with exception

These logs are especially great for application startup problems. Startup problems won’t appear in application insights or the Event Viewer, but they will appear here.

Make sure to turn off stdout logs when done. Failing to disable them can lead to app or server failure. There’s no limit on log file size or the number of log files created.

Error Monitor Tools aggregate request failures for you. A few of them are elmah.io, Raygyn, and Application Insights. They will show your failures in a table where you can see how many times each type of error happened. An error type is an exception with a unique URL, Call Stack and Exception Type. So when an exception that has a different call stack or exception type occurs, it’s considered a different error.

Raygun exceptions

Besides the exception details, you’ll be able to see the failure count, number of users, IP addresses, and distribution over time. Some of these tools, like Raygun, can assign errors to developers and integrate with bug-tracking systems like Jira.

If you’re using an Application Performance Monitor (APM) tool like Application Insights, you can get even more information. APMs measure your server’s performance. So they record network requests, database requests, I/O operations, etc. That’s why you can see a sort of Timeline view of the request’s context – from the time it was received by the server up to the time it failed.

Application insights request failure

Besides the exception details (message, call stack) you can see that this request executed an HTTP POST call, a couple of database calls, another POST to Raygun, until it finally failed with a NullReferenceException.

Unfortunately, Application Insights doesn’t show the details of those events. I would like to be able to see HTTP request details – headers, query parameters, and request body. The same goes for database call details. I would have liked to see SQL queries and responses.

Using Application insights or an Error Monitoring tool like Raygun is probably the best way to monitor request failures. It’s much more practical than using stdout logs or the Event Viewer. However, it’s not always enough. Sometimes you’ll want to see more details – like local variables and object heap at the time of the exception. That’s what the next techniques will provide.

Break on Exceptions with Cloud Explorer

You can attach Visual Studio’s debugger to the Azure resource. It’s pretty simple with the Cloud Explorer. Go to Views | Cloud Explorer, connect to your Azure subscription, find your resource, and right-click to debug it.

Cloud explorer attach a debugger

Your code will be considered “non-user” by Visual Studio. The reason is that production code is usually optimized, or you don’t have symbols deployed. To allow “non-user code” to work, go to Debug | Options and uncheck Enable Just my Code. If everything goes well (which is not guaranteed at all), you’ll be able to break on exceptions and debug them.

Problems with breaking on exceptions with the Cloud Explorer:

  • The biggest problem with this approach is that breaking the process means your server stops working. It stops serving requests, which is not something you want in production.

  • To see the line of code of the exception, you’ll need to have the exact same version of the source code that was built to create the binaries on the server. And symbols deployed.

  • The code on the server is usually optimized, which means you most likely won’t see local variables in the Locals window.

    There are ways to overcome issues with code optimization and matching symbols to source code. Check out my article: Debugging 3rd Party .NET Code without symbols in Visual Studio.

Use Microsoft’s Snapshot Collector

Microsoft’s Snapshot Collector is a sort of extension to Application Insights. It’s been in Preview for over 2 years now. In theory, it promises an amazing debugging experience. When investigating the request failure in Application Insights, you’ll be able to see the exception (from production) inside Visual Studio, as if you’re debugging it on your development machine. You’ll see the code, the exception itself, the local variables, and the class members.

Follow these instructions to install it. You’ll need to have Application Insights(AI) installed and working as a prerequisite. With .NET Core 2+ versions it’s just a matter of a few clicks in Azure portal. In older versions, you’ll need to install the Snapshot Collector NuGet package and republish to Azure.

Once installed. The next web request failure you’ll encounter should have an Open debug snapshot button available in Application Insights. Although the same failure needs to happen at least twice and it will take 5-10 minutes for it to appear:

After clicking the Open debug snapshot button, you’ll see the call stack and the local variables. Another button Download Snapshot will appear. Clicking it will download a .diagsession file to your computer. This is a big file since it contains a Minidump of your application. You can open it in VS Enterprise 2017 and above. When opening, Visual Studio proceeds as if you’re opening a regular dump.

You’ll have the same problems like with other dumps from production – Optimized code, possibly no symbols, and lack of original source code. Except that the snapshot debugger makes sure the last frame in the call stack is not optimized, so you will be able to see the local variables of the last frame.

Problems with Snapshot Debugger:

  • The onboarding experience is a nightmare. I always encountered problems trying to set up the snapshot debugger.
  • You need a VS Enterprise version to open the .diagsession file.
  • It takes a lot of time for the Open debug snapshot button to appear after the failure, even with a small proof-of-concept application. Although the documentation says it should take 5-10 minutes.
  • The exception has to happen at least twice for it to appear. You can change that to a bigger number so as to capture only the more common problems.
  • Since the .diagsession files are so large, there are some very harsh limits on them. A maximum of 50 snapshots per day are retained and up to 15 days for each Application Insights instance. In a typical server, a bug might cause thousands of requests to fail in a day. The first 50 will exhaust this capacity and prevent you from investigating the errors you really want to dig into.
  • The entire experience is very slow. It takes forever to see the snapshot, open it, download it, open in Visual Studio and debug it.

Capture Dumps with ProcDump

It’s possible to capture a Dump file exactly when your web requests fail. You can then download the dump to your development machine and debug with: Visual Studio, WinDbg, or a memory profiler like SciTech’s memprofiler. This is very similar to what the snapshot debugger does, except that:

  • Snapshot debugger captures Dumps automatically, whereas here you’ll have to do it manually.
  • You can open the Dump in any VS version, no need to have VS Enterprise.
  • The snapshot debugger makes sure the last frame of the exception is not optimized, whereas with here it will stay optimized.
  • On the upside you don’t have to go through with the snapshot debugger’s set up experience.

The best tool to capture dumps in both Azure App Service, a Virtual Machine, or any machine hosting an ASP.NET application, is with a tool called ProcDump. It’s a free, command-line tool, that’s part of the SysInternals tools suite. You can download it from here. But if you’re using an Azure App Service, it’s probably pre-installed in d:devtoolssysinternalsprocdump.exe and d:devtoolssysinternalsprocdump64.exe. Make sure to use the correct ProcDump version according to your process’s architecture.

You can have ProcDump to automatically create a dump file on some trigger. It might be when a CPU threshold is reached, when a memory limit is reached, on a crash, or on a first-chance exception. Since web request 500 errors are caused by exceptions, you can use that as a trigger. Use this command:

d:devtoolssysinternalsprocdump.exe -accepteula -ma -e 1 <process_id>

Kudu tools use ProcDump

The -e 1 option tells ProcDump to break on first-chance exceptions. Omitting the 1 would break only on unhandled exceptions. ASP.NET web requests are actually user-unhandled exceptions, handled by the framework itself. -accepteula silently accepts the user agreement. -ma is necessary to get a full memory dump. This will offer the best debugging experience when investigating. To get the process ID (16544 in the example), you can use Kudu’s process explorer:

image-20200101155909425

Use the -f command to break only on an exception of a certain type. Note that the exception name is a bit different than the ones you’re used to. For example, DivideByZeroException is actually DIVIDE_BY_ZERO as far as ProcDump is concerned. This command uses “contains” logic, so you can write something like:

d:devtoolssysinternalsprocdump.exe -accepteula -ma -e 1 -f Divide <process_id>

This will catch any exception that contains the word “Divide”.

TIP: Launching such a command will have ProcDump working indefinitely. If you want to stop it, pressing Ctrl + C doesn’t work in Kudu tools. You’ll need to go to Process Explorer, right-click on the cmd.exe process and kill it manually.

Investigating the Dump file

Once you’ve captured a Dump, download it to your development machine.

Download dump in Kudu tools

The easiest way to investigate dumps is to open them with Visual Studio. This will show you the exception, call stack, and possibly local variables. You won’t see the source code unless you have the exact source that was used to build and deploy this process. And you might not see local variables because the process is optimized in Release builds. There are ways to overcome these problems. Check out the following resources to see how to investigate Dumps and how to overcome said problems:

  • How to Create, Use, and Debug .NET application Crash Dumps
  • https://docs.microsoft.com/en-us/dotnet/framework/debug-trace-profile/making-an-image-easier-to-debug – Go to the part that talks about creating an INI file. This will allow telling the JIT compiler not to optimize code in the assembly. After creating the file, you’ll need to restart the application. When you’re done, make sure to remove the INI file and restart the app again. Unoptimized code hurts performance.
  • Debugging 3rd Party .NET Code without symbols in Visual Studio

OzCode’s Production Debugger

You might know OzCode for its Visual Studio extension. OzCode is developing a new product that brings similar debugging capabilities to a production environment. Here’s how it works:

  1. Install OzCode’s agent to your Azure App Service / Virtual Machine with IIS / Docker container.
  2. When a web request fails due to an exception (500 error), you’ll see it in OzCode’s dashboard. This part is similar to other error-monitoring tools like Raygun or Application Insights.

OzCode production debugger

  1. The second time this exception happens, OzCode will collect additional data and allow you to debug the exception in your browser. Similarly to debugging with Visual Studio.

OzCode debugging ASP.NET failure

Unlike with other tools, OzCode shows information that you wouldn’t expect from a production environment. For each request failure you can see:

  • The code, including the line of code that caused the exception. You’ll always see code, even if you deployed without symbols files. But it might be decompiled code.
  • Local variables in all call stack frames (of the exception thread).
  • Timeline events – HTTP requests, Database calls. Including payloads like request & response headers, request & response body, and SQL queries.
  • Logs – Latest logs that were written before the exception. This including your regular logs that you logged with NLog, Serilog, or log4net.

This product is still in development, so you can’t use it just yet, but you can apply to the beta.

Summary

Production debugging is hard. It sometimes feels like we’re solving a problem blindfolded. That’s why we need to take the extra step and prepare our projects for debuggability. This includes setting up proper logging and using the best debugging tools. Hope this article helped you out. Please subscribe to get updates on new blog posts and bonus articles. Happy debugging.

Сегодня обсудим, как на asp.net mvc можно настроить обработку ошибок 404, 500, ну и любых других. Рассмотрим на примере 404 и 500, как наиболее популярных и важных. Как вместо стандартного не очень красивого желтого окна ошибки показывать свои собственные красивые интересные страницы, и при этом как правильно отдавать код ошибки в браузер пользователя.

Казалось бы, задача довольно тривиальная и может быть решена написанием буквально пары строк кода. Действительно, так и есть, если вы используете любую популярную серверную технологию. Но только не ASP.NET. Если ваше приложение написано на ASP.NET MVC, и вы первый раз сталкиваетесь с проблемой обработки ошибок, очень легко запутаться и сделать неправильные настройки. Что впоследствии негативно отразится на продвижении сайта в поисковых системах, удобстве работы для пользователя, SEO-оптимизации.

Рассмотрим два подхода, как настроить страницы ошибок. Они в целом похожи, какой выбрать – решать вам.

Для начала вспомним, что означают наиболее популярные коды ошибок, которые отдает сервер.

Код ответа 200. Это значит что все ОК. Запрос клиента обработан успешно, и сервер отдал затребованные клиентом данные в полном объеме. Например, пользователь кликнул по гиперссылке, и в ответ на это в браузере отобразилась нужная ему информация.

Код ответа 404. Это означает, что запрошенный клиентом ресурс не найден на сервере. Например, указанная в адресе гиперссылки статья не найдена, или *.pdf файл был удален и теперь недоступен для скачивания.

Код ответа 500. Внутренняя ошибка на сайте. Что-то сломалось. Это может быть все что угодно, от неправильно написанного кода программистом, до отказа оборудования на сервере.

Допустим, мы только что создали новое веб-приложение типа MVC. На текущий момент, если никаких дополнительных действий для обработки ошибок не принимать, то стандартный сценарий обработки ошибок будет работать как нужно. В браузер пользователя будет отдаваться правильный код ошибки, пользователю будет показана стандартная страница с ошибкой и ее описанием.

Стандартная страница ошибки

Стандартная страница ошибки

Теперь займемся настройкой собственных страниц ошибок. При этом для нас важно не только показать пользователю красивую страницу ошибки, но также сохранить правильный код ответа сервера.

Вариант 1. Ссылка на статичные заранее подготовленные html-страницы.

Первым делом в файле web.config в разделе system.web добавляем новую секцию customErrors со следующими настройками:

web.config

<system.web>
  <customErrors mode="On" redirectMode="ResponseRewrite" defaultRedirect="~/404.aspx">
    <error statusCode="404" redirect="~/404.aspx"/>
    <error statusCode="500" redirect="~/500.aspx"/>
  </customErrors>
  ...
</system.web>

Эта секция служит для обработки ошибок на уровне платформы ASP.NET.

Атрибут mode=»On» определяет, что пользовательские страницы ошибок включены. Также допустимы значения Off / RemoteOnly.

Атрибут redirectMode=»ResponseRewrite» определяет, следует ли изменять URL-адрес запроса при перенаправлении на пользовательскую страницу ошибки. Естественно, нам этого не нужно.

Атрибут defaultRedirect=»~/404.aspx» указывает на то, какая страница ошибки будет показана в случае возникновения кода ответа сервера, который мы не описали в настройках. Пусть при любых других ошибках пользователь будет думать, что страница не найдена.

И уже внутри этой секции мы определяем два кода, для которых у нас будут кастомные страницы ошибок.

Далее, как видно из настроек выше, нам понадобятся *.aspx файлы, на которые будет делаться редирект. Обратите внимание, что мы ссылаемся именно на *.aspx файлы, а не на *.html. Эти файлы являются проходными, служебными, в них содержатся настройки для ответа сервера. Содержимое файла 404.aspx:

404.aspx

<%@ Page Language="C#" %>

<%
    var filePath = MapPath("~/404.html");
    Response.StatusCode = 404;
    Response.ContentType = "text/html; charset=utf-8";
    Response.WriteFile(filePath);
%>

В коде выше мы указываем путь непосредственно до конечного *.html файла, а также дополняем настройки ответа сервера. Указываем код ответа, тип отдаваемого контента и кодировку. Если не указать кодировку, то браузер пользователя может интерпретировать ответ от сервера как не отформатированную строку, и, соответственно, не преобразует ее в html-разметку. А если не указать StatusCode = 404 , то получится следующая интересная ситуация:

Код ответа сервера отдается неверно

Код ответа сервера отдается неверно

И хотя на рисунке выше нам показывается пользовательская страница с ошибкой, при этом код ответа 200 — это конечно же неверно. Когда-то давно на форумах Microsoft такое поведение зарепортили как баг. Однако Microsoft возразила, что это не баг, а фича и не стала ничего менять в будущих релизах ASP.NET. Поэтому приходится это исправлять вручную, и вручную в *.aspx файле в ответе сервера указывать код ответа 404.

Попробуйте собственноручно намеренно убрать какую-нибудь из объявленных на данный момент настроек из секции customErrors и понаблюдайте за результатом.

Также по аналогии создаем подобный *.aspx файл для ошибки 500.

И уже после этого нам нужно создать статичные html-файлы, соответственно для ошибок 404 и 500. Пусть они лежат в корне нашего проекта.

Статичные файлы расположены в корне проекта

Статичные файлы расположены в корне проекта

Здесь же в файле web.config определяем раздел system.WebServer, если он еще не определен, и в нем объявляем секцию httpErrors:

web.config

  <system.webServer>
    <httpErrors errorMode="Custom" defaultResponseMode="File" defaultPath="c:projectsmysite404.html">
      <remove statusCode="404" />
      <remove statusCode="500" />
      <error statusCode="404" path="404.html" responseMode="File" />
      <error statusCode="500" path="500.html" responseMode="File" />
    </httpErrors>
  </system.webServer>

Эта секция служит для обработки ошибок на уровне сервера IIS. Суть в том, что иногда обработка запроса происходит непосредственно на уровне ASP.NET. А иногда ASP.NET просто определяет нужный код ответа и пропускает запрос выше, на уровень сервера. Такой сценарий может случиться, если, например, мы в действии контроллера возвращаем экземпляр класса HttpNotFound:

Или же когда система маршрутизации в MVC-приложении не может определить, к какому маршруту отнести запрошенный пользователем URL-адрес:

https://site.com/long/long/long/long/path

Для секции httpErrors важно отметить следующее. Так как мы ссылаемся на статичные *.html файлы, то и в качестве значений для нужных атрибутов здесь также указываем File . Для атрибута defaultPath необходимо указать абсолютный путь до файла ошибки. Относительный путь именно в этом месте работать не будет. Сам атрибут defaultPath определяет файл, который будет выбран для всех других ошибок, которые мы явно не указали. Но здесь есть одна небольшая проблема. Дело в том, что этот атрибут по умолчанию заблокирован на сервере IIS Express. Если вы разрабатываете свое приложение именно на локальном сервере, то это ограничение нужно снять. Для этого в директории своего проекта нужно найти файл конфигурации сервера и удалить этот атрибут из заблокированных, как это показано на рисунке:

Расположение файла applicationhost.config

Расположение файла applicationhost.config

Также проверьте папку App_Start. Если вы создали не пустое приложение, а работаете над реальным проектом, там может находиться класс FilterConfig, в котором регистрируются все глобальные фильтры в приложении. В методе регистрации удалите строчку кода, где регистрируется HandleErrorAttribute, в нашем случае он не понадобится.

Вот такой комплекс мер нужно предпринять, чтобы настроить обработку ошибок 404, 500, и любых других. Это настройки в файле web.config, и добавление в наш проект статичных файлов.

Вариант 2. Обработка ошибок с использованием специального контроллера.

Второй подход немного отличается от первого. Здесь нам не понадобится секция customErrors, так как обработку всех ошибок мы будем передавать сразу из приложения на уровень сервера, и он уже будет решать что делать. Можно удалить или закомментировать эту секцию в файле web.config.

Далее создадим специальный контроллер, который будет принимать все ошибки, которые мы хотим обрабатывать:

public class ErrorController : Controller
{
    public ActionResult NotFound()
    {
        Response.StatusCode = 404;
        return View();
    }

    public ActionResult Internal()
    {
        Response.StatusCode = 500;
        return View();
    }
}

Также создадим соответствующие представления с нужной нам красивой разметкой.

Также в файле web.config нам нужно изменить настройки в секции httpErrors. Если раньше мы ссылались на статичные html-файлы, то теперь мы будем обращаться по указанным URL, которые мы определили в ErrorController’е, чтобы именно там обрабатывать ошибки:

web.config

<httpErrors errorMode="Custom" existingResponse="Replace" defaultResponseMode="ExecuteURL" defaultPath="/Error/NotFound">
  <remove statusCode="404"/>
  <remove statusCode="500"/>
  <error statusCode="404" path="/Error/NotFound" responseMode="ExecuteURL"/>
  <error statusCode="500" path="/Error/Internal" responseMode="ExecuteURL"/>
</httpErrors>

Вариант 3. Фильтр HandleErrorAttribute

Замечу, что есть еще один способ взять под свой контроль обработку ошибок в приложении – это наследоваться от стандартного класса HandleErrorAttribute и написать свой фильтр. Но это уже более частный случай, когда нужно реализовать какую-то особенную логику при возникновении той или иной ошибки. В большинстве же более менее стандартных приложений наша проблема решается двумя выше описанными способами и в этом фильтре нет необходимости. Более подробную информацию, как работать с классом HandleErrorAttribute можно найти в официальной документации в интернете по этой ссылке.

Итого

Мы посмотрели на два разных подхода, которые можно применить при обработке ошибок на платформе ASP.NET. Опять же повторюсь, что если для вас не принципиально настраивать собственные страницы ошибок, то лучше не изменять эти настройки, так как даже одна упущенная деталь или неверно сконфигурированный параметр может очень сильно навредить репутации вашего сайта для конечного пользователя и в поисковых системах.

This is the eighth of a new series of posts on ASP .NET Core 3.1 for 2020. In this series, we’ll cover 26 topics over a span of 26 weeks from January through June 2020, titled ASP .NET Core A-Z! To differentiate from the 2019 series, the 2020 series will mostly focus on a growing single codebase (NetLearner!) instead of new unrelated code snippets week.

Previous post:

  • Generic Host Builder in ASP .NET Core 3.1

NetLearner on GitHub :

  • Repository: https://github.com/shahedc/NetLearnerApp
  • v0.8-alpha release: https://github.com/shahedc/NetLearnerApp/releases/tag/v0.8-alpha

In this Article:

  • H is for Handling Errors
  • Exceptions with Try-Catch-Finally
  • Try-Catch-Finally in NetLearner
  • Error Handling for MVC
  • Error Handling for Razor Pages
  • Error Handling in Blazor
  • Logging Errors
  • Transient Fault Handling
  • References

H is for Handling Errors

Unless you’re perfect 100% of the time (who is?), you’ll most likely have errors in your code. If your code doesn’t build due to compilation errors, you can probably correct that by fixing the offending code. But if your application encounters runtime errors while it’s being used, you may not be able to anticipate every possible scenario.

Runtime errors may cause Exceptions, which can be caught and handled in many programming languages. Unit tests will help you write better code, minimize errors and create new features with confidence. In the meantime, there’s the good ol’ try-catch-finally block, which should be familiar to most developers.

NOTE: You may skip to the next section below if you don’t need this refresher.

try-catch-finally in C#

Exceptions with Try-Catch-Finally

The simplest form of a try-catch block looks something like this:

**try** { _ **// try something here** _} **catch** ( **Exception** ex){ _ **// catch an exception here** _}

You can chain multiple catch blocks, starting with more specific exceptions. This allows you to catch more generic exceptions toward the end of your try-catch code. In a string of _ catch _() blocks, only the caught exception (if any) will cause that block of code to run.

**try** { _ **// try something here** _} **catch** ( **IOException** ioex){ _ **// catch specific exception, e.g. IOException** _} **catch (Exception** ex){ _ **// catch generic exception here** _}

Finally, you can add the optional _ finally _block. Whether or not an exception has occurred, the finally block will always be executed.

**try** { _ **// try something here** _} **catch** ( **IOException** ioex){ _ **// catch specific exception, e.g. IOException** _} **catch (Exception** ex){ _ **// catch generic exception here** _} **finally** { _ **// always run this code** _}

Try-Catch-Finally in NetLearner

In the NetLearner sample app, the LearningResourcesController uses a LearningResourceService from a shared .NET Standard Library to handle database updates. In the overloaded Edit() method, it wraps a call to the Update() method from the service class to catch a possible DbUpdateConcurrencyException exception. First, it checks to see whether the ModelState is valid or not.

if (ModelState.IsValid){ **...** }

Then, it tries to update user-submitted data by passing a LearningResource object. If a DbUpdateConcurrencyException exception occurs, there is a check to verify whether the current LearningResource even exists or not, so that a 404 (NotFound) can be returned if necessary.

**try** { await _learningResourceService.Update(learningResource);} **catch** ( **DbUpdateConcurrencyException** ){ if (!LearningResourceExists(learningResource.Id)) { return NotFound(); } else { **throw** ; }}return RedirectToAction(nameof(Index));

In the above code, you can also see a throw keyword by itself, when the expected exception occurs if the Id is found to exist. In this case, the throw statement (followed immediately by a semicolon) ensures that the exception is rethrown, while preserving the stack trace.

Run the MVC app and navigate to the Learning Resources page after adding some items. If there are no errors, you should see just the items retrieved from the database.

Learning Resources UI (no errors)

If an exception occurs in the Update() method, this will cause the expected exception to be thrown. To simulate this exception, you can intentionally throw the exception inside the update method. This should cause the error to be displayed in the web UI when attempting to save a learning resource.

Exception details in Web UI

Error Handling for MVC

In ASP .NET Core MVC web apps, unhandled exceptions are typically handled in different ways, depending on whatever environment the app is running in. The default template uses the DeveloperExceptionPage middleware in a development environment but redirects to a shared Error view in non-development scenarios. This logic is implemented in the Configure () method of the Startup.cs class.

if (env. **IsDevelopment** ()){ app. **UseDeveloperExceptionPage** (); app. **UseDatabaseErrorPage** (); }else{ **app.UseExceptionHandler("/Home/Error");** _ **...** _}

The DeveloperExceptionPage middleware can be further customized with DeveloperExceptionPageOptions properties, such as FileProvider and SourceCodeLineCount.

var options = new **DeveloperExceptionPageOptions** { **SourceCodeLineCount** = 2}; app.UseDeveloperExceptionPage(options); 

Using the snippet shown above, the error page will show the offending line in red, with a variable number of lines of code above it. The number of lines is determined by the value of SourceCodeLineCount , which is set to 2 in this case. In this contrived example, I’m forcing the exception by throwing a new Exception in my code.

Customized lines of code within error page

For non-dev scenarios, the shared Error view can be further customized by updating the Error.cshtml view in the Shared subfolder. The ErrorViewModel has a ShowRequestId boolean value that can be set to true to see the RequestId value.

@model **ErrorViewModel** @{ ViewData["Title"] = "Error";}<h1 class="text-danger">Error.</h1><h2 class="text-danger">An error occurred while processing your request.</h2>@if (Model. **ShowRequestId** ){<p><strong>Request ID:</strong> <code>@Model. **RequestId** </code></p>}<h3>header content</h3><p>text content</p> 

In the MVC project’s Home Controller, the Error () action method sets the RequestId to the current Activity.Current.Id if available or else it uses HttpContext.TraceIdentifier. These values can be useful during debugging.

[**ResponseCache** (Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]public IActionResult **Error** (){ return View(new **ErrorViewModel** { **RequestId** = Activity.Current?.Id ?? HttpContext.TraceIdentifier });} 

But wait… what about Web API in ASP .NET Core? After posting the 2019 versions of this article in a popular ASP .NET Core group on Facebook, I got some valuable feedback from the admin:

Dmitry Pavlov: “For APIs there is a nice option to handle errors globally with the custom middleware https://code-maze.com/global-error-handling-aspnetcore – helps to get rid of try/catch-es in your code. Could be used together with FluentValidation and MediatR – you can configure mapping specific exception types to appropriate status codes (400 bad response, 404 not found, and so on to make it more user friendly and avoid using 500 for everything).”

For more information on the aforementioned items, check out the following resources:

  • Global Error Handling in ASP.NET Core Web API: https://code-maze.com/global-error-handling-aspnetcore/
  • FluentValidation • ASP.NET Integration: https://fluentvalidation.net/aspnet
  • MediatR Wiki: https://github.com/jbogard/MediatR/wiki
  • Using MediatR in ASPNET Core Apps: https://ardalis.com/using-mediatr-in-aspnet-core-apps

Later on in this series, we’ll cover ASP .NET Core Web API in more detail, when we get to “W is for Web API”. Stay tuned!

Error Handling for Razor Pages

Since Razor Pages still use the MVC middleware pipeline, the exception handling is similar to the scenarios described above. For starters, here’s what the Configure () method looks like in the Startup.cs file for the Razor Pages web app sample.

if (env. **IsDevelopment** ()){ app. **UseDeveloperExceptionPage** (); app. **UseDatabaseErrorPage** (); }else{ app. **UseExceptionHandler** ("/Error"); ...}

In the above code, you can see the that development environment uses the same DeveloperExceptionPage middleware. This can be customized using the same techniques outlined in the previous section for MVC pages, so we won’t go over this again.

As for the non-dev scenario, the exception handler is slightly different for Razor Pages. Instead of pointing to the Home controller’s Error () action method (as the MVC version does), it points to the to the / Error page route. This Error.cshtml Razor Page found in the root level of the Pages folder.

@ **page** @model **ErrorModel** @{ ViewData["Title"] = "Error";}<h1 class="text-danger">Error.</h1><h2 class="text-danger">An error occurred while processing your request.</h2>@if (Model. **ShowRequestId** ){ <p> <strong>Request ID:</strong> <code>@Model. **RequestId** </code> </p>}<h3>custom header text</h3><p>custom body text</p>

The above Error page for looks almost identical to the Error view we saw in the previous section, with some notable differences:

  • @ page directive (required for Razor Pages, no equivalent for MVC view)
  • uses ErrorModel (associated with Error page) instead of ErrorViewModel (served by Home controller’s action method)

Error Handling for Blazor

In Blazor web apps, the UI for error handling is included in the Blazor project templates. Consequently, this UI is available in the NetLearner Blazor sample as well. The _Host.cshtml file in the Pages folder holds the following < environment > elements:

<div id="blazor-error-ui"> < **environment** include="Staging,Production"> An error has occurred. This application may no longer respond until reloaded. </ **environment** > < **environment** include="Development"> An unhandled exception has occurred. See browser dev tools for details. </ **environment** > <a href="" class="reload">Reload</a> <a class="dismiss">🗙</a> </div>

The div identified by the id “blazor-error-ui” ensures that is hidden by default, but only shown when an error has occured. Server-side Blazor maintains a connection to the end-user by preserving state with the use of a so-called circuit.

Each browser/tab instance initiates a new circuit. An unhandled exception may terminate a circuit. In this case, the user will have to reload their browser/tab to establish a new circuit.

According to the official documentation, unhandled exceptions may occur in the following areas:

  1. Component instantiation : when constructor invoked
  2. Lifecycle methods : (see Blazor post for details)
  3. Upon rendering : during BuildRenderTree() in .razor component
  4. UI Events : e.g. onclick events, data binding on UI elements
  5. During disposal : while component’s .Dispose() method is called
  6. JavaScript Interop : during calls to IJSRuntime.InvokeAsync
  7. Prerendering : when using the Component tag helper

To avoid unhandled exceptions, use try-catch exception handlers within .razor components to display error messages that are visible to the user. For more details on various scenarios, check out the official documentation at:

  • Handle errors in ASP.NET Core Blazor apps: https://docs.microsoft.com/en-us/aspnet/core/blazor/handle-errors?view=aspnetcore-3.1

Logging Errors

To log errors in ASP .NET Core, you can use the built-in logging features or 3rd-party logging providers. In ASP .NET Core 2.x, the use of CreateDefaultBuilder () in Program.cs takes of care default Logging setup and configuration (behind the scenes).

public static IWebHostBuilder **CreateWebHostBuilder** (string[] args) => WebHost. **CreateDefaultBuilder** (args) .UseStartup<Startup>();

The Web Host Builder was replaced by the Generic Host Builder in ASP .NET Core 3.0, so it looks slightly different now. For more information on Generic Host Builder, take a look at the previous blog post in this series: Generic Host Builder in ASP .NET Core.

public static IHostBuilder **CreateHostBuilder** (string[] args) => Host. **CreateDefaultBuilder** (args) . **ConfigureWebHostDefaults** (webBuilder => { webBuilder. **UseStartup** (); });

The host can be used to set up logging configuration, e.g.:

public static IHostBuilder **CreateHostBuilder** (string[] args) => Host. **CreateDefaultBuilder** (args) . **ConfigureLogging** ((hostingContext, logging) => { logging.ClearProviders(); logging.AddConsole(options => options.IncludeScopes = true); logging.AddDebug(); }) . **ConfigureWebHostDefaults** (webBuilder => { webBuilder. **UseStartup** (); });

To make use of error logging (in addition to other types of logging) in your web app, you may call the necessary methods in your controller’s action methods or equivalent. Here, you can log various levels of information, warnings and errors at various severity levels.

As seen in the snippet below, you have to do the following in your _ MVC Controller _ that you want to add Logging to:

  1. Add using statement for Logging namespace
  2. Add a private readonly variable for an ILogger object
  3. Inject an ILogger object into the constructor
  4. Assign the private variable to the injected variable
  5. Call various log logger methods as needed.
... **using Microsoft.Extensions.Logging;** public class **MyController** : Controller{ ... **private**  **readonly ILogger _logger;** public **MyController** (..., ILogger< **MyController** > logger) { ... **_logger = logger;** } public IActionResult MyAction(...) { _logger. **LogTrace** ("log trace"); _logger. **LogDebug** ("log debug"); _logger. **LogInformation** ("log info"); _logger. **LogWarning** ("log warning"); _logger. **LogError** ("log error"); _logger. **LogCritical** ("log critical"); }}

In Razor Pages, the logging code will go into the Page’s corresponding Model class. As seen in the snippet below, you have to do the following to the _ Model class that corresponds to a Razor Page _:

  1. Add using statement for Logging namespace
  2. Add a private readonly variable for an ILogger object
  3. Inject an ILogger object into the constructor
  4. Assign the private variable to the injected variable
  5. Call various log logger methods as needed.
... **using Microsoft.Extensions.Logging;** public class **MyPageModel** : PageModel{ ... **private readonly ILogger _logger;** public **MyPageModel** (..., **ILogger<MyPageModel> logger** ) { ... **_logger = logger;** } ... public void **MyPageMethod** () { ... _logger. **LogInformation** ("log info"); _logger. **LogError** ("log error"); ... }} 

You may have noticed that Steps 1 through 5 are pretty much identical for MVC and Razor Pages. This makes it very easy to quickly add all sorts of logging into your application, including error logging.

Transient fault handling

Although it’s beyond the scope of this article, it’s worth mentioning that you can avoid transient faults (e.g. temporary database connection losses) by using some proven patterns, practices and existing libraries. To get some history on transient faults, check out the following article from the classic “patterns & practices”. It describes the so-called “Transient Fault Handling Application Block”.

  • Classic Patterns & Practices: https://docs.microsoft.com/en-us/previous-versions/msp-n-p/dn440719(v=pandp.60)

More recently, check out the docs on Transient Fault Handling:

  • Docs: https://docs.microsoft.com/en-us/aspnet/aspnet/overview/developing-apps-with-windows-azure/building-real-world-cloud-apps-with-windows-azure/transient-fault-handling

And now in .NET Core, you can add resilience and transient fault handling to your .NET Core HttpClient with Polly!

  • Adding Resilience and Transient Fault handling to your .NET Core HttpClient with Polly: https://www.hanselman.com/blog/AddingResilienceAndTransientFaultHandlingToYourNETCoreHttpClientWithPolly.aspx
  • Integrating with Polly for transient fault handling: https://www.stevejgordon.co.uk/httpclientfactory-using-polly-for-transient-fault-handling
  • Using Polly for .NET Resilience with .NET Core: https://www.telerik.com/blogs/using-polly-for-net-resilience-and-transient-fault-handling-with-net-core

You can get more information on the Polly project on the official Github page:

  • Polly on Github: https://github.com/App-vNext/Polly

References

  • try-catch-finally: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/try-catch-finally
  • Handle errors in ASP.NET Core: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/error-handling
  • Use multiple environments in ASP.NET Core: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/environments
  • UseDeveloperExceptionPage: https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.developerexceptionpageextensions.usedeveloperexceptionpage
  • DeveloperExceptionPageOptions: https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.developerexceptionpageoptions
  • Logging: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging
  • Handle errors in ASP.NET Core Blazor apps: https://docs.microsoft.com/en-us/aspnet/core/blazor/handle-errors?view=aspnetcore-3.1

Introduction

Structured exception handling is a fundamental part of the CLR and provides
.NET programmers with a great way of managing errors. In addition to the CLR
exception system, ASP.NET also provides ways of handling errors.

When a runtime or design-time error occurs in an application, ASP.NET shows a
default error page that gives a brief description of the error along with the
line number on which the error occurred. A developer would wish to view this
default error page, during the testing of the application since the description
helps him in rectifying the error. But he would never want a user trying to
access his application, to view this error page. The user would be least
bothered to know about the error. Instead of showing the default error page, it
would be more sensible to show a customized error page that would let the user
send notification of the error to the administrator.

Explanation

Consider an example of an ASP.NET application that generates an error
intentionally to show how ASP.NET detects it and shows the default error page.
The below given webform contains a label and a button server control. In the
eventhandler for the button click event, the user will be redirected to another
webform Trial.aspx. Since the page being redirected to is missing
ASP.NET it will show the default error page indicating it is a runtime error.

Unlike classic ASP, ASP.NET separates the code for the business logic from
the content (i.e. HTML and interface logic). The sample application has two
files named webform1.aspx containing the content and webform1.aspx.vb
containing the code.

WebForm1.aspx

<%@ Page Language="vb" AutoEventWireup="false" Codebehind="webform1.aspx.vb" 
Inherits="ErrorSample.WebForm1"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
    <HEAD>
        <title></title>
        <meta name="GENERATOR" content="Microsoft Visual Studio.NET 7.0">
        <meta name="CODE_LANGUAGE" content="Visual Basic 7.0">
        <meta name="vs_defaultClientScript" content="JavaScript">
        <meta name="vs_targetSchema" 
            content="http://schemas.microsoft.com/intellisense/ie5">
    </HEAD>
    <body MS_POSITIONING="GridLayout">
        <form id="Form1" method="post" runat="server">
            <asp:Label id="Message" style="Z-INDEX: 101; LEFT: 34px; 
                POSITION: absolute; TOP: 46px" runat="server"></asp:Label>
            <asp:Button id="ErrorButton" style="Z-INDEX: 102; LEFT: 268px; 
                POSITION: absolute; TOP: 41px" runat="server" 
                Text="Generate Error"></asp:Button>
        </form>
    </body>
</HTML>

WebForm1.aspx.vb

Public Class WebForm1
    Inherits System.Web.UI.Page
    Protected WithEvents Message As System.Web.UI.WebControls.Label
    Protected WithEvents ErrorButton As System.Web.UI.WebControls.Button

    Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs)_
        Handles MyBase.Load
        Message.Text = "This sample page generates an Error..."
    End Sub

    Public Sub ErrorButton_Click(ByVal sender As Object, ByVal e As System.EventArgs)_
        Handles ErrorButton.Click
        Response.Redirect("Trial.aspx")
    End Sub

End Class

Now if you try to run the above web form by viewing it on the browser, you
will get the below shown web page:

Image 1

Now if you click on the button labelled «Generate Error», you will get the
below shown default ASP.NET error page.

Image 2

Customizing Error Page

To customize the default error page, one will have to change the default
configuration settings of the application.

There are three error modes in which an ASP.NET application can work:

1) Off Mode
2) On Mode
3) RemoteOnly Mode

The Error mode attribute determines whether or not an ASP.NET error message
is displayed. By default, the mode value is set to «RemoteOnly».

Off Mode

When the error attribute is set to «Off», ASP.NET uses its default error page
for both local and remote users in case of an error.

On Mode

In case of «On» Mode, ASP.NET uses user-defined custom error page instead of
its default error page for both local and remote users. If a custom error page
is not specified, ASP.NET shows the error page describing how to enable remote
viewing of errors.

RemoteOnly

ASP.NET error page is shown only to local users. Remote requests will first
check the configuration settings for the custom error page or finally show an
IIS error.

Configuration File

Customisation of error page can be implemented by adding a value for an
attribute defaultRedirect in the <customErrors>
tag of the configuration file web.config. This file determines
configuration settings for the underlying application.

Off Mode

In this scenario, set the mode attribute value to «Off» as shown below:

Web.Config File

="1.0"="utf-8"
<configuration>
    <system.web>
         <customErrors mode="Off" />
    </system.web>
</configuration>

When the sample ASP.NET web page is viewed in the browser from the remote
machine, one gets the below shown default error page.

Image 3

The above example thus shows that, whether it is local or remote access,
ASP.NET error page is shown.

On Mode

In this scenario, set the mode attribute value to «On» as shown below:

Web.Config File

="1.0"="utf-8"
<configuration>
    <system.web>
         <customErrors defaultRedirect="error.htm" mode="On" />
    </system.web>
</configuration>

As shown in the configuration file, the «defaultRedirect» attribute has been
set to a user-defined page error.htm. The user-defined error page can be
an ASP.NET web page, classic ASP page or a simple HTML page.

For example, the contents of the user-defined error page error.htm can
be given as follows:

Error.htm

<HTML>
    <BODY>
        <b>We are very sorry for the inconvenience caused to you...<br> </b>
    </BODY>
</HTML>

When the sample ASP.NET web page is viewed in the browser from the
remote/local machine, one gets the below shown custom error page.

Image 4

RemoteOnly Mode

In this scenario, set the mode attribute value to «RemoteOnly» as shown
below:

Web.Config File

="1.0"="utf-8"
<configuration>
    <system.web>
         <customErrors defaultRedirect="error.htm" mode="RemoteOnly" />
    </system.web>
</configuration>

Since the defaultRedirect attribute has been set, if the page is
requested from a remote machine page is redirected to error.htm and if
the page is requested from the local machine the default error page is shown.

Notification of Error to the Administrator

In a practical web application, customisation of error pages is not the only
requirement. The error, if encountered, should be reported to the administrator
so that it can be rectified thus enabling subsequent requests to work properly
without any error.

Notification of the error can be sent to the administrator in one of the
following two ways:

1) Error can be registered as a log entry in the Windows Event Log on the
administrator’s machine
2) An Email can be sent to the administrator with a suitable error message

Writing to the Event Log

In ASP.NET, error can be handled programmatically by writing appropriate code
in the page-level error event, for errors on an individual page or in the
application-level error event for handling errors that may occur in any page of
the application.

Therefore, code for writing in the Event Log should be written in either of
the events, depending on the requirement of the application. To illustrate this
example, I have written the code in the application-level event with the error
mode set to «RemoteOnly» and the defaultRedirect attribute to error.htm.
The application-level error event should be included in the global file global.asax
within the same application folder.

The contents of the global file can be given as follows:

Writing Log Entry in the Event Log

Imports System.Web
Imports System.Web.SessionState
Imports System.Diagnostics

Public Class Global
    Inherits System.Web.HttpApplication

     Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)

        Dim ErrorDescription As String = Server.GetLastError.ToString

        
        Dim EventLogName As String = "ErrorSample"
        If (Not EventLog.SourceExists(EventLogName)) Then
            EventLog.CreateEventSource(EventLogName, EventLogName)
        End If

        
        Dim Log As New EventLog()
        Log.Source = EventLogName
        Log.WriteEntry(ErrorDescription, EventLogEntryType.Error)

    End Sub

End Class

Event Log support is provided in .NET through the namespace System.Diagnostics.
So, for the above code to work, it is very essential to add a reference to the
above-mentioned namespace in the project. In the event handler for
application-level error, a log named «ErrorSample» is created if it does not
exist in the Event Log. If it already exists, the error entry is added to the
existing list of events. After viewing the page on the browser from a remote
machine, the event will get listed in the Event Log on the administrator’s
machine as shown below:

Image 5

Description of the error can be viewed by selecting the appropriate event and
double clicking it.

Another form pops up as shown below:

Image 6

Sending an Email to the Administrator

To illustrate this example, I have written the code for sending an Email to
the administrator in the application-level error event. The contents of the
global file can be given as follows:

Imports System.Web
Imports System.Web.SessionState
Imports System.Web.Mail

Public Class Global
    Inherits System.Web.HttpApplication
 
    Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)

        Dim mail As New MailMessage()
        Dim ErrorMessage = "The error description is as follows : " &_
            Server.GetLastError.ToString
        mail.To = "administrator@domain.com"
        mail.Subject = "Error in the Site"
        mail.Priority = MailPriority.High
        mail.BodyFormat = MailFormat.Text
        mail.Body = ErrorMessage
        SmtpMail.Send(mail)

    End Sub

End Class

In the above code, SMTP service is being used to send the mail across. SMTP
mail service support is provided in .NET through the namespace System.Web.Mail.
So, for the above code to work, it is very essential to add a reference to the
above-mentioned namespace in the project.

A BE in Computers, currently working with Syntel India Ltd. His skillsets include VB 6.0,ASP,ADO.He is currently working on VB.Net,C#,ADO.Net,ASP.NET.
You can contact him at amit_kukreja@syntelinc.com

How to handle errors and exception in ASP.NET MVC applications the right way.
I want the following:
– All the errors and exceptions must be logged
– Logging should be easy to program and it should not influence main code flow
– Error log must contain call stack and time stamp
– User should receive a friendly error page.
Sample project for Visual Studio 2015 can be downloaded here https://github.com/mchudinov/AspMvcErrorHandler
error404

Contents

  • 1. Install and configure NLog
  • 2. Add errors handling to Application_Error method and show custom error page
  • 3. Use aspects for logging and exception handling
  • 4. Use ELMAH
    • Share this:

I use the following tools:
– Application_Error method to catch all errors in ASP.NET application that were not caught inplace
– ELMAH to log all errors
– PostSharp to integrate logging as aspect. Read my blog post about using PostSharp for logging
– NLog as logging framework. Read my blog post about using NLog.

1. Install and configure NLog

NLog or other logging framework. NLog is good because it can log asynchronously. Here is my blog post Logging in .NET Mono on Linux and Windows using NLog.

2. Add errors handling to Application_Error method and show custom error page

I need one single point in my application to handle exceptions. Exception must be logged and user will see an error page. Error page is in fact an Error view with Error controller behind.

– Remove all customErrors and httpErrors from Web.config
– Create Error view model in Models folder of ASP.NET MVC project

public class Error

{

public string StatusCode { get; set; }

public string StatusDescription { get; set; }

public string Message { get; set; }

public DateTime DateTime { get; set; }

}

– Create ErrorPageController

public class ErrorPageController : Controller

{

public ActionResult Error(int statusCode, Exception exception)

{

Response.StatusCode = statusCode;

var error = new Models.Error

{

StatusCode = statusCode.ToString(),

StatusDescription = HttpWorkerRequest.GetStatusDescription(statusCode),

Message = exception.Message,

DateTime = DateTime.Now

};

return View(error);

}

}

– Create Error view

@model MvcErrorHandler.Models.Error

@{

    ViewBag.Title = Model.StatusCode;

}

<h1 class=«error»>@Model.StatusCode</h1>

@Model.StatusDescription<br />

@Model.Message<br />

@Model.DateTime

– Add Application_Error method to Global.asax.cs
Application_Error will catch all uncatched exceptions, log exception message with call stack and session data and run ErrorPageController that will show the user friendly error page.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

protected void Application_Error(object sender, EventArgs e)

{

Exception ex = Server.GetLastError();

if (ex != null)

{

StringBuilder err = new StringBuilder();

err.Append(«Error caught in Application_Error eventn»);

err.Append(«Error in: « + (Context.Session == null ? string.Empty : Request.Url.ToString()));

err.Append(«nError Message:» + ex.Message);

if (null != ex.InnerException)

err.Append(«nInner Error Message:» + ex.InnerException.Message);

err.Append(«nnStack Trace:» + ex.StackTrace);

Server.ClearError();

if (null != Context.Session)

{

err.Append($«Session: Identity name:[{Thread.CurrentPrincipal.Identity.Name}] IsAuthenticated:{Thread.CurrentPrincipal.Identity.IsAuthenticated}»);

}

_log.Error(err.ToString());

if (null != Context.Session)

{

var routeData = new RouteData();

routeData.Values.Add(«controller», «ErrorPage»);

routeData.Values.Add(«action», «Error»);

routeData.Values.Add(«exception», ex);

if (ex.GetType() == typeof(HttpException))

{

routeData.Values.Add(«statusCode», ((HttpException)ex).GetHttpCode());

}

else

{

routeData.Values.Add(«statusCode», 500);

}

Response.TrySkipIisCustomErrors = true;

IController controller = new ErrorPageController();

controller.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));

Response.End();

}

}

}

Let’s test now what we did!
Throw an exception somewhere in the application (in a controller or wherever). This should generate a 500 internal server error and an Error page will be shown.
Run application in Visual Studio and open it in Internet Explorer. IE (v11) shows its own 500 error page:

500ie

Open same page in FireFox. Our custom error page will be shown:
500firefox

Navigate to a non existing path will generate a 404 error page:
404firefox

Note! Errors occurred in Application_Start will be logged but user will not be redirected to an Error page because Response object does not existed in Application_Start yet.

3. Use aspects for logging and exception handling

I use to separate aspects for logging and for exception logging.
– Install PostSharp. Here is my blog post Logging in .NET with AOP using PostSharp

– Add exception log aspect

[Serializable]

public class LogExceptionAttribute : OnExceptionAspect

{

private static readonly Logger log = LogManager.GetCurrentClassLogger();

public LogExceptionAttribute()

{

AspectPriority = 10;

}

public override void OnException(MethodExecutionArgs args)

{

log.Error(«Exception {0} in {1}.{2}()», args.Exception, args.Method.DeclaringType.Name, args.Method.Name);

}

}

– Add log aspect.
It will logg all methods on entry and on leaving when log level is set to DEBUG. Can be quite helpful when troubleshooting.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

[Serializable]

public class LogAttribute : OnMethodBoundaryAspect

{

private static readonly Logger log = LogManager.GetCurrentClassLogger();

public LogAttribute()

{

AspectPriority = 20;

}

public override void OnEntry(MethodExecutionArgs args)

{

log.Debug(«Entering {0}.{1}({2})», args.Method.DeclaringType.Name, args.Method.Name, DisplayObjectInfo(args));

}

public override void OnExit(MethodExecutionArgs args)

{

log.Debug(«Leaving {0}.{1}() Return value [{2}]», args.Method.DeclaringType.Name, args.Method.Name, args.ReturnValue);

}

static string DisplayObjectInfo(MethodExecutionArgs args)

{

StringBuilder sb = new StringBuilder();

Type type = args.Arguments.GetType();

FieldInfo[] fi = type.GetFields();

if (fi.Length > 0)

{

foreach (FieldInfo f in fi)

{

sb.Append(f + » = « + f.GetValue(args.Arguments));

}

}

else

sb.Append(«None»);

return sb.ToString();

}

}

Use these aspects on every class that you need exceptions logging and debug logging:

[Log]

[LogException]

public class DataProvider

{

private static readonly Random _rnd = new Random();

public static string GetData()

{

throw new Exception(«Hoho!»);

return «Hello!»;

}

}

AspectPriority modifier is needed to avoid execution priority conflict since both aspects implement OnException method.

Log example with on entry and on leaving events:

20160303 14:47:38.5828 INFO Application started (AspMvcErrorHandler.MvcApplication.Application_Start)

20160303 14:47:38.6993 DEBUG Entering DataProvider..cctor(PostSharp.Aspects.Arguments Empty = PostSharp.Aspects.Arguments) (AspMvcErrorHandler.LogAttribute.OnEntry)

20160303 14:47:38.6993 DEBUG Leaving DataProvider..cctor() Return value [] (AspMvcErrorHandler.LogAttribute.OnExit)

20160303 14:47:38.6993 DEBUG Entering DataProvider.GetData(PostSharp.Aspects.Arguments Empty = PostSharp.Aspects.Arguments) (AspMvcErrorHandler.LogAttribute.OnEntry)

20160303 14:47:38.7583 DEBUG Leaving DataProvider.GetData() Return value [] (AspMvcErrorHandler.LogAttribute.OnExit)

20160303 14:47:38.7813 ERROR Exception System.Exception: Hoho!

   at AspMvcErrorHandler.DataProvider.GetData() in D:projectsAspMvcErrorHandlerAspMvcErrorHandlerDataProvider.cs:line 13 in DataProvider.GetData() (AspMvcErrorHandler.LogExceptionAttribute.OnException)

20160303 14:47:41.7452 ERROR Error caught in Application_Error event

Error in: http://localhost:57686/

Error Message:Hoho!

Stack Trace:   at AspMvcErrorHandler.DataProvider.GetData() in D:projectsAspMvcErrorHandlerAspMvcErrorHandlerDataProvider.cs:line 13

   at AspMvcErrorHandler.Controllers.HomeController.Index() in D:projectsAspMvcErrorHandlerAspMvcErrorHandlerControllersHomeController.cs:line 10

   at lambda_method(Closure , ControllerBase , Object[] )

   at System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters)

4. Use ELMAH

ELMAH is Error Logging Modules and Handlers. Here is a simple ELMAH tutorial. With ASP.NET MVC applications I use a special NuGet package Elmah.MVC. Just install and use it.

Problem: You are developing/maintaining an ASP.NET web-site, and would like the ability to conditionally show/hide runtime error messages depending on who the user visiting the site is.

For a normal user visiting the site you want to be able to display a friendly error message like this when a runtime error occurs:

But when someone within the “developers” security role of your application remotely accesses the site you want to instead show a more detailed exception stack trace error message about the problem without having to change any configuration data:

The below post describes how to use ASP.NET’s role-based security architecture in conjunction with the Global.asax Application_Error event handler to enable this.  You can also download a sample I’ve built that shows how to implement this here.

Some Background Discussion on Error Handling and ASP.NET Custom Error Pages:

ASP.NET and .NET support a rich error-handling architecture that provides a flexible way to catch/handle errors at multiple levels within an application.  Specifically, you can catch and handle a runtime exception with a class, within a page, or on the global application level using the Application_Error event handler within the Global.asax class.  If a runtime exception isn’t handled/cancelled by one of these mechanisms, then ASP.NET’s Custom Error Page feature will kick-in, and an error page will be sent back to the browser accessing the application.

ASP.NET’s Custom Error Page feature can be used to configure a “friendly error page” to be displayed to end-users in place of the standard “server error occurred” message sent back by ASP.NET.  For example, the below web.config file section will cause remote users visiting the site to be redirected to a “friendlyErrorPage.htm” file anytime a runtime error occurs (note: HTTP 500 status code responses indicate runtime errors on the server):

      <customErrors mode=«RemoteOnly«>

        <error statusCode=«500« redirect=«friendlyErrorPage.htm«/>

      </customErrors>

To learn more about how the ASP.NET Custom Errors feature works, and how to configure it, please review this article.

Important: I would recommend never setting the <customErrors> mode attribute to “Off”.  Doing this will cause detailed error messages to be sent back to all normal users visiting your site.  This can lead to information disclosure issues that can compromise the security of your site.  Instead, only change this setting to “On” or “RemoteOnly” (the default) and use the technique below for cases where you want to display detailed error messages to only some users.

Solution:

The above <customErrors> configuration section will cause a friendly error message to be sent to the browser anytime a runtime error message occurs.  This is what we want to happen anytime a normal user access the site, and will allow us to display error pages like this:

To enable developers to instead see detailed error messages when they access the site, I can then add this code to the Application_Error event handler within the Global.asax class:

    Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)

        If (Context IsNot Nothing) And (Context.User.IsInRole(«Developer»)) Then

            Dim err As Exception = Server.GetLastError()

            Response.Clear()

            Response.Write(«<h1>» & err.InnerException.Message & «</h1>»)

            Response.Write(«<pre>» & err.ToString & «</pre>»)

            Server.ClearError()

        End If

    End Sub

The above code checks to see if the current user visiting the site is within a specific security role.  If so, then it retrieves the exception raised during the current request via the Server.GetLastError() method.  It then clears out any content already generated during the current request, and instead outputs the Exception details to the response.  Lastly, it clears out the Exception from the request – which will prevent ASP.NET’s custom error architecture from kicking in.  The result instead is a page that details the error like so:

To learn more about how you can easily create and manage a “Developer” role like the one I’m using above, please review my previous post: Implementing Role Based Security with ASP.NET using Windows Authentication and SQL Server.  If you don’t want to store your role-mappings within a database, you can also store them within Active Directory or your own custom role-provider. 

This sample demonstrates using a Windows User’s membership within the local “Administrators” group on a machine to control whether or not the detailed error message is displayed.  Note: you can perform a role-check on a local Windows group by writing: User.IsInRole(“BUILTINAdministrators”) – where the “BUILTIN” prefix indicates that it is a local group as opposed to a domain level one). 

Hope this helps,

Scott

P.S. Please visit this page to read more articles within my “ASP.NET 2.0 Tips/Tricks/Recipes and Gotchas” series.

Понравилась статья? Поделить с друзьями:
  • Asp net error 500 internal server error
  • Asp net err ssl protocol error
  • Asp net custom error page
  • Asp net core return internal server error
  • Asp net core cors error