Skip to content
This repository has been archived by the owner on May 23, 2024. It is now read-only.

00 Details on updates targeting .NET Core 2.0

Cesar De la Torre edited this page Nov 15, 2017 · 5 revisions

Background

Earlier this year, we published an eBook and sample application offering guidance for Architecting Modern Web Applications with ASP.NET Core and Microsoft Azure.

You can read more about the goals for this eBook and its initial publication here.

We have recently published updates to the eBook and sample application to bring them in line with the latest releases of ASP.NET Core 2.0 and Entity Framework Core 2.0.

This post describes some of the highlights of the changes. Note that the sample project is open source and you can contribute by filing issues (if you have questions or discover problems) and submitting pull requests.

What’s New in the Web apps architecture guidance with .NET Core 2.0

The eBook has been updated to ASP.NET Core 2.0, .NET Standard Library 2.0 and Entity Framework 2.0 and .NET Core 2.0 in general, while referencing to the new code implemented in the updated sample app.

The sample application has been expanded to support additional functionality over what existed in the initial version. Specifically, in addition to allowing users to manage a shopping cart, log in, and check out, users can now view past orders. Orders also include shipping address data, which is implemented as a value object using the new owned entities in EF Core 2.0.

All of the projects have been updated to the ASP.NET 2.0, netstandard 2.0, and EF Core 2.0, as appropriate.

Also, the identity management system has been updated to use the latest ASP.NET Identity 2.0 version, which includes built-in support for Two Factor Authentication (2FA) using an Authenticator app.

In addition, the sample now makes use of the Specification pattern to improve the design of its data access and better follow separation of concerns. These additional application features were added to help make the sample more “real world” in nature, providing more opportunities to demonstrate new features and techniques available in ASP.NET Core.

Finally, one of the new features added in ASP.NET Core 2.0 is Razor Pages, and so the eBook and the sample application have been updated to demonstrate side-by-side in separate web projects both a Razor Pages implementation and a Controllers-and-Views implementation of the same business application.

Upgrading to ASP.NET Core 2.0 (and EF Core 2.0)

There is great guidance on how to upgrade your apps from 1.x to 2.0 of ASP.NET Core available in the docs.

For this application, the upgrade process went relatively smoothly. One change in the design of EF Core migrations that’s worth mentioning has to do with seed data. In the 1.x version of the sample, seed data was added during the call to ConfigureServices in Startup. In EF Core 2.0, migrations load and run Startup (and thus ConfigureServices) before they create the data structures. This of course means that if you’re trying to add data during Startup, it’s not going to work since the migrations haven’t yet created the necessary data schema. This issue is easily addressed by moving your code to add seed data out of Startup – in this case into Program.cs. This has the added benefit of making it easier for your functional tests to seed data, too, without having to clutter your production Startup class with test data.

Below is the code used to add seed data:

public class Program
    {
        public static void Main(string[] args)
        {
            var host = BuildWebHost(args);

            using (var scope = host.Services.CreateScope())
            {
                var services = scope.ServiceProvider;
                var loggerFactory = services.GetRequiredService<ILoggerFactory>();
                try
                {
                    var catalogContext = services.GetRequiredService<CatalogContext>();
                    CatalogContextSeed.SeedAsync(catalogContext, loggerFactory)
            .Wait();

                    var userManager = services.GetRequiredService<UserManager<ApplicationUser>>();
                    AppIdentityDbContextSeed.SeedAsync(userManager).Wait();
                }
                catch (Exception ex)
                {
                    var logger = loggerFactory.CreateLogger<Program>();
                    logger.LogError(ex, "An error occurred seeding the DB.");
                }
            }

            host.Run();
        }

        public static IWebHost BuildWebHost(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseUrls("http://0.0.0.0:5106")
                .UseStartup<Startup>()
                .Build();
    }

Note that the upgraded version of the app now makes use of CreateDefaultBuilder, which also builds the app’s configuration system and injects it into Startup’s constructor (rather than having the Startup constructor create it).

EF Core 2.0 and Value Objects

Using Value Objects, you can model types that are not defined by a key, like an Entity is. Two separate instances of a value object are considered equal if all of their properties are the same. Support for value objects is added through a new feature in EF 2.0, owned types. An physical mailing address is an example of a value object, since any two such addresses are considered to be the same as long as their properties (street, city, state, etc.) are all the same. In the eShopOnWeb sample, customer orders include a ShipToAddress, which is of type Address. This type is associated with the order as an owned type through the following configuration:

// builder is an instance of EntityTypeBuilder<Order>
builder.OwnsOne(o => o.ShipToAddress);

With this configuration in place, when EF Core creates the Order table in the database, it uses the following schema:

image

Each property of the Address type is stored in the Orders table as a separate column, prefixed with the associated property name. In an eCommerce app, it’s a good idea to store a copy of the shipping address information with each order, rather than a foreign key reference. That way, when a customer updates their address at some future date, it doesn’t change the shipping address associated with past orders. This presents a good use case for a value object.

Updated Identity (ASP.NET Identity 2.0)

The app now uses the 2.0 version of authorization and authentication support. This resulted in a minor change in the Startup Configure method, switching from app.UseIdentity() to app.UseAuthentication(). Aside from this change and using the newer Nuget packages for AspNetCore, nothing else needed to change in terms of how the app dealt with auth. However, additional functionality to allow users to manage their accounts was added in this version, borrowing code that’s available in the application templates for ASP.NET Core MVC (and Razor Pages) apps. The new functionality allows users to change their password or set up 2FA using an Authenticator app:

image

After installing an Authenticator app on their phone, user’s can use the app to enter a verification code, providing them with enhanced security over just a username/password combination:

image

Adding Specification pattern Support

A common problem in many apps that use EF Core (or similar object/relational mappers) is that data access concerns can leak across boundaries, resulting in this code polluting business logic and user interface logic. Alternately, if an abstraction like the Repository pattern is being used, the abstraction may grow larger and larger to accommodate more and more different ways to query or retrieve data based on client needs.

Using specification objects can keep your repositories small and focused on data access while also providing you with a library of named, tested, reusable query objects.

In the eShopOnWeb sample, when the shopping basket is fetched from persistence, its Items property should be populated. The app is using a generic repository for its basic CRUD data access, and naturally this generic repository can’t know on its own that the Basket entity should call .Include(b => b.Items).

A common solution to this dilemma is to create a new type, BasketRepository, which inherits from the generic EfRepository and overrides the GetById method (and any others as required) simply to add this eager loading logic. Since many types will require custom eager loading, this can result in a lot of separate repository types. Frequently, you’ll also find the need to make custom queries, too. For instance, when fetching a shopping basket, the app may not know the basket’s ID, but instead may only know a unique identifier corresponding to an anonymous user of the system. In that case, we might need to add a GetBasketByBuyerId method to BasketRepository. And at that point, you’ll need a new IBasketRepository interface that includes this method, so that you can inject it into your controllers and services that need this functionality.

An alternative approach is to define a specification which includes both the criteria and the eager loading rules for a given query. The BasketWithItemsSpecification can be used to fetch a basket given either a basket ID or a buyer ID, and will eager load the basket’s associated Items property:

public class BasketWithItemsSpecification : BaseSpecification<Basket>
    {
        public BasketWithItemsSpecification(int basketId)
            :base(b => b.Id == basketId)
        {
            AddInclude(b => b.Items);
        }
        public BasketWithItemsSpecification(string buyerId)
            :base(b => b.BuyerId == buyerId)
        {
            AddInclude(b => b.Items);
        }
    }

In the generic EF Core repository, support for specifications is added by simply adding a method that accepts a specification and uses it to perform the necessary query commands. Client code can then use this specification when working with the repository like so:

        public async Task<int> GetBasketItemCountAsync(string userName)
        {
            var basketSpec = new BasketWithItemsSpecification(userName);
            var basket = (await _basketRepository.ListAsync(basketSpec)).FirstOrDefault();
           // more code omitted
        }

Migrating to Razor Pages

The last significant update made to the sample as part of this release is the addition of a separate web project that uses Razor Pages.

Copying the existing sample application to Razor Pages was straightforward, and resulted in a significant reduction in the number of folders and files required for the app. Although some ViewModel files remain, many of these types were incorporated directly into their associated page’s PageModel class.

Working with a particular feature, such as the user’s list of previous orders, only requires looking at two connected files in one folder in the Razor Pages version. In the MVC version, this same feature would require working with controllers, views, and view models all in separate folders within the project.

Although Razor Pages use fewer folders and files, they don’t suffer from a lack of solid architecture. They’re built on the same ASP.NET Core MVC packages and types and leverage the same features like routing, model binding, and filters. PageModel types support dependency injection and can support unit testing and functional testing as readily as MVC controllers.

We hope developers will load these two web projects and see how they compare as they’re evaluating where it makes sense to start using Razor Pages in their ASP.NET Core apps.

Summary

With the availability of ASP.NET Core 2.0 and EF Core 2.0, it’s a great time to start building apps on .NET Core. Using these frameworks and tools, developers can follow well-established principles and practices to write high-quality, testable, and maintainable software. Check out the extensive guidance offered in the Architecting Modern Web Applications with ASP.NET Core 2.0 and Azure eBook and the associated eShopOnWeb sample application to learn more.

Clone this wiki locally