I have never used Web API before, but I need a web service that will accept/return JSON objects and using this seemed like a reasonable thing. It looked pretty simple (if not a bit of overkill for my purposes), but a data structure I need to deal with looks something like:
{
"values":["foo", "bar"],
"default":"bar"
}
And so I went to make a Model object:
class DropDownValues {
public string[] values { get; set; }
public string default { get; set; }
}
Problem is that default seems to be a protected keyword. There must be some way to get around that, right?
You can use keywords in C# as identifiers by prepending # in front of them.
I would suggest to go different way. Keep your C# object model as much standard as possible (I wouldn't use # sign and C# keywords as property name).
We can separate the serialized (JSON) world and C# objects - just by using the Json.NET features.
One of the simpliest to use is decoration with Attribute:
[JsonProperty(PropertyName = "default")]
public string DefaultValue { get; set; }
In this case we have to reference Newtonsoft.Json in the project. If it must be POCO, we can introduce CustomResolver derrived from DefaultContractResolver and define these conversions there...
But separation of concern in this case is a bit more pure solution, I would say
EDIT: JSON Contract Resolver draft (see comments)
Important NOTE: Newtonsoft.Json is part of the Web API. Not only it is an open source, but even MS team bet on that as a core JSON serializer.
1) Newtonsoft.Json (as a part of the Web.API) is already installed in your solution. So you do not have to downloaded (nuget) separately. It would always be in your packages folder. So, to use the attribute is just adding the reference. It is there...
2) There is a small draft how to do the attribute stuff, while keeping the POCO. As I've tried explain here: POCO's, behavior and Peristance Igorance, to keep POCO (e.g. we do profit from layered Architecture with NHibernate on a data layer), we can replace attributes with a Contract Resolver. Our POCO library does not have to reference anything
We just have to do extend the service layer:
public class MyResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(
MemberInfo member,
MemberSerialization memberSerialization)
{
var jProperty = base.CreateProperty(member, memberSerialization);
var propertyInfo = member as PropertyInfo;
if (propertyInfo == null)
{
return jProperty;
}
// just adjust in case if Property name is DefaultValue
var isDefaultValueProeprty =
propertyInfo.Name.Equals("DefaultValue");
if(isDefaultValueProeprty)
{
jProperty.PropertyName = "default";
}
return jProperty;
}
...
This way we've provided the same information to serailizer as with the [JsonPropertyAttribute].
Now, we just have to use it. There are many ways (e.g. global) but we can do it for a controller only:
protected override void Initialize(HttpControllerContext context)
{
base.Initialize(context);
var jSettings = context.Configuration.Formatters.JsonFormatter.SerializerSettings;
jSettings.ContractResolver = MyResolver;
}
The class DropDownValues using camel convention:
class DropDownValues {
public string[] values { get; set; }
public string default { get; set; }
}
You can use prefix # to passby but it is still not following C# coding convention.
The better solution which you can both avoid reserved keyword and still use C# coding convention is using CamelCasePropertyNamesContractResolver:
class DropDownValues {
public string[] Values { get; set; }
public string Default { get; set; }
}
And customize JsonFormatter to avoid convention mismatch between C# and json object as below:
var jsonFormatter = configuration.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings = new JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
Related
My solution has a WebAPI project (.net core 3.1, Microsoft.AspNetCore.Mvc) and a (.Net Standard 2.1) class library that defines the data structures.
My Controller takes a post with a single parameter that deserializes mostly correctly
public class apiRequest
{
public RequestData TheData { get; set; }
public Options Options { get; set; }
public apiRequest() { }
}
The RequestData and child objects are defined i a .Net Standard 2.1 class library and added via a nuget package
public class RequestData : IRequestData
{
public int Datum{ get; set; }
...
public List<ComplexItem> ComplexItems { get; set; }
...
}
public class ComplexItem: ItemBase, IComplexItem
{
public ComplexItem() : base() { }
public ComplexItem(Pricing defaultPricing) : base(defaultPricing) { }
[JsonConstructor]
public ComplexItem(Pricing defaultPricing, Pricing selectedPricing) : base(defaultPricing, selectedPricing) { }
}
The problem I am running into is with the defaultPricing is always null when it gets to the controller
public class ItemBase : IItemBase
{
public ItemBase () { }
public ItemBase (Pricing defaultPricing)
{
DefaultPricing = defaultPricing;
}
[JsonConstructor]
public ItemBase (Pricing defaultPricing, Pricing selectedPricing)
{
DefaultPricing = defaultPricing;
SelectedPricing = selectedPricing;
}
#region Pricing
[JsonProperty]
protected Pricing DefaultPricing { get; set; }
public Pricing SelectedPricing { get; set; }
[JsonIgnore]
protected Pricing CurrentPricing
{
get { return SelectedPricing ?? DefaultPricing; }
set { SelectedPricing = value; }
}
[JsonIgnore]
public decimal Cost { get => CurrentPricing?.Cost ?? 0; }
[JsonIgnore]
public decimal Price { get => CurrentPricing?.Price ?? 0; }
#endregion
}
I've tried using [DataContract] and [DataMember] attributes, JsonObject, JsonConstructor, JsonProperty attributes and [Serializable] attribute. (Is there a current best practice on what to use?)
If I read the Json from a file and use Newtonsoft.Json.JsonConvert.DeserializeObject it deserializes correctly with the Json attributes added, but still null in the controller.
It also deserializes in the API properly if I make it public, so it doesn't seem like a problem in the Pricing class itself
After posting I found this Question about making Newtonsoft the default and using MikeBeaton's accepted solution there with Microsoft.AspNetCore.Mvc.NewtonsoftJson package worked so I'll put this as one potential answer for anyone else with this issue. Would still like to know if there is a more correct solution available.
System.Text.Json Serializes Public Properties
As the documentation implies (emphasis mine):
By default, all (read: only) public properties are serialized. You can specify properties to exclude.
I would guess that this was the design chosen because serializing an object is allowing that object to cross barriers of scope and the public scope is the only one that can reliably be assumed.
If you think about it, it makes sense. Lets say, you define a protected property and serialize the object. Then a client picks it up and deserializates that text representation into a public property. What you have designed to be an implementation detail of/to derived types is now accessible outside the scope defined by the modifier.
Apart from simply pointing you to your own answer where Newtonsoft allows this protected property to be serialized, I would suggest you look more intently at your design and why those properties are protected in the first place. It makes sense within the context of your API implementation, but the client can't (shouldn't) be assumed to follow your same inheritance structure (or support inheritance at all). It seems like you might want to define a true DTO to act as the "shape" of your API response and find the right place to transition from your internal types using protected scope to control access and the DTO that can cross the border of the API.
I have an MVC Web API Controller that exposes a method:
[HttpPost]
public JsonResult<CustomClass> Details(RequestClass request)
{
var encoding = Encoding.GetEncoding("iso-8859-1");
var settings = new Newtonsoft.Json.JsonSerializerSettings();
return Json(_InputService.CustomClassGet(request.Id), settings, encoding);
}
CustomClass is a really complex class wich contains several levels of objects of different classes. Objects from these classes are somehow built in another part of the code using a mapper that relies on Newtonsoft Json.
I just got a request to modify my code to change some of CustomClass property names (along the whole tree). My first approach was to create another set of classes, so I could have one for receiving data and other for exposing data with a converter in the middle but there are so many classes in the structure and they are so complex that it would consume a lot of effort. Also, during the process of converting from input to output classes, it would require twice the memory to hold 2 copies of the same exact data.
My second approach was using JsonProperty(PropertyName ="X") to change the resulting json BUT, as the input also relies in Newtonsoft Json, I completely broke the input process.
My next approach was to create a custom serializer and user [JsonConverter(typeof(CustomCoverter))] attribute BUT it changes the way CustomClass is serialized everywhere and I can't change the way the rest of the API responds, just some specific methods.
So, the question is... does anyone imagine a way to change the way my CustomClass is serialized just in certain methods?
This is the final solution:
1) Created a custom Attribute and decorated the properties that I needed its name changed in the serialized Json:
[AttributeUsage(AttributeTargets.Property)]
class MyCustomJsonPropertyAttribute : Attribute
{
public string PropertyName { get; protected set; }
public MyCustomJsonPropertyAttribute(string propertyName)
{
PropertyName = propertyName;
}
}
2) Decordated the properties accordingly
class MyCustomClass
{
[MyCustomJsonProperty("RenamedProperty")]
public string OriginalNameProperty { get; set; }
}
3) Implemented a Custom Resolver using reflection to remap the property name for those properties decorated with MyCustomJsonPropertyAttribute
class MyCustomResolver : DefaultContractResolver
{
public MyCustomResolver()
{
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty prop = base.CreateProperty(member, memberSerialization);
var attr = member.GetCustomAttribute(typeof(MyCustomJsonPropertyAttribute));
if (attr!=null)
{
string jsonName = ((MyCustomJsonPropertyAttribute)attr).PropertyName;
prop.PropertyName = jsonName;
}
return prop;
}
}
4) Implemented the method in the controller like this:
[HttpPost]
public JsonResult<MyCustomClass> Details(RequestClass request)
{
var encoding = Encoding.GetEncoding("iso-8859-1");
var settings = new Newtonsoft.Json.JsonSerializerSettings()
{
ContractResolver = new MyCustomResolver()
};
return Json(_InputService.CustomClassGet(request.Id), settings, encoding);
}
Thanks a lot to dbc for pointing me the way.
I am using Json.Net for serialization and unserialization on a class. When I try to obsfucate the class in an assembly, I will not be able to unserialize the JSON string correctly. I have try using JsonProperty Attribute but it still not able to unserialize correctly.
Does anyone know how to get around this other than setting the class not to obsfucate?
Thanks in advance.
Updates:
I have created a simple class for JSON object as below:
public class JsonTestClass
{
[JsonProperty("JsonID")]
public int Id { get; set; }
[JsonProperty("JsonName")]
public string Name { get; set; }
[JsonProperty("JsonYesNo")]
public bool YesNo { get; set; }
}
And the code to serialize JsonTestClass objects into Json string is:
//Serialize
var jsonObj = new JsonTestClass()
{
Id = 1,
Name = "John",
YesNo = true,
};
var JsonStr = JsonConvert.SerializeObject(jsonObj);
Trace.WriteLine(">>" + JsonStr);
And unserialize Json string to JsonTestClass is as follow:
//Unserialize
var JsonStr = "{\"JsonID\":1,\"JsonName\":\"John Abc\",\"JsonYesNo\":true}";
Trace.WriteLine("<<" + JsonStr);
var jsonObj = JsonConvert.DeserializeObject<JsonTestClass>(JsonStr);
if (jsonObj == null)
{
Trace.WriteLine("-- JsonObj is null");
}
else
{
Trace.WriteLine(string.Format("-- Id={0} Name={1} YesNo={2}", jsonObj.Id, jsonObj.Name, jsonObj.YesNo));
}
Everything works fine unobsfucated and the debug output for serialization is:
[64200] >>{"JsonID":1,"JsonName":"John Abc","JsonYesNo":true}
And debug output for deserialization is:
[64200] <<{"JsonID":1,"JsonName":"John Abc","JsonYesNo":true}
[64200] -- Id=1 Name=John Abc YesNo=True
After the assembly has been obsfucated (using SmartAssembly to obsfucate this) and using dotPeek to see the obsfucated assembly, the only one which somewhat resemble the JsonTestClass are:
namespace
{
internal class
{
}
}
The debug output for serialization is:
[65956] >>{}
The debug output for deserialization is:
[65956] <<{"JsonID":1,"JsonName":"John Abc","JsonYesNo":true}
[65956] -- Id=0 Name= YesNo=False
Hope this helps to clear things up.
If you are using Dotfuscator which is shipped as Community Edition with Visual Studio 15 and Visual Studio 17, you have to exclude the property names of the object you serialize from renaming. You do this in the Dotfuscator renaming tab.
After that you can serialize and deserialize like normal.
You find a detailed description here in the support corner of the Preemtive Solutions web site.
There are a couple of options, depending on your preference as to maintainability, security, etc.
Excluding properties from obfuscation
If it's just the type names that you're concerned about being obfuscated and not the property names, then you can decorate each property that is to be serialized with the Obfuscation attribute. (Here is the link for Dotfuscator's support for this attribute.) Basically, by excluding the property from renaming, this will allow Json.NET to easily identify the properties to set.
[Obfuscation(Exclude=true, Feature="renaming")]
public int MyProperty { get; set; }
Pros: Easy and straightforward
Cons: You lose some security by not
obfuscating your property names.
Expose the private backing field for deserialization
Another way to get Json.NET to deserialize an obfuscated class is to change the target of the deserialization from the public property to the private field using the JsonProperty attribute, e.g.
[JsonProperty("a")]
private int _myField;
[JsonIgnore]
public int MyProperty
{
get => _myField;
set => _myField = value;
}
NOTE: If you use "MyProperty" or nameof(MyProperty) in the JsonProperty attribute, then depending on your obfuscation settings it's very possible that it will be compiled into the .dll as "MyProperty", thus reducing the overall security that you were trying to achieve with obfuscation in the first place.
NOTE 2: This solution will most likely not work when working with the MVVM design pattern. When using this pattern, a common coding practice is to utilize the property setter to raise the INotifyPropertyChanged event. However, using the JsonProperty and JsonIgnore attributes will cause Json.NET to set the value directly on the private field using reflection instead of the via the property setter. Therefore the change event will not be raised, making this a poor solution in this particular scenario.
Pros: You can maintain full obfuscation of your property names.
Cons: You need to create backing fields for every obfuscated property that you want to participate in serialization/deserialization. Also, may break the INotifyPropertyChanged functionality when using with the MVVM pattern, depending on your implementation details.
I have never used Web API before, but I need a web service that will accept/return JSON objects and using this seemed like a reasonable thing. It looked pretty simple (if not a bit of overkill for my purposes), but a data structure I need to deal with looks something like:
{
"values":["foo", "bar"],
"default":"bar"
}
And so I went to make a Model object:
class DropDownValues {
public string[] values { get; set; }
public string default { get; set; }
}
Problem is that default seems to be a protected keyword. There must be some way to get around that, right?
You can use keywords in C# as identifiers by prepending # in front of them.
I would suggest to go different way. Keep your C# object model as much standard as possible (I wouldn't use # sign and C# keywords as property name).
We can separate the serialized (JSON) world and C# objects - just by using the Json.NET features.
One of the simpliest to use is decoration with Attribute:
[JsonProperty(PropertyName = "default")]
public string DefaultValue { get; set; }
In this case we have to reference Newtonsoft.Json in the project. If it must be POCO, we can introduce CustomResolver derrived from DefaultContractResolver and define these conversions there...
But separation of concern in this case is a bit more pure solution, I would say
EDIT: JSON Contract Resolver draft (see comments)
Important NOTE: Newtonsoft.Json is part of the Web API. Not only it is an open source, but even MS team bet on that as a core JSON serializer.
1) Newtonsoft.Json (as a part of the Web.API) is already installed in your solution. So you do not have to downloaded (nuget) separately. It would always be in your packages folder. So, to use the attribute is just adding the reference. It is there...
2) There is a small draft how to do the attribute stuff, while keeping the POCO. As I've tried explain here: POCO's, behavior and Peristance Igorance, to keep POCO (e.g. we do profit from layered Architecture with NHibernate on a data layer), we can replace attributes with a Contract Resolver. Our POCO library does not have to reference anything
We just have to do extend the service layer:
public class MyResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(
MemberInfo member,
MemberSerialization memberSerialization)
{
var jProperty = base.CreateProperty(member, memberSerialization);
var propertyInfo = member as PropertyInfo;
if (propertyInfo == null)
{
return jProperty;
}
// just adjust in case if Property name is DefaultValue
var isDefaultValueProeprty =
propertyInfo.Name.Equals("DefaultValue");
if(isDefaultValueProeprty)
{
jProperty.PropertyName = "default";
}
return jProperty;
}
...
This way we've provided the same information to serailizer as with the [JsonPropertyAttribute].
Now, we just have to use it. There are many ways (e.g. global) but we can do it for a controller only:
protected override void Initialize(HttpControllerContext context)
{
base.Initialize(context);
var jSettings = context.Configuration.Formatters.JsonFormatter.SerializerSettings;
jSettings.ContractResolver = MyResolver;
}
The class DropDownValues using camel convention:
class DropDownValues {
public string[] values { get; set; }
public string default { get; set; }
}
You can use prefix # to passby but it is still not following C# coding convention.
The better solution which you can both avoid reserved keyword and still use C# coding convention is using CamelCasePropertyNamesContractResolver:
class DropDownValues {
public string[] Values { get; set; }
public string Default { get; set; }
}
And customize JsonFormatter to avoid convention mismatch between C# and json object as below:
var jsonFormatter = configuration.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings = new JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
I'm writing a PropertiesMustMatch validation attribute that can take a string property name as a parameter. I'd like it to find the corresponding property by name on that object and do a basic equality comparison. What's the best way to access this through reflection?
Also, I checked out the Validation application block in the Enterprise Library and decided its PropertyComparisonValidator was way too intense for what we need.
UPDATE: For further clarification (to provide some context), the goal is simply validation that enforces field matching (e.g., password verification). We'd like it to work with property-level attribute data annotations that inherit from the ValidationAttribute class, if possible.
UPDATE: In case anyone is curious, I ended up solving the actual business problem through tweaking code provided as an answer to this question
You can't, basically. The code that checks the object for the presence of the attribute must also assume responsibility for telling any code which type/object it was looking at. You can't obtain any additional metadata from within an attribute.
You cannot do that. See also this question. Try to change the logic to work with the object, checking its attributes, not vice versa. You can also provide more information about your task, not just this narrow question.
You can something like this.
//target class
public class SomeClass{
[CustomRequired(ErrorMessage = "{0} is required", ProperytName = "DisplayName")]
public string Link { get; set; }
public string DisplayName { get; set; }
}
//custom attribute
public class CustomRequiredAttribute : RequiredAttribute, IClientValidatable
{
public string ProperytName { get; set; }
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var propertyValue = "Value";
var parentMetaData = ModelMetadataProviders.Current
.GetMetadataForProperties(context.Controller.ViewData.Model, context.Controller.ViewData.Model.GetType());
var property = parentMetaData.FirstOrDefault(p => p.PropertyName == ProperytName);
if (property != null)
propertyValue = property.Model.ToString();
yield return new ModelClientValidationRule
{
ErrorMessage = string.Format(ErrorMessage, propertyValue),
ValidationType = "required"
};
}
}