Deserialize XML To Object using Dynamic - c#

Is it possible Deserialize unknown XML to object like below?
var xml = #"<Students><Student><Name>Arul</Name><Mark>90</Mark></Student></Students>";
var serializer = new XmlSerializer(typeof(DynamicObject));
dynamic students = serializer.Deserialize(new XmlTextReader(new StringReader(xml)));

You may want to try this.
string xml = #"<Students>
<Student ID=""100"">
<Name>Arul</Name>
<Mark>90</Mark>
</Student>
<Student>
<Name>Arul2</Name>
<Mark>80</Mark>
</Student>
</Students>";
dynamic students = DynamicXml.Parse(xml);
var id = students.Student[0].ID;
var name1 = students.Student[1].Name;
foreach(var std in students.Student)
{
Console.WriteLine(std.Mark);
}
public class DynamicXml : DynamicObject
{
XElement _root;
private DynamicXml(XElement root)
{
_root = root;
}
public static DynamicXml Parse(string xmlString)
{
return new DynamicXml(XDocument.Parse(xmlString).Root);
}
public static DynamicXml Load(string filename)
{
return new DynamicXml(XDocument.Load(filename).Root);
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = null;
var att = _root.Attribute(binder.Name);
if (att != null)
{
result = att.Value;
return true;
}
var nodes = _root.Elements(binder.Name);
if (nodes.Count() > 1)
{
result = nodes.Select(n => n.HasElements ? (object)new DynamicXml(n) : n.Value).ToList();
return true;
}
var node = _root.Element(binder.Name);
if (node != null)
{
result = node.HasElements || node.HasAttributes ? (object)new DynamicXml(node) : node.Value;
return true;
}
return true;
}
}
--EDIT--
To make it work with xml namespaces, I added RemoveNamespaces method.
public class DynamicXml : DynamicObject
{
XElement _root;
private DynamicXml(XElement root)
{
_root = root;
}
public static DynamicXml Parse(string xmlString)
{
return new DynamicXml(RemoveNamespaces(XDocument.Parse(xmlString).Root));
}
public static DynamicXml Load(string filename)
{
return new DynamicXml(RemoveNamespaces(XDocument.Load(filename).Root));
}
private static XElement RemoveNamespaces(XElement xElem)
{
var attrs = xElem.Attributes()
.Where(a => !a.IsNamespaceDeclaration)
.Select(a => new XAttribute(a.Name.LocalName, a.Value))
.ToList();
if (!xElem.HasElements)
{
XElement xElement = new XElement(xElem.Name.LocalName, attrs);
xElement.Value = xElem.Value;
return xElement;
}
var newXElem = new XElement(xElem.Name.LocalName, xElem.Elements().Select(e => RemoveNamespaces(e)));
newXElem.Add(attrs);
return newXElem;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = null;
var att = _root.Attribute(binder.Name);
if (att != null)
{
result = att.Value;
return true;
}
var nodes = _root.Elements(binder.Name);
if (nodes.Count() > 1)
{
result = nodes.Select(n => n.HasElements ? (object)new DynamicXml(n) : n.Value).ToList();
return true;
}
var node = _root.Element(binder.Name);
if (node != null)
{
result = node.HasElements || node.HasAttributes ? (object)new DynamicXml(node) : node.Value;
return true;
}
return true;
}
}

Related

C# XmlIgnoreAttribute similar property name

XmlIgnoreAttribute ignoring both property's IsEnabled and isEnabledFieldSpecified bacause they have similar names. How to fix this problem? As result in i property secondCar i have IsEnabled=false but i expect to get IsEnabled=true. Maybe this is duplicate question but i can not find answer on stack.
class Program
{
static void Main(string[] args)
{
Car firstCar = new Car() { IsEnabled = true };
Car secondCar = new Car() { IsEnabled = false };
secondCar = XmlUtility.XmlStr2Obj<Car>(XmlUtility.Obj2XmlStr(firstCar));
}
}
[Serializable]
public class Car
{
private bool isEnabledFieldSpecified;
private bool isEnabledField;
[XmlAttributeAttribute()]
public bool IsEnabled
{
get
{
return this.isEnabledField;
}
set
{
this.isEnabledField = value;
}
}
[XmlIgnoreAttribute()]
public bool IsEnabledSpecified
{
get
{
return this.isEnabledFieldSpecified;
}
set
{
this.isEnabledFieldSpecified = value;
}
}
}
namespace Utils
{
public class XmlUtility
{
public static string Obj2XmlStr(object obj, string nameSpace)
{
if (obj == null) return string.Empty;
XmlSerializer sr = SerializerCache.GetSerializer(obj.GetType());
StringBuilder sb = new StringBuilder();
StringWriter w = new StringWriter(sb, CultureInfo.InvariantCulture);
sr.Serialize(
w,
obj,
new XmlSerializerNamespaces(
new[]
{
new XmlQualifiedName("", nameSpace)
}
));
return sb.ToString();
}
public static string Obj2XmlStr(object obj)
{
if (obj == null) return string.Empty;
XmlSerializer sr = SerializerCache.GetSerializer(obj.GetType());
StringBuilder sb = new StringBuilder();
StringWriter w = new StringWriter(sb, CultureInfo.InvariantCulture);
sr.Serialize(
w,
obj,
new XmlSerializerNamespaces(new[] { new XmlQualifiedName(string.Empty) }));
return sb.ToString();
}
public static T XmlStr2Obj<T>(string xml)
{
if (xml == null) return default(T);
if (xml == string.Empty) return (T)Activator.CreateInstance(typeof(T));
StringReader reader = new StringReader(xml);
XmlSerializer sr = SerializerCache.GetSerializer(typeof(T));
return (T)sr.Deserialize(reader);
}
public static XmlElement XmlStr2XmlDom(string xml)
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
return doc.DocumentElement;
}
public static XmlElement Obj2XmlDom(object obj, string nameSpace)
{
return XmlStr2XmlDom(Obj2XmlStr(obj, nameSpace));
}
}
internal class SerializerCache
{
private static readonly Hashtable Hash = new Hashtable();
public static XmlSerializer GetSerializer(Type type)
{
XmlSerializer res;
lock (Hash)
{
res = Hash[type.FullName] as XmlSerializer;
if (res == null)
{
res = new XmlSerializer(type);
Hash[type.FullName] = res;
}
}
return res;
}
}
}
Try to set IsEnabledSpecified property to true in both objects. Like this:
Car firstCar = new Car() { IsEnabled = true, IsEnabledSpecified = true };
Car secondCar = new Car() { IsEnabled = false, IsEnabledSpecified = true };
This way serializer would know, that isEnabled property should be serialized.
IsEnabledSpecified property has special meaning to the serializer: it wouldn't be serialized itself(because of XmlIgnore attribute), but it controls would xml element it related to appear("Specified" property set to true) in the xml payload or not("Specified" property set to false).

Is it possible to get the predicate (Expression<Func<T,bool>>) used on an IQueryable<T> and apply it to another Lambda function?

I have the following issue:
I get an IQueryable<T> from LinqToQueryString
This IQueryable<T> is used to query MongoDB
The IQueryable<T> is used to return paged dataset as well as total number of items to determine pages etc
MongoDB adds a group by to the Count on an IQueryable<T>.Where().Count(). This causes Count operations to run very slowly.
Possible solution:
Get the Expression<Func<T,bool>> from the original IQueryable<T> and apply it to the mongoCollection<T>.Count(filter). This bypasses the issue.
I have tried to get the "Where" from the IQueryable<T>.Expression and then manipulate the ExpressionType to a format that can be used in DynamicExpression.ParseLambda(). For the most part, this worked fine until I tested the code with a DateTime expression.
I have attached an LINQPad script that uses a local MongoDB installation to populate data and then Count using the new Expression created from the ExpressionVisitor.
I am hoping there is an easier way to reuse the "Where" from the original Expression in the new MongoDB FilterDefinitionBuilder<T>.Where(originalWhereExpression).
Script dependencies are:
Code:
<Query Kind="Program">
<Reference><RuntimeDirectory>\System.Linq.dll</Reference>
<Reference><RuntimeDirectory>\System.Linq.Expressions.dll</Reference>
<Reference><RuntimeDirectory>\System.Linq.Queryable.dll</Reference>
<NuGetReference>Faker</NuGetReference>
<NuGetReference>LINQKit.Core</NuGetReference>
<NuGetReference>mongocsharpdriver</NuGetReference>
<NuGetReference>MongoDB.Driver</NuGetReference>
<NuGetReference>NBuilder</NuGetReference>
<NuGetReference>Newtonsoft.Json</NuGetReference>
<NuGetReference>System.Linq.Dynamic</NuGetReference>
<Namespace>FizzWare.NBuilder</Namespace>
<Namespace>LinqKit</Namespace>
<Namespace>MongoDB.Bson</Namespace>
<Namespace>MongoDB.Bson.Serialization.Attributes</Namespace>
<Namespace>MongoDB.Driver</Namespace>
<Namespace>MongoDB.Driver.Builders</Namespace>
<Namespace>MongoDB.Driver.Linq</Namespace>
<Namespace>myAlias = System.Linq.Dynamic</Namespace>
<Namespace>Newtonsoft.Json</Namespace>
<Namespace>System.Linq</Namespace>
<Namespace>System.Linq.Expressions</Namespace>
<Namespace>System.Threading.Tasks</Namespace>
<Namespace>System.Threading.Tasks.Dataflow</Namespace>
</Query>
private string _mongoDBConnectionString = "mongodb://localhost";
private string _mongoDBDatabase = "LinqToQ";
private string _mongoDBCollection = "People";
private IMongoClient _mongoClient;
private IMongoDatabase _mongoDb;
private int _demoCount = 100000;
private bool _doPrep = true;
void Main()
{
_connectToMongoDB();
if (_doPrep)
_prepMongo();
var mongoDataQuery = _queryDemoData().Result;
mongoDataQuery.Expression.ToString().Dump("Original Expression");
var whereFinder = new WhereFinder();
whereFinder.SetWhere(mongoDataQuery.Expression);
var tempColl = _getPeopleCollection();
if (!string.IsNullOrEmpty(whereFinder.WhereClause))
{
var filter = new FilterDefinitionBuilder<Person>();
tempColl.Count(filter.Where(_createWherePredicate<Person>(whereFinder.GetLambdaParts<Person>()))).Dump("Dynamic where count");
}
else
tempColl.Count(FilterDefinition<Person>.Empty).Dump("No filter count");
"Done".Dump();
}
// Define other methods and classes here
//
private void _replaceExpressionTypes(ref StringBuilder whereBuilder, Dictionary<ExpressionType,string> expressionTypes)
{
foreach (var expType in expressionTypes.Keys)
{
whereBuilder.Replace($" {expType} ", $" {expressionTypes[expType]} ");
}
var openBracketCount = whereBuilder.ToString().Count(s => s == char.Parse("("));
var closeBracketCount = whereBuilder.ToString().Count(s=> s==char.Parse(")"));
//whereBuilder.Replace("new DateTime(1974, 1, 1)","\"1974-01-01T00:00:00.00Z\"");
whereBuilder.Insert(0,"(",1);
whereBuilder.Append(")");
$"OpenBrackets: {openBracketCount} vs CloseBrackets: {closeBracketCount}".Dump("Found Brackets");
if(openBracketCount==closeBracketCount)
return;
if (openBracketCount > closeBracketCount)
{
var firstopenBracket = whereBuilder.ToString().IndexOf("(");
whereBuilder.Remove(firstopenBracket,1);
}
var lastCloseBracket = whereBuilder.ToString().LastIndexOf(")");
if(lastCloseBracket>-1)
whereBuilder.Remove(lastCloseBracket,1);
}
private Dictionary<ExpressionType, string> _buildExpressionTypePairs()
{
var result = new Dictionary<ExpressionType, string>();
result.Add(ExpressionType.Not, "!");
result.Add(ExpressionType.Add, "+");
result.Add(ExpressionType.AddChecked, "+");
result.Add(ExpressionType.Subtract, "-");
result.Add(ExpressionType.SubtractChecked, "-");
result.Add(ExpressionType.Multiply, "*");
result.Add(ExpressionType.MultiplyChecked, "*");
result.Add(ExpressionType.Divide, "/");
result.Add(ExpressionType.Modulo, "%");
result.Add(ExpressionType.And, "&");
result.Add(ExpressionType.AndAlso, "&&");
result.Add(ExpressionType.Or, "|");
result.Add(ExpressionType.OrElse, "||");
result.Add(ExpressionType.LessThan, "<");
result.Add(ExpressionType.LessThanOrEqual, "<=");
result.Add(ExpressionType.GreaterThan, ">");
result.Add(ExpressionType.GreaterThanOrEqual, ">=");
result.Add(ExpressionType.Equal, "==");
result.Add(ExpressionType.NotEqual, "!=");
return result;
}
private Expression<Func<Person, bool>> _createWherePredicate<T>(LamdaParts<T> lamdaParts)
{
var whereBuilder = new StringBuilder(lamdaParts.ExpressionString);
_replaceExpressionTypes(ref whereBuilder, _buildExpressionTypePairs());
whereBuilder.ToString().Dump("Manipulated where cluase");
var parameter = Expression.Parameter(lamdaParts.ParamterType, lamdaParts.ExpressionParameter);
//lamdaParts.ParamterType.Dump("Parameter");
//var parameter = Expression.Parameter(typeof(Person), "p");
var expression = myAlias.DynamicExpression.ParseLambda(new[] { parameter }, null, whereBuilder.ToString());
//return Expression.Lambda<Func<Person, bool>>(whereExpression, parameter);
return Expression.Lambda<Func<Person, bool>>(expression.Body, expression.Parameters);
}
private async Task<IMongoQueryable<Person>> _queryDemoData()
{
var people = _getPeopleCollection();
return people.AsQueryable().Where(p => p.DateOfBirth <= new DateTime(1974, 1, 1));
//return people.AsQueryable().Where(p => p.LastName == "Anderson" && p.FirstName.Contains("f") && p.DateOfBirth >= new DateTime(1968, 1, 1) && p.DateOfBirth < new DateTime(1974, 1, 1));
//return people.AsQueryable().Where(p => p.LastName == "Anderson" && p.FirstName.Contains("f") && (p.DateOfBirth>=new DateTime(1968,1,1) && p.DateOfBirth<new DateTime(1974,1,1)));
//return people.AsQueryable().Where(p => p.LastName == "Anderson" && p.FirstName.Contains("f"));
//return people.AsQueryable().Where(p => p.FirstName.Contains("f"));
//return people.AsQueryable().Where(p => p.LastName == "Anderson");
}
private void _prepMongo()
{
_mongoDb.DropCollection(_mongoDBCollection, CancellationToken.None);
var testData = _getDemoList(_demoCount);
var people = _getPeopleCollection();
people.Indexes.CreateOne(Builders<Person>.IndexKeys.Ascending(_ => _.LastName));
people.Indexes.CreateOne(Builders<Person>.IndexKeys.Ascending(_ => _.Email));
testData.ForEachOverTpl((person) =>
{
people.InsertOneAsync(person).Wait();
});
$"Inserted {testData.Count} demo records".Dump();
}
private IList<Person> _getDemoList(int demoCount)
{
var result = Builder<Person>.CreateListOfSize(demoCount)
.All()
.With(p => p.FirstName = Faker.NameFaker.FirstName())
.With(p => p.LastName = Faker.NameFaker.LastName())
.With(p => p.Email = Faker.InternetFaker.Email())
.With(p => p.DateOfBirth = Faker.DateTimeFaker.BirthDay(21,50))
.Build();
return result;
}
private IMongoCollection<Person> _getPeopleCollection()
{
return _mongoDb.GetCollection<Person>(_mongoDBCollection);
}
private void _connectToMongoDB()
{
_mongoClient = new MongoClient(_mongoDBConnectionString);
_mongoDb = _mongoClient.GetDatabase(_mongoDBDatabase);
}
public class Person
{
[BsonId]
public string Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public DateTime DateOfBirth { get; set; }
}
public class WhereFinder : MongoDB.Driver.Linq.ExpressionVisitor
{
//private IList<MethodCallExpression> whereExpressions = new List<MethodCallExpression>();
private bool _foundWhere = false;
private bool _setWhere = false;
public string WhereClause { get; set; }
public string Parameter { get; set; }
public LamdaParts<T> GetLambdaParts<T>()
{
return new LamdaParts<T> {
ExpressionParameter=Parameter,
ExpressionString = WhereClause
};
}
public void SetWhere(Expression expression)
{
Visit(expression);
//return whereExpressions;
}
protected override Expression VisitBinary(BinaryExpression node)
{
//$"{node.Left} {_convertNodeType(node.NodeType)} {node.Right}".Dump();
if (_foundWhere && !_setWhere)
{
//node.ToString().Dump("VisitBinary");
$"{node.Left} {_convertNodeType(node.NodeType)} {node.Right}".Dump("Setting Where Clause");
WhereClause= $"{node.Left} {_convertNodeType(node.NodeType)} {node.Right}";
//WhereClause.Dump("WhereClause");
_setWhere=true;
}
return base.VisitBinary(node);
}
private string _convertNodeType(ExpressionType nodeType)
{
switch (nodeType)
{
case ExpressionType.Not:
return "!";
case ExpressionType.Add:
case ExpressionType.AddChecked:
return "+";
case ExpressionType.Subtract:
case ExpressionType.SubtractChecked:
return "-";
case ExpressionType.Multiply:
case ExpressionType.MultiplyChecked:
return "*";
case ExpressionType.Divide:
return "/";
case ExpressionType.Modulo:
return "%";
case ExpressionType.And:
return "&";
case ExpressionType.AndAlso:
return "&&";
case ExpressionType.Or:
return "|";
case ExpressionType.OrElse:
return "||";
case ExpressionType.LessThan:
return "<";
case ExpressionType.LessThanOrEqual:
return "<=";
case ExpressionType.GreaterThan:
return ">";
case ExpressionType.GreaterThanOrEqual:
return ">=";
case ExpressionType.Equal:
return "==";
case ExpressionType.NotEqual:
return "!=";
default:
throw new Exception(string.Format("Unhandled expression type: '{0}'", nodeType));
}
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (_foundWhere)
{
//node.ToString().Dump("VisitParameter");
Parameter=node.ToString();
}
return base.VisitParameter(node);
}
protected override Expression VisitMethodCall(MethodCallExpression expression)
{
if (expression.Method.Name == "Where")
{
//whereExpressions.Add(expression);
_foundWhere = true;
}
if (expression?.Arguments != null)
{
foreach (var arg in expression.Arguments)
{
Visit(arg);
}
}
return expression;
}
}
public class LamdaParts<T>
{
public Type ParamterType
{
get
{
return typeof(T);
}
}
public string ExpressionParameter { get; set; }
public string ExpressionString { get;set;}
}
public static class Extensions
{
public static void ForEachOverTpl<T>(this IEnumerable<T> enumerable, Action<T> call)
{
var cancellationTokenSource = new CancellationTokenSource();
var actionBlock = new ActionBlock<T>(call, new ExecutionDataflowBlockOptions
{
TaskScheduler = TaskScheduler.Current,
MaxDegreeOfParallelism = Environment.ProcessorCount * 2,
CancellationToken = cancellationTokenSource.Token,
});
foreach (T item in enumerable)
{
if (cancellationTokenSource.IsCancellationRequested) return;
actionBlock.Post(item);
}
actionBlock.Complete();
actionBlock.Completion.Wait(cancellationTokenSource.Token);
}
}
The solution is fairly simple once you understand the Expression tree and possible root level expression method names. Thanks to #bolanki for the assistance.
Attached is an updated LINQPad script (to test set _doPrep = true):
<Query Kind="Program">
<Reference><RuntimeDirectory>\System.Linq.dll</Reference>
<Reference><RuntimeDirectory>\System.Linq.Expressions.dll</Reference>
<Reference><RuntimeDirectory>\System.Linq.Queryable.dll</Reference>
<NuGetReference>Faker</NuGetReference>
<NuGetReference>mongocsharpdriver</NuGetReference>
<NuGetReference>MongoDB.Driver</NuGetReference>
<NuGetReference>NBuilder</NuGetReference>
<NuGetReference>Newtonsoft.Json</NuGetReference>
<NuGetReference>System.Linq.Dynamic</NuGetReference>
<Namespace>FizzWare.NBuilder</Namespace>
<Namespace>MongoDB.Bson</Namespace>
<Namespace>MongoDB.Bson.Serialization.Attributes</Namespace>
<Namespace>MongoDB.Driver</Namespace>
<Namespace>MongoDB.Driver.Builders</Namespace>
<Namespace>MongoDB.Driver.Linq</Namespace>
<Namespace>myAlias = System.Linq.Dynamic</Namespace>
<Namespace>Newtonsoft.Json</Namespace>
<Namespace>System.Linq</Namespace>
<Namespace>System.Linq.Expressions</Namespace>
<Namespace>System.Threading.Tasks</Namespace>
<Namespace>System.Threading.Tasks.Dataflow</Namespace>
</Query>
private string _mongoDBConnectionString = "mongodb://localhost";
private string _mongoDBDatabase = "LinqToQ";
private string _mongoDBCollection = "People";
private IMongoClient _mongoClient;
private IMongoDatabase _mongoDb;
private int _demoCount = 2000000;
private bool _doPrep = false;
void Main()
{
_connectToMongoDB();
// Should demo data be generated
if (_doPrep)
_prepMongo();
// Get the queryable to test with
var mongoDataQuery = _getIQueryable();
// Print the original expression
//mongoDataQuery.Expression.ToString().Dump("Original Expression");
// Evaluate the expression and try find the where expression
var whereFinder = new WhereFinder<Person>(mongoDataQuery.Expression);
// Get the MongoCollection to be Filtered and Count
var tempColl = _getPeopleCollection();
if (whereFinder.FoundWhere)
{
//whereFinder.TheWhereExpression.ToString().Dump("Calculated where expression");
var filter = new FilterDefinitionBuilder<Person>();
var stopwatch = new Stopwatch();
stopwatch.Start();
tempColl.Count(filter.Where(whereFinder.TheWhereExpression)).Dump("Dynamic where count");
var afterCalculatedWhere = stopwatch.Elapsed;
mongoDataQuery.Count().Dump("IQueryable<T> where count");
var afterIQuerableWhere = stopwatch.Elapsed;
stopwatch.Stop();
$"Calculated where:{afterCalculatedWhere:c}\nIQueryable where:{afterIQuerableWhere:c}".Dump("Where Durations");
}
else
tempColl.Count(FilterDefinition<Person>.Empty).Dump("No filter count");
"Done".Dump();
}
///////////////////////////////////////////////////////
// END SOLUTION
///////////////////////////////////////////////////////
private IMongoQueryable<Person> _getIQueryable()
{
var people = _getPeopleCollection();
//return people.AsQueryable().Where(p => p.DateOfBirth <= new DateTime(1974, 1, 1));
return people.AsQueryable().Where(p => p.LastName == "Anderson" && p.FirstName.Contains("f") && p.DateOfBirth >= new DateTime(1968, 1, 1) && p.DateOfBirth < new DateTime(1974, 1, 1));
//return people.AsQueryable().Where(p => p.LastName == "Anderson" && p.FirstName.Contains("f") && (p.DateOfBirth>=new DateTime(1968,1,1) && p.DateOfBirth<new DateTime(1974,1,1)));
//return people.AsQueryable().Where(p => p.LastName == "Anderson" && p.FirstName.Contains("f"));
//return people.AsQueryable().Where(p => p.FirstName.Contains("f"));
//return people.AsQueryable().Where(p => p.LastName == "Anderson");
}
public class Person
{
[BsonId]
public string Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public DateTime DateOfBirth { get; set; }
}
public class WhereFinder<T> : MongoDB.Driver.Linq.ExpressionVisitor
{
private bool _processingWhere = false;
private bool _processingLambda = false;
public ParameterExpression _parameterExpression { get; set; }
public WhereFinder(Expression expression)
{
Visit(expression);
}
public Expression<Func<T, bool>> TheWhereExpression { get; set; }
public bool FoundWhere
{
get { return TheWhereExpression != null; }
}
protected override Expression VisitBinary(BinaryExpression node)
{
var result = base.VisitBinary(node);
if(_processingWhere)
TheWhereExpression = (Expression<Func<T, bool>>)Expression.Lambda(node, _parameterExpression);
return result;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (_processingWhere || _processingLambda || _parameterExpression==null)
_parameterExpression = node;
return base.VisitParameter(node);
}
protected override Expression VisitMethodCall(MethodCallExpression expression)
{
string methodName = expression.Method.Name;
if (TheWhereExpression==null && ( methodName == "Where" || methodName == "Contains"))
{
_processingWhere = true;
if (expression?.Arguments != null)
foreach (var arg in expression.Arguments)
Visit(arg);
_processingWhere = false;
}
return expression;
}
protected override Expression VisitLambda(LambdaExpression exp)
{
if (_parameterExpression == null)
_parameterExpression = exp.Parameters?.FirstOrDefault();
TheWhereExpression = (Expression<Func<T, bool>>)Expression.Lambda(exp.Body, _parameterExpression);
return exp;
}
}
///////////////////////////////////////////////////////
// END SOLUTION
///////////////////////////////////////////////////////
///////////////////////////////////////////////////////
// BEGIN DEMO DATA
///////////////////////////////////////////////////////
private void _prepMongo()
{
_mongoDb.DropCollection(_mongoDBCollection, CancellationToken.None);
var testData = _getDemoList(_demoCount);
var people = _getPeopleCollection();
people.Indexes.CreateOne(Builders<Person>.IndexKeys.Ascending(_ => _.FirstName));
people.Indexes.CreateOne(Builders<Person>.IndexKeys.Ascending(_ => _.LastName));
people.Indexes.CreateOne(Builders<Person>.IndexKeys.Ascending(_ => _.Email));
people.Indexes.CreateOne(Builders<Person>.IndexKeys.Ascending(_ => _.DateOfBirth));
$"Inserting ...{testData.Count}... demo records".Dump();
Extensions.ForEachOverTpl<Person>(testData, (person) =>
{
people.InsertOneAsync(person).Wait();
});
$"Inserted {testData.Count} demo records".Dump();
}
private IList<Person> _getDemoList(int demoCount)
{
var result = Builder<Person>.CreateListOfSize(demoCount)
.All()
.With(p => p.FirstName = Faker.NameFaker.FirstName())
.With(p => p.LastName = Faker.NameFaker.LastName())
.With(p => p.Email = Faker.InternetFaker.Email())
.With(p => p.DateOfBirth = Faker.DateTimeFaker.BirthDay(21, 50))
.Build();
return result;
}
private IMongoCollection<Person> _getPeopleCollection()
{
return _mongoDb.GetCollection<Person>(_mongoDBCollection);
}
private void _connectToMongoDB()
{
_mongoClient = new MongoClient(_mongoDBConnectionString);
_mongoDb = _mongoClient.GetDatabase(_mongoDBDatabase);
}
///////////////////////////////////////////////////////
// END DEMO DATA
///////////////////////////////////////////////////////
public static class Extensions
{
public static void ForEachOverTpl<T>(this IEnumerable<T> enumerable, Action<T> call)
{
var cancellationTokenSource = new CancellationTokenSource();
var actionBlock = new ActionBlock<T>(call, new ExecutionDataflowBlockOptions
{
TaskScheduler = TaskScheduler.Current,
MaxDegreeOfParallelism = Environment.ProcessorCount * 2,
CancellationToken = cancellationTokenSource.Token,
});
foreach (T item in enumerable)
{
if (cancellationTokenSource.IsCancellationRequested) return;
actionBlock.Post(item);
}
actionBlock.Complete();
actionBlock.Completion.Wait(cancellationTokenSource.Token);
}
}
Update: -- Fix for expressions containing Take, OrderBy etc.
public class WhereFinder<T> : MongoDB.Driver.Linq.ExpressionVisitor
{
private bool _processingWhere = false;
private bool _processingLambda = false;
public ParameterExpression _parameterExpression { get; set; }
public WhereFinder(Expression expression)
{
Visit(expression);
}
public Expression<Func<T, bool>> TheWhereExpression { get; set; }
public bool FoundWhere
{
get { return TheWhereExpression != null; }
}
protected override Expression Visit(Expression exp)
{
return base.Visit(exp);
}
protected override Expression VisitBinary(BinaryExpression node)
{
var result = base.VisitBinary(node);
if (_processingWhere)
{
TheWhereExpression = (Expression<Func<T, bool>>) Expression.Lambda(node, _parameterExpression);
}
return result;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (_processingWhere || _processingLambda || _parameterExpression == null)
_parameterExpression = node;
return base.VisitParameter(node);
}
protected override Expression VisitMethodCall(MethodCallExpression expression)
{
string methodName = expression.Method.Name;
if (methodName == "Where")
_processingWhere = true;
if (expression?.Arguments != null)
foreach (var arg in expression.Arguments)
Visit(arg);
_processingWhere = false;
return expression;
}
protected override Expression VisitLambda(LambdaExpression exp)
{
if (_processingWhere)
{
if (_parameterExpression == null)
_parameterExpression = exp.Parameters?.FirstOrDefault();
TheWhereExpression = (Expression<Func<T, bool>>)Expression.Lambda(exp.Body, _parameterExpression);
}
return exp;
}
}

Deserialize list of objects with list

I have the following XML:
<?xml version="1.0" encoding="utf-8"?>
<CallEvents xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<CallEvent>
<Time>2014-02-24T06:44:27.12</Time>
<Type>Inner</Type>
<Fs>
<StrPair>
<Key>Name</Key>
<Value>Call1</Value>
</StrPair>
<StrPair>
<Key>Owner</Key>
<Value>Ali</Value>
</StrPair>
</Fs>
</CallEvent>
<CallEvent>
<Time>2014-02-24T06:44:29.089</Time>
<Type>Outer</Type>
<Fs>
<StrPair>
<Key>Name</Key>
<Value>Call2</Value>
</StrPair>
<StrPair>
<Key>Id</Key>
<Value>3242</Value>
</StrPair>
<StrPair>
<Key>Another</Key>
<Value>123</Value>
</StrPair>
</Fs>
</CallEvent>
</CallEvents>
I tried to deserialize it, but it doesn't want to deserialize list Fs. I get CallEvents with CallEvent items, and members of CallEvent filled with correct values except list Fs. The list Fs is empty. Why?
What do I do wrong?
class Program
{
static void Main(string[] args)
{
string xmlFile = "call_events.xml";
CallEvents events = CallEvents.OpenFromXmlFile(xmlFile);
Console.ReadKey();
}
}
[Serializable]
public class CallEvent
{
[XmlElement]
public DateTime Time;
[XmlElement]
public CallEventType Type;
public CallEvent()
{
this.Fields = new Dictionary<string, string>();
}
[XmlArray("Fs"), XmlArrayItem("StrPair")]
public List<StrPair> Fs
{
get
{
var list = new List<StrPair>();
foreach (var pair in Fields)
{
list.Add(new StrPair(pair.Key, pair.Value));
}
return list;
}
set
{
Fields.Clear();
foreach (var dictPair in value)
{
Fields.Add(dictPair.Key, dictPair.Value);
}
}
}
[XmlIgnore]
public Dictionary<string, string> Fields;
public void ParseFields(List<LogMessage> eventLogMessages)
{
int eventLogMessagesCount = eventLogMessages.Count;
this.Fields.Clear();
for (int i = 0; i < eventLogMessagesCount; i++)
{
LogMessage logMessage = eventLogMessages[i];
int pos = logMessage.Message.IndexOf(": ");
if(pos == -1)
continue;
string fieldName = logMessage.Message.Substring(0, pos);
pos+=2;
string fieldValue = logMessage.Message.Substring(pos);
if (this.Fields.ContainsKey(fieldName))
{
this.Fields[fieldName] += ("\r\n" + fieldValue);
}
else
{
this.Fields.Add(fieldName, fieldValue);
}
}
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.AppendFormat("{0} {1} | ", Type, Time);
foreach (var pair in Fields)
{
sb.AppendFormat("{0}: {1}, ", pair.Key, pair.Value);
}
return sb.ToString();
}
[Serializable]
public class StrPair
{
[XmlElement]
public string Key;
[XmlElement]
public string Value;
public StrPair() { }
public StrPair(string key, string value)
{
Key = key;
Value = value;
}
}
}
[XmlRoot("CallEvents")]
public class CallEvents : List<CallEvent>
{
static public CallEvents OpenFromXmlFile(string xmlFileName)
{
CallEvents callEvents;// = new CallEvents();
XmlSerializer ser = new XmlSerializer(typeof(CallEvents));
XmlReader xmlReader = new XmlTextReader(xmlFileName);
try
{
callEvents = (CallEvents)ser.Deserialize(xmlReader);
}
finally
{
xmlReader.Close();
}
return callEvents;
}
public void SaveToXmlFile(string xmlFileName)
{
XmlWriterSettings xmlWriterSettings = new XmlWriterSettings();
xmlWriterSettings.Encoding = System.Text.Encoding.UTF8;
xmlWriterSettings.Indent = true;
XmlSerializer ser = new XmlSerializer(this.GetType());
XmlWriter xmlWriter = null;
tryAgain:
try
{
xmlWriter = XmlTextWriter.Create(xmlFileName, xmlWriterSettings);
ser.Serialize(xmlWriter, this);
}
catch (Exception ex)
{
System.Windows.Forms.DialogResult dr = System.Windows.Forms.MessageBox.Show("Couldn't serialize to XML. Details: " + ex.Message, "Error", System.Windows.Forms.MessageBoxButtons.RetryCancel, System.Windows.Forms.MessageBoxIcon.Warning);
if (dr == System.Windows.Forms.DialogResult.Retry)
{
goto tryAgain;
}
}
finally
{
if (xmlWriter != null)
{
xmlWriter.Close();
}
}
}
}
I havn'e used this in a while so the code might need some touching up.
CallEvents events;
using(XmlReader reader = XmlReader.Create("call_events.xml"))
{
XmlDeserializer deSerializer = new XmlDeserializer(typeof(CallEvents));
events = (CallEvents)deSerializer.Deserialize(reader);
}

Convert custom action filter for Web API use?

I found a really nice action filter that converts a comma-separated parameter to a generic type list: http://stevescodingblog.co.uk/fun-with-action-filters/
I would like to use it but it will not work for an ApiController, it completely ignore it. Can someone help convert this for Web API use?
[AttributeUsage(AttributeTargets.Method)]
public class SplitStringAttribute : ActionFilterAttribute
{
public string Parameter { get; set; }
public string Delimiter { get; set; }
public SplitStringAttribute()
{
Delimiter = ",";
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.ActionParameters.ContainsKey(this.Parameter))
{
string value = null;
var request = filterContext.RequestContext.HttpContext.Request;
if (filterContext.RouteData.Values.ContainsKey(this.Parameter)
&& filterContext.RouteData.Values[this.Parameter] is string)
{
value = (string)filterContext.RouteData.Values[this.Parameter];
}
else if (request[this.Parameter] is string)
{
value = request[this.Parameter] as string;
}
var listArgType = GetParameterEnumerableType(filterContext);
if (listArgType != null && !string.IsNullOrWhiteSpace(value))
{
string[] values = value.Split(Delimiter.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
var listType = typeof(List<>).MakeGenericType(listArgType);
dynamic list = Activator.CreateInstance(listType);
foreach (var item in values)
{
try
{
dynamic convertedValue = TypeDescriptor.GetConverter(listArgType).ConvertFromInvariantString(item);
list.Add(convertedValue);
}
catch (Exception ex)
{
throw new ApplicationException(string.Format("Could not convert split string value to '{0}'", listArgType.FullName), ex);
}
}
filterContext.ActionParameters[this.Parameter] = list;
}
}
base.OnActionExecuting(filterContext);
}
private Type GetParameterEnumerableType(ActionExecutingContext filterContext)
{
var param = filterContext.ActionParameters[this.Parameter];
var paramType = param.GetType();
var interfaceType = paramType.GetInterface(typeof(IEnumerable<>).FullName);
Type listArgType = null;
if (interfaceType != null)
{
var genericParams = interfaceType.GetGenericArguments();
if (genericParams.Length == 1)
{
listArgType = genericParams[0];
}
}
return listArgType;
}
}
What namespace are you using for ActionFilterAttribute? For Web API you need to be using System.Web.Http.Filters namespace and not System.Web.Mvc.
EDIT
Here's a rough conversion, not fully tested.
SplitStringAttribute
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
namespace StackOverflowSplitStringAttribute.Infrastructure.Attributes
{
[AttributeUsage(AttributeTargets.Method)]
public class SplitStringAttribute : ActionFilterAttribute
{
public string Parameter { get; set; }
public string Delimiter { get; set; }
public SplitStringAttribute()
{
Delimiter = ",";
}
public override void OnActionExecuting(HttpActionContext filterContext)
{
if (filterContext.ActionArguments.ContainsKey(Parameter))
{
var qs = filterContext.Request.RequestUri.ParseQueryString();
if (qs.HasKeys())
{
var value = qs[Parameter];
var listArgType = GetParameterEnumerableType(filterContext);
if (listArgType != null && !string.IsNullOrWhiteSpace(value))
{
string[] values = value.Split(Delimiter.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
var listType = typeof(List<>).MakeGenericType(listArgType);
dynamic list = Activator.CreateInstance(listType);
foreach (var item in values)
{
try
{
dynamic convertedValue = TypeDescriptor.GetConverter(listArgType).ConvertFromInvariantString(item);
list.Add(convertedValue);
}
catch (Exception ex)
{
throw new ApplicationException(string.Format("Could not convert split string value to '{0}'", listArgType.FullName), ex);
}
}
filterContext.ActionArguments[Parameter] = list;
}
}
}
base.OnActionExecuting(filterContext);
}
private Type GetParameterEnumerableType(HttpActionContext filterContext)
{
var param = filterContext.ActionArguments[Parameter];
var paramType = param.GetType();
var interfaceType = paramType.GetInterface(typeof(IEnumerable<>).FullName);
Type listArgType = null;
if (interfaceType != null)
{
var genericParams = interfaceType.GetGenericArguments();
if (genericParams.Length == 1)
{
listArgType = genericParams[0];
}
}
return listArgType;
}
}
}
CsvController
using System.Web.Http;
using System.Collections.Generic;
using StackOverflowSplitStringAttribute.Infrastructure.Attributes;
namespace StackOverflowSplitStringAttribute.Controllers
{
public class CsvController : ApiController
{
// GET /api/values
[SplitString(Parameter = "data")]
public IEnumerable<string> Get(IEnumerable<string> data)
{
return data;
}
}
}
Example request
http://localhost:52595/api/csv?data=this,is,a,test,joe
Here is another way:
public class ConvertCommaDelimitedList<T> : CollectionModelBinder<T>
{
public override bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
var _queryName = HttpUtility.ParseQueryString(actionContext.Request.RequestUri.Query)[bindingContext.ModelName];
List<string> _model = new List<string>();
if (!String.IsNullOrEmpty(_queryName))
_model = _queryName.Split(',').ToList();
var converter = TypeDescriptor.GetConverter(typeof(T));
if (converter != null)
bindingContext.Model = _model.ConvertAll(m => (T)converter.ConvertFromString(m));
else
bindingContext.Model = _model;
return true;
}
}
And list your param in the ApiController ActionMethod:
[ModelBinder(typeof(ConvertCommaDelimitedList<decimal>))] List<decimal> StudentIds = null)
Where StudentIds is the querystring param (&StudentIds=1,2,4)

How do I add a attribute to a XmlArray element (XML Serialization)?

How do I add a attribute to a XmlArray element ( not to XmlArrayItem ) while serializing the object?
XmlArray is used to tell the xmlserializer to treat the property as array and serialize it according its parameters for the element names.
[XmlArray("FullNames")]
[XmlArrayItem("Name")]
public string[] Names{get;set;}
will give you
<FullNames>
<Name>Michael Jackson</Name>
<Name>Paris Hilton</Name>
</FullNames>
In order to add an xml attribute to FullNames element, you need declare a class for it.
[XmlType("FullNames")]
public class Names
{
[XmlAttribute("total")]
public int Total {get;set;}
[XmlElement("Name")]
public string[] Names{get;set;}
}
This will give you
<FullNames total="2">
<Name>Michael Jackson</Name>
<Name>Paris Hilton</Name>
</FullNames>
This can be done by deriving from IXmlSerializable. I have attached a sample with a base class which does the job:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
namespace XmlSerializerApp {
class Program {
static void Main() {
using (var ms = new MemoryStream()) {
var serializer = new XmlSerializer(typeof(RootObject));
serializer.Serialize(ms, new RootObject());
ms.Position = 0;
var formatted =
new XmlDocument {
XmlResolver = null,
PreserveWhitespace = false
};
formatted.Load(ms);
var stringWriter = new StringWriter();
var xmlTextWriter =
new XmlTextWriter(stringWriter) {Formatting = Formatting.Indented};
formatted.WriteTo(xmlTextWriter);
Console.WriteLine(stringWriter.ToString());
ms.Position = 0;
var rootObj = serializer.Deserialize(ms) as RootObject;
if (rootObj?.Children != null) {
Console.WriteLine($"Whatever: {rootObj?.Children?.Whatever}");
foreach (var child in rootObj.Children) {
if (child == null) {
continue;
}
Console.WriteLine($" {child.Name}={child.Value}");
}
}
}
}
}
[XmlRoot(ElementName = "root")]
public class RootObject{
[XmlAttribute(AttributeName = "version")]
public string Version {get; set;} = "1.0.0";
[XmlElement(ElementName = "children")]
public ListOfChildren Children {get; set;} = new ListOfChildren {
new Child{ Name = "one", Value = "firstValue"}
};
}
[XmlRoot(ElementName = "add")]
public class Child {
[XmlAttribute(AttributeName = "name")]
public string Name { get; set; }
[XmlAttribute(AttributeName = "value")]
public string Value { get; set; }
}
public class ListOfChildren : ListBase<Child> {
[XmlAttribute(AttributeName = "whatever")]
public bool Whatever { get; set; } = true;
}
public class ListBase<T>
: List<T>
, IXmlSerializable
{
private static readonly Type _s_type = typeof(T);
// ReSharper disable once StaticMemberInGenericType
private static readonly XmlAttributeOverrides _s_overrides =
new Func<XmlAttributeOverrides>(
() => {
var overrides = new XmlAttributeOverrides();
overrides.Add(_s_type, new XmlAttributes{ XmlRoot = new XmlRootAttribute("add")});
return overrides;
})();
// ReSharper disable once StaticMemberInGenericType
private static readonly XmlSerializer _s_serializer = new XmlSerializer(_s_type, _s_overrides);
/// <inheritdoc />
public XmlSchema GetSchema() { throw new NotImplementedException(); }
/// <inheritdoc />
public void ReadXml(XmlReader reader) {
var localName = reader.LocalName;
var prefix = reader.Prefix;
var namespaceUri = reader.NamespaceURI;
var depth = reader.Depth;
var attributes =
GetAttributes()?.ToArray()
?? Array.Empty<KeyValuePair<PropertyInfo, XmlAttributeAttribute>>();
while (reader.MoveToNextAttribute()) {
var attribute =
attributes
.Where(
a =>
string.Equals(
a.Value?.AttributeName,
reader.LocalName,
StringComparison.Ordinal)
&& string.Equals(
a.Value?.Namespace ?? string.Empty,
reader.NamespaceURI,
StringComparison.Ordinal)
)
.Select(x => x.Key)
.FirstOrDefault();
if (attribute != null) {
var attributeValue = reader.Value;
if (attribute.PropertyType == typeof(string)) {
attribute.SetValue(attributeValue, null);
}
else if (attribute.PropertyType == typeof(bool)) {
if ("1".Equals(attributeValue, StringComparison.Ordinal)
|| "-1".Equals(attributeValue, StringComparison.Ordinal)
|| "TRUE".Equals(attributeValue, StringComparison.OrdinalIgnoreCase)) {
attribute.SetValue(this, true);
}
}
else if (attribute.PropertyType == typeof(short)
&& short.TryParse(attributeValue, out var shortValue)) {
attribute.SetValue(this, shortValue);
}
else if (attribute.PropertyType == typeof(int)
&& int.TryParse(attributeValue, out var intValue)) {
attribute.SetValue(this, intValue);
}
else if (attribute.PropertyType == typeof(long)
&& long.TryParse(attributeValue, out var longValue)) {
attribute.SetValue(this, longValue);
}
else if (attribute.PropertyType == typeof(decimal)
&& decimal.TryParse(attributeValue, out var decimalValue)) {
attribute.SetValue(this, decimalValue);
}
else if (attribute.PropertyType == typeof(float)
&& float.TryParse(attributeValue, out var floatValue)) {
attribute.SetValue(this, floatValue);
}
else if (attribute.PropertyType == typeof(double)
&& double.TryParse(attributeValue, out var doubleValue)) {
attribute.SetValue(this, doubleValue);
}
else if (attribute.PropertyType == typeof(Guid)
&& Guid.TryParse(attributeValue, out var guidValue)) {
attribute.SetValue(this, guidValue);
}
else if (attribute.PropertyType == typeof(Version)
&& Version.TryParse(attributeValue, out var versionValue)) {
attribute.SetValue(this, versionValue);
}
else if (attribute.PropertyType == typeof(Uri)
&& Uri.TryCreate(
attributeValue,
UriKind.RelativeOrAbsolute,
out var uriValue)) {
attribute.SetValue(this, uriValue);
}
}
}
Clear();
while (reader.Read()) {
if (reader.NodeType != XmlNodeType.Element) {
if (reader.NodeType == XmlNodeType.EndElement
&& prefix.Equals(reader.Prefix, StringComparison.Ordinal)
&& localName.Equals(reader.LocalName, StringComparison.Ordinal)
&& namespaceUri.Equals(reader.NamespaceURI, StringComparison.Ordinal)
&& depth == reader.Depth
) {
break;
}
continue;
}
var x = reader.ReadSubtree();
var item = (T)_s_serializer?.Deserialize(x);
Add(item);
}
}
/// <inheritdoc />
public void WriteXml(XmlWriter writer) {
var enumerable = GetAttributes();
if (enumerable != null) {
foreach (var attribute in enumerable) {
if (attribute.Key == null || attribute.Value?.AttributeName == null) {
continue;
}
var value = attribute.Key.GetValue(this, null);
if (value is bool b) {
value = b
? "true"
: "false";
}
if (value != null) {
writer.WriteAttributeString(attribute.Value.AttributeName,
attribute.Value.Namespace,
value.ToString() ?? string.Empty
);
}
}
}
foreach (var item in this) {
if (item == null) {
continue;
}
_s_serializer?.Serialize(writer, item);
}
}
private IEnumerable<KeyValuePair<PropertyInfo, XmlAttributeAttribute>> GetAttributes() {
return GetType()
.GetProperties()
.Select(
p =>
new KeyValuePair<PropertyInfo, XmlAttributeAttribute>(
p,
p.GetCustomAttributes(
typeof(XmlAttributeAttribute),
true)
.Cast<XmlAttributeAttribute>()
.FirstOrDefault())
)
.Where(x => x.Value != null);
}
}
}

Categories