PRACTICAL 9.2: EXCEPTION HANDLING & LOGGING

STEP 1: Configure Logging Levels / environment (Properties/launchSettings.json)
CODE:
{
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:33456",
      "sslPort": 44332
    }
  },
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Production"
      }
    },
    "Prac_9_2": {
      "commandName": "Project",
      "launchBrowser": true,
      "applicationUrl": "https://localhost:5001;http://localhost:5000",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}


STEP 2: Wire Exception Handling Middleware (Startup.cs -> Configure)
 - In Development : app.UseDeveloperExceptionPage()
 - In Production  : app.UseExceptionHandler("/Home/Error")
Keep the code as it is.
CODE: (Startup.cs)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace Prac_9_2
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews((options=>
            {
                options.Filters.Add<Prac_9_2.Filters.CustomExceptionFilter>();
            }
            ));
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }
            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}


STEP 3: Add Error Action + View
In Controllers -> HomeController.cs -> in the Error() method just keep the following
(remove everything else):
{
    return View();
}
Return Views/Shared/Error.cshtml as the friendly error page.

CODE:
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
    return View();
}


STEP 4: Add a Global Exception Filter (Optional)
Create a folder named "Filters" in the project -> add a class named
"CustomExceptionFilter.cs" in it.

CODE: (Filters/CustomExceptionFilter.cs)
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Prac_9_2.Filters
{
    public class CustomExceptionFilter:IExceptionFilter
    {
        private readonly ILogger<CustomExceptionFilter> _logger;

        public  CustomExceptionFilter(ILogger<CustomExceptionFilter> logger)
        {
            _logger = logger;
        }

        public void OnException(ExceptionContext context)
        {
            _logger.LogError(context.Exception, "Unhandled Exception occured.");
            context.Result = new ViewResult { ViewName = "~/Views/Shared/Error.cshtml" };
            context.ExceptionHandled = true;
        }
    }
}

Register in Startup.cs (ConfigureServices):
services.AddControllersWithViews((options =>
{
    options.Filters.Add<Prac_9_2.Filters.CustomExceptionFilter>();
}));
(the namespace must match your project name; the full Startup.cs is shown in Step 2)


STEP 5: Use ILogger in Controllers
Inject ILogger into controllers and log events. In HomeController.cs:
 - Add this in the Index() method:
   _logger.LogInformation("Index page visited at {time}", DateTime.Now);
 - Add a new Boom() method:
   public IActionResult Boom()
   {
       throw new Exception("Test exception");
   }

FULL CODE: (Controllers/HomeController.cs)
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Prac_9_2.Models;

namespace Prac_9_2.Controllers
{
    public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;

        public HomeController(ILogger<HomeController> logger)
        {
            _logger = logger;
        }

        public IActionResult Index()
        {
            _logger.LogInformation("Index Page visited at {time}", DateTime.Now);
            return View();
        }

        public IActionResult Privacy()
        {
            return View();
        }

        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            // 1. Retrieve the exception feature from the HTTP context
            var exceptionHandlerFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();

            if (exceptionHandlerFeature != null)
            {
                // 2. Log the exception and path directly into Serilog
                _logger.LogError(
                    exceptionHandlerFeature.Error,
                    "An unhandled exception occurred while processing request at path: {Path}",
                    exceptionHandlerFeature.Path
                );
            }

            // 3. Return the Error view with the request ID model
            return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }

        public IActionResult Boom()
        {
            throw new Exception("Test Exception");
        }
    }
}


STEP 6: Test in Development vs Production
Change ASPNETCORE_ENVIRONMENT (Development / Production) in launchSettings.json,
run the project and open /Home/Boom in each mode.
 - Development : detailed developer exception page.
 - Production  : friendly Views/Shared/Error.cshtml page.


STEP 7: Write Logs to File with Serilog (Optional)
 - Install the NuGet packages Serilog.AspNetCore and Serilog.Sinks.File.
 - Configure Program.cs to log into Logs/log-.txt files.

CODE: (Program.cs)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog;

namespace Prac_9_2
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Log.Logger = new LoggerConfiguration()
                .WriteTo.File("Logs/log-.txt", rollingInterval: RollingInterval.Day)
                .CreateLogger();

            Log.Information("Application is starting up...");

            try
            {
                CreateHostBuilder(args).Build().Run();
            }
            finally
            {
                Log.CloseAndFlush();
            }
        }

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

OUTPUT:
/Home/Boom throws "Test Exception"; the friendly error page is shown and the error is
written to the Logs/log-<date>.txt file.
