c# - español - ¿Cuál es la mejor manera de manejar la validación con diferentes culturas?
ui culture (2)
Estoy tratando de construir una aplicación MVC multilingüe. Tengo un formulario en mi solicitud y campo para ingresar un costo. Puedo crear un registro usando la cultura española.
Pero al tratar de actualizar el registro, obtengo la validación de jquery como falsa. y recibo un mensaje de error predeterminado como:
El campo debe ser numérico.
En mi opinión, el modelo I ha establecido los siguientes atributos.
[LocalizedDisplayName("Label_Cost")]
[RegularExpression("^[^<>,<|>]+$", ErrorMessage = null, ErrorMessageResourceName = "Error_Message_Html_Tags_Prevented", ErrorMessageResourceType = typeof(Resources))]
[Range(0, 9999.99, ErrorMessage = null, ErrorMessageResourceName = "Error_Message_Cost_Not_Valid", ErrorMessageResourceType = typeof(Resources))]
public decimal? Cost { get; set; }
He establecido en mi archivo Gobal.asax con el siguiente
protected void Application_AcquireRequestState(object sender, EventArgs e)
{
try
{
HttpCookie cookie = HttpContext.Current.Request.Cookies.Get("CurrentCulture");
string culutureCode = cookie != null && !string.IsNullOrEmpty(cookie.Value) ? cookie.Value : "en";
CultureInfo ci = new CultureInfo(culutureCode);
System.Threading.Thread.CurrentThread.CurrentUICulture = ci;
System.Threading.Thread.CurrentThread.CurrentCulture =
CultureInfo.CreateSpecificCulture(ci.Name);
}
catch(Exception ex)
{
// Code
}
}
y el método anterior funciona como se esperaba en el servidor al cambiar la cultura. Pero la validación del lado del cliente se rompe en las culturas no inglesas, ya que javascript reconoce solo literales decimales. Me gustaría saber la mejor manera de extender la validación del lado del cliente mvc con la validación específica de la cultura.
EDITAR
Con referencia a la url de Mike, he realizado los siguientes cambios en el paquete Js. El paquete Js es el siguiente
public static void RegisterBundles(BundleCollection bundles)
{
BundleTable.EnableOptimizations = true;
bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
"~/Scripts/jquery-{version}.js"));
bundles.Add(new ScriptBundle("~/bundles/globalisation").Include(
"~/Scripts/globalize.js",
"~/Scripts/globalize/currency.js",
"~/Scripts/globalize/date.js",
"~/Scripts/globalize/message.js",
"~/Scripts/globalize/number.js",
"~/Scripts/globalize/plural.js",
"~/Scripts/globalize/relative-time.js"));
bundles.Add(new ScriptBundle("~/bundles/globalisationEN").Include(
"~/Scripts/GlobalisationCulture/globalize.culture.en-AU.js"));
bundles.Add(new ScriptBundle("~/bundles/globalisationES").Include(
"~/Scripts/GlobalisationCulture/globalize.culture.es-AR.js"));
bundles.Add(new ScriptBundle("~/bundles/jqueryuiEN").Include(
"~/Scripts/jquery-ui-1.10.3.js"));
bundles.Add(new ScriptBundle("~/bundles/jqueryuiES").Include(
"~/Scripts/jquery-ui-1.10.3.js"));
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery.validate.js",
"~/Scripts/jquery.validate.unobtrusive.js",
"~/Scripts/jquery.unobtrusive-ajax.js",
"~/Scripts/jquery.validate.globalize.js"));
}
En la página de diseño, he implementado de la siguiente manera
HttpCookie cookie = HttpContext.Current.Request.Cookies.Get("CurrentCulture");
string culutureCode = cookie != null && !string.IsNullOrEmpty(cookie.Value) ? cookie.Value : "en";
if (culutureCode.Equals("en-AU", StringComparison.OrdinalIgnoreCase))
{
culutureCode = "EN";
}
else if (culutureCode.Equals("es-AR", StringComparison.OrdinalIgnoreCase))
{
culutureCode = "ES";
}
else
{
culutureCode = "EN";
}
@Scripts.Render("~/bundles/jquery",
"~/bundles/globalisation",
string.Format("~/bundles/globalisation{0}", culutureCode),
"~/bundles/jqueryval",
string.Format("~/bundles/jqueryui{0}", culutureCode))
Ha agregado paquetes en RegisterBundles pero no los utilizó en la página de diseño. También ha agregado un archivo jqueryui redundante en RegisterBundles. Actualice su método RegisterBundles de esta manera:
public static void RegisterBundles(BundleCollection bundles)
{
BundleTable.EnableOptimizations = true;
bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
"~/Scripts/jquery-{version}.js"));
bundles.Add(new ScriptBundle("~/bundles/globalisation").Include(
"~/Scripts/globalize.js",
"~/Scripts/globalize/currency.js",
"~/Scripts/globalize/date.js",
"~/Scripts/globalize/message.js",
"~/Scripts/globalize/number.js",
"~/Scripts/globalize/plural.js",
"~/Scripts/globalize/relative-time.js"));
bundles.Add(new ScriptBundle("~/bundles/globalisationEN").Include(
"~/Scripts/GlobalisationCulture/globalize.culture.en-AU.js"));
bundles.Add(new ScriptBundle("~/bundles/globalisationES").Include(
"~/Scripts/GlobalisationCulture/globalize.culture.es-AR.js"));
bundles.Add(new ScriptBundle("~/bundles/jqueryui").Include(
"~/Scripts/jquery-ui-1.10.3.js"));
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery.validate.js",
"~/Scripts/jquery.validate.unobtrusive.js",
"~/Scripts/jquery.unobtrusive-ajax.js",
"~/Scripts/jquery.validate.globalize.js"));
}
y luego actualiza la página de diseño de esta manera:
@section Scripts
{
@Scripts.Render("~/bundles/jquery",
"~/bundles/globalisation",
"~/bundles/globalisationEN",
"~/bundles/globalisationES",
"~/bundles/jqueryval",
"~/bundles/jqueryui"))
<script type="text/javascript">
$.validator.methods.number = function (value, element) {
return this.optional(element) ||
!isNaN(Globalize.parseFloat(value));
}
$(document).ready(function () {
Globalize.culture(''es-AR''); //set spanish culture
});
</script>
}
Espero que esto ayude :)
Hay 2 plugins jQuery Globalize.
La versión anterior es v0.0.1 contiene una secuencia de comandos globalize.js
y tiene una subcarpeta de cultures
en la que puede encontrar todas las culturas de script, tales como:
- globalize.culture.en-AU.js
- globalize.culture.es-AR.js
Esos scripts le permiten agregar tantas culturas como desee, por lo que sería perfectamente correcto crear su paquete de esta manera:
bundles.Add(new ScriptBundle("~/bundles/globalisation").Include(
"~/Scripts/globalize.js",
"~/Scripts/cultures/globalize.culture.en-AU.js",
"~/Scripts/cultures/globalize.culture.es-AR.js"
));
Globalize
tendrá una colección de scripts de localización que puede configurar simplemente usando:
Globalize.culture(''en-AU'');
o
Globalize.culture(''es-AR'');
Puede usar algún tipo de proximidad para descubrir cuál es la cultura más cercana que desea usar. Si ha cargado en su paquete globalize.culture.es-AR.js
, puede configurar Globalize.culture(''es'');
y Globalize
podría darse cuenta de que desea usar la cultura ''es-AR''; por supuesto, si ha agregado globalize.culture.es.js
el cargador elegiría este último.
La nueva versión de jQuery Globalize (estable) es v1.0.0 y funciona de una manera completamente diferente.
Todavía tiene el archivo de script principal llamado globalize.js
pero debe agregar muchos más scripts para que funcione.
Alguien ha creado una tool que le dice exactamente qué secuencia de comandos necesita, dependiendo del tipo de módulo (número, fechas, monedas) que quiera usar.
Si opta por usar v1.0.0, verá que la herramienta sugerirá incluir los scripts básicos (solo números):
- cldr.js
- cldr / event.js
- cldr / supplemental.js
- globalize.js
- globalize / number.js
más algunos scripts CLDR JSON:
- cldr / supplemental / likelySubtags.json
- cldr / main / {locale} /numbers.json
- cldr / supplemental / numberingSystems.json
Puede encontrar estos archivos en el paquete core y en el paquete de numbers .
Si quiere validar las fechas, este es el package . Más información here .
Estos son todos archivos json y no puedes agruparlos. Puede cargarlos en tiempo de ejecución haciendo algo como esto:
Application.loadCulture = function (culture) {
$.when(
$.get(Application.CldrFetch + ''/'' + culture + ''/'' + encodeURIComponent("likelySubtags.json")),
$.get(Application.CldrFetch + ''/'' + culture + ''/'' + "numberingSystems.json"),
$.get(Application.CldrFetch + ''/'' + culture + ''/'' + "plurals.json"),
$.get(Application.CldrFetch + ''/'' + culture + ''/'' + "ordinals.json"),
$.get(Application.CldrFetch + ''/'' + culture + ''/'' + "currencyData.json"),
$.get(Application.CldrFetch + ''/'' + culture + ''/'' + "timeData.json"),
$.get(Application.CldrFetch + ''/'' + culture + ''/'' + "weekData.json"),
$.get(Application.CldrFetch + ''/'' + culture + ''/'' + "ca-gregorian.json"),
$.get(Application.CldrFetch + ''/'' + culture + ''/'' + "timeZoneNames.json"),
$.get(Application.CldrFetch + ''/'' + culture + ''/'' + "numbers.json"),
$.get(Application.CldrFetch + ''/'' + culture + ''/'' + "currencies.json")
)
.then(function () {
// Normalize $.get results, we only need the JSON, not the request statuses.
return [].slice.apply(arguments, [0]).map(function (result) {
return result[0];
});
}).then(Globalize.load).then(function () {
Globalize.locale(culture);
});
};
De todas formas; digamos que quieres mantenerte en el antiguo v0.0.1, que sigue siendo el mejor.
Su paquete tendrá el script globalize y el cultural:
bundles.Add(new ScriptBundle("~/bundles/globalisation").Include(
"~/Scripts/globalize.js",
"~/Scripts/cultures/globalize.culture.en-AU.js",
"~/Scripts/cultures/globalize.culture.es-AR.js"
));
La validación de jQuery ofrece alguna otra extensión adicional que quizás desee considerar:
- additional-methods.js
- localization / messages_es_AR.js (mensajes de error para la cultura)
He visto que está configurando su cultura en Application_AcquireRequestState
. Alguien sugiere que es mejor hacerlo en Application_BeginRequest
ya que se procesa anteriormente en la tubería:
protected void Application_BeginRequest(object sender, EventArgs e)
{
HttpCookie cookie = HttpContext.Current.Request.Cookies.Get("CurrentCulture");
string cultureCode = cookie != null && !string.IsNullOrEmpty(cookie.Value) ? cookie.Value : "en";
CultureInfo ci = new CultureInfo(cultureCode);
System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(cultureCode);
System.Threading.Thread.CurrentThread.CurrentUICulture = System.Threading.Thread.CurrentThread.CurrentCulture;
}
Parece que estás usando este plugin jQuery para la validación. Lo que normalmente haría es, tan pronto como cargue el script, configure el cultivo y establezca la validación personalizada:
Globalize.culture(this.culture);
$.validator.methods.number = function (value, element) {
return this.optional(element) || jQuery.isNumeric(Globalize.parseFloat(value));
};
$.validator.methods.date = function (value, element) {
return (this.optional(element) || Globalize.parseDate(value));
};
jQuery.extend(jQuery.validator.methods, {
range: function (value, element, param) {
var val = Globalize.parseFloat(value);
return this.optional(element) || (val >= param[0] && val <= param[1]);
}
});
Una cosa que te falta es una carpeta modelo para decimales:
using System;
using System.Web.Mvc;
using System.Globalization;
public class DecimalModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
ModelState modelState = new ModelState { Value = valueResult };
object actualValue = null;
try
{
//Check if this is a nullable decimal and a null or empty string has been passed
var isNullableAndNull = (bindingContext.ModelMetadata.IsNullableValueType && string.IsNullOrEmpty(valueResult.AttemptedValue));
//If not nullable and null then we should try and parse the decimal
if (!isNullableAndNull)
{
actualValue = decimal.Parse(valueResult.AttemptedValue, NumberStyles.Any, CultureInfo.CurrentCulture);
}
}
catch (FormatException e)
{
modelState.Errors.Add(e);
}
bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
return actualValue;
}
}
que se puede configurar en su Global.asax Application_Start
:
ModelBinders.Binders.Add(typeof(decimal), new DecimalModelBinder());
ModelBinders.Binders.Add(typeof(decimal?), new DecimalModelBinder());
Esto es prácticamente todo lo que necesitas.
Solo hay un problema molesto con este enfoque.
Digamos que está utilizando la cultura en-AU
y usted ingresa en su campo numérico un valor: 10,4. Este número es perfectamente válido en es-AR
pero no debería ser válido para la cultura en-AU
.
jQuery Globalize lo considerará válido de todos modos, ya que lo traduciría a 104 aquí:
$.validator.methods.number = function (value, element) {
return this.optional(element) || jQuery.isNumeric(Globalize.parseFloat(value));
};
Globalize.parseFloat(''10,4'')
para la cultura en-AU transformaría ese número en 104.
Lo mismo sucedería si haces lo mismo con Globalize.parseFloat(''10.4'')
para la cultura es-AR; se convertiría, de nuevo, 104.
Puede verificar este comportamiento ejecutando este fiddle .
Ambos ,
y .
son símbolos válidos ya que se usarían como separador decimal y separador de miles.
Hay algunos problemas abiertos sobre este tema en github y supongo que sería difícil solucionarlos, ya que ahora están trabajando en la nueva versión, donde el mismo problema persiste, por cierto.
Vas a enfrentar el mismo problema en el lado del servidor con nuestra carpeta de modelo decimal :
decimal.Parse(''10,4'', NumberStyles.Any, CultureInfo.CurrentCulture);
donde CultureInfo.CurrentCulture es ''en-AU'', de nuevo, produciría el mismo resultado: 104 .
Puede colocar un punto de quiebre allí y ver cómo convierte el valor.
Supongo que probablemente sea más fácil solucionarlo, tal vez usando algunas expresiones regulares.
Si quieres jugar con la solución con jQuery Validator v.0.1.1 o jQuery Validator v.1.0.0, he creado dos repositorios here y here .