Ignore a property from a code first generated entity
Asked Answered
D

2

7

Is there a .Net attribute that will tell Breeze to completely ignore a property?

I know that making the property protected is one way to hide it from Breeze and prevent its serialization, but what if I would like it to remain public?

Differentia answered 29/4, 2013 at 9:23 Comment(0)
C
15

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_OLDproperty 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.

Corregidor answered 1/5, 2013 at 7:33 Comment(5)
Can you show me an example that uses DTOs, but only for sensitive data, and normal entities for the rest?Differentia
Bump. Such example would be awesome! Thank you Ward for this explanation. It is very clear and it answered my questions. The last nice thing to have would be this example :)Pack
Superb answer! I just want to add that if you want to ignore a type, you will need to use modelBuilder.Ignore<TypeToIgnore>(); and remember to set your database initialization strategy to null (otherwise EF will complain about the backing database not being in sync with your model)Thyroid
found the answer to how to mix DTOs here #22711211Lindsley
Regarding the 'BEWARE' section. We can still use the fake DbContext for the query(select) but use the real DBContext for the save and we can avoid the issue. In the example, oData query is able to retrieve a hidden/confidential field e.g. CustomerID_OLD since it uses the real DBcontext for query.Methodical
V
6

If you are using the Json.Net serializer ( the Breeze.WebApi default) you can use the JsonIgnoreAttribute, described here

Vazquez answered 30/4, 2013 at 5:31 Comment(8)
Can I mark a property to be excluded from the auto-generated metadata as well?Differentia
Not yet, but we are working on allowing for the possibility of tweaking the metadata after it has been delivered to the client.Vazquez
Or (better?) before it leaves the server.Differentia
@Differentia - There are heavier weight options such as defining the entity on the server such that it excludes the property. This is still an EF model with its own EF DbContext; it's just stripped down.Corregidor
@Differentia - Alternatively, you can hack the metadata before it ever leaves the server. It is just JSON and people say there are good tools for parsing that kind of thing in .NET. It all depends on how important this is to you.Corregidor
@Corregidor - Thanks for the hints - I will investigate. Just to clarify - I need the property in the DbContext (so I can filter on it for example). I just want to completely hide it from breeze (like if doesn't exist).Differentia
@Differentia Not sure I understand. How is the client app supposed to filter a property that it shouldn't be able to see? I'm guessing that you want to filter on it on the server (e.g., filter on the server for data that belong to the current user without revealing that data of this type belong to a user). I have a hacky way that I'll put in an answer.Corregidor
@Corregidor - you guessed right - on the server, filter on entities that belong to the current user.Differentia

© 2022 - 2024 — McMap. All rights reserved.