When I make updates inside a property of SortedList type of a document (haven't checked other Dictionary / List types) Those updates are ignored by the serialization process (even by the ToString method of the object).
Example:
using Microsoft.Azure.Documents;
public class BusinessStats : Document
{
public BusinessStats()
{
}
public BusinessStats(int businessId)
{
this.Id = businessId.ToString();
Counts = new SortedList<int, int>();
}
/// <summary>
/// Business id
/// </summary>
public override string Id
{
get
{
return base.Id;
}
set
{
base.Id = value;
}
}
[JsonProperty()]
public SortedList<int, int> Counts { get; set; }
}
public static class Test
{
BusinessStats bizStats = DocumentDB.Client.CreateDocumentQuery<BusinessStats>(DocumentDB.BusinessStats.SelfLink).Where(s => s.Id == p.BusinessId.ToString()).ToArray().FirstOrDefault();
if (bizStats == null)
{
bizStats = new BusinessStats(p.BusinessId);
}
}
if ( ! bizStats.Counts.ContainsKey(1))
{
bizStats.Counts.Add(1, 10);
}
else
{
bizStats.Counts[1] = bizStats.Counts[1] + 1;
}
var updateOperation = await DocumentDB.Client.UpsertDocumentAsync(DocumentDB.BusinessStats.DocumentsLink, bizStats);
First time this runs and inserts a proper Json, second time it reads the proper json, and deserialization goes well.
then, after it updates Counts[1] to be 11, it doesn't effect the ToString() and serialization of bizStats.
DocumentDB's .NET SDK makes me sad
Related
we are writing some API which required sessionId in header and some other data in body.
Is it possible to have only one class automatically parsed partially from header and from body?
Something like:
[HttpGet("messages")]
[Produces("application/json")]
[Consumes("application/json")]
[Authorize(Policy = nameof(SessionHeaderKeyHandler))]
public async Task<ActionResult<MessageData>> GetPendingClockInMessages(PendingMessagesData pendingMessagesRequest)
{
some body...
}
with request class like:
public class PendingMessagesData
{
[FromHeader]
public string SessionId { get; set; }
[FromBody]
public string OrderBy { get; set; }
}
I know, it is possible to do this, but it means, that I have to pass SessionId into the other methods as a parameter, instead of pass only one object. And we would have to do that in every API call.
public async Task<ActionResult<MessageData>> GetPendingClockInMessages(
[FromHeader] string sessionId,
[FromBody] PendingMessagesData pendingMessagesRequest)
{
some body...
}
Thank you,
Jakub
we are writing some API which required sessionId in header and some other data in body. Is it possible to have only one class automatically parsed partially from header and from body
Your GetPendingClockInMessages is annotated with a [HttpGet("messages")]. However, a HTTP GET method has no body at all. Also, it can't consume application/json. Please change it to HttpPost("messages")
Typically, SessionId is not passed in header of Session: {SessionId} like other HTTP headers. Session are encrypted via IDataProtector. In other words, you can't get it by Request.Headers["SessionId"].
Apart from the above two facts, you can create a custom model binder to do that.
Since the Session doesn't come from header directly, let's create a custom [FromSession] attribute to replace your [FromHeader]
public class FromSessionAttribute : Attribute, IBindingSourceMetadata
{
public static readonly BindingSource Instance = new BindingSource("FromSession", "FromSession Binding Source", true, true);
public BindingSource BindingSource { get { return FromSessionAttribute.Instance; } }
}
And since you're consuming application/json, let's create a binder as below:
public class MyModelBinder : IModelBinder
{
private readonly JsonOptions jsonOptions;
public MyModelBinder(IOptions<JsonOptions> jsonOptions)
{
this.jsonOptions = jsonOptions.Value;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var type = bindingContext.ModelType;
var pis = type.GetProperties();
var result= Activator.CreateInstance(type);
var body= bindingContext.ActionContext.HttpContext.Request.Body;
var stream = new System.IO.StreamReader(body);
var json = await stream.ReadToEndAsync();
try{
result = JsonSerializer.Deserialize(json, type, this.jsonOptions.JsonSerializerOptions);
} catch(Exception){
// in case we want to pass string directly. if you don't need this feature, remove this branch
if(pis.Count()==2){
var prop = pis
.Where(pi => pi.PropertyType == typeof(string) )
.Where(pi => !pi.GetCustomAttributesData().Any(ca => ca.AttributeType == typeof(FromSessionAttribute)))
.FirstOrDefault();
if(prop != null){
prop.SetValue( result ,json.Trim('"'));
}
} else{
bindingContext.ModelState.AddModelError("", $"cannot deserialize from body");
return;
}
}
var sessionId = bindingContext.HttpContext.Session.Id;
if (string.IsNullOrEmpty(sessionId)) {
bindingContext.ModelState.AddModelError("sessionId", $"cannot get SessionId From Session");
return;
} else {
var props = pis.Where(pi => {
var attributes = pi.GetCustomAttributesData();
return attributes.Any( ca => ca.AttributeType == typeof(FromSessionAttribute));
});
foreach(var prop in props) {
prop.SetValue(result, sessionId);
}
bindingContext.Result = ModelBindingResult.Success(result);
}
}
}
How to use
Decorate the property with a FromSession to indicate that we want to get the property via HttpContext.Sessino.Id:
public class PendingMessagesData
{
[FromBody]
public string OrderBy { get; set; } // or a complex model: `public MySub Sub{ get; set; }`
[FromSession]
public string SessionId { get; set; }
}
Finally, add a modelbinder on the action method parameter:
[HttpPost("messages")]
[Produces("application/json")]
[Consumes("application/json")]
public async Task<ActionResult> GetPendingClockInMessages([ModelBinder(typeof(MyModelBinder))]PendingMessagesData pendingMessagesRequest)
{
return Json(pendingMessagesRequest);
}
Personally, I would prefer another way, i.e, creating a FromSessionBinderProvider so that I can implement this without too much effort. :
public class FromSessionDataModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var sessionId = bindingContext.HttpContext.Session.Id;
if (string.IsNullOrEmpty(sessionId)) {
bindingContext.ModelState.AddModelError(sessionId, $"cannot get SessionId From Session");
} else {
bindingContext.Result = ModelBindingResult.Success(sessionId);
}
return Task.CompletedTask;
}
}
public class FromSessionBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null) { throw new ArgumentNullException(nameof(context)); }
var hasFromSessionAttribute = context.BindingInfo?.BindingSource == FromSessionAttribute.Instance;
return hasFromSessionAttribute ?
new BinderTypeModelBinder(typeof(FromSessionDataModelBinder)) :
null;
}
}
(if you're able to remove the [ApiController] attribute, this way is more easier).
My EF models are like so:
public class Base
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class Foo : Base
{
public IEnumerable<Bar> Bars { get; set; }
}
public class Bar : Base
{
...
}
My intention was to build the API in such a way that if you specify null on an update it would discard that value however it does not appear to work that way. In my repository update code I do the following:
public override IEnumerable<Base> Update(IEnumerable<Base> items)
{
foreach (var item in items.OfType<Foo>())
{
var existingItem = _context.Foos.Find(item.Id);
if (existingItem == null)
{
throw new InvalidOperationException(
$"Can't update item of type `{typeof(Foo)}` as it doesn't exist. ");
}
var entry = _context.Entry(existingItem);
entry.CurrentValues.SetValues(item);
foreach (var property in entry.Properties)
{
var original = property.OriginalValue;
var current = property.CurrentValue;
property.IsModified = original != null && !original.Equals(current);
}
var collection = entry.Collection(nameof(Foo.Bars));
if (collection.CurrentValue == null)
collection.IsModified = false;
}
var rows = _context.SaveChanges();
return Read(items.Select(e => e.Id));
}
Since the Foo.Bars property is actually a Collection/Navigation property there's no OriginalValue property to it, only a CurrentValue and this means I can't discard the value if it is null. Also it seems that setting the collection.IsModified to false has no effect and the Foo.Bars property is set to null regardless of the IsModified state.
Looking for advice on perhaps a better way to handle this or something I'm missing. Thanks.
Say you had a simple object as follows:
public class PaymentLineItem
{
public DateTime TimeStamp { get; set; }
public decimal Amount { get; set; }
public string Description { get; set; }
public PayCodeDto PayCode { get; set; } //= PayCodeDto.Invalid;
public override string ToString()
{
return StringUtils.ToString(this);
}
public override bool Equals(object obj)
{
return Equals(obj as PaymentLineItem);
}
public override int GetHashCode()
{
unchecked
{
var hashCode = TimeStamp.GetHashCode();
hashCode = (hashCode * 397) ^ Amount.GetHashCode();
hashCode = (hashCode * 397) ^ (Description != null ? Description.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (PayCode != null ? PayCode.GetHashCode() : 0);
return hashCode;
}
}
public bool Equals(PaymentLineItem other)
{
if (other == null) return false;
if (ReferenceEquals(this, other)) return true;
return TimeStamp.Equals(other.TimeStamp)
&& Amount.Equals(other.Amount)
&& string.Equals(Description, other.Description)
&& Object.Equals(PayCode, other.PayCode);
}
}
There is nothing special with this class apart from usage of the new C# auto-property initialize introduced in C# 6.0. PayCodeDto is simple with three properties and implements IEquatable.
If you create a collection of these payment items and serialize this using
var serialized = JsonConvert.SerializeObject(sut);
and you then take the correctly serialized string and deserialize using
var deserialized = JsonConvert.DeserializeObject<List<PaymentLineItemLight>>(serialized);
what you end up with is a collection all instances of the line items having the same PayCode.
This is the sample test:
[Test]
public void Should_Serialize_Collection_In_Package_Correctly()
{
var sut = new List<PaymentLineItemLight>
{
new PaymentLineItemLight
{
Amount = new decimal(153.0000),
Description = "48385 - Multiple items payment",
TimeStamp = DateTime.Parse("2018-01-30T10:40:47.477"),
PayCode = new PayCodeDto("105", "dont-care") {DictionaryId = 2}
},
new PaymentLineItemLight
{
Amount = new decimal(53.0000),
Description = "483816 - Multiple items payment",
TimeStamp = DateTime.Parse("2018-01-30T10:40:47.477"),
PayCode = new PayCodeDto("104", "dont-care") {DictionaryId = 2}
},
new PaymentLineItemLight
{
Amount = new decimal(200.0000),
Description = "483817 - Multiple items payment",
TimeStamp = DateTime.Parse("2018-01-30T10:40:47.477"),
PayCode = new PayCodeDto("102", "dont-care-102") {DictionaryId = 2}
}
};
var serialized = JsonConvert.SerializeObject(sut);
var deserialized = JsonConvert.DeserializeObject<List<PaymentLineItemLight>>(serialized);
CollectionAssert.AreEqual(sut, deserialized);
}
If I remove the auto-property initialize, the test passes. I must be missing something here. Anyone seen this?
Is there a way to ignore get-only properties using the Json.NET serializer but without using JsonIgnore attributes?
For example, I have a class with these get properties:
public Keys Hotkey { get; set; }
public Keys KeyCode
{
get
{
return Hotkey & Keys.KeyCode;
}
}
public Keys ModifiersKeys
{
get
{
return Hotkey & Keys.Modifiers;
}
}
public bool Control
{
get
{
return (Hotkey & Keys.Control) == Keys.Control;
}
}
public bool Shift
{
get
{
return (Hotkey & Keys.Shift) == Keys.Shift;
}
}
public bool Alt
{
get
{
return (Hotkey & Keys.Alt) == Keys.Alt;
}
}
public Modifiers ModifiersEnum
{
get
{
Modifiers modifiers = Modifiers.None;
if (Alt) modifiers |= Modifiers.Alt;
if (Control) modifiers |= Modifiers.Control;
if (Shift) modifiers |= Modifiers.Shift;
return modifiers;
}
}
public bool IsOnlyModifiers
{
get
{
return KeyCode == Keys.ControlKey || KeyCode == Keys.ShiftKey || KeyCode == Keys.Menu;
}
}
public bool IsValidKey
{
get
{
return KeyCode != Keys.None && !IsOnlyModifiers;
}
}
Do I need to add [JsonIgnore] to all of them (I also have many other classes), or there is better way to ignore all get-only properties?
You can do this by implementing a custom IContractResolver and using that during serialization. If you subclass the DefaultContractResolver, this becomes very easy to do:
class WritablePropertiesOnlyResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
IList<JsonProperty> props = base.CreateProperties(type, memberSerialization);
return props.Where(p => p.Writable).ToList();
}
}
Here is a test program demonstrating how to use it:
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
class Program
{
static void Main(string[] args)
{
Widget w = new Widget { Id = 2, Name = "Joe Schmoe" };
JsonSerializerSettings settings = new JsonSerializerSettings
{
ContractResolver = new WritablePropertiesOnlyResolver()
};
string json = JsonConvert.SerializeObject(w, settings);
Console.WriteLine(json);
}
}
class Widget
{
public int Id { get; set; }
public string Name { get; set; }
public string LowerCaseName
{
get { return (Name != null ? Name.ToLower() : null); }
}
}
Here is the output of the above. Notice that the read-only property LowerCaseName is not included in the output.
{"Id":2,"Name":"Joe Schmoe"}
Use the OptIn mode of JSON.net and you'll only need to decorate the properties you want to serialize. This isn't as good as automatically opting out all read only properties, but it can save you some work.
[JsonObject(MemberSerialization.OptIn)]
public class MyClass
{
[JsonProperty]
public string serializedProp { get; set; }
public string nonSerializedProp { get; set; }
}
Udate: Added another possibility using reflection
If the above solution still isn't quite what you're looking for, you could use reflection to make dictionary objects which would then be serialized. Of course the example below will only work for simple classes, so you would need to add recursion if your classes contain other classes. This should at least point you in the right direction.
The subroutine to put the filtered result into a dictionary:
private Dictionary<String, object> ConvertToDictionary(object classToSerialize)
{
Dictionary<String, object> resultDictionary = new Dictionary<string, object>();
foreach (var propertyInfo in classToSerialize.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (propertyInfo.CanWrite) resultDictionary.Add(propertyInfo.Name, propertyInfo.GetValue(classToSerialize, null));
}
return resultDictionary;
}
A snippet showing its use:
SampleClass sampleClass = new SampleClass();
sampleClass.Hotkey = Keys.A;
var toSerialize = ConvertToDictionary(sampleClass);
String resultText = JsonConvert.SerializeObject(toSerialize);
You can use a contract resolver like this:
public class ExcludeCalculatedResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
property.ShouldSerialize = _ => ShouldSerialize(member);
return property;
}
internal static bool ShouldSerialize(MemberInfo memberInfo)
{
var propertyInfo = memberInfo as PropertyInfo;
if (propertyInfo == null)
{
return false;
}
if (propertyInfo.SetMethod != null)
{
return true;
}
var getMethod = propertyInfo.GetMethod;
return Attribute.GetCustomAttribute(getMethod, typeof(CompilerGeneratedAttribute)) != null;
}
}
It will exclude calculated properties but include C#6 get only properties and all properties with a set method.
Json.net does have the ability to conditionally serialize properties without an attribute or contract resolver. This is especially useful if you don't want your project to have a dependency on Json.net.
As per the Json.net documentation
To conditionally serialize a property, add a method that returns boolean with
the same name as the property and then prefix the method name with
ShouldSerialize. The result of the method determines whether the
property is serialized. If the method returns true then the property
will be serialized, if it returns false then the property will be
skipped.
I am currently working with MVC4 data annotations to handle validation. I am working on a site that will be very much international and as such I keep all of my text in resource files.
I also want to keep regular expressions for validation in resource files so I can use the same code to check, for example, Post Codes (UK) and Zip Codes (US) just by using a different RegEx (and resources for the different names etc).
I have the below attribute which is already pulling the error message from a resource file. How can I have it get the regex from a resource file too?
[RegularExpression(#"^[\w]{1,2}[0-9]{1,2}[\w]?\s?[0-9]{1,2}[\w]{1,2}$", ErrorMessageResourceType = typeof(Resources.ValidationMessages), ErrorMessageResourceName = "validPostcode")]
EDIT (AGAIN)
Where I am now
Following the answer below and some additional searching around, I have the following:
In Global.asax.cs I have added the below line to ensure client side validation is invoked
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(LocalisedAttribute), typeof(RegularExpressionAttributeAdapter));
In my model, I have this call to the attribute extension
[Localised(typeof(Resources.FormValidation), "postcodeRegEx", "postcodeMsg")]
And finally, the attribute extension for localised regex validation
public class LocalisedAttribute : RegularExpressionAttribute
{
public LocalisedAttribute(Type resource, string regularExpression, string errorMessage)
: base(GetRegex(regularExpression))
{
ErrorMessageResourceType = resource;
ErrorMessageResourceName = errorMessage;
}
private static string GetRegex(string value)
{
return Resources.FormValidation.ResourceManager.GetString(value);
}
}
This works, but ONLY the first time I use it when starting the application.
I am going to open another question to get around that problem - it's not directly related to the original request, doesn't seem to be relevant to most peoples implementation and doesn't seem to be specific to data annotations.
I already have some extended kind of RegularExpressionAttribute implementation, that allows to use resources for regex pattern. It looks like:
public class RegularExpressionExAttribute : RegularExpressionAttribute, IClientValidatable
{
private Regex regex { get; set; }
private string pattern;
private string resourceName;
private Type resourceType;
/// <summary>
/// constructor, calls base with ".*" basic regex
/// </summary>
/// <param name="resName">resource key</param>
/// <param name="resType">resource type</param>
public RegularExpressionExAttribute(string resName, Type resType)
: base(".*")
{
resourceName = resName;
resourceType = resType;
}
/// <summary>
/// override RegularExpressionAttribute property
/// </summary>
public new string Pattern
{
get
{
SetupRegex();
return pattern;
}
}
/// <summary>
/// loads regex from resources
/// </summary>
private void SetupRegex()
{
ResourceAccessor ra = new ResourceAccessor(resourceName, resourceType);
pattern = ra.resourceValue;
regex = new Regex(pattern);
}
/// <summary>
/// override validation with our regex
/// </summary>
/// <param name="value">string for validation</param>
/// <returns></returns>
public override bool IsValid(object value)
{
SetupRegex();
string val = Convert.ToString(value);
if (string.IsNullOrEmpty(val))
return true;
var m = regex.Match(val);
return (m.Success && (m.Index == 0));
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metaData, ControllerContext controllerContext)
{
yield return new ModelClientValidationRegexRule(base.ErrorMessageString, this.Pattern);
}
}
Also it's using ResourceAccessor class to get regex out of resources
public class ResourceAccessor
{
private string resourceName;
private Type resourceType;
private Func<string> accessor;
private string _resourceValue;
public ResourceAccessor(string resourceName, Type resourceType)
{
this.resourceName = resourceName;
this.resourceType = resourceType;
}
public string resourceValue
{
get
{
SetupAccessor();
return accessor();
}
}
private void SetupAccessor()
{
if (accessor != null) //already set
return;
string localValue = _resourceValue;
bool flag1 = !string.IsNullOrEmpty(resourceName);
bool flag2 = !string.IsNullOrEmpty(localValue);
bool flag3 = resourceType != (Type)null;
if (flag1 == flag2)
{
throw new InvalidOperationException("Can't set resource value");
}
if (flag3 != flag1)
{
throw new InvalidOperationException("Resource name and type required");
}
if (flag1)
PropertyLookup();
else
{
accessor = (Func<string>)(() => localValue);
}
}
private void PropertyLookup()
{
if (resourceType == (Type)null || string.IsNullOrEmpty(resourceName))
{
throw new InvalidOperationException("Resource name and type required");
}
PropertyInfo property = resourceType.GetProperty(resourceName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
if (property != (PropertyInfo)null)
{
MethodInfo getMethod = property.GetGetMethod(true);
if (getMethod == (MethodInfo)null || !getMethod.IsAssembly && !getMethod.IsPublic)
property = (PropertyInfo)null;
}
if (property == (PropertyInfo)null)
{
throw new InvalidOperationException("Resource type doesn't have property");
}
else if (property.PropertyType != typeof(string))
{
throw new InvalidOperationException("Resource type must be string");
}
else
{
accessor = (Func<string>)(() => (string)property.GetValue((object)null, (object[])null));
}
}
}
And here is usage samples:
public class SignUpInput
{
[RegularExpressionEx("EmailValidationRegex", typeof(LocalizedResources), ErrorMessageResourceType = typeof(Messages), ErrorMessageResourceName = "invalidEmail")]
public string Email { get; set; }
}
I think yuo can extend RegularExpressionAttribute
public class PostCodeValidationAttribute : RegularExpressionAttribute
{
public PostCodeValidationAttribute()
: base(Resources.PostCodeValidationExpression)
{
}
}
UPDATE
Put culture info name in session for example accordingly with user choice. And use it in
ResourceManager.GetString(value, CultureInfo.CreateSpecificCulture(userCulture));
At first you can test it with hardcode value. Something like this
ResourceManager.GetString(value, CultureInfo.CreateSpecificCulture("en-GB"));
instead
ResourceManager.GetString(value, CultureInfo.CreateSpecificCulture(currentCulture));
or in base constructor
base(GetRegex(regularExpression, ""en-GB""))