Required Attribute in DataAnnotations Does Not seem to work - c#

I have a DataAnnotationValidator that I created. I am currently trying to test it with a Required Field attribute and I can't get the IsValid property to fail when my property is null. It does work correctly when I create a number with a Range attribute that is outside of the specified Range.
public class TestEntityWithDataAnnotations
{
public Guid Id { get; set; }
[Required(ErrorMessage = "Required")]
public string Name { get; set; }
}
[TestFixture]
public class DataAnnotationValidatorTest
{
[Test]
public void Validate_ReturnsFailure_WhenPropertyValidationIsNotValid()
{
var validator = new DataAnnotationValidator();
var invalidEntity = new TestEntityWithDataAnnotations
{
Id = Guid.NewGuid()
};
var validationResult = validator.Validate(invalidEntity);
Assert.IsFalse(validationResult.IsValid);
}
}
public class DataAnnotationValidator
{
public ValidationResult Validate(object obj)
{
Type objType = obj.GetType();
var typeDescriptor = GetTypeDescriptor(obj, objType);
var validationResult = new ValidationResult();
var classValidationResult = CheckClassIsValid(obj, typeDescriptor);
if (!classValidationResult.IsValid)
{
validationResult.AddErrors(classValidationResult.Errors);
}
foreach (PropertyDescriptor propertyDescriptor in typeDescriptor.GetProperties())
{
// Loop over all of the properties on our object that have Validation Attributes
var propValidationResult = CheckPropertyIsValid(obj, propertyDescriptor);
if(!propValidationResult.IsValid)
{
validationResult.AddErrors(propValidationResult.Errors);
}
}
return validationResult;
}
/// <summary>
/// Checks to see if there are any class level validation attributes and runs them
/// </summary>
/// <returns></returns>
private static ValidationResult CheckClassIsValid(object obj, ICustomTypeDescriptor typeDescriptor)
{
var errors = typeDescriptor.GetAttributes().OfType<ValidationAttribute>()
.Where(x => !x.IsValid(obj))
.Select(x => new ValidationError(typeDescriptor.GetClassName(), x.ErrorMessage));
return new ValidationResult(errors.ToList());
}
/// <summary>
/// Checks to see if a property has any DataAnnotations that it has violated
/// </summary>
private static ValidationResult CheckPropertyIsValid(object obj, PropertyDescriptor propertyDescriptor)
{
var errors = propertyDescriptor.Attributes.OfType<ValidationAttribute>()
.Where(x => !x.IsValid(obj))
.Select(x => new ValidationError(propertyDescriptor.Name, x.ErrorMessage));
return new ValidationResult(errors.ToList());
}
/// <summary>
/// Gets the model's type descriptor. In order to support the buddy class metadata model
/// for LINQ to SQL and Entity Framework, it uses
/// <see cref="AssociatedMetadataTypeTypeDescriptionProvider"/>.
/// </summary>
/// <param name="obj">The model object</param>
/// <param name="objType">The type of the model object</param>
/// <returns>The model's type descriptor</returns>
private static ICustomTypeDescriptor GetTypeDescriptor(object obj, Type objType)
{
var provider = new AssociatedMetadataTypeTypeDescriptionProvider(objType);
return provider.GetTypeDescriptor(objType, obj);
}
}

A bit of stupidity on my part. I needed to pass the value of the property into IsValid inside of CheckPropertyIsValid instead of the whole object.
private static ValidationResult CheckPropertyIsValid(object obj, PropertyDescriptor propertyDescriptor)
{
var errors = propertyDescriptor.Attributes.OfType<ValidationAttribute>()
.Where(x => !x.IsValid(propertyDescriptor.GetValue(obj)))
.Select(x => new ValidationError(propertyDescriptor.Name, x.ErrorMessage));
return new ValidationResult(errors.ToList());
}

Related

JsonConverter applied to class causes JsonConverter applied to property to be ignored

I defined two JsonConverter classes. One I attach to the class, the other I attach to a property of that class. If I only attach the converter to the property, it works fine. As soon as I attach a separate converter to the class, it ignores the one attached to the property. How can I make it not skip such JsonConverterAttributes?
Here is the class-level converter (which I adapted from this: Alternate property name while deserializing). I attach it to the test class like so:
[JsonConverter(typeof(FuzzyMatchingJsonConverter<JsonTestData>))]
Then here is the FuzzyMatchingJsonConverter itself:
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Optimizer.models;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Optimizer.Serialization
{
/// <summary>
/// Permit the property names in the Json to be deserialized to have spelling variations and not exactly match the
/// property name in the object. Thus puntuation, capitalization and whitespace differences can be ignored.
///
/// NOTE: As implemented, this can only deserialize objects from a string, not serialize from objects to a string.
/// </summary>
/// <seealso cref="https://stackoverflow.com/questions/19792274/alternate-property-name-while-deserializing"/>
public class FuzzyMatchingJsonConverter<T> : JsonConverter
{
/// <summary>
/// Map the json property names to the object properties.
/// </summary>
private static DictionaryToObjectMapper<T> Mapper { get; set; } = null;
private static object SyncToken { get; set; } = new object();
static void InitMapper(IEnumerable<string> jsonPropertyNames)
{
if (Mapper == null)
lock(SyncToken)
{
if (Mapper == null)
{
Mapper = new DictionaryToObjectMapper<T>(
jsonPropertyNames,
EnumHelper.StandardAbbreviations,
ModelBase.ACCEPTABLE_RELATIVE_EDIT_DISTANCE,
ModelBase.ABBREVIATION_SCORE
);
}
}
else
{
lock(SyncToken)
{
// Incremental mapping of additional attributes not seen the first time for the second and subsequent objects.
// (Some records may have more attributes than others.)
foreach (var jsonPropertyName in jsonPropertyNames)
{
if (!Mapper.CanMatchKeyToProperty(jsonPropertyName))
throw new MatchingAttributeNotFoundException(jsonPropertyName, typeof(T).Name);
}
}
}
}
public override bool CanConvert(Type objectType) => objectType.IsClass;
/// <summary>
/// If false, this class cannot serialize (write) objects.
/// </summary>
public override bool CanWrite { get => false; }
/// <summary>
/// Call the default constructor for the object and then set all its properties,
/// matching the json property names to the object attribute names.
/// </summary>
/// <param name="reader"></param>
/// <param name="objectType">This should match the type parameter T.</param>
/// <param name="existingValue"></param>
/// <param name="serializer"></param>
/// <returns>The deserialized object of type T.</returns>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Note: This assumes that there is a default (parameter-less) constructor and not a constructor tagged with the JsonCOnstructorAttribute.
// It would be better if it supported those cases.
object instance = objectType.GetConstructor(Type.EmptyTypes).Invoke(null);
JObject jo = JObject.Load(reader);
InitMapper(jo.Properties().Select(jp => jp.Name));
foreach (JProperty jp in jo.Properties())
{
var prop = Mapper.KeyToProperty[jp.Name];
prop?.SetValue(instance, jp.Value.ToObject(prop.PropertyType, serializer));
}
return instance;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}
Do not get bogged down with DictionaryToObjectMapper (it is proprietary, but uses fuzzy matching logic to deal with spelling variations). Here is the next JsonConverter, that will change "Y", "Yes", "T", "True", etc into Boolean true values. I adapted it from this source: https://gist.github.com/randyburden/5924981
using System;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Optimizer.Serialization
{
/// <summary>
/// Handles converting JSON string values into a C# boolean data type.
/// </summary>
/// <see cref="https://gist.github.com/randyburden/5924981"/>
public class BooleanJsonConverter : JsonConverter
{
private static readonly string[] Truthy = new[] { "t", "true", "y", "yes", "1" };
private static readonly string[] Falsey = new[] { "f", "false", "n", "no", "0" };
/// <summary>
/// Parse a Boolean from a string where alternative spellings are permitted, such as 1, t, T, true or True for true.
///
/// All values that are not true are considered false, so no parse error will occur.
/// </summary>
public static Func<object, bool> ParseBoolean
= (obj) => { var b = (obj ?? "").ToString().ToLower().Trim(); return Truthy.Any(t => t.Equals(b)); };
public static bool ParseBooleanWithValidation(object obj)
{
var b = (obj ?? "").ToString().ToLower().Trim();
if (Truthy.Any(t => t.Equals(b)))
return true;
if (Falsey.Any(t => t.Equals(b)))
return false;
throw new ArgumentException($"Unable to convert ${obj}into a Boolean attribute.");
}
#region Overrides of JsonConverter
/// <summary>
/// Determines whether this instance can convert the specified object type.
/// </summary>
/// <param name="objectType">Type of the object.</param>
/// <returns>
/// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
/// </returns>
public override bool CanConvert(Type objectType)
{
// Handle only boolean types.
return objectType == typeof(bool);
}
/// <summary>
/// Reads the JSON representation of the object.
/// </summary>
/// <param name="reader">The <see cref="T:Newtonsoft.Json.JsonReader"/> to read from.</param>
/// <param name="objectType">Type of the object.</param>
/// <param name="existingValue">The existing value of object being read.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>
/// The object value.
/// </returns>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
=> ParseBooleanWithValidation(reader.Value);
/// <summary>
/// Specifies that this converter will not participate in writing results.
/// </summary>
public override bool CanWrite { get { return false; } }
/// <summary>
/// Writes the JSON representation of the object.
/// </summary>
/// <param name="writer">The <see cref="T:Newtonsoft.Json.JsonWriter"/> to write to.</param><param name="value">The value.</param><param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
//TODO: Implement for serialization
//throw new NotImplementedException("Serialization of Boolean");
// I have no idea if this is correct:
var b = (bool)value;
JToken valueToken;
valueToken = JToken.FromObject(b);
valueToken.WriteTo(writer);
}
#endregion Overrides of JsonConverter
}
}
And here is how I created the test class used in my unit tests:
[JsonConverter(typeof(FuzzyMatchingJsonConverter<JsonTestData>))]
public class JsonTestData: IEquatable<JsonTestData>
{
public string TestId { get; set; }
public double MinimumDistance { get; set; }
[JsonConverter(typeof(BooleanJsonConverter))]
public bool TaxIncluded { get; set; }
[JsonConverter(typeof(BooleanJsonConverter))]
public bool IsMetsFan { get; set; }
[JsonConstructor]
public JsonTestData()
{
TestId = null;
MinimumDistance = double.NaN;
TaxIncluded = false;
IsMetsFan = false;
}
public JsonTestData(string testId, double minimumDistance, bool taxIncluded, bool isMetsFan)
{
TestId = testId;
MinimumDistance = minimumDistance;
TaxIncluded = taxIncluded;
IsMetsFan = isMetsFan;
}
public override bool Equals(object obj) => Equals(obj as JsonTestData);
public bool Equals(JsonTestData other)
{
if (other == null) return false;
return ((TestId ?? "") == other.TestId)
&& (MinimumDistance == other.MinimumDistance)
&& (TaxIncluded == other.TaxIncluded)
&& (IsMetsFan == other.IsMetsFan);
}
public override string ToString() => $"TestId: {TestId}, MinimumDistance: {MinimumDistance}, TaxIncluded: {TaxIncluded}, IsMetsFan: {IsMetsFan}";
public override int GetHashCode()
{
return -1448189120 + EqualityComparer<string>.Default.GetHashCode(TestId);
}
}
The reason that [JsonConverter(typeof(BooleanJsonConverter))] as applied to your properties is not working is that you have supplied a JsonConverter for the containing type, and are not calling the applied converter(s) for its members inside your ReadJson() method.
When a converter is not applied to a type, prior to (de)serialization Json.NET uses reflection to create a JsonContract for the type that specifies how to map the type from and to JSON. In the case of an object with properties, a JsonObjectContract is generated that includes methods to construct and populate the type and lists all the members of the type to be serialized, including their names and any applied converters. Once the contract is built, the method JsonSerializerInternalReader.PopulateObject() uses it to actually deserialize an object.
When a converter is applied to a type, all the logic described above is skipped. Instead JsonConverter.ReadJson() must do everything including deserializing and setting all member values. If those members happen to have converters applied, ReadJson() will need to notice this fact and manually invoke the converter. This is what your converter needs to be doing around here:
foreach (JProperty jp in jo.Properties())
{
var prop = Mapper.KeyToProperty[jp.Name];
// Check for and use [JsonConverter(typeof(...))] if applied to the member.
prop?.SetValue(instance, jp.Value.ToObject(prop.PropertyType, serializer));
}
So, how to do this? One way would be to use c# reflection tools to check for the appropriate attribute(s). Luckily, Json.NET has already done this for you in constructing its JsonObjectContract; you can get access to it from within ReadJson() simply by calling:
var contract = serializer.ContractResolver.ResolveContract(objectType) as JsonObjectContract;
Having done that you can use the already-constructed contract to guide your deserialization.
Since you don't provide a working example of your FuzzyMatchingJsonConverter I've created something similar that will allow both snake case and pascal case properties to be deserialized into an object with camel case naming:
public abstract class FuzzyMatchingJsonConverterBase : JsonConverter
{
protected abstract JsonProperty FindProperty(JsonObjectContract contract, string propertyName);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var contract = serializer.ContractResolver.ResolveContract(objectType) as JsonObjectContract;
if (contract == null)
throw new JsonSerializationException(string.Format("Contract for type {0} is not a JsonObjectContract", objectType));
if (reader.TokenType == JsonToken.Null)
return null;
if (reader.TokenType != JsonToken.StartObject)
throw new JsonSerializationException(string.Format("Unexpected token {0}", reader.TokenType));
existingValue = existingValue ?? contract.DefaultCreator();
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonToken.Comment:
break;
case JsonToken.PropertyName:
{
var propertyName = (string)reader.Value;
reader.ReadAndAssert();
var jsonProperty = FindProperty(contract, propertyName);
if (jsonProperty == null)
continue;
object itemValue;
if (jsonProperty.Converter != null && jsonProperty.Converter.CanRead)
{
itemValue = jsonProperty.Converter.ReadJson(reader, jsonProperty.PropertyType, jsonProperty.ValueProvider.GetValue(existingValue), serializer);
}
else
{
itemValue = serializer.Deserialize(reader, jsonProperty.PropertyType);
}
jsonProperty.ValueProvider.SetValue(existingValue, itemValue);
}
break;
case JsonToken.EndObject:
return existingValue;
default:
throw new JsonSerializationException(string.Format("Unexpected token {0}", reader.TokenType));
}
}
throw new JsonReaderException("Unexpected EOF!");
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public abstract class FuzzySnakeCaseMatchingJsonConverterBase : FuzzyMatchingJsonConverterBase
{
protected override JsonProperty FindProperty(JsonObjectContract contract, string propertyName)
{
// Remove snake-case underscore.
propertyName = propertyName.Replace("_", "");
// And do a case-insensitive match.
return contract.Properties.GetClosestMatchProperty(propertyName);
}
}
// This type should be applied via attributes.
public class FuzzySnakeCaseMatchingJsonConverter : FuzzySnakeCaseMatchingJsonConverterBase
{
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}
}
// This type can be used in JsonSerializerSettings.Converters
public class GlobalFuzzySnakeCaseMatchingJsonConverter : FuzzySnakeCaseMatchingJsonConverterBase
{
readonly IContractResolver contractResolver;
public GlobalFuzzySnakeCaseMatchingJsonConverter(IContractResolver contractResolver)
{
this.contractResolver = contractResolver;
}
public override bool CanConvert(Type objectType)
{
if (objectType.IsPrimitive || objectType == typeof(string))
return false;
var contract = contractResolver.ResolveContract(objectType);
return contract is JsonObjectContract;
}
}
public static class JsonReaderExtensions
{
public static void ReadAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (!reader.Read())
throw new JsonReaderException("Unexpected EOF!");
}
}
Then you would apply it as follows:
[JsonConverter(typeof(FuzzySnakeCaseMatchingJsonConverter))]
public class JsonTestData
{
public string TestId { get; set; }
public double MinimumDistance { get; set; }
[JsonConverter(typeof(BooleanJsonConverter))]
public bool TaxIncluded { get; set; }
[JsonConverter(typeof(BooleanJsonConverter))]
public bool IsMetsFan { get; set; }
}
Notes:
I avoided pre-loading the JSON into an intermediate JToken hierarchy, since there was no need to do so.
You don't provide a working example of your own converter so I can't fix it for you in this answer, but you would want to make it inherit from FuzzyMatchingJsonConverterBase and then write your on version of protected abstract JsonProperty FindProperty(JsonObjectContract contract, string propertyName);.
You might also need to check and use other properties of JsonProperty such as JsonProperty.ItemConverter, JsonProperty.Ignored, JsonProperty.ShouldDeserialize and so on. But if you do, you may eventually end up duplicating the entire logic of JsonSerializerInternalReader.PopulateObject().
A null JSON value should be checked for near the beginning of ReadJson().
Sample working .Net fiddle that shows that the following JSON can be deserialized successfully, and thus that both the type and member converters are getting invoked:
{
"test_id": "hello",
"minimum_distance": 10101.1,
"tax_included": "yes",
"is_mets_fan": "no"
}

Dapper/Npgsql stored procedure with refcursor parameter query

I am using Dapper ( and I couldn't be happier), I know how to access normal stored procedures as mentioned here, however, how do I pass on the the Npgsql refcursor name to the proc (using C#)? For example:
I have a proc that looks like:
FUNCTION xx.getData(
v_ref refcursor,
v_id integer)
RETURNS refcursor AS
...
How would I specify the parameters for xx.getData?
For example, if getData accepted just one parameter of type int, then I could call it like so:
var data = cnn.Query<myType>("xx.getData", new {Id = 1},
commandType: CommandType.StoredProcedure);
OR
var p = new DynamicParameters();
p.Add("#id", 11);
cnn.Execute("xx.getData", p, commandType: CommandType.StoredProcedure);
I can't find the correct type in System.DbType to pass on in the query.
Note that a refcursor corresponds to an active cursor that has already been opened in a previous call. In other words, it does not correspond to a stored procedure, but rather to a resultset (possibly but not necessarily returned from a stored procedure).
Just in case you really do need to send a refcursor, what you're looking for is NpgsqlDbType.Refcursor.
Bellow custom dynamic parameter compatible for Npgsql data types. It will work for with output refcursor parameters.
/// <summary>
/// Npgsql Dynamic Param for Dapper
/// </summary>
public class PgParam : SqlMapper.IDynamicParameters
{
private static readonly Dictionary<SqlMapper.Identity, Action<IDbCommand, object>> paramReaderCache = new Dictionary<SqlMapper.Identity, Action<IDbCommand, object>>();
private readonly Dictionary<string, ParamInfo> _parameters = new Dictionary<string, ParamInfo>();
private List<object> templates;
/// <summary>
/// construct a dynamic parameter bag
/// </summary>
public PgParam()
{
}
/// <summary>
/// construct a dynamic parameter bag
/// </summary>
/// <param name="template">can be an anonymous type or a DynamicParameters bag</param>
public PgParam(object template)
{
AddDynamicParams(template);
}
/// <summary>
/// All the names of the param in the bag, use Get to yank them out
/// </summary>
public IEnumerable<string> ParameterNames
{
get { return _parameters.Select(p => p.Key); }
}
void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Identity identity)
{
AddParameters(command, identity);
}
/// <summary>
/// Append a whole object full of params to the dynamic
/// EG: AddDynamicParams(new {A = 1, B = 2}) // will add property A and B to the dynamic
/// </summary>
/// <param name="param"></param>
public void AddDynamicParams(
dynamic param
)
{
if (param is object obj)
{
if (!(obj is PgParam subDynamic))
{
if (!(obj is IEnumerable<KeyValuePair<string, object>> dictionary))
{
templates = templates ?? new List<object>();
templates.Add(obj);
}
else
{
foreach (var kvp in dictionary)
{
Add(kvp.Key, kvp.Value);
}
}
}
else
{
if (subDynamic._parameters != null)
foreach (var kvp in subDynamic._parameters)
_parameters.Add(kvp.Key, kvp.Value);
if (subDynamic.templates != null)
{
templates = templates ?? new List<object>();
foreach (var t in subDynamic.templates) templates.Add(t);
}
}
}
}
/// <summary>
/// Add a parameter to this dynamic parameter list
/// </summary>
/// <param name="name"></param>
/// <param name="value"></param>
/// <param name="dbType"></param>
/// <param name="direction"></param>
/// <param name="size"></param>
public void Add(string name, object value = null, NpgsqlDbType? dbType = null, ParameterDirection? direction = null,int? size = null)
{
_parameters[name] = new ParamInfo
{
Name = name, Value = value, ParameterDirection = direction ?? ParameterDirection.Input, DbType = dbType,
Size = size
};
}
/// <summary>
/// Add all the parameters needed to the command just before it executes
/// </summary>
/// <param name="command">The raw command prior to execution</param>
/// <param name="identity">Information about the query</param>
protected void AddParameters(IDbCommand command, SqlMapper.Identity identity)
{
if (templates != null)
foreach (var template in templates)
{
var newIdent = identity.ForDynamicParameters(template.GetType());
Action<IDbCommand, object> appender;
lock (paramReaderCache)
{
if (!paramReaderCache.TryGetValue(newIdent, out appender))
{
appender = SqlMapper.CreateParamInfoGenerator(newIdent, false, true);
paramReaderCache[newIdent] = appender;
}
}
appender(command, template);
}
foreach (var param in _parameters.Values)
{
var add = !((NpgsqlCommand) command).Parameters.Contains(param.Name);
NpgsqlParameter p;
if (add)
{
p = ((NpgsqlCommand) command).CreateParameter();
p.ParameterName = param.Name;
}
else
{
p = ((NpgsqlCommand) command).Parameters[param.Name];
}
var val = param.Value;
p.Value = val ?? DBNull.Value;
p.Direction = param.ParameterDirection;
if (param.Size != null) p.Size = param.Size.Value;
if (param.DbType != null) p.NpgsqlDbType = param.DbType.Value;
if (add) command.Parameters.Add(p);
param.AttachedParam = p;
}
}
/// <summary>
/// Get the value of a parameter
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="name"></param>
/// <returns>The value, note DBNull.Value is not returned, instead the value is returned as null</returns>
public T Get<T>(string name)
{
var val = _parameters[name].AttachedParam.Value;
if (val == DBNull.Value)
{
if (default(T) != null)
throw new ApplicationException("Attempting to cast a DBNull to a non nullable type!");
return default(T);
}
return (T) val;
}
private class ParamInfo
{
public string Name { get; set; }
public object Value { get; set; }
public ParameterDirection ParameterDirection { get; set; }
public NpgsqlDbType? DbType { get; set; }
public int? Size { get; set; }
public IDbDataParameter AttachedParam { get; set; }
}
}

Export Razor View To Excel With Big DataSet

I have list of Objects upto 150000 and I want to bind to razor view at run time for it export as EXCEL but at the time binding i am getting out of memory exception , is there any workaround to overcome this limitation ?
Export Method :
public void ExportToExcel()
{
string viewPath = "~/Modules/Reports/Views/" + TempData["reportName"] + ".cshtml";
string viewPathCopy = "~/Modules/Reports/Views/" + TempData["reportName"] + "2.cshtml";
string serverViewPath = Server.MapPath(viewPath);
string serverViewPathCopy = Server.MapPath(viewPathCopy);
if (System.IO.File.Exists(serverViewPathCopy))
{
System.IO.File.Delete(serverViewPathCopy);
}
System.IO.File.Copy(serverViewPath, serverViewPathCopy);
string viewContents = System.IO.File.ReadAllText(serverViewPathCopy).Replace("thead", "tr");
viewContents += "<style>body{font-size:8px !important;}table {padding:0 !important,margin:0 !important}</style>";
System.IO.File.WriteAllText(serverViewPathCopy, viewContents);
System.IO.File.WriteAllText(viewPathCopy, TemplateFromFile(viewPathCopy, TempData["reportData"]));
FileInfo file = new FileInfo(viewPathCopy);
if (file.Exists)
{
Response.Clear();
Response.AddHeader("Content-Disposition", "attachment; filename=" + file.Name + ".xls");
Response.AddHeader("Content-Length", file.Length.ToString());
Response.ContentType = "application/octet-stream";
Response.WriteFile(file.FullName);
Response.End();
}
else
{
Response.Write("This file does not exist.");
}
}
Binding Model To View :
public string TemplateFromFile(string file, dynamic model)
{
string template = "";
TextReader textReader = new StreamReader(HelperMethods.GetFullFilePath(file));
try
{
template = textReader.ReadToEnd();
}
finally
{
textReader.Close();
}
return Razor.Parse(template, model);
}
If i have large amounts of data to export, my goto tool is DoddleReport this lets you create a valid xlsx export. With doddle you can even create multi tab excel sheets (see my example on Github).
Some code
Extension for IEnumerable
public static ExportBuilder<TModel> Export<TModel>(this IEnumerable<TModel> models) where TModel : class
{
return ExportBuilder<TModel>.Create(models);
}
The Export Builder
public class ExportBuilder<TModel> where TModel : class
{
private readonly IEnumerable<TModel> _models;
private readonly ICollection<IExportColumn<TModel>> _columns;
/// <summary>
/// Initializes a new instance of the <see cref="T:System.Object"/> class.
/// </summary>
private ExportBuilder(IEnumerable<TModel> models)
{
_models = models;
_columns = new List<IExportColumn<TModel>>();
}
public static ExportBuilder<TModel> Create(IEnumerable<TModel> models)
{
return new ExportBuilder<TModel>(models);
}
public ExportBuilder<TModel> Column<TProperty>(Expression<Func<TModel, TProperty>> display)
{
if (!(display.Body is MemberExpression))
throw new ArgumentException(display + " is not a property!");
var memberInfo = ((MemberExpression)display.Body).Member;
if (!memberInfo.HasAttribute<DisplayNameAttribute>())
throw new ArgumentException(display + " does not have a [Display] attribute");
var displayAttribute = ExtensionsForMemberInfo.GetAttribute<DisplayNameAttribute>(memberInfo);
_columns.Add(new ExportColumn<TModel, TProperty>(displayAttribute.DisplayName, display));
return this;
}
public ExportBuilder<TModel> Column<TProperty>(string header, Expression<Func<TModel, TProperty>> property)
{
_columns.Add(new ExportColumn<TModel, TProperty>(header, property));
return this;
}
public IReportSource ToReportSource()
{
if (_models.Any())
{
return DoddleExporter.ToReportSource(_models.Select(model => _columns.ToDictionary(c => c.Header, c => c.Display(model))));
}
var result = _columns
.ToDictionary(a => a.Header, a => string.Empty);
return DoddleExporter.ToReportSource(new[] { result });
}
public Report ToReport([CanBeNull] IEnumerable<KeyValuePair<string, string>> headers, [CanBeNull] IReportWriter writer = null)
{
headers = headers ?? Enumerable.Empty<KeyValuePair<string, string>>();
var report = new Report(ToReportSource(), writer);
//report.TextFields.Footer = string.Format(#"Aangemaakt op: {0}", DateTime.Now.ToString(DataFormatStrings.Date));
var headersArray = headers as KeyValuePair<string, string>[] ?? headers.ToArray();
if (headersArray.Any())
{
report.TextFields.Header = headersArray.Aggregate(string.Empty,
(currentHeaders, header) => string.Format("{0}{3}{1} : {2}", currentHeaders, header.Key, header.Value, Environment.NewLine));
}
return report;
}
public ReportResult ToExcelReportResult([CanBeNull] IEnumerable<KeyValuePair<string, string>> headers)
{
return new ReportResult(ToReport(headers), new ExcelReportWriter(), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
}
}
The Exporter
public static class DoddleExporter
{
/// <summary>
/// Converts an enumerable of dictionaries to a report source
/// </summary>
/// <typeparam name="TValue">
/// The type of values in the dictionaries
/// </typeparam>
/// <param name="elements">
/// An enumerable of elements
/// </param>
/// <returns>The report source that was created from the elements</returns>
public static IReportSource ToReportSource<TValue>(IEnumerable<IDictionary<string, TValue>> elements)
{
var elementsArray = elements.ToArray();
if (!elementsArray.Any())
throw new ArgumentException("Can't export empty list of elements");
return ToReportSource(elementsArray, elementsArray.First().Keys.ToArray(),
(element, key) => element.ContainsKey(key) ? element[key] : default(TValue));
}
/// <summary>
/// Converts an enumerable of XElement to a report source
/// </summary>
/// <param name="rootElements">
/// The xml root elements that contain the values
/// </param>
/// <param name="keys">
/// They keys that can be used to fetch values from each root element
/// </param>
/// <returns>The report source that was created from the elements</returns>
public static IReportSource ToReportSource(IEnumerable<XElement> rootElements, string[] keys)
{
return ToReportSource(rootElements, keys, delegate(XElement element, string key)
{
var value = element.Element(XmlConvert.EncodeLocalName(key));
return value != null ? value.Value : null;
});
}
/// <summary>
/// Converts a list of elements to a report source
/// </summary>
/// <param name="elements">
/// An enumerable of elements
/// </param>
/// <param name="keys">
/// They keys with which the values can be fetched from one element
/// </param>
/// <param name="valueSelector">
/// The function with which one value can be fetched given one key and one element
/// </param>
/// <returns>The report source that was created from the elements</returns>
public static IReportSource ToReportSource<T>(IEnumerable<T> elements, string[] keys,
Func<T, string, object> valueSelector)
{
var expandos = new List<ExpandoObject>();
foreach (var element in elements)
{
var expando = new ExpandoObject();
var expandoDictionary = (IDictionary<string, object>) expando;
foreach (var key in keys)
{
var value = valueSelector(element, key);
expandoDictionary[key] = value;
}
expandos.Add(expando);
}
return expandos.ToReportSource();
}
}
Helper classes
public interface IExportColumn<TModel> where TModel : class
{
string Header { get; }
Func<TModel, Object> Display { get; }
}
public class ExportColumn<TModel, TProperty> : IExportColumn<TModel> where TModel : class
{
private readonly string _header;
private readonly Expression<Func<TModel, TProperty>> _display;
public string Header { get { return _header; } }
public Func<TModel, Object> Display { get { return model => _display.Compile().Invoke(model); } }
/// <summary>
/// Initializes a new instance of the <see cref="T:System.Object"/> class.
/// </summary>
public ExportColumn(string header, Expression<Func<TModel, TProperty>> display)
{
_header = header;
_display = display; ;
}
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>
/// A string that represents the current object.
/// </returns>
public override string ToString()
{
return string.Format("Header: {0}, Display: {1}", _header, _display);
}
}
Usage
var report = probing.Measurements.Export()
.Column(MeasurementResource.Depth, m => m.Depth)
.Column(MeasurementResource.DepthBelowWater, m => m.DepthBelowWater)
.Column(MeasurementResource.ResistancePoint, m => m.ResistancePoint)
.Column(MeasurementResource.FrictionLateral, m => m.FrictionLateral)
.Column(MeasurementResource.FrictionLocal, m => m.FrictionLocal)
.Column(MeasurementResource.FrictionTotal, m => m.FrictionTotal)
.Column(MeasurementResource.Inclination, m => m.Inclination)
.Column(MeasurementResource.PoreWaterPressure, m => m.PoreWaterPressure)
.Column(MeasurementResource.Speed, m => m.Speed)
.Column(MeasurementResource.CalcAlpha, m => m.CalcAlpha)
.Column(MeasurementResource.CalcGammaDry, m => m.CalcGammaDry)
.Column(MeasurementResource.CalcGammaWet, m => m.CalcGammaWet)
.Column(MeasurementResource.GrainTension, m => m.GrainTension)
.Column(MeasurementResource.CompressionCoefficient, m => m.CompressionCoefficient)
.ToReport(null, new ExcelReportWriter());
var stream = new MemoryStream();
writer.WriteReport(report, stream);
stream.Seek(0, SeekOrigin.Begin);
return File(stream, "application/vnd.ms-excel", string.Format(#"{0}-{1}.xlsx", probing.Project.ProjectNumber, probing.ProbingNumber));

JsonConverter: Get parent object in ReadJson, without $refs

I have a JSON input whose objects are all derived from a base class along the lines of this:
public abstract class Base
{
public Base Parent { get; set; }
}
I'm trying to create a CustomCreationConverter to set the Parent property of each object to the parent node in the JSON input using ReadJson (except for the root node, of course). Is this possible? I'd rather not have to traverse the objects after creation to set the Parent property.
Example Time!
Say I have this input JSON:
{
"Name": "Joe",
"Children": [
{ "Name": "Sam", "FavouriteToy": "Car" },
{ "Name": "Tom", "FavouriteToy": "Gun" },
]
}
I have the following two classes:
public class Person
{
public Person Parent { get; set; }
public string Name { get; set; }
public List<Child> Children { get; set; }
}
public class Child : Person
{
public string FavouriteToy { get; set; }
}
The Name and FavouriteToy properties deserialise fine, but I want to have the Parent property of any Person object set to, as you'd expect, the actual parent object within the JSON input (presumably using a JsonConverter). The best I've been able to implement so far is recursively traversing each object after deserialisation and setting the Parent property that way.
P.S.
I want to point out that I know I'm able to do this with references inside the JSON itself, but I'd rather avoid that.
Not a duplicate :(
That question refers to creating an instance of the correct derived class, the issue I'm having is finding a way to get context during the deserialisation of the objects. I'm trying to use a JsonConverter's ReadJson method to set a property of a deserialised object to refer to another object within the same JSON input, without using $refs.
My best guess is that you are after something like this:
public override bool CanConvert(Type objectType)
{
return typeof(Person).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
object value = Activator.CreateInstance(objectType);
serializer.Populate(reader, value);
Person p = value as Person;
if (p.Children != null)
{
foreach (Child child in p.Children)
{
child.Parent = p;
}
}
return value;
}
Note: If you are de-serializing this class a lot (like de-serializing model from http request in a web application) you'll get better performance creating objects with a pre-compiled factory, rather than with object activator:
object value = serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
Note that it is not possible to get access to a parent, because parent objects are always created after their children. That is you need to read the json that the object consists of to the end to be able fully construct the object and by the time you've read the last bracket of an object, you've already read all its children. When a child parsed there is no parent yet to get the reference of.
I ran into a similar dilemma. However, in my particular scenario, I really needed property Parent to be readonly or, at least, private set. For that reason, #Andrew Savinykh's solution, despite being very good, wasn't enough for me. Thus, I finally ended merging different approaches together until I reached a possible "solution" or workaround.
JsonContext
To start with, I noticed that JsonSerializer provides a public readonly Context property, which may be used to share data between instances and converters involved in the same deserialization proccess. Taking advantage of this, I implemented my own context class as follows:
public class JsonContext : Dictionary<string, object>
{
public void AddUniqueRef(object instance)
{
Add(instance.GetType().Name, instance);
}
public bool RemoveUniqueRef(object instance)
{
return Remove(instance.GetType().Name);
}
public T GetUniqueRef<T>()
{
return (T)GetUniqueRef(typeof(T));
}
public bool TryGetUniqueRef<T>(out T value)
{
bool result = TryGetUniqueRef(typeof(T), out object obj);
value = (T)obj;
return result;
}
public object GetUniqueRef(Type type)
{
return this[type.Name];
}
public bool TryGetUniqueRef(Type type, out object value)
{
return TryGetValue(type.Name, out value);
}
}
Next, I needed to add an instance of my JsonContext to my JsonSerializerSetttings:
var settings = new JsonSerializerSettings
{
Context = new StreamingContext(StreamingContextStates.Other, new JsonContext()),
// More settings here [...]
};
_serializer = JsonSerializer.CreateDefault(settings);
OnDeserializing / OnDeserialized
I tried to use this context at OnDeserializing and OnDeserialized callbacks, but, as #Andrew Savinykh states, they are called in the following order:
Child.OnDeserializing
Child.OnDeserialized
Person.OnDeserializing
Person.OnDeserialized
EDIT: After finishing my initial implementation (see Solution 2) I noticed that, while using any kind of *CreationConverter, the above order is modified as follows:
Person.OnDeserializing
Child.OnDeserializing
Child.OnDeserialized
Person.OnDeserialized
I am not really sure of the reason behind this. It might be related to the fact that JsonSerializer normally uses Deserialize which wraps, between deserialization callbacks, instance creation and population, from bottom to top, of the object composition tree. By contrast, while using a CustomCreationConverter, the serializer delegates the instantiation to our Create method and then it might be only executing Populate in the second stacked order.
This stacked callback calling order is very convenient if we are looking for a simpler solution (see Solution 1). Taking advantage of this edition, I am adding this new approach in first place below (Solution 1) and the original and more complex one at the end (Solution 2).
Solution 1. Serialization Callbacks
Compared to Solution 2, this may be a simpler and more elegant approach. Nevertheless, it does not support initializing readonly members via constructor. If that is your case, please refer to Solution 2.
A requirement for this implementation, as I stated above, is a CustomCreationConverter to force callbacks to be called in a convenient order. E.g, we could use the following PersonConverter for both Person and Child.
public sealed class PersonConverter : CustomCreationConverter<Person>
{
/// <inheritdoc />
public override Person Create(Type objectType)
{
return (Person)Activator.CreateInstance(objectType);
}
}
Then, we only have to access our JsonContext on serialization callbacks to share the Person Parent property.
public class Person
{
public Person Parent { get; set; }
public string Name { get; set; }
public List<Child> Children { get; set; }
[OnDeserializing]
private void OnDeserializing(StreamingContext context)
{
((JsonContext)context.Context).AddUniqueRef(this);
}
[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
((JsonContext)context.Context).RemoveUniqueRef(this);
}
}
public class Child : Person
{
public string FavouriteToy { get; set; }
[OnDeserializing]
private void OnDeserializing(StreamingContext context)
{
Parent = ((JsonContext)context.Context).GetUniqueRef<Person>();
}
}
Solution 2. JObjectCreationConverter
Here it is my initial solution. It does support initializing readonly members via parameterized constructor. It could be combined with Solution 1, moving JsonContext usage to serialization callbacks.
In my specific scenario, Person class lacks a parameterless constructor because it needs to initialize some readonly members (i.e. Parent). To achieve this, we need our own JsonConverter class, totally based on CustomCreationConverter implementation, using an abstract T Create method with two new arguments: JsonSerializer, in order to provide access to my JsonContext, and JObject, to pre-read some values from reader.
/// <summary>
/// Creates a custom object.
/// </summary>
/// <typeparam name="T">The object type to convert.</typeparam>
public abstract class JObjectCreationConverter<T> : JsonConverter
{
#region Public Overrides JsonConverter
/// <summary>
/// Gets a value indicating whether this <see cref="JsonConverter" /> can write JSON.
/// </summary>
/// <value>
/// <c>true</c> if this <see cref="JsonConverter" /> can write JSON; otherwise, <c>false</c>.
/// </value>
public override bool CanWrite => false;
/// <summary>
/// Writes the JSON representation of the object.
/// </summary>
/// <param name="writer">The <see cref="JsonWriter" /> to write to.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param>
/// <exception cref="NotSupportedException">JObjectCreationConverter should only be used while deserializing.</exception>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotSupportedException($"{nameof(JObjectCreationConverter<T>)} should only be used while deserializing.");
}
/// <summary>
/// Reads the JSON representation of the object.
/// </summary>
/// <param name="reader">The <see cref="JsonReader" /> to read from.</param>
/// <param name="objectType">Type of the object.</param>
/// <param name="existingValue">The existing value of object being read.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>The object value.</returns>
/// <exception cref="JsonSerializationException">No object created.</exception>
/// <exception cref="JsonReaderException"><paramref name="reader" /> is not valid JSON.</exception>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
// Load JObject from stream
JObject jObject = JObject.Load(reader);
T value = Create(jObject, objectType, serializer);
if (value == null)
{
throw new JsonSerializationException("No object created.");
}
using (JsonReader jObjectReader = jObject.CreateReader(reader))
{
serializer.Populate(jObjectReader, value);
}
return value;
}
/// <summary>
/// Determines whether this instance can convert the specified object type.
/// </summary>
/// <param name="objectType">Type of the object.</param>
/// <returns>
/// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
/// </returns>
public override bool CanConvert(Type objectType)
{
return typeof(T).IsAssignableFrom(objectType);
}
#endregion
#region Protected Methods
/// <summary>
/// Creates an object which will then be populated by the serializer.
/// </summary>
/// <param name="jObject"><see cref="JObject" /> instance to browse the JSON object being deserialized</param>
/// <param name="objectType">Type of the object.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>The created object.</returns>
protected abstract T Create(JObject jObject, Type objectType, JsonSerializer serializer);
#endregion
}
NOTE: CreateReader is a custom extension method that calls the default and parameterless CreaterReader and then imports all settings from the original reader. See #Alain's response for more details.
Finally, if we apply this solution to the given (and customized) example:
//{
// "name": "Joe",
// "children": [
// {
// "name": "Sam",
// "favouriteToy": "Car",
// "children": []
// },
// {
// "name": "Tom",
// "favouriteToy": "Gun",
// "children": []
// }
// ]
//}
public class Person
{
public string Name { get; }
[JsonIgnore] public Person Parent { get; }
[JsonIgnore] public IEnumerable<Child> Children => _children;
public Person(string name, Person parent = null)
{
_children = new List<Child>();
Name = name;
Parent = parent;
}
[JsonProperty("children", Order = 10)] private readonly IList<Child> _children;
}
public sealed class Child : Person
{
public string FavouriteToy { get; set; }
public Child(Person parent, string name, string favouriteToy = null) : base(name, parent)
{
FavouriteToy = favouriteToy;
}
}
We only have to add the following JObjectCreationConverters:
public sealed class PersonConverter : JObjectCreationConverter<Person>
{
#region Public Overrides JObjectCreationConverter<Person>
/// <inheritdoc />
/// <exception cref="JsonSerializationException">No object created.</exception>
/// <exception cref="JsonReaderException"><paramref name="reader" /> is not valid JSON.</exception>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
object result = base.ReadJson(reader, objectType, existingValue, serializer);
((JsonContext)serializer.Context.Context).RemoveUniqueRef(result);
return result;
}
#endregion
#region Protected Overrides JObjectCreationConverter<Person>
/// <inheritdoc />
protected override Person Create(JObject jObject, Type objectType, JsonSerializer serializer)
{
var person = new Person((string)jObject["name"]);
((JsonContext)serializer.Context.Context).AddUniqueRef(person);
return person;
}
public override bool CanConvert(Type objectType)
{
// Overridden with a more restrictive condition to avoid this converter from being used by child classes.
return objectType == typeof(Person);
}
#endregion
}
public sealed class ChildConverter : JObjectCreationConverter<Child>
{
#region Protected Overrides JObjectCreationConverter<Child>
/// <inheritdoc />
protected override Child Create(JObject jObject, Type objectType, JsonSerializer serializer)
{
var parent = ((JsonContext)serializer.Context.Context).GetUniqueRef<Person>();
return new Child(parent, (string)jObject["name"]);
}
/// <inheritdoc />
public override bool CanConvert(Type objectType)
{
// Overridden with a more restrictive condition.
return objectType == typeof(Child);
}
#endregion
}
Bonus Track. ContextCreationConverter
public class ContextCreationConverter : JsonConverter
{
#region Public Overrides JsonConverter
/// <summary>
/// Gets a value indicating whether this <see cref="JsonConverter" /> can write JSON.
/// </summary>
/// <value>
/// <c>true</c> if this <see cref="JsonConverter" /> can write JSON; otherwise, <c>false</c>.
/// </value>
public override sealed bool CanWrite => false;
/// <summary>
/// Determines whether this instance can convert the specified object type.
/// </summary>
/// <param name="objectType">Type of the object.</param>
/// <returns>
/// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
/// </returns>
public override sealed bool CanConvert(Type objectType)
{
return false;
}
/// <summary>
/// Writes the JSON representation of the object.
/// </summary>
/// <param name="writer">The <see cref="JsonWriter" /> to write to.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param>
/// <exception cref="NotSupportedException">ContextCreationConverter should only be used while deserializing.</exception>
public override sealed void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotSupportedException($"{nameof(ContextCreationConverter)} should only be used while deserializing.");
}
/// <summary>
/// Reads the JSON representation of the object.
/// </summary>
/// <param name="reader">The <see cref="JsonReader" /> to read from.</param>
/// <param name="objectType">Type of the object.</param>
/// <param name="existingValue">The existing value of object being read.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>The object value.</returns>
/// <exception cref="JsonReaderException"><paramref name="reader" /> is not valid JSON.</exception>
/// <exception cref="JsonSerializationException">No object created.</exception>
public override sealed object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
// Load JObject from stream
JObject jObject = JObject.Load(reader);
object value = Create(jObject, objectType, serializer);
using (JsonReader jObjectReader = jObject.CreateReader(reader))
{
serializer.Populate(jObjectReader, value);
}
return value;
}
#endregion
#region Protected Methods
protected virtual object GetCreatorArg(Type type, string name, JObject jObject, JsonSerializer serializer)
{
JsonContext context = (JsonContext)serializer.Context.Context;
if (context.TryGetUniqueRef(type, out object value))
{
return value;
}
if (context.TryGetValue(name, out value))
{
return value;
}
if (jObject.TryGetValue(name, StringComparison.InvariantCultureIgnoreCase, out JToken jToken))
{
return jToken.ToObject(type, serializer);
}
if (type.IsValueType)
{
return Activator.CreateInstance(type);
}
return null;
}
#endregion
#region Private Methods
/// <summary>
/// Creates a instance of the <paramref name="objectType" />
/// </summary>
/// <param name="jObject">
/// The JSON Object to read from
/// </param>
/// <param name="objectType">
/// Type of the object to create.
/// </param>
/// <param name="serializer">
/// The calling serializer.
/// </param>
/// <returns>
/// A new instance of the <paramref name="objectType" />
/// </returns>
/// <exception cref="JsonSerializationException">
/// Could not found a constructor with the expected signature
/// </exception>
private object Create(JObject jObject, Type objectType, JsonSerializer serializer)
{
JsonObjectContract contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(objectType);
ObjectConstructor<object> creator = contract.OverrideCreator ?? GetParameterizedConstructor(objectType).Invoke;
if (creator == null)
{
throw new JsonSerializationException($"Could not found a constructor with the expected signature {GetCreatorSignature(contract)}");
}
object[] args = GetCreatorArgs(contract.CreatorParameters, jObject, serializer);
return creator(args);
}
private object[] GetCreatorArgs(JsonPropertyCollection parameters, JObject jObject, JsonSerializer serializer)
{
var result = new object[parameters.Count];
for (var i = 0; i < result.Length; ++i)
{
result[i] = GetCreatorArg(parameters[i].PropertyType, parameters[i].PropertyName, jObject, serializer);
}
return result;
}
private ConstructorInfo GetParameterizedConstructor(Type objectType)
{
var constructors = objectType.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
return constructors.Length == 1 ? constructors[0] : null;
}
private string GetCreatorSignature(JsonObjectContract contract)
{
StringBuilder sb = contract.CreatorParameters
.Aggregate(new StringBuilder("("), (s, p) => s.AppendFormat("{0} {1}, ", p.PropertyType.Name, p.PropertyName));
return sb.Replace(", ", ")", sb.Length - 2, 2).ToString();
}
#endregion
}
USAGE:
// For Person we could use any other CustomCreationConverter.
// The only purpose is to achievee the stacked calling order for serialization callbacks.
[JsonConverter(typeof(ContextCreationConverter))]
public class Person
{
public string Name { get; }
[JsonIgnore] public IEnumerable<Child> Children => _children;
[JsonIgnore] public Person Parent { get; }
public Person(string name, Person parent = null)
{
_children = new List<Child>();
Name = name;
Parent = parent;
}
[OnDeserializing]
private void OnDeserializing(StreamingContext context)
{
((JsonContext)context.Context).AddUniqueRef(this);
}
[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
((JsonContext)context.Context).RemoveUniqueRef(this);
}
[JsonProperty("children", Order = 10)] private readonly IList<Child> _children;
}
[JsonConverter(typeof(ContextCreationConverter))]
public sealed class Child : Person
{
[JsonProperty(Order = 5)] public string FavouriteToy { get; set; }
public Child(Person parent, string name, string favouriteToy = null) : base(name, parent)
{
FavouriteToy = favouriteToy;
}
}
I create an example:
public class Base
{
//This is JSON object's attribute.
public string CPU {get; set;}
public string PSU { get; set; }
public List<string> Drives { get; set; }
public string price { get; set; }
//and others...
}
public class newBase : Base
{
////same
//public string CPU { get; set; }
//public string PSU { get; set; }
//public List<string> Drives { get; set; }
//convert to new type
public decimal price { get; set; } //to other type you want
//Added new item
public string from { get; set; }
}
public class ConvertBase : CustomCreationConverter<Base>
{
public override Base Create(Type objectType)
{
return new newBase();
}
}
static void Main(string[] args)
{
//from http://www.newtonsoft.com/json/help/html/ReadJsonWithJsonTextReader.htm (creadit) + modify by me
string SimulateJsonInput = #"{'CPU': 'Intel', 'PSU': '500W', 'Drives': ['DVD read/writer', '500 gigabyte hard drive','200 gigabype hard drive'], 'price' : '3000', 'from': 'Asus'}";
JsonSerializer serializer = new JsonSerializer();
Base Object = JsonConvert.DeserializeObject<Base>(SimulateJsonInput);
Base converted = JsonConvert.DeserializeObject<Base>(SimulateJsonInput, new ConvertBase());
newBase newObject = (newBase)converted;
//Console.Write(Object.from);
Console.WriteLine("Newly converted atrribute type attribute =" + " " + newObject.price.GetType());
Console.WriteLine("Newly added attribute =" + " " + newObject.from);
Console.Read();
}
hope this helps.
Support link: Json .net documentation

Get model type and StringLength attribute

I want to get the StringLength attribute.
Class code :
var type1 = Type.GetType("MvcApplication4.Models.Sample.SampleMasterModel");
var metadata = ModelMetadataProviders.Current.GetMetadataForType(null, type1);
var properties = metadata.Properties;
var prop = properties.FirstOrDefault(p => p.PropertyName == "Remark");
?? Get StringLength attr?
Model Code:
public class SampleModel
{
[StringLength(50)]
public string Remark { get; set; }
}
Based on wudzik and Habib help. I modified the code.
Final Code:
PropertyInfo propertyInfo = type1.GetProperties().FirstOrDefault(p => p.Name == "Remark");
if (propertyInfo != null)
{
var attributes = propertyInfo.GetCustomAttributes(true);
var stringLengthAttrs =
propertyInfo.GetCustomAttributes(typeof (StringLengthAttribute), true).First();
var stringLength = stringLengthAttrs != null ? ((StringLengthAttribute)stringLengthAttrs).MaximumLength : 0;
}
You can get CustomAttributes through PropertyInfo like:
PropertyInfo propertyInfo = type1.GetProperties().FirstOrDefault(p=> p.Name == "Remark");
if (propertyInfo != null)
{
var attributes = propertyInfo.GetCustomAttributes(true);
}
/// <summary>
/// Returns the StringLengthAttribute for a property based on the property name passed in.
/// Use this method in the class or in a base class
/// </summary>
/// <param name="type">This type of the class where you need the property StringLengthAttribute.</param>
/// <param name="propertyName">This is the property name.</param>
/// <returns>
/// StringLengthAttribute of the propertyName passed in, for the Type passed in
/// </returns>
public static StringLengthAttribute GetStringLengthAttribute(Type type, string propertyName)
{
StringLengthAttribute output = null;
try
{
output = (StringLengthAttribute)type.GetProperty(propertyName).GetCustomAttribute(typeof(StringLengthAttribute));
}
catch (Exception ex)
{
//error handling
}
return output;
} //GetStringLengthAttribute
/// <summary>
/// Returns the StringLengthAttribute for a property based on the property name passed in.
/// Use this method in the class or in a base class
/// </summary>
/// <param name="propertyName">This is the property name.</param>
/// <returns>
/// StringLengthAttribute of the propertyName passed in, for the current class
/// </returns>
public StringLengthAttribute GetStringLengthAttribute(string propertyName)
{
StringLengthAttribute output = null;
try
{
output = (StringLengthAttribute)this.GetType().GetProperty(propertyName).GetCustomAttribute(typeof(StringLengthAttribute));
}
catch (Exception ex)
{
//error handling
}
return output;
} //GetStringLengthAttribute
}

Categories

Resources