As you may figured out from 1 of my previous posts, i’m not a big fan of Silverlight Dataform control, although I do like some of the capabilities presented there. In fact, after numerous frustrations with it, i decided to roll my own customization of it, rip out everything i didn’t need and adjust it to act the way i like it. You can find my first stab at it in this post.
That turned out to be one of the better decisions, although fighthing with the code mess inside the datafirn proved to be quite a challenge. Lately i had an issue with validation which i thought was just another dataform bug, but it proved to be a lot harder than it seemed at first. It’s well presented with the following picture i picked up from linkedIn:
Both of the fields are required to be filled out but the latter one is not highlighted. This case is only present if you try to submit unchanged value, this is very important as you’ll see later on.
Lets assume you have the following entities:
public class Partner { [Key] public int Id { get; set; } [Required] public int? CountryId { get; set; } public Country Country{ get; set; } } public class Country { [Key] public int? Id { get; set; } }
Now, let’s say that you bound the combobox to Country property of a Partner entity:
<ComboBox Name="Country" SelectedItem="{Binding Country,Mode=TwoWay}" />
So you press an ok button and nothing really happens.
The problem
The main problem is not actually the dataform, but the way validation works with binding and the way Ria generates code for you. You see, every time you change value and lose focus, or you submit the data with an OK button, the bindings try to push the value to your entity, which means set the property. And the property in your generated file looks like this:
public class Partner: Entity { . . . /// <summary> /// Gets or sets the associated <see cref="Country"/> entity. /// </summary> [Association("Partner_Country", "CountryId", "Id", IsForeignKey=true)] public Country Country { get { if ((this._c == null)) { this._c = new EntityRef<Country>(this, "Country", this.FilterC); } return this._c.Entity; } set { Country previous = this.Country; if ((previous != value)) { this.ValidateProperty("Country", value); if ((value != null)) { this.CountryId = value.Id; } else { this.CountryId = default(Nullable<int>); } this._c.Entity = value; this.RaisePropertyChanged("Country"); } } } }
Pay attention to the part if (previous!=value). What that effectively means is that if your property came in as invalid, and you try to set that same value (for instance null) your property wouldn’t even go through the vaildation (that this.ValidateProperty(“Country”, value) line) !
A second problem alltogether would be that you can’t even put a required attribute on a property which already has the association attribute. You can put a required attibute on a CountryId field:
public class Partner { [Key] public int Id { get; set; } [Required] public int? CountryId { get; set; } public Country Country{ get; set; } } public class Country { [Key] public int? Id { get; set; } }
But that wouldn’t then trigger validation for the Country if you defined your combobox like in the upper case.
So how to fix this?
Ok, let’s first define the combobox so that it uses the Required attribute of CountryId. The solution would look something like this:
<ComboBox ItemsSource="{Binding Data, ElementName=DF_CSource}" SelectedValue="{Binding CountryId,Mode=TwoWay}" SelectedItem="{Binding Country,Mode=TwoWay}" SelectedValuePath="Id"/>
We are biniding the selectedValue to CountryId, and selected value is determined from SelectedItem.SelectedValuePath. This way the binding will trigger the validation of CountryId which would then highlight our combobox that there’s something wrong with it.
An alternative approach would be to define a custom rule which would check the value of Country property for null:
public class Partner { [Key] public int Id { get; set; } public int? CountryId { get; set; } [CustomValidation(typeof(Validation),"ValidateAssociation")] public Country Country { get; set; } } public class Country { [Key] public int? Id { get; set; } }
This way, you could leave only the SelectedItem inside combobox and have full validation. My approach was the latter one, but i hid the custom validation behind some nice fluent interface so it’s easy to use.
Ok so now to the problem of original values not validating properly….
As i shown before, this is mainly the problem because dataform and controls rely on bindings to update the source and do validation consequently. Validation is not done if value never changes. One of the quick fixes you could introduce is to hook on the DataForm’s ValidatingItem event and do the following:
var entity= currentItem as Entity; var context = new ValidationContext(entity, null, null); var validationResults = new List<ValidationResult>(); Validator.TryValidateObject(entity, context, validationResults, validateAllProperties); entity.ValidationErrors.Clear(); validationResults.ForEach(entity.ValidationErrors.Add);
This will force the validation of entity and it will update the validation bindings. I have this implemented in my SlimForm, i did have to include additional reference to Domainservices.Client assembly, but i have that assembly defined either way in my project, plus i couldn’t find a more elegant solution. In general, there’s no easy way to solve this problem i’m afraid….
Anyway i hope this helps someone, if something’s not clear enough just post a comment. Cheers! The sample project is a bit different than the sample code, but i’ll think you’ll get the picture:)
Combobox sample project with updated SlimForm
Christ Holmes Mar 01 , 2011 at 1:24 pm /
Your naming conventions for classes and properties make this entirely too confusing to follow. I still have no idea how this works after reading your blog post.
How about, instead of trying to make super-generic and meaningless class/property naems, you just name things based on something people can relate to, like say Products/Orders or BlogPosts/Comments… I mean, I am completely lost here. This makes no sense.
Peymankh May 01 , 2011 at 5:10 am /
NBecause you probably haven’t faced the problem.
kagjes Mar 01 , 2011 at 1:38 pm /
Yea, i agree that the naming convention is a bit confusing, i basically adjusted a combobox sample project which i previously downloaded , it’s by no means the way i write code:). The reason i went along with it is because the source is available for download, plus the whole example is composed of only 2 classes. But yea, i think blog post and comment would make more sense, even just for the code snippets, i’ll put it on my todo list. Thanks for the input!
Bryan Mar 22 , 2011 at 9:16 am /
I agree with the comments above. Object names are so generic they make it hard to understand whats what.
Bruno Mar 22 , 2011 at 9:57 am /
Ok, i updated the sample code, i hope it’s a bit clearer now. The point is basically the code you hook on dataform’s ValidatingItem event, so i think you will be good to go:)
vvalk Apr 20 , 2011 at 7:36 am /
Thanks! This saved my time and helped me very much.
Peymankh May 01 , 2011 at 5:15 am /
Seriously, thanks man. good one.
Pankaj Kumar Jun 26 , 2011 at 3:17 pm /
This is really a brilliant example of how the property gets validated and bound to the parent entity. I searched numerous MSDN pages, but thanks to Google to have me landed onto this page and finally could resolve the issue in just additional one line of code.
Shenbagaraj Dec 26 , 2011 at 6:16 am /
for combobox its working fine but for Radcombobox the event is not firing can anyone help