3

I have a working model that i use within my application. I want to expose some of this data via a public API, but i have fields that I don't want to return for general consumption. One laborious way to do this is to create a seperate model and create use automapper to map my output to create some a series of DTOs.

What i'd like to do is annotate my model with a custom attribute and then somehow have some sort of extension method or web API actionfilter filter out the annotated fields at runtime before sending JSON to the client. I can't use JsonIgnore because i need those fields for operations within my application.

Can someone give me an overview on how I would do this?

thanks in advance

Edit

So I am thinking I can use the newtonsoft ShouldSerialize property , however I'm at a loss in regards to finding an elegant way of setting a condition that triggers this. I have a complex model and I would think at runtime I would need to reflect the entire output, detect any class within a certain namespace, and set some value that causes ShouldSerialize to return true

2

1 Answer 1

1

If you absolutely want to avoid DTOs and [JsonIgnore], and really want to use a custom attribute, you would probably have to use some reflection. I'll present a solution which is far from being the best option, but it can give you some ideas.

First, create a custom attribute to mark the properties of your model which aren't supposed to be displayed via your public API:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
class DontSendInPublicApiAttribute : Attribute { }

You'd have to create a method for "erasing" the data on the object's properties which you do not want to display.

public static void RemoveSecretData(object obj)
{
    // Retrieve all public instance properties defined for the object's type and marked with [DontSendInPublicApi]
    var propertiesToHide = obj.GetType()
        .GetProperties(BindingFlags.Instance | BindingFlags.Public)
        .Where(p => p.GetCustomAttribute<DontSendInPublicApiAttribute>() != null);

    foreach (var prop in propertiesToHide)
    {
        // Set all of these properties in the given object to their default values.
        // VALUE TYPES (ints, chars, doubles, etc.) will be set to default(TheTypeOfValue), by calling Activator.CreateInstance(TheTypeOfValue).
        // REFERENCE TYPES will simply be set to null.
        var propertyType = prop.PropertyType;
        if (propertyType.IsValueType)
            prop.SetValue(obj, Activator.CreateInstance(prop.PropertyType));
        else
            prop.SetValue(obj, null);
    }
}

Then apply the attribute to any fields in your model which you wish to hide:

class Person
{
    public string Name { get; set; }
    public int Age { get; set; }

    [DontSendInPublicApi]
    public string Occupation { get; set; }
    [DontSendInPublicApi]
    public int Salary { get; set; }
}

And here's an example of how to call it:

var person = new Person() { Name = "John", Age = 29, Occupation = "Engineer", Salary = 200000 };
RemoveSecretData(person);

After RemoveSecretData(person) has been executed, you'll have the Occupation and Salary properties of the person object set to null and 0, respectivelly.

Notes about this solution:

  • Works only on properties. You'd have to modify the RemoveSecretData() method to also work with fields, if necessary.
  • Doesn't recursivelly access the objects graph. If your object references another object with some property marked with [DontSendInPublicApi], this property will not be hidden. You'd have to modify the RemoveSecretData() method to perform recursive calls on deeper objects, if necessary. Watch out for circular references if you plan on doing so.
  • Hidden properties will still be shown on output JSON, but value-typed properties will always present a value of 0 (zero) and reference-typed properties will always present a value of null.
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you this is along the lines of what I was thinking but I need the entire property removed. I'm thinking at this point of just returning a dictionary since it all gets serialized into JSON anyways.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.