It is tricky to devise an easy, maintainable way to have EF-generated metadata say one thing to the client and another to EF itself.
But you can do it if you're willing to have two DbContexts: the "real" DbContext for server-side model operations and another DbContext strictly for client metadata generation.
A DbContext for Metadata
It isn't as difficult as it sounds. I experimented with such a thing in DocCode. I created a NorthwindMetadataContext
that inherits from the NorthwindContext
. It hides the disused CustomerID_OLD
property.
Here is that DbContext in its entirety:
public class NorthwindMetadataContext : NorthwindContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Customer>().Ignore(t => t.CustomerID_OLD);
}
}
You read that right! I simply add a single EF Fluent API instruction to hide the CustomerID_OLD
property from the NorthwindMetadataContext
.
It's still on the Customer
type and it's still known to the base NorthwindContext
. That means a query will return the CustomerID_OLD
property when you query with the NorthwindContext
but will not return the property when you query with the NorthwindMetadataContext
.
Of course you only use the NorthwindMetadataContext
to generate metadata for the Breeze client. All of your server logic continues to use the "real", base NorthwindContext
I make sure that's what happens by changing the way the NorthwindRepository
implements the Metadata
property:
public string Metadata
{
get
{
var metaContextProvider = new EFContextProvider<NorthwindMetadataContext>();
return metaContextProvider.Metadata();
}
}
All other members of the NorthwindRepository
use an EFContextProvider for the "real" DbContext:
private readonly EFContextProvider<NorthwindContext>
_contextProvider = new EFContextProvider<NorthwindContext>();
This works like a charm. As far as the client know, the CustomerID_OLD
property doesn't exist.
Try to keep the hidden data off the wire
Although you've hidden the property from Breeze clients, the property remains on the server-side entity type and, because we use the "real" DbContext for the query and save operations, you can see the CustomerID_OLD
property going over the wire in the query results payload.
To keep that from happening, you can add the JSON.NET serializer [JsonIgnore]
attribute to the Customer.CustomerID_OLD
property in the model (you use other JSON.NET configuration options if you don't want to pollute your model with JSON.NET serialization attributes).
Run again and CustomerID_OLD
is no longer serialized. The CustomerID_OLD
property is now completely invisible to the client while still accessible in your model on the server.
BEWARE
The property that is hidden in metadata is hidden from Breeze clients ... but not from the world. Because you want that property available on the SERVER-SIDE TYPE, an evil client (not a Breeze client) can still GET that data with a projection even though you have hidden it when serializing the complete type.
The following query URL returns projection data that include the CustomerID_OLD
that would be invisible if you queried for entire Customer
objects.
http://localhost:47595/breeze/Northwind/Customers?$filter=startswith(CompanyName,'C') eq true&$select=CustomerID_OLD,CompanyName
Here is a bit of the result:
{
"$id": "1",
"$type": "_IB_em9q7XTURqKf5bmIrAQD0bJ6f_po[[System.String, mscorlib],[System.String, mscorlib]], _IB_em9q7XTURqKf5bmIrAQD0bJ6f_po_IdeaBlade",
"CustomerID_OLD": "CACTU",
"CompanyName": "Cactus Comidas para llevar"
},
Perhaps more seriously, a POST can update to that hidden property in the payload of a "SaveChanges" request.
As always with sensitive data, you must inspect every save request to make sure that the user is permitted to save each and every changed property value identified in the OriginalValues
collection.
If you have security concerns, I feel it is much safer to use DTOs for types that carry data that must not be exposed to unauthorized clients. I don't like DTOs for every type; that's overkill that can ruin productivity. But I do like them for entity types with significant confidentiality requirements.
Example code
I have not yet decided if I will keep this example in the published DocCode or stash it in my private reserve. It is a cool trick.
My worry is that people will think this technique is a secure way to hide confidential data which it definitely is not. It's fine for hiding data that you'd rather keep hidden. But if it MUST be kept off the wire, you better use a DTO.
Let me know if what I've described is clear enough for you to proceed without a concrete example.