Beste Möglichkeit, Zeichenfolgen nach der Dateneingabe zu kürzen. Soll ich einen benutzerdefinierten Modellordner erstellen?

172

Ich verwende ASP.NET MVC und möchte, dass alle vom Benutzer eingegebenen Zeichenfolgenfelder gekürzt werden, bevor sie in die Datenbank eingefügt werden. Und da ich viele Dateneingabeformulare habe, suche ich nach einer eleganten Möglichkeit, alle Zeichenfolgen zu kürzen, anstatt jeden vom Benutzer angegebenen Zeichenfolgenwert explizit zu kürzen. Ich bin interessiert zu wissen, wie und wann Leute Saiten schneiden.

Ich habe darüber nachgedacht, vielleicht einen benutzerdefinierten Modellordner zu erstellen und dort alle Zeichenfolgenwerte zu kürzen. Auf diese Weise ist meine gesamte Trimmlogik an einem Ort enthalten. Ist das ein guter Ansatz? Gibt es Codebeispiele, die dies tun?

Johnny Oshika
quelle

Antworten:

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

Wie wäre es mit diesem Code?

ModelBinders.Binders.DefaultBinder = new TrimModelBinder();

Setzen Sie das Ereignis global.asax Application_Start.

takepara
quelle
3
Ich würde der Kürze halber nur den Code im innersten {} durch diesen ersetzen: string stringValue = (string) value; value = string.IsNullOrEmpty (stringValue)? stringValue: stringValue.Trim ();
Simon_Weaver
4
Dies verdient mehr Gegenstimmen. Ich bin tatsächlich überrascht, dass das MVC-Team dies nicht in den Standardmodellordner implementiert hat ...
Portman
1
@BreckFresen Ich hatte das gleiche Problem. Sie müssen die BindModel-Methode überschreiben und den bindingContext.ModelType auf eine Zeichenfolge überprüfen und dann zuschneiden, wenn dies der Fall ist.
Kelly
3
Für jeden wie mich, der eine Mehrdeutigkeit in DefaultModelBinder hat, verwendet die richtige System.Web.Mvc.
GeoffM
3
Wie würden Sie dies ändern, um type="password"Eingaben unberührt zu lassen?
Extragorey
77

Dies ist @takepara dieselbe Auflösung, jedoch als IModelBinder anstelle von DefaultModelBinder, sodass das Hinzufügen des Modellbinders in global.asax abgeschlossen ist

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

Die Klasse:

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

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

Korayem
quelle
1
+1 für eine saubere Lösung! Sie könnten die Lesbarkeit Ihres Codes noch weiter verbessern, indem Sie die Reihenfolge der returnAnweisungen ändern und die Bedingung negieren:if (valueResult == null || string.IsNullOrEmpty(valueResult.AttemptedValue)) return null;
Marius Schulz
6
Dies behandelt das Controller-Attribut [ValidateInput (false)] nicht. Es verursacht die Ausnahme "Gefährliche Anforderung ...".
CodeGrue
2
Für diejenigen, die die Ausnahme "Gefährliche Anfrage ..." erhalten,
lesen Sie
2
Ein Mitarbeiter von mir hat eine Variation davon implementiert, die alle möglichen Probleme verursacht hat: Issues.umbraco.org/issue/U4-6665 Ich würde empfehlen, null und leer zurückzugeben, anstatt immer einander vorzuziehen (in Ihrem Fall Sie) Geben Sie immer null zurück, auch wenn der Wert eine leere Zeichenfolge ist.
Nicholas Westby
2
Dies scheint das [AllowHtml]Attribut auf Modelleigenschaften zu brechen (zusammen mit dem [ValidateInput(false)]oben erwähnten CodeGrue
Mingwei Samuel
43

Eine Verbesserung der Antwort von @takepara.

Irgendwo im Projekt:

public class NoTrimAttribute : Attribute { }

In TrimModelBinder Klassenwechsel

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

zu

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

und Sie können Eigenschaften, die vom Zuschneiden ausgeschlossen werden sollen, mit dem Attribut [NoTrim] markieren.

Anton
quelle
1
Wie können wir so etwas wie dieses Attribut implementieren, wenn wir den IModelBinder-Ansatz von @Korayem verwenden? In einigen Anwendungen verwende ich einen anderen Modellordner (von Drittanbietern) (z. B. S # arp Archeticture's). Ich möchte dies in eine private DLL schreiben, die von Projekten gemeinsam genutzt wird, daher muss es sich um einen IModelBinder-Ansatz handeln.
Carl Bussema
1
@CarlBussema Hier ist eine Frage zum Zugriff auf Attribute aus einem IModelBinder heraus. stackoverflow.com/questions/6205176
Mac Attack
4
Ich denke , es ist eine große Bereicherung , aber ich würde ersetzt die .Cast<object>().Any(a => a.GetType() == typeof(NoTrimAttribute))mit .OfType<NoTrimAttribute>().Any(). Nur ein bisschen sauberer.
DBueno
Ich habe meine Attribute in eine gemeinsam genutzte Assembly eingefügt, da diese Attribute wie bei Datenanmerkungen einen breiteren Verwendungsbereich haben als nur MVC-Clients, z. B. Business-Tier-Clients. Eine weitere Beobachtung, das "DisplayFormatAttribute (ConvertEmptyStringToNull)", steuert, ob die zugeschnittene Zeichenfolge als Null oder als leere Zeichenfolge gespeichert wird. Der Standardwert ist true (null), was mir gefällt, aber falls Sie leere Zeichenfolgen in Ihrer Datenbank benötigen (hoffentlich nicht), können Sie ihn auf false setzen, um dies zu erhalten. Wie auch immer, das sind alles gute Sachen, ich hoffe, MS erweitern ihre Attribute um Trimmen und Polstern und viele andere übliche Sachen wie diese.
Tony Wall
17

Mit Verbesserungen in C # 6 können Sie jetzt einen sehr kompakten Modellordner schreiben, der alle Zeichenfolgeneingaben trimmt:

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

Sie müssen diese Zeile irgendwo in Application_Start()Ihre Global.asax.csDatei aufnehmen, um den Modellordner beim Binden von strings verwenden zu können:

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

Ich finde es besser, einen Modellordner wie diesen zu verwenden, als den Standardmodellordner zu überschreiben, da er dann immer dann verwendet wird, wenn Sie a binden string, sei es direkt als Methodenargument oder als Eigenschaft für eine Modellklasse. Wenn Sie jedoch den Standardmodellordner überschreiben, wie andere Antworten hier vorschlagen, funktioniert dies nur , wenn Sie Eigenschaften für Modelle binden, nicht, wenn Sie stringein Argument für eine Aktionsmethode haben

Bearbeiten: Ein Kommentator fragte nach dem Umgang mit der Situation, in der ein Feld nicht validiert werden sollte. Meine ursprüngliche Antwort wurde reduziert, um nur die Frage zu behandeln, die das OP gestellt hatte. Für Interessierte können Sie sich jedoch mit der Validierung befassen, indem Sie den folgenden erweiterten Modellordner verwenden:

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();
    }
}
Adrian
quelle
Siehe auch die obigen Kommentare. In diesem Beispiel wird die SkipValidation-Anforderung von IUnvalidatedValueProvider nicht behandelt.
Aaron Hudon
@adrian, Die IModelBinder-Schnittstelle verfügt nur über die Methode BindModel mit dem Rückgabetyp bool. Wie haben Sie dann das Objekt vom Typ return hier verwendet?
Magendran V
@MagendranV Ich bin nicht sicher, welche Schnittstelle Sie suchen, aber diese Antwort basiert auf dem IModelBinder in ASP.NET MVC 5, der ein Objekt zurückgibt
Adrian
1
@ AaronHudon Ich habe meine Antwort aktualisiert, um ein Beispiel für das Überspringen der Validierung aufzunehmen
Adrian
Wenn für Ihre Kennwortfelder der richtige Datentyp festgelegt ist (z. B. [DataType (DataType.Password)]), können Sie die letzte Zeile wie folgt aktualisieren, damit diese Felder nicht gekürzt werden: return string.IsNullOrWhiteSpace (versuchteWert) || bindingContext.ModelMetadata.DataTypeName == "Passwort"? versucheValue: versuchteValue.Trim ();
Trfletch
15

In ASP.Net Core 2 hat dies bei mir funktioniert. Ich verwende das [FromBody]Attribut in meinen Controllern und in der JSON-Eingabe. Um die Zeichenfolgenbehandlung bei der JSON-Deserialisierung zu überschreiben, habe ich meinen eigenen JsonConverter registriert:

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

Und das ist der Konverter:

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();
    }
}
Kai G.
quelle
Ihre Lösung funktioniert gut! Vielen Dank. Ich habe die anderen Lösungen für .Net Core mit IModelBinderProvider ausprobiert, es hat nicht funktioniert.
Cedric Arnould
Außer in startup.cs kann es in Model auch als [JsonConverter (typeof (TrimmingStringConverter))] verwendet werden. Übrigens. Gibt es einen Grund für die Verwendung von .Insert () anstelle von .Add ()?
Verschwendung
@wast Ich denke, ich habe gerade .Insert () anstelle von .Add () gemacht, um sicherzustellen, dass es vor anderen Konvertern ausgeführt wird. Ich kann mich jetzt nicht erinnern.
Kai G
Wie hoch ist der Leistungsaufwand für DefaultContractResolver?
Maulik Modi
13

Eine andere Variante von @ takeparas Antwort, aber mit einer anderen Wendung:

1) Ich bevorzuge den Opt-In-Attributmechanismus "StringTrim" (anstelle des Opt-Out-Beispiels "NoTrim" von @Anton).

2) Ein zusätzlicher Aufruf von SetModelValue ist erforderlich, um sicherzustellen, dass der ModelState korrekt ausgefüllt ist und das Standardmuster für Validierung / Akzeptieren / Ablehnen wie gewohnt verwendet werden kann, dh TryUpdateModel (Modell) zum Anwenden und ModelState.Clear () zum Akzeptieren aller Änderungen.

Fügen Sie dies in Ihre Entität / gemeinsam genutzte Bibliothek ein:

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

Dann dies in Ihrer MVC-Anwendung / Bibliothek:

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

Wenn Sie den Eigenschaftswert nicht im Ordner festlegen, auch wenn Sie nichts ändern möchten, blockieren Sie diese Eigenschaft vollständig aus ModelState! Dies liegt daran, dass Sie als bindend für alle Zeichenfolgentypen registriert sind. Daher scheint es (in meinen Tests), dass der Standardordner dies dann nicht für Sie erledigt.

Tony Wall
quelle
7

Zusätzliche Informationen für alle, die dies in ASP.NET Core 1.0 suchen. Die Logik hat sich sehr verändert.

Ich habe einen Blog-Beitrag darüber geschrieben, wie es geht. Er erklärt die Dinge etwas detaillierter

Also ASP.NET Core 1.0-Lösung:

Modellbinder für das eigentliche Trimmen

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

Außerdem benötigen Sie in der neuesten Version den Model Binder Provider. Dies gibt an, ob dieser Binder für dieses Modell verwendet werden soll

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

Dann muss es in Startup.cs registriert werden

 services.AddMvc().AddMvcOptions(options => {  
       options.ModelBinderProviders.Insert(0, new TrimmingModelBinderProvider());
 });
Tuukka Lindroos
quelle
Es hat auch bei mir nicht funktioniert, alle meine Felder sind jetzt null
Cedric Arnould
5

Während ich die hervorragenden Antworten und Kommentare oben durchlas und zunehmend verwirrt wurde, dachte ich plötzlich: Hey, ich frage mich, ob es eine jQuery-Lösung gibt. Für andere, die ModelBinders wie ich etwas verwirrend finden, biete ich das folgende jQuery-Snippet an, das die Eingabefelder schneidet, bevor das Formular gesendet wird.

    $('form').submit(function () {
        $(this).find('input:text').each(function () {
            $(this).val($.trim($(this).val()));
        })
    });
Eric Nelson
quelle
1
2 Dinge: 1 - Cache deine Client-Objekte (wie $ (this)), 2 - Du kannst dich niemals auf Client-Eingaben verlassen, aber du kannst dich definitiv auf Server-Code verlassen. Ihre Antwort ist also eine Vervollständigung der
Servercode-
5

Im Falle von MVC Core

Bindemittel:

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

Anbieter:

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

Registrierungsfunktion:

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

Registrieren:

service.AddMvc(option => option.AddStringTrimmingProvider())
Vikash Kumar
quelle
+1. Genau das, wonach ich gesucht habe. Was ist der Zweck des "binderToFind" -Codes in der Registrierungsfunktion?
Brad
Ich versuche nur, einen benutzerdefinierten Anbieter mit dem Fallback von zu versehen, SimpleTypeModelBinderProviderindem ich denselben Index behalte.
Vikash Kumar
Die vollständige Beschreibung finden Sie hier vikutech.blogspot.in/2018/02/…
Vikash Kumar
3

Spät zur Party, aber das Folgende ist eine Zusammenfassung der Anpassungen, die für MVC 5.2.3 erforderlich sind, wenn Sie die skipValidationAnforderungen der integrierten Wertanbieter erfüllen möchten.

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());
        ...
    }
Aaron Hudon
quelle
2

Ich bin mit der Lösung nicht einverstanden. Sie sollten GetPropertyValue überschreiben, da die Daten für SetProperty auch vom ModelState gefüllt werden können. Um die Rohdaten von den Eingabeelementen abzufangen, schreiben Sie Folgendes:

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

}

Filtern Sie nach propertyDescriptor PropertyType, wenn Sie wirklich nur an Zeichenfolgenwerten interessiert sind, dies aber keine Rolle spielen sollte, da alles, was hereinkommt, im Grunde eine Zeichenfolge ist.

Rudimenter
quelle
2

Ersetzen Sie für ASP.NET Core das ComplexTypeModelBinderProviderdurch einen Anbieter, der Zeichenfolgen schneidet.

Fügen Sie in Ihrer Startcodemethode Folgendes ConfigureServiceshinzu:

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

Definieren Sie TrimmingModelBinderProviderwie folgt :

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

Der hässliche Teil davon ist das Kopieren und Einfügen der GetBinderLogik von ComplexTypeModelBinderProvider, aber es scheint keinen Haken zu geben, mit dem Sie dies vermeiden können.

Edward Brey
quelle
Ich weiß nicht warum, aber es funktioniert nicht für ASP.NET Core 1.1.1. Alle Eigenschaften des Modellobjekts, das ich in der Controller-Aktion erhalte, sind null. Die Methode "SetProperty" heißt nerver.
Waldo
Hat bei mir nicht funktioniert, der Platz am Anfang meines Grundstücks ist noch da.
Cedric Arnould
2

Ich habe Wertanbieter erstellt, um die Parameterwerte der Abfragezeichenfolge und die Formularwerte zu kürzen. Dies wurde mit ASP.NET Core 3 getestet und funktioniert einwandfrei.

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

Registrieren Sie dann die Value Provider-Fabriken in der ConfigureServices()Funktion 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();
});
Bassem
quelle
0

Es gab viele Beiträge, die einen Attributansatz vorschlugen. Hier ist ein Paket, das bereits ein Trim-Attribut und viele andere hat: Dado.ComponentModel.Mutations oder 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();

Nach dem Aufruf von Mutate () wird user.UserName in mutiert m@x_speed.01!.

In diesem Beispiel werden Leerzeichen abgeschnitten und die Zeichenfolge in Kleinbuchstaben geschrieben. Es wird keine Validierung eingeführt, aber die System.ComponentModel.Annotationskann nebenbei verwendet werden Dado.ComponentModel.Mutations.

Roydukkey
quelle
0

Ich habe dies in einem anderen Thread gepostet. In asp.net Core 2 bin ich in eine andere Richtung gegangen. Ich habe stattdessen einen Aktionsfilter verwendet. In diesem Fall kann der Entwickler es entweder global festlegen oder als Attribut für die Aktionen verwenden, die er zum Trimmen der Zeichenfolge anwenden möchte. Dieser Code wird ausgeführt, nachdem die Modellbindung stattgefunden hat, und kann die Werte im Modellobjekt aktualisieren.

Hier ist mein Code, erstelle zuerst einen Aktionsfilter:

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

Um es zu verwenden, registrieren Sie sich entweder als globaler Filter oder dekorieren Sie Ihre Aktionen mit dem TrimInputStrings-Attribut.

[TrimInputStrings]
public IActionResult Register(RegisterViewModel registerModel)
{
    // Some business logic...
    return Ok();
}
Marcos de Aguiar
quelle