Error Handling

Introduction

As you started thinking about in today’s preparation, when an application is running, there are all kinds of unexpected things that can and will happen.

It’s our job as developers to identify, manage, and respond to these situations. We want to handle these cases gracefully and make sure the application doesn’t crash and we give useful messages to the user about what went wrong.

Learning Goals

  • Understand types of errors and their impact on user experience
  • Practice the following error handling strategies:
    • Null Checking
    • Validation
    • Try/Catch

Set-Up

Today we’re going to revisit the MVC Movies Application from Mod 3. Rre-fork and clone it here (you may need to re-name the directory). Then check out the branch read-related-resources. Start the application up and make sure it runs successfully!

Ways Applications Can Break!

There are so many things that might cause an application to throw an error!

Let’s fill in this Jamboard with some of these ways.

With your partner, take 10 minutes and add to the Jamboard as many ideas as you can for what might cause the application to throw error. Think about both MVC applications and APIs. Feel free to google or experiment with MVC Movies and try to break it!

The 400 error codes here are good place to start for API errors! HTTP Cats

More Examples

Server Side Errors:

  • An API you are calling is down
  • The API changes so the request now gives an error instead of data
  • The database is down
  • The database changes the password
  • Necessary environmental variables are missing
  • The package changes version and the old version you are using doesn’t work anymore

Client Side Errors:

  • The user has syntax errors like missing an equal sign in an API request.
  • You try to write something of the wrong type to the database
  • A user submits a form without a required field
  • The user tries to load something, but there are no records of that type in the database, and the frontend breaks when the database returns null
  • The user tries to go to a route that doesn’t exist
  • The user is not authorized to access a page

What happens when an error occurs?

Without proper error handling, if your .NET app breaks this is what happens.

.NET Error

Can I get a show of hands, who has seen something like this before?

With your partner discuss the following questions:

  • Is this a good user experience? Why or why Not?
  • What would make this user experience better?

Error Handling Techniques

Checking for Null

One of the most common edge cases that can cause your app to crash is when something expects a value but instead is given null.

In the View

What happens if what’s passed in for the Movie is Null? Currently, our code doesn’t have any error handling and we get this messy looking error we just discussed.

.Unhandled Exception

If some value could possibly be null (even if you don’t think it will be), it’s best to add a check and specify what you want to happen in that null case.

For example, we could add a null check to our view like this:

@model Movie

@if (Model == null)
{
    <p>No movie details available.</p>
}
else
{
    <h1>Movie Details</h1>

    <p>Title: @Model.Title</p>

    <p>Genre: @Model.Genre</p>
}

In The Controller

Take a look at this action from the MoviesController.cs.

 // GET: /Movies/<id>
[Route("Movies/{id:int}")]
public IActionResult Show(int id)
{
    var movie = _context.Movies.Find(id);
    return View(movie);
}

With your partner: Discuss what might be null here? What might happen if a value is null?

Let’s add a null check to our controller

[Route("Movies/{id:int}")]
public IActionResult Show(int id)
{
    var movie = _context.Movies.Find(id);
    if (movie == null)
    {
        return NotFound();
    }

    return View(movie);
}

What if our controller looked like this, with an optional parameter?

 // GET: /Movies/<id>
[Route("Movies/{id:int}")]
public IActionResult Show(int? id)
{
    var movie = _context.Movies.Find(id);
    return View(movie);
}

Then we would need to do two null checks!

// GET: /Movies/<id>
[Route("Movies/{id:int}")]
public IActionResult Show(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = _context.Movies.Find(id);
    if (movie == null)
    {
        return NotFound();
    }

    return View(movie);
}

This is much less likely to break, you will also hear this referred to as more “robust code”.

With your partner: Discuss if you think it’s better to have null checks in the controller, in the view, or both?

Data Validations

What would happen if I put an incredibly long title in for a movie? At some very very very large point it might be to large for our SQL database and throw an error, and even before that a massive title has the potential to mess up the usability of our website.

Or what would happen if someone input a string for a field that can only store an integer? Unless we have some error handling, that would cause an error when we try to write to the database.

The way around these and many other potential problems is to add Data Validations. Data validations are checks and rules that are applied to data to ensure its accuracy, integrity, and consistency. The purpose of data validations is to prevent bad data from entering a system, database, or application.

The goal of our data validation is to prevent writing invalid data to the database and instead show users a helpful error message.

.NET Error

Data Validation Step 1: The Model

Let’s start by adding attributes to our model for the validations we want, there are TONS of available validations, today we will use a couple common ones.

 public class Movie
    {
        public int Id { get; set; }

        [Required(ErrorMessage = "Title is required")]  // This is new
        [StringLength(100, ErrorMessage = "Title cannot exceed 100 characters")]  // This is new
        public string Title { get; set; }

        [Required(ErrorMessage = "Genre is required")]  // This is new
        public string Genre { get; set; }
        public List<Review> Reviews { get; set; } = new List<Review>();
    }

Data Validation Step 2: The Controller

Now we need to add a check to see if the incoming data meets these validations in our MoviesController.cs.

[HttpPost]
public IActionResult Index(Movie movie)
{
    //ModelState.IsValid is a built in .NET tool we can use that will check all of the validations on our models and then attach any error messages to the object. 
    if (ModelState.IsValid) // This is new
    {
        //Take the movie sent in the request and save it to the database
        _context.Movies.Add(movie);
        _context.SaveChanges();

        // The id generated by the database is now on the object we added to the context
        var newMovieId = movie.Id;

        // Redirect to our route /movies/show and pass in the newMovieId for the id parameter
        return RedirectToAction("show", new { id = newMovieId });
    }
    else // This is new
    {
        // WHAT DO WE WANT TO DO IF VALIDATION FAILS?
    }
}

With your partner: Discuss what you think we want to happen if the validation fails on the movie passed in.

What we want to do

We want to send the user to the New page and pass in the error information to display for the user. The error info is all on the model so we will pass the model to the view.

//Render the New view and pass in the movie so that we have the validation information
return View("New", movie);

Data Validation Step 3: The View

We need to allow a movie to get passed into our view and display any available validation messages.

@model Movie <----This is new

<h1>Add a Movie</h1>

<form method="post" action="/movies">
    <div class="form-group">
        <label for="Title">Title:</label>
        <input type="text" id="Title" name="Title" />
        <span asp-validation-for="Title" class="text-danger"></span> <--- This is new
    </div>
    <div class="form-group">
        <label for="Genre">Genre:</label>
        <input type="text" id="Genre" name="Genre" />
        <span asp-validation-for="Genre" class="text-danger"></span>  <--- This is new
    </div>
    <button type="submit">Create Movie</button>
</form>

It’s out of scope of this lesson, but if you want to display what the user previous input I recommend looking into .NET tag helpers, specifically asp-for.

This type of validation we implemented is called server-side validation. because the validation logic is sent to the view from the server. There is another type of validation called client-side validation that takes place in the browser. You can learn more about .NET client-side validation in this article.

Try/Catch Blocks

A try-catch block is a programming concept that allows you to handle potential errors or exceptions that might occur during the execution of your code.

The “try” section contains the code you want to execute, and if an exception occurs within it, the “catch” section is run, letting you handle the exception gracefully by providing appropriate error-handling code.

Let’s take a look at an example:

var filePath = "c:/example.txt";
try
{
    using (StreamReader sr = new StreamReader(filePath)) {
        string line;

        // Read and display lines from the file until 
        // the end of the file is reached. 
        while ((line = sr.ReadLine()) != null) {
            Console.WriteLine(line);
        }
    }
}
catch (Exception ex)
{
    Console.WriteLine($"Error: The file '{filePath}' was not found.");
}

Because we are “catching” the error that occurred in the try block, the program will not crash it will just log the error!

Don’t Swallow Errors

Take a look at this second example of a try/catch block. This is an example of what in the software engineering industry gets referred to as “Swallowing an error”

try
{
    int dividend = 10;
    int divisor = 0;

    int result = dividend / divisor; // This will cause a divide by zero exception

    Console.WriteLine("Result: " + result);
}
catch (Exception ex)
{
}

Console.WriteLine("Program continues on with whatever else it was supposed to do.");

With your partner: Discuss what you think “Swallowing an error” means? Start by thinking on your own based on this example then feel free to google. Then discuss why it’s bad practice to swallow errors.

In our current applications we don’t have any places we really need a try/catch block, but soon when we are calling external APIs from our applications this tool will be very useful.

Check for Understanding

With your partner: Discuss how you would answer the following interview question.

What do you know about error handling in a .NET application? What are best practices for error handling that you prioritize, and how have you implemented these practices in the applications you’ve developed?

You might find it helpful to look back on the four error handling rules that you learned about in today’s preparation.

Lesson Search Results

Showing top 10 results