fluentvalidation InclusiveBetween dynamically set range - model-view-controller

Im using FluentValidation
I would like to do a range validation using:
InclusiveBetween
RuleFor(x => x.Height)
.InclusiveBetween(x=> x.min, x.max).
I want to be able to get the 'from' and 'to' values dynamically from the model..rather than being hardcoded in the validator
Is this possible?
Thanks

Well, there's nothing in FluentValidation for that.
But you could write your own extension method (and validator), something like that (fast shot, so you'll have to make this better, but you've got the idea).
//the extension method
public static class ValidationExtensions
{
public static IRuleBuilder<T, TProperty> InclusiveBetween<T, TProperty>(this IRuleBuilder<T, TProperty> ruleBuilder, Expression<Func<T, TProperty>> fromExpression, Expression<Func<T, TProperty>> toExpression)
{
var fromFunc = leftExpression.Compile();
var toFunc = rightExpression.Compile();
return ruleBuilder.SetValidator(new InclusiveBetweenPropertyValidator(fromFunc.CoerceToNonGeneric(), fromExpression.GetMember(), toFunc.CoerceToNonGeneric(), toExpression.GetMember()));
}
}
Then the Validator class
public class InclusiveBetweenPropertyValidator : PropertyValidator, IBetweenValidator, IPropertyValidator
{
public Func<object, object> FromFunc { get; set; }
public MemberInfo FromMemberInfo { get; set; }
public Func<object, object> ToFunc { get; set; }
public MemberInfo ToMemberInfo { get; set; }
public IComparable From { get; private set; }
public IComparable To { get; private set; }
public InclusiveBetweenPropertyValidator(Func<object, object> fromFunc, MemberInfo fromMember, Func<object, object> toFunc, MemberInfo toMember)
: base((() => Messages.inclusivebetween_error))
{
FromFunc = fromFunc;
FromMemberInfo = fromMember;
ToFunc = toFunc;
ToMemberInfo = toMember;
}
protected override bool IsValid(PropertyValidatorContext context)
{
var comparable = (IComparable)context.PropertyValue;
From = (IComparable)this.FromFunc(context.Instance);
To = (IComparable)this.ToFunc(context.Instance);
if (comparable == null || FluentValidation.Internal.Comparer.GetComparisonResult(comparable, From) >= 0 && FluentValidation.Internal.Comparer.GetComparisonResult(comparable, To) <= 0)
return true;
context.MessageFormatter.AppendArgument("From", string.Format("{0} ({1})", FromMemberInfo.Name, From)).AppendArgument("To", string.Format("{0} ({1})",ToMemberInfo.Name, To)).AppendArgument("Value", context.PropertyValue);
return false;
}
}
usage :
RuleFor(x => x.Height)
.InclusiveBetween(x=> x.min, x.max)

If you don't want to write an extension you could use the additional overload of the Predicate Validator - which also accepts an instance of the parent object - like this:
RuleFor(x => x.Height)
.Must((model, height) => height >= model.Min && height <= model.Max);

This is similar to Raphaƫl's answer, but is more of a case-by-case usage as opposed to a general usage extension.
RuleFor(x => x).Must(HeightValidation);
private static bool HeightValidation(Model m)
{
return m.Height >= m.min && m.Height <= m.max;
}

Let's imagine your Model as follow:
public class YourModel
{
public int Height { get; set; }
public int Min { get; set; }
public int Max { get; set; }
}
then Validation will be as follow:
public class YourModelValidation : AbstractValidator<YourModel>
{
public YourModelValidation(int min,int max)
{
RuleFor(x => x.Height).InclusiveBetween(min, max);
}
}
then Validation usage is :
var validation = new YourModelValidation(model.Min,model.Max).Validate(model);
as you can see dynamic parameters are passed in validation's Constructor.
you can pass model or dto as Constructor parameter

Related

how to apply generic multiple include with generic repository?

I need to make generic multiple include function as a service with generic repository.
But unfortunately, I get nothing !!
here is my attempt using aggregate linq.
public IQueryable<TEntityDTO> getRowsWithIncludeMultiple(int page = 0, params Expression<Func<TEntityDTO, object>>[] includes)
{
GridSetting gs = GetGrid();
IEnumerable<TEntity> getPage = _dbSet.Skip((page == 0 ? page : page - 1) * gs.ItemsPerPage).Take(gs.ItemsPerPage);
IQueryable<TEntityDTO> rows = _mapper.Map<IEnumerable<TEntityDTO>>(getPage).AsQueryable();
if (includes != null) { rows = includes.Aggregate(rows, (current, include) => current.Include(include)); }
// or
//foreach (var include in includes)
//{
// rows = rows.Include(include);
//}
return rows;
}
when I add debugging point I get that the includes has list of expression
and then here is how I use it
var xxx = _customerService.getRowsWithIncludeMultiple(page: 0, i => i.cityDTO, i => i.ageDTO);
the problem here I get customers without the included things (cityDTO & ageDTO)
let me include here models
public class CustomerDTO
{
public int Id { get; set; }
public string CustName { get; set; }
public string CustJobTitle { get; set; }
public string CustAge { get; set; }
public bool IsManager { get; set; }
// FKs
public int AgeId { get; set; }
public int CityId { get; set; }
public AgeDTO ageDTO { get; set; }
public CityDTO cityDTO { get; set; }
}
public class CityDTO
{
public int Id { get; set; }
public string CityName { get; set; }
public List<CustomerDTO> customerDTO { get; set; }
}
public class AgeDTO
{
public int Id { get; set; }
public int AgeName { get; set; }
public List<CustomerDTO> customerDTO { get; set; }
}
Update ... showing the whole service, usage, and injection
here is the whole generic repository service and how it looks like
public class Repository<TEntity, TEntityDTO> : IRepository<TEntity, TEntityDTO> where TEntity : class where TEntityDTO : class
{
protected readonly AppDbContext _context;
private readonly DbSet<TEntity> _dbSet;
private readonly IMapper _mapper;
public Repository(AppDbContext context, IMapper mapper)
{
_context = context;
_dbSet = context.Set<TEntity>();
_mapper = mapper;
}
// GENERIC CRUD ...
// and then here where i want to focus
public IQueryable<TEntityDTO> getRowsWithIncludeMultiple(int page = 0, params Expression<Func<TEntityDTO, object>>[] includes)
{
GridSetting gs = GetGrid();
IEnumerable<TEntity> getPage = _dbSet.Skip((page == 0 ? page : page - 1) * gs.ItemsPerPage).Take(gs.ItemsPerPage);
IQueryable<TEntityDTO> rows = _mapper.Map<IEnumerable<TEntityDTO>>(getPage).AsQueryable();
if (includes != null) { rows = includes.Aggregate(rows, (current, include) => current.Include(include)); }
// or
//foreach (var include in includes)
//{
// rows = rows.Include(include);
//}
return rows;
}
}
and then here is how customer service uses generic repo
public class CustomerService : Repository<Customer, CustomerDTO>, ICustomerService
{
public CustomerService(AppDbContext db, IMapper mapper) : base(db, mapper) { }
}
finally injection in Program.cs
builder.Services.AddScoped(typeof(IRepository<,>), typeof(Repository<,>));
builder.Services.AddScoped<ICustomerService, CustomerService>();
thanks #Svyatoslav Danyliv and thanks for all contributors your answer helped me a lot.
cannot apply Include to DTO object
so, I would to share what I have made so far.
instead of
public IQueryable<TEntityDTO> getRowsWithIncludeMultiple(int page = 0, params Expression<Func<TEntityDTO, object>>[] includes)
{
GridSetting gs = GetGrid();
IEnumerable<TEntity> getPage = _dbSet.Skip((page == 0 ? page : page - 1) * gs.ItemsPerPage).Take(gs.ItemsPerPage);
IQueryable<TEntityDTO> rows = _mapper.Map<IEnumerable<TEntityDTO>>(getPage).AsQueryable();
if (includes != null) { rows = includes.Aggregate(rows, (current, include) => current.Include(include)); }
return rows;
}
I applied include to the original entity not to DTO
public IEnumerable<TEntityDTO> IncludeMultiple(int page = 0, params Expression<Func<TEntity, object>>[] includes)
{
GridSetting gs = GetGrid();
IQueryable<TEntity> rows = _dbSet.Skip((page == 0 ? page : page - 1) * gs.ItemsPerPage).Take(gs.ItemsPerPage).AsQueryable();
if (includes != null)
{ rows = includes.Aggregate(rows, (current, include) => current.Include(include).AsQueryable().AsNoTracking()); }
return _mapper.Map<IEnumerable<TEntityDTO>>(rows).AsQueryable();
}
And here is some clarification to make the idea more understandable
public IEnumerable<TEntityDTO> IncludeMultiple(params Expression<Func<TEntity, object>>[] includes)
{
// get IQueryable of TEntity
IQueryable<TEntity> rows = _dbSet.AsQueryable();
// and here using `linq Aggregate` to append multiple include statement
if (includes != null)
{ rows = includes.Aggregate(rows, (current, include) => current.Include(include).AsQueryable().AsNoTracking()); }
// here I use auto mapper to map IEnumerable object to IEnumerable object as a result
return _mapper.Map<IEnumerable<TEntityDTO>>(rows).AsQueryable();
}
and finally the usage
_ServiceName.IncludeMultiple(i => i.city, i => i.age);

FluentValidation Set Valid Result to a Custom Property

I am validating the content for file import and I have an IsValid property for each line.
public class Header
{
public int LineNumber { get; set; }
public string Property1 { get; set; }
public string Property2 { get; set; }
public bool IsValid { get; set; }
}
public class Detail
{
public int LineNumber { get; set; }
public string Property1 { get; set; }
public string Property2 { get; set; }
public string Property3 { get; set; }
public bool IsValid { get; set; }
}
public class Trailer
{
public int LineNumber { get; set; }
public string Property1 { get; set; }
public bool IsValid { get; set; }
}
public class ImportFile
{
public Header Header { get; set; }
public List<Detail> Details { get; set; }
public Trailer Trailer { get; set; }
}
and my validators look somewhat like:
public class DetailValidator : AbstractValidator<Detail>
{
public DetailValidator()
{
RuleFor(d => d.Property1)
.Cascade(CascadeMode.Stop)
.NotEmpty()
.WithState(d => d.LineNumber)
.Length(3)
.WithState(d => d.LineNumber);
RuleFor(d => d.Property2)
.Cascade(CascadeMode.Stop)
.NotEmpty()
.WithState(d => d.LineNumber)
.MaximumLength(50)
.WithState(d => d.LineNumber);
...
}
}
public class ImportFileValidator : AbstractValidator<ImportFile>
{
public ImportFileValidator()
{
RuleFor(f => f.Header)
.SetValidator(new HeaderValidator());
RuleForEach(f => f.Details)
.SetValidator(new DetailsValidator());
...
}
}
After I call the validation, I wanted to set the IsValid property of each line of the file (be it header, detail or trailer) base from the result of the validation.
What is possible for now is, since I am using WithState to store the LineNumber, I can match the ValidationResult against the ImportFile instance to set each line's validity like below:
ImportFile file = // parsed file content
var result = new ImportFileValidator().Validate(file);
foreach (var detail in file.Details)
{
var error = result.Errors.FirstOrDefault(e =>
Convert.ToInt32(e.CustomState) == detail.LineNumber);
detail.IsValid = error == null;
}
And I have to check for the header and trailer as well.
Is there a way I can do this inside the validators? I am trying to explore the FluentValidation's documentation, but I can't seem to find what I needed there.
As I was exploring the available methods in FluentValidation, I saw OnFailure and OnAnyFailure methods. This methods might be a good help to what I needed to do, but the problem is they're obsolete as of 10.3.0 and will be removed on version 11. They're suggesting to use a custom validator instead.
The Header, Detail and Trailer Abstract Validators remain as is.
I created custom validator extensions for those 3.
Each extension methods creates an instance of the corresponding validator and executes it. I can make them generic for header, detail and trailer since they will do the same thing, set IsValid property to the validation result.
public static IRuleBuilderOptionsConditions<ImportFile, T> IsHeaderValid<T>(this IRuleBuilder<ImportFile, T> ruleBuilder)
where T : Header
{
return builder.Custom((header, context) =>
{
// Create the Header Abstract Validator Instance
var validator = new HeaderValidator();
var result = validator.Validate(Header);
header.IsValid = result.IsValid;
// Pass the errors to the context
result.Errors.ForEach(context.AddFailure);
}
}
I had to change the ImportFileValidator to call the custom validators, instead of using setvalidator.
The ImportFileValidator looks like this:
public class ImportFileValidator : AbstractValidator<ImportFile>
{
public ImportFileValidator()
{
RuleFor(f => f.Header)
.IsHeaderValid();
RuleForEach(f => f.Details)
.IsDetailValid();
...
}
}
This is pretty much how I was able to set the IsValid property without having to do the matching I initially did in the question.

C#, LINQ a generic sort method to sort a list by object properties and nested properties

I have an entity called User in my EF Model:
public class User
{
public int UserId { get; set; }
public Branch HomeLocation{ get; set; }
public string CellPhone { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserCode { get; set; }
}
Branch is another entity in the model:
public class Branch
{
public int BranchId { get; set; }
public string BranchName{ get; set; }
public string Address { get; set; }
}
My requirement is to get a users list and display it on a grid, and then sort the list by some of the columns (one at a time). Say, for example, sort by username, firstname, lastname and HomeLocation. When sorting by homelocation, it should be sorted by the branch name.
I have many grids like this displaying other data as well. So I want to develop a generic sort mechanism and I have achieved it using some of the examples found in Google, for example this one:
public class GenericSorter<T>
{
public IEnumerable<T> Sort(IEnumerable<T> source, string sortBy, string sortDirection)
{
var param = Expression.Parameter(typeof(T), "item");
var sortExpression = Expression.Lambda<Func<T, object>>
(Expression.Convert(Expression.Property(param, sortBy), typeof(object)), param);
switch (sortDirection.ToLower())
{
case "asc":
return source.AsQueryable<T>().OrderBy<T, object>(sortExpression);
default:
return source.AsQueryable<T>().OrderByDescending<T, object>(sortExpression);
}
}
}
However, sorting by home location fails because it needs to be sorted by an inner property of the user entity. I've tried using Dynamic LINQ library too, but there's no luck.
Update: Note that I have to sort a List, not IQueryable, because my list contains fields encrypted with AE, which don't support DB-level sorting.
Can someone point out to me how to achieve the dynamic sorting from an inner property?
Update2: I followed the example and implemented the sort using the extension methods and this is how it's applied on my list:
var users = (from u in context.Users.Include("Branch")
where (u.FkBranchId == branchId || branchId == -1) && u.IsActive
&& (searchTerm == string.Empty || (u.FirstName.Contains(searchTerm) || u.LastName.Equals(searchTerm)
|| u.UserName.Contains(searchTerm) || u.UserCode.Contains(searchTerm)))
select u).ToList();
var rowCount = users.Count;
var orderedList = users.OrderBy(sortInfo.SortColumn).Skip(pageInfo.Skip).Take(pageInfo.PageSize).ToList();
But I get the following error:
Object of type 'System.Linq.Expressions.Expression1[System.Func2[ClientData.User,System.String]]' cannot be converted to type 'System.Func`2[ClientData.User,System.String]'.
Error is thrown from the following:
object result = typeof(Enumerable).GetMethods().Single(
method => method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2
&& method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), type)
.Invoke(null, new object[] { source, lambda });
After this, I'm receiving the following error, in some occasions as explained in the comment:
Adapt the code from #MarcGravell found here.
public static class EnumerableExtensions
{
public static IOrderedEnumerable<T> OrderBy<T>(
this IEnumerable<T> source,
string property)
{
return ApplyOrder<T>(source, property, "OrderBy");
}
public static IOrderedEnumerable<T> OrderByDescending<T>(
this IEnumerable<T> source,
string property)
{
return ApplyOrder<T>(source, property, "OrderByDescending");
}
public static IOrderedEnumerable<T> ThenBy<T>(
this IOrderedEnumerable<T> source,
string property)
{
return ApplyOrder<T>(source, property, "ThenBy");
}
public static IOrderedEnumerable<T> ThenByDescending<T>(
this IOrderedEnumerable<T> source,
string property)
{
return ApplyOrder<T>(source, property, "ThenByDescending");
}
static IOrderedEnumerable<T> ApplyOrder<T>(
IEnumerable<T> source,
string property,
string methodName)
{
string[] props = property.Split('.');
Type type = typeof(T);
ParameterExpression arg = Expression.Parameter(type, "x");
Expression expr = arg;
foreach (string prop in props)
{
// use reflection (not ComponentModel) to mirror LINQ
PropertyInfo pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);
object result = typeof(Enumerable).GetMethods().Single(
method => method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2
&& method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), type)
.Invoke(null, new object[] { source, lambda.Compile() });
return (IOrderedEnumerable<T>)result;
}
}
UPDATED
Use it from a List<> :
var list = new List<MyModel>();
list = list.OrderBy("MyProperty");

How do I custom validate collections withing MVC5

I am building an MVC5 application and I have the following viewmodels:
public class UserPartyViewModel
{
public UserPartyViewModel()
{
Entitlements = new Collection<AssignedClaims>();
}
public Guid PartyID { get; set; }
public string PartyName { get; set; }
public ICollection<AssignedClaim> AssignedClaims{ get; set; }
}
public class AssignedClaims
{
public AssignedClaims()
{
ClaimValues = new Collection<AssignedClaimValue>();
}
public string Name { get; set; }
public int Max { get; set; }
public int Min { get; set; }
public ICollection<AssignedClaimValue> ClaimValues { get; set; }
}
public class AssignedClaimValue
{
public Guid ClaimValueID { get; set; }
public string ClaimValue { get; set; }
public bool Assigned { get; set; }
}
Contained in the UserPartyViewModel will always be an assignedclaim with a name of "Security" and the assignedclaimvalue with a claimvalue of "User"
If the ClaimValue of user is Assigned then I need to validate the rest of the model. If it is not then no further validation should take place.
Within AssignedClaims there is a min and max, these are the minimum and maximum number of assignedclaimvalues that should be Assigned.
I have tried to use AttributeValidate cannot stop it validating the rest of the model.
I have also looked at the IValidatableObject interface but also can't work out how to control the validation of the child collections depending on the User claim.
What's the best way to achieve this?
Found a solution which appears to do what I want:
public class UserPartyViewModel : IValidatableObject
{
public UserPartyViewModel()
{
Entitlements = new Collection<AssignedClaims>();
}
public string AccessLevel { get; set; }
public Guid PartyID { get; set; }
public string PartyName { get; set; }
public ICollection<AssignedClaims> Entitlements { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var isUser = Entitlements.Any(c => c.Name == "Security" && c.ClaimValues.Any(v => v.Assigned == true && v.ClaimValue == "User"));
if (isUser)
{
int i = 0;
foreach (var result in Entitlements)
{
yield return result.Validate(i++);
}
}
else
{
yield return ValidationResult.Success;
}
}
}
public class AssignedClaims
{
public AssignedClaims()
{
ClaimValues = new Collection<AssignedClaimValue>();
}
public string Name { get; set; }
public string Description { get; set; }
public int Max { get; set; }
public int Min { get; set; }
public ICollection<AssignedClaimValue> ClaimValues { get; set; }
public ValidationResult Validate(int item)
{
int min = Min;
int max = (ClaimValues.Count() < Max) ? ClaimValues.Count() : Max;
int assignedCount = ClaimValues.Where(i => i.Assigned == true).Count();
if (!(min <= assignedCount && assignedCount <= max))
{
string errMessage = String.Format("{2} should have between {0} and {1} Security Claims checked.", min, max, Name);
return new ValidationResult(errMessage, new[] { string.Format("Entitlements[{0}]", item) });
}
else
{
return ValidationResult.Success;
}
}
}
The only issue I had was trying to get the error messages appearing in the correct place. In my view for assignedclaims I added:
#Html.ValidationMessageFor(model => model, "", new { #class = "text-danger" })
and passed the iteration through to the validate function on assignedclaim to ensure it was added to the correct member.

Concrete implementation of generic base class and extension method

The end goal for this post is to override the ToString() method of a concrete implementation of a generic base class while still being able to search the implementation using Linq flattening technique. So if you read this and see a better way let me know. I'm using Telerik controls for Silverlight and they won't change their api to allow some of their control properties to be data-bound and instead rely on the ToString() method of whatever object they are bound to. yea, stupid.. Anyway here is what I've got.
RadTreeView control on my page. The FullPath property of each node in the treeview uses the ToString() method of each item its bound to (so this is what I need to override).
I had to create an "intermediary" class to enhance my base model class so it can be bound as a heirarchy in the tree view and then a concrete implementation of that generic class to override ToString(). Now the problem is I have a Linq extension that explodes because it cannot convert the concrete implementation back to the base generic class. I love generics but this is too much for me. Need help on solving the extension method issue.
Intermediary generic class:
public class HeirarchicalItem<T> : NotifyPropertyChangedBase, INotifyCollectionChanged where T : class
{
public event NotifyCollectionChangedEventHandler CollectionChanged;
public virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs ea)
{
if (CollectionChanged != null)
CollectionChanged(this, ea);
}
public HeirarchicalItem() { }
public HeirarchicalItem(T item)
{
Item = item;
}
public HeirarchicalItem(IEnumerable<T> collection)
{
CopyFrom(collection);
}
private T _item;
public T Item
{
get
{
return _item;
}
set
{
_item = value;
RaisePropertyChanged<HeirarchicalItem<T>>(a => a.Item);
}
}
private ObservableCollection<HeirarchicalItem<T>> _children = new ObservableCollection<HeirarchicalItem<T>>();
public virtual ObservableCollection<HeirarchicalItem<T>> Children
{
get { return _children; }
set
{
_children = value;
RaisePropertyChanged<HeirarchicalItem<T>>(a => a.Children);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
private void CopyFrom(IEnumerable<T> collection)
{
if ((collection != null))
{
using (IEnumerator<T> enumerator = collection.GetEnumerator())
{
while (enumerator.MoveNext())
{
HeirarchicalItem<T> newHeirarchicalItem = new HeirarchicalItem<T>(enumerator.Current);
Children.Add(newHeirarchicalItem);
RaisePropertyChanged<HeirarchicalItem<T>>(a => a.Children);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add));
}
}
}
}
}
Base model class: (data is shuttled to and from WCF Ria service using this class)
public class tbl_Path : EntityBase, IFullPath, IEquatable<tbl_Path>, IEqualityComparer<tbl_Path>
{
public tbl_Path();
public int GetHashCode(tbl_Path obj);
public override string ToString();
public DateTime CreateDate { get; set; }
public short Depth { get; set; }
public string FullPath { get; set; }
public bool IsAuthorized { get; set; }
public bool IsSelected { get; set; }
public string Name { get; set; }
public override IEnumerable<Operation> Operations { get; }
public int? ParentPathID { get; set; }
public int PathID { get; set; }
public Guid SecurityKey { get; set; }
public EntityCollection<tbl_Configuration> tbl_Configuration { get; set; }
public EntityCollection<tbl_Key> tbl_Key { get; set; }
public EntityCollection<tbl_SecurityACL> tbl_SecurityACL { get; set; }
public EntityCollection<tbl_SecurityInheriting> tbl_SecurityInheriting { get; set; }
public EntityCollection<tbl_Variable> tbl_Variable { get; set; }
}
Concrete Implementation so that I can override ToString():
public class HeirarchicalPath : HeirarchicalItem<tbl_Path>
{
public HeirarchicalPath()
{
}
public HeirarchicalPath(tbl_Path item)
: base(item)
{
}
public HeirarchicalPath(IEnumerable<tbl_Path> collection)
: base(collection)
{
}
public override string ToString()
{
return Item.Name; **// we override here so Telerik is happy**
}
}
And finally here is the Linq extension method that explodes during compile time because I introduced a concrete implementation of my generic base class.
public static IEnumerable<T> Traverse<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> fnRecurse)
{
foreach (T item in source)
{
yield return item;
IEnumerable<T> seqRecurse = fnRecurse(item);
if (seqRecurse != null)
{
foreach (T itemRecurse in Traverse(seqRecurse, fnRecurse))
{
yield return itemRecurse;
}
}
}
}
Actual code that is breaking: (x.Children is highlighted with the error)
Cannot implicitly convert type
'System.Collections.ObjectModel.ObservableCollection<HeirarchicalItem<tbl_Path>>' to
'System.Collections.Generic.IEnumerable<HeirarchicalPath>'. An explicit conversion
exists (are you missing a cast?)
HeirarchicalPath currentItem = this.Paths.Traverse(x => x.Children).Where(x => x.Item.FullPath == "$/MyFolder/Hello").FirstOrDefault();
Figured it out. Been working on this all day and minutes after posting the question I resolve it as always.
Just needed to add this bit to my concrete implementation and no more compiler errors.
private ObservableCollection<HeirarchicalPath> _children = new ObservableCollection<HeirarchicalPath>();
public new ObservableCollection<HeirarchicalPath> Children
{
get
{
return _children;
}
set
{
if (value == null)
return;
_children = value;
RaisePropertyChanged<HeirarchicalPath>(a => a.Children);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}

Resources