© Lee Naylor 2016

Lee Naylor, ASP.NET MVC with Entity Framework and CSS , 10.1007/978-1-4842-2137-2_12

12. Introduction to ASP.NET Core v1.0 (MVC6 and EF 7)

Lee Naylor

(1)Newton-le-Willows, Merseyside, UK

At the time of writing this book, Microsoft was working on the next version of .NET, known as .NET Core v1.0. This contains some significant changes to .NET, with the biggest aim to make it more platform independent. Due to the size of the changes, the release is going to be named ASP.NET Core v1.0. Originally it was going to be known as ASP.NET v5 and ASP.NET MVC included in this release was going to be MVC v6. Entity Framework was going to be known as v7; however, everything is now included under the single umbrella of ASP.NET Core v1.0.

As we write this, the available version of ASP.NET Core is v1.0. One of the most significant changes to ASP.NET MVC Core is an update to the way HTML tags are generated in scaffolded views, using a new feature known as tag helpers.

Note

To complete this chapter, you must have installed Visual Studio 2015 Update 3 in order to access ASP.NET Core v1.0. The chapter does not require any source code downloads or any of the previous chapters to have been completed.

Creating an ASP.NET Core v1.0 MVC Project

The following is an example of working with MVC6 and ASP.NET Core v1.0 to build a simple Baby Store web site. It will show products and categories and use two database contexts—one for the store data and one for authentication—as in the MVC5 project earlier in the book.

Start by creating a new ASP.NET Core web application using .NET Framework 4.6.1 in Visual Studio and name it BabyStoreCore, as shown in Figure 12-1.

A419071_1_En_12_Fig1_HTML.jpg
Figure 12-1. Creating the BabyStoreCore ASP.NET core web application

In the next window, choose a Web Application template type and leave the authentication set to the Individual User Accounts, as shown in Figure 12-2.

A419071_1_En_12_Fig2_HTML.jpg
Figure 12-2. Choosing a Web Application template

When you click the OK a button, a new solution will be created. The solution contains a different structure than MVC5 projects and now contains two top-level folders—one named Solution Items and one name src. The Solution Items folder will contain a global.json file containing some simple information about the solution in JSON format, containing entries for projects and SDK.

The BabyStoreCore project is located in the src folder and already contains several folders that you will be familiar with from the MVC5 projects. Note that we did not specify we were creating an MVC project when we chose the template type, yet an MVC project was created. This is due to the fact that Microsoft is aiming to move web development away from Web Forms and aims only to support MVC projects in .NET Core.

Adding Product and Category Models

Add a new class named Category to the Models folder and update the content to add properties to represent the ID, Name, and a navigational property as an ICollection of Products.

using System.Collections.Generic;

namespace BabyStoreCore.Models
{
    public class Category
    {
        public int Id { get; set; }
        public string Name { get; set; }


        public virtual ICollection<Product> Products { get; set; }
    }
}

Now add a new Product class to the Models folder and update the content to add an ID, Name, Description, and Price. Then add a CategoryID and navigational property to a Category as follows:

using System.ComponentModel.DataAnnotations;

namespace BabyStoreCore.Models
{
    public class Product
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        [DisplayFormat(DataFormatString = "{0:c}")]
        public decimal Price { get; set; }
        public int? CategoryID { get; set; }


        public virtual Category Category { get; set; }
    }
}

These classes work the same as the other simple classes used in the Models folder during the MVC5 projects and model a zero-to-many relationship between a category and products (a category can have several products and a product can belong to none or one category).

Adding a Database Context

We’re going to follow the pattern we used for MVC5 projects and create a separate database context class for the store data rather than use the existing user database. This time though we’re going to add the file to the Models folder rather than create a DAL folder. Add a database context class called StoreContext to the Models folder as follows. The class derives from DbContext and contains properties to represent each table in the database. Note that DbContext is now part of the Microsoft.EntityFrameworkCore namespace rather than Microsoft.Data.Entity:

using Microsoft.EntityFrameworkCore;

namespace BabyStoreCore.Models
{
    public class StoreContext : DbContext
    {
        public StoreContext(DbContextOptions<StoreContext> options) : base(options) { }


        public DbSet<Category> Categories { get; set; }
        public DbSet<Product> Products { get; set; }
    }
}

The class contains a constructor method, which takes an input parameter named options of the type DbContextOptions<StoreContext>, which is passed to the constructor of the base class (DbContext). This ensures the context is created with a set of options, such as which provider to use (in this case SQL Server). The options are specified in the Startup.cs file, as you will see later.

Seeding the Database with Test Data

Data seeding is done differently in ASP.NET Core (EF7 and MVC6) than with previous versions. To seed the database with some test data, you add a new class named SeedData to the Models folder, as follows:

using Microsoft.Extensions.DependencyInjection;
using System;
using System.Linq;


namespace BabyStoreCore.Models
{
    public class SeedData
    {
        public static void Initialize(IServiceProvider serviceProvider)
        {
            var context = serviceProvider.GetService<StoreContext>();


            if (context.Database == null)
            {
                throw new Exception("DB is null");
            }


            if (context.Products.Any())
            {
                return;   // DB has been seeded
            }


            var feeding = context.Categories.Add(new Category { Name = "Feeding" }).Entity;
            var sleeping = context.Categories.Add(new Category { Name = "Sleeping" }).Entity;


            context.Products.AddRange(
                new Product
                {
                    Name = "Milk",
                    Description = "Tasty anti-reflux milk",
                    Price = 9.99M,
                    Category = feeding
                },
                new Product
                {
                    Name = "SleepSuit",
                    Description = "Comfortable sleep wear",
                    Price = 3.99M,
                    Category = sleeping
                }
             );


            context.SaveChanges();
        }
    }
}

The SeedData contains a method named Initialize and later in the text we will add a call to this method when the application starts up via the Startup.cs file. This method takes an input parameter of the type IServiceProvider named serviceProvider. ASP.NET Core MVC works based on the concept of services, where a service is a feature accessible to the project. This method attempts to obtain the StoreContext service, so we'll need to add this to the services available to the project. We will cover this shortly. The code then checks to determine if the database is null or has been seeded already. If has not been seeded, the new test data is created and added using the Context object in a similar way to the rest of the book (see Chapter 4 for an explanation on seeding with MVC5 and EF6).

Configuring the Connection String for the Database

For ASP.NET Core projects, configuration is no longer performed in a Web.Config file; it is instead done via JSON in the appsettings.json file. To add a new connection string for the StoreContext and to create a database named BabyStoreCore_v1, add the following bold entry to the appsettings.json file:

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\mssqllocaldb;Database=aspnet-BabyStoreCore-4a7d214c-3ec1-4ee6-8ffb-a34de98bdc02;Trusted_Connection=True;MultipleActiveResultSets=true",
    "StoreConnection": "Server=(localdb)\mssqllocaldb;Database=BabyStoreCore_v1;Trusted_Connection=True;MultipleActiveResultSets=true"
  },
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

This will create the database under your user folder in Windows in the folder C:Users<UserName>, where <UserName> is the Windows account you are logged into.

Configuring the Project to Use the SeedData Class and StoreContext

We now need to configure the application to use the SeedData class, the StoreContext class, and the new connection string. Edit StartUp.cs to add the following to the ConfigureServices method so it can use the new StoreConnection connection string and StoreContext class:

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));


    services.AddDbContext<StoreContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("StoreConnection")));


    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();


    services.AddMvc();

    // Add application services.
    services.AddTransient<IEmailSender, AuthMessageSender>();
    services.AddTransient<ISmsSender, AuthMessageSender>();
}

Next, you’ll update the Configure() method to add a call to the Initialize method of the SeedData class. Add this line of code to the end of the method:

SeedData.Initialize(app.ApplicationServices);

Using Migrations to Create the Database

First, you must build the solution. Migrations in ASP.NET MVC Core no longer run in the Package Manager Console. They instead run in the command window. To create and populate the database, run the following steps:

  1. Open a command prompt and navigate to the ...BabyStoreCoresrcBabyStoreCore directory.

  2. Run the dotnet ef migrations add Initial -c StoreContext command. This adds a migration called Initial to the project using the StoreContext context file, which will target the database that stores products and categories rather than the database that stores users and roles.

    • A new file named <timestamp>_Initial.cs will now have been created under the Migrations folder, where <timestamp> is a numeric representation of the time the file was created. This folder also includes a file named StoreContextModelSnapshot.cs which, as its name suggests, contains a snapshot of the models backing the StoreContext file. This allows Entity Framework to differentiate against the current model and it then uses this to scaffold the operations in order to bring the database in line with the current models.

  3. Create the database by running the dotnet ef database update -c StoreContext command. This will probably take a few seconds to run and is completed when the Done message appears in the command window. The database will now be visible in SQL Server Object Explorer; however, it will not contain any data.

  4. To populate the database, start the project without debugging. The Category and Product tables will now be populated with the data from the SeedData class.

Adding Controllers and Views

We’re now going to add two controller classes for Products and Categories to get a simple site up and running with the new data. Right-click on the Controllers folder and choose the Add ➤ Controller option. In the Add Scaffold window, choose the MVC Controller with Views, Using Entity Framework option, as shown in Figure 12-3.

A419071_1_En_12_Fig3_HTML.jpg
Figure 12-3. Adding a new MVC controller with views, using Entity Framework

Click the Add button. Then, in the Add Controller window, add a new controller named CategoriesController with the Model class set to Category, the Data context class set to StoreContext, and all the options for in the views section checked, as shown in Figure 12-4.

A419071_1_En_12_Fig4_HTML.jpg
Figure 12-4. Adding the CategoriesController

Click the Add button to create the new CategoriesController. The controller class will be created with the scaffolded methods for Index, Details, Create, Edit, and Delete, similar to those found in MVC5; however, all the methods that query the database are asynchronous by default in ASP.NET Core. One point to note is that the class no longer creates an instance of StoreContext in the controller itself; instead, it is passed in via the constructor as follows:

public class CategoriesController : Controller
{
    private readonly StoreContext _context;


    public CategoriesController(StoreContext context)
    {
        _context = context;    
    }

ASP.NET Core is geared toward supporting dependency injection to avoid hard-coding dependencies in the code. This is an example of where the generated code no longer hard-codes an instance of the object it requires; instead, it is passed into the class (also known as "injection"). The line of code

services.AddDbContext<StoreContext>(options =>                    
    options.UseSqlServer(Configuration.GetConnectionString("StoreConnection")));

that was added to the StartUp.cs file earlier ensures that whenever the project requires an instance of the type StoreContext it will use the StoreContext class. The AddDbContext method deals with creating the instance of StoreContext.

Repeat the process of adding a controller and add a new ProductsController using the Model Class Product and the Data context class StoreContext. Then generate the views. You should now have a ProductsController and a CategoriesController and the views folder should contain Products and Categories folders containing the related views for each method in the respective controllers.

Viewing the Data in the Web Site

First add some new hyperlinks to the Products and Categories controller in the ViewsSharedLayout.cshtml file:

<div class="navbar-collapse collapse">
    <ul class="nav navbar-nav">
        <li><a asp-controller="Home" asp-action="Index">Home</a></li>
        <li><a asp-controller="Home" asp-action="About">About</a></li>
        <li><a asp-controller="Home" asp-action="Contact">Contact</a></li>
        <li><a asp-controller="Categories" asp-action="Index">Categories</a></li>
        <li><a asp-controller="Products" asp-action="Index">Products</a></li>
    </ul>
    @await Html.PartialAsync("_LoginPartial")
</div>

Note that rather the using the HTML.ActionLink helper, MVC6 now supports a new concept known as tag helpers; the links to the products and categories controllers use the new Anchor Tag Helper. There are many tag helpers available and their aim is to make the markup of the views resemble HTML as much as possible while simplifying the code needed to generate HTML elements. For example, the new way of generating a hyperlink is neater than using the HTML ActionLink helper.

Start the application without debugging. In the home page, click on each of the new links you just added. They should display the category and product data, as shown in Figures 12-5 and 12-6, respectively.

A419071_1_En_12_Fig5_HTML.jpg
Figure 12-5. Viewing category data
A419071_1_En_12_Fig6_HTML.jpg
Figure 12-6. Viewing product data
Note

At the time of this writing, ASP.NET MVC does not contain tag helpers for displaying model fields. If you open ViewsProductsDetails.cshtml, you will see that the code used to display each of the product's field still uses the MVC5-style HTML helpers.

Correcting Bugs with the Scaffolding Generated Code

With the web site running, open the Products Index page and click on the Create New link. You will receive the error “NullReferenceException: Object reference not set to an instance of an object”. This bug is caused by issues with the scaffolded code in the Create and Edit methods in the ProductsController class.

Update both Create methods in the ControllersProductsController.cs file to change the data generated in each method so that the SelectList element generates the text element of each item in the list. This is generated from the Name property of a Category. Update the GET version of the Create method as follows:

// GET: Products/Create
public IActionResult Create()
{
    ViewData["CategoryID"] = new SelectList(_context.Categories, "Id", "Name");
    return View();
}

Now update the HttpPost version of the Create method as follows, in order to cover the creation of the SelectList when the model is not valid:

// POST: Products/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("ID,CategoryID,Description,Name,Price")] Product
    product)
{
    if (ModelState.IsValid)
    {
        _context.Add(product);
        await _context.SaveChangesAsync();
        return RedirectToAction("Index");
    }
    ViewData["CategoryID"] = new SelectList(_context.Categories, "Id", "Name",  
        product.CategoryID);
    return View(product);
}

Make the same changes to the SelectList code in the two versions of the Edit methods, since they also suffer from the same issue.

Summary

This has been a brief introduction to using MVC in ASP.NET Core. We covered creating a project, a basic model, and the database context. I then showed you how to seed the database with test data and update the project configuration for the new database connection string and use the new database context and seeding method. Finally, we covered adding some simple controllers and views using the scaffolding process.

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

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