J'utilise ASP.NET MVC et j'aimerais que tous les champs de chaîne saisis par l'utilisateur soient supprimés avant leur insertion dans la base de données. Et comme j'ai beaucoup de formulaires de saisie de données, je cherche un moyen élégant de couper toutes les chaînes au lieu de couper explicitement chaque valeur de chaîne fournie par l'utilisateur. Je suis intéressé de savoir comment et quand les gens coupent les chaînes.
Je pensais peut-être à la création d'un classeur de modèle personnalisé et au rognage des valeurs de chaîne ... De cette façon, toute ma logique de rognage est contenue à un seul endroit. Est-ce une bonne approche? Y at-il des exemples de code qui font cela?
public class TrimModelBinder : DefaultModelBinder
{
protected override void SetProperty(ControllerContext controllerContext,
ModelBindingContext bindingContext,
System.ComponentModel.PropertyDescriptor propertyDescriptor, object value)
{
if (propertyDescriptor.PropertyType == typeof(string))
{
var stringValue = (string)value;
if (!string.IsNullOrWhiteSpace(stringValue))
{
value = stringValue.Trim();
}
else
{
value = null;
}
}
base.SetProperty(controllerContext, bindingContext,
propertyDescriptor, value);
}
}
Que diriez-vous de ce code?
ModelBinders.Binders.DefaultBinder = new TrimModelBinder();
Définissez l'événement global.asax Application_Start.
@Takepara est la même résolution mais en tant qu’IModelBinder au lieu de DefaultModelBinder, de sorte que l’ajout du modelbinder dans global.asax se fait par
ModelBinders.Binders.Add(typeof(string),new TrimModelBinder());
La classe:
public class TrimModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueResult== null || valueResult.AttemptedValue==null)
return null;
else if (valueResult.AttemptedValue == string.Empty)
return string.Empty;
return valueResult.AttemptedValue.Trim();
}
}
basé sur @haacked post: http://haacked.com/archive/2011/03/19/fixing-binding-to-decimals.aspx
Une amélioration à la réponse @takepara.
Quelque chose en projet:
public class NoTrimAttribute : Attribute { }
Dans TrimModelBinder, changement de classe
if (propertyDescriptor.PropertyType == typeof(string))
à
if (propertyDescriptor.PropertyType == typeof(string) && !propertyDescriptor.Attributes.Cast<object>().Any(a => a.GetType() == typeof(NoTrimAttribute)))
et vous pouvez marquer les propriétés à exclure du découpage avec l'attribut [NoTrim].
Grâce aux améliorations apportées à C # 6, vous pouvez désormais écrire un classeur de modèle très compact qui coupera toutes les entrées de chaîne:
public class TrimStringModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
var attemptedValue = value?.AttemptedValue;
return string.IsNullOrWhiteSpace(attemptedValue) ? attemptedValue : attemptedValue.Trim();
}
}
Vous devez inclure cette ligne quelque part dans Application_Start()
dans votre fichier Global.asax.cs
pour utiliser le classeur de modèle lors de la liaison de string
s:
ModelBinders.Binders.Add(typeof(string), new TrimStringModelBinder());
Je trouve qu'il est préférable d'utiliser un classeur de ce type, plutôt que de remplacer le classeur par défaut, car il sera utilisé chaque fois que vous liez une string
, que ce soit directement en tant qu'argument de méthode ou en tant que propriété d'une classe de modèle. Toutefois, si vous remplacez le classeur de modèle par défaut, comme le suggèrent d’autres réponses, uniquement fonctionnera lors de la liaison de propriétés sur des modèles, not lorsque vous avez un string
comme argument d’une méthode d’action
Éditer: un intervenant a demandé comment gérer la situation lorsqu'un champ ne devrait pas être validé. Ma réponse initiale était réduite à la question posée par le PO, mais pour ceux qui sont intéressés, vous pouvez traiter la validation en utilisant le classeur de modèle étendu suivant:
public class TrimStringModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var shouldPerformRequestValidation = controllerContext.Controller.ValidateRequest && bindingContext.ModelMetadata.RequestValidationEnabled;
var unvalidatedValueProvider = bindingContext.ValueProvider as IUnvalidatedValueProvider;
var value = unvalidatedValueProvider == null ?
bindingContext.ValueProvider.GetValue(bindingContext.ModelName) :
unvalidatedValueProvider.GetValue(bindingContext.ModelName, !shouldPerformRequestValidation);
var attemptedValue = value?.AttemptedValue;
return string.IsNullOrWhiteSpace(attemptedValue) ? attemptedValue : attemptedValue.Trim();
}
}
Dans ASP.Net Core 2 cela a fonctionné pour moi. J'utilise l'attribut [FromBody]
dans mes contrôleurs et entrée JSON. Pour redéfinir le traitement des chaînes dans la désérialisation JSON, j'ai enregistré mon propre JsonConverter:
services.AddMvcCore()
.AddJsonOptions(options =>
{
options.SerializerSettings.Converters.Insert(0, new TrimmingStringConverter());
})
Et voici le convertisseur:
public class TrimmingStringConverter : JsonConverter
{
public override bool CanRead => true;
public override bool CanWrite => false;
public override bool CanConvert(Type objectType) => objectType == typeof(string);
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
if (reader.Value is string value)
{
return value.Trim();
}
return reader.Value;
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Une autre variante de la réponse de @ takepara mais avec une touche différente:
1) Je préfère le mécanisme d'attribut optionnel "StringTrim" (plutôt que l'exemple opt-out "NoTrim" de @Anton).
2) Un appel supplémentaire à SetModelValue est requis pour s’assurer que ModelState est correctement rempli et que le modèle de validation/accepter/rejet par défaut peut être utilisé normalement, c’est-à-dire TryUpdateModel (modèle) à appliquer et ModelState.Clear () à accepter toutes les modifications.
Mettez ceci dans votre entité/bibliothèque partagée:
/// <summary>
/// Denotes a data field that should be trimmed during binding, removing any spaces.
/// </summary>
/// <remarks>
/// <para>
/// Support for trimming is implmented in the model binder, as currently
/// Data Annotations provides no mechanism to coerce the value.
/// </para>
/// <para>
/// This attribute does not imply that empty strings should be converted to null.
/// When that is required you must additionally use the <see cref="System.ComponentModel.DataAnnotations.DisplayFormatAttribute.ConvertEmptyStringToNull"/>
/// option to control what happens to empty strings.
/// </para>
/// </remarks>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class StringTrimAttribute : Attribute
{
}
Ensuite ceci dans votre application/bibliothèque MVC:
/// <summary>
/// MVC model binder which trims string values decorated with the <see cref="StringTrimAttribute"/>.
/// </summary>
public class StringTrimModelBinder : IModelBinder
{
/// <summary>
/// Binds the model, applying trimming when required.
/// </summary>
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// Get binding value (return null when not present)
var propertyName = bindingContext.ModelName;
var originalValueResult = bindingContext.ValueProvider.GetValue(propertyName);
if (originalValueResult == null)
return null;
var boundValue = originalValueResult.AttemptedValue;
// Trim when required
if (!String.IsNullOrEmpty(boundValue))
{
// Check for trim attribute
if (bindingContext.ModelMetadata.ContainerType != null)
{
var property = bindingContext.ModelMetadata.ContainerType.GetProperties()
.FirstOrDefault(propertyInfo => propertyInfo.Name == bindingContext.ModelMetadata.PropertyName);
if (property != null && property.GetCustomAttributes(true)
.OfType<StringTrimAttribute>().Any())
{
// Trim when attribute set
boundValue = boundValue.Trim();
}
}
}
// Register updated "attempted" value with the model state
bindingContext.ModelState.SetModelValue(propertyName, new ValueProviderResult(
originalValueResult.RawValue, boundValue, originalValueResult.Culture));
// Return bound value
return boundValue;
}
}
Si vous ne définissez pas la valeur de la propriété dans le classeur, même si vous ne souhaitez rien modifier, vous bloquerez cette propriété de ModelState! Ceci est dû au fait que vous êtes enregistré en tant que liaison de tous les types de chaîne. Il est donc apparu (dans mes tests) que le classeur par défaut ne le ferait pas pour vous ensuite.
Informations supplémentaires pour les personnes cherchant comment procéder dans ASP.NET Core 1.0. La logique a beaucoup changé.
J'ai écrit un article de blog sur la façon de le faire , il explique les choses un peu plus en détail
Donc, solution ASP.NET Core 1.0:
Modèle de classeur pour faire la taille réelle
public class TrimmingModelBinder : ComplexTypeModelBinder
{
public TrimmingModelBinder(IDictionary propertyBinders) : base(propertyBinders)
{
}
protected override void SetProperty(ModelBindingContext bindingContext, string modelName, ModelMetadata propertyMetadata, ModelBindingResult result)
{
if(result.Model is string)
{
string resultStr = (result.Model as string).Trim();
result = ModelBindingResult.Success(resultStr);
}
base.SetProperty(bindingContext, modelName, propertyMetadata, result);
}
}
De plus, vous avez besoin du fournisseur de classeur de modèle dans la dernière version. Cela indique que si ce classeur est utilisé pour ce modèle
public class TrimmingModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType)
{
var propertyBinders = new Dictionary();
foreach (var property in context.Metadata.Properties)
{
propertyBinders.Add(property, context.CreateBinder(property));
}
return new TrimmingModelBinder(propertyBinders);
}
return null;
}
}
Ensuite, il doit être enregistré dans Startup.cs
services.AddMvc().AddMvcOptions(options => {
options.ModelBinderProviders.Insert(0, new TrimmingModelBinderProvider());
});
En lisant les excellentes réponses et commentaires ci-dessus et en devenant de plus en plus confus, je me suis soudainement dit, hé, je me demande s’il existe une solution jQuery. Ainsi, pour les autres qui, comme moi, trouvent ModelBinders un peu déconcertant, j'offre l'extrait de code jQuery suivant qui réduit les champs de saisie avant que le formulaire ne soit soumis.
$('form').submit(function () {
$(this).find('input:text').each(function () {
$(this).val($.trim($(this).val()));
})
});
En cas de MVC Core
Classeur:
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System;
using System.Threading.Tasks;
public class TrimmingModelBinder
: IModelBinder
{
private readonly IModelBinder FallbackBinder;
public TrimmingModelBinder(IModelBinder fallbackBinder)
{
FallbackBinder = fallbackBinder ?? throw new ArgumentNullException(nameof(fallbackBinder));
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult != null &&
valueProviderResult.FirstValue is string str &&
!string.IsNullOrEmpty(str))
{
bindingContext.Result = ModelBindingResult.Success(str.Trim());
return Task.CompletedTask;
}
return FallbackBinder.BindModelAsync(bindingContext);
}
}
Fournisseur:
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using System;
public class TrimmingModelBinderProvider
: IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (!context.Metadata.IsComplexType && context.Metadata.ModelType == typeof(string))
{
return new TrimmingModelBinder(new SimpleTypeModelBinder(context.Metadata.ModelType));
}
return null;
}
}
Fonction d'enregistrement:
public static void AddStringTrimmingProvider(this MvcOptions option)
{
var binderToFind = option.ModelBinderProviders
.FirstOrDefault(x => x.GetType() == typeof(SimpleTypeModelBinderProvider));
if (binderToFind == null)
{
return;
}
var index = option.ModelBinderProviders.IndexOf(binderToFind);
option.ModelBinderProviders.Insert(index, new TrimmingModelBinderProvider());
}
Registre:
service.AddMvc(option => option.AddStringTrimmingProvider())
Pour ASP.NET Core , remplacez ComplexTypeModelBinderProvider
par un fournisseur qui limite les chaînes.
Dans votre méthode de code de démarrage ConfigureServices
, ajoutez ceci:
services.AddMvc()
.AddMvcOptions(s => {
s.ModelBinderProviders[s.ModelBinderProviders.TakeWhile(p => !(p is ComplexTypeModelBinderProvider)).Count()] = new TrimmingModelBinderProvider();
})
Définissez TrimmingModelBinderProvider
comme ceci:
/// <summary>
/// Used in place of <see cref="ComplexTypeModelBinderProvider"/> to trim beginning and ending whitespace from user input.
/// </summary>
class TrimmingModelBinderProvider : IModelBinderProvider
{
class TrimmingModelBinder : ComplexTypeModelBinder
{
public TrimmingModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders) : base(propertyBinders) { }
protected override void SetProperty(ModelBindingContext bindingContext, string modelName, ModelMetadata propertyMetadata, ModelBindingResult result)
{
var value = result.Model as string;
if (value != null)
result = ModelBindingResult.Success(value.Trim());
base.SetProperty(bindingContext, modelName, propertyMetadata, result);
}
}
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType) {
var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();
for (var i = 0; i < context.Metadata.Properties.Count; i++) {
var property = context.Metadata.Properties[i];
propertyBinders.Add(property, context.CreateBinder(property));
}
return new TrimmingModelBinder(propertyBinders);
}
return null;
}
}
La partie laide de ceci est le copier-coller de la logique GetBinder
de ComplexTypeModelBinderProvider
, mais il ne semble pas y avoir de crochet pour vous permettre d'éviter cela.
En retard pour la partie, mais voici un résumé des ajustements nécessaires pour MVC 5.2.3 si vous devez gérer l'exigence skipValidation
des fournisseurs de valeur intégrés.
public class TrimStringModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// First check if request validation is required
var shouldPerformRequestValidation = controllerContext.Controller.ValidateRequest &&
bindingContext.ModelMetadata.RequestValidationEnabled;
// determine if the value provider is IUnvalidatedValueProvider, if it is, pass in the
// flag to perform request validation (e.g. [AllowHtml] is set on the property)
var unvalidatedProvider = bindingContext.ValueProvider as IUnvalidatedValueProvider;
var valueProviderResult = unvalidatedProvider?.GetValue(bindingContext.ModelName, !shouldPerformRequestValidation) ??
bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
return valueProviderResult?.AttemptedValue?.Trim();
}
}
Global.asax
protected void Application_Start()
{
...
ModelBinders.Binders.Add(typeof(string), new TrimStringModelBinder());
...
}
Je ne suis pas d'accord avec la solution . Vous devez remplacer GetPropertyValue car les données de SetProperty peuvent également être renseignées par le ModelState .
public class CustomModelBinder : System.Web.Mvc.DefaultModelBinder
{
protected override object GetPropertyValue(System.Web.Mvc.ControllerContext controllerContext, System.Web.Mvc.ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, System.Web.Mvc.IModelBinder propertyBinder)
{
object value = base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
string retval = value as string;
return string.IsNullOrWhiteSpace(retval)
? value
: retval.Trim();
}
}
Filtrez par propriétéDescriptor PropertyType si vous n'êtes vraiment intéressé que par les valeurs de chaîne, mais cela ne devrait pas avoir d'importance, car tout ce qui entre est fondamentalement une chaîne.
Il y a eu beaucoup de publications suggérant une approche attributaire. Voici un paquet qui a déjà un attribut trim et bien d’autres: Dado.ComponentModel.Mutations ou NuGet
public partial class ApplicationUser
{
[Trim, ToLower]
public virtual string UserName { get; set; }
}
// Then to preform mutation
var user = new ApplicationUser() {
UserName = " M@X_speed.01! "
}
new MutationContext<ApplicationUser>(user).Mutate();
Après l'appel à Mutate (), user.UserName sera muté en m@x_speed.01!
.
Cet exemple réduira les espaces et la casse en minuscule. Il n'introduit pas de validation, mais le System.ComponentModel.Annotations
peut être utilisé à côté de Dado.ComponentModel.Mutations
.