tutorial net mvc form español entre ejemplos diferencias asp asp.net-mvc validation razor angularjs data-annotations

español - Formulario de validación ASP.NET MVC con AngularJS



web forms c# ejemplos (6)

Estoy con un proyecto en MVC 4 y AngularJS (+ twitter bootstrap). Usualmente uso en mis proyectos de MVC "jQuery.Validate", "DataAnnotations" y "Razor". Luego habilito estas claves en mi web.config para validar las propiedades del modelo en el cliente:

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

Por ejemplo, si tengo en mi modelo esto:

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

Con este Cshtml:

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

El resultado html:

<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>

Pero ahora cuando uso AngularJS, quiero renderizar así:

<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>

No sé si hay ayuda o "Anotación de datos" para resolver esto. Entiendo que AngularJS tiene muchas más características como:

<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>

Bueno, específicamente. Necesito ayuda o "Anotación de datos" para resolver los atributos (Anotación de datos) para mostrar en el cliente con AngularJS.

Si todavía no existe, tal vez sea el momento de hacerlo, como RazorForAngularJS

Editar

Creo que quizás la mejor manera de trabajar con ASP.NET MVC y AngularJS es hacerlo ( front-end ) a mano (escribir todo el HTML a mano)


Como alguien que es autor de un sitio web ASP.Net/Angular, puedo decirte que vas a estar mucho mejor si evitas usar Razor para renderizar tu HTML donde puedas.

En mis proyectos, he configurado una vista de afeitar para representar mi página principal (estoy usando una aplicación de una sola página escrita en Angular), luego tengo una carpeta de archivos .html que uso como plantillas para Angular.

El resto se realiza en las llamadas a la API web ASP.Net en mi caso, pero también puede usar la acción MVC con los resultados JSON.

Tan pronto como cambié a esta arquitectura, las cosas fueron mucho más suaves para mí, en cuanto al desarrollo.


Creo que esta es una pregunta que pondrán los principiantes angulares (así es como lo encontré :)), y es por eso que creo que merece una respuesta que tal vez explique la edición de los autores y espero que ayude a los que se hacen la misma pregunta y me gusta de reformularlo: ¿cómo mantengo la coherencia entre la validación angular y la validación del modelo de mvc ?.

El campo modelo a validar se lleva de la vista angular -> a un controlador angular -> a un servicio angular -> al método asp.webapi o a la acción del controlador asp.mvc, que en los mapas finales -> a un modelo mvc

Esto significa que en todos esos (al menos 4 "proyectores") debe asegurarse de transferir el modelo exacto y el campo al que se referirá con la maquinilla de afeitar.

Lo que quiero decir es que hay muchas cosas que pueden hacer volar tu coherencia en el camino.

Así que estoy de acuerdo con: reescribirlos manualmente para el lado del cliente y usar pruebas automatizadas para garantizar la coherencia


Creo que hay probablemente media docena de maneras de hacer lo que quieras. Probablemente, lo más fácil sea utilizar una directiva angular que reconozca el marcado jquery.validation.

Aquí hay un proyecto de este tipo: https://github.com/mdekrey/unobtrusive-angular-validation

Y aquí hay otro: https://github.com/danicomas/angular-jquery-validate

No lo he intentado tampoco porque, personalmente, resolví este problema escribiendo código para hacer atributos de validación angular de salida MVC en lugar de atributos jquery.validation.unobtrusive.

Una tercera opción es confiar solo en la validación del lado del servidor. Aunque esto es obviamente más lento, puede ser su única opción a veces para escenarios de validación más complejos. En este caso, solo tiene que escribir javascript para analizar el objeto ModelStateDictionary que generalmente devuelven los controladores de API web. Hay algunos ejemplos sobre cómo hacer eso e integrarlo en el modelo de validación nativo de AngularJS.

Aquí hay un código incompleto para analizar el 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); } }); } }; }]);

`` ``

Estoy bastante decepcionado con las otras respuestas proporcionadas aquí. "No hacerlo" no es una gran sugerencia cuando intentas validar algo un poco más difícil que una dirección de correo electrónico.


Escribí una directiva para suavizar la transición de MVC a AngularJs. El marcado se ve así:

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

Que se comporta de manera idéntica a las convenciones de Razor, incluido el retraso de la validación hasta después de que se modifique un campo. Con el tiempo, he encontrado que mantener mi marcado es bastante intuitivo y simple.

Mi artículo sobre el tema

Plinkr


Estoy de acuerdo con la idea de Blesh de alejarse de la afeitadora, pero puedes crear algunas herramientas para crear páginas más rápido. En mi humilde opinión, es mejor utilizar las características de la maquinilla de afeitar en el lugar que necesitaban en lugar de eliminarlas del conjunto de herramientas.

Por cierto, eche un vistazo a ngval . Trae anotaciones de datos al lado del cliente como validadores angularjs. Tiene un helper html y un módulo angular. Debo mencionar que el proyecto se encuentra en las primeras etapas de desarrollo.


Lo resolví de una manera ligeramente diferente. Modifiqué mi aplicación MVC para responder al tipo de contenido application / json a través de un filtro y un motor de vista personalizado que inyecta una plantilla de rasuradora serializadora Json en las ubicaciones de visualización para buscar.

Esto se hizo para permitir el desdoblamiento de nuestro sitio web con respuestas de jQuery UI, Bootstrap y Json para los mismos controladores / acciones.

Aquí hay un ejemplo de resultado json:

{ "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" } ] } }

El filtro se utiliza para traducir los resultados de redirección en un objeto json que pasa la siguiente url al programa de llamada:

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) }; }

Aquí está el Views / Json / Serializer.cshml, con el uso de declaraciones excluidas por brevedad y seguridad de nuestra base de código. Esto hace tres intentos para devolver una respuesta. El primero es leer el View original {controller} {action} .cshtml, analizar los helpers html y colocarlos en formularios y campos. El segundo intento busca y elementos de nuestro sistema de blogging incorporado (PostContent a continuación) y en su defecto solo usamos el Modelo.

@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) }

Para los métodos de clonación, consulte Mejor forma de clonar propiedades de objetos dispares

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; }

Y aquí hay una implementación de codificación de valores clave para hacer que el manejo de las cadenas de propiedades sea un poco más fácil:

/// <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; }