Udostępnij za pośrednictwem


Appendix A - How the MvcWebApi Web Service Works

patterns & practices Developer Center

On this page: Download:
Understanding the Flow of Control Through the Web Service | Routing Incoming Requests to a Controller | Transmitting Data Between a Controller and a Client | Using the Repository Pattern to Access Data | How the Entity Framework Repository Classes Work - How the Context Classes Store and Retrieve Data from the Database, How the Repository Classes Read and Save the Data for Objects | How the Repository Classes for Other Databases Work | Decoupling Entities from the Data Access Technology | How the Entity Framework and MongoDB Repository Classes use AutoMapper to Convert Data | How the Windows Azure Table Service Repository Converts Data | How the Neo4j Repository Converts Data | Instantiating Repository Objects | More Information

Download code samples

Download PDF

Order Paperback

The purpose of the MvcWebApi web service is to take REST requests sent from the user interface web application, validate these requests by applying the appropriate business logic, and then convert them into the corresponding CRUD (create, retrieve, update, and delete) operations in the various databases that contain information used by the Shopping application. This appendix describes how the developers designed and implemented the MvcWebApi web service to perform these tasks.

Understanding the Flow of Control Through the Web Service

Note

This section is a high level overview of the design of the MvcWebApi web service. The remaining sections in this appendix describe in detail how the elements that comprise the MvcWebApi web service operate.

The developers at Adventure Works needed the web service to provide the functionality to support the core business cases described in Chapter 2, "The Adventure Works Scenario." The system uses a collection of SQL and NoSQL databases, but the developers wanted to design a system that decoupled the data storage technologies from the business logic of the web service. This approach enables Adventure Works to switch a particular repository to use a different data storage technology if necessary, without impacting the business logic in the web service. It also helps to ensure that the low level details of the various databases are not visible to the user interface web application, and which could result in accidental dependencies between the user interface and the databases.

The MvcWebAPI web service exposes its functionality through a series of controllers that respond to HTTP REST requests. The section "Routing Incoming Requests to a Controller" later in this appendix describes the controllers and the requests that they support.

The business logic inside each controller stores and retrieves information in the database by using the Repository pattern. This pattern enabled the developers to isolate the database-specific aspects of the code inside a repository class, potentially allowing them to build different repository classes for different databases and easily switching between them. Each repository class manages the details of its connection to the underlying database, and takes responsibility for formatting the data in the manner expected by the database. The section "Using the Repository Pattern to Access Data" later in this appendix provides the details on how Adventure Works implemented this pattern in the web service.

Note

To enable the code to use different repositories without requiring that the web service is rebuilt and redeployed, the developers decided to use the Unity Application Block. The Unity Application Block allows a developer (or an administrator) to specify how an application should resolve references to objects at runtime rather than at compile time. The section "Instantiating Repository Objects" later in this appendix contains a description of how the developers at Adventure Works designed the MvcWebApi web service to support dynamic configuration by using the Unity Application Block.

The data that passes between the controllers and the repository classes are domain entity objects. A domain entity object is a database-neutral type that models business-specific data. It provides a logical structure that hides the way that the database stores and retrieves the data. The details of the domain entity objects used by the web service are described in the section "Decoupling Entities from the Data Access Technology" later in this appendix.

The response messages that the controllers create and send back to the user interface web application are HTTP REST messages, in many cases wrapping the data that was requested by the user interface web application, following the Data Transfer Object (DTO) pattern. The section "Transmitting Data Between a Controller and a Client" later in this appendix contains more information about the DTO objects created by the MvcWebApi web service.

Figure 1 illustrates the high level flow of control through the MvcWebApi web service. This figure depicts the user interface web application sending a request that is handled by the Products controller in the MvcWebApi web service. This controller retrieves information about products and product recommendations by using Product repository and ProductRecommendation repository objects. The repository objects can connect to the same database or to different databases (in the Shopping cart application, product information is held in a document database, and product recommendations are stored in a graph database). The data access logic is incorporated into a series of database-specific classes. The repository objects convert the information from a database-specific format into a neutral entity objects. The controller uses the entity objects to construct DTOs that it passes back in the REST responses to the user interface web application.

Figure 1 - The high-level flow of control through the MvcWebApi web service

Figure 1 - The high-level flow of control through the MvcWebApi web service

Note

IMPORTANT
The UI web application generates anti-forgery tokens that help to protect critical operations such as registering as a new customer, logging in, and placing an order (other operations, such as viewing the product catalog, expose data that is in the public domain and are not protected). However, the MvcWebApi web service currently implements only minimal security measures. Any application that knows the URL of the web service can connect to it and send requests. A real world implementation of this system would include more robust authentication and prevent unauthorized use, but in this reference implementation the additional code would obscure the data access guidance that this application is designed to convey.
Additionally, although the structure of the MvcWebApi separates the business logic of the web service out into discrete functional areas, this separation is driven by the implementation of the controllers and is not an attempt to implement a strict domain driven design (DDD).

Routing Incoming Requests to a Controller

Note

The MvcWebApi web service is implemented in the DataAccess.MvcWebApi project in the solution provided with this guide.

In common with most ASP.NET MVC4 applications, all incoming REST requests are routed to a controller based on the URI that the client application specifies. The controllers are implemented by using the controller classes defined in the Controllers folder of the DataAccess.MvcWebApi project. The following table describes each controller class and the public methods that they each contain:

Controller Class

Description

AccountController

Implements services that authenticate users and provide access to customer accounts and related information. This controller exposes the following operations:

  • Get. Returns the details for the customer that matches a specified customer ID.
  • Login. Authenticates the specified username and password.
  • Register. Adds the details of a new customer to the database.

CartController

Provides the following services that implement shopping cart functionality:

  • Get. Returns the list of items in the shopping cart that has a specified shopping cart ID.
  • Add. Adds the specified item to the shopping cart.
  • DeleteCartItem. Removes the specified item from the shopping cart.

CategoriesController

Implements services that provides access to the product categories in the database:

  • GetAll. Returns a list containing the details of every product category.

OrdersController

Provides the following operations that enable an application to retrieve order information and place an order for a customer:

  • Get. Returns the details of the specified order.
  • Post. Creates an order from the items in the customer's shopping cart and submits it to the order service which processes and handles the order.
  • Note that in the sample application, the order service simulates the process of handling an order but it illustrates good practice for handling idempotency and eventual consistency when you need to implement operations that span non-transactional data stores. The order service is described in detail in Chapter 8, "Building a Polyglot Solution."
  • GetHistory. Returns the list of order history documents for the customer. The document database maintains a full audit trail of each order for every customer.
  • GetHistoryDetails. Returns the details contained in an order history document.

ProductsController

Implements functionality that provides access to the product information in the database:

  • GetProducts. Returns a list containing the details of every product in a specified product subcategory.
  • Get. Returns the details of the product with a specified product ID.
  • GetRecommendations. Returns a list of products that customers who purchased the specified product also bought.

StatesController

Provides the following service that is used to return information about states and provinces (referenced by customer addresses) in the database:

  • Get. Returns a list containing the details of every state and province in the database.

SubcategoriesController

Implements the following operation that provides access to the subcategory information in the database:

  • GetSubcategories. Returns a list containing the details of every subcategory in a specified category.

Note

The MvcWebApi web service also defines the HomeController class. This class is the controller that is associated with the root URI of the web service, and it generates the default Home view for the website. This view displays a welcome page describing the ASP.NET Web API. The HomeController class and the Home view are not described further in this appendix.

The routes exposed by the MvcWebApi web service are defined in the static Register method of the WebApiConfig class, in the App_Start folder of the DataAccess.MvcWebApi project. The code example below shows the Register method of the WebApiConfig class, and illustrates how requests are routed to the appropriate controller:

public static void Register(HttpConfiguration config)
{
    // Subcategories
    config.Routes.MapHttpRoute(
        name: "Subcategories",
        routeTemplate: "api/categories/{categoryId}/subcategories",
        defaults: new {controller="Subcategories", action="GetSubcategories"});
            
    // Products
    config.Routes.MapHttpRoute(
        name: "Products",
        routeTemplate: "api/subcategories/{subcategoryId}/products",
        defaults: new { controller = "Products", action = "GetProducts" });

    config.Routes.MapHttpRoute(
        name: "Recommendations",
        routeTemplate: "api/products/{productId}/recommendations",
        defaults: new { controller = "Products", action = "GetRecommendations" });

    // Orders
    config.Routes.MapHttpRoute(
        name: "OrdersHistory",
        routeTemplate: "api/account/{personId}/orders",
        defaults: new { controller = "Orders", action = "GetHistory" });

     config.Routes.MapHttpRoute(
        name: "OrderHistoryDetails",
        routeTemplate: "api/orders/history/{historyId}",
        defaults: new { controller = "Orders", action = "GetHistoryDetails" });

    // Default
    config.Routes.MapHttpRoute(
        name: "DefaultApi",
        routeTemplate: "api/{controller}/{action}/{id}",
        defaults: new { action = RouteParameter.Optional, 
                        id = RouteParameter.Optional },
        constraints: new { action = "[a-zA-Z]+" });

    config.Routes.MapHttpRoute(
        name: "DefaultApiFallback",
        routeTemplate: "api/{controller}/{id}",
        defaults: new { id = RouteParameter.Optional });

    ...
}

The following table summarizes these routes and the data that they pass to the relevant controller, together with the expected response from the controller. Note that all routes begin with the literal "api".

Route name

Controller

Examples

Description

Subcategories

Subcategories

api/categories/1/subcategories

Returns a JSON array containing a list of subcategories for the specified category, or the Not Found HTTP status code if there is no subcategory with the specified ID.

The example returns an array containing the subcategories for the Bikes category (in the database, Bikes is category 1.)

Products

Products

api/subcategories/3/products

Returns a JSON array containing a list of products for the specified subcategory, or the Not Found HTTP status code if there is no matching subcategory.

The example returns an array of bicycles from the Touring Bikes subcategory (subcategory 3 in the database.)

Recommendations

Products

api/products/710/recommendations

Returns a JSON array containing a list of products and ratings that other customers who purchased the specified product also bought, or the Not Found HTTP status code if there is no matching product.

The example returns the list of products (and their ratings) that were bought by customers who also bought product 710.

OrdersHistory

Orders

api/account/156dfc64-abb3-44d4-a373-1884ba077271 /orders

Returns a JSON array containing a list of orders placed by the customer with the specified ID. If there is no matching customer, then the request returns the Not Found HTTP status code.

The example returns the list of orders for customer 156dfc64-abb3-44d4-a373-1884ba077271.

OrderHistoryDetails

Orders

api/orders/history/8d068e00-824d-4dff-8afe-552e367a7d6e

Returns a JSON object containing the details of the specified order history record. If there is no matching order history record, then the request returns the Not Found HTTP status code.

In the example, note that the ID of the order history record is a GUID.

DefaultApi and DefaultApiFallback

All

api/categories


api/products/706

These routes capture all requests that do not match any of the preceding routes. The request is routed to the named controller and invokes the specified action.

Without the optional trailing ID, these routes respond with a JSON array containing the objects returned by the specified GetAll action of the specified controller.

With the optional trailing ID, this route responds with a JSON object containing the object returned by the Get action of the specified controller, passing the ID as the parameter to this action.

The first example returns the list of product categories in the Adventure Works database.

The second example returns the details of product 706.

The order in which the routes are configured in the Register method defines their precedence for cases where the same URI matches more than one route.

For more information about defining routes for Web API web services, see "Routing in ASP.NET Web API" on the ASP.NET website.
The MvcWebApi web service is configured to accept and handle cross-origin resource sharing (CORS) requests. This feature enables the user interface web application to run in a separate web domain from the MvcWebApi web service.

Transmitting Data Between a Controller and a Client

The controllers receive REST requests and send REST responses. The data received in these requests and sent in these responses can contain structured information, such as the details of a product, the items in a shopping cart, or the address of a customer. The MvcWebApi web service defines a series of serializable data types that define the shape of this structured information. These types act as data transfer objects (DTOs), and the information that they contain is transmitted as JSON objects and JSON arrays. These DTOs are implemented in the Models folder in the DataAccess.MvcWebApi project. The following table briefly describes these classes:

Model Class

Description

AddressInfo

Contains a street address, including the city, state, country, and postal code.

CartItem

Specifies the details of an item in the shopping cart. The properties include the shopping cart ID, the product ID, the product name, the quantity, and the price of the product.

CartItemDelete

Contains a reference to an item to remove from a shopping cart.

Category

Holds the name of the category and its ID.

CreditCardInfo

Contains the credit card type and credit card number, as strings.

LoginInfo

Contains the credentials of a user (user name and password).

OrderDetail

Specifies the complete details of an order. This class includes a list of OrderItemInfo objects that define each line item for the order (for details, see the OrderItemInfo class described later in this table).

OrderHistoryInfo

Contains a reference to an order together with the date and time that the order was placed or last modified.

OrderInfo

Specifies the summary details of an order. This class simply contains a set of IDs that reference the shopping cart containing the products being ordered, the shipping address and billing address of the customer, and the credit card used to pay for the order.

OrderItemInfo

Contains the information for a line item in an order; the product ID, product name, unit price, and quantity.

ProductDetail

Holds the details of a product, such as the product name, product number, color, list price, size, and the weight.

ProductInfo

Specifies the price of a product together with the category and subcategory to which the product belongs.

Recommendation

Holds the name and ID of a product, together with its recommendation rating.

RegistrationInfo

Contains the information needed to register a new customer, including the first name, last name, email address, password, address, and credit card details.

Subcategory

Holds the name of the subcategory, its ID, and the category to which it belongs.

Note

Chapter 7, "Implementing a Graph Database," describes how the recommendation ratings for a product are calculated.

Note

The Category, ProductDetail, ProductInfo, Recommendation, and Subcategory classes inherit from the abstract ModelBase class that provides the fields (Id, Name, and ParentId) that are common to these classes.

The MvcWebApi web service uses AutoMapper to convert the domain entity objects that it receives from the various repository objects into DTOs. AutoMapper simplifies the process of translating objects from one format into another by defining a set of mapping rules that can be used throughout an application. In the MvcWebApi web service, these rules are defined in the AutoMapperConfig.cs file in the App_Start folder in the DataAccess.MvcWebApi project.

As an example, the Product domain entity type that contains the information returned by method in the ProductRepository class looks like this:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string ProductNumber { get; set; }
    public string Color { get; set; }
    public decimal ListPrice { get; set; }
    public string Size { get; set; }
    public string SizeUnitMeasureCode { get; set; }
    public decimal? Weight { get; set; }
    public string WeightUnitMeasureCode { get; set; }
    public string Class { get; set; }
    public string Style { get; set; }
    public Subcategory Subcategory { get; set; }
}

The Product domain entity type contains a reference to the subcategory in which it is contained. In turn, the subcategory contains a reference to the category to which it belongs. The Subcategory and Category domain entity types look like this:

public class Subcategory
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Category Category { get; set; }
}

public class Category
{
    public int Id { get; set; }
    public string Name { get; set; }
}

The GetProducts method of the ProductsController transmits ProductInfo objects back to the client as DTOs. The ProductInfo class looks like this:

public class ProductInfo : ModelBase
{
    public decimal ListPrice { get; set; }
    public int CategoryId { get; set; }
    public string CategoryName { get; set; }
    public string SubcategoryName { get; set; }
}

A ProductInfo DTO is a flattened structure that contains information from Product, Subcategory, and Category domain entity objects. The AutoMapperConfig.cs file contains mappings that specify how to populate a ProductInfo DTO from a Product entity object, as shown by the following code example:

public static void SetAutoMapperConfiguration()
{
    ...
    // define the mapping from the Product domain entity to the ProductInfo DTO
    Mapper.CreateMap<DE.Catalog.Product, DTO.ProductInfo>()
        .ForMember(dest => dest.CategoryId, src => src.MapFrom(
            dest => dest.Subcategory == null ? 0 : 
                (dest.Subcategory.Category == null ? 0 : 
                    dest.Subcategory.Category.Id)))
        .ForMember(dest => dest.CategoryName, src => src.MapFrom(
            dest => dest.Subcategory == null ? null : 
                (dest.Subcategory.Category == null ? null : 
                    dest.Subcategory.Category.Name)))
        .ForMember(dest => dest.SubcategoryName, src => src.MapFrom(
            dest => dest.Subcategory == null ? null : dest.Subcategory.Name));
    ...
}

This mapping specifies that the CategoryId property in the DTO should be read from the Id property of the Category object referenced by the Subcategory property of the domain entity object as long as the Category and Subcategory properties reference valid objects. If not, the default value of 0 is used instead. In a similar manner, the CategoryName property is populated from the Name property of the Category object referenced by the Subcategory property of the domain entity object, or is set to null if the Category or Subcategory objects are null. The SubcategoryName property is copied from the Name property of the Subcategory property of the domain object. Again a null value is used if this property does not reference a valid object. The values of any properties not explicitly mapped (such as the ListPrice) are automatically copied from the domain entity object to the DTO as long as they have the same name in the domain entity object and the DTO.

The GetProducts method in the ProductsController uses the static Map method of the Mapper class (this is the AutoMapper that invokes the rules in the AutoMapperConfig.cs file) to translate a collection of Product domain entity objects returned by the product repository into a collection of ProductInfo DTOs that it sends back in the REST response to the client:

public HttpResponseMessage GetProducts(int subcategoryId)
{
    ...
    var products = this.productRepository.GetProducts(subcategoryId);
    if (products == null)
    {
        return Request.CreateErrorResponse(HttpStatusCode.NotFound, 
             string.Format(CultureInfo.CurrentCulture, 
                           Strings.SubcategoryNotFound));
    }

    var productInfos = new List<ProductInfo>();
            
    Mapper.Map(products, productInfos);

    return Request.CreateResponse(HttpStatusCode.OK, productInfos);
}

The other controllers use the same approach for generating DTOs.

For more information about AutoMapper, see AutoMapper on github.com.

Using the Repository Pattern to Access Data

The Repository pattern enables you to separate the code that accesses a data store from the business logic that uses the data in this store, minimizing any dependencies that the controller might have on the data store. Each of the controllers creates one or more repository objects to connect to the various databases and retrieve, create, update, or delete data. For example, the following code shows the GetAll method in the CategoriesController class that uses the GetAllCategories method of a CategoryRepository object to fetch category information:

public class CategoriesController : ApiController
{
    private ICategoryRepository categoryRepository;

    public CategoriesController(ICategoryRepository categoryRepository)
    {
        this.categoryRepository = categoryRepository;
    }

    public HttpResponseMessage GetAll()
    {
        var categories = this.categoryRepository.GetAllCategories();
        var result = new List<Category>();

        Mapper.Map(categories, result);

        return Request.CreateResponse(HttpStatusCode.OK, result);
    }
    ...
}

Each repository object is an instance of a database-specific repository class. The repository classes implement a set of database-agnostic interfaces. The methods defined by the repository interfaces return or manipulate collections and instances of domain entity objects. The section "Decoupling Entities from the Data Access Technology" later in this appendix describes these types. The following table summarizes the repository interfaces:

Note

The repository interfaces are defined in the DataAccess.Repo.Interface project in the Repo folder of the solution code provided with this guide.

Repository Interface

Methods

Description

ICategoryRepository

GetAllCategories

GetSubcategories

This repository interface provides access to product category and subcategory information.

The GetAllCategories method returns a list containing the details of every category as a collection of Category domain entity objects.

The GetSubcategories method returns a collection of Subcategory objects, listing all the subcategories in the specified category.

Category and subcategory information is read only in the Shopping application, so this class does not provide any means to save changes made to Category domain entity objects back to the database.

IInventoryProductRepository

GetInventoryProduct

This repository interface defines methods that retrieve inventory information about products.

The GetInventoryProduct method takes a product ID and returns an InventoryProduct object that contains the current price of the product from the inventory database.

IOrderHistoryRepository

GetOrderHistoryByTrackingId

GetOrdersHistories

GetOrderHistoryByHistoryId

IsOrderCompleted

GetPendingOrderByTrackingId

SaveOrderHistory

This repository interface defines methods that store and retrieve order history information.

The GetOrderHistoryByTrackingId

method returns an Order domain entity object that matches the specified order code. Each order has its own order code that identifies the order.

The GetOrdersHistories method returns a collection of OrderHistory domain entity objects containing complete audit history of all orders for a customer.

The GetOrderHistoryByHistoryId

method returns an OrderHistory domain entity object containing the details from a single order document.

The IsOrderCompleted method examines the status of the specified order and returns true if the status indicates that the order has been completed, false otherwise.

The GetPendingOrderByTrackingId method returns an Order domain entity object containing the details of the order that matches the specified tracking ID and that has a status of Pending, or null if there is no such order.

The SaveOrderHistory method writes an OrderHistory domain entity object to the database.

ISalesOrderRepository

SaveOrder

UpdateOrderStatus

IsOrderSaved


This repository interface manages order information in the database.

The SaveOrder method takes an Order domain entity object and uses it to create and populate an order in the database.

The UpdateOrderStatus method takes the ID of an order and a new order status, and updates the specified order to have this status in the database.

The IsOrderSaved method takes the ID of an order and returns a Boolean value indicated whether an order with this ID has already been saved to the database.

IPersonRepository

GetPerson

GetPersonByEmail

SavePerson

This repository interface provides access to the details of customers.

The GetPerson method returns a Person domain entity object containing the details of the customer that matches the ID specified as the parameter to this method.

The GetPersonByEmail method performs a similar task, except that it matches customers by using their email address.

The SavePerson method is used when a new customer is registered. This method saves the registration details (held in a Person domain entity object) to the database.

IProductRecommendationRepository

GetProductRecommendations


This repository interface enables an application to retrieve product recommendations from the database.

The GetProductRecommendations method takes a product ID as the parameter and returns a collection of RecommendedProduct domain entity objects. This collection contains information about products that other customers who bought the specified product also purchased.

IProductRepository

GetProducts

GetProduct

ProductExists

This repository interface defines methods that return information about the products that customers can order.

The GetProducts method retrieves a list of all products in a specified subcategory specified as a parameter to this method. The list is returned as a collection of Product domain entity objects.

The GetProduct method returns a single Product domain entity object that matches the product ID specified as the parameter to this method.

The ProductExists method takes a product ID and returns a Boolean value indicating whether a product with this ID exists in the product catalog.

In the Shopping application, product information is read only so this repository does not provide any means to save changes to products back to the database.

IShoppingCartRepository

GetShoppingCart

SaveShoppingCart

DeleteShoppingCart

This repository interface provides access to the contents of a customer's shopping cart.

The GetShoppingCart method returns the contents of the specified shopping cart as a ShoppingCart domain entity object.

The SaveShoppingCart method saves the shopping cart to the database.

The DeleteShoppingCart method removes the shopping cart items from the database specified by the shopping cart ID passed as the parameter to this method. Note that the shopping cart ID is actually the same as the customer's user ID because a customer can only have one shopping cart.

IStateProvinceRepository

GetStateProvince

GetStateProvinces

This repository retrieves the information about the states and provinces that can be included in customers' addresses.

The GetStateProvince method returns a StateProvince domain entity object with an ID that matches the parameter value passed in.

The GetStateProvinces method returns a collection of StateProvince domain entity objects, listing every state or province in the database.

The sample solution supports a number of different databases, and each database provides its own implementation of one or more of these interfaces containing database-specific code. Note that not all databases implement every repository; repositories are only provided where it is useful to store the data made available through that repository in a particular database.

The following section summarizes the repository classes that the developers at Adventure Works created for storing and retrieving order, person, and inventory data from SQL Server. These classes connect to the database by using the Entity Framework 5.0.

How the Entity Framework Repository Classes Work

Note

The repository classes for the Entity Framework, and the data types that these repository classes use to retrieve and modify data in the SQL Server database, are defined in the DataAccess.Repo.Impl.Sql project in the Repo folder.

The DataAccess.Repo.Impl.Sql project contains four repository classes named SalesOrderRepository, PersonRepository, InventoryProductRepository, and StateProvinceRepository. These classes provide the business methods that the controller classes use to store and retrieve data. Internally, these repository classes save and fetch data by using a context object (SalesOrderContext, PersonContext, InventoryProductContext, or StateProvinceContext as appropriate). Each context object is responsible for connecting to the database and managing or retrieving the data from the appropriate tables.

How the Context Classes Store and Retrieve Data from the Database

The context classes are defined in the DataAccess.Repo.Impl.Sql project, in the Repo folder in the solution code (the code is stored in the Order, Person, and StateProvince folders in this project). They are all custom Entity Framework context objects, derived indirectly from the DbContext class (via the BaseContext generic class) that exposes the data from the SQL Server database through a group of public IDbSet collection properties. The following code example shows how some of the properties for handling customer information are defined in the PersonContext class:

public class PersonContext : BaseContext<PersonContext>, IPersonContext
{
    ...
    public IDbSet<Person> Persons { get; set; }
    public IDbSet<PersonEmailAddress> EmailAddresses { get; set; }
    public IDbSet<PersonAddress> Addresses { get; set; }
    public IDbSet<PersonCreditCard> CreditCards { get; set; }
    ...
}

The type parameters referenced by the IDbSet collection properties are entity classes that are specific to the Entity Framework repositories; they correspond to individual tables in the SQL Server database. These classes are defined in the same folders of the DataAccess.Repo.Impl.Sql project (Order, Person, and StateProvince) as the various context classes. The following table briefly summarizes these entity classes:

Folder\Entity Class

Description

Person\Person

This class contains the data that describes a customer. The data is exposed as public properties. Many of the properties correspond to fields in the Person table in the SQL Server database, and relationships between the Person table and other tables such as PersonCreditCard, EmailAddress, and Address are implemented as collections of the appropriate entity object.

Person\PersonAddress

This class contains the data that describes the address of a customer.

Person\PersonBusinessEntity and Person\PersonBusinessEntityAddress

These two classes provide the connection between Person entities and Address entities. A PersonBusinessEntity represents a Person, and the PersonBusinessEntityAddress entity implements a one-to-many relationship between a PersonBusinessEntity object and a PersonAddress object.

Person\PersonCreditCard

This class holds the data that describes a credit card for a customer.

Person\PersonEmailAddress

This class describes the email address of a customer.

Person\PersonPassword

This class contains the password for the customer. For security reasons, the password is held as a hash in the PasswordHash property rather than as clear text. The PasswordSalt property contains the random salt string appended to the password before it is hashed.

StateProvince\StateProvince

This class contains information about the states and provinces that can be included in customer addresses.

Order\SalesOrderHeader

This class contains the information that describes a customer's order. It includes data such as the billing address and shipping address of the customer (held as PersonAddress objects), a reference to the credit card that was used to pay for the order, the total value of the order, and a collection of SalesOrderDetail objects describing the line items for the order.

Order\SalesOrderDetail

This class describes a line item for an order, indicating the product, quantity, and unit price amongst other details.

Order\InventoryProduct

This class contains the inventory information for a product required by the order service. This class contains the current price of a specified product. The InventoryService object used by the Orders controller retrieves an InventoryProduct object that contains the current price for each item in a new order before the order is placed. The Orders controller uses this information to determine whether the price of an item has changed since the customer added it to their shopping cart.

The developers at Adventure Works followed a code first approach to implementing the entity model for the repositories and they defined each of the entity classes manually. For example, the Person and PersonEmailAddress classes look like this:

public class Person : ...
{
    public string PersonType { get; set; }
    public bool NameStyle { get; set; }
    public string Title { get; set; }
    public string FirstName { get; set; }
    public string MiddleName { get; set; }
    public string LastName { get; set; }
    public string Suffix { get; set; }
    public int EmailPromotion { get; set; }
    public Guid PersonGuid { get; set; }
    public virtual PersonPassword Password { get; set; }
    public virtual ICollection<PersonEmailAddress> EmailAddresses { get; set; }
    public virtual ICollection<PersonBusinessEntityAddress> Addresses {get; set;}
    public virtual ICollection<PersonCreditCard> CreditCards { get; set; }
}
public class PersonEmailAddress
{
    public int BusinessEntityId { get; set; }
    public int EmailAddressId { get; set; }
    public string EmailAddress { get; set; }
    public virtual Person Person { get; set; }
}

The PersonContext class uses the Entity Framework Fluent API to map the entity classes to tables in the SQL Server database and specify the relationships between tables. The following sample shows the code in the OnModelCreating event handler method for the PersonContext class that populates the Persons and EmailAddressesIDbSet collections. This event handler runs when the PersonContext object is created, and it passes a reference to the in-memory data model builder that is constructing the model for the Entity Framework as the parameter:

public sealed class PersonContext : ...
{
    public IDbSet<Person> Persons { get; set; }
    public IDbSet<PersonEmailAddress> EmailAddresses { get; set; }
    ...
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        ...
        modelBuilder.Configurations.Add(new PersonMap());
        modelBuilder.Configurations.Add(new PersonEmailAddressMap());
        ...
    }
}

In this example the OnModelCreating method creates new PersonMap and PersonEmailAddressMap objects. The code in these types describes how to associate each of the entity classes with the corresponding tables in the database. They are defined in the PersonMap.cs and PersonEmailAddressMap.cs files in the Person folder of the DataAccess.Repo.Impl.Sql project. The following code sample shows how the PersonEmailAddressMap class is implemented:

public class PersonEmailAddressMap : EntityTypeConfiguration<PersonEmailAddress>
{
    public PersonEmailAddressMap()
        : base()
    {
        this.ToTable("EmailAddress", "Person");
        this.HasKey(e => e.EmailAddressId);

        this.HasRequired(e => e.Person)
            .WithMany()
            .WillCascadeOnDelete(true);
    }
}

The call to the ToTable method in this code specifies that the DbModelBuilder object should map instances of the PersonEmailAddress type to rows in the EmailAddress table in the Person schema in the SQL Server database. The call to the HasKey method specifies that the EmailAddressId field contains the primary key used to establish relationships with other objects (such as instances of the Person type). The WithMany method defines the type of relationship that a PersonEmailAddress object has with a Person object. In the Adventure Works database, a Person row in the database can have many associated EmailAddress rows, and if a Person row is deleted then all associated EmailAddress rows should be deleted as well.

The PersonMap class shown in the following code example is more complicated due to the relationships that the underlying Person table has with the EmailAddress, Password, PersonCreditCard, and BusinessEntity tables in the SQL Server database. Specifically, the PersonMap class stipulates that:

  • The Person type maps to rows in the Person table in the Person schema in the SQL Server database,
  • The BusinessEntityId field contains the primary key,
  • The Person table in the database has a one-to-one relationship with rows in the Password table, and that the password for a person should be removed if the person is deleted,
  • The Person table has a one-to-many relationship with the EmailAddress table, and that the email addresses for a person should be removed if the person is deleted, and
  • The Person table has a many-to-many relationship with the CreditCard table implemented as a one-to-many relationship between the Person and PersonCreditCard tables, and a many-to-one relationship between the PersonCreditCard table and CreditCard table (this is a common technique for implementing many-to-many relationships in a relational database).

See the section "How Adventure Works Designed the Database for the Shopping Application" in Chapter 3, "Implementing a Relational Database" for more information about the structure of the Person, EmailAddress, Password, PersonCreditCard, and BusinessEntity tables in the SQL Server database.

public class PersonMap : EntityTypeConfiguration<Person>
{
    public PersonMap()
        : base()
    {
        this.ToTable("Person", "Person");
        this.HasKey(p => p.BusinessEntityId);

        this.HasRequired(p => p.Password)
            .WithRequiredPrincipal(p => p.Person)
            .WillCascadeOnDelete(true); 
            
        this.HasMany(p => p.EmailAddresses)
            .WithRequired(e => e.Person)
            .WillCascadeOnDelete(true);

        this.HasMany(p => p.CreditCards)
            .WithMany(c => c.Persons)
            .Map(map => map.ToTable("PersonCreditCard", "Sales")
                .MapLeftKey("BusinessEntityID")
                .MapRightKey("CreditCardID"));
    }
}

For more information about constructing an entity model at runtime by using the Fluent API, see "Configuring/Mapping Properties and Types with the Fluent API" and "Configuring Relationships with the Fluent API" on MSDN.

How the Repository Classes Read and Save the Data for Objects

The repository classes read and save data through the context objects described in the previous section. To ensure consistency, all read operations are performed within the scope of a SQL Server transaction that uses the ReadCommitted isolation level. The repository classes inherit from the BaseRepository class that defines the TransactionOptions property and the GetTransactionScope method shown in the following code example:

public abstract class BaseRepository
{
    private static TransactionOptions transactionOptions = 
        new TransactionOptions()
    {
        IsolationLevel = IsolationLevel.ReadCommitted
    };

    ...

    protected virtual TransactionScope GetTransactionScope()
    {
        return new TransactionScope(TransactionScopeOption.Required,
                                    transactionOptions);
    }
}

When a repository object retrieves data, it calls the GetTransactionScope method to create a TransactionScope object and reads the data within this scope as shown in the next code example.

In the context classes, each IDbSet property is an enumerable collection. This means that a repository****object can retrieve data from a context object by performing LINQ queries. As an example, the following code shows the statements in the GetPerson method of the PersonRepository class that uses a PersonContext object to retrieve customer data from the database through the PersonsIDbSet collection:

public class PersonRepository : BaseRepository, IPersonRepository
{
    public DE.Person GetPerson(Guid personGuid)
    {
        using (var context = new PersonContext())        {            Person person = null;            using (var transactionScope = this.GetTransactionScope())            {                person = context.Persons                    .Include(p => p.Addresses)                    .Include(p => p.CreditCards)                    .Include(p => p.EmailAddresses)                    .Include(p => p.Password)                    .SingleOrDefault(p => p.PersonGuid == personGuid);                transactionScope.Complete();            }
            ...
        }
    }
    ...
}

Note

The data for a Person object includes address, credit card, email address, and password information that are retrieved from separate tables in the SQL Server database. The Include method ensures that these fields are populated when the data for the Person object is retrieved from the database (the Entity Framework performs lazy evaluation by default).

Additionally, because they are descended from the Entity Framework DbContext class, the context classes implement the Unit of Work pattern, enabling the repository classes to combine operations that span multiple entities into a single transaction. All changes made to the collections attached to a context object are packaged up and converted into the corresponding SQL commands by the SaveChanges method inherited from the DbContext class. For example, the SavePerson method in the PersonRepository class creates and populates PersonEmailAddress objects and PersonBusinessEntityAddress objects containing the email addresses and business addresses of a customer that it adds to the EmailAddresses and Addresses collections in the Person object. The Person object is then added to the Persons collection in the DbContext object. The call to the SaveChanges method ensures that the new person, email address, and business entity address details are saved to the tables in the SQL Server database as an atomic transaction. The transaction runs by using the ReadCommitted isolation level using a TransactionScope object created by the GetTransactionScope method. The code for the SavePerson method looks like this (the important statements are highlighted):

public class PersonRepository : BaseRepository, IPersonRepository
{
    ...
    public DE.Person SavePerson(DE.Person person)
    {
        var newPerson = new Person()
        {
            Addresses = new List<PersonBusinessEntityAddress>(),
            CreditCards = new List<PersonCreditCard>(),
            EmailAddresses = new List<PersonEmailAddress>(),
            Password = new PersonPassword()
        };

        Mapper.Map(person, newPerson);

        // add email addresses
        foreach (var emailAddress in person.EmailAddresses)
        {
            newPerson.EmailAddresses.Add(new PersonEmailAddress()
                { EmailAddress = emailAddress });
        }

        // add addresses
        foreach (var address in person.Addresses)
        {
            var personAddress = new PersonAddress();
            Mapper.Map(address, personAddress);

            newPerson.Addresses.Add(new PersonBusinessEntityAddress()
            {
                Address = personAddress,
                AddressTypeId = 2 // static value
            });
        }
        // Since the PersonGuid is a storage implementation, 
        // we create it now instead of earlier when the DE was created
        newPerson.PersonGuid = Guid.NewGuid();           
 
        try
        {
            using (var context = new PersonContext())            {                using (var transactionScope = this.GetTransactionScope())                {                    context.Persons.Add(newPerson);                    context.SaveChanges();                    ...                    transactionScope.Complete();                }            }
        }
        catch (DbUpdateException ex)
        {
            ... // Handle errors that may occur when updating the database
        }

        return person;
    }
}

How the Repository Classes for Other Databases Work

The MvcWebApi web service also contains repository classes for a variety of NoSQL databases. At a high level, they all function in a similar manner to the Entity Framework repository. They implement one or more of the repository interfaces, and they pass domain entity objects back to the controllers that use them. Internally, each of the repository classes can be quite different, and they use database-specific APIs to connect to the database to store and retrieve data.

Like the Entity Framework repositories, the developers implemented the repositories for each of the databases in separate projects under the Repo folder in the solution. The following table summarizes these projects and the repositories that they contain.

Data Storage Technology

Repository Classes

Description

Windows Azure Table service

ShoppingCartRepository

This repository class implements the IShoppingCartRepository interface to store and retrieve shopping cart information from the Windows Azure Table service. The data access logic is provided by ShoppingCartContext class in the DataAccess.Storage.Impl.TableService project. The data structures that define the storage classes for this repository are defined in the ShoppingCart folder.

For more information, see the section "How Adventure Works Used a Key/Value Store to Hold Shopping Cart Information" in Chapter 4, "Implementing a Key/Value Store."

MongoDB

CategoryRepository, OrderHistoryRepository, and ProductRepository

These repository classes implement the ICategoryRepository, IOrderHistoryRepository, and IProductRepository interfaces to store product and order history information in a MongoDB document database.

The Order and Catalog folders in the DataAccess.Repo.Impl.Mongo project contains the data types that the context classes use to store information in the database.

For more information, see the section "How Adventure Works Used a Document Database to Store the Product Catalog and Order History Information" in Chapter 5, "Implementing a Document Database."

Neo4j

ProductRecommendationRepository

This repository class implements the IProductRecommendationRepository interface to retrieve product recommendation information from a Neo4j graph database.

The Catalog folder in the DataAccess.Repo.Impl.Neo4j project defines the ProductGraphEntity class that specifies the structure of the information that the context class retrieves from the database.

For more information, see the section "How Adventure Works Used a Graph Database to Store Product Recommendations" in Chapter 7, "Implementing a Graph Database."

In-Memory Storage

CategoryRepository, OrderHistoryRepository, ProductRecommendationRepository, ProductRepository, and ShoppingCartRepository

These repository classes, defined in the DataAccess.Repo.Impl.InMemory folder, simulate NoSQL document and graph databases by using in-memory collections. They are provided to enable you to run the application without installing MongoDB or Neo4j if you do not have access to this software. They are not described further in this guide.

The details of the ShoppingCartRepository class for theWindows Azure Table service are described in Chapter 4, "Implementing a Key/Value Store," the details of the repository classes for MongoDB are described in Chapter 5, "Implementing a Document Database,", and the details of the ProductRecommendationRepository classe for Neo4j are described in Chapter 7, "Implementing a Graph Database."

Decoupling Entities from the Data Access Technology

As described earlier in this appendix, the data that passes between the controllers and the repository classes are instances of domain entity objects. These are database-neutral types that help to remove any dependencies that the controller classes might otherwise have on the way that the data is physically stored. The classes that implement these entities are organized into the Catalog, Order, Person, and ShoppingCart folders in the DataAccess.Domain project under the Domain folder. These types incorporate the business logic necessary to validate the data that they hold, also defined in a database-agnostic manner (this validation occurs before the data is saved to the database). For example, the Person class makes use of validation annotations, read only collections, and regular expressions, like this:

public class Person
{
    private readonly IList<string> emailAddresses = new List<string>();
    ...

    public int Id { get; set; }

    [Required]
    public string FirstName { get; set; }
        
    [Required]
    public string LastName { get; set; }

    ...

    public IReadOnlyCollection<string> EmailAddresses { 
        get { return new ReadOnlyCollection<string>(this.emailAddresses); } }
    ...

    public void AddEmailAddress(string emailAddress)
    {
        if (string.IsNullOrWhiteSpace(emailAddress))
        {
            throw new ArgumentNullException(
                "Email Address parameter cannot be null or white space");
        }

        var regex = 
            new Regex(@"^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$");
        var match = regex.Match(emailAddress);
        if (!match.Success)
        {
            throw new ValidationException(string.Format(
                "Invalid email address: {0}", emailAddress));
        }

        this.emailAddresses.Add(emailAddress);
    }
    ...
} 

The repository classes in the MvcWebApi web service pass domain entity objects to the controllers. Therefore, an important task of the repository classes is to take the data that they retrieve from a database and reformat it as one or more of these domain entity objects. The repository classes for the Entity Framework and MongoDB use AutoMapper for this purpose. Note that this is the same technology that is used by the controllers to convert domain entity objects into DTOs—see the section "Transmitting Data Between a Controller and a Client" earlier in this appendix for more information. The repository classes for the Windows Azure Table service and Neo4j implement their own manual mapping functions; the nature of the database-specific types for these repositories is not best suited to using AutoMapper; the data is retrieved in a serialized form and has to be deserialized before it can be converted.

How the Entity Framework and MongoDB Repository Classes use AutoMapper to Convert Data

The projects that implement repositories for the Entity Framework and MongoDB provide their own mappings in a file called AutoMapperConfig.cs. The following code example shows part of the AutoMapper configuration for the Entity Framework repository classes in the DataAccess.Repo.Impl.Sql project. This code handles the mapping from an Entity Framework InventoryProduct object to a domain InventoryProduct entity object, and from an Entity Framework Person object to a domain Person object. Note that in this code sample, DE is an alias for the DataAccess.Domain namespace that contains the domain entity types:

private static void SetAutoMapperConfigurationPrivate()
{
    // define the mapping from the InventoryProduct entity 
    // to the InventoryProduct domain entity
    Mapper.CreateMap<Order.InventoryProduct, DE.Order.InventoryProduct>();

    ...

    // define the mapping from the Person entity to the Person domain entity
    Mapper.CreateMap<Person.Person, DE.Person.Person>()
        .ForMember(dest => dest.Id, 
                   src => src.MapFrom(val => val.BusinessEntityId))
        .ForMember(dest => dest.PasswordHash, 
                   src => src.MapFrom(val => val.Password.PasswordHash))
        .ForMember(dest => dest.PasswordSalt, 
                   src => src.MapFrom(val => val.Password.PasswordSalt));
    ...
}

Note

The SetAutoMapperConfigurationPrivate method is invoked by the public static SetAutoMapperConfiguration method that ensures that the configuration is performed only once.

The InventoryRepository class calls the static Map method of the Mapper class to convert product information that it has retrieved from the SQL Server database into an InventoryProduct object, as shown in the following code example:

public class InventoryProductRepository : BaseRepository, IInventoryProductRepository
{
    public DE.InventoryProduct GetInventoryProduct(int productId)
    {
        using (var context = new InventoryProductContext())
        {
            ...
            var inventoryProduct = context.InventoryProducts.SingleOrDefault(
                p => p.ProductId == productId);
            ...

            var result = new DE.InventoryProduct();
            Mapper.Map(inventoryProduct, result);
            return result;
        }
    }
}

Similarly, the PersonRepository class uses calls the Map method after it has retrieved the customer information from the SQL Server database. The following code example highlights the statement in the GetPerson method that performs this task:

public class PersonRepository : BaseRepository, IPersonRepository
{
    public DE.Person GetPerson(Guid personGuid)
    {
        ...
        using (var context = new PersonContext())
        {
            person = context.Persons
                ...
                .SingleOrDefault(p => p.PersonGuid == personGuid);
            ...    
            var result = new DE.Person();
            ...
            Mapper.Map(person, result);
            ...
            return result;
        }
    }
    ...
}

Where appropriate, the repository classes also use AutoMapper to convert from domain entity objects into database-specific types. For example, the AutoMapper configuration for the Entity Framework repository classes includes the following mapping that translates an Order domain entity object into a SalesOrderHeader object. Note that the Order class does not include a value for every property in the SalesOrderHeader class, and the mapping generates static values for these properties:

private static void SetAutoMapperConfigurationPrivate()
{
    ...
    // define the mapping from the Order domain entity 
    // to the SalesOrderHeader entity
    Mapper.CreateMap<DE.Order.Order, Order.SalesOrderHeader>()
        .ForMember(dest => dest.OnlineOrderFlag, 
                   src => src.MapFrom(val => true)) // static value
        .ForMember(dest => dest.RevisionNumber, 
                   src => src.MapFrom(val => 3)) // static value
        .ForMember(dest => dest.SalesOrderDetails, 
                   src => src.MapFrom(val => val.OrderItems))
        .ForMember(dest => dest.ShipMethodId, 
                   src => src.MapFrom(val => 5)) // static value
        .ForMember(dest => dest.Status, 
                   src => src.MapFrom(val => (byte)val.Status))
        .ForMember(dest => dest.TaxAmt, 
                   src => src.MapFrom(val => 0)) // static value
        .ForMember(dest => dest.BillToAddressId, 
                   src => src.MapFrom(val => val.BillToAddress.Id))
        .ForMember(dest => dest.ShipToAddressId, 
                   src => src.MapFrom(val => val.ShippingAddress.Id))
        .ForMember(dest => dest.CreditCardId, 
                   src => src.MapFrom(val => val.CreditCard.Id))
        .ForMember(dest => dest.BillToAddress, src => src.Ignore())
        .ForMember(dest => dest.CreditCard, src => src.Ignore());
    ...
}

This mapping is used when a SalesOrderRepository object saves an order to the SQL Server database. The following code example shows the SaveOrder method of the SalesOrderRepository class:

public class SalesOrderRepository : BaseRepository, ISalesOrderRepository
{
    public DE.Order SaveOrder(DE.Order order)
    {
        var salesOrderHeader = new SalesOrderHeader();
        Mapper.Map(order, salesOrderHeader);

        using (var context = new SalesOrderContext())
        {
            ...
            context.SalesOrderHeaders.Add(salesOrderHeader);
            context.SaveChanges();
        }

        return order;
    }
    ...
}

How the Windows Azure Table Service Repository Converts Data

The ShoppingCartRepository stores and retrieves shopping cart data from the Windows Azure Table service using the following types:

public sealed class ShoppingCartTableEntity: ...
{
    ...
    public string ShoppingCartItemsJSON { get; set; }
    public Guid TrackingId { get; set; }
}

public class ShoppingCartItemTableEntity
{
    public int Quantity { get; set; }
    public int ProductId { get; set; }
    public string ProductName { get; set; }
    public decimal ProductPrice { get; set; }
    public string CheckoutErrorMessage { get; set; }
}

The ShoppingCartTableEntity represents a shopping cart, while the ShoppingCartItemTableEntity contains a single item held in the shopping cart. The ShoppingCartItems property in the ShoppingCartTableEntity class is a string that contains one or more ShoppingCartItemTableEntity objects serialized as a JSON string. The section "How Adventure Works Used a Key/Value Store to Hold Shopping Cart Information" in Chapter 4 provides more information on why the data is stored in this format.

The ShoppingCartRepository class converts data between ShoppingCartTableEntity objects and ShoppingCart domain objects by using a custom mapper class called ShoppingCartMapper. This class provides two static Map methods—one that converts a ShoppingCartTableEntity object into a ShoppingCart domain object, and another that performs the opposite operation. Chapter 4 describes how these methods work.

The ShoppingCartRepository class invokes the Map methods as it retrieves and stores shopping cart information. The code example below highlights the statements that convert from a ShoppingCartTableEntity object to a ShoppingCart domain object in the GetShoppingCart method of the ShoppingCartRepository class, and from a ShoppingCart domain object to a ShoppingCartTableEntity object in the SaveShoppingCart method:

public class ShoppingCartRepository : IShoppingCartRepository
{
    public ShoppingCart GetShoppingCart(string shoppingCartId)
    {
        var storedCart = new ShoppingCartContext().Get(shoppingCartId);
        return storedCart != null ? ShoppingCartMapper.Map(storedCart) 
                                  : new ShoppingCart(shoppingCartId);
    }

    public ShoppingCart SaveShoppingCart(ShoppingCart shoppingCart)
    {
        new ShoppingCartContext().Save(ShoppingCartMapper.Map(shoppingCart));
        return shoppingCart;
    }
    ...
}

For more information about how the ShoppingCartRepository is implemented, see the section "Storing and Retrieving Data in the Windows Azure Table Service" in Chapter 4.

How the Neo4j Repository Converts Data

The product recommendations information stored in the Neo4j database is stored as serialized JSON data. The Neo4j ProductRecommendationRepository class retrieves data from the database and deserializes it as ProductGraphEntity objects. The following code example shows the ProductGraphEntity class:

public class ProductGraphEntity
{
    [JsonProperty("productId")]
    public int ProductId { get; set; }

    [JsonProperty("name")]
    public string Name { get; set; }

    [JsonProperty("percentage")]
    public decimal Percentage { get; set; }
}

The GetProductRecommendations method of the ProductRecommendationRepository class converts ProductGraphEntity objects into RecommendedProduct domain objects that have a similar structure except that the properties are not tagged as JSON-serializable. This is the type expected by the GetRecommendations method in the ProductsController. The following code example highlights how the GetProductRecommendations method manually converts a ProductGraphEntity object into a RecommendedProduct domain object:

public class ProductRecommendationRepository : IProductRecommendationRepository
{
    ...
    public ICollection<RecommendedProduct> GetProductRecommendations(
        int productId)
    {
        var recommendedProducts = new List<RecommendedProduct>();
        ...
        var graphResults = ... // fetch data from the graph database
                               // as a collection of ProductGraphEntity objects
        ...
            
        foreach (var graphProduct in graphResults)
        {
            recommendedProducts.Add(new RecommendedProduct()            {                Name = graphProduct.Name,                Percentage = graphProduct.Percentage,                ProductId = graphProduct.ProductId            });
        }

        return recommendedProducts;
    }
    ...
}

For more information about how the ProductRecommendationRepository works, see the section "Retrieving Product Recommendations from the Database" in Chapter 7.

Instantiating Repository Objects

The repository classes implement a common set of interfaces that enabled the developers at Adventure Works to decouple the business logic in the controller classes from the database-specific code. However, a controller still has to instantiate the appropriate repository classes, and this step can introduce dependencies back into the code for the controller classes if it is not performed carefully.

To eliminate these dependencies, the developers chose to use the Unity Application Block to inject references to the repository classes dynamically rather than coding them statically. This approach enables the developers to more easily switch to a different set of repository classes that reference an alternative data access mechanism without requiring that they rebuild or even redeploy the entire application.

Each controller references the repositories that it uses through the repository interface. The following code example shows how the AccountController class references the PersonRepository type through the IPersonRepositoryInterface:

public class AccountController : ApiController
{
    private IPersonRepository personRepository;
    ...

    public AccountController(IPersonRepository personRepository, ...)
    {
        this.personRepository = personRepository;
        ...
    }

    public HttpResponseMessage Get(string id)
    {
        Guid guid;
        if (!Guid.TryParse(id, out guid))
        {
            ...
        }

        var person = this.personRepository.GetPerson(guid);
        ...
    }
    ...
}

The constructor is passed a reference to a PersonRepository object that is used by the methods in the AccountController class. At runtime, the MvcWebApi web service uses the Unity Application block to resolve the IPersonRepository reference passed to the AccountController constructor and create a new PersonRepository object. The web.config file for the DataAccess.MvcWebApi project contains the following configuration information:

<?xml version="1.0" encoding="utf-8"?>
...
<configuration>
  <configSections>
    ...
    <section name="unity" 
type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, 
Microsoft.Practices.Unity.Configuration"/>
  </configSections>
  ...
  <unity xmlns="https://schemas.microsoft.com/practices/2010/unity">
    ...
    <alias alias="IPersonRepository" type="DataAccess.Repo.Interface.IPersonRepository, 
DataAccess.Repo.Interface" />
    ...
    <container>
      ...
      <register type="IPersonRepository" mapTo="DataAccess.Repo.Impl.Sql.Person.PersonRepository, 
DataAccess.Repo.Impl.Sql">
        ...
       </register>
      ...
    </container>
  </unity>
</configuration>

This configuration enables the Unity Application Block to resolve references to the IPersonRepository interface as an instance of the PersonRespository class in the DataAccess.Repo.Impl.Sql assembly. To switch to a different repository that uses a different data access technology, update the reference in the <container> section of the configuration file to reference the appropriate repository.

For more information about using the Unity Application Block to resolve dependencies, visit the "Unity" page on MSDN.

More Information

All links in this book are accessible from the book's online bibliography on MSDN at: https://msdn.microsoft.com/en-us/library/dn320459.aspx.

Next Topic | Previous Topic | Home | Community