locked
SPA and client side validation RRS feed

  • Question

  • User-209473449 posted

    Hello.

    I'm looking for best practices for validation in SPA applications built using ASP.NET stack.

    Asp .Net MVC/ Web API provides both client side and server side validation using annotation attributes for model.

    To build SPA I want to use AngularJS which has its own validation attributes.

    How you are implementing client side validation in AngularJS?

    1. Do you ignore model annotations and manually create Angular's validation attributes and tags which display errors?

    2. Do you use angular-specific html helpers in MVC views to generate client validation logic?

    3. Something else.

    Is some way to plug in AngularJS validation which allows do not change MVC views and use standard html helpers like EditorFor(...)?

    Does MVC and SPA can be effectively used simultaneously?

    Wednesday, February 25, 2015 4:18 AM

Answers

  • User1689970273 posted

    Hi Alex,

    In order for you to have model validations without those data annotations, you can just inherit your model from IValidateObject. These code snippets may not be complete since this is an enterprise validation. We normally don't put attributes on the model as you'll end up recreating almost the same model when you get into view/model/controller manipulation and won't adhere to DRY or SRP principles. These attributes are not reusable outside of MVC. I hope this helps...

    The practice here is separation of concern. Validation can be both at Model level(server side) or client side.  As much as possible, we want to hide some business logic in angular or any javascript as it can easily been hacked to understand how you validate it before you passed it. You don't want to give an entire control to one layer and perhaps rely that it is completely safe.

    e.g.

    Sample of not a good practice

    public class Employee{

       [Required]

       [ErrorMessage(xxxxxxx)]

       public string Name {get;set;}

    }

    public abstract class BaseModel : IValidatableObject
        {
          

            #region Implementation of IValidatableObject
            public abstract System.Collections.Generic.IEnumerable<ValidationResult> Validate(ValidationContext validationContext);              
            #endregion

                
        }

     public class Employee:BaseModel
        {
            public string FullName { get; set; }
            public virtual Address Address { get; set; }
            public ICollection<Benefit> Benefits {get;set;}

            public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
            {
                if (Address == null)
                {
                    yield return new EnhancedMappedValidationResult<Employee>(d => d.Address, "Address is mandatory");
                   
                }
                if (FullName == null)
                {
                    yield return new EnhancedMappedValidationResult<Employee>(d => d.FullName, "FullName is mandatory");
                   
                }
            }
        }

     public class EnhancedMappedValidationResult<TEntity> : ValidationResult
        {
            /// <summary>
            /// Property in error on which the validation result error is assigned.
            /// Getter only because we want the exception to be set when the validation result
            /// is created.
            /// </summary>
            /// <value>
            /// The property.
            /// </value>
            public Expression<Func<TEntity, object>> Property { get; private set; }

            /// <summary>
            /// Initializes by setting the propertu and the error.
            /// </summary>
            /// <param name="property">The property.</param>
            /// <param name="errorMessage">The error message.</param>
            public EnhancedMappedValidationResult(Expression<Func<TEntity, object>> property, string errorMessage)
                : base(errorMessage, new List<string>())
            {
                Property = property;
                ((List<string>)base.MemberNames).Add(LambdaUtilities.GetExpressionText(Property));
            }
        }

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Wednesday, February 25, 2015 4:34 PM

All replies

  • User-1620313041 posted

    People usually duplicates validation rules in AngularJs and on the server side. Server side validation works properly, but validation messages are not be dispatched automatically to the user interface. You need to show server side errors in a message box or some other custom display area.

    If you use knockout.js for the SPA there are tricky ways to use both client side and server side Asp.net stack validation based on validation attributes (you need to write some not trivial Js code, however), but with angulaJs this is quite impossible.

    Wednesday, February 25, 2015 1:45 PM
  • User1689970273 posted

    Hi Alex,

    In order for you to have model validations without those data annotations, you can just inherit your model from IValidateObject. These code snippets may not be complete since this is an enterprise validation. We normally don't put attributes on the model as you'll end up recreating almost the same model when you get into view/model/controller manipulation and won't adhere to DRY or SRP principles. These attributes are not reusable outside of MVC. I hope this helps...

    The practice here is separation of concern. Validation can be both at Model level(server side) or client side.  As much as possible, we want to hide some business logic in angular or any javascript as it can easily been hacked to understand how you validate it before you passed it. You don't want to give an entire control to one layer and perhaps rely that it is completely safe.

    e.g.

    Sample of not a good practice

    public class Employee{

       [Required]

       [ErrorMessage(xxxxxxx)]

       public string Name {get;set;}

    }

    public abstract class BaseModel : IValidatableObject
        {
          

            #region Implementation of IValidatableObject
            public abstract System.Collections.Generic.IEnumerable<ValidationResult> Validate(ValidationContext validationContext);              
            #endregion

                
        }

     public class Employee:BaseModel
        {
            public string FullName { get; set; }
            public virtual Address Address { get; set; }
            public ICollection<Benefit> Benefits {get;set;}

            public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
            {
                if (Address == null)
                {
                    yield return new EnhancedMappedValidationResult<Employee>(d => d.Address, "Address is mandatory");
                   
                }
                if (FullName == null)
                {
                    yield return new EnhancedMappedValidationResult<Employee>(d => d.FullName, "FullName is mandatory");
                   
                }
            }
        }

     public class EnhancedMappedValidationResult<TEntity> : ValidationResult
        {
            /// <summary>
            /// Property in error on which the validation result error is assigned.
            /// Getter only because we want the exception to be set when the validation result
            /// is created.
            /// </summary>
            /// <value>
            /// The property.
            /// </value>
            public Expression<Func<TEntity, object>> Property { get; private set; }

            /// <summary>
            /// Initializes by setting the propertu and the error.
            /// </summary>
            /// <param name="property">The property.</param>
            /// <param name="errorMessage">The error message.</param>
            public EnhancedMappedValidationResult(Expression<Func<TEntity, object>> property, string errorMessage)
                : base(errorMessage, new List<string>())
            {
                Property = property;
                ((List<string>)base.MemberNames).Add(LambdaUtilities.GetExpressionText(Property));
            }
        }

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Wednesday, February 25, 2015 4:34 PM
  • User1711366110 posted


    How you are implementing client side validation in AngularJS?
    s some way to plug in AngularJS validation which allows do not change MVC views and use standard html helpers like EditorFor(...)?
    Does MVC and SPA can be effectively used simultaneously?

      As per your case, the following link which may give you some guidance for fix this issue.

    1. Click here to know more about the model validation via annotations.

    2. If you need to implement a conditional validation based on some field (e.g. if A=true, then B is requited), while maintain property level error message (this is not true for the custom validators that are on object level) you can achieve this by handling "ModelState" by simply remove unwanted validations from it .Click here to refer.

    3. Click here to know more about the building SPA with angular.js

    --
    with regards,
    Edwin

    Thursday, February 26, 2015 12:50 AM