I have a DTO that looks like this:
public class MyDto
{
[MyAttribute("attribute1")]
public string Property1 {get;set;}
[MyAttribute("attribute2")]
public string Property2 {get;set;}
}
If I have the string "attribute1", how do I use that to get to the value of Property1 in an instance of MyDto?
Use Reflection. Unfortunately, there's no way to obtain the property from an attribute: you have to iterate through each property and check its attribute.
Not the most robust code, but try this:
public class MyAttributeAttribute : Attribute
{
public MyAttributeAttribute(string value)
{
Value=value;
}
public string Value { get; private set; }
}
public class MyDto
{
[MyAttribute("attribute1")]
public string Property1 { get; set; }
[MyAttribute("attribute2")]
public string Property2 { get; set; }
}
class Program
{
static void Main(string[] args)
{
MyDto dto=new MyDto() { Property1="Value1", Property2="Value2" };
string value=GetValueOf<string>(dto, "attribute1");
// value = "Value1"
}
public static T GetValueOf<T>(MyDto dto, string description)
{
if(string.IsNullOrEmpty(description))
{
throw new InvalidOperationException();
}
var props=typeof(MyDto).GetProperties().ToArray();
foreach(var prop in props)
{
var atts=prop.GetCustomAttributes(false);
foreach(var att in atts)
{
if(att is MyAttributeAttribute)
{
string value=(att as MyAttributeAttribute).Value;
if(description.Equals(value))
{
return (T)prop.GetValue(dto, null);
}
}
}
}
return default(T);
}
}
Related
Some of my actions accept models like:
public class PaymentRequest
{
public decimal Amount { get; set; }
public bool? SaveCard { get; set; }
public int? SmsCode { get; set; }
public BankCardDetails Card { get; set; }
}
public class BankCardDetails
{
public string Number { get; set; }
public string HolderName { get; set; }
public string ExpiryDate { get; set; }
public string ValidationCode { get; set; }
}
And the action method looks like:
[HttpPost]
[Route("api/v1/payment/pay")]
public Task<BankCardActionResponse> Pay([FromBody] PaymentRequest request)
{
if (request == null)
throw new HttpResponseException(HttpStatusCode.BadRequest);
return _paymentService.PayAsync(DataUserHelper.PhoneNumber, request);
}
I use Nlog. I think it's clear this is a bad idea to log all this bank data. My log config file contained the following line:
<attribute name="user-requestBody" layout="${aspnet-request-posted-body}"/>
I logged the request. I decided to refactor that and planned the following strategy. Actions that contain sensitive data into their requests I will mark with an attribute like
[RequestMethodFormatter(typeof(PaymentRequest))]
then take a look at my custom renderer:
[LayoutRenderer("http-request")]
public class NLogHttpRequestLayoutRenderer : AspNetRequestPostedBody
{
protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent)
{
base.DoAppend(builder, logEvent);
var body = builder.ToString();
// Get attribute of the called action.
var type = ... // How can I get "PaymentRequest" from the [RequestMethodFormatter(typeof(PaymentRequest))]
var res = MaskHelper.GetMaskedJsonString(body, type);
// ... and so on
}
}
I think you understand the idea. I need the type from the method's RequestMethodFormatter attribute. Is it even possible to get it into the renderer? I need it because I'm going to deserialize request JSON into particular models (it's gonna be into the MaskHelper.GetMaskedJsonString), work with the models masking the data, serialize it back into JSON.
So, did I choose a wrong approach? Or it's possible to get the type from the attribute into the renderer?
After some research, I ended up with the following solution:
namespace ConsoleApp7
{
internal class Program
{
private static void Main()
{
var sourceJson = GetSourceJson();
var userInfo = JsonConvert.DeserializeObject(sourceJson, typeof(User));
Console.WriteLine("----- Serialize without Resolver-----");
Console.WriteLine(JsonConvert.SerializeObject(userInfo));
Console.WriteLine("----- Serialize with Resolver-----");
Console.WriteLine(JsonConvert.SerializeObject(userInfo, new JsonSerializerSettings
{
ContractResolver = new MaskPropertyResolver()
}));
}
private static string GetSourceJson()
{
var guid = Guid.Parse("3e92f0c4-55dc-474b-ae21-8b3dac1a0942");
return JsonConvert.SerializeObject(new User
{
UserId = guid,
Age = 19,
Name = "John",
BirthDate = new DateTime(1990, 5, 12),
Hobbies = new[]
{
new Hobby
{
Name = "Football",
Rating = 5,
DurationYears = 3,
},
new Hobby
{
Name = "Basketball",
Rating = 7,
DurationYears = 4,
}
}
});
}
}
public class User
{
[MaskGuidValue]
public Guid UserId { get; set; }
[MaskStringValue("***")] public string Name { get; set; }
public int Age { get; set; }
[MaskDateTimeValue]
public DateTime BirthDate { get; set; }
public Hobby[] Hobbies { get; set; }
}
public class Hobby
{
[MaskStringValue("----")]
public string Name { get; set; }
[MaskIntValue(replacement: 11111)]
public int Rating { get; set; }
public int DurationYears { get; set; }
}
public class MaskPropertyResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var props = base.CreateProperties(type, memberSerialization);
var allowedPropertyTypes = new Type[]
{
typeof(Guid),
typeof(DateTime),
typeof(string),
typeof(int),
};
foreach (var prop in props.Where(p => allowedPropertyTypes.Contains(p.PropertyType)))
{
if (prop.UnderlyingName == null)
continue;
var propertyInfo = type.GetProperty(prop.UnderlyingName);
var attribute =
propertyInfo?.GetCustomAttributes().FirstOrDefault(x => x is IMaskAttribute) as IMaskAttribute;
if (attribute == null)
{
continue;
}
if (attribute.Type != propertyInfo.PropertyType)
{
// Log this case, cause somebody used wrong attribute
continue;
}
prop.ValueProvider = new MaskValueProvider(propertyInfo, attribute.Replacement, attribute.Type);
}
return props;
}
private class MaskValueProvider : IValueProvider
{
private readonly PropertyInfo _targetProperty;
private readonly object _replacement;
private readonly Type _type;
public MaskValueProvider(PropertyInfo targetProperty, object replacement, Type type)
{
_targetProperty = targetProperty;
_replacement = replacement;
_type = type;
}
public object GetValue(object target)
{
return _replacement;
}
public void SetValue(object target, object value)
{
_targetProperty.SetValue(target, value);
}
}
}
[AttributeUsage(AttributeTargets.Property)]
public class MaskStringValueAttribute : Attribute, IMaskAttribute
{
public Type Type => typeof(string);
public object Replacement { get; }
public MaskStringValueAttribute(string replacement)
{
Replacement = replacement;
}
}
[AttributeUsage(AttributeTargets.Property)]
public class MaskIntValueAttribute : Attribute, IMaskAttribute
{
public object Replacement { get; }
public Type Type => typeof(int);
public MaskIntValueAttribute(int replacement)
{
Replacement = replacement;
}
}
[AttributeUsage(AttributeTargets.Property)]
public class MaskGuidValueAttribute : Attribute, IMaskAttribute
{
public Type Type => typeof(Guid);
public object Replacement => Guid.Empty;
}
[AttributeUsage(AttributeTargets.Property)]
public class MaskDateTimeValueAttribute : Attribute, IMaskAttribute
{
public Type Type => typeof(DateTime);
public object Replacement => new DateTime(1970, 1, 1);
}
public interface IMaskAttribute
{
Type Type { get; }
object Replacement { get; }
}
}
I hope somebody will find it helpful.
You can try nuget package https://www.nuget.org/packages/Slin.Masking and https://www.nuget.org/packages/Slin.Masking.NLog.
It can easily be integrated with DotNet projects with slight changes, and you can define your rules for it. But the document needs some improvement.
As a suggestion, you can use two files:
masking.json (can be a generic one, that shared across all projects)
masking.custom.json (can be used with particular rules for specific projects)
I have two classes OnlineBoolTag and OnlineDoubleTag. I add these objects to a list and want to get the Value of different types. How to return Value property of double or bool?
public class OnlineDoubleTag : IOnlineTag
{
public string Name { get; set; }
public double Value { get; set; }
}
public class OnlineBoolTag : IOnlineTag
{
public string Name { get; set; }
public bool Value { get; set; }
}
Add objects to a list:
var onlinetags = new List<IOnlineTag>();
onlinetags.Add(new OnlineBoolTag { Name = "Bool1", Value = true });
onlinetags.Add(new OnlineDoubleTag { Name = "Float1", Value = 777.22 });
foreach (var tag in onlinetags)
{
Console.WriteLine(tag.*****Value*****);
}
You can use dynamic instead of object
interface IOnlineTag
{
public dynamic GetValue();
}
public class OnlineDoubleTag : IOnlineTag
{
public string Name { get; set; }
public double Value { get; set; }
public dynamic GetValue()
{
return this.Value;
}
}
public class OnlineBoolTag : IOnlineTag
{
public string Name { get; set; }
public bool Value { get; set; }
public dynamic GetValue()
{
return this.Value;
}
}
public static void Main()
{
var onlinetags = new List<IOnlineTag>();
onlinetags.Add(new OnlineBoolTag { Name = "Bool1", Value = true });
onlinetags.Add(new OnlineDoubleTag { Name = "Float1", Value = 7777.22 });
foreach (var tag in onlinetags)
{
Console.WriteLine($"{tag.GetValue()} {tag.GetValue().GetType()}");
}
// Value: True Type: System.Boolean
// Value: 7777.22 Type: System.Double
}
Using dynamic will help here, as Guy's answer points out. We can simplify this even further though, by defining the property Value to be of type dynamic itself. This removes the need to implement the interface for GetValue method. Instead, we can do this:
public class OnlineTag
{
public string Name {get;set;}
public dynamic Value {get;set;}
}
public class Program
{
public static void Main(string[] args)
{
var onlinetags = new List<OnlineTag>();
onlinetags.Add(new OnlineTag { Name = "Bool1", Value = true });
onlinetags.Add(new OnlineTag { Name = "Float1", Value = 7777.22 });
foreach (var tag in onlinetags)
{
Console.WriteLine($"{tag.Value} {tag.Value.GetType()}");
//prints the below
//True System.Boolean
//7777,22 System.Double
}
}
}
The pitfall with this is that defining Value as dynamic allows you to reassign a value of totally different type later on in your code. For example, the below code will not throw any errors:
Console.WriteLine($"{onlinetags[0].Value} {onlinetags[0].Value is bool}"); //prints True True
onlinetags[0].Value = 123M;
Console.WriteLine($"{onlinetags[0].Value} {onlinetags[0].Value.GetType()}"); // prints 123 System.Decimal
I have a type which is compared to an object of the same type (or a sub class) multiple times. For this specific type, this would be a bug. The type has a Id property which should be compared instead.
Is there some way i can mark this type so a reference check creates a build warning/error?
Sample code:
static class Program
{
static void Main(string[] args)
{
ISomeData realData = new SomeData(Guid.NewGuid(), "PropValue1", "PropValue2");
ISomeData differentViewOnTheRealData = new ChangeProperty1(realData, "OtherPropValue1");
var refEquals = realData == differentViewOnTheRealData; //This will return false which would be a bug
var idEquals = realData.BaseDataId == differentViewOnTheRealData.BaseDataId; //This will be true which is correct
}
}
public interface ISomeData
{
Guid BaseDataId { get; }
string Property1 { get; }
string Property2 { get; }
}
public class SomeData : ISomeData
{
public SomeData(Guid id, string property1, string property2)
{
this.BaseDataId = id;
Property1 = property1;
Property2 = property2;
}
public Guid BaseDataId { get; }
public string Property1 { get; }
public string Property2 { get; }
}
//This class represents a modified view on ISomeData
public class ChangeProperty1 : ISomeData
{
private readonly ISomeData _someData;
private readonly string _someOtherValueForProperty1;
public ChangeProperty1(ISomeData someData, string someOtherValueForPropertyValue)
{
_someData = someData;
_someOtherValueForProperty1 = someOtherValueForPropertyValue;
}
public Guid BaseDataId => _someData.BaseDataId;
public string Property1 => _someOtherValueForProperty1;
public string Property2 => _someData.Property2;
}
Sample class:
public class ClassA
{
public int Id { get; set; }
public string SomeString { get; set; }
public int? SomeInt { get; set; }
}
Default deserializer:
var myObject = JsonConvert.DeserializeObject<ClassA>(str);
Create the same object for two different inputs
{"Id":5}
or
{"Id":5,"SomeString":null,"SomeInt":null}
How can I track properties that were missing during deserialization process and preserve the same behavior? Is there a way to override some of JSON.net serializer methods (e.g. DefaultContractResolver class methods) to achive this. For example:
List<string> missingProps;
var myObject = JsonConvert.DeserializeObject<ClassA>(str, settings, missingProps);
For the first input list should contains the names of the missing properties ("SomeString", "SomeInt") and for second input it should be empty. Deserialized object remains the same.
1. JSON has a property which is missing in your class
Using property JsonSerializerSettings.MissingMemberHandling you can say whether missing properties are handled as errors.
Than you can install the Error delegate which will register errors.
This will detect if there is some "garbage" property in JSON string.
public class ClassA
{
public int Id { get; set; }
public string SomeString { get; set; }
}
internal class Program
{
private static void Main(string[] args)
{
const string str = "{'Id':5, 'FooBar': 42 }";
var myObject = JsonConvert.DeserializeObject<ClassA>(str
, new JsonSerializerSettings
{
Error = OnError,
MissingMemberHandling = MissingMemberHandling.Error
});
Console.ReadKey();
}
private static void OnError(object sender, ErrorEventArgs args)
{
Console.WriteLine(args.ErrorContext.Error.Message);
args.ErrorContext.Handled = true;
}
}
2. Your class has a property which is missing in JSON
Option 1:
Make it a required property:
public class ClassB
{
public int Id { get; set; }
[JsonProperty(Required = Required.Always)]
public string SomeString { get; set; }
}
Option 2:
Use some "special" value as a default value and check afterwards.
public class ClassB
{
public int Id { get; set; }
[DefaultValue("NOTSET")]
public string SomeString { get; set; }
public int? SomeInt { get; set; }
}
internal class Program
{
private static void Main(string[] args)
{
const string str = "{ 'Id':5 }";
var myObject = JsonConvert.DeserializeObject<ClassB>(str
, new JsonSerializerSettings
{
DefaultValueHandling = DefaultValueHandling.Populate
});
if (myObject.SomeString == "NOTSET")
{
Console.WriteLine("no value provided for property SomeString");
}
Console.ReadKey();
}
}
Option 3:
Another good idea would be to encapsulate this check iside the class istself. Create a Verify() method as shown below and call it after deserialization.
public class ClassC
{
public int Id { get; set; }
[DefaultValue("NOTSET")]
public string SomeString { get; set; }
public int? SomeInt { get; set; }
public void Verify()
{
if (SomeInt == null ) throw new JsonSerializationException("SomeInt not set!");
if (SomeString == "NOTSET") throw new JsonSerializationException("SomeString not set!");
}
}
Another way to find null/undefined tokens during De-serialization is to write a custom JsonConverter , Here is an example of custom converter which can report both omitted tokens (e.g. "{ 'Id':5 }") and null tokens (e.g {"Id":5,"SomeString":null,"SomeInt":null})
public class NullReportConverter : JsonConverter
{
private readonly List<PropertyInfo> _nullproperties=new List<PropertyInfo>();
public bool ReportDefinedNullTokens { get; set; }
public IEnumerable<PropertyInfo> NullProperties
{
get { return _nullproperties; }
}
public void Clear()
{
_nullproperties.Clear();
}
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
existingValue = existingValue ?? Activator.CreateInstance(objectType, true);
var jObject = JObject.Load(reader);
var properties =
objectType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
foreach (var property in properties)
{
var jToken = jObject[property.Name];
if (jToken == null)
{
_nullproperties.Add(property);
continue;
}
var value = jToken.ToObject(property.PropertyType);
if(ReportDefinedNullTokens && value ==null)
_nullproperties.Add(property);
property.SetValue(existingValue, value, null);
}
return existingValue;
}
//NOTE: we can omit writer part if we only want to use the converter for deserializing
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var objectType = value.GetType();
var properties =
objectType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
writer.WriteStartObject();
foreach (var property in properties)
{
var propertyValue = property.GetValue(value, null);
writer.WritePropertyName(property.Name);
serializer.Serialize(writer, propertyValue);
}
writer.WriteEndObject();
}
}
Note: we can omit the Writer part if we don't need to use it for serializing objects.
Usage Example:
class Foo
{
public int Id { get; set; }
public string SomeString { get; set; }
public int? SomeInt { get; set; }
}
class Program
{
static void Main(string[] args)
{
var nullConverter=new NullReportConverter();
Console.WriteLine("Pass 1");
var obj0 = JsonConvert.DeserializeObject<Foo>("{\"Id\":5, \"Id\":5}", nullConverter);
foreach(var p in nullConverter.NullProperties)
Console.WriteLine(p);
nullConverter.Clear();
Console.WriteLine("Pass2");
var obj1 = JsonConvert.DeserializeObject<Foo>("{\"Id\":5,\"SomeString\":null,\"SomeInt\":null}" , nullConverter);
foreach (var p in nullConverter.NullProperties)
Console.WriteLine(p);
nullConverter.Clear();
nullConverter.ReportDefinedNullTokens = true;
Console.WriteLine("Pass3");
var obj2 = JsonConvert.DeserializeObject<Foo>("{\"Id\":5,\"SomeString\":null,\"SomeInt\":null}", nullConverter);
foreach (var p in nullConverter.NullProperties)
Console.WriteLine(p);
}
}
I got this problem, but defaultValue was not solution due to POCO object. I think this is simpler approach than NullReportConverter.
There are three unit tests. Root is class that encapsulate whole json. Key is type of the Property. Hope this helps someone.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
namespace SomeNamespace {
[TestClass]
public class NullParseJsonTest {
[TestMethod]
public void TestMethod1()
{
string slice = "{Key:{guid:\"asdf\"}}";
var result = JsonConvert.DeserializeObject<Root>(slice);
Assert.IsTrue(result.OptionalKey.IsSet);
Assert.IsNotNull(result.OptionalKey.Value);
Assert.AreEqual("asdf", result.OptionalKey.Value.Guid);
}
[TestMethod]
public void TestMethod2()
{
string slice = "{Key:null}";
var result = JsonConvert.DeserializeObject<Root>(slice);
Assert.IsTrue(result.OptionalKey.IsSet);
Assert.IsNull(result.OptionalKey.Value);
}
[TestMethod]
public void TestMethod3()
{
string slice = "{}";
var result = JsonConvert.DeserializeObject<Root>(slice);
Assert.IsFalse(result.OptionalKey.IsSet);
Assert.IsNull(result.OptionalKey.Value);
}
}
class Root {
public Key Key {
get {
return OptionalKey.Value;
}
set {
OptionalKey.Value = value;
OptionalKey.IsSet = true; // This does the trick, it is never called by JSON.NET if attribute missing
}
}
[JsonIgnore]
public Optional<Key> OptionalKey = new Optional<Key> { IsSet = false };
};
class Key {
public string Guid { get; set; }
}
class Optional<T> {
public T Value { get; set; }
public bool IsSet { get; set; }
}
}
I would like to recursively build an complex object.
public class Name
{
public string firstName {get;set;}
public string lastName {get;set;}
}
public class Address
{
public string city {get;set;}
public string state {get;set;}
public string street {get;set;}
public string zip {get;set;}
}
public class Customer
{
public Name customerName {get;set;}
public Address customerAddress {get;set;}
public Guid id {get;set;}
}
Lets say that Customer lives in an assembly that I'm loading on the fly :)
I want to instantiate a type of Customer and populate its properties.
Customer object has more custom objects and a Guid property. How can I use recursion to create the Customer object and its nested objects. I have some code below where I stumbled into the fact that I should be using recursion.
static object TraversePropertyInfo(object obj, Assembly assembly)
{
Console.WriteLine(obj.GetType().Name);
foreach(PropertyInfo pi in obj.GetType().GetProperties())
{
if(pi.PropertyType.IsClass && pi.PropertyType.Namespace != "System")
{
if(pi.PropertyType.UnderlyingSystemType.GenericTypeArguments.Count() > 0)
{
Console.WriteLine("\tIList<{0}>", pi.Name);
}
else
{
Console.WriteLine("\t{0}\t<class>", pi.Name);
object child = Activator.CreateInstance(assembly.GetType(pi.PropertyType.FullName)); // create the child instance
obj.GetType().GetProperty(pi.Name).SetValue(obj, child); // set the child on the parent
// but the child can have children...
// I should be using recurrsion here
}
}
else
{
Console.WriteLine("\t{0}\t{1}", pi.Name, pi.PropertyType);
}
}
return obj;
}
void Main()
{
Create<Customer>().Dump();
}
// Define other methods and classes here
public class Name
{
public string Firstname { get; set; }
public string Lastname { get; set; }
}
public class Address
{
public string City { get; set; }
public string State { get; set; }
public string Street { get; set; }
public string Zip { get; set; }
}
public class Customer
{
public Name CustomerName { get; set; }
public Address CustomerAddress { get; set; }
public Guid Id { get; set; }
}
public static T Create<T>()
{
var type = typeof(T);
return (T)Create(type);
}
public static object Create(Type type)
{
var obj = Activator.CreateInstance(type);
foreach(var property in type.GetProperties())
{
var propertyType = property.PropertyType;
if (propertyType.IsClass
&& string.IsNullOrEmpty(propertyType.Namespace)
|| (!propertyType.Namespace.Equals("System")
&& !propertyType.Namespace.StartsWith("System.")))
{
var child = Create(propertyType);
property.SetValue(obj, child);
}
}
return obj;
}
Maybe this will work:
static object TraversePropertyInfo(object obj, Assembly assembly)
{
Console.WriteLine(obj.GetType().Name);
// we stop the iteration when we reached the root-class "object"
// which won´t add any custom properties
if (obj.GetType() == typeof(object) return obj;
foreach(PropertyInfo pi in obj.GetType().GetProperties())
{
if(pi.PropertyType.IsClass && pi.PropertyType.Namespace != "System")
{
if(pi.PropertyType.UnderlyingSystemType.GenericTypeArguments.Count() > 0)
{
Console.WriteLine("\tIList<{0}>", pi.Name);
}
else
{
Console.WriteLine("\t{0}\t<class>", pi.Name);
object child = Activator.CreateInstance(assembly.GetType(pi.PropertyType.FullName)); // create the child instance
child = TraversePropertyInfo(child, child.GetType().Assembly);
obj.GetType().GetProperty(pi.Name).SetValue(obj, child); // set the child on the parent
// this will do the recursion
return obj;
}
}
else
{
Console.WriteLine("\t{0}\t{1}", pi.Name, pi.PropertyType);
}
}
return obj;
}