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
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).
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;
}
}
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);
}
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 ( 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);
}
}
}