Buddy Classes

Jan 11, 2009 at 7:52 PM
You mention that you will add support for metadata buddy classes.  Does this mean that the current version won't work with buddy classes at all?


Coordinator
Jan 12, 2009 at 12:43 PM
Hi Trevor

Indeed, what I meant was that the default rules provider (i.e., DataAnnotationsRuleProvider, which detects System.ComponentModel.DataAnnotations attributes) simply ignored the [MetadataType] attribute, so buddy classes would be ignored.

However, I understand that buddy classes are very important for anyone working with the LINQ to SQL designer, and it's a very easy thing to add support for, so I've just done it. Please find a new release (version 0.5.1) which adds support for this attribute. (In fact, it goes further and adds support for any custom metadata provider - the default one supports buddy classes.)
Jan 12, 2009 at 7:38 PM
Great!  I wish now that I had implemented the repository pattern in which case I wouldn't have needed this, but in one project of mine I'm just too far along and have used the Linq 2 SQL generated classes directly so the buddy classes were a must.  This will definitely come in handy!
Feb 17, 2009 at 1:16 AM
Hi Steve,

I can't seem to get this functioning with the Metadata Type attribute? (errors.Any() is false and no RulesException is being thrown).

I have attached the Bookings Demo with the new xVal 0.5.1 and Metadata class, could you tell me where I might be going wrong? I have not changed much.  Perhaps it needs to be enabled in some way?


Graham
Coordinator
Feb 17, 2009 at 10:15 AM
Hi Graham

I think you aren't using the latest version of xVal. Please download the 0.5.1 release and use it to update your version xVal.dll in your project. I've just checked, and this does make the client-side validation aware of rules defined in your buddy class. 

Please note that it doesn't affect server-side validation: xVal doesn't include a validation runner for the DataAnnotations attributes, and the runner included in the Bookings demo doesn't recognize buddy classes. You should be able to amend your server-side validation runner to recognize [MetadataType] using something like the following:

public static IEnumerable<ErrorInfo> GetErrors(object instance)
{
    var metadataAttrib = instance.GetType().GetCustomAttributes(typeof (MetadataTypeAttribute), true).OfType<MetadataTypeAttribute>().FirstOrDefault();
    var typeHoldingRules = metadataAttrib != null ? metadataAttrib.MetadataClassType : instance.GetType();

    return from prop in TypeDescriptor.GetProperties(typeHoldingRules).Cast<PropertyDescriptor>()
           from attribute in prop.Attributes.OfType<ValidationAttribute>()
           where !attribute.IsValid(prop.GetValue(instance))
           select new ErrorInfo(prop.Name, attribute.FormatErrorMessage(string.Empty), instance);
}

Please note that I haven't tested this amended runner at all - it's here just to give you an idea of how to proceed.
Feb 17, 2009 at 11:54 PM
Hi Steve,

It does appear to make the CreateBooking page "aware" of the validation rules, I am receiving this output at the bottom of the html file:
<script type="text/javascript">xVal.AttachValidator("booking", {"Fields":[{"FieldName":"ClientName","FieldRules":[{"RuleName":"Required","RuleParameters":{}},{"RuleName":"StringLength","RuleParameters":{"MaxLength":"15"}}]},{"FieldName":"NumberOfGuests","FieldRules":[{"RuleName":"Range","RuleParameters":{"Min":"1","Max":"20","Type":"decimal"}},{"RuleName":"DataType","RuleParameters":{"Type":"Integer"}}]},{"FieldName":"ArrivalDate","FieldRules":[{"RuleName":"Required","RuleParameters":{}},{"RuleName":"DataType","RuleParameters":{"Type":"Date"}}]}]})</script>
It's just not firing on submit, instead the form is being submitted and stepping-in to server-side code.
And as for the code sample you gave me - yeah I would imagine we would all need MetadataType to be recognised by both Server and Client side validation for it to be a usable out of the box solution.  But thanks for everything thus far.
I am receiving this error with our latest code sample, but I will try to work through it when I get a chance if you don't want to invest time in this.
Line: "where !attribute.IsValid(prop.GetValue(instance))" in GetErrors():
Property accessor 'ClientName' on object 'DomainModel.Booking' threw the following exception:'Object does not match target type.'
I'm not sure why it would be saying that, as the 'instance' is the booking object and it is not casting to a different type? Odd.
Coordinator
Feb 18, 2009 at 10:26 AM
Edited Feb 18, 2009 at 10:28 AM
Hi there - I think there are two problems:

(1) If the client-side validation isn't firing, even though it's emitting the correct config into the HTML source, then it's probably because you've upgraded to 
ASP.NET MVC RC1 which isn't supported by the xVal.jquery.validate.js file you're using (because in the RC, element IDs are different). Please get the 
latest xVal.jquery.validate.js file from the current source code version of xVal because this does add support for ASP.NET MVC RC 1. I know this is 
inconvenient - sorry - and I do plan to do an updated "proper" release in the next week that will make things much easier for everyone.

(2) Yes, I now actually tried running the sample runner I gave you and can see that it wouldn't work. Here's one that actually does work:

public static IEnumerable<ErrorInfo> GetErrors(object instance)
{
    var metadataAttrib = instance.GetType().GetCustomAttributes(typeof(MetadataTypeAttribute), true).OfType<MetadataTypeAttribute>().FirstOrDefault();
    var buddyClassOrModelClass = metadataAttrib != null ? metadataAttrib.MetadataClassType : instance.GetType();
    var buddyClassProperties = TypeDescriptor.GetProperties(buddyClassOrModelClass).Cast<PropertyDescriptor>();
    var modelClassProperties = TypeDescriptor.GetProperties(instance.GetType()).Cast<PropertyDescriptor>();

    return from buddyProp in buddyClassProperties
           join modelProp in modelClassProperties on buddyProp.Name equals modelProp.Name
           from attribute in buddyProp.Attributes.OfType<ValidationAttribute>()
           where !attribute.IsValid(modelProp.GetValue(instance))
           select new ErrorInfo(buddyProp.Name, attribute.FormatErrorMessage(string.Empty), instance);
}

Yes, I've tried running it this time! Maybe xVal really does need a built-in runner for DataAnnotations attributes. I was hoping it wouldn't be necessary but perhaps it is.
Mar 12, 2009 at 7:36 PM
Hi Steve,

Great work on the BuddyClasses for LinqtoSql btw. Im a little bit stuck here though. Im trying to make a wizard like form and use xval on my domain object so I need help modifying GetErrors to be flexible in this type of behavior.

I dont know how to isolate the properties so i can validate it by groups. Im thinking just have a condition here to test against certain properties or test the whole domain object but i cant seem to get it right. help anyone :)

thanks
Coordinator
Mar 14, 2009 at 12:13 AM
Hi Alain

This is something we're discussing at http://xval.codeplex.com/Thread/View.aspx?ThreadId=50093. No specific conclusion yet but this question is going to get some attention.
Coordinator
Mar 14, 2009 at 12:22 AM
Actually I should point out a fairly simple way of doing that.

Get the full set of errors from the domain object, but when you copy the errors to ModelState, filter the set so that you only copy errors relating to properties displayed on the current step of the wizard.

For example, write:

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult MyWizardStep(Booking booking)
    {
        try {
            booking.PlaceBooking();
        }
        catch (RulesException ex) {
            // Only register errors about Name and Age
            ex.AddModelStateErrors(ModelState, "booking",
                x => new[] { "Name", "Age" }.Contains(x.PropertyName)
            );
        }

        if (ModelState.IsValid)
            return RedirectToAction("NextWizardStep");
        else
            return View(); // Error, so re-render same view
    }

This code will only take account of errors relating to Name and Age, ignoring other errors.
Mar 15, 2009 at 5:27 AM
Hi Steve,

I forgot to give you the update on this.  The new GetErrors() method you've given me works now for server and client-side.

Thanks heaps for that, I've also blogged about it with final solution in place and with an example for others and props to you =)

Graham
Mar 17, 2009 at 6:13 PM
cool thanks ill try this out

On Fri, Mar 13, 2009 at 4:22 PM, SteveSanderson <notifications@codeplex.com> wrote:

From: SteveSanderson

Actually I should point out a fairly simple way of doing that.

Get the full set of errors from the domain object, but when you copy the errors to ModelState, filter the set so that you only copy errors relating to properties displayed on the current step of the wizard.

For example, write:

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult MyWizardStep(Booking booking)
    {
        try {
            booking.PlaceBooking();
        }
        catch (RulesException ex) {
            // Only register errors about Name and Age
            ex.AddModelStateErrors(ModelState, "booking",
                x => new[] { "Name", "Age" }.Contains(x.PropertyName)
            );
        }

        if (ModelState.IsValid)
            return RedirectToAction("NextWizardStep");
        else
            return View(); // Error, so re-render same view
    }

This code will only take account of errors relating to Name and Age, ignoring other errors.

Read the full discussion online.

To add a post to this discussion, reply to this email (xval@discussions.codeplex.com)

To start a new discussion for this project, email xval@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe on codePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at codeplex.com


Mar 26, 2009 at 11:04 PM
wow nice its working well.

but how do i pass the values collected and save only at the last step? also, what if a user access a step through a url?

cant wait for the book about that. hope it has a cookbook kind of content :D
Oct 26, 2009 at 8:36 AM

Hi Steve,

I have a scenario in which I need to validate "Username" validation from database on click of "Verify" button. and all client side validation on click of "Submit" button.

So, I need client side validation for both the buttons which are on the same page. How can I achieve this?.

I tried with Remote ajax call which is a part of Xval 1.0 release, but it fires all the validations on click of "Verify" button.

I need to do server side validation (by using some API) on click of "Verify".

 

Regards,

Swathi