Designing Business Objects to indicate constraints such as Max Length - oop

Is there a standard convention when designing business objects for providing consumers with a way to discover constraints such as a property's maximum length?
It could be used up in the UI layer to, for example, set a Textbox's MaxLength property according to the maximum length limit back in the business object.
Is there a standard design approach for this?

Validation frameworks often contain parts for integrating with UI technologies in communicating the errors. Microsoft Enterprise Library Validation Application Block for instance contains a ValidationProvider extender control for WinForms that binds with the WinForms ErrorProvider control.
Your wish is different though. You want to communicate the constraints before they turn in to errors. Because this is not a standard requirement, I don't believe most validation frameworks have something for this out of the box. However, depending on the chosen framework creating this might be achievable. The Validation Application Block for instance, allows you to analyze the rules that you have registered / configured on a entity. So it is possible to build a control that will do this for you.
[Edit]
What you could also do is validate a form immediately upon startup and after each keystroke. This causes error icons or messages to show up immediately, which allows users to directly see what the constraints are (when you use icons, the user can hover an icon to see the error message). This isn't perhaps as nice as creating your own control, but it much easier to implement.

I have my own validation framework that lets me validate each field with the help of designated ValidationAttribute. It uses Attributes to automate most of the validations.
A sample business object would look like this in my application.
Each business object would inherit from EntityBase abstract class that has a public method called "Validate()". When this method is called on the given instance of the business object it will iterate through all properties of its own having Attributes that are derived from ValidationAttribute can call ValidationAttriubte's IsValid method to validate the value of associated proerty and return true/false with err. msg if any.
User.cs
[TableMapping("Users")]
public class User : EntityBase
{
#region Constructor(s)
public AppUser()
{
BookCollection = new BookCollection();
}
#endregion
#region Properties
#region Default Properties - Direct Field Mapping using DataFieldMappingAttribute
private System.Int32 _UserId;
private System.String _FirstName;
private System.String _LastName;
private System.String _UserName;
private System.Boolean _IsActive;
[DataFieldMapping("UserID")]
[DataObjectFieldAttribute(true, true, false)]
[NotNullOrEmpty(Message = "UserID From Users Table Is Required.")] // VALIDATION ATTRIBUTE
public override int Id
{
get
{
return _UserId;
}
set
{
_UserId = value;
}
}
[DataFieldMapping("UserName")]
[Searchable]
[NotNullOrEmpty(Message = "Username Is Required.")] // VALIDATION ATTRIBUTE
public string UserName
{
get
{
return _UserName;
}
set
{
_UserName = value;
}
}
[DataFieldMapping("FirstName")]
[Searchable]
public string FirstName
{
get
{
return _FirstName;
}
set
{
_FirstName = value;
}
}
[DataFieldMapping("LastName")]
[Searchable]
public string LastName
{
get
{
return _LastName;
}
set
{
_LastName = value;
}
}
[DataFieldMapping("IsActive")]
public bool IsActive
{
get
{
return _IsActive;
}
set
{
_IsActive = value;
}
}
#region One-To-Many Mappings
public BookCollection Books { get; set; }
#endregion
#region Derived Properties
public string FullName { get { return this.FirstName + " " + this.LastName; } }
#endregion
#endregion
public override bool Validate()
{
bool baseValid = base.Validate();
bool localValid = Books.Validate();
return baseValid && localValid;
}
}
BookCollection.cs
/// <summary>
/// The BookCollection class is designed to work with lists of instances of Book.
/// </summary>
public class BookCollection : EntityCollectionBase<Book>
{
/// <summary>
/// Initializes a new instance of the BookCollection class.
/// </summary>
public BookCollection()
{
}
/// <summary>
/// Initializes a new instance of the BookCollection class.
/// </summary>
public BookCollection (IList<Book> initialList)
: base(initialList)
{
}
}

Custom Attributes might serve your need.

Related

Strategy pattern to consume REST API

I have to consume two different REST API providers about VoIP. Both API do the same with different endpoints and parameters. I'm modeling classes as strategy pattern and the problem that i have encountered is the parameters of each method strategy because are different.
public interface VoIPRequests
{
string ApiKey { get; set; }
string GetExtensionsList();
string TriggerCall();
string DropCall();
string RedirectCall();
}
How can i change parameters for each of this methods depend on the implementation?.
It's good idea use strategy pattern for this case?
There is another pattern that suits better?
Thank you.
Per comment thread:
TriggerCall(), one api only needs one parameter "To" , and other api has two mandatory parameters "extension" and "destination"
I'll focus on TriggerCall, then, and let you extrapolate from there.
Implementation 1
public class VoIPRequests1 : VoIPRequests
{
private readonly object to; // Give this a more appropriate type
public VoIPRequests1(object to)
{
this.to = to;
}
public string TriggerCall()
{
// Use this.to here and return string;
}
// Other interface members go here...
}
Implementation 2
public class VoIPRequests2 : VoIPRequests
{
private readonly object extension; // Give this a more appropriate type
private readonly object destination; // Give this a more appropriate type
public VoIPRequests2(object extension, object destination)
{
this.extension = extension;
this.destination = destination;
}
public string TriggerCall()
{
// Use this.extension and this.destination here and return string;
}
// Other interface members go here...
}

Fluent NHibernate ShouldMap does not detect my custom attribute

I have been spending a couple of days now to get to know the Fluent NHibernate automapping working model. It is quite nice, but I keep detecting new details missing from my schemas. Now I want to add extra properties to my classes, but not have them mapped to the database. A typical case is when I need extra properties with internal logic.
So I read the examples and scanned StackOverflow and found out that this was not another convention to be added, but rather a matter of inheriting the DefaultAutomappingConfiguration and override the ShouldMap method.
Fine, no problem, one minute later I had something like this:
public class CustomAutomappingConfiguration : DefaultAutomappingConfiguration
{
public override bool ShouldMap(Member member)
{
var explicitSkip = member.PropertyType.GetCustomAttributes(typeof(SkipMap), false).Length > 0;
if ((member.IsProperty && !member.CanWrite) || explicitSkip)
{
return false;
}
return base.ShouldMap(member);
}
}
/// <summary>
/// Don't map this property to database.
/// </summary>
public class SkipMap : Attribute
{
}
public class DemoClass
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual MyBitwiseEnum Status { get; set; }
public virtual bool IsValid
{
get
{
return (int)Status > 3;
}
}
[SkipMap]
public virtual bool IsBad
{
get
{
return MyBitwiseEnum.HasFlag(MyBitwiseEnum.Bad);
}
set
{
MyEnum = value ? MyBitwiseEnum | MyBitwiseEnum.Bad : MyBitwiseEnum ^ MyBitwiseEnum.Bad;
}
}
}
I know that my demo class is kind of stupid, but it will illustrate my point.
The idea is that I want to manually decide what properties to map to database.
The readonly property works fine because the ShouldMap method will look for property.CanWrite. But the custom attribute that definitely is set will not be detected. Why is that!?
In the convention methods I have used the same approach frequently and there it works fine. Why is the property not able to detect defined attributes here, when it obviously can in the convention setting. Is there a workaround?
have you added your new automapconvention to Automap?
AutoMap.AssemblyOf<>(new CustomAutomappingConfiguration())
Update: you are getting the skip attribute from Boolean class instead of the property
member.PropertyType.GetCustomAttributes(typeof(SkipMap), false)
should be
member.MemberInfo.GetCustomAttributes(typeof(SkipMap), false)
Just to be sure the custom attribute is applicable to properties, try adding [AttributeUsage(AttributeTargets.Property)] to your SkipMap class.
Another possibility is an attribute name clash with another attribute that applies to different targets. Try renaming the class to something like MyVerySpecialSkipMap and retest to verify you don't have an attribute clash. At the very least, write some simple reflection code to test for the SkipMap attribute outside the context of your application to ensure it can be found.

Temporarily turn off identity column with Fluent AutoMap?

I have begun to test Fluent NHibernate in C#
I have a well normalized object structure with 20 related classes.
I currently use Fluent 1.3 with NHibernate 3.2.
So far I have managed to use the AutoMap feature which suits me fine,
Very convenient!
BUT ...
3 of the tables are "enum tables" that need to have their records set with specific Id value.
I tried to make manual mappings of these tables and let the rest be automapped.
But when the manual table is created it fails because it references a table that is automapped (and not available for manual mapper?)
Is it possible to use AutoMapping but for some very few classes override identity creation on primary key?
I tried to make a custom convention but without success.
public class OverrideIdentityGeneration : Attribute
{
}
public class ConventionIdentity : AttributePropertyConvention<OverrideIdentityGeneration>
{
protected override void Apply(OverrideIdentityGeneration attribute, IPropertyInstance instance)
{
instance.Generated.Never();
}
}
Is there some other way?
It would be sad to be forced back to use manual mapping for all classes ....
class MyIdConvention : IIdConvention
{
public void Apply(IIdentityInstance instance)
{
if (instance.EntityType == ...)
{
instance.GeneratedBy.Assigned();
}
}
}
Update:
for enum-like classes it's often easier to define an enum as id
class ConfigValue
{
public virtual Config Id { get; set; }
}
// the convention is easy
if (instance.EntityType.IsEnum)
{
instance.GeneratedBy.Assigned();
// to save as int and not string
instance.CustomType(typeof(Config));
}
// querying without magic int values
var configValue = Session.Get<ConfigValue>(Config.UIColor);
I used the idea given by Fifo and extended it to use a custom attribute instead.
To make code readable and avoid redundance when using similar idea in other conventions I added an extension method to check for custom attribute.
This is the code I ended up with:
/// <summary>
/// Convention to instruct FluentNHIbernate to NOT generate identity columns
/// when custom attribute is set.
/// </summary>
public class ConventionIdentity : IIdConvention
{
public void Apply(IIdentityInstance instance)
{
if(instance.CustomAttributeIsSet<NoIdentity>())
instance.GeneratedBy.Assigned();
}
}
/// <summary>
/// Custom attribute definition.
/// </summary>
public class NoIdentity : Attribute
{
}
/// <summary>
/// Example on how to set attribute.
/// </summary>
public class Category
{
[NoIdentity]
public int Id { get; set; }
public string Name { get; set; }
}
public static class IInspectorExtender
{
/// <summary>
/// Extender to make convention usage easier.
/// </summary>
public static T GetCustomAttribute<T>(this IInspector instance)
{
var memberInfos = instance.EntityType.GetMember(instance.StringIdentifierForModel);
if(memberInfos.Length > 0)
{
var customAttributes = memberInfos[0].GetCustomAttributes(false);
return customAttributes.OfType<T>().FirstOrDefault();
}
return default(T);
}
}

Serialising classes that implement List<T> for transferring over WCF

I have spent some time writing code for my application assuming that the serialisation bit would be the easiest part of it. Pretty much both sides (client and server) are done and all I need to do is passing a class AccountInfo from the service to the client... The problem is that AccountInfo inherits List and therefore [DataContract] attribute is not valid. I tried using the [CollectionDataContract] attribute but then the class that is received on the other side (client) contains only generic List methods without my custom implemented properties such as GroupTitle...I have worked out a solution for this problem but I don't know how to apply it.
Basically everything works when I make a property instead of inheriting a List but then I can't bind this class to LongListSelector (WP7) because it's not a collection type.
There are three classes I'm on about. AccountInfo that contains multiple instances of: AccountInfoGroup that contains multiple instances of:AccountInfoEntry (this one does not inherit list therefore there are no problems serialising it and all properties are accessible).
Could someone help me using right attributes to serialise and transfer these classes using a WCF method?
Here is the code of 2 of these collection classes:
public class AccountInfo : List<AccountInfoGroup>
{
public AccountInfo()
{
UpdateTime = DateTime.UtcNow;
EntryID = Guid.NewGuid();
}
public bool HasItems
{
get
{
return (Count != 0);
}
private set
{
}
}
public Guid EntryID
{
get;
set;
}
public decimal GetTotalCredit()
{
decimal credit = 0;
foreach (AccountInfoGroup acg in this.Where(item => item.Class == AccountInfoEntry.EntryType.Credit))
{
acg.Where(item => item.ItemClass == AccountInfoEntry.EntryType.Credit).ToList().ForEach(entry =>
{ credit += entry.Remaining; }
);
}
return credit;
}
public bool UsedForCreditComparison = false;
public DateTime UpdateTime { get; private set; }
}
public class AccountInfoGroup : List<AccountInfoEntry>
{
public AccountInfoEntry.EntryType Class
{
get;
private set;
}
public string Title
{
get
{
return AccountInfoEntry.ClassToString(Class);
}
}
public AccountInfoGroup(AccountInfoEntry.EntryType groupClass)
{
this.#Class = groupClass;
}
public bool HasItems
{
get
{
return (Count != 0);
}
private set
{
}
}
}
Thank you for any suggestions... :)
The sample you had is quite painful for WCF in serialization.
What I suggest is you to revised and have a common models for your WCF messages (That means it only contains properties with getter and setter, serialization attributes).
If you have a problem in LongListSelector binding in WP7, you might want to convert the message to the actual type the WP7 object supports to use in binding.

WorkItemChangedEvent and AddedRelations field

I am trying to capture links that were added to a work item in TFS by catching WorkItemChangedEvent via TFS services. Here is the relevant XML part of the message that comes through:
<AddedRelations><AddedRelation><WorkItemId>8846</WorkItemId></AddedRelation></AddedRelations>
This is declared as a field in WorkItemChangedEvent class that should be deserialized into object upon receiving the event:
public partial class WorkItemChangedEvent
{
private string[] addedRelations;
/// <remarks/>
[XmlArrayItemAttribute("WorkItemId", IsNullable = false)]
public string[] AddedRelations
{
get { return this.addedRelations; }
set { this.addedRelations = value; }
}
}
I cannot figure out why the AddedRelations does not get deserialized properly.
I can only suspect that the object structure does not match the XML schema.
I have changed the structure of my WorkItemChangedEvent class a little bit to match the XML:
public partial class WorkItemChangedEvent
{
private AddedRelation[] addedRelations;
/// <remarks/>
[XmlArrayItemAttribute("AddedRelation", IsNullable = false)]
public AddedRelation[] AddedRelations
{
get { return this.addedRelations; }
set { this.addedRelations = value; }
}
[GeneratedCodeAttribute("xsd", "2.0.50727.42")]
[SerializableAttribute()]
[DebuggerStepThroughAttribute()]
[DesignerCategoryAttribute("code")]
[XmlTypeAttribute(Namespace = "")]
public partial class AddedRelation
{
#region Fields
private string workItemId;
#endregion
/// <remarks/>
public string WorkItemId
{
get { return this.workItemId; }
set { this.workItemId = value; }
}
}
}
I still think that there must be some logic behind the original solution since it was designed by TFS authors (MS)? Anyway I am glad it works now and that I answered my question first ;]