The Web API controller

The Basic Breeze teaching tests demonstrate the Breeze EntityManager making requests of an ASP.NET Web API controller.

The ASP.NET Web API is a framework for building HTTP services. Its simplicity has made it instantly popular with .NET backend developers who are used to struggling with Microsoft’s enormously complex, SOAP-based, WCF communications stack.

In a nutshell, the Web API routes an HTTP request (GET, POST, PUT, DELETE, etc) to an action method of a controller. The implementor has easy access to the complete request but is usually interested in the data that arrives in the URL or in the body of the request. There’s more to it of course but that’s the gist of it.

A Web API client developer targeting a Breeze client typically writes one Web API controller per service. The DocCode teaching tests talk to two different services, one for the “Todos” model and one for the Northwind model.  So there are two Web API controllers in the code sample, TodosController and NorthwindController

The Todos model only has one entity so its Web API controller is small, making it a good place for us to start. Here’s the TodosController:

[BreezeController]
public class TodosController : ApiController {

    readonly EFContextProvider<TodosContext> _contextProvider = 
        new EFContextProvider<TodosContext>();

    // ~/breeze/todos/Metadata 
    [HttpGet]
    public string Metadata() {
        return _contextProvider.Metadata();
    }

    // ~/breeze/todos/Todos
    // ~/breeze/todos/Todos?$filter=IsArchived eq false&$orderby=CreatedAt 
    [HttpGet]
    public IQueryable<TodoItem> Todos() {
        return _contextProvider.Context.Todos;
    }

    // ~/breeze/todos/SaveChanges
    [HttpPost]
    public SaveResult SaveChanges(JObject saveBundle) {
        return _contextProvider.SaveChanges(saveBundle);
    }

    // other miscellaneous actions of no interest to us here
}

The TodosController inherits from the Web API base class, ApiController. The Web API spins up a new instance of the TodosController for every client request.

The [BreezeController] attribute

A Breeze Web API controller and a BreezeJS client have a shared understanding about the nature and format of HTTP requests, responses and payloads. The Web API pipeline has to be configured to conform to that "understanding". Because your Breeze Web API Controller may cohabitate with other, non-Breeze controllers. Breeze shouldn't impose its configuration requirements on them. Therefore, we should configure the Web API pipeline on a controller basis ... and that's what this BreezeController attribute does.

When the Web API routes a request to the TodosController, it creates a new instance of that controller type and then calls the BreezeController attribute to configure the Web API pipeline for this controller instance only.

The BreezeController attribute first removes filters and services that interfere with Breeze (e.g, all MediaTypeFormatters and the Web API's own OData query string interpreter). Then it adds its own JsonFormatter and its own OData query string interpreter called the ODataActionFilter.

You could apply these components to the controller yourself instead of applying the BreezeController attribute; they exist in attribute form as the JsonFormatterAttributee and the ODataActionFilterAttribute. And we'll talk about them next in order to understand how Breeze formats content and how it handles OData query strings. But be careful: you also have to remove the Web API pipeline filters that interfere with Breeze. Unless you have a compelling reason to do otherwise it is best to keep it simple:

Always decorate your Breeze controller with the BreezeController attribute.

Serialize with JsonFormatter

The data that flow between Breeze clients and Web API controllers are formatted as JSON. A Web API "formatter" serializes .NET objects as JSON. Out-of-the box, the Web API installs a JsonMediaTypeFormatter as the global default formatter.

Breeze also uses this same formatter for its client/server communications. But that formatter has to be configured specifically for Breeze. The JsonFormatterAttribute replaces the default formatter instance with one that is setup for Breeze; see the Breeze.WebApi.JsonFormatter class for details.

You can change the formatter's configuration [TO BE EXPLAINED] but if you do so you, you are responsible for ensuring that your changes do not conflict with Breeze expectation.

Do not reconfigure the formatter to use camel casing on the client. Use the BreezeJS NamingConvention instead.

ODataActionFilter

Breeze clients query for data by sending HTTP GET requests to the controller formatted as OData Query URLs. For example, a client could query for active (non-archived) Todos, sorted by creation date, with this URL.

  .../breeze/todos/Todos?$filter=IsArchived eq false&$orderby=CreatedAt 

Thanks to this Breeze Web Api ActionFilter [2], Breeze can extend the IQueryable<Todo> returned by the controller's Todos action method (see code sample above). The net effect is a LINQ query such as this one:

  query = _contextProvider.Context.Todos
             .Where(t => t.IsArchived == false).OrderBy("CreatedAt");

Having revised the IQueryable<Todo>, Breeze can execute it, invoking the query's LINQ provider (an Entity Framework provider in this example).

EFContextProvider

The TodosController has a _contextProvider field which is initialized to a fresh instance of an EFContextProvider, a Breeze .NET class [3].

readonly EFContextProvider<TodosContext> _contextProvider =
    new EFContextProvider<TodosContext>();

EFContextProvider eases development of Web API controllers that use Entity Framework for database access. It is a generic class that wraps a single EF model’s DbContext [4].

You can use the out-of-the-box EFContextProvider from Breeze exactly as we have here. But we recommend that you customize it via a subclass if for no other reason than to intercept and validate efforts to save changes to your database.

The EFContextProvider creates a new instance of the DbContext and makes it available to the Web API controller through its Context property … as we’ll see in the action methods below.

But first we look at the Metadata action method.

The “Metadata” action

The Breeze JavaScript client requires metadata to query the service, create new entities, and save changes. These metadata describe the client-side entity model and how to translate it into the service model that is understood by the persistence service.

As we saw earlier, it takes a lot of metadata to adequately describe a model. Fortunately, the _contextProvider can construct a suitable metadata document for the Breeze client so the JavaScript developer doesn’t have to write the metadata by hand.

The client request for metadata is routed to the controller’s Metadata action::

// ~/breeze/todos/Metadata 
[HttpGet]
public string Metadata() {
    return _contextProvider.Metadata();
}

We’ll see how the client makes this request and consumes the metadata document when we turn our attention to the client.  We continue here with the next action.

An entity query

The “Todos” query action appears next:

// ~/breeze/todos/Todos
// ~/breeze/todos/Todos?$filter=IsArchived eq false&$orderby=CreatedAt 
[HttpGet]
public IQueryable<TodoItem> Todos() {
    return _contextProvider.Context.Todos;
}

A query action usually returns an IQueryable of an entity type in the model, the TodoItem entity type in this case.

The IQueryable<T> is important. It means that the action does not return data!  Instead, it returns a LINQ query object that can be extended with additional query parameters to filter, order, page, project, and expand the query results. The Web API executes the query after applying these parameters.

A Breeze client expresses those query parameters in OData URI syntax. The comments show two examples of URIs with that syntax. The first shows a request for all TodoItems. The second requests just the active TodoItems (the ones whose “IsArchived” property is false), sorted by creation date.

Thanks to the power and flexibility of OData query syntax, a single controller action method can satisfy all of this particular application’s TodoItem query requirements. We don’t have to write a separate query action method for every application query request.

The Todos method implementation requires a single line to get the base “Todos” query from the DbContext (which we get from the contextProvider).

return _contextProvider.Context.Todos;

Query actions per entity type

The Todos model only has one entity so the TodosController only has one query action.  The Northwind model has more entity types … and its NorthwindController has corresponding query actions, implemented in the same manner:

[BreezeController]
public class NorthwindController : ApiController {

  // ...

  [HttpGet]
  public IQueryable<Customer> Customers() {
    return _contextProvider.Context.Customers;
  }

  // ...

  [HttpGet]
  public IQueryable<Order> Orders() {
    return _contextProvider.Context.Orders;
  }

  // ...

  [HttpGet]
  public IQueryable<OrderDetail> OrderDetails() {
    return _contextProvider.Context.OrderDetails;
  }

  [HttpGet]
  public IQueryable<Product> Products() {
    return _contextProvider.Context.Products;
  }

  // ...  

}

You do not have to publish a query method for every entity type. For example, you might choose not to publish an OrderDetails action; OrderDetail entities belong exclusively to their parent Orders. You might feel that a client should only access them through their parent orders.

How do you get the OrderDetails to the client if there is no controller action for them? The client can add them to the payload of an Orders query, using the OData “expand” feature.

query = breeze.EntityQuery.from("Orders")
    .where("CustomerID", "==", alfredsID)
    .expand("OrderDetails"); // <--- gets OrderDetails of 'Alfreds' orders

After “eagerly” fetching the related order line items with this query, the client can navigate from an order to its details (e.g, someOrder.OrderDetails()) without first loading those details in a separate step.

There’s another way to do this. You can write a specialized query action that includes each order’s OrderDetails in the payload automatically … a technique we discuss next.

Specialized query actions

You can encapsulate complex query logic on the server in a special purpose query action. Perhaps you want to prepare a complex entity graph on the server so that client developers don’t get it wrong. Perhaps you require a query that simply can’t be constructed on the client at all … because there is no way to express it in OData query syntax.

The solution: write a specialized query action. Here’s one that includes the OrderDetails when the client queries for Orders:

[HttpGet]
public IQueryable<Order> OrdersAndDetails()
{
    return _contextProvider.Context.Orders.Include("OrderDetails");
}

When the client queries for orders with the “OrdersAndDetails” action method, the line items come along for the ride. The client developer can still filter, but he doesn’t have to specify the expansion.

query = breeze.EntityQuery.from("OrdersAndDetails")
    .where("CustomerID", "==", alfredsID) // no expand needed!

This example is probably not a great case for a specialized query action. It’s easy to put the “expand” on the client query rather than clutter up the controller with extra actions.

But the technique is worth knowing because someday you will have a query that should be or can only be written on the server.

The SaveChanges action

When the client calls SaveChanges(), the EntityManager posts a bundle of JSON entity changes to the controller’s SaveChanges action. The bundle could contain a mix of different types and different operations (add, update, delete). They all travel together in the body of the POST.

The Web API delivers the bundle to the controller’s SaveChanges method as a JSON object (JObject). You could pick that bundle apart yourself but it is expedient to let the _contextProvider.SaveChanges method handle that … and save those changes in EF as a single transaction:

[HttpPost]
public SaveResult SaveChanges(JObject saveBundle) {
  return _contextProvider.SaveChanges(saveBundle);
}

Alarm bells should be ringing when you read this code. We shouldn't blithely save everything the client tells us to save. We should inspect every request, making sure that the changes are valid and that the user is authorized to make them.

Learn how to do that with a custom EFContextProvider and save Interception.

Notes

[1] Alternatively, you could make the Breeze JsonFormatter your application's global default JSON formatter. You could also make the Breeze ODataActionFilter  the default for all of your application controllers by adding one to the Web API's GlobalFilters (perhaps in the App_Start/FilterConfig.cs. We prefer the per-controller approach but the choice is yours.

[2] The ASP Web Api team is developing its own ActionFilter for IQueryable processing. We can't use it ... yet; at present the Web API OData ActionFillter lacks many of the features Breeze applications need (e.g., $expand and $select).

[3] EFContextProvider is defined in the Breeze.WebApi.dll. You'll find a copy of it in the packages folder of the sample code, along with its dependent Irony.dll assembly. The Breeze.MVCWebApi NuGet package adds references to these assemblies to your project.

[4] If you wrote an EF “database first” model rather than a “code first” model, you have an EDMX and a custom ObjectContext. Pass your ObjectContext as the type parameter instead of a DbContext.