ASP.NET MVC Validation form with AngularJS

angularjs form validation
angular form validation
angularjs form validation on submit
angularjs form validation error message

I'm with a project in MVC 4 and AngularJS (+ twitter bootstrap). I usually use in my MVC projects "jQuery.Validate", "DataAnnotations" and "Razor". Then I enable these keys in my web.config to validate properties of model on the client:

<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />

For example if I have in my model this:

[Required]
[Display(Name = "Your name")]
public string Name { get; set; }

With this Cshtml:

@Html.LabelFor(model => model.Name)
@Html.TextBoxFor(model => model.Name)
@Html.ValidationMessageFor(model => model.Name)

The html result would:

<label for="Name">Your name</label>
<input data-val="true" data-val-required="The field Your name is required." id="Name" name="Name" type="text" value="" />
<span class="field-validation-valid" data-valmsg-for="Name" data-valmsg-replace="true"></span>

But now when I use AngularJS, I want to render maybe like this:

<label for="Name">Your name</label>
<input type="text" ng-model="Name" id="Name" name="Name" required />
<div ng-show="form.Name.$invalid">
   <span ng-show="form.Name.$error.required">The field Your name is required</span>
</div>

I do not know if there are any helper or "Data Annotation" to resolve this. I understand that AngularJS has many more features like:

<div ng-show="form.uEmail.$dirty && form.uEmail.$invalid">Invalid:
    <span ng-show="form.uEmail.$error.required">Tell us your email.</span>
    <span ng-show="form.uEmail.$error.email">This is not a valid email.</span>
</div>

Well, specifically. I need some helper or "Data Annotation" to resolve the attributes (Data Annotation) for display on the client with AngularJS.

If it still does not exist, perhaps it is time to do, like RazorForAngularJS

Edit

I think perhaps the best way to work with ASP.NET MVC and AngularJS is do it (front-end) by hand (writing all the HTML by hand)


As someone that's authored an ASP.Net/Angular website, I can tell you that you're going to be way better off stepping away from using Razor to render your HTML where you can.

In my projects I've set up one razor view to render my main page (I'm using a single page app written in Angular), then I have a folder of straight .html files that I use as my templates for Angular.

The rest is done in ASP.Net Web API calls in my case, but you can also use MVC action with JSON results.

As soon as I switched to this architecture, things went a lot more smoothly for me, development wise.

ASP.NET MVC Validation form with AngularJS, As someone that's authored an ASP.Net/Angular website, I can tell you that you'​re going to be way better off stepping away from using Razor to render your  In this tutorial, I will show you how to create a form with validation using AngularJs and ASP.NET MVC 5.In the previous tutorial, I have shown how to fetch record from the database using AngularJs in ASP.NET MVC 5 Application.


I agree with blesh idea about stepping away from razor, but you can create some tools for creating pages more rapid. IMHO it is better to use razor features where they needed instead of removing it from out toolset.

BTW have a look at ngval. It brings data annotations to client side as angularjs validators. It has an html helper and an angular module. I have to mention that project is in early development stages.

AngularJS with ASP.NET MVC [3] - Form Validation, In this video we will implement form validation with angularjs and asp.net mvc Angular 6 Duration: 13:29 Posted: Nov 4, 2017 One of the first things you’ll notice doesn’t work very well when you integrate AngularJS and ASP.NET MVC is forms validation. We used to have it so good with unobtrusive validation, things just worked. Now we need to get used to a new validation system, as well as tie our existing server side in.


I wrote a directive to smooth out the transition from MVC to AngularJs. The markup looks like:

<validated-input name="username" display="User Name" ng-model="model.username" required>

Which behaves identically to Razor conventions, including delaying validation until after a field is modified. Over time, I've found maintaining my markup pretty intuitive and simple.

My article on the subject

Plinkr

Validating a Form using Angular Validation Directives, In ASP.NET MVC, validations can be checked using DataAnnotations by applying validation rules and error messages on the model properties. ASP.NET 5 and AngularJS Part 5, Form Validation This is the fifth part in a multiple part blog series on building ASP.NET 5 (ASP.NET vNext) apps with AngularJS. In this series of blog posts, I show how you can create a simple Movie app using ASP.NET 5, MVC 6, and AngularJS.


I think there are probably half a dozen ways to do what you want. Probably the easiest is to use an Angular directive that recognizes jquery.validation markup.

Here is such a project: https://github.com/mdekrey/unobtrusive-angular-validation

And here is another: https://github.com/danicomas/angular-jquery-validate

I haven't tried either because personally, I solved this problem by writing code to make MVC output angular validation attributes instead of jquery.validation.unobtrusive attributes.

A 3rd option is to rely only on server side validation. Though this is obviously slower, it may be your only option sometimes for more complex validation scenarios. In this case, you just have to write javascript to parse the ModelStateDictionary object that Web API controllers usually return. There are some examples out there on how to do that and integrate it into AngularJS's native validation model.

Here is some incomplete code to parse the ModelStateDictionary:

````

angular.module('app')
    .directive('joshServerValidate', ['$http', function ($http) {
        return {
            require: 'ngModel',
            link: function (scope, ele, attrs, c) {
                console.info('wiring up ' + attrs.ngModel + ' to controller ' + c.$name);
                scope.$watch('modelState', function () {
                    if (scope.modelState == null) return;
                    var modelStateKey = attrs.joshServerValidate || attrs.ngModel;
                    modelStateKey = modelStateKey.replace(attrs.joshServerValidatePrefix, '');
                    modelStateKey = modelStateKey.replace('$index', scope.$index);
                    modelStateKey = modelStateKey.replace('model.', '');
                    console.info('validation for ' + modelStateKey);
                    if (scope.modelState[modelStateKey]) {
                        c.$setValidity('server', false);
                        c.$error.server = scope.modelState[modelStateKey];
                    } else {
                        c.$setValidity('server', true);
                    }
                });
            }
        };
    }]);

````

I'm rather disappointed with the other answers provided here. "Don't do it" isn't such a great suggestion when you're trying to validate something a little more difficult than an email address.

ASP.NET MVC Custom Validation using AngularJS Custom Directive, in an ASP.NET MVC app using Custom Directive in AngularJS. ASP.NET MVC Custom Validation using AngularJS Custom Directive. Posted by: form novalidate role = "form" name = "frmEmployee" >. < table class  Hmtl: <div class=”form-group”> <label for=”loaisanpham”>Loại Sản Phẩm</label> <select class=”form-control” name=”idLoaisp” id=&#…


I solved this in a slightly different way. I modified my MVC application to response to the application/json content type via a filter and a custom view engine which injects a Json serializer razor template into the view locations to search.

This was done to allow skinning of our website with jQuery UI, Bootstrap & Json responses for the same controllers/actions.

Here is a sample json result:

{
  "sid": "33b336e5-733a-435d-ad11-a79fdc1e25df",
  "form": {
    "id": 293021,
    "disableValidation": false,
    "phone": null,
    "zipCode": "60610",
    "firstName": null,
    "lastName": null,
    "address": null,
    "unit": null,
    "state": "IL",
    "email": null,
    "yearsAtAddress": null,
    "monthsAtAddress": null,
    "howHeard": null
  },
  "errors": [
    "The first name is required",
    "The last name is required",
    "Please enter a phone number",
    "Please enter an email address"
  ],
  "viewdata": {
    "cities": [
      {
        "selected": false,
        "text": "CHICAGO",
        "value": "CHICAGO"
      }
    ],
    "counties": [
      {
        "selected": false,
        "text": "COOK"
      }
    ]
  }
}

The filter is used to translate redirect results into a json object which passes the next url onto the calling program:

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        base.OnActionExecuted(filterContext);

        // if the request was application.json and the response is not json, return the current data session.
        if (filterContext.HttpContext.Request.ContentType.StartsWith("application/json") && 
            !(filterContext.Result is JsonResult || filterContext.Result is ContentResult))
        {
            if (!(filterContext.Controller is BaseController controller)) return;

            string url = filterContext.HttpContext.Request.RawUrl ?? "";
            if (filterContext.Result is RedirectResult redirectResult)
            {
                // It was a RedirectResult => we need to calculate the url
                url = UrlHelper.GenerateContentUrl(redirectResult.Url, filterContext.HttpContext);
            }
            else if (filterContext.Result is RedirectToRouteResult routeResult)
            {
                // It was a RedirectToRouteResult => we need to calculate
                // the target url
                url = UrlHelper.GenerateUrl(routeResult.RouteName, null, null, routeResult.RouteValues, RouteTable.Routes,
                    filterContext.RequestContext, false);
            }
            else
            {
                return;
            }
            var absolute = url;
            var currentUri = filterContext.HttpContext.Request.Url;
            if (url != null && currentUri != null && url.StartsWith("/"))
            {
                absolute = currentUri.Scheme + "://" + currentUri.Host + url;
            }

            var data = new {
                nextUrl =  absolute,
                uid = controller.UniqueSessionId(),
                errors = GetFlashMessage(filterContext.HttpContext.Session)
            };

            var settings = new JsonSerializerSettings
            {
                ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
                PreserveReferencesHandling = PreserveReferencesHandling.Objects,
                Formatting = Formatting.Indented,
                NullValueHandling = NullValueHandling.Ignore
            };
            filterContext.Result = new ContentResult
            {
                ContentType = "application/json",
                Content = JsonConvert.SerializeObject(data,settings)
            };
        }

Here is the Views\Json\Serializer.cshml, with using statements excluded for brevity and security of our codebase. This does three attempts at returning a response. The first is to read the original View{controller}{action}.cshtml, parsing out the html helpers and placing those into forms and fields. The second attempt looks for and elements from our built-in blogging system (PostContent below) and failing that we just use the Model.

@model dynamic
@{
    Response.ContentType = "application/json";

    Layout = "";
    var session = new Object(); // removed for security purposes

    var messages = ViewBag.Messages as List<string>() ?? new List<string>();
    var className = "";
    if (!ViewData.ModelState.IsValid)
    {
        messages.AddRange(ViewData.ModelState.Values.SelectMany(val => val.Errors).Select(error => error.ErrorMessage));
    }


    dynamic result;
    string serial;

    try
    {
        Type tModel = Model == null ? typeof(Object) : Model.GetType();
        dynamic form = new ExpandoObject();
        dynamic fields = new ExpandoObject();

        var controller = ViewContext.RouteData.Values["controller"] as string ?? "";
        var action = ViewContext.RouteData.Values["action"] as string;

        var viewPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Views", controller, action + ".cshtml");
        if (File.Exists(viewPath))
        {
            string contents = File.ReadAllText(viewPath);
            var extracted = false;
            var patterns = new[]
            {
                @"@Html\.\w+For\(\w+ => \w+\.(.*?)[,\)]",
                @"@Html\.(\w+)For\(\w+ => \w+\.([\w\.]+)[, ]*(\(SelectList\))*(ViewBag\.\w+)*[^\)]*",
                "name=\"(.*?)\""
            };

            for (var i = 0; i < 3 && !extracted; i++)
            {
                switch (i)
                {
                    case 0:
                        form = contents.ExtractFields(patterns[0], Model as object, out extracted);
                        fields = contents.ExtractElements(patterns[1], Model as object, out extracted, ViewData);
                        break;
                    case 1:
                        form = Model as mvcApp.Models.Blog == null ? null : (Model.PostContent as string).ExtractFields(patterns[2], Model as object, out extracted);
                        break;
                    default:
                        form = Model;
                        break;
                }
            }
        }
        else if (Model == null)
        {
            // nothing to do here - safeModel will serialize to an empty object
        }
        else if (Model is IEnumerable)
        {
            form = new List<object>();

            foreach (var element in ((IEnumerable) Model).AsQueryable()
                    .Cast<dynamic>())
            {
                form.Add(CustomExtensions.SafeClone(element));
            }

        } else {
            form = Activator.CreateInstance(tModel);
            CustomExtensions.CloneMatching(form, Model);
        }

        // remove any data models from the viewbag to prevent
        // recursive serialization
        foreach (var key in ViewData.Keys.ToArray())
        {
            var value = ViewData[key];
            if (value is IEnumerable)
            {
                var enumerator = (value as IEnumerable).GetEnumerator();
                value = enumerator.MoveNext() ? enumerator.Current : null;
            }
            if (value != null)
            {
                var vtype = value.GetType();
                if (vtype.Namespace != null && (vtype.Namespace == "System.Data.Entity.DynamicProxies" || vtype.Namespace.EndsWith("Models")))
                {
                    ViewData[key] = null;
                }
            }
        }

        result = new
        {
            uid = session.UniqueId,
            form,
            fields,
            errors = messages.Count == 0 ? null : messages,
            viewdata = ViewBag
        };
        var setting = new JsonSerializerSettings
        {
            PreserveReferencesHandling = PreserveReferencesHandling.None,
            ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
            ContractResolver = new CamelCasePropertyNamesContractResolver(),
            Formatting = Formatting.Indented
        };
        if (form is IEnumerable)
        {
            setting.NullValueHandling = NullValueHandling.Ignore;
        }
        serial = JsonConvert.SerializeObject(result, setting);
    }
    catch (Exception e)
    {
        result = new {
            uid = session.UniqueId,
            error = e.Message.Split('|')
        };
        serial = JsonConvert.SerializeObject(result);
    }
    @Html.Raw(serial)
}

For the clone methods see Best way to clone properties of disparate objects

    public static dynamic ExtractFields(this string html, string pattern, object model, out bool extracted)
    {
        if (html == null || model == null)
        {
            extracted = false;
            return null;
        }
        dynamic safeModel = new ExpandoObject();
        var safeDict = (IDictionary<string, Object>)safeModel;

        var matches = new Regex(pattern).Matches(html);
        extracted = matches.Count > 0;

        if ( extracted )
        {
            foreach (Match match in matches)
            {
                var name = match.Groups[1].Value;
                var value = CustomExtensions.ValueForKey(model, name);
                var segments = name.Split('.');
                var obj = safeDict;
                for (var i = 0; i < segments.Length; i++)
                {
                    name = segments[i];
                    if (i == segments.Length - 1)
                    {
                        if (obj.ContainsKey(name))
                        {
                            obj[name] = value;
                        }
                        else
                        {
                            obj.Add(name, value);
                        }
                        continue;
                    }
                    if (!obj.ContainsKey(name))
                    {
                        obj.Add(name, new ExpandoObject());
                    }
                    obj = (IDictionary<string, Object>)obj[name];
                }
            }
        }
        return safeModel;
    }

And here is an implementation of key value coding to make dealing with property chains a bit easier:

/// <summary>
/// This borrows KeyValueCoding from Objective-C and makes working with long chains of properties more convenient. 
/// KeyValueCoding is null tolerant, and will stop if any element in the chain returns null instead of throwing a NullReferenceException. 
/// Additionally, the following Linq methods are supported: First, Last, Sum &amp; Average.
/// <br/>
/// KeyValueCoding flattens nested enumerable types, but will only aggregate the last element: "children.grandchildren.first" will return 
/// the first grandchild for each child. If you want to return a single grandchild, use "first.children.grandchildren". The same applies to
/// Sum and Average.
/// </summary>
/// <param name="source">any object</param>
/// <param name="keyPath">the path to a descendant property or method "child.grandchild.greatgrandchild".</param>
/// <param name="throwErrors">optional - defaults to supressing errors</param>
/// <returns>returns the specified descendant. If intermediate properties are IEnumerable (Lists, Arrays, Collections), the result *should be* IEnumerable</returns>
public static object ValueForKey(this object source, string keyPath, bool throwErrors = false)
{
    try
    {
        while (true)
        {
            if (source == null || keyPath == null) return null;
            if (keyPath == "") return source;

            var segments = keyPath.Split('.');
            var type = source.GetType();
            var first = segments.First();
            var property = type.GetProperty(first);
            object value = null;
            if (property == null)
            {
                var method = type.GetMethod(first);
                if (method != null)
                {
                    value = method.Invoke(source, null);
                }
            }
            else
            {
                value = property.GetValue(source, null);
            }

            if (segments.Length == 1) return value;


            var children = string.Join(".", segments.Skip(1));
            if (value is IEnumerable || "First|Last|Sum|Average".IndexOf(first, StringComparison.OrdinalIgnoreCase) > -1)
            {
                var firstChild = children.Split('.').First();
                var grandchildren = string.Join(".", children.Split('.').Skip(1));
                if (value == null) {
                    var childValue = source.ValueForKey(children);
                    value = childValue as IEnumerable<object>;
                    switch (first.Proper())
                    {
                        case "First":
                            return value == null ? childValue : ((IEnumerable<object>)value).FirstOrDefault();
                        case "Last":
                            return value == null ? childValue : ((IEnumerable<object>)value).LastOrDefault();
                        case "Count":
                            return value == null ? (childValue == null ? 0 : 1) : (int?)((IEnumerable<object>)value).Count();
                        case "Sum":
                            return value == null
                                ? Convert.ToDecimal(childValue ?? "0")
                                : ((IEnumerable<object>) value).Sum(obj => Convert.ToDecimal(obj ?? "0"));
                        case "Average":
                            return value == null
                                ? Convert.ToDecimal(childValue ?? "0")
                                : ((IEnumerable<object>) value).Average(obj => Convert.ToDecimal(obj ?? "0"));
                    }
                } else {
                    switch (firstChild.Proper())
                    {
                        case "First":
                            return ((IEnumerable<object>)value).FirstOrDefault().ValueForKey(grandchildren);
                        case "Last":
                            return ((IEnumerable<object>)value).LastOrDefault().ValueForKey(grandchildren);
                        case "Count":
                            if (!string.IsNullOrWhiteSpace(grandchildren))
                            {
                                value = value.ValueForKey(grandchildren);
                                if (value != null && ! (value is IEnumerable<object>))
                                {
                                    return 1;
                                }
                            }
                            return value == null ? 0 : ((IEnumerable<object>)value).Count();
                        case "Sum":
                            return ((IEnumerable<object>)value).Sum(obj => Convert.ToDecimal(obj.ValueForKey(grandchildren)??"0"));
                        case "Average":
                            return ((IEnumerable<object>)value).Average(obj => Convert.ToDecimal(obj.ValueForKey(grandchildren) ?? "0"));
                    }
                }
                if (value == null) return null;
                var flat = new List<object>();
                foreach (var element in (IEnumerable<object>)value)
                {
                    var child = element.ValueForKey(children);
                    if (child == null)
                    {
                        continue;
                    }
                    if (child is IEnumerable && !(child is string))
                    {
                        flat.AddRange((IEnumerable<object>) child);
                    }
                    else
                    {
                        flat.Add(child);
                    }
                }
                return flat.Count == 0? null: flat;
            }
            source = value;
            keyPath = children;
        }
    }
    catch (Exception)
    {
        if (throwErrors) throw;
    }
    return null;
}

Handling Validation Errors with AngularJS and ASP.NET MVC, One of the first things you'll notice doesn't work very well when you integrate AngularJS and ASP.NET MVC is forms validation. We used to have it  In this article, we will create ASP.NET MVC using Angular.js and perform the CRUD create, read, update and delete) operations using Entity Framework.


AngularJS Validation In MVC - Part One, In this article you will learn about AngularJS Validation in MVC. <form ng-app="​myApp" ng-controller="validateCtrl"; name="myForm"  AngularJS is a front-end tool and it support all kinds of validations need to be done at the user-end (front-end). The Data annotations in your model would help to validate your data before both front-end and back-end if you don't use front-end validation such as AngularJS.


AngularJS Validation In MVC - Part Two, In this blog, we will learn about form validation in angularjs. from angular 1.3 onwards, ngmessages module has been introduced and we will be using the same  In this topic we will overview on AngularJS, after getting an overview we will create an ASP.NET MVC application with implementing AngularJS (v1.4.8). AngularJS the way I learned, I thought to share with the audience who are searching for learning AngularJS in a simplified way. This article is for them. Here's the flow of this article which we


Form Validation with AngularJS and ASP.NET MVC, NET MVC 3 around October 2010, they introduced a feature called "unobtrusive Now Angular features its own form validation attributes, e.g  We recently released Visual Studio 20102012 project template to support integrating of AngularJS with ASP.Net MVC 4. This post outlines what exactly happens underneath the hood. How can we make AngularJS and MVC play nicely with with each other.