Adding Validation Annotation to fields on EF complex objects created from DB Stored Procedures

I have a model which is made up of complex objects created by EF 5.0 from stored procedures in database.  I want to add some data validation annotation.  However, this needs to be added to the specific fields the model.  But EF model classes of the complex
objects carry the warning that they are auto-generated and that each update from the database will re-write the model from scratch, and therefore the developer should not alter them.

On http://forums.asp.net/t/1997986.aspx "One View 3 type of Address and one Submit Button", cnuonline provided what I consider to be the perfect example of what I am doing except for the fact that his model
was developer defined whereas mine will be coming from EF.  He shows the validation message in the view like I do.  My question is how does one customize that validation if you can’t use annotation in the model?

Here is view model

    public class PRTrackViewModel
    {
        public PurchaseRequests_Get_Result PurchaseRequest { get; set; }

        public List<PRLineTrackViewModel> PRLines { get; set; }

        public List<PRCDRLsTrackViewModel> PRCdrls { get; set; }

        public SelectList DDAMS { get; set; }
    }

Here is model of complex object PurchaseRequests_Get_Result .

//------------------------------------------------------------------------------
// <auto-generated>
//    This code was generated from a template.
//
//    Manual changes to this file may cause unexpected behavior in your application.
//    Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace VFS_ProcurementTracking.Models
{
    using System;
    
    public partial class PurchaseRequests_Get_Result
    {
        public int PRID { get; set; }
        public string PR_Number { get; set; }
        public Nullable<System.DateTime> Estimated_Award_Dte { get; set; }
        public Nullable<System.DateTime> Date_Sent_To_NavSup { get; set; }
        public string SolicitationNum { get; set; }
        public System.DateTime PRDateCreated { get; set; }
        public string Originator { get; set; }
        public bool PR_Cancelled { get; set; }
        public string PR_Remarks { get; set; }
    }
}

Actually, the proper way to handle this is to use MetaData classes that allow you to assign data annotations without disturbing the original generated model

Example–

Generated model:

public class User
{
  public int UserId { get; set; }
  public string UserName { get; set; }
  public string Email { get; set; }
  public string Phone { get; set; }
}

the MetaData class:

using System.ComponentModel;
using System.ComponentModel.DataAnnotations; public class UserMetaData { [Required] [DisplayName("User Name")] public string UserName { get; set; } [Required] [DataType(DataType.EmailAddress)] public string Email { get; set; } [DataType(DataType.PhoneNumber)] public string Phone { get; set; } }

You should notice that I didn’t include the "UserId" property from the original model, and this is because it doesn’t need any validation/annotations.  Any property that doesn’t need validation or annotations can be excluded.

Lastly, create the metadata-partial class (I name mine "MetaPartialClasses.cs")

using System.ComponentModel.DataAnnotations;

namespace AppName.Models
{

  [MetadataType(typeof(UserMetaData))]
  public partial class User
  {
  }

}

This last class binds the annotations of the MetaData class to our generated "User" model.  If you have multiple MetaData classes, you can add all of their associations to your "MetaPartialClasses.cs" class.  Example:

[MetadataType(typeof(UserMetaData))]
  public partial class User
  {
  }

[MetadataType(typeof(ProductMetaData))]
  public partial class Product
  {
  }

[MetadataType(typeof(StudentMetaData))]
  public partial class Student
  {
  }

Hope this helps

Let say I have a model like the following

public class Person
{
  public int PersonId { get; set; }
  public string Name { get; set; }
  public DateTime birthDate { get; set; }
  public DateTime employmentDate { get; set; }
}

I would want to generate a Range attribute that required that employmentDate < birthDate

using System.ComponentModel;using System.ComponentModel.DataAnnotations;

public class PersonMetaData
{
  [Required]
  [DisplayName("User Name")]
  public string UserName { get; set; }

  [Required]
  [Range(typeof(DateTime), DateTime.MinValue.ToString(), employmentDate,
ErrorMessage = "Value for {0} must be between {1} and {2}")][Range(DataType.EmailAddress)] public DateTime birthDate { get; set; } [Required]
public DateTime employmentDate { get; set; } }

But I found this on Stackoverflow by Daniel Elliot at http://stackoverflow.com/questions/1406046/data-annotation-ranges-of-dates

unfortunately in a decorating attribute the values you use must be static and not dynamic. I’d suggest writing your own DataAnnotation if you need it to be dynamic.

So how would one go about doing what he recommended?

 

Hi joeller,

For this requirement, please refer to these links below:

# MVC unobtrusive range validation of dynamic values

http://stackoverflow.com/questions/7851035/mvc-unobtrusive-range-validation-of-dynamic-values

# Custom Unobtrusive jQuery Validation with Data Annotations in MVC 3

http://thewayofcode.wordpress.com/2012/01/18/custom-unobtrusive-jquery-validation-with-data-annotations-in-mvc-3/

Best Regards

Starain Chen

Ideally you should not use these poco classes into your views.

You should have separate project for Data operations i.e EF.

You should have viewmodels in your project reflecting these EF poco classes and in those classes you should put dataannotation validations.

For Example: You will have 2 projects say 1. WebUI i.e your main project and 2. BL project having EF.

In WebUI viewmodels, you will put DataAnnotation validations and not in BL project.

You will pass WebUI viewmodels into your views.

@DhavalShah89 I don’t understand what you are getting at.  Every tutorial I’ve read or viewed on using MVC 4 with EF5 never said anything about having multiple projects.  Are you saying to build a dll to access the data and another dll to massage the data
and a web app to use the data? 

But I don’t see how that is going to help.  How can I put data annotations on my view model, when I can’t see the individual fields of the class on that model.  The view model would be using a PR class to display everything about the PR.  But the date fields
in the PR class are not going to be redefined in the view model.

Also I am not sanguine about the ability to properly post a multi project solution on our servers, as I have no confidence in the ability of the so-called Web admins to do so.

@cuonline  This article ends by applying the decorations to the class of the object from the model which is the file that is going to be rewritten everytime there is an update from the database. 

@Starain chen Both of these examples end with them decorating the model which is precisely what I can’t do.

@cuonline and @Starain chen That is why JohnLocke suggested making partial classes of the metadata files to do the decoration.

Also I really don’t want put any major effort into building a custom class to carry out this validation, as I have a short deadline, the code being used so far beyond me that it would require a steep learning curve that I do not have time for.  If I can’t
do this without a custom class, I can much more quickly build a JQuery onblur event handler which will compare the two values and return and write text to a label reporting the error.

@JohnLocke  Are there supposed to be MetaData files for my generated objects already in the project?  I did a "Find" for the "entire solution" and did not see any Metadata files for my EF objects.

joeller

I can much more quickly build a JQuery onblur event handler which will compare the two values and return and write text to a label reporting the error.

OK I simply did this:

    $(function () {
        //alert("created function");
        var CheckPRDateCreated = function () {
            var bError = false;
            var createDate = new Date($("#PurchaseRequest_PRDateCreated").val());
            var currentDate = new Date();
            if (createDate > currentDate) {
                var bError = true;
            }

            if (bError == true) {
                $("#PurchaseRequest_PRDateCreated").addClass("input-validation-error").removeClass("valid");
                $("span[data-valmsg-for = 'PurchaseRequest.PRDateCreated']").text("This PR Create Date is in the future.nPlease choose another.").addClass("field-validation-error").removeClass("field-validation-valid");
                //alert("This PR Number already exists.nPlease choose another.");
                setTimeout(function () { $('*').datepicker("hide"); $("#PurchaseRequest_PRDateCreated").focus(); }, 100);
            }
            else {
                $("#PurchaseRequest_PRDateCreated").addClass("valid").removeClass("input-validation-error");
                $("span[data-valmsg-for = 'PurchaseRequest.PRDateCreated']").text("").addClass("field-validation-valid").removeClass("field-validation-error");
            }
        };
        $("#PurchaseRequest_PRDateCreated").on("change", CheckPRDateCreated);
    });

My only issue is that I can’t get the span generated by the ValidateMessageFor to show the width attribute of the style  from the class "field-validation-error" to be observed and I can’t get the text that goes into that span to observe the "new line", (n) escape
character.  So instead of showing a multiline bit of text on a corner of the table, it shows a message that stretches across the width of the and is hidden under a datepicker.

Very frustrating.  I think Razor was designed to shorten the lives of older programmers so that the young kids could get all of their jobs. ;-)

 

Leave a Reply