Solution
Solution, in a
nuthsell is to store the entity name somewhere during the binding, and refer to
this entity name to fetch information about the field's properties
like MaxLength etc.
This could be
achieved in many ways, by keeping global list of items used ina
dictionary(key/value) format, etc. But for sake of optimization I've tried to
reuse existing property "Tag" for the purpose. This will store the broken piece of the
puzzle, i.e. the Entity name using View Model so that our incomplete pair of
Entity Name/ Field name would be completed.
Tag is an age old property in .Net that is available for most of
the controls. This property is rarely used by developer. At least I never had
to use/manipulate this property in any of the projects that I've done so far in
my career. In the current development project too, this property stays
untouched throughout the modules. When I researched within our .NET community,
I found similar conclusions for the Tagproperty, and its limited use.
Since my
project has not used this property, I have decided to use this property for our
Textbox max length implementation so that I can fill the gap of EntityName.
I have created
one string property named
“EntityTableKey” in my view model base class. This property would be set for Tag in Textbox style. One
important point to be noted here is if needed you can declare this property as
an "object" type also (to pass an array / list or some
collection) because Tag property is an Object type property.
Using the code
public string EntityTableKey { get; set; }
Now you must
be getting an idea what we are going to do further. This EntityTableKey property needs
to be set with the Entity Name in constructor of our view model. In the below
code, my entity table name is Book so "EntityTableKey" property is set to “Book” this is an
indication that in EDMX file the code would search textbox bound property in
entity whose name is Book.
[ImportingConstructor] //Forget about this codeline if you are not using MEF.
internal BookViewModel(IBookView view)
: base(view)
{
EntityTableKey = "Book";
}
Now, we will
move to our control's resource file where we have defined all our style. Here,
we are going to bind this EntityTableKey property to tag in our textbox base style.
So till here,
we have created our mapping of entity name to the textbox control, but how do
we use this and utilize this EntityTableKey property for our max length? Pretty simple…
<Style TargetType="TextBox">
<Setter Property="Width" Value="Auto"/>
<Setter Property="MinHeight" Value="20"/>
<Setter Property="Margin" Value="2"/>
<Setter Property="Tag" Value="{Binding EntityTableKey}"/>
</Style>
Do not give x:Key for your textbox style, this
way all these styling changes would be defaulted to textbox control.
Now the
extraction part, we need to use some kind of converter where we can pass the
whole control and then extract the tag value and bind property. Since simple Binding property do not pass control.
Passing Control to Converter Using Style
So here is the
main implementation of our whole article, we need to use multibinding to pass
our control (textbox) to our code class. For this, we will use a
converter which would be called and process the current control to extract the
bound field and tag property. Once we have bound field name and the entity name,
then it is very easy to search in our entity and get the max length.
So let’s see
how this can be achieved.
<Setter Property="MaxLength">
<Setter.Value>
<MultiBinding Converter="{StaticResource maxlen}">
<Binding RelativeSource="{RelativeSource Mode=Self}" />
<Binding Path="Tag"
RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type TextBox}}" />
</MultiBinding>
</Setter.Value>
</Setter>
Code for Max Length Converter
public class MaxLenSetter : IMultiValueConverter
{
public object Convert(object[] value, Type targetType, object parameter,
CultureInfo culture)
{
int maxLen = 0;
var currentControl = (value[0] as TextBox);
if (currentControl != null && currentControl.Tag != null)
{
try
{
var propertyName = currentControl.GetBindingExpression
(TextBox.TextProperty).ParentBinding.Path.Path;
var boundproperty = propertyName.Split('.');
if (boundproperty.Length > 0)
propertyName = boundproperty[boundproperty.Length - 1];
else
propertyName = boundproperty[0];
maxLen = Waf.BookLibrary.Library.Domain.BookLibraryEntities.GetMaxLengths
(currentControl.Tag.ToString(), propertyName);
currentControl.ToolTip = maxLen > 0 ? "Maximum
length allowed is " + maxLen : null;
}
catch
{
//nothing to do.
}
}
return maxLen;
}
Fetching Maximum Length value from EDMX file.
public static int GetMaxLengths(String entitySetName, string propertyName)
{
int maxLength = 0;
//sometime control rendring is earlier then entity initializing
specially in Release mode duet o code optomization.
while (MaxLenEntity == null)
System.Threading.Thread.Sleep(1000);
MetadataWorkspace workspace = MaxLenEntity.MetadataWorkspace;
ReadOnlyCollection<EntityType> entities =
workspace.GetItems<EntityType>(DataSpace.CSpace);
foreach (EntityType entity in entities)
{
if (entity.Name.Equals(entitySetName))
{
foreach (EdmProperty property in entity.Properties)
{
if (property.TypeUsage.EdmType.Name.Equals("String"))
{
if (property.Name == propertyName && property.TypeUsage.
Facets["MaxLength"].Value != null)
{
int res = 0;
if (int.TryParse(property.TypeUsage.Facets["MaxLength"]
.Value.ToString(),out res))
{
maxLength = res;
break;
}
}
}
}
break;
}
}
Benefits
Making a generic property, we can set its value in
constructor of each ViewModel class. This class exposes the properties which are
used in binding the text controls. Since MVVM with MEF works on one to one
mapping mechanism, this means every view is having one view model. We just need
to set this “EntityTableKey” property with our entity table name and the rest
will be done automatically.
What if Binding Property Belongs to Two Different Tables in same View ?
Now what if
your ViewModel is exposing properties from two different tables? Let’s say a
complex view is having 10 textbox controls and two textboxes are using some field which belongs to other table?
It is very
simple, just explicitly set the textbox tag property with your entity name. XAML always overrides
all style properties
with the properties used explicitly on control.
Conclusion
Though the MaxLength validation is
not straight forward, the above solution brings a step closer to implement it
using Textbox tag property. There is a way to use some dependency property but I
wanted to keep this implementation as simple as possible. Few technical guys
may not agree on using tag property for this purpose and I am open to hear from them.
At last before
finishing my article, I would like to thank CodePlex community for their great
work on MVVM & MEF solution. After using their MEF solution, I am now a diehard fan of MVVM & MEF.
Thanks for
reading the article & happy coding!
Reference
WAF.CodePlex.com