Developing Controllers

An ASP.NET 5 MVC controller handles user requests to your site. Recall that the routing engine uses the URL convention to route a request to your controller and on to a method on that controller. For example, a request for ./customer/edit/5 will, by default, route to your CustomerController.Edit(id) method; the value 5 will be passed as the id parameter.

The controller method is then responsible for connecting the request to your model and returning the appropriate view result. The Edit(id) method, for example, will likely use a DbContext (created in the prior section) to find a specific customer from the database.

The controller would pass the Customer instance from the model to the Customer/Edit view page and return a ViewResult. Finally, the runtime would then likely look for a view called Edit inside the Views/Customer folder. As part returning results, ASP.NET would generate on the server, based on the view code, the appropriate HTML and JavaScript to be sent back as the response to the user machine.

This section covers creating controllers, working with the model, and returning an IActionResult to let MVC generate the right web response.

The Result Objects

An action method on an ASP.NET 5 MVC controller should return results that can be processed by ASP.NET and sent back to the user’s browser as a valid HTTP response. For the most part, these methods should return an object that implements either the IActionResult or IViewComponentResult interface (found in the Microsoft.AspNet.Mvc namespace). The former includes classes used to return HTML and JavaScript (ViewResults, PartialViewResult), JSON formatted messages (JsonResult), Files (FileResult, FileContentResult), and other result objects. The latter is new to ASP.NET 5 MVC. It is used to return a portion of the response. (See the section “View Components, View Models, and Partial Views.”)

You typically define your controller action methods as returning one of these interfaces (IActionResult being the most common). You then return an actual object that implements the interface as the response to the MVC runtime. When you do so, you use a method on the Controller class from which your controller inherits. It contains method names that match the action result objects, minus the word Result, as in View(), Json(), Redirect(), Content(), and so on.

These methods know how to act on your behalf. They find the view, often find a model, and return the related action result object. The MVC framework defines many of these action result classes as listed here:

Image ContentResult—Used to return a custom content type as the result of the action method. This is an HTTP content type, such as text/plain.

Image EmptyResult—Used to return nothing (void) as the result.

Image FileResult (FileContentResult, FilePathResult, FileStreamResult)—Used to send back binary output (and files) as the response.

Image HttpNotFoundResult—Used to send an HTTP status indicating that the requested resource was not found.

Image HttpStatusCodeResult—Used to send a specific HTTP status code (from the many available) as the result.

Image JsonResult—Used to return a message formatted as JSON.

Image NoContentResult—Used to indicate that the response has no actual content.

Image ObjectResult—Used to return an object as the result. (See Chapter 19.)

Image RedirectResult—Used to redirect to another URI.

Image RedirectToRouteResult (RedirectToActionResult)—Used to redirect to another action method.

Image PartialViewResult—Used to send a section of a partial view (portion of HTML) to be rendered inside another view.

Image ViewViewcomponentResult—Used to return a view component (portion of the response) as a result (implements IViewComponentResult).

Image ViewResult—Used to return a web page generated from a view.

Let’s now take a look at using these action result objects inside a controller.

Creating the Controller

Visual Studio 2015 provides an ASP.NET 5 MVC template for adding a controller to your project. This template already derives from Controller. You store controllers in the Controllers folder. When creating a new controller, you use the naming standard FeatureAreaController, where FeatureArea is the name of your feature (in our example, CustomerController). The following walks you through creating a controller:

1. Right-click the Controllers folder in Solution Explorer. Choose Add, New Item.

2. From the Add New Item dialog (see Figure 17.22), select MVC Controller Class. Name your controller CustomerController.cs. Click the Add button to continue.

Image

FIGURE 17.22 You add a new controller to an ASP.NET MVC project using the Add New Item dialog box.

3. Visual Studio creates a basic controller with an Index() method that returns an IActionResult (in this case, a ViewResult). Listing 17.7 shows an example.

LISTING 17.7 The CustomerController.cs Class Generated by the Controller Template


using Microsoft.AspNet.Mvc;

namespace AspNet5AppSample.Controllers
{
  public class CustomerController : Controller
  {
    //GET: /<controller>/
    public IActionResult Index()
    {
      return View();
    }
  }
}


Adding a DbContext

The CustomerController class will work closely with the model (your data access classes) to handle requests to read and write customer data. Therefore, the class needs an instance of your DbContext object, CustomerDbContext. Recall that you added this to the request pipeline earlier using the Startup.cs file ConfigureServices method. ASP.NET will pass this content to your controller provided you request it in the constructor. The following walks you through creating this code:

1. Open the CustomerController class file from Solution Explorer.

2. Add a using statement at the top of the class file for AspNet5Unleashed.Models.

3. Define a class-level variable to hold an instance of CustomerDbContext. Name this variable db, as in the following:

private CustomerDbContext db;

4. Create a constructor at the top of the class (under the class definition). You can use the snippet ctor as a shortcut.

Indicate that the constructor takes an instance of CustomerDbContext and then assigns that to the class-level variable db as in the following:

public CustomerController(CustomerDbContext context)
{
  db = context;
}

ASP.NET will now provide the database context object as part of the request when creating an instance of your controller. The next step is to define actual action methods on the controller for returning, editing, creating, and deleting customers. The following sections examine each in turn.

Returning a List of Customers

We will use the Index action method of the controller to return a list of customers from the model and pass it to a view page called Index.cshtml (which we will create later). This is a simple method. Remember, we already set up the database context inside a variable called db. Therefore, the following is all that is needed for the Index() action method.

public IActionResult Index()
{
  return View(db.Customers);
}

This method is set to return an IActionResult instance. This is common for most of your action methods (see the “The Result Objects” section) and indicates the result should be an action. In this case, the action will be to create a view. Therefore, the code uses the Controller.View() method to return a ViewResult instance. The actual view page is not named because you can rely on the framework to follow convention and look for a view called Index in the Views/Customer folder.

Notice that a list of customers is passed to the view using the model and data context. The view, as you’ll see later, will be strongly typed to expect a list of customers.

Returning a New Customer Page

Likely, the most simplistic controller method is one that only returns a view. In our example, a user requests to create a new customer. The data context is not needed for this (until the form is filled out and posted back to the server anyway). Therefore, you simply return a View. The following shows an example.

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

In this case, ASP.NET will look for a view called Create.cshtml in the Views/Customer folder. The view itself will know to bind to the Customer class for outputting validation rules and error messages. It will then return a view with essentially a blank customer so the user can enter new customer information.

Returning a Single Customer for Edit

We will now create an Edit(id) method to find a customer in the database using the Id property and return an Edit.cshtml view page (which we will also create later). This id parameter is passed to the action method from the URL using the ASP.NET routing engine convention. For example, a user may the request ./customer/edit/2. The value 2 will be passed to CustomerController.Edit(id) as a parameter.

The first step is to add a using statement to the top of the CustomerController class to add support for the LINQ query engine. The following shows an example.

using System.Linq;

Next, we write the method. The method should use LINQ to look up a customer from the DbContext. If it exists, it will return a ViewResult object. If not, it returns the HttpNotFound error (resulting in an HTTP 404 error page). An example of the code is shown here.

public IActionResult Edit(int id)
{
  Customer customer =
    db.Customers.FirstOrDefault(x => x.Id == id);

  if (customer == null)
  {
    return HttpNotFound();
  }
  return View(customer);
}

Accepting a Customer Edit (POST)

So far we have looked at action methods that simply return views. These action methods are called as part of HTTP GET requests. In the case of Edit(id) and Create(), a user must fill out a form. The form is then sent back to the server as an HTTP POST. What is needed then are action methods to handle these POST requests.

The convention is to define these action methods with the same name as their corresponding request action method. The difference is that these action methods include the attribute HttpPost to indicate the method should be called as part of an HTTP POST request. These action methods also take different parameters. The Create and Edit methods, for example, take a Customer object.

The following code shows an example of the additional Edit method.

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(
  [Bind(new string[] { "Id", "Name", "Email", "OptInEmail", "Notes"})]
  Customer customer)
{
  if (ModelState.IsValid)
  {
    db.Customers.Update(customer);
    db.SaveChanges();
    return RedirectToAction("Index");
  }
  return View(customer);
}

The Edit method uses the Bind model binder attribute to map form post fields to the Customer instance properties explicitly. This is a security precaution to prevent something called over posting. Over posting occurs when you maintain a property on your object that you do not expose on the form. This allows someone to post extra data to your form and, if lucky enough, set properties on your object you did not intend for him to be able to set. Using Bind is essentially whitelisting which properties you allow to be set; only those form fields will be bound to the Customer instance passed as a parameter to the action method. That is not required in this case because we allow all properties to be written. However, it is a good practice, and consistency is key. You may add a property later you do not want to bind, for example.

Notice that inside the method we first check to confirm the model state is valid. This executes the business rules you added to the model class as data annotations. If not valid, the Edit.cshtml view is returned and the errors are shown to the user. If the model is valid, we use the Update method to mark the customer for update and SaveChanges to commit the update to the database. Finally, upon completion, we return the user to the Customer/Index.cshtml page using the RedirectToAction action method.


Cross Site Request Forgery

Notice that the Edit method is decorated with the ValidateAntiForgeryToken attribute. This is to help prevent the security threat Cross Site Request Forgery (CSRF). CSRF is when another application tries to post to your controller. This attribute, along with the AntiForgeryToken() HTML helper in the view, helps validate calls to your controller.


Accepting a New Customer (POST)

Handling a HTTP POST for a new customer is nearly the same as handling a POST for customer edit. In fact, the only difference is the call to the DbContext; instead of Update, we use Add. The following shows the Create method for the Customer controller.

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(
  [Bind(new string[] { "Name", "Email", "OptInEmail", "Notes"})]
  Customer customer)
{
  if (ModelState.IsValid)
  {
    db.Customers.Add(customer);
    db.SaveChanges();
    return RedirectToAction("Index");
  }
  return View(customer);
}

Processing a Customer Delete Request

The last method we will add to the CustomerController is a method to handle a request to delete a customer. The method is similar to the one that simply returns a customer based on Id. In this case, the call will take an id parameter, use it to find the customer instance, and then use the DbContext to remove this customer. The following shows the code.

public IActionResult Delete(int id)
{
  Customer customer =
    db.Customers.FirstOrDefault(x => x.Id == id);

  db.Customers.Remove(customer);
  db.SaveChanges();

  return RedirectToAction("Index");
}

Note that in an actual application, you may not want to make it this easy to delete data. Instead, you might require this via a form submit (and HTTP POST), only by an authorized user, and provide an “are you sure” message for committing the data deletion.

You now have a model, a database, and a controller for working with customer objects. We have not, however, created views yet. Therefore, you cannot simply run this code and see your results. Let’s take a look at creating the views.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset