CodeSmith Community
Your Code. Your Way. Faster!

validation rules across entities

Latest post 01-23-2008 5:35 PM by vsagar41. 15 replies.
  • 10-06-2006 8:56 AM

    • swin
    • Top 10 Contributor
    • Joined on 06-14-2006
    • London, UK
    • Posts 924
    • Points 34,750

    validation rules across entities

    Is it possible and if so how would I add validation rules that span across entities:

    for example ie have a Product and it can have many child ProductAttributes - one attribute may be a startdate and another enddate - can I validate that the enddate is > startdate in my Product? (or maybe the Service??)

    TIA

    swin

    ------------------------------------------------- Member of the .NetTiers team -------------------------------------------------
    • Post Points: 45
  • 10-09-2006 4:26 PM In reply to

    Re: validation rules across entities

    There are currently a few ways you can do this.  The most instinctual way would be to create a validation rule in the Product entity that checks against' it's product types. But  are the product Attributes just an entity with Key Value pairs?  It sounds like it since your are saying it could or could not have those attributes, so it sounds to me like you would have a Key Value Pair. 

    If so, then you could find those attributes:

            private bool ValidateAttributeDateRange(object target, Validation.ValidationRuleArgs e)
    {
    	    //This will only get set if you return false, so you can define your error message early.
    e.Message = "Product Attributes End Date must be before Start Date";

    int start = this.ProductAttributeCollection.Find(ProductAttributeColumns.Key, "StartDate");
    int end = this.ProductAttributeCollection.Find(ProductAttributeColumns.Key, "EndDate");
    if (start < 0 || end < 0)
    return false;

    DateTime paStart = this.ProductAttributeCollection[start].Value ?? DateTime.MinValue;
    DateTime paEnd = this.ProductAttributeCollection[end].Value ?? DateTime.MinValue;
    if (paStart > paEnd)
    return false;
    return true;
    }


    Robert Hinojosa
    -------------------------------------
    Member of the Codesmith Tools, .netTiers, teams
    http://www.nettiers.com
    -------------------------------------
    Filed under: ,
    • Post Points: 35
  • 10-10-2006 2:02 AM In reply to

    • swin
    • Top 10 Contributor
    • Joined on 06-14-2006
    • London, UK
    • Posts 924
    • Points 34,750

    Re: validation rules across entities

    I assume that for this to work though I will have had to DeepLoad my ProductAttributes first?  This is not generally a problem where I can control everything, but aren't there places in the depths of NetTiers where the an entity is checked to be valid but won't necessarily be DeepLoaded? i.e. on Update in an EntityDataSource? and so the validation will fail?

    You are right in saying that I won't necesarily always have both "start" and "end" dates.  Unfortunately the nature of our (financial) Products is such that they have a wide variety of attributes and so restricting to a fixed column format is difficult as we'd be forever changing our structure.  So we decided to put the dynamic attributes into a child table but this poses it own problems as well!

    swin

     

    ------------------------------------------------- Member of the .NetTiers team -------------------------------------------------
    • Post Points: 35
  • 10-10-2006 11:01 AM In reply to

    Re: validation rules across entities

    Well, you can control in which situations this rule is a valid rule that needs to be checked.  Typically, you should validate only when Inserting or Updating, so in those situations you might create a method that either accepts a List<ValidationRuleHandler> list that you can pass in any number of rules for these particular instances where you want to do more breadth type validation and add those rules to your rules.  Or you can just create methods that add these rules, since you know they exist but only want to be notified as to when to use the rules for a particular situation when you know your collection will be loaded or added for this validation period.

    It's really up to you.

    Robert Hinojosa
    -------------------------------------
    Member of the Codesmith Tools, .netTiers, teams
    http://www.nettiers.com
    -------------------------------------
    Filed under: ,
    • Post Points: 35
  • 10-12-2006 7:28 PM In reply to

    • Evan
    • Top 150 Contributor
    • Joined on 09-19-2006
    • Seattle, WA
    • Posts 30
    • Points 820

    Re: validation rules across entities

    I'm facing an issue similar to the one swin describes. Let's say I have a Salary entity. When a Salary is added or updated, the standard validation rules are applied (no nulls, proper date format, etc) but there's a custom business rule I'd like to apply: the new or updating Salary can't have a StartDate that is between an existing Salary's StartDate and EndDate. To apply that rule I need to retrieve the other Salary entities and do some comparing. Here's how I've handled it (using the ServiceLayer):

    I wrote a processor that compares the new or updating Salary to existing ones. I'm inserting the processor into the service pipeline in the SalaryService class like so:

     public override bool Insert(Salary entity)
            {
                ProcessorList.Add(new App_Code.Processors.NewSalaryAdjustmentProcessor(ref entity));
                Execute();
                return base.Insert(entity);
            }

    Now, the problems. When the processor sees a violation of the rule, I'm not sure what to do. Since I'm not using the Entity ValidationRules, I can't copy a BrokenRulesList into the ProcessResult. As a result, I have no meaningful way let the user know that a rule was violated. I'm leery of adding a ValidationRuleHandler to the SalaryEntity that calls the SalaryService to retrieve a TList<Salary>...it seems wrong, but maybe I've smoked too much smack. I'm guessing I may be approaching this completely bass ackwards.

    Is there an elegant/preferred solution?

    Cheers,

    Evan

     

    • Post Points: 5
  • 10-12-2006 7:36 PM In reply to

    • Evan
    • Top 150 Contributor
    • Joined on 09-19-2006
    • Seattle, WA
    • Posts 30
    • Points 820

    Re: validation rules across entities

    I should have elaborated a little more on what I'm thinking about doing to make the Service/Processor route fit my needs:

    *Manually create a BrokenRule and BrokenRuleList inside the processor when a custom business rule is broken and append the BrokenRuleList to the processor's  ProcessResult via the AddBrokenRulesList method

    *Create a custom ProcessorResult  (that inherits IProcessorResult) that provides a way to add information about rule violations.

     Any thoughts?
     

     

    • Post Points: 35
  • 10-13-2006 12:52 PM In reply to

    Re: validation rules across entities

    Those are both fine ideas that could work so that you don't have to maintain the logic inside of the entity.  Personally, i like to inject my rules dependant on the processor, but, again, this requires you to expose a method that allows this.  Your way is non-intrusive, which is good.


    Robert Hinojosa
    -------------------------------------
    Member of the Codesmith Tools, .netTiers, teams
    http://www.nettiers.com
    -------------------------------------
    Filed under:
    • Post Points: 35
  • 10-13-2006 2:15 PM In reply to

    • Evan
    • Top 150 Contributor
    • Joined on 09-19-2006
    • Seattle, WA
    • Posts 30
    • Points 820

    Re: validation rules across entities

    Thanks for the reply, Robert. A quick followup question: I'm doing these inserts and updates from an EntityDataSource. I'm not really a huge fan of inserting custom processors inside the Update and Insert methods of the EntityService itself. Is my only other option to insert them in the GridView_Inserting or _Deleting events?

    Cheers (and thanks!)

    Evan 

    • Post Points: 35
  • 10-30-2006 11:01 AM In reply to

    • swin
    • Top 10 Contributor
    • Joined on 06-14-2006
    • London, UK
    • Posts 924
    • Points 34,750

    Re: validation rules across entities

    Hi Evan,

     What did you do in the end regarding your processor and datasource/form/service?

    Cheers

    swin

    ------------------------------------------------- Member of the .NetTiers team -------------------------------------------------
    • Post Points: 35
  • 10-30-2006 11:09 PM In reply to

    • Evan
    • Top 150 Contributor
    • Joined on 09-19-2006
    • Seattle, WA
    • Posts 30
    • Points 820

    Re: validation rules across entities

    Hi swin,

    I'm sorry I neglected to make good on my promise to post my method. Deadlines are a killer :) Those deadlines also prevented me from implementing this *exactly* like I wanted to, but I had to settle for good enough.

    The meat of this solution is in the EntityDataSourceEventAdapter:

    using System;
    using System.Collections.Generic;
    using System.Text;


    namespace SBRILD.Web.App_Code
    {
        public class EntityDataSourceEventAdapter
        {
            public void RegisterDataSource<D, P>(D ds, P provider)
                where D : SBRILD.Web.Data.IDataSourceEvents, SBRILD.Web.Data.ILinkedDataSource
                where P : SBRILD.Services.IComponentService
            {
                MessageAdapter<D, P> ma = new MessageAdapter<D, P>();
                ma.RegisterIndividualDataSource(ds, provider);
                ma.DataSourceMessageEvent += new MessageAdapter<D, P>.DataSourceMessageReport(DataSourceMessageReported);
            }

            void DataSourceMessageReported(Object sender, string message)
            {
                MessageReported(sender, message);
            }

            public delegate void MessageRaised(Object sender, String message);
            public event MessageRaised MessageReported;
        }

        class MessageAdapter<D, P>
            where D : SBRILD.Web.Data.IDataSourceEvents, SBRILD.Web.Data.ILinkedDataSource
            where P : SBRILD.Services.IComponentService
        {
            //Service provider
            private SBRILD.Services.IComponentService service;
            private String messages = String.Empty;

            /// <summary>
            /// Register a datasource's Inserted and Updated events
            /// </summary>
            /// <param name="ds"></param>
            /// <param name="provider"></param>
            public void RegisterIndividualDataSource(D ds, P provider)
            {
                //Register Inserted and AfterInserted event handles
                ds.Inserted += new System.Web.UI.WebControls.ObjectDataSourceStatusEventHandler(ds_Inserted);
                //ds.AfterInserted += new SBRILD.Web.Data.LinkedDataSourceEventHandler(ds_AfterInserted);
               
                //Register Updated and AfterUpdated event handlers
                ds.Updated += new System.Web.UI.WebControls.ObjectDataSourceStatusEventHandler(ds_Updated);
                //ds.AfterUpdated += new SBRILD.Web.Data.LinkedDataSourceEventHandler(ds_AfterUpdated);

                //Register service provider
                service = provider;
            }

            /// <summary>
            /// Occurs when a DataSource's Inserted event is fired
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            void ds_Inserted(object sender, System.Web.UI.WebControls.ObjectDataSourceStatusEventArgs e)
            {
                //Iterate through each of the service's processors
                foreach (SBRILD.Services.ProcessorBase proc in service.ProcessorList)
                {
                    //Get the result associated with this processor
                    SBRILD.Services.IProcessorResult result = proc.ProcessResult;

                    //Get each BrokenRulesList from the processor
                    foreach (KeyValuePair<Type, SBRILD.Entities.Validation.BrokenRulesList> kvp in result.BrokenRulesLists)
                    {
                        //Get each BrokenRule from the BrokenRulesList
                        foreach (SBRILD.Entities.Validation.BrokenRule br in kvp.Value)
                        {
                            //Report broken rule
                            //MessageEvent(br.Description.ToString());
                            messages += br.Description;
                        }
                    }

                    //Define an IWarningProcessorResult
                    SBRILD.Services.App_Code.IWarningProcessorResult warningResult = proc.ProcessResult as SBRILD.Services.App_Code.IWarningProcessorResult;

                    //If it exists
                    if (warningResult != null)
                    {
                        //Get each warning message in the list
                        foreach (String warning in warningResult.WarningList)
                        {
                            messages += warning;
                        }
                    }

                }

                //Handle any EntityNotValidExceptions
                if (e.Exception != null && e.Exception.GetType() == typeof(SBRILD.Entities.EntityNotValidException))
                {
                    e.ExceptionHandled = true;
                }

                if (messages != String.Empty)
                {
                    DataSourceMessageEvent(sender, messages);
                }
            }

            /// <summary>
            /// Occurs when a DataSource's Updated event is fired
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            void ds_Updated(object sender, System.Web.UI.WebControls.ObjectDataSourceStatusEventArgs e)
            {
                //Iterate through each of the service's processors
                foreach (SBRILD.Services.ProcessorBase proc in service.ProcessorList)
                {
                    //Get the result associated with this processor
                    SBRILD.Services.IProcessorResult result = proc.ProcessResult;

                    //Get each BrokenRulesList from the processor
                    foreach (KeyValuePair<Type, SBRILD.Entities.Validation.BrokenRulesList> kvp in result.BrokenRulesLists)
                    {
                        //Get each BrokenRule from the BrokenRulesList
                        foreach (SBRILD.Entities.Validation.BrokenRule br in kvp.Value)
                        {
                            //Report broken rule
                            //MessageEvent(br.Description.ToString());
                            messages += br.Description;
                        }
                    }

                    //Define an IWarningProcessorResult
                    SBRILD.Services.App_Code.IWarningProcessorResult warningResult = proc.ProcessResult as SBRILD.Services.App_Code.IWarningProcessorResult;

                    //If it exists
                    if (warningResult != null)
                    {
                        //Get each warning message in the list
                        foreach (String warning in warningResult.WarningList)
                        {
                            messages += warning;
                        }
                    }

                }

                //Handle any EntityNotValidExceptions
                if (e.Exception != null && e.Exception.GetType() == typeof(SBRILD.Entities.EntityNotValidException))
                {
                    e.ExceptionHandled = true;
                }

                if (messages != String.Empty)
                {
                    DataSourceMessageEvent(sender, messages);
                }
            }

            public delegate void DataSourceMessageReport(Object sender, String message);
            public event DataSourceMessageReport DataSourceMessageEvent;
        }
    }

    //END

    I'm using the ServiceLayer model. Even more specifically, the processor results returned from the service's execution implement a custom interface (IWarningProcessorResult) in addition to extending nettier's GenericProcessorResult. The idea here is that I have a number of processors that modify entities that would otherwise be invalid. These modifications prevent some entities from being flagged as invalid (and thus providing information in the BrokenRulesList), but I still need to notify the user that the adjustments were made (an example here is a percentage entity that is increased by, say, 7%, and this increase makes the sum of some related entities = 103%...the processor would reduce the 7% to 4% so the total was 100%, and then inform the user of the automatic reduction) . That's where the IWarningProcessorResult came in:

        /// <summary>
        /// The interface that processors implement if they report warnings in addition to the
        /// BrokenRules reported by entity validation
        /// </summary>
        public interface IWarningProcessorResult
        {
            /// <summary>
            /// List of strings describing each warning
            /// </summary>
            List<String> WarningList { get;}
        }

    //END 

    The Inserted and Updated event handlers iterate through the BrokenRulesLists and WarningLists and fires an event reporting the messages. I've hooked up to that event in the asp page (also, here's where I register the DataSources):

        private void RegisterDataSourceEventHandlers()
        {
            //Create new EntityDataSourceEventAdapter
            SBRILD.Web.App_Code.EntityDataSourceEventAdapter edsa = new SBRILD.Web.App_Code.EntityDataSourceEventAdapter();

            //Register data sources
            edsa.RegisterDataSource(DefaultDistributionDataSource, DefaultDistributionDataSource.Provider as SBRILD.Services.IComponentService);
            edsa.RegisterDataSource(SalaryDataSource, SalaryDataSource.Provider as SBRILD.Services.IComponentService);
           
            //Register event handler will write messages to page that  that will
            edsa.MessageReported += new SBRILD.Web.App_Code.EntityDataSourceEventAdapter.MessageRaised(edsa_MessageReported);      
        }

        /// <summary>
        /// Handle messages sent from the EntityDataSourceEventAdapter
        /// </summary>
        /// <param name="message"></param>
        void edsa_MessageReported(Object sender, string message)
        {
            DataSourceEventsText = message;
        }
     

    I wish I had time to make the implementation more generic, but then again, I wish I had more time for alot of things :) I'm absolutely positive there are better ways to accomplish this, but alas, I needed a concrete implementation and fast. Please do let me know if there's anything that doens't make sense or isn't helpful.

    Cheers,

    Evan

     

    • Post Points: 45
  • 11-25-2007 11:48 PM In reply to

    • vsagar41
    • Not Ranked
    • Joined on 11-26-2007
    • Canberra
    • Posts 7
    • Points 101

    Re: validation rules across entities

    Hi Evan,

    Will you be able to guide me how you implemented the IWarningProcessorResult.

    I want to send the warning and not the Broken Rule. I am using your above said class. Just not able to fiqure out how I hook it in my processor class.

     Can you please show me some code where you implemented where you added the warning

    thank you very much

    Vijay

    • Post Points: 35
  • 11-26-2007 12:39 PM In reply to

    • Evan
    • Top 150 Contributor
    • Joined on 09-19-2006
    • Seattle, WA
    • Posts 30
    • Points 820

    Re: validation rules across entities

    Hi Vijay,

    Good question. What I did was create a custom ProcessorResult class that implements the NetTiers GenericProcessorResult class and the IWarningProcessorResult interface, like so:

    using System;
    using System.Collections.Generic;
    using System.Text;

        /// <summary>
        /// A custom processor result
        /// </summary>
        public class CustomProcessorResult : Services.GenericProcessorResult, IWarningProcessorResult
        {
            private System.Collections.Generic.List<String> warningList;
           
            public List<String> WarningList
            {
                get
                {
                    if (warningList == null)
                    {
                        warningList = new System.Collections.Generic.List<String>();
                    }
                    return warningList;
                }
            }

            public bool HasWarnings
            {
                get
                {
                    if (WarningList.Count == 0)
                    {
                        return false;
                    }
                    else
                    {
                        return true;
                    }
                }
            }
        }

     

    Then, in my service class I reference the CustomProcessorResult and add message to the List<String> WarningList:

    using System;
    using System.Collections.Generic;
    using System.Text;

        /// <summary>
        /// Example class
        /// </summary>
        public class CustomProcessor : ProcessorBase
        {
            /// <summary>
            /// Process
            /// </summary>
            /// <returns></returns>
            public override IProcessorResult Process()
            {
               if(true)
               {
                   ReportWarning("Some warning text");
               }
      
                return ProcessResult;
            }


            /// <summary>
            /// Report an error
            /// </summary>
            /// <param name="message"></param>
            private void ReportWarning(String warningMessage)
            {
               processorResult.WarningList.Add(warningMessage);
            }

            /// <summary>
            /// ProcessResult for this process
            /// </summary>
            public override IProcessorResult ProcessResult
            {
                get
                {
                    if (processorResult == null)
                    {
                        processorResult = new CustomProcessorResult ();
                    }
                    return processorResult;
                }
            }

            private CustomProcessorResult processorResult;
        }
     

     I haven't compiled any of the above code, but hopefully it gives you an idea. Please let me know if you have any more questions!

    Cheers,

     Evan

    Filed under: ,
    • Post Points: 35
  • 11-26-2007 9:04 PM In reply to

    • vsagar41
    • Not Ranked
    • Joined on 11-26-2007
    • Canberra
    • Posts 7
    • Points 101

    Re: validation rules across entities

    Hi Evan,

    Thanks a lot. It works perfectly, but I am getting strange problem. Sorry I am bit new to this.

    In the aspx page where I am using it, what we are doing if the Insert is successful then we are redirecting it to same page in updae mode. Basically we are using FormUtil class for that.

    So if we have a successful insert it come up with a blank, but if there is an error it shows the error message. I feel it looses the Datasource current entity after successful insert and the Forutil class is not able to create the querystring.

    I am registering RegisterDataSourceEventHandlers(); in page load.....

    Any thoughts about it.

    Thanks again for helping me out here

    Cheers

    Vijay

    • Post Points: 5
  • 11-27-2007 9:05 PM In reply to

    • vsagar41
    • Not Ranked
    • Joined on 11-26-2007
    • Canberra
    • Posts 7
    • Points 101

    Re: validation rules across entities

    Hi Evan,

    Don't worry, I found a way to redirect after a successful insert. I write that part of code in After_Inserted event.

    Thanks a lot of your output.

    Cheers

    Vijay

    • Post Points: 35
  • 11-28-2007 1:39 AM In reply to

    • swin
    • Top 10 Contributor
    • Joined on 06-14-2006
    • London, UK
    • Posts 924
    • Points 34,750

    Re: validation rules across entities

     Can you share with us what you did for the benefit of others.

    Thanks

    swin 

    ------------------------------------------------- Member of the .NetTiers team -------------------------------------------------
    • Post Points: 35
Page 1 of 2 (16 items) 1 2 Next > | RSS
Copyright © 2008 CodeSmith Tools, LLC
Powered by Community Server (Commercial Edition), by Telligent Systems