"RequiredAsSet" validation attribute - c#

I'm using the data annotations attributes for validation in my app, and I want to have a RequiredAsSet attribute, which will require either all properties decorated with the attribute to be populated, or none of them. The set cannot be part populated.
I thought a clever way to do it would be like this:
public class RequiredAsSetAttribute : RequiredAttribute
{
/// <summary>
/// Initializes a new instance of the <see cref="RequiredAsSetAttribute"/> class.
/// </summary>
/// <param name="viewModel">The view model.</param>
public RequiredAsSetAttribute(IViewModel viewModel)
{
this.ViewModel = viewModel;
}
/// <summary>
/// Gets or sets the view model.
/// </summary>
/// <value>The view model.</value>
private IViewModel ViewModel { get; set; }
/// <summary>
/// Determines whether the specified value is valid.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>
/// <c>true</c> if the specified value is valid; otherwise, <c>false</c>.
/// </returns>
public override bool IsValid(object value)
{
IEnumerable<PropertyInfo> properties = GetPropertiesWihRequiredAsSetAttribute();
bool aValueHasBeenEnteredInTheRequiredFieldSet = properties.Any(property => !string.IsNullOrEmpty(property.GetValue(this.ViewModel, null).ToString()));
if (aValueHasBeenEnteredInTheRequiredFieldSet)
{
return base.IsValid(value);
}
return true;
}
/// <summary>
/// Gets the properties with required as set attribute.
/// </summary>
/// <returns></returns>
private IEnumerable<PropertyInfo> GetPropertiesWithRequiredAsSetAttribute()
{
return this.ViewModel.GetType()
.GetProperties()
.Where(p => GetValidatorsFromProperty(p).Length != 0 && !GetValidatorsFromProperty(p).Any(x => x == this));
}
/// <summary>
/// Gets the validators from property.
/// </summary>
/// <param name="property">The property.</param>
/// <returns></returns>
private static RequiredAsSetAttribute[] GetValidatorsFromProperty(PropertyInfo property)
{
return (RequiredAsSetAttribute[])property.GetCustomAttributes(typeof(RequiredAsSetAttribute), true);
}
}
It basically takes my view model as a constructor argument and uses reflection to find the other properties decorated with the RequiredAsSet attribute to check if anything has been entered.
Turns out this isn't such a clever idea though, because you can't pass instances into the constructor of an attribute..only constant expresssions, typeof expressions or array creation expressions, as the compiler helpfully pointed out.
So is there another way to do this?

If I understand the problem correcty, the way to do this is with a class level validation attribute. You then have access to the entire object and can use reflection to access whatever properties you wish. The instance is passed in during validation.
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public class RequiredAsSetAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
var properties = TypeDescriptor.GetProperties(value);
...
}
}

Related

MVC: VirtualPathProvider

I've already searched here on SO, but I don't find a solutions, so let's hope that somebody of you can provide me some guidance.
I want MVC to load views from the database, therefore I've created a VirtualFile and a VirtualPathProvider.
This is the code from the VirtualFile:
public class OxygenVirtualFile : VirtualFile
{
#region Constructors
/// <summary>
/// Create a new instance of the <see cref="OxygenVirtualFile"/>.
/// </summary>
/// <param name="virtualPath">The virtual path to the resource represented by this instance.</param>
/// <param name="body">The contents of the virtual file.</param>
public OxygenVirtualFile(string virtualPath, string body)
: base(virtualPath)
{
content = body;
}
#endregion
#region Properties
/// <summary>
/// Gets the contents of the virtual file.
/// </summary>
private readonly string content;
/// <summary>
/// A boolean that indicates wether this virtual file does exists.
/// </summary>
public bool Exists
{
get { return (content != null); }
}
#endregion
#region VirtualFile Members
/// <summary>
/// returns a read-only stream to the virtual resource.
/// </summary>
/// <returns>A read-only stream to the virtual resource.</returns>
public override Stream Open()
{
var encoding = new ASCIIEncoding();
return new MemoryStream(encoding.GetBytes(content), false);
}
#endregion
}
Then I have the provider itself:
public class OxygenVirtualPathProvider : VirtualPathProvider
{
#region Constructors
/// <summary>
/// Create a new instance of the <see cref="OxygenVirtualPathProvider"/>.
/// </summary>
/// <param name="exludedPath">The path that should be excluded and thus not be rendered using this virtual path provider.</param>
/// <remarks>
/// If the specified path does occur somewhere in the url, the view is not rendered using this <see cref="VirtualPathProvider"/>.
/// In all the other cases, the view is rendered using this <see cref="VirtualPathProvider"/>.
/// </remarks>
public OxygenVirtualPathProvider(string exludedPath)
{
var unityContainer = DependencyResolver.Current.GetService<IUnityContainer>();
unitOfWork = unityContainer.Resolve<IUnitOfWork>();
this.exludedPath = exludedPath;
}
#endregion
#region Properties
/// <summary>
/// Gets the <see cref="IUnitOfWork"/> which is used to access the database.
/// </summary>
private readonly IUnitOfWork unitOfWork;
/// <summary>
/// Get the root path of the dynamic views.
/// </summary>
private readonly string exludedPath;
#endregion
#region Methods
/// <summary>
/// Check if the view is dynamic view.
/// </summary>
/// <param name="virtualPath">The path of the virtual view.</param>
/// <returns>True is the view is a dynamic view, false otherwise.</returns>
public bool IsDynamicView(string virtualPath)
{
// If the path which is requested does contain an excluded url, this virtual path provider will not handle this.
if (virtualPath.ToLower().Contains(exludedPath))
{ return false; }
var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(virtualPath);
var viewExists = DoesViewExistInDataBase(fileNameWithoutExtension);
return viewExists && !virtualPath.ToLower().Contains(exludedPath);
}
/// <summary>
/// Verify that the requested view exists in the database.
/// </summary>
/// <param name="virtualPath">The path of the virtual view.</param>
/// <returns>True if the view exists in the database, false otherwise.</returns>
public bool DoesViewExistInDataBase(string virtualPath)
{
var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(virtualPath);
return new ViewManager(unitOfWork).Exists(fileNameWithoutExtension);
}
/// <summary>
/// Gets the view from the database.
/// </summary>
/// <param name="virtualPath">The virtual path of the view to retrieve.</param>
/// <returns>A string representing the body of the view.</returns>
public String GetViewBodyFromDb(string virtualPath)
{
var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(virtualPath);
return new ViewManager(unitOfWork).Get(fileNameWithoutExtension);
}
#endregion
#region VirtualPathProvider Members
/// <summary>
/// Gets a value that indicates whether a file exists in the virtual file system.
/// </summary>
/// <param name="virtualPath">The path to the virtual file.</param>
/// <returns>true if the file exists in the virtual file system; otherwise, false.</returns>
public override bool FileExists(string virtualPath)
{
return IsDynamicView(virtualPath) || base.FileExists(virtualPath);
}
/// <summary>
/// Gets a virtual file from the virtual file system.
/// </summary>
/// <param name="virtualPath">The path to the virtual file.</param>
/// <returns>A descendent of the <see cref="VirtualFile"/> class that represents a file in the virtual file system.</returns>
public override VirtualFile GetFile(string virtualPath)
{
return IsDynamicView(virtualPath) ? new OxygenVirtualFile(virtualPath, GetViewBodyFromDb(virtualPath)) : base.GetFile(virtualPath);
}
/// <summary>
/// Returns a hash of the specified virtual paths.
/// </summary>
/// <param name="virtualPath">The path to the primary virtual resource.</param>
/// <param name="virtualPathDependencies">An array of paths to other virtual resources required by the primary virtual resource.</param>
/// <returns>A hash of the specified virtual paths.</returns>
public override string GetFileHash(string virtualPath, System.Collections.IEnumerable virtualPathDependencies)
{
return base.GetFileHash(virtualPath, virtualPathDependencies);
}
/// <summary>
/// Creates a cache dependency based on the specified virtual paths.
/// </summary>
/// <param name="virtualPath">The path to the primary virtual resource.</param>
/// <param name="virtualPathDependencies">An array of paths to other resources required by the primary virtual resource.</param>
/// <param name="utcStart">The UTC time at which the virtual resources were read.</param>
/// <returns>A <see cref="CacheDependency"/> object for the specified virtual resources.</returns>
public override CacheDependency GetCacheDependency(string virtualPath, System.Collections.IEnumerable virtualPathDependencies, DateTime utcStart)
{
return Previous.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
}
#endregion
}
In order to ensure that my view is inheriting correctly, I do return this:
public string Get(string name)
{
return "#inherits System.Web.Mvc.WebViewPage<dynamic>" +
" <h2>title</h2>";
}
According to me and to what I've found here on SO, this should work, however the following error is throwed:
The view at '~/Views/Home/articles.aspx' must derive from ViewPage, ViewPage<TModel>, ViewUserControl, or ViewUserControl<TModel>.
EDIT:
The provider is registered in the global.asax file aswell:
protected void Application_Start()
{
HostingEnvironment.RegisterVirtualPathProvider(new OxygenVirtualPathProvider("/CMS/"));
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
Anyone has an idea about this?
Ok,
I've managed to find a solution.
Mvc was searchinf for ASPX, ASCX, CSHTML and other files.
When I changed my VirtualPathProvider to only load dynamic files when the searched file ends with .CSHTML the error went away.
It's working now, but I would like to know why I has this behaviour in the first place.
Anyone who can provide me an answer on that one?

C# Fluent API: How to construct

Currently, I'm creating custom MVC Html Helpers which I will be using through a fluent API.
To give an example to understand it, I'll have the following helper which is (or should be in a not too distant future) generate a grid:
#(Html.GridFor(Model)
.WithName("MyName")
.WithColumns(model =>
{
model.Bind(x => x.Name);
model.Bind(x => x.DateCreated);
model.Bind(x => x.DateUpdated);
}).Render());
Now, everything is constructed with the start point. The IGridBuilder.
/// <summary>
/// When implemented by a class, it defines the class as an object that can construct a grid by using a fluent API.
/// </summary>
public interface IGridBuilder<TModel> : IHtmlHelper, IDataSource<TModel>
{
#region Properties
/// <summary>
/// Gets the name of the <see cref="IGridBuilder{TModel}" />.
/// </summary>
string Name { get; }
#endregion
#region Methods
/// <summary>
/// Sets the name of the <see cref="IGridBuilder{TModel}" />. This name will be used as an id on the outer element that
/// holds the entire grid.
/// </summary>
/// <param name="name">The name that the <see cref="IGridBuilder{TModel}" /> should have.</param>
/// <returns>An <see cref="IGridBuilder{TModel}" /> that can be used to construct the grid through a fluent API.</returns>
IGridBuilder<TModel> WithName(string name);
/// <summary>
/// Set the columns of the model that should be bound to grid.
/// </summary>
/// <param name="bindAllColumns">The action that will bind all the columns.</param>
/// <returns>An <see cref="IGridBuilder{TModel}" /> that is used to construct the grid.</returns>
IGridBuilder<TModel> WithColumns(Action<IColumnBinder<TModel>> bindAllColumns);
/// <summary>
/// Renders the grid with all the set properties.
/// </summary>
/// <returns>A <see cref="MvcHtmlString" /> that contains the HTML representation of the grid.</returns>
MvcHtmlString Render();
#endregion
}
and to make the bind commands, I'm using an IColumnBinder interface:
/// <summary>
/// When implemented by a class, this class is marked as being an builder that can construct a column through a fluent API.
/// </summary>
/// <typeparam name="TModel"></typeparam>
public interface IColumnBinder<TModel> : IHtmlHelper, IDataSource<TModel>
{
#region Methods
/// <summary>
/// Binds an column to the grid.
/// </summary>
/// <typeparam name="TItem">The type of the column on which to bind the items.</typeparam>
/// <param name="propertySelector">The functional that will bind the control to the grid.</param>
void Bind<TItem>(Expression<Func<TModel, TItem>> propertySelector);
/// <summary>
/// Apply a specific css class on an element.
/// </summary>
/// <param name="className">The name of the css class that should be placed on the element.</param>
/// <returns>As <see cref="IColumnBinder{TModel}"/> that is used to construct this column through a fluent API.</returns>
IColumnBinder<TModel> WithCss(string className);
#endregion
}
Now, what's the best approach to link an IColumnBuilder to an IGridBuilder?
To make it very short, I'm struggling with the following:
The IColumnBuilder set ups specific properties but the rendering takes place in the IGridBuilder interface.
The main problem lies in the following code:
/// <summary>
/// Set the columns of the model that should be bound to grid.
/// </summary>
/// <param name="bindAllColumns">The action that will bind all the columns.</param>
/// <returns>An <see cref="IGridBuilder{TModel}" /> that is used to construct the grid.</returns>
public IGridBuilder<TModel> WithColumns(Action<IColumnBinder<TModel>> bindAllColumns)
{
bindAllColumns(new ColumnBinder<TModel>());
return this;
}
So here I execute the action to bind the column:
model.Bind(x => x.Name)
But how can I keep a reference between the IGridBuilder and the IColumnBuilder to construct it in a proper way afterwars?
Or are there other solutions?
Ok,
After hours of searching I've found a solution and therefore I'm answering my own question. However, if there are users that have another approach to the same problem, please tell me so I can adapt me code maybe.
This code will primarly use a class to transfer objects, since a class is a reference type it can be passed to another object, and that object can manipulate the object.
So, I have written a custom HTML helper that should work as the following:
#(Html.GridFor(Model)
.WithName("MyName")
.WithColumns(model =>
{
model.Bind(x => x.Name).WithCss("row first");
model.Bind(x => x.DateCreated);
model.Bind(x => x.DateUpdated);
}).Render());
So, I do have a model that's enumerable and that I will pass to the grid. The grid takes 3 columns of the model and renders the grid.
For this code, I have a couple of interfaces which helps me throughout the process:
An HTML Helper interface (just holds an object to the HtmlHelper):
/// <summary>
/// Provides a way to extend the <see cref="HtmlHelper" /> to construct objects of various kinds.
/// </summary>
public static class HtmlHelperExtensions
{
#region Grid
/// <summary>
/// Constructs a grid for a property that holds a collection.
/// </summary>
/// <typeparam name="TModel">The type of the model on which this grid is being build.</typeparam>
/// <typeparam name="TEntity">The type of a single item in the collection. </typeparam>
/// <param name="htmlHelper">The helper on which this method is executed. </param>
/// <param name="dataSource">The datasource on which the items are bound. </param>
/// <returns>An <see cref="IGridBuilder{TEntity}" /> that is used to construct the grid.</returns>
public static IGridBuilder<TEntity> GridFor<TModel, TEntity>(this HtmlHelper<TModel> htmlHelper,
IEnumerable<TEntity> dataSource)
{
return new GridBuilder<TEntity>(htmlHelper, dataSource);
}
#endregion
}
A Datasource interface (just holds an interface to the datasource):
public interface IDataSource<out TModel>
{
#region Properties
/// <summary>
/// Gets the source that will be bound to the implemented object.
/// </summary>
IEnumerable<TModel> DataSource { get; }
#endregion
}
And then we have all the other code.
The HTML Helper extension class is the first one:
/// <summary>
/// Provides a way to extend the <see cref="HtmlHelper" /> to construct objects of various kinds.
/// </summary>
public static class HtmlHelperExtensions
{
#region Grid
/// <summary>
/// Constructs a grid for a property that holds a collection.
/// </summary>
/// <typeparam name="TModel">The type of the model on which this grid is being build.</typeparam>
/// <typeparam name="TEntity">The type of a single item in the collection.</typeparam>
/// <param name="htmlHelper">The helper on which this method is executed.</param>
/// <param name="dataSource">The datasource on which the items are bound.</param>
/// <returns>An <see cref="IGridBuilder{TEntity}" /> that is used to construct the grid.</returns>
public static IGridBuilder<TEntity> GridFor<TModel, TEntity>(this HtmlHelper<TModel> htmlHelper,
IEnumerable<TEntity> dataSource)
{
return new GridBuilder<TEntity>(htmlHelper, dataSource);
}
#endregion
}
Then the next one is an implementation of the IGridBuilder interface:
/// <summary>
/// Provides an implemention of the <see cref="IGridBuilder{TModel}" /> that is used to construct the grid through a
/// fluent API.
/// </summary>
/// <typeparam name="TModel">The type of the model that the grid will hold.</typeparam>
public class GridBuilder<TModel> : IGridBuilder<TModel>
{
#region Constructors
/// <summary>
/// Creates a new instance of the <see cref="GridBuilder{TModel}" />.
/// </summary>
/// <param name="helper">The <see cref="HtmlHelper" /> that is used to construct the grid.</param>
/// <param name="dataSource">The collection of objects that will be bound to the grid.</param>
public GridBuilder(HtmlHelper helper, IEnumerable<TModel> dataSource)
{
htmlHelper = helper;
DataSource = dataSource;
Constructor = new GridConstructor<TModel>(htmlHelper, DataSource);
}
#endregion
#region IGridBuilder Members
/// <summary>
/// Gets the name of the <see cref="IGridBuilder{TModel}" />.
/// </summary>
public string Name { get; private set; }
/// <summary>
/// Gets the constructor that will be used to construct this <see cref="IGridBuilder{TModel}" />.
/// </summary>
public IGridContructor<TModel> Constructor { get; set; }
/// <summary>
/// Gets the source that will be bound to the implemented object.
/// </summary>
public IEnumerable<TModel> DataSource { get; private set; }
/// <summary>
/// Gets the <see cref="HtmlHelper" /> object.
/// </summary>
public HtmlHelper htmlHelper { get; private set; }
/// <summary>
/// Sets the name of the <see cref="IGridBuilder{TModel}" />. This name will be used as an id on the outer element that
/// holds the entire grid.
/// </summary>
/// <param name="name">The name that the <see cref="IGridBuilder{TModel}" /> should have.</param>
/// <returns>An <see cref="IGridBuilder{TModel}" /> that can be used to construct the grid through a fluent API.</returns>
public IGridBuilder<TModel> WithName(string name)
{
Name = name;
return this;
}
/// <summary>
/// Set the columns of the model that should be bound to grid.
/// </summary>
/// <param name="bindAllColumns">The action that will bind all the columns.</param>
/// <returns>An <see cref="IGridBuilder{TModel}" /> that is used to construct the grid.</returns>
public IGridBuilder<TModel> WithColumns(Action<IColumnBinder<TModel>> bindAllColumns)
{
var columnBinder = new ColumnBinder<TModel>(Constructor);
bindAllColumns(columnBinder);
return this;
}
/// <summary>
/// Renders the grid with all the set properties.
/// </summary>
/// <returns>A <see cref="MvcHtmlString" /> that contains the HTML representation of the grid.</returns>
public MvcHtmlString Render()
{
var outputBuilder = new StringBuilder();
BaseElementBuilder parentElement = DivFactory.DivElement().WithCss("header");
outputBuilder.Append(parentElement.ToString(TagRenderMode.StartTag));
outputBuilder.Append(parentElement.ToString(TagRenderMode.EndTag));
return new MvcHtmlString(outputBuilder.ToString());
}
#endregion
}
Then an implementation of the IGridColumnBinder:
/// <summary>
/// Provides an implementation of the <see cref="IColumnBinder{TModel}" /> that can be used to construct a column
/// through a fluent API.
/// </summary>
/// <typeparam name="TModel">The type of the datasource that's bound to the grid.</typeparam>
public class ColumnBinder<TModel> : IColumnBinder<TModel>
{
#region Constructors
/// <summary>
/// Creates a new instance of the <see cref="ColumnBinder{TModel}" />.
/// </summary>
/// <param name="constructor">An <see cref="IGridContructor{TModel}" /> that contains the builder to construct the grid.</param>
public ColumnBinder(IGridContructor<TModel> constructor)
{
Constructor = constructor;
}
#endregion
#region IColumnBinder Members
/// <summary>
/// Gets the values that are bound to this <see cref="IColumnBinder{TModel}" />.
/// </summary>
public IGridContructor<TModel> Constructor { get; private set; }
/// <summary>
/// Gets the css class of the <see cref="IColumnBinder{TModel}" />.
/// </summary>
public string CssClass { get; private set; }
/// <summary>
/// Gets the values that are bound to this <see cref="IColumnBinder{TModel}" />.
/// </summary>
public IList<object> Values { get; set; }
/// <summary>
/// Binds an column to the grid.
/// </summary>
/// <typeparam name="TItem">The type of the column on which to bind the items.</typeparam>
/// <param name="propertySelector">The functional that will bind the control to the grid.</param>
/// <returns>As <see cref="IColumnBinder{TModel}" /> that is used to construct this column through a fluent API.</returns>
public IColumnBinder<TModel> Bind<TItem>(Expression<Func<TModel, TItem>> propertySelector)
{
string name = ExpressionHelper.GetExpressionText(propertySelector);
name = Constructor.htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(() => default(TModel),
typeof (TModel), name);
// Get's the name to display on the column in grid. The Display attribute is used if present, otherwise the name of the property is used.
string displayName = string.IsNullOrEmpty(metadata.DisplayName)
? metadata.PropertyName
: metadata.DisplayName;
Values =
Constructor.DataSource.Select(myVar => propertySelector.Compile()(myVar))
.Select(dummy => (object) dummy)
.ToList();
Constructor.builderProperties.Add(displayName, this);
return this;
}
/// <summary>
/// Apply a specific css class on an element.
/// </summary>
/// <param name="className">The name of the css class that should be placed on the element.</param>
/// <returns>As <see cref="IColumnBinder{TModel}" /> that is used to construct this column through a fluent API.</returns>
public IColumnBinder<TModel> WithCss(string className)
{
CssClass = className;
return this;
}
#endregion
}
And as last the implementation of the IGridConstructor.
/// <summary>
/// Provides an implemention of the <see cref="IGridContructor{TModel}" /> that is used to construct the grid through a
/// fluent API.
/// </summary>
/// <typeparam name="TModel">The type of the model that the grid will hold.</typeparam>
public class GridConstructor<TModel> : IGridContructor<TModel>
{
#region Constructors
/// <summary>
/// Creates a new instance of the <see cref="GridConstructor{TModel}" />.
/// </summary>
/// <param name="helper">The <see cref="HtmlHelper" /> that is used to built the model.</param>
/// <param name="source">The model that is bound to the grid.</param>
public GridConstructor(HtmlHelper helper, IEnumerable<TModel> source)
{
htmlHelper = helper;
DataSource = source;
builderProperties = new Dictionary<string, IColumnBinder<TModel>>();
}
#endregion
#region Properties
/// <summary>
/// Provides a dictionary that contains all the properties for the builder.
/// </summary>
public IDictionary<string, IColumnBinder<TModel>> builderProperties { get; set; }
/// <summary>
/// Gets the source that will be bound to the implemented object.
/// </summary>
public IEnumerable<TModel> DataSource { get; private set; }
/// <summary>
/// Gets the <see cref="HtmlHelper" /> object.
/// </summary>
public HtmlHelper htmlHelper { get; private set; }
#endregion
}
Now, how does this works exactely?
The HtmlHelper returns an member that implements an IGridBuilder, so in the example above, it returns a GridBuilder.
On that GridBuilder, there are a couple of elements that you can call, and one more important is the WithColumns method that takes a IColumnBinder action and there's the trick. The implementation of IColumnBinder takes a reference to an IGridConstructor. And it's that constructor that will be fully built up.
So all the things we need to know for rendering, including each css class for a given column are exposed through the GridBuilder.GridContructor
So, a very vert long post, but I hope it helps some people.

Implementing IList facade, but GetEnumerator returns a count of 0

I'm a little bit new to programming in .NET, and there's a little bit of a problem that's truly confusing me.
I am trying to implement the IList<T> interface in one of my classes. I wanted to keep things simple for now, so I simply used the functionality of a declared List<T> field for the methods and added some custom data processing in between.
Most of the methods work well, and the field list gets added to as expected. However, when I try to use the class as a list (e.g. class.ForEach()), it returns a Count of 0. This is despite the internal List<T> having a count of 25.
I'm sure this is really simple, and I apologise if this question is a waste of database space, but it's something that's really holding me back. Can any of you guys help me solve it?
I'll post my current code to show what I'm trying to do:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reflection;
using ClubEventsData;
using ClubEventsData.Attributes;
using ClubEventsDataHandling.System.Implementations.Conversion;
using Telerik.Sitefinity.DynamicModules.Model;
namespace ClubEventsDataHandling.System.Implementations
{
public class DataRepository<T> : IDataRepository<T> where T : ClubEventData, new()
{
private readonly PropertyDictionaryBasedDynamicConverter<T> _converter;
private readonly IDataHandler _dataHandler;
private readonly List<T> _storageList;
/// <summary>
/// Initializes a new instance of the DataRepository class.
/// </summary>
public DataRepository()
{
_dataHandler = new SitefinityBasedDataHandler<T>();
_converter = new PropertyDictionaryBasedDynamicConverter<T>();
_storageList = new List<T>();
// Populate the data repository.
foreach (DynamicContent dynamicContent in _dataHandler.Get())
{
_storageList.Add(_converter.ConvertToModel(dynamicContent));
}
}
#region Implementation of IEnumerable
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>
/// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
/// </returns>
/// <filterpriority>1</filterpriority>
public IEnumerator<T> GetEnumerator()
{
return _storageList.GetEnumerator();
}
#endregion
#region Implementation of ICollection<T>
/// <summary>
/// Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1"/>.
/// </summary>
/// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.</exception>
public void Add(T item)
{
// Create the data item.
T modelItem = CreateDataItem(item);
// Add the item to the list.
_storageList.Add(modelItem);
}
/// <summary>
/// Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1"/>.
/// </summary>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only. </exception>
public void Clear()
{
throw new NotImplementedException();
}
/// <summary>
/// Determines whether the <see cref="T:System.Collections.Generic.ICollection`1"/> contains a specific value.
/// </summary>
/// <returns>
/// true if <paramref name="item"/> is found in the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false.
/// </returns>
/// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param>
public bool Contains(T item)
{
// Determine if the database contains the item.
return _dataHandler.Get(item.MasterContentID) != null;
}
/// <summary>
/// Copies the elements of the <see cref="T:System.Collections.Generic.ICollection`1"/> to an <see cref="T:System.Array"/>, starting at a particular <see cref="T:System.Array"/> index.
/// </summary>
/// <param name="array">The one-dimensional <see cref="T:System.Array"/> that is the destination of the elements copied from <see cref="T:System.Collections.Generic.ICollection`1"/>. The <see cref="T:System.Array"/> must have zero-based indexing.</param><param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param><exception cref="T:System.ArgumentNullException"><paramref name="array"/> is null.</exception><exception cref="T:System.ArgumentOutOfRangeException"><paramref name="arrayIndex"/> is less than 0.</exception><exception cref="T:System.ArgumentException">The number of elements in the source <see cref="T:System.Collections.Generic.ICollection`1"/> is greater than the available space from <paramref name="arrayIndex"/> to the end of the destination <paramref name="array"/>.</exception>
public void CopyTo(T[] array, int arrayIndex)
{
_storageList.CopyTo(array, arrayIndex);
}
/// <summary>
/// Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1"/>.
/// </summary>
/// <returns>
/// true if <paramref name="item"/> was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false. This method also returns false if <paramref name="item"/> is not found in the original <see cref="T:System.Collections.Generic.ICollection`1"/>.
/// </returns>
/// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.</exception>
public bool Remove(T item)
{
// Determine if the master content ID has been set.
ValidateItem(item);
// Delete the item from the database.
_dataHandler.Delete(item.MasterContentID);
// Remove the item from the database.
return _storageList.Remove(item);
}
/// <summary>
/// Gets the number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"/>.
/// </summary>
/// <returns>
/// The number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"/>.
/// </returns>
public int Count { get; private set; }
/// <summary>
/// Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.
/// </summary>
/// <returns>
/// true if the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only; otherwise, false.
/// </returns>
public bool IsReadOnly { get; private set; }
/// <summary>
/// Determines whether the <see cref="T:System.Collections.Generic.ICollection`1"/> contains a specific value.
/// </summary>
/// <returns>
/// true if <paramref name="itemID"/>'s item is found in the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false.
/// </returns>
/// <param name="itemID">The ID of the object to locate in the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param>
public bool Contains(Guid itemID)
{
return _dataHandler.Get(itemID) != null;
}
///<summary>Creates the data item in the database and returns the model version of it.</summary>
private T CreateDataItem(T item)
{
// Get the properties of the item.
Dictionary<string, object> propertyDictionary = GetItemProperties(item);
// Create the data representation of the item.
Guid createdItemID = _dataHandler.Create(propertyDictionary);
// Convert a new model item from the dynamic content.
DynamicContent dynamicContent = _dataHandler.Get(createdItemID);
T modelItem = _converter.ConvertToModel(dynamicContent);
return modelItem;
}
///<summary>Gets the properties of the item as a dictionary that is ready to input into data handler methods.</summary>
private static Dictionary<string, object> GetItemProperties(T item)
{
var propertyDictionary = new Dictionary<string, object>();
// Filter the properties according to the mapping attribute.
foreach (PropertyInfo property in item.GetType().GetProperties())
{
// Get the mapping attribute.
object[] customMappingAttributes = property.GetCustomAttributes(typeof (ClubEventDataMappingAttribute), true);
var mappingAttribute = customMappingAttributes[0] as ClubEventDataMappingAttribute;
// Apply the action, depending on the mapping attribute.
if (mappingAttribute != null && mappingAttribute.MappingEnabled)
{
string mappingValue = mappingAttribute.MappingName != null && string.IsNullOrEmpty(mappingAttribute.MappingName) ? property.Name : mappingAttribute.MappingName;
if (mappingValue != null)
{
propertyDictionary.Add(mappingValue, property.GetValue(item, null));
}
}
// Old code, in case didn't work.
/*if ()
{
propertyDictionary.Add(property.Name, property.GetValue(item, null));
}*/
}
return propertyDictionary;
}
/// <summary>
/// Performs validation operations on the item.
/// </summary>
private static void ValidateItem(T item)
{
// Check that the ID is present on the item.
if (item.MasterContentID == Guid.Empty)
{
throw new ContentIDNotSetException("The master content ID was not set for this exception.");
}
}
#endregion
#region Implementation of IList<T>
/// <summary>
/// Determines the index of a specific item in the <see cref="T:System.Collections.Generic.IList`1"/>.
/// </summary>
/// <returns>
/// The index of <paramref name="item"/> if found in the list; otherwise, -1.
/// </returns>
/// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.IList`1"/>.</param>
public int IndexOf(T item)
{
return _storageList.IndexOf(item);
}
/// <summary>
/// Inserts an item to the <see cref="T:System.Collections.Generic.IList`1"/> at the specified index.
/// </summary>
/// <param name="index">The zero-based index at which <paramref name="item"/> should be inserted.</param><param name="item">The object to insert into the <see cref="T:System.Collections.Generic.IList`1"/>.</param><exception cref="T:System.ArgumentOutOfRangeException"><paramref name="index"/> is not a valid index in the <see cref="T:System.Collections.Generic.IList`1"/>.</exception><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IList`1"/> is read-only.</exception>
public void Insert(int index, T item)
{
// Create a new data item based off the properties of this one.
T modelItem = CreateDataItem(item);
// Insert the model item into the list.
_storageList.Insert(index, modelItem);
}
/// <summary>
/// Removes the <see cref="T:System.Collections.Generic.IList`1"/> item at the specified index.
/// </summary>
/// <param name="index">The zero-based index of the item to remove.</param><exception cref="T:System.ArgumentOutOfRangeException"><paramref name="index"/> is not a valid index in the <see cref="T:System.Collections.Generic.IList`1"/>.</exception><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IList`1"/> is read-only.</exception>
public void RemoveAt(int index)
{
// Get the item at the index.
T item = this[index];
// Validate the item ID.
ValidateItem(item);
// Delete the item from the database.
_dataHandler.Delete(item.MasterContentID);
// Remove the item from the list.
_storageList.RemoveAt(index);
}
/// <summary>
/// Gets or sets the element at the specified index.
/// </summary>
/// <returns>
/// The element at the specified index.
/// </returns>
/// <param name="index">The zero-based index of the element to get or set.</param><exception cref="T:System.ArgumentOutOfRangeException"><paramref name="index"/> is not a valid index in the <see cref="T:System.Collections.Generic.IList`1"/>.</exception><exception cref="T:System.NotSupportedException">The property is set and the <see cref="T:System.Collections.Generic.IList`1"/> is read-only.</exception>
public T this[int index]
{
get { return _storageList[index]; }
set
{
// Validate the specified content ID.
ValidateItem(value);
// Update the database instance with the item properties.
_dataHandler.Update(value.MasterContentID, GetItemProperties(value));
// Reflect the changes in the list.
_storageList[index] = value;
}
}
#endregion
#region Implementation of IEnumerable
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
/// </returns>
/// <filterpriority>2</filterpriority>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
}
And here is the method I use to call the repository:
using System.Collections.Generic;
using ClubEventsData;
using System.Linq;
using System;
namespace ClubEventsDataHandling.System.Implementations.Controllers
{
public class TicketDataController : IDataController
{
private DataRepository<Ticket> _repository;
public TicketDataController()
{
_repository = new DataRepository<Ticket>();
}
public List<Ticket> GetDateTickets(Guid dateID)
{
return _repository.Where(ticket => ticket.EventDateBoughtFor.MasterContentID == dateID).ToList();
}
}
}
The reason for _repository.Count being 0 is simply that you don't access your _storageList in the Count property. You need to change it to this:
public int Count
{
get { return _storageList.Count; }
}

WPF Property Grid

Is there a way, when binding to a Dependency Object class, to hide class properties?
I mean those "Dispatcher", "DependencyObjectType" and "IsSealed"?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Windows;
namespace WPGDemoApp
{
public class SampleObject : DependencyObject
{
readonly static DependencyProperty IdProperty = DependencyProperty.Register("ID", typeof(int), typeof(SampleObject));
public int ID
{
get { return (int)GetValue(IdProperty); }
set
{
SetValue(IdProperty, value);
}
}
public string Name
{
get { return "Leeroy Jenkins"; }
}
}
}
Jesse, your question not very clear but if I understand it correctly you just want to show the properties present in your SampleObject i.e. those added by you and not the ones inherited from its base class.
1. check how your PropertyGrid is getting the properties of the selected object -
it should fetch the properties of selected objects type and not for object itself -
Type type = this.SelectedItem.GetType();
properties =TypeDescriptor.GetProperties(type);
instead of -
properties = TypeDescriptor.GetProperties(this.SelectedItem);
2. If this doesn't solve the problem or if you want more control over which properties to show in your PG then you can create a custom attribute. To achieve this I have created a custom attribute(similar to IsBrowsable), you just need to decorate your property with that attribute and modify your Property Grid implementation to honor it.
Here is the attribute class -
/// <summary>
/// Attribute to identify the Custom Proeprties.
/// Only Proeprties marked with this attribute(true) will be displayed in property grid.
/// </summary>
[global::System.AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
public sealed class IsCustomPropertyAttribute : Attribute
{
// See the attribute guidelines at
// http://go.microsoft.com/fwlink/?LinkId=85236
private bool isCustomProperty;
public static readonly IsCustomPropertyAttribute Default = new IsCustomPropertyAttribute(false);
public static readonly IsCustomPropertyAttribute No = new IsCustomPropertyAttribute(false);
public static readonly IsCustomPropertyAttribute Yes = new IsCustomPropertyAttribute(true);
/// <summary>
/// Initializes a new instance of the <see cref="IsCustomPropertyAttribute"/> class.
/// </summary>
/// <param name="isCustomProperty">if set to <c>true</c> [is RT display property].</param>
public IsCustomPropertyAttribute(bool isCustomProperty)
{
this.isCustomProperty = isCustomProperty;
}
/// <summary>
/// Gets a value indicating whether this instance is RT display property.
/// </summary>
/// <value>
/// <c>true</c> if this instance is RT display property; otherwise, <c>false</c>.
/// The default is false.
/// </value>
public bool IsCustomProperty
{
get { return isCustomProperty; }
set { isCustomProperty = value; }
}
/// <summary>
/// Determines whether the specified <see cref="System.Object"/> is equal to this instance.
/// </summary>
/// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
/// <returns>
/// <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(object obj)
{
IsCustomPropertyAttribute attribute = obj as IsCustomPropertyAttribute;
if (obj == null)
return false;
if (obj == this)
return true;
return attribute.isCustomProperty == isCustomProperty;
}
public override int GetHashCode()
{
return isCustomProperty.GetHashCode();
}
public override bool IsDefaultAttribute()
{
return isCustomProperty == IsCustomPropertyAttribute.Default.isCustomProperty;
}
}
In your property grid, add a check before adding the property to grid. something like this -
// Gets the attributes for the property.
AttributeCollection attributes = propertyDescriptor.Attributes;
//Checks to see if the value of the IsCustomPropertyAttribute is Yes.
IsCustomPropertyAttribute myAttribute =
(IsCustomPropertyAttribute)attributes[typeof(IsCustomPropertyAttribute)];
//Check if current property is CustomProperty or not
if (myAttribute.IsCustomProperty == true)
{
AddProperty(propertyDescriptor);
}
This worked for expandable objects which were dependency objects. I just created a simple marker Attribute called ViewablePropertyAttribute. I didn't want all the dependency objects to be available in the grid.
public class EllevateExpandableObjectConverter : ExpandableObjectConverter
{
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
var propertyDescriptors =
base.GetProperties(context, value, attributes.OfType<ViewablePropertyAttribute>().Cast<Attribute>().ToArray());
var result = propertyDescriptors.Cast<PropertyDescriptor>()
.Where(pd => pd.Attributes.OfType<ViewablePropertyAttribute>().Any())
.ToArray();
return new PropertyDescriptorCollection(result);
}
}
[ViewablePropertyAttribute]
[TypeConverter(typeof(EllevateExpandableObjectConverter)]
public MyComplexType MyInstance {get;set; }

Using Windows Forms Combo Box with List<KeyValuePair<UserEnum,String>> as DataSource - C#

I'm currently working on a Windows Forms GUI and I have a Combo that I need to display a list of string values as the DisplayMembers and use a list of user defined enum values as the ValueMember. I'm currently returning a List> from my database access function and I would like to bind this to my Combo box. I've tried assigning the list to the .DataSource property, assigning "Key" to .DataMember and "Value" to .DisplayMember. This is clearly not a valid approach as it is not working.
Can someone please give me another approach that is in good form and actually works?
Thanks
I do use my own class EnumPair<> in combination with two extension methods to bind comboboxes to Properties with enum types.
See if this can help you, that you can work directly with the enums.
Use it like this after implementation:
comboBox.BindToEnumValue<MyEnumType>(myBindingSourceInstance, "PropertyNameOfBindingSource");
That assumes you have a ComboBox named "comboBox" on your form, an Enum called "MyEnumType" and an instance of a BindingSource. The PropertyNameOfBindingSource should be the name of the Property of the type that your BindingSource has a list of, that has the PropertyType of MyEnumType.
Implementation for the background work is found below, the extension methods are not needed, i just do not like write nearly identical lines of code ;-)
public static class ComboBoxExtensions
{
public static void BindToEnumValue<TEnum>(this ComboBox cbo, BindingSource bs, string propertyName)
{
cbo.DataSource = EnumPair<TEnum>.GetValuePairList();
cbo.ValueMember = EnumPair<TEnum>.ValueMember;
cbo.DisplayMember = EnumPair<TEnum>.DisplayMember;
cbo.DataBindings.Add(new Binding("SelectedValue", bs, propertyName));
}
public static void BindClear(this ComboBox cbo)
{
cbo.DataSource = null;
cbo.DataBindings.Clear();
}
}
/// <summary>
/// Represents a <see cref="EnumPair"/> consisting of an value
/// of an enum T and a string represantion of the value.
/// </summary>
/// <remarks>
/// With this generic class every <see cref="Enum"/> can be
/// dynamically enhanced by additional values, such as an empty
/// entry, which is usefull in beeing used with
/// <see cref="ComboBox"/>es.
/// </remarks>
/// <typeparam name="T">The type of the <see cref="Enum"/> to represent.</typeparam>
public partial class EnumPair<T>
{
#region Constants
public const string ValueMember = "EnumValue";
public const string DisplayMember = "EnumStringValue";
#endregion
#region Constructor
/// <summary>
/// Initializes a new instance of the <see cref="EnumPair"/> class.
/// </summary>
public EnumPair()
{
Type t = typeof(T);
if (!t.IsEnum)
{
throw new ArgumentException("Class EnumPair<T> can only be instantiated with Enum-Types!");
}
}
/// <summary>
/// Initializes a new instance of the <see cref="EnumPair"/> class.
/// </summary>
/// <param name="value">The value of the enum.</param>
/// <param name="stringValue">The <see cref="string"/> value of the enum.</param>
public EnumPair(T value, string stringValue)
{
Type t = typeof(T);
if (!t.IsEnum)
{
throw new ArgumentException("Class EnumPair<T> can only be instantiated with Enum-Types!");
}
this.EnumValue = value;
this.EnumStringValue = stringValue;
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the value part of the <see cref="EnumPair"/>.
/// </summary>
public T EnumValue { get; set; }
/// <summary>
/// Gets or sets the string value of the <see cref="EnumPair"/>.
/// </summary>
public string EnumStringValue { get; set; }
#endregion
#region Methods
/// <summary>
/// Returns a <see cref="string"/> that represents the current <see cref="EnumPair"/>.
/// </summary>
public override string ToString()
{
return this.EnumStringValue;
}
/// <summary>
/// Generates a <see cref="List<T>"/> of the values
/// of the <see cref="Enum"/> T.
/// </summary>
public static List<EnumPair<T>> GetValuePairList()
{
List<EnumPair<T>> list = new List<EnumPair<T>>();
EnumPair<T> pair = new EnumPair<T>();
foreach (var item in Enum.GetValues(typeof(T)))
{
pair = new EnumPair<T>();
pair.EnumValue = (T)item;
pair.EnumStringValue = ((T)item).ToString();
list.Add(pair);
}
return list;
}
/// <summary>
/// Implicit conversion from enum value to <see cref="EnumPair<>"/> from that enum.
/// </summary>
/// <param name="e">The enum value to convert to.</param>
/// <returns>A <see cref="EnumPair<>"/> to the enum value.</returns>
public static implicit operator EnumPair<T>(T e)
{
Type t = typeof(EnumPair<>).MakeGenericType(e.GetType());
return new EnumPair<T>((T)e, ((T)e).ToString());
}
#endregion
}
You can try something like this:
ddTemplates.DataSource =
Enum.GetValues(typeof(EmailTemplateType))
.Cast<EmailTemplateType>().ToList()
.Select(v => new KeyValuePair<int, string>((int)v, v.ToString())).ToList();

Categories