This answer is as much a tutorial on analyzing the problem as answer to the question.
Step 1 - Simplify and Isolate
Let's simplify radically until we find the missing step. Let's start with the simplest entity type you have ... one with 2 to 5 properties max. Don't have one? Make one up. Cut down the Manufacturer
to just "id" and "name". We're trying to get the mechanics down first.
You aren't using any breeze components on the server. Fine. Pick a server endpoint that delivers that test entity data. Have that endpoint deliver a JSON array with just one instance. Show us the JSON that are arriving on the client ... the entire JSON payload, exactly as it arrives on the wire. It should be brief; if it isn't, you haven't simplified enough.
THEN we can figure out whether you need a JsonResultsAdapter and what it should look like if you do.
Show us the exact sequence by which you populate the metadataStore
with an EntityType, ctor, and initializer. Frankly, I'd rather have no ctor or initializer until we've got the first one working.
How do you make sure that the EntityManager
you create is using that store? We need to see your configuration code and how you new-up the EntityManager
and use it to query the endpoint.
If you follow my suggestion there won't be much code. Twenty lines maybe. And the JSON feed should be about 10 lines. If you can't hit these numbers, you haven't simplified enough.
Step 2 - Review the simpler example
Now that you've re-worked the example, I have a better idea where to look.
Two things leap out at me:
- The JSON result from the server
- The
camelCase
naming convention
JSON from the Server
Let's pretty print the JSON results you provided and discuss them:
{
"id": "141",
"name": "Trek",
"website": "http:\/\/www.trekbikes.com\/",
"approved": "1",
"user_id": "3"
}
Breeze won't know what to do with that JSON object because it lacks type information. To Breeze its just an arbitrary object, perhaps the result of a projection.
Compare that to the JSON result of a query hitting the DocCode Web API. Here's the URL generated for the query:
>http://localhost:47595/breeze/northwind/Suppliers/?$top=1
and here's the (abbreviated) JSON result
[
{
"$id":"1",
"$type":"Northwind.Models.Supplier, DocCode.Models",
"SupplierID":1,
"CompanyName":"Exotic Liquids"
}
]
By default a Breeze client expects data that have been serialized with JSON.NET, the default serializer in ASP.NET.
A JSON.NET payload is either a node or an array of nodes. JSON.NET adds its own $id
and $type
properties to each node.
I want to focus your attention on the $type
property which you may recognize as the full name (class-with-namespace, assembly-name) of a .NET type.
You could get away without the $id
property.
The $id is an auto-incrementing serialization key. Often the same object appears multiple times in a payload. Instead of repeating the contents, JSON.NET substitutes a simple node like {$ref: #}
where #
refers to the $id
of an earlier node. This approach both reduces the payload size and breaks circular references.
But Breeze is really looking forward to that $type
property. That is how it connects the JSON object/node to a type in your metadata. If your manufacturer example node had one, it might like this:
"$type": "StackAndReach.Manufacturer, MyModel"
I don't know how you're serializing data on your server. It would seem you are using something other than JSON.NET.
That is cool. I'm just telling you how Breeze works by default; it is very .NET friendly. But Breeze doesn't need .NET. It is a pure JavaScript library. You just have to tell it what you want.
Use toType(...)
The simplest thing you can do is add toType
to your query.
var query = breeze.EntityQuery.from('Manufacturers')
.where( ... )
.toType( 'Manufacturer' );
In this way, you state explicitly that the top level node(s) returned by the 'Manufacturers' endpoint contain data for the Manufacturer
type that you described in metadata.
I'll bet this works for you right away (once you fix the Naming Convention problem described below).
This is an effective approach but it has several drawbacks. I'll mention two:
You have to remember to add it to every query.
It only works for top-level entities; if won't work for nested entities such as are returned when you apply the .expand()
clause.
I prefer to teach the Breeze client how to interpret the JSON results on its own ... with a custom JsonResultsAdapter
.
Custom JsonResultsAdapter
Check out the Breeze Edmunds Sample in which a Breeze client consumes data from the Edmunds Vehicle Information service.
The Edmunds server sends a completely different kind of JSON payload in response to queries. Here is a snippet:
{
"makeHolder":[
{
"id":200347864,
"models":[
{
"link":"/api/vehicle/am-general/hummer",
"id":"AM_General_Hummer",
"name":"Hummer"
}
],
"name":"AM General",
"niceName":"amgeneral",
"manufacturer":null,
"attributeGroups":{
}
},
... more ...
]
}
No $type
there either. What did the Breeze developer do? He wrote a custom Breeze JsonResultsAdapter and it's in the file app/jsonResultsAdapter.js.
I'm not going to reproduce that file here although it is only 40 lines. I want you to read the jsonResultsAdapter
documentation, pull down the Edmunds sample, and read it for yourself.
I will summarize what it does and how it works. Breeze calls your jsonResultsAdapter
first when it receives a JSON payload and again as it processes each node in that payload. Your job is tell Breeze how to treat that node which you do by tweaking the node itself and returning a meta object that describes the node.
Here's a snippet:
>if (node.id && node.models) {
// move 'node.models' links so 'models' can be empty array
node.modelLinks = node.models;
node.models = [];
return { entityType: "Make" }
}
There are three activities in this snippet:
- identifying what the node is about (the
if ...
)
- adjusting the node values (for whatever reasons make sense to you)
- composing and returning the "meta" object result.
Focus on #3. That's where the developer told Breeze "Turn this node into a Make
entity.
Structural type matching
You might say, "Hey Ward, the Manufacturer
entity type matches the JSON object structure exactly. Breeze should recognize it as a Manufacturer
."
Breeze does not divine the entity type by matching the type structure. Nor do I think it should ... because different types often share the same structure. For example: I have a StatusCode
and ProductCode
entity types that are both { id: int, name: string}
. We have plenty of other enhancements to work on; coping with type ambiguity is not high on our list.
Naming Convention
Finally, let's return to the other problem that I saw.
Your configureBreezeManager
method begins:
breeze.NamingConvention.camelCase.setAsDefault();
You've changed the default Naming Convention from "same-on-client-and-server" to "pascalCase-on-client/CamelCase-on-server".
By switching to the camelCase
convention, you're telling Breeze that a client-side property foo
should be sent to the server as Foo
.
Is that the right thing to do? It would be if your server expected CamelCase property names. But, based on the property names in your JSON payload, the server expects CamelCase too. The property names are identical on client and server. Bad things will happen if breeze sends a manufacturer with a Name
property value instead of a name
property value.
Leave the breeze default, "do nothing" convention in place. Don't override it. Remove that pascalCase convention line from your configureBreezeManager
.
Saving changes
We've been talking about query results. We haven't talked at all about how you're going to save changes back to the server.
I'm sure you have your own protocol (something ReST-like?) and serialization format. That is a completely different discussion. Let's not get into that in this Stack Overflow question. I'm just alerting you to the probability that you'll be scratching your head about this one pretty soon.