I am trying to create a specific HtmlHelper table extension to reduce the spaghetti code in my View.
Taking a list of domain objects I would like to display a table that is a little bit more intelligent in using the properties of the domain object as columns. In addition, I would like to disable showing of some properties as columns. An idea would be to decorate properties with attributes that tell it not to be shown.
Hopefully that makes sense but here's where I got to so far...
public static string MyTable(this HtmlHelper helper, string name,
IList<MyObject> items, object tableAttributes)
{
if (items == null || items.Count == 0)
return String.Empty;
StringBuilder sb = new StringBuilder();
BuildTableHeader(sb, items[0].GetType());
//TODO: to be implemented...
//foreach (var i in items)
// BuildMyObjectTableRow(sb, i);
TagBuilder builder = new TagBuilder("table");
builder.MergeAttributes(new RouteValueDictionary(tableAttributes));
builder.MergeAttribute("name", name);
builder.InnerHtml = sb.ToString();
return builder.ToString(TagRenderMode.Normal);
}
private static void BuildTableHeader(StringBuilder sb, Type p)
{
sb.AppendLine("<tr>");
//some how here determine if this property should be shown or not
//this could possibly come from an attribute defined on the property
foreach (var property in p.GetProperties())
sb.AppendFormat("<th>{0}</th>", property.Name);
sb.AppendLine("</tr>");
}
//would be nice to do something like this below to determine what
//should be shown in the table
[TableBind(Include="Property1,Property2,Property3")]
public partial class MyObject
{
...properties are defined as Linq2Sql
}
So I was just wondering if anyone had any opinions/suggestions on this idea or any alternatives?
Looks good so far, but Gil Fink may have already done the work for you here: http://blogs.microsoft.co.il/blogs/gilf/archive/2009/01/13/extending-asp-net-mvc-htmlhelper-class.aspx
I strongly suggest to use MvcContrib's Grid. If you decide not to, at least you can take a look at how they solved the table generation interface problem.
After about an hour of work I was able to create what I wanted. My solution was to create an attribtue on the domain object class which specified which properties were visible in my table.
Based on the BindAttribute attribute in MVC 1.0 (having a look at the source code), I created a TableProperty attribute.
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class TableProperty : Attribute
{
private string m_include;
private string[] m_includeSplit;
public TableProperty()
{
m_includeSplit = new string[0];
}
public string Include
{
get
{
return (m_include ?? string.Empty);
}
set
{
m_include = value;
m_includeSplit = value.Split(',');
}
}
public bool IsPropertyAllowed(string propertyName)
{
return IsPropertyAllowed(propertyName, m_includeSplit);
}
internal static bool IsPropertyAllowed(string propertyName, string[] includeProperties)
{
return ((includeProperties == null) || (includeProperties.Length == 0)) || includeProperties.Contains<string>(propertyName, StringComparer.OrdinalIgnoreCase);
}
}
This allowed me to decorate my domain object with this attribute...
[TableProperty(Include="Property1,Property2,Property3")]
public partial class MyObject
{ ...
Then inside the BuildTableHeader I used reflection to get the properties of the object and match each property to the allowed list.
private static void BuildTableHeader(StringBuilder sb, Type p)
{
sb.AppendLine("<tr>");
TableProperty tp = p.GetCustomAttributes(typeof(TableProperty), true)[0];
foreach (var property in p.GetProperties())
if (tp.IsPropertyAllowed(property.Name))
sb.AppendFormat("<th>{0}</th>", property.Name);
Please note this solution worked for me in my little application however will be looking more at MvcContrib's Grid for a better implementation.
Related
I created some code from an XSD-schema-file using CodeDOM:
XmlSchemaImporter importer = new XmlSchemaImporter(schemas);
CodeNamespace code = new CodeNamespace(targetNamespace);
XmlCodeExporter exporter = new XmlCodeExporter(code);
foreach (XmlSchemaElement element in schema.Elements.Values)
{
XmlTypeMapping mapping = importer.ImportTypeMapping(element.QualifiedName);
exporter.ExportTypeMapping(mapping);
}
Now within my post-processing I realized that this code will generate properties like this:
bool prop1Field;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=0)]
public bool Prop1
{
get
{
return prop1Field;
}
set
{
prop1Field = value;
}
}
But I want the generator to simply produce fields instead. Is there a way to achieve this? I know xsd.exe also produces fields when using the /f-argument.
EDIT: Afterwards I want to replace those by auto-properties. To do so with the current approach I´d have to delete the backing-field from the property and all its occurences within the generated code. If CodeDOM however generates a public field in the first place all I had to do is to delete this field, create a new property with the same name using CodeSnippedTypeMember as shown in this answer. Thus I won´t need to search the codes for occurences of the private backing-field and replace them by calls to the property.
No, there´s no option to be set to allow this as it shouldn´t matter for consuming code if your attribute is a field or a property (assuming we don´t use reflection) and public fields are considered bad practice.
Anyway I found a way to achieve this by copying all the comments and attributes of the property to the private backing-field and make the field public. However remember that doing so is mostly a bad design-idea.
public void Process(CodeNamespace code, XmlSchema schema)
{
foreach (var type in code.Types.Cast<CodeTypeDeclaration>().Where(x => !x.IsEnum))
{
var result = new List<CodeMemberField>();
var properties = type.Members.OfType<CodeMemberProperty>().ToList();
foreach (var property in properties)
{
ReplacePropertyByField(type, property);
}
}
}
private static void ReplacePropertyByField(CodeTypeDeclaration type, CodeMemberProperty property)
{
var backingField = GetBackingField(property, type);
backingField.Comments.AddRange(property.Comments);
backingField.Attributes = property.Attributes;
backingField.CustomAttributes = property.CustomAttributes;
backingField.Name = property.Name;
type.Members.Remove(property);
}
private static CodeMemberField GetBackingField(CodeMemberProperty property, CodeTypeDeclaration type)
{
var getterExpression = ((CodeMethodReturnStatement)property.GetStatements[0]).Expression;
var backingFieldName = ((CodeFieldReferenceExpression)getterExpression).FieldName;
return type.Members.OfType<CodeMemberField>().Single(x => x.Name == backingFieldName);
}
I'm currently struggling with finding a better way to populate my ViewModel objects with my Entitiy objects. I have the following Web Api controller method:
[HttpGet]
public IEnumerable<ClientSearchViewModel> FindClients(string query)
{
var clients = _clientService.SearchClient(query).ToList();
var returnClients = new List<ClientSearchViewModel>();
foreach (var client in clients)
{
returnClients.Add(new ClientSearchViewModel(client));
}
return returnClients;
}
And I'm doing this in my ClientSearchViewModel constructor:
public ClientSearchViewModel(Client client)
{
this.Id = client.Id;
this.FirstName = client.PersonName.FirstName;
this.LastName = client.PersonName.LastName;
}
Is there another way other than going through the list of returned objects and creating a new ViewModel list?
I strongly suggest use of a mapping plugin for this, such as:
AutoMapper
or
ValueInjector
Plugins like this will allow you to map between the objects being used internally or in your data layer, with your external objects (DTOs/ViewModels). They handle a number of things out of the box such as automatic mapping of any like named properties with the same type, but also allow for a lot of control in the specific mapping of properties or types, for those times when you need something more custom.
For a brief comparison of the two, there isn't much better than hearing the authors themselves respond: AutoMapper vs ValueInjecter
Personally, I find ValueInjector to be quicker to use, while having more control overall, but I also find it to be much less readable/inuitive than AutoMapper, which can require a bit more code to accomplish similar goals. As such, I'd pick the one that you find you and/or your team will prefer the syntax of and how easily you can grasp the concepts vs how much power you really need.
So I had the same miff... I can't say that I've benchmarked my solution, but it does seem to run reasonably fast...
3 bits:
public static T Transform<T>(this object convertFrom) where T : class, new()
{
return (T) (new ServiceExtension().Transform(convertFrom, typeof (T)));
}
private class ServiceExtension
{
public object Transform(object convertFrom, Type convertTo)
{
object _t = Activator.CreateInstance(convertTo);
if (convertFrom == null) return _t;
var convertType = convertFrom.GetType();
foreach (
var property in _t.GetType().GetProperties().Where(f => f.CanWrite && f.GetSetMethod(true).IsPublic)
)
{
if (property.GetCustomAttributes(typeof (TransformAttribute), true).Any())
{
var transform =
(property.GetCustomAttributes(typeof (TransformAttribute), true).FirstOrDefault() as
TransformAttribute);
var transformname = transform.RelatedField ?? property.Name;
if (convertType.GetProperty(transformname) == null)
throw new ArgumentException(
string.Format(
"We were unable to find property:\"{0}\" on {1}. Please check the RelativeField value on the {2} for \"{0}\"",
transformname, convertFrom.GetType().Name, convertTo.Name));
var theValue = convertType.GetProperty(transformname).GetValue(convertFrom);
if (isCollection(theValue))
{
foreach (var item in (theValue as ICollection))
{
var someVal = new object();
var newToType = property.PropertyType.GetGenericArguments().FirstOrDefault();
if (!String.IsNullOrEmpty(transform.FullyQualifiedName))
someVal =
Transform(
item.GetType().GetProperty(transform.FullyQualifiedName).GetValue(item),
newToType);
else
someVal = Transform(item, newToType);
if (property.GetValue(_t) == null)
throw new NullReferenceException(
string.Format(
"The following property:{0} is null on {1}. Likely this needs to be initialized inside of {1}'s empty constructor",
property.Name, _t.GetType().Name));
property.PropertyType.GetMethod("Add")
.Invoke(property.GetValue(_t), new[] {someVal});
//property.SetValue(_t, theValue.Transform(theValue.GetType()));
}
}
else
property.SetValue(_t, theValue);
}
//property.SetValue(_t, property.GetValue(convertFrom, null), null);
}
return _t;
}
public bool isCollection(object o)
{
return o is ICollection
|| typeof (ICollection<>).IsInstanceOfType(o);
}
}
public class TransformAttribute : Attribute
{
public string RelatedField { get; private set; }
public string FullyQualifiedName { get; set; }
public TransformAttribute()
{
}
public TransformAttribute(string relatedField)
{
RelatedField = relatedField;
}
}
such that the end result is: myObject.Transform()
But the decorations let you account for differences between your POCO and your ViewModel
After retrieving data from a database I find myself doing this to create a domain object from the data in a DataRow (in this case, a DVD):
DataRow drDvd = myDataTable.Rows[0];
Dvd myDvd = new Dvd();
myDvd.id = drDvd.Field<long>("id");
myDvd.title = drDvd.Field<string>("title");
myDvd.description = drDvd.Field<string>("description");
myDvd.releaseDate = drDvd.Field<DateTime>("releaseDate");
As I soon felt of course, I am doing this over and over in pseudo-code:
myDvd.field = drDvd.Field<field.type>(field.name);
And I wondered if I could get it into a loop, however I've never used reflection before. The code I tried is this:
Dvd aDvd = new Dvd();
Type t = aDvd.GetType();
FieldInfo[] fields = t.GetFields();
foreach (FieldInfo fi in fields)
{
fi.SetValue(aDvd, drDvd.Field<fi.FieldType>(fi.Name));
}
The problem is, as you may know, that the extension for the Field method of class DataRow does not accept a variable and needs to be explicitely filled in.
I am not that experienced in C# so I would like to pose the following two questions:
Is it good practice what I am trying to do?
How can I fill in the correct extension for Field<extension>(name)?
You'll need to get the method info for the generic method, and call invoke on it. This way you can pass in the generic type to it programmatically. I'm on my phone, but it should look something like this:
MethodInfo mField = typeof(Dvd).GetMethod("Field");
MethodInfo genericMethod = mField.MakeGenericMethod(new Type[] { fi.FieldType });
GenericMethod.Invoke(aDvd,new Object[]{fi.Name});
It is usually a bad practice to use reflection when it is not really necessary. Because reflection methods are checked at runtime rather than compile time, faulty code is harder to track, because the compiler can't check for errors.
If I were you, id have a look at the Entity Framework, because youre basically mapping database data to domain objects. http://msdn.microsoft.com/en-us/library/aa697427%28v=vs.80%29.aspx
This is one of the way of constructing and populating your domain object
DataRow drDvd = new DataRow();
Dvd aDvd = new Dvd();
Type type = typeof(Dvd);
foreach (FieldInfo fi in type.GetFields())
{
fi.SetValue(aDvd, drDvd[fi.Name]);
}
Your approach of using DataRow.Field may be round about. In you case, it is not applicable.
Alternatively you can think about using one of the Entity frameworks (NHibernate, Microsoft EF etc) in your application.
I would do a custom attribute. In doing an attribute you are stuck with your field name being the same as the database. I currently use this in my current applications and it works great. It is very similar to Entity SQL.
public class SqlMetaAttribute : Attribute
{
public string ColumnName { get; set; }
}
Then you have your class like this
public class Person
{
[SqlMeta(ColumnName = "First_Name")]
publice string FirstName { get; set; }
[SqlMeta(ColumnName = "Last_Name")]
publice string LastName { get; set; }
}
You would then have a helper class with the same kind of functions. In this case I am assuming the outside caller is looping through the datatable. Making it generic using the template T makes this really reusable. Rather than just having a "DVD" type implementation and coping and pasting for another.
public static T CreateObjectFromRow<T>(DataRow row)
{
var newObject = new T();
if (row != null) SetAllProperties(row, newObject);
return newObject;
}
public static void SetAllProperties<T>(DataRow row, T newObject)
{
var properties = typeof(T).GetProperties();
foreach(var propertyInfo in properties)
{
SetPropertyValue(row, newObject, propertyInfo);
}
}
public static void SetPropertyValue(DataRow row, T newObject, PropertyInfo propertyInfo)
{
var columnAttribute = propertyInfo.FindAttribute<SqlMetaAttribute>();
if (columnAttribute == null) return;
// If the row type is different than the object type and exception will be thrown, but that is
// okay because if that happens you have to fix your object you are using, or might need some
// more custom code to help you with that.
propertyInfo.SetValue(newObject, row.GetValue<object>(columnAttribute.ColumnName), null);
}
// Extension method for row.GetValue<object> used above
public static T GetValue<T>(this DataRow row, string columnName)
{
if (row.ColumnNameNotFound(columnName) || row.Table.Columns[columnName] == null || row[columnName] is DBNull)
{
return default(T);
}
return (T)row[columnName];
}
I have an attribute for validation called Required() : BaseAttribute, and I can track it using the following code: (The BaseAttribute just implements the IsValid() Method.)
public static String Validate(object Object)
{
StringBuilder builder = new StringBuilder();
if (Object != null)
{
Type ObjectType = Object.GetType();
PropertyInfo[] Properties = ObjectType.GetProperties();
foreach (PropertyInfo Property in Properties)
{
object[] Attributes = Property.GetCustomAttributes(typeof(BaseAttribute), true);
foreach (object Attribute in Attributes)
builder.AppendLine(((BaseAttribute)Attribute).IsValid(Property, Object));
}
}
return builder.ToString();
}
The problem is, this works:
class roh {
[Required()]
public string dah { get; set; }
}
class main {
Console.WriteLine(Validate(new roh()));
}
but this doesn't:
class fus {
private roh _roh
public roh Roh {
get { if (_roh == null)
_roh = new roh;
return _roh; }
set { _roh = value; }
}
}
class roh {
[Required()]
public string Dah { get; set; }
}
class main {
Console.WriteLine(Validate(new fus()));
}
How can I modify my Validate method so that it can recursively find the custom attributes no matter how deep the object is?
You could use the Microsoft Enterprise Library. It has some build in validation blocks (similar in style to what you're using here) and does support recursive object validation down the object graph, I believe.
You reference the EntLib Validation DLL, and can either use the built-in validation or write your own. You can then validate it using a simple Validation.Validate(myObject) call.
Hope that might help :)
You already said the magic word - recursion. For every property you visit, call Validate on the object it stores, and voila.
One caveat is infinite recursion - this will work OK if your object graph is a tree. If it's more complex, you'll need to track which objects you have already visited.
I have a class with only string members like this :
public class MyClass
{
public string MyProp1 { get; set; }
public string MyProp2 { get; set; }
}
I create an instance :
Var myClass = new MyClass();
Later in the code, I’d like to know if all the member (MyProp1 and MyProp2) are not null or empty. I know I can use a if of course but there is much more properties than 2 in my real code.
Is there a way to do this ?
Thanks,
Using a dictionary based store for your properties is probably the easiest way of doing this:
public class MyClass
{
private IDictionary<String, String> _store;
public MyClass()
{
_store = new Dictionary<String, String>();
}
public string MyProp1 {
get { return GetOrDefault("MyProp1"); }
set { _store["MyProp1"] = value; }
}
public string MyProp2 {
get { return GetOrDefault("MyProp2"); }
set { _store["MyProp2"] = value; }
}
public Boolean HasData()
{
return _store.Any(x => !String.IsNullOrWhiteSpace(x.Value));
}
public Boolean IsEmpty()
{
return _store.All(x => String.IsNullOrWhiteSpace(x.Value));
}
private String GetOrDefault(String propertyName)
{
if (_store.ContainsKey(propertyName))
{
return _store[propertyName];
}
return String.Empty;
}
}
Another method for doing this would be to compare it with a default instance:
public class MyClass
{
public string MyProp1 { get; set; }
public string MyProp2 { get; set; }
public static readonly MyClass Empty = new MyClass();
public Boolean HasData()
{
return !Empty.Equals(this);
}
public Boolean IsEmpty()
{
return Empty.Equals(this);
}
}
You can try to use the reflect to check the properties. You should need confirm that all the properties are public, and the type is string. Here is the code.
public static bool IsNullOrEmpty(MyClass prop)
{
bool result = true;
PropertyInfo[] ps = prop.GetType().GetProperties();
foreach (PropertyInfo pi in ps)
{
string value = pi.GetValue(prop, null).ToString();
if (string.IsNullOrEmpty(value))
{
result = false;
break;
}
}
return result;
}
To check if your class contains 'any' properties which are null:
System.Reflection.PropertyInfo[] properties = myClass.GetType().GetProperties
(BindingFlags.Public | BindingFlags.Instance);
bool hasNullProperty = properties.Any(y => y.GetValue(x, null) == null);
You can always initialize your class like
public class MyClass
{
public MyClass() {
this.MyProp1 = this.MyProp2 = String.Empty;
}
public string MyProp1 { get; set; }
public string MyProp2 { get; set; }
}
and, unless your programmatically assign a null value to it, the new MyClass() will always have String.Empty in their 2 properties...
from comment:
What I do in those cases is call a helper, for example: string name = myHelper.CheckNode(xmlNode); and in that helper I check if it's null, any other check, I can easily tweek the helper method and it will be available to all elements, and you can extend it to support not only strings but all other data types as well
So, imagine that you are reading nodes from your XML, you write them like:
string name = myHelper.CheckNode(node);
in your helper, you could have something like:
XmlNodeList datasourceNodes = rootNode.SelectNodes("dataSources/dataSource");
foreach (XmlNode datasourceNode in datasourceNodes)
{
DataSource dataSource = new DataSource();
dataSource.Name = myHelper.CheckAttr(datasourceNode.Attributes["name"]);
dataSource.ODBC = myHelper.CheckNode(datasourceNode.SelectSingleNode("odbc"));
// or a variant (Extension Method)
dataSource.UID = datasourceNode.CheckNode("user");
dataSource.PWD = datasourceNode.CheckAttr("password");
ds.Add(dataSource);
}
your helper then could have a method like:
public static string CheckAttr(XmlAttribute attr)
{
return attr == null ? "" : attr.Value.Trim();
}
public static string CheckNode(XmlNode node)
{
return node == null ? "" : node.InnerText.Trim();
}
or for the variant (Extension Method)
public static string CheckAttr(this XmlNode, string attrName)
{
return attrName[attrName] == null ? "" : attrName[attrName].Value.Trim();
}
public static string CheckNode(this XmlNode, string nodeName)
{
return node.SelectSingleNode(nodeName) == null ?
"" :
node.SelectSingleNode(nodeName).InnerText.Trim();
}
If there are many properties in the class, one way of handling this is storing them in a collection, such as an array or a dictionary, instead of declaring each property as a separate member of the class.
Then you can access data in the dictionary by key, which is as easy as accessing a property of a class. And the advantage is that you can loop over the dictionary and check all the properties in a loop.
I would suggest creating a function in your class where you check String.IsNullOrEmpty(MyProp1) etc. for all your properties. This way you at least have gathered all the ckecking functionality in a single place. And you only have this place to modify whenever you add new properties.
To check if all the elements are set you could add a IsEmpty() method to your class that would check the internal properties. Then you wouldn't have to duplicate the if statements everywhere trough your code.
In your IsEmpty() method you can use a regular if statement to check all the fields or you can use reflection to automaticaly retrieve all string properties and check their values. The performance of reflection will be worse then a normal if check but if that's not a problem you can reuse the reflection code in all your entities to check their values.
By using Attribute Base programming you can achieve this. In this approach you will need to place attribute over the class member, and validation is can be done. You can also use Microsoft Enterprise Library for this.
Probably the best way would be to :
Restructure your properties in the form of a Dictionary of strings. Loop through the dictionary to test the strings using string.IsNullOrEmpty(). You could replace the N getter/setters by a single Indexer property which sets and retrieves the strings directly from the dictionary based on a key
You can use try the following style. I haven't tried it before but you might see if it helps
If (String.IsNullOrEmpty(string1 && string2 && string3))