Il modo migliore per tagliare le stringhe dopo l'inserimento dei dati. Devo creare un raccoglitore di modelli personalizzato?


172

Sto usando ASP.NET MVC e vorrei che tutti i campi stringa immessi dall'utente fossero tagliati prima che fossero inseriti nel database. E poiché ho molti moduli di immissione dei dati, sto cercando un modo elegante per tagliare tutte le stringhe invece di tagliare esplicitamente ogni valore di stringa fornito dall'utente. Sono interessato a sapere come e quando le persone tagliano le corde.

Ho pensato forse di creare un raccoglitore di modello personalizzato e di tagliare qualsiasi valore di stringa lì ... in questo modo, tutta la mia logica di taglio è contenuta in un posto. è un buon approccio? Ci sono esempi di codice che lo fanno?

Risposte:


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

Che ne dici di questo codice?

ModelBinders.Binders.DefaultBinder = new TrimModelBinder();

Imposta l'evento Application_Start global.asax.


3
sostituirei il codice più interno {} con questo per brevità: string stringValue = (string) value; value = string.IsNullOrEmpty (stringValue)? stringValue: stringValue.Trim ();
Simon_Weaver,

4
Questo merita più voti. In realtà sono sorpreso che il team MVC non abbia scelto di implementarlo nel raccoglitore modello predefinito ...
Portman,

1
@BreckFresen Ho avuto lo stesso problema, dovrai sovrascrivere il metodo BindModel e controllare il bindingContext.ModelType per una stringa, quindi tagliare se lo è.
Kelly,

3
Per chiunque come me ottenga un'ambiguità su DefaultModelBinder, quello corretto sta usando System.Web.Mvc.
GeoffM,

3
Come modificheresti questo per lasciare type="password"intatti gli input?
Extragorey,

77

Questa è la stessa risoluzione di @takepara ma come IModelBinder anziché DefaultModelBinder in modo che l'aggiunta del modelbinder in global.asax avvenga

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

basato sul post @haacked: http://haacked.com/archive/2011/03/19/fixing-binding-to-decimals.aspx


1
+1 per una soluzione pulita! Puoi migliorare ulteriormente la leggibilità del tuo codice modificando l'ordine delle returndichiarazioni e annullando la condizione:if (valueResult == null || string.IsNullOrEmpty(valueResult.AttemptedValue)) return null;
Marius Schulz,

6
Questo non gestisce l'attributo del controller [ValidateInput (false)]. Causa l'eccezione "Richiesta pericolosa ...".
CodeGrue,

2
Per coloro che ottengono l'eccezione "Richiesta pericolosa ...", fare riferimento a questo articolo - blogs.taiga.nl/martijn/2011/09/29/…
GurjeetSinghDB

2
Un mio collega ha implementato una variazione di questo che ha causato ogni sorta di problemi: issues.umbraco.org/issue/U4-6665 Consiglierei di restituire nulli e vuoti nel modo appropriato piuttosto che preferire sempre uno rispetto all'altro (nel tuo caso, tu restituisce sempre null anche quando il valore è una stringa vuota).
Nicholas Westby,

2
Questo sembra spezzare l' [AllowHtml]attributo sulle proprietà del modello (insieme a [ValidateInput(false)]come CodeGrue sopra menzionato
Mingwei Samuel

43

Un miglioramento alla risposta di @takepara.

Qualcuno era nel progetto:

public class NoTrimAttribute : Attribute { }

In TrimModelBinder cambio di classe

if (propertyDescriptor.PropertyType == typeof(string))

per

if (propertyDescriptor.PropertyType == typeof(string) && !propertyDescriptor.Attributes.Cast<object>().Any(a => a.GetType() == typeof(NoTrimAttribute)))

e puoi contrassegnare le proprietà da escludere dal taglio con l'attributo [NoTrim].


1
Come possiamo implementare qualcosa come questo attributo quando utilizziamo l'approccio IModelBinder di @Korayem? In alcune applicazioni, utilizzo un raccoglitore di modelli (di terze parti) diverso (ad esempio, S # arp Archeticture). Vorrei scriverlo in una DLL privata condivisa tra progetti, quindi deve essere un approccio IModelBinder.
Carl Bussema,

1
@CarlBussema Ecco una domanda sull'accesso agli attributi da un IModelBinder. stackoverflow.com/questions/6205176
Attacco Mac

4
Penso che sia una grande aggiunta, ma vorrei sostituire il .Cast<object>().Any(a => a.GetType() == typeof(NoTrimAttribute))con .OfType<NoTrimAttribute>().Any(). Solo un po 'più pulito.
DBueno,

Ho inserito i miei attributi in un assembly condiviso perché, come per le annotazioni dei dati, tali attributi hanno un ambito di utilizzo più ampio di un semplice MVC, ad esempio livello aziendale, client. Un'altra osservazione, "DisplayFormatAttribute (ConvertEmptyStringToNull)" controlla se la stringa tagliata verrà salvata come null o come stringa vuota. Il valore predefinito è true (null) che mi piace, ma nel caso in cui siano necessarie stringhe vuote nel database (si spera di no), è possibile impostarlo su false per ottenerlo. Ad ogni modo, sono tutte cose buone, spero che MS estenda i loro attributi includendo rifilatura e imbottitura e molte altre cose comuni come quella.
Tony Wall,

17

Con i miglioramenti in C # 6, ora puoi scrivere un raccoglitore modello molto compatto che taglierà tutti gli input di stringa:

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

È necessario includere questa linea da qualche parte nel Application_Start()nel Global.asax.csfile per utilizzare il modello legante quando si associa strings:

ModelBinders.Binders.Add(typeof(string), new TrimStringModelBinder());

Trovo che sia meglio usare un raccoglitore di modelli come questo, piuttosto che sovrascrivere il raccoglitore di modelli predefinito, perché poi verrà usato ogni volta che si associa un a string, che sia direttamente come argomento del metodo o come proprietà di una classe del modello. Tuttavia, se si sovrascrive il raccoglitore modello predefinito come suggeriscono altre risposte, funzionerà solo quando si associano proprietà sui modelli, non quando si ha stringun argomento come a un metodo di azione

Modifica: un commentatore ha chiesto di gestire la situazione quando un campo non deve essere convalidato. La mia risposta originale è stata ridotta per affrontare solo la domanda posta dall'OP, ma per coloro che sono interessati, è possibile gestire la convalida utilizzando il seguente raccoglitore modello esteso:

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

Ancora una volta, vedere i commenti sopra. Questo esempio non gestisce il requisito skipValidation di IUnvalidatedValueProvider.
Aaron Hudon,

@adrian, L'interfaccia di IModelBinder ha solo il metodo BindModel con tipo di ritorno bool. Quindi come hai usato l'oggetto tipo di ritorno qui?
Magendran V,

@MagendranV Non sono sicuro di quale interfaccia stai guardando, ma questa risposta si basa su IModelBinder in ASP.NET MVC 5, che restituisce un oggetto: docs.microsoft.com/en-us/previous-versions/aspnet / ...
Adrian,

1
@AaronHudon Ho aggiornato la mia risposta per includere un esempio per gestire la convalida del salto
adrian,

Se i campi della password hanno il set di tipi di dati corretto (es. [DataType (DataType.Password)]), puoi aggiornare l'ultima riga come segue in modo da non tagliare questi campi: return string.IsNullOrWhiteSpace (tryedValue) || bindingContext.ModelMetadata.DataTypeName == "Password"? tryingedValue: tentedValue.Trim ();
Trfletch,

15

In ASP.Net Core 2 ha funzionato per me. Sto usando l' [FromBody]attributo nei miei controller e l'input JSON. Per sovrascrivere la gestione delle stringhe nella deserializzazione JSON ho registrato il mio JsonConverter:

services.AddMvcCore()
    .AddJsonOptions(options =>
        {
            options.SerializerSettings.Converters.Insert(0, new TrimmingStringConverter());
        })

E questo è il convertitore:

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

La tua soluzione funziona benissimo! Grazie. Ho provato le altre soluzioni per .Net Core usando IModelBinderProvider, non ha funzionato.
Cedric Arnould,

Tranne che in startup.cs, può essere utilizzato anche in Model come [JsonConverter (typeof (TrimmingStringConverter))]. Btw. c'è un motivo dietro l'utilizzo di .Insert () invece .Add ()?
eri

@wast Immagino di averlo appena fatto .Insert () invece di .Add () per assicurarmi che venga eseguito prima di altri convertitori. Non ricordo ora.
Kai G,

Qual è il sovraccarico prestazionale di questo su DefaultContractResolver?
Maulik Modi,

13

Un'altra variante della risposta di @ takepara ma con una diversa svolta:

1) Preferisco il meccanismo di attributo opt-in "StringTrim" (piuttosto che l'esempio "NoTrim" di opt-out di @Anton).

2) È necessaria una chiamata aggiuntiva a SetModelValue per assicurarsi che ModelState sia popolato correttamente e che il modello di convalida / accettazione / rifiuto predefinito possa essere utilizzato normalmente, ad esempio TryUpdateModel (modello) da applicare e ModelState.Clear () per accettare tutte le modifiche.

Inserisci questo nella tua entità / libreria condivisa:

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

Quindi questo nella tua applicazione / libreria 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;
    }
}

Se non si imposta il valore della proprietà nel raccoglitore, anche quando non si desidera modificare nulla, si bloccherà completamente quella proprietà da ModelState! Questo perché sei registrato come vincolante per tutti i tipi di stringa, quindi sembra (nei miei test) che il raccoglitore predefinito non lo farà per te.


7

Informazioni extra per chiunque cerchi come farlo in ASP.NET Core 1.0. La logica è cambiata parecchio.

Ho scritto un post sul blog su come farlo , spiega le cose in modo più dettagliato

Quindi soluzione ASP.NET Core 1.0:

Raccoglitore modello per eseguire il taglio effettivo

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

Inoltre, nell'ultima versione è necessario il provider del raccoglitore modelli, che indica che questo raccoglitore deve essere utilizzato per questo modello

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

Quindi deve essere registrato in Startup.cs

 services.AddMvc().AddMvcOptions(options => {  
       options.ModelBinderProviders.Insert(0, new TrimmingModelBinderProvider());
 });

Non ha funzionato anche per me, tutti i miei campi sono nulli ora
Cedric Arnould

5

Mentre leggevo le risposte e i commenti eccellenti sopra e diventando sempre più confuso, improvvisamente ho pensato, ehi, mi chiedo se ci sia una soluzione jQuery. Quindi, per gli altri che, come me, trovano ModelBinders un po 'sconcertante, offro il seguente frammento di jQuery che taglia i campi di input prima che il modulo venga inviato.

    $('form').submit(function () {
        $(this).find('input:text').each(function () {
            $(this).val($.trim($(this).val()));
        })
    });

1
2 cose: 1 - Cache gli oggetti client (come $ (questo)), 2 - Non puoi mai fare affidamento sugli input del client, ma puoi sicuramente fare affidamento sul codice del server. Quindi la tua risposta è un completamento delle risposte del codice del server :)
graumanoz

5

In caso di MVC Core

Raccoglitore:

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

Provider:

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

Funzione di registrazione:

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

Registrati:

service.AddMvc(option => option.AddStringTrimmingProvider())

+1. Esattamente quello che stavo cercando. Qual è lo scopo del codice "binderToFind" nella funzione di registrazione?
Brad,

Sto solo cercando di mettere il provider personalizzato con il fallback di SimpleTypeModelBinderProvidermantenendo lo stesso indice.
Vikash Kumar,

Tutta la descrizione può essere trovata qui vikutech.blogspot.in/2018/02/…
Vikash Kumar

3

In ritardo per la parte, ma di seguito è riportato un riepilogo degli aggiustamenti richiesti per MVC 5.2.3 se si deve gestire il skipValidationrequisito dei fornitori di valore integrati.

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

2

Non sono d'accordo con la soluzione. Dovresti sovrascrivere GetPropertyValue perché i dati per SetProperty potrebbero anche essere riempiti da ModelState. Per catturare i dati grezzi dagli elementi di input scrivi questo:

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

}

Filtra per propertyDescriptor PropertyType se sei veramente interessato solo ai valori delle stringhe ma non dovrebbe importare perché tutto ciò che arriva è sostanzialmente una stringa.


2

Per ASP.NET Core , sostituisci ComplexTypeModelBinderProvidercon un provider che taglia le stringhe.

Nel ConfigureServicesmetodo del codice di avvio , aggiungi questo:

services.AddMvc()
    .AddMvcOptions(s => {
        s.ModelBinderProviders[s.ModelBinderProviders.TakeWhile(p => !(p is ComplexTypeModelBinderProvider)).Count()] = new TrimmingModelBinderProvider();
    })

Definisci in TrimmingModelBinderProviderquesto modo:

/// <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 parte brutta di questo è il copia e incolla della GetBinderlogica ComplexTypeModelBinderProvider, ma non sembra esserci alcun gancio per evitarlo.


Non so perché, ma non funziona con ASP.NET Core 1.1.1. Tutte le proprietà dell'oggetto modello che ottengo nell'azione del controller sono nulle. Il metodo "SetProperty" è chiamato nerver.
Waldo,

Non ha funzionato per me, lo spazio all'inizio della mia proprietà è ancora lì.
Cedric Arnould,

2

Ho creato provider di valori per tagliare i valori dei parametri della stringa di query e i valori del modulo. Questo è stato testato con ASP.NET Core 3 e funziona perfettamente.

public class TrimmedFormValueProvider
    : FormValueProvider
{
    public TrimmedFormValueProvider(IFormCollection values)
        : base(BindingSource.Form, values, CultureInfo.InvariantCulture)
    { }

    public override ValueProviderResult GetValue(string key)
    {
        ValueProviderResult baseResult = base.GetValue(key);
        string[] trimmedValues = baseResult.Values.Select(v => v?.Trim()).ToArray();
        return new ValueProviderResult(new StringValues(trimmedValues));
    }
}

public class TrimmedQueryStringValueProvider
    : QueryStringValueProvider
{
    public TrimmedQueryStringValueProvider(IQueryCollection values)
        : base(BindingSource.Query, values, CultureInfo.InvariantCulture)
    { }

    public override ValueProviderResult GetValue(string key)
    {
        ValueProviderResult baseResult = base.GetValue(key);
        string[] trimmedValues = baseResult.Values.Select(v => v?.Trim()).ToArray();
        return new ValueProviderResult(new StringValues(trimmedValues));
    }
}

public class TrimmedFormValueProviderFactory
    : IValueProviderFactory
{
    public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
    {
        if (context.ActionContext.HttpContext.Request.HasFormContentType)
            context.ValueProviders.Add(new TrimmedFormValueProvider(context.ActionContext.HttpContext.Request.Form));
        return Task.CompletedTask;
    }
}

public class TrimmedQueryStringValueProviderFactory
    : IValueProviderFactory
{
    public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
    {
        context.ValueProviders.Add(new TrimmedQueryStringValueProvider(context.ActionContext.HttpContext.Request.Query));
        return Task.CompletedTask;
    }
}

Quindi registrare le fabbriche del provider di valori nella ConfigureServices()funzione in Startup.cs

services.AddControllersWithViews(options =>
{
    int formValueProviderFactoryIndex = options.ValueProviderFactories.IndexOf(options.ValueProviderFactories.OfType<FormValueProviderFactory>().Single());
    options.ValueProviderFactories[formValueProviderFactoryIndex] = new TrimmedFormValueProviderFactory();

    int queryStringValueProviderFactoryIndex = options.ValueProviderFactories.IndexOf(options.ValueProviderFactories.OfType<QueryStringValueProviderFactory>().Single());
    options.ValueProviderFactories[queryStringValueProviderFactoryIndex] = new TrimmedQueryStringValueProviderFactory();
});

0

Ci sono stati molti post che suggeriscono un approccio di attributo. Ecco un pacchetto che ha già un attributo trim e molti altri: Dado.ComponentModel.Mutations o 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();

Dopo la chiamata a Mutate (), user.UserName verrà modificato m@x_speed.01!.

Questo esempio taglierà gli spazi bianchi e metterà la stringa in minuscolo. Non introduce la convalida, ma System.ComponentModel.Annotationspuò essere utilizzato a fiancoDado.ComponentModel.Mutations .


0

L'ho pubblicato in un'altra discussione. In asp.net core 2, sono andato in una direzione diversa. Ho usato invece un filtro d'azione. In questo caso lo sviluppatore può impostarlo a livello globale o utilizzare come attributo per le azioni che desidera applicare il taglio della stringa. Questo codice viene eseguito dopo l'esecuzione dell'associazione del modello e può aggiornare i valori nell'oggetto modello.

Ecco il mio codice, prima crea un filtro di azione:

public class TrimInputStringsAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        foreach (var arg in context.ActionArguments)
        {
            if (arg.Value is string)
            {
                string val = arg.Value as string;
                if (!string.IsNullOrEmpty(val))
                {
                    context.ActionArguments[arg.Key] = val.Trim();
                }

                continue;
            }

            Type argType = arg.Value.GetType();
            if (!argType.IsClass)
            {
                continue;
            }

            TrimAllStringsInObject(arg.Value, argType);
        }
    }

    private void TrimAllStringsInObject(object arg, Type argType)
    {
        var stringProperties = argType.GetProperties()
                                      .Where(p => p.PropertyType == typeof(string));

        foreach (var stringProperty in stringProperties)
        {
            string currentValue = stringProperty.GetValue(arg, null) as string;
            if (!string.IsNullOrEmpty(currentValue))
            {
                stringProperty.SetValue(arg, currentValue.Trim(), null);
            }
        }
    }
}

Per usarlo, registrati come filtro globale o decora le tue azioni con l'attributo TrimInputStrings.

[TrimInputStrings]
public IActionResult Register(RegisterViewModel registerModel)
{
    // Some business logic...
    return Ok();
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.