Print Page | Close Window

Automated string length validation

Printed From: IdeaBlade
Category: DevForce
Forum Name: DevForce 2010
Forum Discription: For .NET 4.0
URL: http://www.ideablade.com/forum/forum_posts.asp?TID=3577
Printed Date: 13-May-2026 at 2:59am


Topic: Automated string length validation
Posted By: harald
Subject: Automated string length validation
Date Posted: 13-Aug-2012 at 6:11pm
I am aware of your StringLengthVerifier and its MaxValue attribute.
The problem we have is that when we change the field length in the database we have to modify the MaxValue attribute of all relevant verifiers. This is not a good solution. Too much possibility for errors.

I am trying to automate this.

My idea is to create a value converter that receives the type of the property as input and returns the field length of the property. Then I can use the converter for the MaxLength property of a TextBox like this:

MaxLength="{Binding Client.clt_name, Converter={StaticResource LengthConverter}}"

I have not written it yet (it probably needs a parameter that receives the type of the property, so I can use reflection to determine the field length or something along those lines).

However, this is an everyday problem for DB UI developers and I am hoping you have a much better solution already ready for me to use.

If not, can you point me to how to use your EntityManager and your VerifierEngine in the converter such that it returns the field length for a property (given by name, like "Client.clt_name")?

Thanks!




Replies:
Posted By: DenisK
Date Posted: 14-Aug-2012 at 1:21pm
Hi Harald,

DevForce provides EntityMetadataStore for such use case. See  http://drc.ideablade.com/xwiki/bin/view/Documentation/model-examine - http://drc.ideablade.com/xwiki/bin/view/Documentation/model-examine  for more info.

I'm curious though. You can skip doing all this if you just update and regenerate your model from the DB. DevForce code generation will automatically update the MaxLength for all your StringLengthVerifierAttribute.

Please let me know if I'm missing something.


Posted By: harald
Date Posted: 14-Aug-2012 at 3:59pm
Hi Denis,

thanks for your swift response.

I looked at the EntityMetadataStore documentation before I posted but I could not find any information about obtaining the max string length from it. I looked at all the fields (including DataEntityProperty) in the debugger but I could not find the max length anywhere.
Could you pls post or otherwise link to some code that shows how to obtain that info from the EntityMetadataStore?


Regarding StringLengthVerifierAttribute:

You are saying that when I decorate a property with something like:
[IbVal.StringLengthVerifier(MaxValue=30, IsRequired=true,  ErrorMessageResourceName="Employee_FirstName")]
and I then regenerate the model this gets automatically updated in the code and it overwrites whatever I put into MaxValue?

However, even if the StringLengthVerifierAttribute gets updated when I regenerate the model (which was news to me) it still is not a convenient solution as I'd need to generate a separate property in the viewmodel for each and every property of my entity and there are sometimes many of them.

I rather bind to the entity properties directly in xaml.

I went ahead yesterday and used reflection (found the code snippet here on the forum) in a value converter to obtain it.
It works and I like that it is a pure xaml solution but I'd still be interested in obtaining this (and other) information from the EntityMetadataStore if you would be so kind and point me in the right direction.




Posted By: mgood
Date Posted: 14-Aug-2012 at 5:06pm
Originally posted by harald

Hi Denis,

However, even if the StringLengthVerifierAttribute gets updated when I regenerate the model (which was news to me) it still is not a convenient solution as I'd need to generate a separate property in the viewmodel for each and every property of my entity and there are sometimes many of them.


When you generate or update the model from the database, the DevForce code generator automatically decorates Entity properties with verifiers according to your DB schema. So for example string properties will be decorated with an appropriate StringLengthVerifier based on your DB schema. 

Having said that, I don't understand why you would have to add properties to your viewmodel for each and every property of your entity. If you bind your entities directly the StringVerifiers on the properties will kick in whenever the user enters data.


Posted By: DenisK
Date Posted: 14-Aug-2012 at 10:56pm
Hi Harald,

Here's a method where you can pass in the entity type and property name and it will return the StringLengthVerifierAttribute.MaxValue.

private int GetMaxLength(Type entityType, string propName) {
      var entityMetadata = EntityMetadataStore.Instance.GetEntityMetadata(entityType);
      var dataProp = entityMetadata.DataProperties.FirstOrDefault(dp => dp.Name == propName);
      var stringLengthAttr = dataProp.PropertyInfo.GetCustomAttributes(typeof(IdeaBlade.Validation.StringLengthVerifierAttribute), false).FirstOrDefault();
      if (stringLengthAttr != null) {
        return ((IdeaBlade.Validation.StringLengthVerifierAttribute)stringLengthAttr).MaxValue;
      }
      return 0;
}


Posted By: harald
Date Posted: 14-Aug-2012 at 11:23pm
@Denis:
Thank you for the code! That is very useful.


@mgood:
I don't understand your response. This is my 3rd week of working with your framework, so there's a lot I don't know/understand yet. (I do have 7+ years of experience with C# and WPF, tho. I am very well familiar with most WPF concepts).

Are you saying all I have to do is decorate my entity property in the view model with the StringLengthVerifier attribute and all string properties of my entity will be verified when I bind to them directly? I don't quite see how that would work...

I have a working solution, so a response is not urgent but if you feel like it, I'd be interested in a clarification in order to understand the issue better.




Posted By: mgood
Date Posted: 15-Aug-2012 at 1:25am
You don't have to decorate anything on the ViewModel. Let's take a look at a quick example. Let's take the Customer entity generated from the NorthwindIB database and let's look at the CompanyName property. Notice the DevForce code generator, generated a bunch of attributes including a StringLengthVerifier.

  public partial class Customer : IbEm.Entity {     /// <summary>Gets or sets the CompanyName. </summary>     [DataMember]     [Bindable(trueBindingDirection.TwoWay)]     [Editable(true)]     [Display(Name="CompanyName", AutoGenerateField=true)]     [IbVal.StringLengthVerifier(MaxValue=40, IsRequired=true, ErrorMessageResourceName="Customer_CompanyName")]     public string CompanyName {       get { return PropertyMetadata.CompanyName.GetValue(this); }       set { PropertyMetadata.CompanyName.SetValue(thisvalue); }     }
  }

Now let's say I have a ViewModel that has the following property to expose an entire customer.

        public Customer CustomerEntity
        {
            get { return _customerEntity; }
            set
            {
                _customerEntity = value;
                NotifyOfPropertyChange(() => CustomerEntity);
            }
        }
In the View you can then bind a TextBox to CompanyName like follows and the StringLengthVerifier on the CompanyName property will do it's job.

        <TextBox Text="{Binding CustomerEntity.CompanyName, ValidatesOnDataErrors=True}" />


Posted By: harald
Date Posted: 15-Aug-2012 at 3:59pm
Thanks, Marcel, for taking the time.


That's basically what we had all along. It works in the sense that the verification kicks in when I try to save the entity.
We did get the verification errors at that point.
But that's way too late.

I just re-read my OP and I guess I wasn't very clear in what I want.

What I want is the behavior I get when I set the MaxLength property of a TextBox: the user is prevented from entering more characters than MaxLength specifies. I need the feedback WHILE the user types, not when I try to save then entity, cause then the user needs to go back and fix things. That's inconvenient.

Or am I missing something?




Posted By: mgood
Date Posted: 15-Aug-2012 at 5:03pm
StringLengthVerifier is what we call a property verifier. It kicks in as soon as the property setter is called and again when you try to save. The setter gets called as soon as you tab out of the textbox the way I have the binding above. In order to get immediate feedback on the UI you need to specify ValidatesOnDataErrors=true as I have above. You'll get immediate feedback in the form of a red border around the text box. If you don't get the red border, then you most likely need to fix your styles.
 
You can see what I'm talking about in our Cocktail Reference Application (TempHire) in action. Click on this link http://apps.ideablade.com/TempHire/ - http://apps.ideablade.com/TempHire/  to launch the Silverlight version. Login, go to Resource Management and edit a resource. Enter less than 5 or more than 10 digits in the zip code box and tab out of it. Notice how you immediately get a red border. This doesn't currently work in the WPF version of TempHire, because I haven't had a chance to fix the styles, but it works in a dummy WPF app that doesn't have any styles messing up the red border. For example you can check out our WPF DevTour http://drc.ideablade.com/xwiki/bin/view/Documentation/tour-devforce-wpf - http://drc.ideablade.com/xwiki/bin/view/Documentation/tour-devforce-wpf .
 
Preventing the user from typing past the MaxLength is a bit unusual, but intriguing and yes to do that you need to bind MaxLength to something in the ViewModel that can obtain the length from the PropertyMetadata.
 
Since you are new to DevForce and if you are not already aware of it, you may be interested in Cocktail, our free open-source end-to-end XAML application framework http://cocktail.ideablade.com/ - http://cocktail.ideablade.com/ . In addition we offer jump start training as part of our professional services offerings if you feel like that's something you want.
 
Hope this gives you something to go on.
 
 


Posted By: harald
Date Posted: 15-Aug-2012 at 5:32pm
I'll check out Cocktail, thanks.

I checked out the TempHire link and that is indeed the behavior I don't want.
I think it is much easier from the user's perspective to get the immediate feedback when (s)he types and further typing is prevented when the max is reached.

Since you guys have been so helpful, I am sharing the solution I am using now. It works well. I implemented a value converter that I bind to like this in xaml:

   <TextBox Width="500" Style="{StaticResource TextBoxStyle}"
    MaxLength="{Binding Converter={StaticResource LengthConverter}, ConverterParameter='Provider.pro_contact'}"
    Text="{Binding Provider.pro_contact, Mode=TwoWay}"  />

Note, that the text I pass on to the ConverterParameter specifies the type Provider and not the property named Provider.
The way it is implemented it only works for entities in a certain name space, which is fine in our scenario.
If you want to make it universal, you'd need to iterate through the assemblies to look for the type in the method GetMaxStringLength. Alternatively, you could provide the name space in the parameters as well.

    public class LengthConverter : IValueConverter
    {
        /// <summary>
        /// Converts the name of a property to the maximum string length of its property.
        /// NOTE:
        /// This converter only works with entities in AUDDomainModel.
        /// </summary>
        /// <param name="value">not used</param>
        /// <param name="targetType">not used</param>
        /// <param name="parameter">The name of the entity property, e.g. "Client.clt_title"</param>
        /// <param name="culture">not used</param>
        /// <returns>the string length, or 0 if it could not be determined.</returns>
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            int length = 0;
            string name = parameter as string;
 
            string[] n = name.Split('.');
            if (n.Length == 2)
            {
                length = GetMaxStringLength(n[0], n[1]);
            }
 
            return length;
        }
 
        /// <summary>
        /// Get the max string length attribute of a property (using reflection).
        /// </summary>
        /// <param name="entityType">Type of entity</param>
        /// <param name="propertyName">The name of the entity property</param>
        /// <returns>The max string length, or 0 if not applicable.</returns>
        private int GetMaxStringLengthAttribute(Type entityType, String propertyName)
        {
            int length = 0;
            try
            {
                var propertyInfo = entityType.GetProperty(propertyName);
                var attributes = (StringLengthVerifierAttribute)propertyInfo.GetCustomAttributes(typeof(StringLengthVerifierAttribute), false).First();
                length = attributes.MaxValue;
            }
            catch
            {
                // An exception is thrown if the type does not have a maximum length attribute,
                // which is true for unlimited strings and all non-string types.
            }
            return length;
        }
 
        /// <summary>
        /// Get the max string length attribute of a property (using reflection).
        /// Note:
        /// This currently only works for entities in AUDDomainModel.
        /// </summary>
        /// <param name="entityName">The name of the entity (as string).</param>
        /// <param name="propertyName">The name of the entity property.</param>
        /// <returns>The max string length, or 0 if not applicable.</returns>
        private int GetMaxStringLength(string entityName, string propertyName)
        {
            // Note:
            // The ", AUDDomainModel" bit is necessary. It won't work without it.
            string fullname = string.Format("AUDDomainModel.{0}, AUDDomainModel", entityName);
            
            Type t = Type.GetType(fullname);
            return GetMaxStringLengthAttribute(t, propertyName);
        }
 
 
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }



Posted By: mgood
Date Posted: 15-Aug-2012 at 6:17pm
If you want immediate feedback as the user types, all you have to do is set UpdateSourceTrigger to PropertyChanged. That will cause the verifier to evaluate continously as the user types. Anyway, just another option to consider. Thanks for sharing your code.
 
        <TextBox Text="{Binding CustomerEntity.CompanyName,
                                ValidatesOnDataErrors=True,
                                UpdateSourceTrigger=PropertyChanged}" />



Print Page | Close Window