3

Say I have a database in which I am storing user details of this structure:

public class User
{
    public string UserId { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public string PasswordHash { get; set; }
}

I have a data access layer that works with this that contains methods such as GetById() and returns me a User object.

But then say I have an API which needs to return a users details, but not sensitive parts such as the PasswordHash. I can get the User from the database but then I need to strip out certain fields. What is the "correct" way to do this?

I've thought of a few ways to deal with this most of which involve splitting the User class into a BaseClass with non sensitive data and a derived class that contains the properties I would want kept secret, and then converting or mapping the object to the BaseClass before returning it, however this feels clunky and dirty.

It feels like this should be a relatively common scenario, so am I missing an easy way to handle it? I'm working with ASP.Net core and MongoDB specifically, but I guess this is more of a general question.

QTom
  • 1,441
  • 1
  • 13
  • 29
  • 1
    have a look at the view model pattern. – Daniel A. White Jan 31 '19 at 15:11
  • @DanielA.White I'm not sure I understand; I don't want to return a view model, this is an API returning plain data – QTom Jan 31 '19 at 15:19
  • https://stackoverflow.com/questions/11064316/what-is-viewmodel-in-mvc – Daniel A. White Jan 31 '19 at 15:19
  • @DanielA.White I understand what a viewmodel is, I just don't think it's what I'm after here. If the only data required is 2 fields from an object I already have, I don't want to create a ViewModel class and manually populate those fields? – QTom Jan 31 '19 at 15:24
  • What is the API? Is it a reference-based API that consumes `User`? Or is it something like a web-based API that gets xml, json, or similar? The answers will change depending. – Marc Gravell Jan 31 '19 at 15:26
  • @MarcGravell In this example the API would probably be called by an Angular frontend, but I'm not sure it really matters. I guess my question really is "Is there a simple way to convert an object to it's base class" but as far as I can find there isn't. – QTom Jan 31 '19 at 15:30
  • automapper is one strategy. – Daniel A. White Jan 31 '19 at 15:30
  • 1
    [JsonIgnore] public string PasswordHash { get; set; } .it will ignore when serializing the model – Jagadeesh Govindaraj Jan 31 '19 at 15:51

4 Answers4

2

It seems for my purposes the neatest solution is something like this:

Split the User class into a base class and derived class, and add a constructor to copy the required fields:

public class User
{
    public User() { }

    public User(UserDetails user)
    {
        this.UserId = user.UserId;
        this.Name = user.Name;
        this.Email = user.Email;
    }

    public string UserId { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}

public class UserDetails : User
{
    public string PasswordHash { get; set; }
}

The data access class would return a UserDetails object which could then be converted before returning:

UserDetails userDetails = _dataAccess.GetUser();
User userToReturn = new User(userDetails);

Could also be done using AutoMapper as Daniel suggested instead of the constructor method. Don't love doing this hence why I asked the question but this seems to be the neatest solution and requires the least duplication.

QTom
  • 1,441
  • 1
  • 13
  • 29
2

If you want ignore the property just add ignore annotation in you model like this, it will skip the property when model is serializing.

[JsonIgnore] 
public string PasswordHash { get; set; }

if you want ignore at runtime(that means dynamically).there is build function avilable in Newtonsoft.Json

public class User
{
    public string UserId { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public string PasswordHash { get; set; }
    //FYI ShouldSerialize_PROPERTY_NAME_HERE()
   public bool ShouldSerializePasswordHash()
    {
        // use the condtion when it will be serlized
        return (PasswordHash != this);
    }
}

It is called "conditional property serialization" and the documentation can be found here. hope this helps

Jagadeesh Govindaraj
  • 6,977
  • 6
  • 32
  • 52
  • 1
    This is by far the easiest solution, as long as you will *never* need to serialize `PasswordHash`. – Gabriel Luci Jan 31 '19 at 15:57
  • Yeah this is a good idea actually that I hadn't considered but in my case I do need to serialize it sometimes. – QTom Jan 31 '19 at 16:02
1

There are two ways to do this:

  1. Use the same class and only populate the properties that you want to send. The problem with this is that value types will have the default value (int properties will be sent as 0, when that may not be accurate).
  2. Use a different class for the data you want to send to the client. This is basically what Daniel is getting at in the comments - you have a different model that is "viewed" by the client.

The second option is most common. If you're using Linq, you can map the values with Select():

users.Select(u => new UserModel { Name = u.Name, Email = u.Email });

A base type will not work the way you hope. If you cast a derived type to it's parent type and serialize it, it still serializes the properties of the derived type.

Take this for example:

public class UserBase {
    public string Name { get; set; }
    public string Email { get; set; }
}

public class User : UserBase {
    public string UserId { get; set; }
    public string PasswordHash { get; set; }
}

var user = new User() {
    UserId = "Secret",
    PasswordHash = "Secret",
    Name = "Me",
    Email = "something"
};

var serialized = JsonConvert.SerializeObject((UserBase) user);

Notice that cast while serializing. Even so, the result is:

{
    "UserId": "Secret",
    "PasswordHash": "Secret",
    "Name": "Me",
    "Email": "something"
}

It still serialized the properties from the User type even though it was casted to UserBase.

Gabriel Luci
  • 38,328
  • 4
  • 55
  • 84
  • Casting a derived type to its base type does not cause an error (I tested this code). But yes, you will have to map the values regardless of what class you map too (base class or not). I don't think there is any benefit to using a base / derived type (for performance anyway). – Gabriel Luci Jan 31 '19 at 15:52
  • Sorry yes you're correct about the casting. Manually mapping it is for now I guess. – QTom Jan 31 '19 at 15:56
0

The problem is that you're viewing this wrong. An API, even if it's working directly with a particular database entity, is not dealing with entities. There's a separation of concerns issue at play here. Your API is dealing with a representation of your user entity. The entity class itself is a function of your database. It has stuff on it that only matters to the database, and importantly, stuff on it that does not matter to your API. Trying to have one class that can satisfy multiple different applications is folly, and will only lead to brittle code with nested dependencies.

More to the point, how are you going to interact with this API? Namely, if your API exposes your User entity directly, then any code that consumes this API either must take a dependency on your data layer so it can access User or it must implement its own class representing a User and hope that it matches up with what the API actually wants.

Now imagine the alternative. You create a "common" class library that will be shared between your API and any client. In that library, you define something like UserResource. Your API binds to/from UserResource only, and maps that back and forth to User. Now, you have completely segregated your data layer. Clients only know about UserResource and the only thing that touches your data layer is your API. And, of course, now you can limit what information on User is exposed to clients of your API, simply by how you build UserResource. Better still, if your application needs should change, User can change without spiraling out as an API conflict for each consuming client. You simply fixup your API, and clients go on unawares. If you do need to make a breaking change, you can do something like create a UserResource2 class, along with a new version of your API. You cannot create a User2 without causing a whole new table to be created, which would then spiral out into conflicts in Identity.

Long and short, the right way to go with APIs is to always use a separate DTO class, or even multiple DTO classes. An API should never consume an entity class directly, or you're in for nothing but pain down the line.

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • In my case, the only thing using this API is a Angular front end website, so I have to create a separate class model in typescript anyway which is why I was just trying to do it in a quick easy way, but you raise good points about this in a bigger picture. – QTom Jan 31 '19 at 17:26