Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVVM / NHibernate - How to do dynamic model validation?

I'm building a C# WPF application using the MVVM pattern. I have repository classes that use NHibernate to persist my domain model.

My model consists of a larger tree structure (Recipes containing Operations containing Phases). Operations and phases both contain a dynamic list of key-value-mappings as an IDictionary<string, string>. The corresponding NHibernate mapping for an Operation is

<class name="Operation" table="operations">
  <id column="id" type="int" generator="native" />
  <property name="Name" column="name" />

  <map name="Parameters" table="operation_params">
    <key column="operation" />
    <index column="param" type="string" />
    <element column="value" type="string" />
  </map>

  <list name="Phases" cascade="all-delete-orphan">
    <key column="operation" />
    <index column="`index`" />
    <one-to-many class="Phase" />
  </list>
</class>

Now, that part is easy and works quite well. The Operation class currently is a POCO with nearly no logic inside, a simple data container.


My problem is: I have to validate the parameters against an external schema my application reads from an .xml file. The schema contains restrictions for single parameters (range, valid values etc.) as well as dependencies between several parameters (i.e. the valid values vary depending on another parameter's value).

What is the best way to integrate my validation logic? I read a lot the last few days and until now, I stumbled upon the following alternatives:

  1. Add the validation logic to the model class itself.

    For this, I don't know how to properly inject the validation schema into the objects created by NHibernate. I don't need the validation functionality all the time, only when the user is editing the parameters or when I'm importing an operation (e.g. from a backup). So maybe I could implement the actual validation logic in the model class and inject the validation rules using a property whenever I really need validation? Is it considered good practice to add that functionality to the model classes that I store using NHibernate or should the model classes stay 'dumb'?

  2. Create a decorator class for the validation logic that wraps around my Operation objects.

    This way I would use the wrapper everytime I need validation and the bare model class when I only need to display it. My problem here is that my ViewModel classes already are wrappers, so I would get another layer of wrapping here. Also, as the Operation class is part of a larger tree structure (Recipe/Operation/Phase) I would need to create wrappers for the collections and map collection changes back to the underlying collections which may be a complex task.

  3. Create a service class that I call passing the operation whenever I want to validate it.

    The problem that I see here is that the service is stateless and would therefore have to re-validate the whole parameter list everytime the user changes a single parameter. This doesn't seem to be the best approach, especially when I want to fire some kind of a change event for the UI when the validation status for a parameter changes.

What is the common approach for my problem? Is there a pattern I haven't found yet that is perfectly what I need? I mean, there are a lot of implementations out there that rely on external schema definitions for validation (read: XML/XSD and similar document structures), there just have to be some geniuses who already found the perfect solution for my problem ;-) Help me, SO!

like image 569
kroimon Avatar asked Oct 01 '12 21:10

kroimon


1 Answers

  1. Add the validation logic to the model class itself.
  2. - Not the best approach, because you’ll blow up the POCO with logic and you’ll end up using ActiveRecord pattern, that it’s not as clean.
  3. Create a decorator class for the validation logic that wraps around my Operation objects.
  4. - Think it’s a better approach, with the difference that you’ll have to wrap already existing wrappers, and you’ll also end up blowing levels of abstractions, so also not recommended.
  5. Create a service class that I call passing the operation whenever I want to validate it.
  6. - Probably also it’s not your case to accomplish such things(if I correctly understood you are saying about a webservice, or some another type of remote service), this solution is more suitable if you have restrictions on these validations rules that should be for example centralized for multiple clients and not tight to concrete application.

I would favor for the following solution:

Add a validator project to you solution that will contain the:

  • Logic that validates the parameters against an external schema that your application reads from .xml file.

  • Validation rules for each POCO object that you use in your project, and it requires validation (or, also you may apply these rules at a higher level, mean not POCO but some Wrapper on the POCO if you have already such an implementation, but as best practice try to apply rules directly to POCO – cleaner and right approach)

So:

1-Your POCO will contain properties and a simple validation SelfValidate():


namespace Core.Domain {
    public class Operation : ValidatableDomainObject {
        #region Properties
        public virtual String Name { get; set; }
        public virtual ISet Phases { get; set; }     
        #endregion Properties

        #region Validation
        public override ValidationResult SelfValidate() {
            return ValidationHelper.Validate(this);
        }
        #endregion Validation       
    }
}

2-Your POCO Validator will contain rules that should be applied to validation of the POCO based on your XML file:


#region Usings

using System.Linq;
using FluentValidation;
using FluentValidation.Results;

#endregion Usings

namespace Core.Validation {

    public class OperationValidator : AbstractValidator {
        #region .Ctors
        /// 
        /// .Ctor used for operation purpose
        /// 
        public OperationValidator() {
            Validate();
        }
        #endregion .Ctors

        /// 
        /// Validation rules for Operation
        /// 
        private void Validate() {
            //here you may get validations rules from you xml file and structure the following code after your requirements
            //Name
            RuleFor(x => x.Name).Length(2, 20).WithMessage("Operation name should have length between 2 and 20 symbols");
            //ApplicationFormsWrapper
            Custom(entity => {
                foreach (var item in entity.Phases)
                    if (item.PhaseState == null)
                        return new ValidationFailure("Phases", "First Phase is missing");
                return null;
            });
        }
    }
}

3-Add the ValidatableDomainObject class, it implements System.ComponentModel.IDataErrorInfo (provides functionality to offer custom error information, that a user interface can bind to):


#region Usings

using System.ComponentModel;
using System.Linq;
using FluentValidation.Results;
using Core.Validation.Helpers;

#endregion Usings

namespace Core.Domain.Base {
    public abstract class ValidatableDomainObject : DomainObject, IDataErrorInfo {

        public abstract ValidationResult SelfValidate();

        public bool IsValid {
            get { return SelfValidate().IsValid; }
        }

        public string Error {
            get { return ValidationHelper.GetError(SelfValidate()); }
        }

        public string this[string columnName] {
            get {
                var validationResults = SelfValidate();
                if (validationResults == null) return string.Empty;
                var columnResults = validationResults.Errors.FirstOrDefault(x => string.Compare(x.PropertyName, columnName, true) == 0);
                return columnResults != null ? columnResults.ErrorMessage : string.Empty;
            }
        }
    }
}

4-Add the following ValidationHelper:


#region Usings

using System;
using System.Text;
using FluentValidation;
using FluentValidation.Results;

#endregion Usings

namespace Core.Validation.Helpers {
    public class ValidationHelper {
        public static ValidationResult Validate(TK entity)
            where T : IValidator, new()
            where TK : class {
            IValidator validator = new T();
            return validator.Validate(entity);
        }

        public static string GetError(ValidationResult result) {
            var validationErrors = new StringBuilder();
            foreach (var validationFailure in result.Errors) {
                validationErrors.Append(validationFailure.ErrorMessage);
                validationErrors.Append(Environment.NewLine);
            }
            return validationErrors.ToString();
        }
    }
}

It will allow you to perform the following in your application code:

  1. At the service, or viewmodel level, you may do this in order to obtain validation errors:

var operation = new Operation(){Name="A"};
var validationResults = operation.SelfValidate();

  1. At the View level, you may write something like this(in this case if any validation errors appears they are directly got from OperationValidator class):
<TextBox Text="{Binding CurrentOperation.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}">

NOTE: implementation is based on FluentValidation(a small validation library for .NET that uses a fluent interface and lambda expressions), see http://fluentvalidation.codeplex.com/, but of course you may use another one, hope I succeded in describing the mechanism of decoupling validation from domain object.

like image 192
diadiora Avatar answered Oct 19 '22 23:10

diadiora