Multi property checking with single rule - c#

How could I make following concise, with an extension method etc.? Also could that InlineValidator consolidated into the MainPropertyValidator class?
public class PropertyDTOValidator : AbstractValidator<PropertyDTO>
{
public PropertyDTOValidator()
{
RuleFor(p => p).SetValidator(new InlineValidator<PropertyDTO>
{
validator =>
{
return validator.RuleFor(p => p.MainProperty)
.SetValidator(new MainPropertyValidator());
}
}).When(p =>
{
var checkMainProperty = p.MainProperty.Id != -1;
if (!checkMainProperty)
{
// Some actions...
}
return checkMainProperty;
});
}
class MainPropertyValidator : AbstractValidator<PropertyDTO>
{
public MainPropertyValidator()
{
RuleFor(p => p.Id).Must(id =>
{
return id >= 1;
}).WithMessage("MainProperty Id value is not valid...");
}
}
}
Edit
I have following extension but i want to consolidate my validator into a single class like InlineValidator. I checked the code of InlineValidator but i could not consolidate InlineValidator and MainPropertyValidator.
public static class Extensions
{
public static IRuleBuilderOptions<T, TProperty1> SetValidator<T, TProperty1, TProperty2>(this IRuleBuilderInitial<T, TProperty1> ruleBuilder,
Expression<Func<T, TProperty2>> exp, IValidator<TProperty2> validator)
{
return ruleBuilder.SetValidator(new InlineValidator<T>
{
v => v.RuleFor(exp).SetValidator(validator)
} as IValidator<TProperty1>);
}
}
RuleFor(p => p).SetValidator(p => p.MainProperty, new MainPropertyValidator()).When(p =>{});

Alternatively, following extensions could be used;
public static class Extensions
{
public static IRuleBuilderInitial<T, TProperty> When<T, TProperty>(this IRuleBuilderInitial<T, TProperty> rule, Func<T, bool> predicate, ApplyConditionTo applyConditionTo = ApplyConditionTo.AllValidators)
{
return rule.Configure(config =>
{
PropertyRule propertyRule = config;
int num = (int)applyConditionTo;
propertyRule.ApplyCondition(ctx => predicate((T)ctx.InstanceToValidate), (ApplyConditionTo)num);
});
}
public static IRuleBuilderInitial<T, TProperty> Custom<T, TProperty, TProperty2>(this IRuleBuilder<T, TProperty> ruleBuilder, Expression<Func<TProperty, TProperty2>> expression, AbstractValidator<TProperty2> validator)
{
var propChain = PropertyChain.FromExpression(expression);
var propName = propChain.ToString();
var prop = expression.Compile();
Func<string, string, string> joinStr = (s1, s2) => string.Join(".", new[] { s1, s2 }.Where(s => !string.IsNullOrEmpty(s)));
return ruleBuilder.Custom((p, context) =>
{
var val = prop(p);
var validationResult = validator.Validate(val);
propName = joinStr(context.PropertyName, propName);
foreach (var failure in validationResult.Errors)
{
failure.PropertyName = joinStr(propName, failure.PropertyName);
context.AddFailure(failure);
}
});
}
}
RuleFor(p => p).Custom(p => p.MainProperty, new MainPropertyValidator()).When(p =>
{
var checkMainProperty = p.MainProperty.Id != -1;
if (!checkMainProperty)
{
// Some actions...
}
return checkMainProperty;
});

Related

EF6 Mocking derived DbSets

I am trying to apply the new mocking of EF6 to my existing code.
I have a class that Extends DbSet. One of the methods call the base class (BdSet) Create method. Here is the a sample code (not the complete solution or real names):
public class DerivedDbSet<TEntity> : DbSet<TEntity>, IKeyValueDbSet<TEntity>, IOrderedQueryable<TEntity> where TEntity : class
{
public virtual bool Add(string value1, string value2) {
var entity = Create(); // There is no direct implementation of the Create method it is calling the base method
// Do something with the values
this.Add(entity);
return true;
}
}
I am mocking using the Test Doubles sample (here is the peace of code):
var data = new List<DummyEntity> {
new DummyEntity { Value1 = "First", Value2 = "001" },
new DummyEntity { Value1 = "Second", Value2 = "002" }
}.AsQueryable();
var mock = new Mock<DerivedDbSet<DummyEntity>>();
mock.CallBase = true;
mock.As<IQueryable<T>>().Setup(m => m.Provider).Returns(source.Provider);
mock.As<IQueryable<T>>().Setup(m => m.Expression).Returns(source.Expression);
mock.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(source.ElementType);
mock.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(source.GetEnumerator());
I've set the CallBase property to true to try to force the call to the base class...
But I keep receiving the following error:
System.NotImplementedException: The member 'Create' has not been implemented on type 'DerivedDbSet1Proxy' which inherits from 'DbSet1'. Test doubles for 'DbSet`1' must provide implementations of methods and properties that are used.
I want the call of create to fallback to the default implementation in DbSet.
Can someone help me with that?
After some struggle with the internal functions and async references of mocking a DbSet I came out with a helper class that solved most of my problems and might serve as base to someone implementation.
Here is the code:
public static class MockHelper
{
internal class TestDbAsyncQueryProvider<TEntity> : IDbAsyncQueryProvider {
private readonly IQueryProvider _inner;
internal TestDbAsyncQueryProvider(IQueryProvider inner) { _inner = inner; }
public IQueryable CreateQuery(Expression expression) { return new TestDbAsyncEnumerable<TEntity>(expression); }
public IQueryable<TElement> CreateQuery<TElement>(Expression expression) { return new TestDbAsyncEnumerable<TElement>(expression); }
public object Execute(Expression expression) { return _inner.Execute(expression); }
public TResult Execute<TResult>(Expression expression) { return _inner.Execute<TResult>(expression); }
public Task<object> ExecuteAsync(Expression expression, CancellationToken cancellationToken) { return Task.FromResult(Execute(expression)); }
public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken) { return Task.FromResult(Execute<TResult>(expression)); }
}
internal class TestDbAsyncEnumerable<T> : EnumerableQuery<T>, IDbAsyncEnumerable<T> {
public TestDbAsyncEnumerable(IEnumerable<T> enumerable) : base(enumerable) { }
public TestDbAsyncEnumerable(Expression expression) : base(expression) { }
public IDbAsyncEnumerator<T> GetAsyncEnumerator() { return new TestDbAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator()); }
IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator() { return GetAsyncEnumerator(); }
public IQueryProvider Provider { get { return new TestDbAsyncQueryProvider<T>(this); } }
}
internal class TestDbAsyncEnumerator<T> : IDbAsyncEnumerator<T> {
private readonly IEnumerator<T> _inner;
public TestDbAsyncEnumerator(IEnumerator<T> inner) { _inner = inner; }
public void Dispose() { _inner.Dispose(); }
public Task<bool> MoveNextAsync(CancellationToken cancellationToken) { return Task.FromResult(_inner.MoveNext()); }
public T Current { get { return _inner.Current; } }
object IDbAsyncEnumerator.Current { get { return Current; } }
}
public static Mock<TDbSet> CreateDbSet<TDbSet, TEntity>(IList<TEntity> data, Func<object[], TEntity> find = null)
where TDbSet : class, IDbSet<TEntity>
where TEntity : class, new() {
var source = data.AsQueryable();
var mock = new Mock<TDbSet> { CallBase = true };
mock.As<IQueryable<TEntity>>().Setup(m => m.Expression).Returns(source.Expression);
mock.As<IQueryable<TEntity>>().Setup(m => m.ElementType).Returns(source.ElementType);
mock.As<IQueryable<TEntity>>().Setup(m => m.GetEnumerator()).Returns(source.GetEnumerator());
mock.As<IQueryable<TEntity>>().Setup(m => m.Provider).Returns(new TestDbAsyncQueryProvider<TEntity>(source.Provider));
mock.As<IDbAsyncEnumerable<TEntity>>().Setup(m => m.GetAsyncEnumerator()).Returns(new TestDbAsyncEnumerator<TEntity>(data.GetEnumerator()));
mock.As<IDbSet<TEntity>>().Setup(m => m.Create()).Returns(new TEntity());
mock.As<IDbSet<TEntity>>().Setup(m => m.Add(It.IsAny<TEntity>())).Returns<TEntity>(i => { data.Add(i); return i; });
mock.As<IDbSet<TEntity>>().Setup(m => m.Remove(It.IsAny<TEntity>())).Returns<TEntity>(i => { data.Remove(i); return i; });
if (find != null) mock.As<IDbSet<TEntity>>().Setup(m => m.Find(It.IsAny<object[]>())).Returns(find);
return mock;
}
public static Mock<DbSet<TEntity>> CreateDbSet<TEntity>(IList<TEntity> data, Func<object[], TEntity> find = null)
where TEntity : class, new() {
return CreateDbSet<DbSet<TEntity>, TEntity>(data, find);
}
}
And here is a use sample (based on the names I've given before):
var data = new List<DummyEntity> {
new DummyEntity { Value1 = "First", Value2 = "001" } },
new DummyEntity { Value1 = "Second", Value2 = "002" } }
};
var mockDummyEntities = MockHelper.CreateDbSet<DerivedDbSet<DummyEntities>, DummyEntities>(data, i => data.FirstOrDefault(k => k.Value2 == (string)i[0]));
var mockContext = new Mock<DummyDbContext>();
mockContext.Setup(c => c.DummyEntities).Returns(mockDummyEntities.Object);
Any suggestions on how to improve this solution is very welcome.
Regards
This is a modified version based on Andre that worked for me. Note, that I did not need the async references. Code will add all derived classes (if any)
Usage:
/// <summary>
///
/// </summary>
[TestMethod]
public void SomeTest()
{
//Setup
var mockContext = new Mock<FakeDbContext>();
//SomeClass can be abstract or concrete
mockContext.createFakeDBSet<SomeClass>();
var db = mockContext.Object;
//Setup create(s) if needed on concrete classes
//Mock.Get(db.Set<SomeOtherClass>()).Setup(x => x.Create()).Returns(new SomeOtherClass());
//DO Stuff
var list1 = db.Set<SomeClass>().ToList();
//SomeOtherClass derived from SomeClass
var subList1 = db.Set<SomeOtherClass>().ToList();
CollectionAssert.AreEquivalent(list1.OfType<SomeOtherClass>.ToList(), subList1);
}
Code:
/// <summary>
/// http://stackoverflow.com/questions/21943328/ef6-mocking-derived-dbsets
/// </summary>
public static class MoqSetupExtensions
{
static IEnumerable<Type> domainTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(x => x.GetTypes());
public static Mock<DbSet<T>> createFakeDBSet<T>(this Mock<FakeDbContext> db, List<T> list = null, Func<List<T>, object[], T> find = null, bool createDerivedSets = true) where T : class
{
list = list ?? new List<T>();
var data = list.AsQueryable();
//var mockSet = MockHelper.CreateDbSet(list, find);
var mockSet = new Mock<DbSet<T>>() { CallBase = true };
mockSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(() => { return data.Provider; });
mockSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(() => { return data.Expression; });
mockSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(() => { return data.ElementType; });
mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(() => { return list.GetEnumerator(); });
mockSet.Setup(m => m.Add(It.IsAny<T>())).Returns<T>(i => { list.Add(i); return i; });
mockSet.Setup(m => m.AddRange(It.IsAny<IEnumerable<T>>())).Returns<IEnumerable<T>>((i) => { list.AddRange(i); return i; });
mockSet.Setup(m => m.Remove(It.IsAny<T>())).Returns<T>(i => { list.Remove(i); return i; });
if (find != null) mockSet.As<IDbSet<T>>().Setup(m => m.Find(It.IsAny<object[]>())).Returns<object[]>((i) => { return find(list, i); });
//mockSet.Setup(m => m.Create()).Returns(new T());
db.Setup(x => x.Set<T>()).Returns(mockSet.Object);
//Setup all derived classes
if (createDerivedSets)
{
var type = typeof(T);
var concreteTypes = domainTypes.Where(x => type.IsAssignableFrom(x) && type != x).ToList();
var method = typeof(MoqSetupExtensions).GetMethod("createFakeDBSetSubType");
foreach (var item in concreteTypes)
{
var invokeResult = method.MakeGenericMethod(type, item)
.Invoke(null, new object[] { db, mockSet });
}
}
return mockSet;
}
public static Mock<DbSet<SubType>> createFakeDBSetSubType<BaseType, SubType>(this Mock<FakeDbContext> db, Mock<DbSet<BaseType>> baseSet)
where BaseType : class
where SubType : class, BaseType
{
var dbSet = db.Object.Set<BaseType>();
var mockSet = new Mock<DbSet<SubType>>() { CallBase = true };
mockSet.As<IQueryable<SubType>>().Setup(m => m.Provider).Returns(() => { return dbSet.OfType<SubType>().Provider; });
mockSet.As<IQueryable<SubType>>().Setup(m => m.Expression).Returns(() => { return dbSet.OfType<SubType>().Expression; });
mockSet.As<IQueryable<SubType>>().Setup(m => m.ElementType).Returns(() => { return dbSet.OfType<SubType>().ElementType; });
mockSet.As<IQueryable<SubType>>().Setup(m => m.GetEnumerator()).Returns(() => { return dbSet.OfType<SubType>().GetEnumerator(); });
mockSet.Setup(m => m.Add(It.IsAny<SubType>())).Returns<SubType>(i => { dbSet.Add(i); return i; });
mockSet.Setup(m => m.AddRange(It.IsAny<IEnumerable<SubType>>())).Returns<IEnumerable<SubType>>((i) => { dbSet.AddRange(i); return i; });
mockSet.Setup(m => m.Remove(It.IsAny<SubType>())).Returns<SubType>(i => { dbSet.Remove(i); return i; });
mockSet.As<IDbSet<SubType>>().Setup(m => m.Find(It.IsAny<object[]>())).Returns<object[]>((i) => { return dbSet.Find(i) as SubType; });
baseSet.Setup(m => m.Create<SubType>()).Returns(() => { return mockSet.Object.Create(); });
db.Setup(x => x.Set<SubType>()).Returns(mockSet.Object);
return mockSet;
}
}
Microsoft published a very well explained guide providing an in-memory implementation of DbSet which has a complete implemention for all the methods on DbSet.
Check the article at http://msdn.microsoft.com/en-us/data/dn314431.aspx#doubles

ViewmodelBuilders for viewmodels with properties to other viewmodels

I'm using a ViewModelBuilder-class to map my models to ViewModels.
Problem is, when I try to build viewmodels for the Projects property I use a viewmodelbuilder for that too and THAT viewmodelbuilder's model has a reference back to Account which it will also try to map to a viewmodel. Result is stackoverflowexeption.
How to solve this?
I thought of sending a bool to the mapping functions, but then I will lose mapping of some nested properties that i do want to map.
Very thankful for any advice I could get!
public class AccountViewModelBuilder : IViewModelBuilder<AccountViewModel, Account>
{
protected AllViewModelBuilders _allViewModelBuilders { get; private set; }
public AccountViewModelBuilder(AllViewModelBuilders allViewModelBuilders)
{
_allViewModelBuilders = allViewModelBuilders;
}
public AccountViewModel BuildFromEntity(Account entity)
{
return new AccountViewModel()
{
ID = entity.ID,
Name = entity.Name,
//Projects = _allViewModelBuilders.ProjectViewModelBuilder.BuildFromEntity(entity.Projects).ToList()
};
}
public Account BuildFromViewModel(AccountViewModel viewModel, Account entity)
{
if (entity == null) return null;
entity.Name = viewModel.Name;
//entity.Projects = _allViewModelBuilders.ProjectViewModelBuilder.BuildFromViewModel(viewModel.Projects).ToList();
return entity;
}
public IEnumerable<AccountViewModel> BuildFromEntity(ICollection<Account> entities)
{
foreach (var entity in entities)
yield return BuildFromEntity(entity);
}
public IEnumerable<Account> BuildFromViewModel(ICollection<AccountViewModel> viewModels)
{
throw new NotImplementedException();
}
}
Edit
So i decided to go with AutoMapper, with some extensions...
public static class ConditionExtensions
{
public static void IgnoreIfSourceIsNull<T>(this IMemberConfigurationExpression<T> expression)
{
expression.Condition(IgnoreIfSourceIsNull);
}
static bool IgnoreIfSourceIsNull(ResolutionContext context)
{
if (!context.IsSourceValueNull)
{
return true;
}
var result = context.GetContextPropertyMap().ResolveValue(context.Parent);
return result.Value != null;
}
}
public static class AutoMapperConfig
{
public static IMappingExpression<TSource, TDestination> IgnoreAllNonExisting<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
{
var sourceType = typeof(TSource);
var destinationType = typeof(TDestination);
var existingMaps = Mapper.GetAllTypeMaps().First(x => x.SourceType.Equals(sourceType)
&& x.DestinationType.Equals(destinationType));
foreach (var property in existingMaps.GetUnmappedPropertyNames())
{
expression.ForMember(property, opt => opt.Ignore());
}
return expression;
}
public static void Configure()
{
Mapper.CreateMap<Project, ProjectViewModel>().IgnoreAllNonExisting();
Mapper.CreateMap<ProjectViewModel, Project>().IgnoreAllNonExisting().ForAllMembers(c => c.IgnoreIfSourceIsNull());
Mapper.CreateMap<LogbookUser, RegisterViewModel>().IgnoreAllNonExisting();
Mapper.CreateMap<RegisterViewModel, LogbookUser>().IgnoreAllNonExisting().ForAllMembers(c => c.IgnoreIfSourceIsNull());
Mapper.CreateMap<Account, AccountViewModel>().IgnoreAllNonExisting();
Mapper.CreateMap<AccountViewModel, Account>().IgnoreAllNonExisting().ForAllMembers(c => c.IgnoreIfSourceIsNull());
Mapper.CreateMap<FileGroup, FileGroupViewModel>().IgnoreAllNonExisting();
Mapper.CreateMap<FileGroupViewModel, FileGroup>().IgnoreAllNonExisting().ForAllMembers(c => c.IgnoreIfSourceIsNull());
Mapper.CreateMap<AttachedFile, AttachedFileViewModel>().IgnoreAllNonExisting();
Mapper.CreateMap<AttachedFileViewModel, AttachedFile>().IgnoreAllNonExisting().ForAllMembers(c => c.IgnoreIfSourceIsNull());
Mapper.CreateMap<Customer, CustomerViewModel>().IgnoreAllNonExisting();
Mapper.CreateMap<CustomerViewModel, Customer>().IgnoreAllNonExisting().ForAllMembers(c => c.IgnoreIfSourceIsNull());
Mapper.CreateMap<WorkOrder, WorkOrderViewModel>().IgnoreAllNonExisting();
Mapper.CreateMap<WorkOrderViewModel, WorkOrder>().IgnoreAllNonExisting().ForAllMembers(c => c.IgnoreIfSourceIsNull());
}
}

switching on type in c# and VB - is this approach stupid?

I have inherited a codebase which contains a lot of upcasting.
I've got tired of all of the switch statements on types with ad-hoc casts inside the code.
I wrote a couple of functions for switching on the type of a variable and getting access to to that variable appropriately cast in the corresponding "case" statement.
As I am relatively new to dot net I thought that perhaps I was coming at it from completely the wrong angle.
If I'm not perhaps this will be useful to someone else.
NB c# specific answers are less useful as the code-base is mostly Visual Basic. I have posted c# code because the c# community is much larger here on stackexchange.
This is an example of the usage:
class Program
{
static void Main(string[] args)
{
List<object> bobbies = new List<object>();
bobbies.Add(new Hashtable());
bobbies.Add(string.Empty);
bobbies.Add(new List<string>());
bobbies.Add(108);
bobbies.Add(10);
bobbies.Add(typeof(string));
bobbies.Add(typeof(string));
bool b = true;
// as an expression
foreach (var bob in bobbies)
Console.WriteLine(
TypeSwitch.on<String>(bob)
.inCase<Hashtable>(x =>
"gotta HASHTABLE")
.inCase<string>(x =>
"its a string " + x)
.inCase<IEnumerable<Object>>(x =>
"I got " + x.Count<Object>().ToString() + " elements")
.inCase<int>(x => (x > 10), x =>
"additional conditions")
.inCase(b, x => {
b = false;
return "non lazy conditions"; })
.otherwise(p =>
"default case"));
// as a statement
foreach (var bob in bobbies)
TypeSwitch.on(bob)
.inCase<Hashtable>(x => Console.WriteLine("one"))
.inCase<String>(x => Console.WriteLine("two"))
.inCase<int>(x => Console.WriteLine("three"))
.otherwise(x => Console.WriteLine("I give up"));
Console.ReadLine();
}
}
and here is the implementation
public static class TypeSwitch
{
public class TypeSwitcher
{
private object _thing;
public TypeSwitcher(object thang) { _thing = thang; }
public TypeSwitcher inCase<TryType>(Func<TryType, bool> guard, Action<TryType> action) {
if (_thing is TryType) {
var t = (TryType)_thing;
if (guard(t)) {
_thing = null;
action(t); } }
return this; }
public TypeSwitcher inCase<TryType>(bool condition, Action<TryType> action) { return inCase<TryType>(p => condition, action); }
public TypeSwitcher inCase<TryType>(Action<TryType> action) { return inCase<TryType>(true, action); }
public TypeSwitcher inCase(bool cond, Action<object> action) { return inCase<object>(cond, action); }
public void otherwise(Action<object> action) { this.inCase<object>(action); }
}
// for case statements with a return value:
public class TypeSwitcherExpression<ResultType>
{
private object _thing;
private ResultType _result;
public ResultType Result { get { return _result; } }
public TypeSwitcherExpression(object thang) { _thing = thang; }
public TypeSwitcherExpression<ResultType> inCase<TryType>(Func<TryType, bool> guard, Func<TryType, ResultType> action) {
if (_thing is TryType) {
var t = (TryType)_thing;
if (guard(t)) {
_thing = null;
_result = action(t); } }
return this; }
public TypeSwitcherExpression<ResultType> inCase<TryType>(bool condition, Func<TryType, ResultType> action) { return inCase<TryType>(p => condition, action); }
public TypeSwitcherExpression<ResultType> inCase<TryType>(Func<TryType, ResultType> action) { return inCase<TryType>(true, action); }
public TypeSwitcherExpression<ResultType> inCase(bool cond, Func<object, ResultType> action) { return inCase<object>(cond, action); }
public ResultType otherwise(Func<object, ResultType> action) { this.inCase<object>(action); return Result; }
}
static public TypeSwitcher on(object thing)
{ return new TypeSwitcher(thing); }
static public TypeSwitcherExpression<ResultType> on<ResultType>(object thing)
{ return new TypeSwitcherExpression<ResultType>(thing); }
public static TypeSwitcher switchOnType(this Object thing)
{ return new TypeSwitcher(thing); }
public static TypeSwitcherExpression<ResultType> switchOnType<ResultType>(this Object thing)
{ return new TypeSwitcherExpression<ResultType>(thing); }
}
Edit 1:
Replaced delegates with Action and Func. Added extension method in case you like that sort of thing.
Edit 2:
Use Is to check type of object
Here is my suggestion
namespace xx{
public static class TypeSwitcher
{
public static dynamic inCase<T>(this object item,Func<dynamic> function)
{
if (item.GetType() == typeof(T))
return function();
else
return "";
}
}
}
using xx;
static void Main(string[] args)
{
List<object> bobbies = new List<object>();
bobbies.Add(new Hashtable());
bobbies.Add(string.Empty);
bobbies.Add(new List<string>());
bobbies.Add(108);
bobbies.Add(10);
bobbies.Add(typeof(string));
bobbies.Add(typeof(string));
foreach (var item in bobbies)
{
Console.WriteLine(item.inCase<Hashtable>(() => "one"));
Console.WriteLine(item.inCase<String>(() => "two"));
Console.WriteLine(item.inCase<int>(() => "three"));
}
Console.ReadLine();
}

How-to generate querystring from model with asp.net mvc framework

I've a model, with some nested properties, lists ... and i want to get a querystring parameters from that model.
Is there any class/helper in asp.net mvc framework to do this ?
I know that with model binder we can bind a model from a querystring, but i want to do the inverse.
Thanks.
I'm fairly certain there is no "serialize to query string" functionality in the framework, mostly because I don't think there's a standard way to represent nested values and nested collections in a query string.
I thought this would be pretty easy to do using the ModelMetadata infrastructure, but it turns out that there are some complications around getting the items from a collection-valued property using ModelMetadata. I've hacked together an extension method that works around that and built a ToQueryString extension you can call from any ModelMetadata object you have.
public static string ToQueryString(this ModelMetadata modelMetadata)
{
if(modelMetadata.Model == null)
return string.Empty;
var parameters = modelMetadata.Properties.SelectMany (mm => mm.SelectPropertiesAsQueryStringParameters(null));
var qs = string.Join("&",parameters);
return "?" + qs;
}
private static IEnumerable<string> SelectPropertiesAsQueryStringParameters(this ModelMetadata modelMetadata, string prefix)
{
if(modelMetadata.Model == null)
yield break;
if(modelMetadata.IsComplexType)
{
IEnumerable<string> parameters;
if(typeof(IEnumerable).IsAssignableFrom(modelMetadata.ModelType))
{
parameters = modelMetadata.GetItemMetadata()
.Select ((mm,i) => new {
mm,
prefix = string.Format("{0}{1}[{2}]", prefix, modelMetadata.PropertyName, i)
})
.SelectMany (prefixed =>
prefixed.mm.SelectPropertiesAsQueryStringParameters(prefixed.prefix)
);
}
else
{
parameters = modelMetadata.Properties
.SelectMany (mm => mm.SelectPropertiesAsQueryStringParameters(string.Format("{0}{1}", prefix, modelMetadata.PropertyName)));
}
foreach (var parameter in parameters)
{
yield return parameter;
}
}
else
{
yield return string.Format("{0}{1}{2}={3}",
prefix,
prefix != null && modelMetadata.PropertyName != null ? "." : string.Empty,
modelMetadata.PropertyName,
modelMetadata.Model);
}
}
// Returns the metadata for each item from a ModelMetadata.Model which is IEnumerable
private static IEnumerable<ModelMetadata> GetItemMetadata(this ModelMetadata modelMetadata)
{
if(modelMetadata.Model == null)
yield break;
var genericType = modelMetadata.ModelType
.GetInterfaces()
.FirstOrDefault (x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEnumerable<>));
if(genericType == null)
yield return modelMetadata;
var itemType = genericType.GetGenericArguments()[0];
foreach (object item in ((IEnumerable)modelMetadata.Model))
{
yield return ModelMetadataProviders.Current.GetMetadataForType(() => item, itemType);
}
}
Example usage:
var vd = new ViewDataDictionary<Model>(model); // in a Controller, ViewData.ModelMetadata
var queryString = vd.ModelMetadata.ToQueryString();
I haven't tested it very thoroughly, so there may be some null ref errors lurking in it, but it spits out the correct query string for the complex objects I've tried.
#Steve's code had some minor bug when extra nesting and enumerables were the case.
Sample Model
public class BarClass {
public String prop { get; set; }
}
public class FooClass {
public List<BarClass> bar { get; set; }
}
public class Model {
public FooClass foo { get; set; }
}
Test Code
var model = new Model {
foo = new FooClass {
bar = new List<BarClass> {
new BarClass { prop = "value1" },
new BarClass { prop = "value2" }
}
}
};
var queryString = new ViewDataDictionary<Model>(model).ModelMetadata.ToQueryString();
The value of queryString should be:
"?foo.bar[0].prop=value1&foo.bar[1].prop=value2"
But #Steve's code produces the following output:
"?foobar[0].prop=value1&foobar[1].prop=value2"
Updated Code
Here is a slightly modified version of the #Steve's solution:
public static class QueryStringExtensions {
#region inner types
private struct PrefixedModelMetadata {
public readonly String Prefix;
public readonly ModelMetadata ModelMetadata;
public PrefixedModelMetadata (String prefix, ModelMetadata modelMetadata) {
Prefix = prefix;
ModelMetadata = modelMetadata;
}
}
#endregion
#region fields
private static readonly Type IEnumerableType = typeof(IEnumerable),
IEnumerableGenericType = typeof(IEnumerable<>);
#endregion
#region methods
public static String ToQueryString<ModelType> (this ModelType model) {
return new ViewDataDictionary<ModelType>(model).ModelMetadata.ToQueryString();
}
public static String ToQueryString (this ModelMetadata modelMetadata) {
if (modelMetadata.Model == null) {
return String.Empty;
}
var keyValuePairs = modelMetadata.Properties.SelectMany(mm =>
mm.SelectPropertiesAsQueryStringParameters(new List<String>())
);
return String.Join("&", keyValuePairs.Select(kvp => String.Format("{0}={1}", kvp.Key, kvp.Value)));
}
private static IEnumerable<KeyValuePair<String, String>> SelectPropertiesAsQueryStringParameters (this ModelMetadata modelMetadata, List<String> prefixChain) {
if (modelMetadata.Model == null) {
yield break;
}
if (modelMetadata.IsComplexType) {
IEnumerable<KeyValuePair<String, String>> keyValuePairs;
if (IEnumerableType.IsAssignableFrom(modelMetadata.ModelType)) {
keyValuePairs = modelMetadata.GetItemMetadata().Select((mm, i) =>
new PrefixedModelMetadata(
modelMetadata: mm,
prefix: String.Format("{0}[{1}]", modelMetadata.PropertyName, i)
)
).SelectMany(prefixed => prefixed.ModelMetadata.SelectPropertiesAsQueryStringParameters(
prefixChain.ToList().AddChainable(prefixed.Prefix, addOnlyIf: IsNeitherNullNorWhitespace)
));
}
else {
keyValuePairs = modelMetadata.Properties.SelectMany(mm =>
mm.SelectPropertiesAsQueryStringParameters(
prefixChain.ToList().AddChainable(
modelMetadata.PropertyName,
addOnlyIf: IsNeitherNullNorWhitespace
)
)
);
}
foreach (var keyValuePair in keyValuePairs) {
yield return keyValuePair;
}
}
else {
yield return new KeyValuePair<String, String>(
key: AntiXssEncoder.HtmlFormUrlEncode(
String.Join(".",
prefixChain.AddChainable(
modelMetadata.PropertyName,
addOnlyIf: IsNeitherNullNorWhitespace
)
)
),
value: AntiXssEncoder.HtmlFormUrlEncode(modelMetadata.Model.ToString()));
}
}
// Returns the metadata for each item from a ModelMetadata.Model which is IEnumerable
private static IEnumerable<ModelMetadata> GetItemMetadata (this ModelMetadata modelMetadata) {
if (modelMetadata.Model == null) {
yield break;
}
var genericType = modelMetadata.ModelType.GetInterfaces().FirstOrDefault(x =>
x.IsGenericType && x.GetGenericTypeDefinition() == IEnumerableGenericType
);
if (genericType == null) {
yield return modelMetadata;
}
var itemType = genericType.GetGenericArguments()[0];
foreach (Object item in ((IEnumerable) modelMetadata.Model)) {
yield return ModelMetadataProviders.Current.GetMetadataForType(() => item, itemType);
}
}
private static List<T> AddChainable<T> (this List<T> list, T item, Func<T, Boolean> addOnlyIf = null) {
if (addOnlyIf == null || addOnlyIf(item)) {
list.Add(item);
}
return list;
}
private static Boolean IsNeitherNullNorWhitespace (String value) {
return !String.IsNullOrWhiteSpace(value);
}
#endregion
}

Merging generic method

I'm new to C# and I have the following 3 methods. These methods allow the caller to retrieve differents properties of the table by specifying a lambda expression on the given method. However, I have the feeling an expert would combine them even further into a single generic method. If this is possible, please let me know how.
private KeyValuePair<T, int> GetHeaderProperty<T>(Func<Header, T> Property,
Source ds)
{
Func<Source, Header> GetValue =
a => Books.Where(str => str.BookId == a.DiscardBookId)
.First().Header;
return new KeyValuePair<T,int>(Property(GetValue(ds)), 0);
}
private KeyValuePair<T, int> GetBookProperty<T>(Func<Book, T> Property,
Source ds)
{
Func<Source, Book> GetValue =
a => Books.Where(str => str.BookId == a.DiscardBookId).First();
return new KeyValuePair<T, int>(Property(GetValue(ds)), 0);
}
private KeyValuePair<T, int> GetFleetProperty<T>(Func<Fleet, T> Property,
Source ds)
{
Func<Source,Fleet> GetValue =
a => Books.Where(str => str.BookId == a.DiscardBookId).First()
.Header.Fleet;
return new KeyValuePair<T,int>(Property(GetValue(ds)), 0);
}
I think the following will be equivalent to calling all three methods in a row and adding the results to a list:
private IEnumerable<KeyValuePair<T, int>> GetFleetProperty<T>(
Func<Book, T> PropertyBook,
Func<Header, T> PropertyHeader,
Func<Fleet, T> PropertyFleet,
Source ds)
{
Func<Source,Fleet> GetValue =
a => Books.Where(str => str.BookId == a.DiscardBookId).First();
var book = GetValue(ds);
var result = new List<KeyValuePair<T, int>>();
result.Add(new KeyValuePair<T, int>(PropertyBook(book), 0);
result.Add(new KeyValuePair<T, int>(PropertyHeader(book.Header), 0);
result.Add(new KeyValuePair<T, int>(PropertyFleet(book.Header.Fleet), 0);
return result;
}
UPDATE:
You could also create a method like this:
private KeyValuePair<T, int> GetProperty<T, TProperty>(
Func<TProperty, T> Property,
Func<Book, TProperty> GetProperty,
Source ds)
{
Func<Source, Header> GetValue =
a => Books.Where(str => str.BookId == a.DiscardBookId)
.First();
var book = GetValue(ds);
return new KeyValuePair<T,int>(Property(GetProperty(book)), 0);
}
You would call it like this for Header:
GetProperty(xyz, b => b.Header, ds);
You would call it like this for Book:
GetProperty(xyz, b => b, ds);
You would call it like this for Fleet:
GetProperty(xyz, b => b.Header.Fleet, ds);
You can use some thing like this
public interface IPieceProvider<T>
{
T GetPiece();
}
public class Fleet
{
public string Test;
}
public class Header
{
public Fleet Fleet;
public string Test;
}
public class Source
{
public int DiscardBookId;
}
public partial class Book
: IPieceProvider<Book>, IPieceProvider<Header>, IPieceProvider<Fleet>
{
public int BookId;
public Header Header;
public string Test;
Book IPieceProvider<Book>.GetPiece()
{
return this;
}
Header IPieceProvider<Header>.GetPiece()
{
return Header;
}
Fleet IPieceProvider<Fleet>.GetPiece()
{
return Header.Fleet;
}
}
class Program
{
Book[] Books;
private KeyValuePair<T, int> GetProperty<T, TP>(Func<TP, T> propertyGetter, Source ds)
{
return Books
.Where(b => b.BookId == ds.DiscardBookId)
.Cast<IPieceProvider<TP>>()
.Select(p => p.GetPiece())
.Select(p => new KeyValuePair<T, int>(propertyGetter(p), 0))
.First();
}
static void Main(string[] args)
{
var source = new Source();
var prg = new Program();
var bookTest = prg.GetProperty((Book b) => b.Test, source);
var headerTest = prg.GetProperty((Header h) => h.Test, source);
var fleetTest = prg.GetProperty((Fleet f) => f.Test, source);
Console.ReadKey();
}
}

Categories

Resources