The main difficulties were:
- validation
- centralized validation model
- being capable of providing multiple field specific error messages, instead of just the first one that is discovered
- mapping objects
- looking for a way to cope with Linq-To-Sql's entities, while maintaining persistance indepdendence
- Validation
- Investigate ways to push validation logic out to the client-side
- Decide on an extensibility option
- Partial classes
- Would this allow someone to implement additional functionality in another assembly? That would be against the design.
- Inheritance
- Everyone nowadays is avoiding inheritance in favor of interfaces
- Direct Script (not the generic template, the script specific to a business object type) modification
- Make the class generic so it can hold another object for additional functionality
The template BusinessObjectTemplate.tt:
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ Import Namespace="System.Linq" #>
<#+
public class BusinessPropertyT4
{
private readonly string _typeString;
public string TypeString { get { return _typeString; } }
public BusinessPropertyT4(string type )
{
_typeString = type;
}
public string BoolValidation { get; set; }
public string RuleViolationMessage { get; set; }
public bool CopyExclude {get; set;}
}
public class BusinessObjectTemplate : Template
{
public string BusinessName;
public IDictionary PropertyList;
public override string TransformText()
{
#>using System;
using System.Collections.Generic;
using BReusable;
namespace DefectSeverityAssessmentBusiness.<#=BusinessName #>
{
public class Model<#=BusinessName #>:I<#=BusinessName #>
{
#region I<#=BusinessName #> Members
<#+
PushIndent("\t");
PushIndent("\t");
foreach (string item in PropertyList.Keys)
{
WriteLine("public "+PropertyList[item].TypeString +" "+ item+" { get; set; }");
}
PopIndent();
PopIndent();
#>
#endregion
public IEnumerable GetRuleViolations()
{
<#+
PushIndent("\t");
PushIndent("\t");
foreach (string item in PropertyList.Keys)
{
if(string.IsNullOrEmpty(PropertyList[item].BoolValidation)==false
&& string.IsNullOrEmpty(PropertyList[item].RuleViolationMessage)==false)
{
WriteLine("\tif ("+PropertyList[item].BoolValidation+")");
WriteLine("\t\t yield return new RuleViolation(\""+PropertyList[item].RuleViolationMessage
+"\",Member.Name(x=>x."+item+"));");
}
}
PopIndent();
PopIndent();
#>
//if (Latitude == 0 || Longitude == 0)
// yield return new RuleViolation("Make sure to enter a valid address!", "Address");
yield break;
}
///
/// This is validated in a unit test to ensure accuracy and that it is not out of sync with
/// the number of members the interface has
///
/// name="T">
///
name="dest">
///
name="source">
///
name="includeIdentifier">
///
public static Dictionary Action> GenerateActionDictionary(T dest, I<#=BusinessName #> source, bool includeIdentifier)
where T : I<#=BusinessName #>
{
var result = new Dictionary Action>
{
<#+
PushIndent("\t");
PushIndent("\t");
PushIndent("\t");
PushIndent("\t");
foreach (string item in PropertyList.Keys)
{
if(PropertyList[item].CopyExclude==false)
{
WriteLine("\t{Member.Name(x=>x."+item+"),");
WriteLine("\t\t()=>dest."+item+"=source."+item+"},");
}
}
PopIndent();
PopIndent();
PopIndent();
PopIndent();
#>
};
return result;
}
///
/// Designed for copying the model to the db persistence object or ui display object
///
/// name="T">
///
name="creator">
///
name="source">
///
name="includeIdentifier">
///
name="excludeList">
///
public static T CopyData(Func creator, I<#=BusinessName #> source, bool includeIdentifier,
ICollection excludeList) where T : I<#=BusinessName #>
{
return CopyDictionary I<#=BusinessName #>>.CopyData(
GenerateActionDictionary, creator, source, includeIdentifier, excludeList);
}
///
/// Designed for copying the ui to the model
///
/// name="T">
///
name="validation">
///
name="creator">
///
name="source">
///
name="includeIdentifier">
///
name="excludeList">
///
public static T CopyData(IValidationDictionary validation, Func creator,
I<#=BusinessName #> source, bool includeIdentifier, ICollection excludeList)
where T : I<#=BusinessName #>
{
return CopyDictionary I<#=BusinessName #>>.CopyData(
GenerateActionDictionary, validation, creator, source, includeIdentifier, excludeList);
}
} // end class
public interface I<#=BusinessName #>
{
<#+
PushIndent("\t");
PushIndent("\t");
foreach (string item in PropertyList.Keys)
{
WriteLine(PropertyList[item].TypeString+" "+item+" { get; set; }");
}
PopIndent();
PopIndent();
#>
}
}//end namespace
<#+
PopIndent();
return this.GenerationEnvironment.ToString();
}
}
#>
<#@ import namespace="System.Collections.Generic" #>
<#@ Import Namespace="System.Linq" #>
<#+
public class BusinessPropertyT4
{
private readonly string _typeString;
public string TypeString { get { return _typeString; } }
public BusinessPropertyT4(string type )
{
_typeString = type;
}
public string BoolValidation { get; set; }
public string RuleViolationMessage { get; set; }
public bool CopyExclude {get; set;}
}
public class BusinessObjectTemplate : Template
{
public string BusinessName;
public IDictionary
public override string TransformText()
{
using System.Collections.Generic;
using BReusable;
namespace DefectSeverityAssessmentBusiness.<#=BusinessName #>
{
public class Model<#=BusinessName #>:I<#=BusinessName #>
{
#region I<#=BusinessName #> Members
<#+
PushIndent("\t");
PushIndent("\t");
foreach (string item in PropertyList.Keys)
{
WriteLine("public "+PropertyList[item].TypeString +" "+ item+" { get; set; }");
}
PopIndent();
PopIndent();
#>
#endregion
public IEnumerable
{
<#+
PushIndent("\t");
PushIndent("\t");
foreach (string item in PropertyList.Keys)
{
if(string.IsNullOrEmpty(PropertyList[item].BoolValidation)==false
&& string.IsNullOrEmpty(PropertyList[item].RuleViolationMessage)==false)
{
WriteLine("\tif ("+PropertyList[item].BoolValidation+")");
WriteLine("\t\t yield return new RuleViolation(\""+PropertyList[item].RuleViolationMessage
+"\",Member.Name
}
}
PopIndent();
PopIndent();
//if (Latitude == 0 || Longitude == 0)
// yield return new RuleViolation("Make sure to enter a valid address!", "Address");
yield break;
}
///
/// the number of members the interface has
///
///
///
name="dest">
///
name="source">
///
name="includeIdentifier">
///
public static Dictionary
where T : I<#=BusinessName #>
{
var result = new Dictionary
{
<#+
PushIndent("\t");
PushIndent("\t");
PushIndent("\t");
PushIndent("\t");
foreach (string item in PropertyList.Keys)
{
if(PropertyList[item].CopyExclude==false)
{
WriteLine("\t{Member.Name(x=>x."+item+"),");
WriteLine("\t\t()=>dest."+item+"=source."+item+"},");
}
}
PopIndent();
PopIndent();
PopIndent();
PopIndent();
#>
};
return result;
}
///
///
///
///
name="creator">
///
name="source">
///
name="includeIdentifier">
///
name="excludeList">
///
public static T CopyData
ICollection
{
return CopyDictionary
GenerateActionDictionary, creator, source, includeIdentifier, excludeList);
}
///
///
///
///
name="validation">
///
name="creator">
///
name="source">
///
name="includeIdentifier">
///
name="excludeList">
///
public static T CopyData
I<#=BusinessName #> source, bool includeIdentifier, ICollection
where T : I<#=BusinessName #>
{
return CopyDictionary
GenerateActionDictionary, validation, creator, source, includeIdentifier, excludeList);
}
} // end class
public interface I<#=BusinessName #>
{
<#+
PushIndent("\t");
PushIndent("\t");
foreach (string item in PropertyList.Keys)
{
WriteLine(PropertyList[item].TypeString+" "+item+" { get; set; }");
}
PopIndent();
PopIndent();
#>
}
}//end namespace
<#+
PopIndent();
return this.GenerationEnvironment.ToString();
}
}
#>
The first script ModelRegistrationTemplate.tt
<#@ template language="C#v3.5" hostspecific="True" debug="True" #>
<#@ output extension="cs" #>
<#@ include file="T4Toolbox.tt" #>
<#@ include file="../BusinessObjectTemplate.tt" #>
<#
BusinessObjectTemplate template = new BusinessObjectTemplate();
template.BusinessName="Registration";
template.PropertyList=new Dictionary{
{"UserName",new BusinessPropertyT4("string"){BoolValidation="String.IsNullOrEmpty(UserName)",
RuleViolationMessage="UserName is required"/*,CopyExclude=true */}},
{"Name",new BusinessPropertyT4("string")},
{"Email",new BusinessPropertyT4("string")},
{"MailCode",new BusinessPropertyT4("string")},
{"TelephoneNumber",new BusinessPropertyT4("string")},
{"OrganizationId",new BusinessPropertyT4("int?")},
{"OrganizationSponsorId",new BusinessPropertyT4("int?")},
};
template.Render();
#>
<#@ output extension="cs" #>
<#@ include file="T4Toolbox.tt" #>
<#@ include file="../BusinessObjectTemplate.tt" #>
<#
BusinessObjectTemplate template = new BusinessObjectTemplate();
template.BusinessName="Registration";
template.PropertyList=new Dictionary
{"UserName",new BusinessPropertyT4("string"){BoolValidation="String.IsNullOrEmpty(UserName)",
RuleViolationMessage="UserName is required"/*,CopyExclude=true */}},
{"Name",new BusinessPropertyT4("string")},
{"Email",new BusinessPropertyT4("string")},
{"MailCode",new BusinessPropertyT4("string")},
{"TelephoneNumber",new BusinessPropertyT4("string")},
{"OrganizationId",new BusinessPropertyT4("int?")},
{"OrganizationSponsorId",new BusinessPropertyT4("int?")},
};
template.Render();
And the final output:
using System;
using System.Collections.Generic;
using BReusable;
namespace DefectSeverityAssessmentBusiness.Registration
{
public class ModelRegistration:IRegistration
{
#region IRegistration Members
public string UserName { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string MailCode { get; set; }
public string TelephoneNumber { get; set; }
public int? OrganizationId { get; set; }
public int? OrganizationSponsorId { get; set; }
#endregion
public IEnumerable<RuleViolation> GetRuleViolations()
{
if (String.IsNullOrEmpty(UserName))
yield return new RuleViolation("UserName is required",Member.Name<ModelRegistration>(x=>x.UserName));
//if (Latitude == 0 || Longitude == 0)
// yield return new RuleViolation("Make sure to enter a valid address!", "Address");
yield break;
}
///
/// the number of members the interface has
///
///
///
///
///
///
public static Dictionary<string, Action> GenerateActionDictionary
where T : IRegistration
{
var result = new Dictionary<string, Action>
{
{Member.Name<IRegistration>(x=>x.UserName),
()=>dest.UserName=source.UserName},
{Member.Name<IRegistration>(x=>x.Name),
()=>dest.Name=source.Name},
{Member.Name<IRegistration>(x=>x.Email),
()=>dest.Email=source.Email},
{Member.Name<IRegistration>(x=>x.MailCode),
()=>dest.MailCode=source.MailCode},
{Member.Name<IRegistration>(x=>x.TelephoneNumber),
()=>dest.TelephoneNumber=source.TelephoneNumber},
{Member.Name<IRegistration>(x=>x.OrganizationId),
()=>dest.OrganizationId=source.OrganizationId},
{Member.Name<IRegistration>(x=>x.OrganizationSponsorId),
()=>dest.OrganizationSponsorId=source.OrganizationSponsorId},
};
return result;
}
///
///
///
///
///
///
///
///
public static T CopyData
ICollection<string> excludeList) where T : IRegistration
{
return CopyDictionary
GenerateActionDictionary, creator, source, includeIdentifier, excludeList);
}
///
///
///
///
///
///
///
///
///
public static T CopyData
where T : IRegistration
{
return CopyDictionary
GenerateActionDictionary, validation, creator, source, includeIdentifier, excludeList);
}
} // end class
public interface IRegistration
{
string UserName { get; set; }
string Name { get; set; }
string Email { get; set; }
string MailCode { get; set; }
string TelephoneNumber { get; set; }
int? OrganizationId { get; set; }
int? OrganizationSponsorId { get; set; }
}
}//end namespace
The CopyDictionary class is as follows:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
namespace DefectSeverityAssessmentBusiness
{
internal static class CopyDictionary where T:TU
{
///
///
///
///
///
///
///
///
null for no exclusions
///
public static T CopyData(Func bool, Dictionary<string, Action>> actionDictionaryFunc,
Func creator, TU source, bool includeIdentifier,ICollection<string>excludeList)
{
return CopyDataMaster(actionDictionaryFunc, creator, source, includeIdentifier,excludeList, kvp => kvp.Value());
}
///
/// Attempts to copy data, stops on first error, adds to validation dictionary, throws the exception
///
///
///
///
///
///
///
null for no exclusions
///
public static T CopyData(Func bool, Dictionary<string, Action>> actionDictionaryFunc,
IValidationDictionary validation, Func creator,
TU source, bool includeIdentifier,ICollection<string> excludeList)
{
var result= CopyDataMaster(actionDictionaryFunc, creator, source, includeIdentifier,excludeList,
kvp =>
{
try
{
kvp.Value();
}
catch (Exception exception)
{
validation.AddError(kvp.Key,exception.Message);
//TODO: log error?
}
});
if(validation.IsValid==false)
throw new ValidationException("Validation contains errors");
return result;
}
private static T CopyDataMaster(Func bool, Dictionary<string, Action>> actionDictionaryFunc,
Func creator,TU source, bool includeIdentifier,ICollection<string> excludeList,
Action<KeyValuePair<string, Action>> action)
{
var result = creator();
foreach (var kvp in actionDictionaryFunc(result,source,includeIdentifier))
{
if(excludeList==null || excludeList.Contains(kvp.Key)==false)
action(kvp);
}
return result;
}
}
}
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
namespace DefectSeverityAssessmentBusiness
{
internal static class CopyDictionary
{
///
///
///
///
///
///
///
null for no exclusions
///
public static T CopyData(Func
Func
{
return CopyDataMaster(actionDictionaryFunc, creator, source, includeIdentifier,excludeList, kvp => kvp.Value());
}
///
///
///
///
///
///
///
///
null for no exclusions
///
public static T CopyData(Func
IValidationDictionary validation, Func
TU source,
{
var result= CopyDataMaster(actionDictionaryFunc, creator, source, includeIdentifier,excludeList,
kvp =>
{
try
{
kvp.Value();
}
catch (Exception exception)
{
validation.AddError(kvp.Key,exception.Message);
//TODO: log error?
}
});
if(validation.IsValid==false)
throw new ValidationException("Validation contains errors");
return result;
}
private static T CopyDataMaster(Func
Func
Action<KeyValuePair<string, Action>> action)
{
var result = creator();
foreach (var kvp in actionDictionaryFunc(result,source,includeIdentifier))
{
if(excludeList==null || excludeList.Contains(kvp.Key)==false)
action(kvp);
}
return result;
}
}
}
Code is posted at Breusable.codeplex.com
ReplyDelete