locked
Re: Dynamic Data EF 5 ( DbContext ) - How to configure Custom Validation ? RRS feed

  • Question

  • User343140345 posted

    Hi,

    How and where do I code Entity Validation logic when I have DbContext and simple classes with DD controls and EntityDataSource ?

    It seems that DD controls need to use ObjectContext but I don't know where I can put custom Entity validation code when I have DbContext classes.

    VS 2012, EF 5, DD 4, C# , Sql Server 2008 , EF Database First edmx with T4 templates

    Global.asax - We register an ObjectContext ( mapped from the generated DBContext )

     // E N T I T Y  F R A M E W O R K - remap DbContext to ObjectContext

    DefaultModel.RegisterContext(() =>

    { return ((IObjectContextAdapter)new NVTracEntities()).ObjectContext; },

         new ContextConfiguration() { ScaffoldAllTables = true }   );

    The pagetemplate Edit.ascx uses the EntityDataSource control which uses the ObjectContext class.

    But when saving the events in the DbContext don’t fire and the Validate event in the entity partial class doesn’t fire also.

         [MetadataType(typeof(ProvidersNewMetadata))]

        public partial class ProvidersNew   : IValidatableObject 

        {

         private IList<ValidationResult> validationErrors = new List<ValidationResult>();

     

    // ??? D O E S N ' T  F I R E  on Save ?????

            IEnumerable<ValidationResult> IValidatableObject.Validate(ValidationContext validationContext)

            {

                this.validationErrors.Clear();

                 if (this.ProviderName == "xxx") // test only

                {

                    this.validationErrors.Add(new ValidationResult("Invalid Provider Name"));

                }

                return this.validationErrors;

            }

             // DbContext  Base Class doesn't have this stuff ! like ObjectContext in Linq To Sql

             //partial void OnContextCreated()

            //{

            //    SavingChanges += OnSavingChanges;

            //}

             //public void OnSavingChanges(object sender, EventArgs e)

            //{

            //    var stateManager = ((ProvidersNew)sender).ObjectStateManager;

            //    var changedEntities = stateManager.GetObjectStateEntries(EntityState.Modified | EntityState.Added);

            //    foreach (var entry in changedEntities)

            //    {

            //        if (entry.Entity is Product)

            //        {

            //            Product prod = (Product)entry.Entity;

            //            if (prod.InStock && prod.Amount <= 0)

            //            {

            //                throw new ValidationException("Product cannot be in stock when the amount is zero or ess");

            //            }

            //        }

            //    }

            //}

               internal class ProvidersNewMetadata

            {

                public int ProviderID { get; set; }

                public string ProviderName { get; set; }

                public int RegionID { get; set; }

                public int FundID { get; set; }

     . . .

    } }

     

    *** I also tried overridng the ValidateEntity  method in DBContext but they DON'T FIRE

        public partial class NVTracEntities : DbContext     {

            protected override bool ShouldValidateEntity(DbEntityEntry entityEntry)       

      {          if ( entityEntry.Entity is ProvidersNew)                 return true;

                return base.ShouldValidateEntity(entityEntry);         }

              protected override DbEntityValidationResult ValidateEntity (       DbEntityEntry entityEntry, IDictionary<object, object> items)       

      {             var result = base.ValidateEntity(entityEntry, items);

          . . . . .

             }

     

    Tuesday, October 8, 2013 5:26 PM

All replies

  • User697462465 posted

    Hi laguy,

    I suggest you to use the TryValidateObject method to solve it.

    According to your description, I have created a sample, it have a custom validate method, which to compare the two values.

    Please try to refer to the following code:

    In the ValidateCustom class:

    public class ValidateCustom : IValidatableObject
        {
            [Required]
            public bool Enable { get; set; }
    
            [Range(1, 5)]
            public int Prop1 { get; set; }
    
            [Range(1, 5)]
            public int Prop2 { get; set; }
    
            public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
            {
                var results = new List<ValidationResult>();
                if (this.Enable)
                {
                    Validator.TryValidateProperty(this.Prop1,
                        new ValidationContext(this, null, null) { MemberName = "Prop1" },
                        results);
                    Validator.TryValidateProperty(this.Prop2,
                        new ValidationContext(this, null, null) { MemberName = "Prop2" },
                        results);
    
                    // you can add your custom validation
                    if (this.Prop1 > this.Prop2)
                    {
                        results.Add(new ValidationResult("Prop1 must be smaller than Prop2"));
                    }
                }
                return results;
            }
        }

    In the test method:

    public void DoValidation()
            {
                var customValidate = new ValidateCustom()
                {
                    //Enable = null,
                    Prop1 = 3,
                    Prop2 = 2
                };
    
                bool validateAllProperties = true;
    
                var results = new List<ValidationResult>();
                customValidate.Enable = true;
                bool isValid = Validator.TryValidateObject(
                    customValidate,
                    new ValidationContext(customValidate, null, null),
                    results,
                    validateAllProperties);
                Response.Write(isValid+"<br/>");
                foreach (ValidationResult r in results)
                {
                    Response.Write(r.ErrorMessage);
                }
                
            }

    There is also a blog, maybe useful to you:
    http://blogs.msdn.com/b/adonet/archive/2010/12/15/ef-feature-ctp5-validation.aspx

    Hope it can help you.

    If your problem still exists, please let me know.

    Best Regards,
    Terry Guo

    Wednesday, October 9, 2013 10:48 PM
  • User343140345 posted

    Hi Terry,

    Thanks for your help. :)

    Since, I'm using Dynamic Data and the Edit Mode is using the Page Template:  edit.ascx  .cs,  I don't explicitly

    create the  DBContext Entities class and do the SaveChanges.

    In Edit.ascx  there is a FormView bound to  EntityDataSource control.

    I don't know how to intercept the Save button submit and issue your code example.

    Again, the EntityDataSource control creates a ObjectContext and not a DbContext object.

    My EF Entities Database First  T4 Generated classes do NOT have the Validation methods that are generated 

    in the Linq To Sql ObjectContext.

    I am unable to get IValidatableObject Validate method to fire during the Save.

    The article you gave me doesn't address the situation when you are using the

    EntityDataSource with the FormView control.

    Switch to Linq To Sql:

    I'm getting frustrated, since I believe that the Entity  Validation is much more straight forward when working

    with a Linq To Sql .dbml file generated ObjectContext.

    So, if I can't get this Entity Validation working, should I switch to DD with  Linq To Sql ?

    Are there some big limitations with DD L2S that are handled with DD EF Entities ?

    Thanks, Paul

    UPDATE: 

    I made a little more progress, using the TryValidate code in the EntityDataSource Control Updating EventHandler

    I can fire the Validate method on the Entity but I don't know how to show ( bind ) the Errors to  the DynamicValidator ( ValidationSummary ) control ?

    I guess I could create another ValidationSummary control on the page and do that ?

    Also, when there is a Validation error and I issue the  e.Cancel  the updates are NOT saved (which is good) but DD logic goes back ( redirects ) to the List View  

    instead of stayiing on the current Edit.aspx page.

    protected void DetailsDataSource_Updating(object sender, EntityDataSourceChangingEventArgs e)

    {
    var obj1 = e.Entity;
    if (obj1 is IValidatableObject)
    {
    var objValidatable = (IValidatableObject)(obj1);

    ICollection<ValidationResult> listErrs = new List<ValidationResult>();

    bool isValid = Validator.TryValidateObject(objValidatable, new ValidationContext(objValidatable), listErrs);

    if (!isValid)
    {
    e.Cancel = true;
    // e.ExceptionHandled = true;

    // ??? How to bind listErrs to the ValidationSummary control ???
    }
    }

    }

    Update2:  Here is code in Edit.ascx.cs to show Custom Validation Errors in injected ValidationSummary control

    protected void FormView1_ItemUpdated(object sender, FormViewUpdatedEventArgs e)
    {
    if ( bErrorFlag == false)
    {
    // no Entity Validate method error, continue

    if (e.Exception == null || e.ExceptionHandled)
    {
    Response.Redirect(table.ListActionPath);
    }
    }
    }

    private bool bErrorFlag = false; // Error Flag set in Updating EventHandler
    private string strErrorMsg = "";

    protected void DetailsDataSource_Updating(object sender, EntityDataSourceChangingEventArgs e)
    {
    var obj1 = e.Entity;
    if (obj1 is IValidatableObject)
    {
    var objValidatable = (IValidatableObject)(obj1);
    //IEnumerable<ValidationResult> errs = obj.Validate(new ValidationContext(obj));

    ICollection<ValidationResult> listErrs = new List<ValidationResult>();

    bool isValid = Validator.TryValidateObject(objValidatable, new ValidationContext(objValidatable), listErrs);

    if (!isValid)
    {
    e.Cancel = true;
    bErrorFlag = true; // set error flag
    foreach ( var item in listErrs)
    {
    strErrorMsg += " " + item.ErrorMessage;
    }

    DisplayCustomMessageInValidationSummary(strErrorMsg); // Display in ValidationSummary
    }
    }

    }

    private void DisplayCustomMessageInValidationSummary(string message)
    {
    //credit to:  http://www.extremeexperts.com/Net/FAQ/DisplayCustomMsgInValidationSummary.aspx


    CustomValidator CustomValidatorCtrl = new CustomValidator();

    CustomValidatorCtrl.IsValid = false;

    CustomValidatorCtrl.ErrorMessage = message;

    this.Page.Controls.Add(CustomValidatorCtrl);

    }

    So, I'm hoping there is a cleaner way to do this where the error message can also show next to the

    column as well as in the ValidationSummary.

    Any help please ?

    LA Guy

    Friday, October 11, 2013 4:47 PM
  • User697462465 posted

    Hi laguy,

    If you want to display thest validation message to client, I suggest you to use the CustomValidator control.

    About the CustomValidator please try to refer to the following code:

    aspx code:

    <asp:CustomValidator id="CustomValidator1" runat="server" OnServerValidate="TestValidate" />

    cs code:

    protected void TestValidate(object sender, ServerValidateEventArgs args)
    {
       args.IsVlid = //this is your validate business code...
       CustomValidator1.ErrorMessage = //this is error message...
    }

    More information please try to refer to:
    http://msdn.microsoft.com/en-us/library/9eee01cx(v=vs.85).aspx

    Hope it can help you.

    Best Regards,
    Terry Guo

    Tuesday, October 15, 2013 11:35 AM
  • User-330204900 posted

    As long as you thow a ValidationException you should be OK and DD works fine with DB context now to see how look here

    Thursday, October 17, 2013 4:53 AM