Getting started
This page takes an ASP.NET Core project from nothing to a working Breeze endpoint that a breeze-client application can query and save against.
Install
dotnet add package Breeze.AspNetCore.NetCore
dotnet add package Breeze.Persistence.EFCoreTwo packages for an Entity Framework Core application. Breeze.Persistence and Breeze.Core come with them and are not installed directly. For NHibernate, swap the second for Breeze.Persistence.NH.
Requirements: .NET 8, 9 or 10, and ASP.NET Core. Version 8.0 targets all three; the EF Core major version is pinned to the matching runtime, so net10.0 gets EF Core 10. For .NET 5, 6 or 7 — all out of support — use 7.5.2 from the old repo.
Configure JSON
Breeze serializes with Newtonsoft.Json, not System.Text.Json, and it needs particular settings: reference handling for object graphs, type names so the client can tell what each JSON object is, and a specific date format. UpdateWithDefaults, in Breeze.Core, applies them.
builder.Services.AddControllers().AddNewtonsoftJson(opt => {
JsonSerializationFns.UpdateWithDefaults(opt.SerializerSettings);
});IMPORTANT
This is not optional. Without it the client receives JSON it cannot turn into entities — circular references throw, and results arrive with no $type for Breeze to match against its metadata.
The second parameter turns on camel casing of the JSON itself, and the third writes enums as integers rather than strings. Both default to false. Leave camelCasing off: the client camel-cases property names for you through its NamingConvention, so turning it on here as well means the names are translated twice. See Metadata.
Add a DbContext
An ordinary EF Core DbContext — Breeze adds nothing to it.
builder.Services.AddDbContext<NorthwindContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("Northwind")));Write a PersistenceManager
EFPersistenceManager<T> is the piece that produces metadata and applies a save bundle. A subclass per DbContext is the usual arrangement, and it is where save interceptors live later.
using Breeze.Persistence.EFCore;
public class NorthwindPersistenceManager : EFPersistenceManager<NorthwindContext> {
public NorthwindPersistenceManager(NorthwindContext context) : base(context) { }
}That is enough to query and save. See The PersistenceManager.
Write a controller
using Breeze.AspNetCore;
using Breeze.Persistence;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq;
using System.Linq;
using System.Threading.Tasks;
[Route("breeze/[controller]/[action]")]
[BreezeQueryFilter]
public class NorthwindController : Controller {
private readonly NorthwindPersistenceManager _pm;
public NorthwindController(NorthwindContext context) {
_pm = new NorthwindPersistenceManager(context);
}
[HttpGet]
public IActionResult Metadata() => Ok(_pm.Metadata());
[HttpPost]
public Task<SaveResult> SaveChanges([FromBody] JObject saveBundle)
=> _pm.SaveChangesAsync(saveBundle);
[HttpGet]
public IQueryable<Customer> Customers() => _pm.Context.Customers;
[HttpGet]
public IQueryable<Order> Orders() => _pm.Context.Orders;
}Three kinds of member, and that is the whole shape of a Breeze controller:
| Member | Job |
|---|---|
Metadata() | describes the entity types, so the client can build its own model |
SaveChanges() | takes every pending change in one request and applies it |
each IQueryable<T> action | exposes one entity set for querying |
[BreezeQueryFilter] on the class is what makes the IQueryable actions queryable: it reads the filter, order, paging, select and expand instructions off the request and applies them to whatever the action returned, before it is serialized. Without the attribute each action returns its whole table. See Querying.
NOTE
The actions return IQueryable<T>, not List<T> or IActionResult. Returning a materialized list means the database sees no WHERE clause — the filter can still be applied in memory, but every row has already been fetched.
Point a client at it
With the route above, the service root is /breeze/Northwind and the endpoints fall out of [action]:
GET /breeze/Northwind/Metadata | metadata |
GET /breeze/Northwind/Customers?... | a query |
POST /breeze/Northwind/SaveChanges | a save |
import { EntityManager } from 'breeze-client';
const em = new EntityManager('/breeze/Northwind');Those names are what the client's defaults expect, so a breeze-client application needs no configuration to talk to this server. The Metadata/SaveChanges names are the convention; the IQueryable action names become the resource names a query targets (EntityQuery.from('Customers')).
Check it works
curl http://localhost:5000/breeze/Northwind/Metadata
curl 'http://localhost:5000/breeze/Northwind/Customers?$top=1'The first returns a JSON metadata document; the second, one customer.
WARNING
The controller above is the smallest thing that works, not something to deploy. It returns every customer and every order to anyone who can reach it, with no limit on how many rows or how deep a query may go, and it saves whatever it is sent. Security covers what to add and why.
Where to go next
- The PersistenceManager — what it does, and its lifetime
- Querying — the query filter, and limiting what clients may ask for
- Saving — interceptors, transactions and key mappings
- Security — the query and the change-set both arrive from a browser
- Metadata — what is in the document and how to change it
- Error handling — returning validation and concurrency errors a client understands