I want to implement a conditional version of [JsonIgnore] attribute in C#. I don't want to use ShouldSerializePropertyName due to it's dependency on hardcoded property name.
My API model inherits from the database model and I certainly want to ignore database Ids but I also want to ignore some other properties based on which features the customer payed for. I don't want to use ShouldSerialize technique because the other developers in my team could change the database property name and accidentally make visible what should not be visible.
I read if I can optionally turn off the JsonIgnore attribute at runtime
but it looks like the suggested technique turns off all JsonIgnore together. What I want to do is to have just some of them off based on some condition.
Is there a work around? Is there some attribute which could do that? If I need to write a custom attribute, could you show me how please? Thanks!
This is an interesting problem. My answer borrows heavily from the link you provided, but checks for a custom attribute defining your "Premium Content" (things that the user paid for):
Like your link, I have defined a class Foo, which will be serialized. It contains a child object PremiumStuff, which contains things that should only be serialized if the user paid for them. I have marked this child object with a custom attribute PremiumContent, which is also defined in this code snippet. I then used a custom class that inherits from DefaultContractResolver just like the link did, but in this implementation, I am checking each property for our custom attribute, and running the code in the if block only if the property is marked as PremiumContent. This conditional code checks a static bool called AllowPremiumContent to see whether we are allowing the premium content to be serialized. If it is not allowed, then we are setting the Ignore flag to true:
class Foo
{
public int Id { get; set; }
public string Name { get; set; }
[JsonIgnore]
public string AlternateName { get; set; }
[PremiumContent]
public PremiumStuff ExtraContent { get; set; }
}
class PremiumStuff
{
public string ExtraInfo { get; set; }
public string SecretInfo { get; set; }
}
class IncludePremiumContentAttributesResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
IList<JsonProperty> props = base.CreateProperties(type, memberSerialization);
foreach (var prop in props)
{
if (Attribute.IsDefined(type.GetProperty(prop.PropertyName), typeof(PremiumContent)))
{
//if the attribute is marked with [PremiumContent]
if (PremiumContentRights.AllowPremiumContent == false)
{
prop.Ignored = true; // Ignore this if PremiumContentRights.AllowPremiumContent is set to false
}
}
}
return props;
}
}
[System.AttributeUsage(System.AttributeTargets.All)]
public class PremiumContent : Attribute
{
}
public static class PremiumContentRights
{
public static bool AllowPremiumContent = true;
}
Now, let's implement this and see what we get. Here is my test code:
PremiumContentRights.AllowPremiumContent = true;
Foo foo = new Foo()
{
Id = 1,
Name = "Hello",
AlternateName = "World",
ExtraContent = new PremiumStuff()
{
ExtraInfo = "For premium",
SecretInfo = "users only."
}
};
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.ContractResolver = new IncludePremiumContentAttributesResolver();
settings.Formatting = Formatting.Indented;
string json = JsonConvert.SerializeObject(foo, settings);
Debug.WriteLine(json);
In the first line, PremiumContentRights.AllowPremiumContent is set to true, and here is the output:
{
"Id": 1,
"Name": "Hello",
"ExtraContent": {
"ExtraInfo": "For premium",
"SecretInfo": "users only."
}
}
If we set AllowPremiumContent to False and run the code again, here is the output:
{
"Id": 1,
"Name": "Hello"
}
As you can see, the ExtraContent property is included or ignored depending on the state of the AllowPremiumContent variable.
Related
I have maybe 60-70 classes that all have various Id columns that I would like to exclude when I return JSON data from the Web API. Internally I join on Id, but anything front-facing uses a Guid. So my primary key is the Id (int) and then there is a Guid there for the outside world to use to make things more secure.
Typically you just add [JsonIgnore] over the property and it takes care of it, but I have a lot of classes that may get updated from time to time. Whenever I scaffold everything and force an overwrite, it's going to remove my changes.
Instead of manually adding [JsonIgnore] to every Id column I want to exclude, it seems more logical to just handle this in OnModelCreating. I am able to loop through properties and use .Ignore, but that removes the property from everything else as well. I just don't want it to serialize and return any of the columns named "Id" and any foreign keys (which are also Ids).
So here is an example from one class
[JsonIgnore]
public int Id { get; set; }
public Guid Guid { get; set; }
public string Name { get; set; }
public bool? Active { get; set; }
[JsonIgnore]
public int HoldTypeId { get; set; }
public DateTime CreateDateTime { get; set; }
public DateTime UpdateDateTime { get; set; }
I can "make it work" the hard way, but I'm hoping there is a quick and easy way to achieve the same results so I can spend time on the important pieces.
EDIT:
Here is what is returning the data to the user.
// GET: api/Distributors
[HttpGet]
public async Task<ActionResult<IEnumerable<Distributor>>> GetDistributor()
{
return await _context.Distributor.ToListAsync();
}
You could write your own DefaultContractResolver to exclude any property that you want on serialization process.
Below there is an example for it:
public class PropertyIgnoringContractResolver : DefaultContractResolver
{
private readonly Dictionary<Type, string[]> _ignoredPropertiesContainer = new Dictionary<Type, string[]>
{
// for type student, we would like to ignore Id and SchooldId properties.
{ typeof(Student), new string[] { "Id", "SchoolId" } }
};
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
string[] ignoredPropertiesOfType;
if (this._ignoredPropertiesContainer.TryGetValue(member.DeclaringType, out ignoredPropertiesOfType))
{
if (ignoredPropertiesOfType.Contains(member.Name))
{
property.ShouldSerialize = instance => false;
// Also you could add ShouldDeserialize here as well if you want.
return property;
}
}
return property;
}
}
then you should configure this in your Startup.cs in ConfigureServices like below
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddJsonOptions(options => options.SerializerSettings.ContractResolver = new PropertyIgnoringContractResolver());
}
However what i actually would do is that i would create response DTO's to match the needs of my API responses. Instead of returning raw entity types. Like;
[HttpGet]
public async Task<ActionResult<IEnumerable<Distributor>>> GetDistributor()
{
return await _context.Distributor.Select(dist => new DistributorDTO
{
Name = dist.Name,
// so on..
}).ToListAsync();
}
By implementing something like this, you would also optimize your database queries as well by only selecting the properties that the API response requires.
I am writing an application where I do need to handle some scenarios beforehand in my controller class like certain property must have been provided otherwise status code would be BadRequest. Here is my class lookalike.
public class MyClass
{
[Required]
public IEnumerable<NewObject> NewObjects { get; set; }
}
public class NewObject : INewObject
{
public NewObject(string typeName, IEnumerable<Property> properties)
{
TypeName = typeName;
Properties = properties;
}
[JsonProperty(Required = Required.Always)]
public string TypeName { get; }
public IEnumerable<IProperty> Properties { get; }
}
public interface IProperty
{
string Name { get; }
object Value { get; }
}
Now though I have marked TypeName as required property and if I do not pass that in json content while sending request from swagger, json deserialization doesn't fail. I tried to search but I got an answer that setting Required to Always should work.
Below is the Json Content I am passing through swagger:
{
"NewObjects": [
{
"Properties": [
{
"Name": "string",
"Value": ''
}
]
}
]
}
I wrote below piece of code too by looking at one of the solution:
var config = new HttpConfiguration();
var jsonFormatter = config.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Error;
config.MapHttpAttributeRoutes();
Still it's not working:
Note: I am using Newtonsoft.Json version 11.0.1
This seems to be swagger issue because when I serialize input C# object and when again deserialize it, I am getting proper error.
For example in my controller class if I say:
var input2 = JsonConvert.DeserializeObject<MyClass>(JsonConvert.SerializeObject(input))
Then input2 throws an exception.
You can take a look at FluentValidation. If I am not mistaken it is designed to validate data in jsons forms specifically.
using FluentValidation;
public CertainActionValidator()
{
RuleFor(x => x.PropertyName).NotEmpty()
}
You can add plenty of additional conditions in there.
From Microsoft MVC doc, related to Authoring Tag Helpers, I can read this:
using System;
namespace AuthoringTagHelpers.Models
{
public class WebsiteContext
{
public Version Version { get; set; }
public int CopyrightYear { get; set; }
public bool Approved { get; set; }
public int TagsToShow { get; set; }
}
}
and this:
using System;
using AuthoringTagHelpers.Models;
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace AuthoringTagHelpers.TagHelpers
{
public class WebsiteInformationTagHelper : TagHelper
{
public WebsiteContext Info { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "section";
output.Content.SetHtmlContent(
$#"<ul><li><strong>Version:</strong> {Info.Version}</li>
<li><strong>Copyright Year:</strong> {Info.CopyrightYear}</li>
<li><strong>Approved:</strong> {Info.Approved}</li>
<li><strong>Number of tags to show:</strong> {Info.TagsToShow}</li></ul>");
output.TagMode = TagMode.StartTagAndEndTag;
}
}
}
I never saw this kind of code before, where public WebsiteContext Info { get; set; } can automagically instantiate an object???
How it works? Is there any documentation on it?
The answer is in the document you linked:
Note
In the Razor markup shown below:
<website-information info="new WebsiteContext {
Version = new Version(1, 3),
CopyrightYear = 1638,
Approved = true,
TagsToShow = 131 }" />
Razor knows the info attribute is a class, not a string, and you want to write C# code. Any non-string tag helper attribute should be written without the # character.
The tag helper itself doesn't know how to instantiate the instance. You have to do it manually in the Razor markup or set it to a default value in the property declaration or class constructor in order for it to be non-null. Here is an example of setting the instance in the property declaration.
public WebsiteContext { get; set; } = new WebSiteContext
{
Version = new Version(1, 3),
CopyrightYear = 1638,
Approved = true,
TagsToShow = 131
};
public WebsiteContext Info { get; set; } is not instantiating anything here. If you call the following code:
var websiteInformationTagHelper = new WebsiteInformationTagHelper();
then websiteInformationTagHelper.Info will be equal to null
Note, that it is now possible in c# to assign default values like the following which is a little bit different than what you are wondering about:
public WebsiteContext Info { get; set; } = new WebsiteContext()
Not automatically, but yes. The get and set keywords are shorthand for methods that are called after the property is accessed (get) or assigned to (set). You can add a body with a regular code block:
get { return _backingField; }
set { _backingField = value; }
The value keyword represents the value being assigned to the property and you can do most things in those blocks, same as any method, including instantiating an object.
Microsoft documentation - Auto implemented properties:
learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/auto-implemented-properties
If you're referring to instantiating the parent object, that I don't believe makes sense.
I am creating a JSON file with Newtonsoft.Json from a set of classes. The file created is very large, so I have created JsonProperty's for the properties to reduce the size and added JsonIgnore and custom formatting for some datatypes.
The result is a reduction from 24MB to 1MB, which is great; however, I'd like the option to produce either the full version or the reduced property version at runtime.
Is there anyway I can get the serializer to optionally use the attributes?
Yes, this can be done using a custom ContractResolver.
You didn't show any code, so I'll just make up an example. Let's say I have a class Foo as shown below. I want the Id and Name properties in the serialization output, but I'm definitely not interested in the AlternateName and Color. I've marked those with [JsonIgnore]. I want the description to appear, but sometimes this can get really long, so I've used a custom JsonConverter to limit its length. I also want to use a shorter property name for the description, so I've marked it with [JsonProperty("Desc")].
class Foo
{
public int Id { get; set; }
public string Name { get; set; }
[JsonIgnore]
public string AlternateName { get; set; }
[JsonProperty("Desc")]
[JsonConverter(typeof(StringTruncatingConverter))]
public string Description { get; set; }
[JsonIgnore]
public string Color { get; set; }
}
When I serialize an instance of the above...
Foo foo = new Foo
{
Id = 1,
Name = "Thing 1",
AlternateName = "The First Thing",
Description = "This is some lengthy text describing Thing 1 which you'll no doubt find very interesting and useful.",
Color = "Yellow"
};
string json = JsonConvert.SerializeObject(foo, Formatting.Indented);
...I get this output:
{
"Id": 1,
"Name": "Thing 1",
"Desc": "This is some lengthy text describing Thing 1 "
}
Now, let's say that I sometimes want to get the full JSON output, ignoring my customizations. I can use a custom ContractResolver to programmatically "unapply" the attributes from the class. Here's the code for the resolver:
class IgnoreJsonAttributesResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
IList<JsonProperty> props = base.CreateProperties(type, memberSerialization);
foreach (var prop in props)
{
prop.Ignored = false; // Ignore [JsonIgnore]
prop.Converter = null; // Ignore [JsonConverter]
prop.PropertyName = prop.UnderlyingName; // restore original property name
}
return props;
}
}
To use the resolver, I add it to the JsonSerializerSettings and pass the settings to the serializer like this:
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.ContractResolver = new IgnoreJsonAttributesResolver();
settings.Formatting = Formatting.Indented;
string json = JsonConvert.SerializeObject(foo, settings);
The output now includes the ignored properties, and the description is no longer truncated:
{
"Id": 1,
"Name": "Thing 1",
"AlternateName": "The First Thing",
"Description": "This is some lengthy text describing Thing 1 which you'll no doubt find very interesting and useful.",
"Color": "Yellow"
}
Full demo here: https://dotnetfiddle.net/WZpeWt
Json support us to ignore property that don't want return.
Example
class Foo
{
public int Id { get; set; }
public string Name { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string AlternateName { get; set; }
}
How to use it:
Foo foo = new Foo
{
Id = 1,
Name = "Thing 1",
AlternateName = null,
};
string json = JsonConvert.SerializeObject(foo);
If you are willing to use F# (or simply use an API not optimized for C#), the FSharp.JsonSkippable library contains a generic wrapper type that allows you to control in a simple and strongly typed manner whether to include a given property when serializing (and determine whether a property was included when deserializing), and moreover, to control/determine exclusion separately of nullability. (Full disclosure: I'm the author of the library.)
I am using HttpPatch to partially update an object. To get that working I am using Delta and Patch method from OData (mentioned here: What's the currently recommended way of performing partial updates with Web API?). Everything seems to be working fine but noticed that mapper is case sensitive; when the following object is passed the properties are getting updated values:
{
"Title" : "New title goes here",
"ShortDescription" : "New text goes here"
}
But when I pass the same object with lower or camel-case properties, Patch doesn't work - new value is not going through, so it looks like there is a problem with deserialisation and properties mapping, ie: "shortDescription" to "ShortDescription".
Is there a config section that will ignore case sensitivity using Patch?
FYI:
On output I have camel-case properties (following REST best practices) using the following formatter:
//formatting
JsonSerializerSettings jss = new JsonSerializerSettings();
jss.ContractResolver = new CamelCasePropertyNamesContractResolver();
config.Formatters.JsonFormatter.SerializerSettings = jss;
//sample output
{
"title" : "First",
"shortDescription" : "First post!"
}
My model classes however are follwing C#/.NET formatting conventions:
public class Entry {
public string Title { get; set;}
public string ShortDescription { get; set;}
//rest of the code omitted
}
Short answer, No there is no config option to undo the case sensitiveness (as far as i know)
Long answer: I had the same problem as you today, and this is how i worked around it.
I found it incredibly annoying that it had to be case sensitive, thus i decided to do away with the whole oData part, since it is a huge library that we are abusing....
An example of this implementation can be found at my github github
I decided to implement my own patch method, since that is the muscle that we are actually lacking. I created the following abstract class:
public abstract class MyModel
{
public void Patch(Object u)
{
var props = from p in this.GetType().GetProperties()
let attr = p.GetCustomAttribute(typeof(NotPatchableAttribute))
where attr == null
select p;
foreach (var prop in props)
{
var val = prop.GetValue(this, null);
if (val != null)
prop.SetValue(u, val);
}
}
}
Then i make all my model classes inherit from *MyModel*. note the line where i use *let*, i will excplain that later. So now you can remove the Delta from you controller action, and just make it Entry again, as with the put method. e.g.
public IHttpActionResult PatchUser(int id, Entry newEntry)
You can still use the patch method the way you used to:
var entry = dbContext.Entries.SingleOrDefault(p => p.ID == id);
newEntry.Patch(entry);
dbContext.SaveChanges();
Now, let's get back to the line
let attr = p.GetCustomAttribute(typeof(NotPatchableAttribute))
I found it a security risk that just any property would be able to be updated with a patch request. For example, you might now want the an ID to be changeble by the patch. I created a custom attribute to decorate my properties with. the NotPatchable attribute:
public class NotPatchableAttribute : Attribute {}
You can use it just like any other attribute:
public class User : MyModel
{
[NotPatchable]
public int ID { get; set; }
[NotPatchable]
public bool Deleted { get; set; }
public string FirstName { get; set; }
}
This in this call the Deleted and ID properties cannot be changed though the patch method.
I hope this solve it for you as well. Do not hesitate to leave a comment if you have any questions.
I added a screenshot of me inspecting the props in a new mvc 5 project. As you can see the Result view is populated with the Title and ShortDescription.
It can be done quite easily with a custom contract resolver that inherits CamelCasePropertyNamesContractResolver and implementing CreateContract method that look at concrete type for delta and gets the actual property name instead of using the one that comes from json. Abstract is below:
public class DeltaContractResolver : CamelCasePropertyNamesContractResolver
{
protected override JsonContract CreateContract(Type objectType)
{
// This class special cases the JsonContract for just the Delta<T> class. All other types should function
// as usual.
if (objectType.IsGenericType &&
objectType.GetGenericTypeDefinition() == typeof(Delta<>) &&
objectType.GetGenericArguments().Length == 1)
{
var contract = CreateDynamicContract(objectType);
contract.Properties.Clear();
var underlyingContract = CreateObjectContract(objectType.GetGenericArguments()[0]);
var underlyingProperties =
underlyingContract.CreatedType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var property in underlyingContract.Properties)
{
property.DeclaringType = objectType;
property.ValueProvider = new DynamicObjectValueProvider()
{
PropertyName = this.ResolveName(underlyingProperties, property.PropertyName),
};
contract.Properties.Add(property);
}
return contract;
}
return base.CreateContract(objectType);
}
private string ResolveName(PropertyInfo[] properties, string propertyName)
{
var prop = properties.SingleOrDefault(p => p.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase));
if (prop != null)
{
return prop.Name;
}
return propertyName;
}
}