Metadata
A Breeze client does not know your model until the server describes it. That description is the metadata document: entity types, their properties and data types, keys, and the navigation properties that join them. The client builds its MetadataStore from it, and everything else — typed queries, change tracking, validation, relationship fixup — depends on it.
[HttpGet]
public IActionResult Metadata() => Ok(_pm.Metadata());Metadata() returns it as a JSON string, generated from your ORM mapping. You do not write it by hand.
What is in it
BreezeMetadata is the shape:
| Member | Contents |
|---|---|
structuralTypes | one MetaType per entity and complex type |
enumTypes | the C# enums a property can hold |
namingConvention | which name translation the client should use, if the server wants to choose |
metadataVersion | the document's own version |
Each MetaType carries its shortName and namespace, its baseTypeName for an inheritance hierarchy, its defaultResourceName — the endpoint a query for that type goes to — and its autoGeneratedKeyType, plus its data and navigation properties.
A data property carries the things a client needs in order to validate before it ever reaches the server: dataType, isPartOfKey, isNullable, maxLength, defaultValue, concurrencyMode, and any validators.
How it is generated
For EF Core, from the DbContext's model — the same mapping EF itself uses, so it already reflects your fluent configuration and data annotations. autoGeneratedKeyType becomes Identity when the key is an identity column and None otherwise. For NHibernate it comes from the NHibernate mapping.
Nothing is cached across requests. A PersistenceManager builds the document the first time Metadata() is called on it and holds it for that instance's lifetime — and since the manager is per request, that means once per request that asks for it. Clients fetch metadata once at startup, so this is rarely worth attention; if it becomes so, cache the string yourself.
Naming
CompanyName on the server conventionally becomes companyName on the client. That translation is the client's job, done by its NamingConvention, which defaults to camelCase. The server sends its own names, and the metadata's nameOnServer for each property is what the client translates from.
IMPORTANT
Do not also turn on camel casing in the JSON serializer. The second argument of UpdateWithDefaults defaults to false; leave it there. Setting it means the names are camel-cased on the wire and translated again on arrival, and properties stop matching the metadata.
If your server genuinely sends the names the client should use, the fix belongs on the client — NamingConvention.none — not here.
Enums
A property whose CLR type is an enum is described by name, and the enum itself appears in enumTypes with its values. Whether the values travel as strings or integers is a serializer setting:
BreezeConfig.Instance.UseIntEnums = false; // the default: stringsIt has to agree with what the client expects, so change it before anything serializes — at startup, alongside the rest of your JSON configuration.
Supplying your own
Two reasons to override the generated document: your model includes types the ORM does not map, or you want to add information of your own for the client to read.
EFPersistenceManager<T> calls BuildAltJsonMetadata(), which returns null by default. Return a JSON string and it is merged into the document under altMetadata, alongside the generated metadata rather than in place of it:
public class AltMetadataPersistenceManager : EFPersistenceManager<NorthwindContext> {
public AltMetadataPersistenceManager(NorthwindContext context) : base(context) { }
protected override string? BuildAltJsonMetadata() {
return "{ \"uiHints\": { \"Customer\": { \"icon\": \"person\" } } }";
}
}To replace the document outright, override BuildJsonMetadata() instead and return whatever you like — including a document read from a file, which is how a model with no ORM behind it is described.
NOTE
Hand-written metadata is a client-side option too: a client can be given its metadata directly and never call this endpoint. That is the usual route for a non-Breeze server.