• 8 hours
  • Medium

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 12/2/19

Modify the Controllers of Your MVC Application

Security First

Whenever you build a new ASP.NET web application, you have the option to add authentication. For your Watchlist application, you chose authentication with individual user accounts. Authentication is simply the process by which your application verifies a user’s identity, typically via username and password. This capability is already set up, and you’ve verified this by registering a new user account.

There is another security mechanism in ASP.NET called authorization. You’ll learn a lot more about it in the .NET Security course, but I want to give you a brief introduction to it here. For simplicity’s sake, authorization is how you decide what parts of your site you will allow users to access. For example, do you want to let unregistered users view your subscribers’ watchlists? Can they add, edit, or delete movies from your database? Authorization lets you control these things through the Authorize data attribute.

It’s not difficult to implement. A good practice is to make a list of the controllers and controller actions in your application and give each one an access level. Start with these three access categories:

  1. All users

  2. Registered (authenticated) users

  3. Specific types of registered users

  4. Specific users

Once you have identified the type of access your controller classes and controller actions (methods) require, it’s time to add the appropriate data attribute to that class or method. You can secure an entire class or individual methods, and the syntax is simple. For example, if you want to allow only registered users to access anything within the MoviesController class, you would add the Authorize data attribute directly above the class declaration. To use this attribute, you will need to include a new using statement as well:

using Microsoft.AspNetCore.Authorization;

[Authorize]
public class MoviesController : Controller
{
    ...
}

Using this attribute above the class definition secures the entire class. A request for any of the methods within the class will require the user to be authenticated via username and password. If the user is not logged in, he or she will automatically be redirected to the Login page.

You can also secure individual methods while leaving others within the same controller open. To do this, omit the Authorize attribute from above the class definition, and instead add it above each method you wish to secure. Only those methods will require the requesting user to be authenticated.

We could discuss more authorization, such as how to secure sections of your application based upon user type or even by specific username, but as it’s not crucial to this course and project right now, let’s save further discussion for the ASP.NET Security course.

Binding the Data

Controller actions receive data coming from HTTP requests. Such data is not coming in ready-to-use .NET data types. If you had to write code to retrieve each value and convert it from a string to the appropriate .NET type, it would be an arduous process. The dynamic model takes care of this for you. Dynamic model binding:

  • Retrieves submitted data from URL routes, form fields, and query strings.

  • Delivers the data to the controller action via method parameters.

  • Converts the string data to the appropriate .NET data types.

By default, all model properties being posted from a form are bound to the corresponding controller action through the bind syntax. It looks like this:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Title,Year")] Movie movie)
{
    if (ModelState.IsValid)
    {
        _context.Add(movie);
        await _context.SaveChangesAsync();
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

If a property is omitted from the bind list, its value is not included with the incoming object payload. Be sure that, if you add properties to your data models after scaffolding your controllers, you insert those new properties into the bind lists in your controller actions.

Add New Watchlist Functionality

In the last chapter, we made several changes to the movies Index page so that you could easily add and remove movies to and from the user’s watchlist. With the front end work complete in that regard, you need to make several changes to the MoviesController so your changes to the view will work. 

First, you need the user’s Id to build the user’s watchlist from the database. This means you need access to the UserManager, which you can inject into the controller through its constructor, just like you do with the database context object. For example:

public class MoviesController : Controller
{
    private readonly ApplicationDbContext _context;
    private readonly UserManager<ApplicationUser> _userManager;

    public MoviesController(ApplicationDbContext context,
        UserManager<ApplicationUser> userManager)
    {
        _context = context;
        _userManager = userManager;
    }

    ...
}

Next retrieve the current user’s data using the UserManager.

public class MoviesController : Controller
{
    private readonly ApplicationDbContext _context;
    private readonly UserManager<ApplicationUser> _userManager;

    public MoviesController(ApplicationDbContext context,
        UserManager<ApplicationUser> userManager)
    {
        _context = context;
        _userManager = userManager;
    }

    [HttpGet]
    public async Task<string> GetCurrentUserId()
    {
        ApplicationUser usr = await GetCurrentUserAsync();
        return usr?.Id;
    }

    private Task<ApplicationUser> GetCurrentUserAsync() =>
    _userManager.GetUserAsync(HttpContext.User);

    ...
}

Now that you can get the user’s Id, you can figure out which movies are in the user’s watchlist so you can build the model. Right now, the index method sends a list of movie objects to the Index page. You need to change that to a list of MovieViewModel objects.

public class MoviesController : Controller
{
    ...

    public async Task<IActionResult> Index()
    {
        var userId = await GetCurrentUserId();
        var model = await _context.Movies.Select(x => 
            new MovieViewModel 
            { 
                MovieId = x.Id, 
                Title = x.Title, 
                Year = x.Year 
            }).ToListAsync();
        foreach(var item in model)
        {
            var m = await _context.UserMovies.FirstOrDefaultAsync(x =>
                x.UserId == userId && x.MovieId == item.MovieId);
            if (m != null)
            {
                item.InWatchlist = true;
                item.Rating = m.Rating;
                item.Watched = m.Watched;
            }
        }
        return View(model);
    }
}

Now you can add the AddRemove method to the MoviesController. You need this method to return a JsonResult object instead of a view. That way, you can update the DOM for the Index page without reloading the entire page. Keep track of whether the movie is being added to or removed from the watchlist, and return a value accordingly. The easiest way may be to return -1 if there is no change, 0 if the movie is removed, and 1 if the movie is added. Define the method like this:

[HttpGet]
public async Task<JsonResult> AddRemove()
{

}

Now add a variable to store the return value and retrieve the user’s Id.

[HttpGet]
public async Task<JsonResult> AddRemove()
{
    int retval = -1;
    var userId = await GetCurrentUserId();
}

Next, check the value of the val parameter to see if you’re adding or removing. If the value is 1, the movie is already in the watchlist and, therefore, should be removed. If the value is 0, the movie is not in the watchlist and should be added.

[HttpGet]
public async Task<JsonResult> AddRemove()
{
    int retval = -1;
    var userId = await GetCurrentUserId();
    if (val == 1)
    {
        // if a record exists in UserMovies that contains both the user’s
        // and movie’s Ids, then the movie is in the watchlist and can
        // be removed
        var movie = _context.UserMovies.FirstOrDefault(x => 
            x.MovieId == id && x.UserId == userId);
        if (movie != null)
        {
            _context.UserMovies.Remove(movie);
            retval = 0;
        }

    }
    else
    {
        // the movie is not currently in the watchlist, so we need to
        // build a new UserMovie object and add it to the database
        _context.UserMovies.Add(
            new UserMovie
            {
                UserId = userId,
                MovieId = id,
                Watched = false,
                Rating = 0
            }
        );
        retval = 1;
    }
    // now we can save the changes to the database
    await _context.SaveChangesAsync();
    // and our return value (-1, 0, or 1) back to the script that called
    // this method from the Index page
    return Json(retval);
}

Let's Recap!

  • In this chapter, you learned how to tighten up security in your controllers with the Authorize attribute.

  • You also learned how .NET MVC binds data attributes in the views to actions (methods) in the controllers.

  • And finally, you made several modifications to your MoviesController, so the changes you made in the last chapter to the index view will work as you intended, completing the functional specification for your Watchlist application.

Time to add the window dressing!

Example of certificate of achievement
Example of certificate of achievement