Cara terbaik untuk memotong string setelah entri data. Haruskah saya membuat pengikat model khusus?

172

Saya menggunakan ASP.NET MVC dan saya ingin semua pengguna memasukkan bidang string untuk dipangkas sebelum dimasukkan ke dalam basis data. Dan karena saya memiliki banyak formulir entri data, saya mencari cara yang elegan untuk memangkas semua string, bukannya secara eksplisit memangkas setiap nilai string yang disediakan pengguna. Saya tertarik untuk mengetahui bagaimana dan kapan orang-orang memotong tali.

Saya berpikir tentang mungkin membuat pengikat model khusus dan memotong nilai string apa pun di sana ... dengan cara itu, semua logika pemangkasan saya terkandung di satu tempat. Apakah ini pendekatan yang baik? Apakah ada contoh kode yang melakukan ini?

Johnny Oshika
sumber

Jawaban:

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

Bagaimana dengan kode ini?

ModelBinders.Binders.DefaultBinder = new TrimModelBinder();

Tetapkan acara global.asax Application_Start.

takepara
sumber
3
saya baru saja mengganti kode di paling dalam {} dengan ini untuk singkatnya: string stringValue = (string) value; value = string.IsNullOrEmpty (stringValue)? stringValue: stringValue.Trim ();
Simon_Weaver
4
Ini layak mendapatkan lebih banyak suara positif. Saya benar-benar terkejut tim MVC tidak memilih untuk menerapkan ini dalam binder model default ...
Portman
1
@BreckFresen Saya punya masalah yang sama, Anda harus mengganti metode BindModel dan memeriksa bindingContext.ModelType untuk sebuah string kemudian potong jika itu.
Kelly
3
Bagi siapa pun seperti saya mendapatkan ambiguitas pada DefaultModelBinder, yang benar adalah menggunakan System.Web.Mvc.
GeoffM
3
Bagaimana Anda memodifikasi ini agar type="password"input tidak tersentuh?
Luar Biasa
77

Ini adalah @takepara resolusi yang sama tetapi sebagai IModelBinder bukan DefaultModelBinder sehingga menambahkan modelbinder di global.asax melalui

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

Kelas:

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

berdasarkan pada posting @haacked: http://haacked.com/archive/2011/03/19/fixing-binding-to-decimals.aspx

Korayem
sumber
1
+1 untuk solusi bersih! Anda dapat meningkatkan keterbacaan kode Anda bahkan lebih lagi dengan mengubah urutan returnpernyataan dan dengan meniadakan kondisinya:if (valueResult == null || string.IsNullOrEmpty(valueResult.AttemptedValue)) return null;
Marius Schulz
6
Ini tidak menangani atribut pengontrol [ValidateInput (false)]. Itu menyebabkan pengecualian "Permintaan Berbahaya ...".
CodeGrue
2
Bagi mereka yang mendapatkan pengecualian 'Permintaan Berbahaya ...', lihat artikel ini - blogs.taiga.nl/martijn/2011/09/29/…
GurjeetSinghDB
2
Seorang rekan kerja saya menerapkan variasi ini yang menyebabkan berbagai masalah: issues.umbraco.org/issue/U4-6665 Saya akan merekomendasikan untuk mengembalikan nol dan kosong seperlunya daripada selalu lebih suka satu sama lain (dalam kasus Anda, Anda selalu mengembalikan null bahkan ketika nilainya adalah string kosong).
Nicholas Westby
2
Ini tampaknya mematahkan [AllowHtml]atribut pada model properties (bersama dengan [ValidateInput(false)]CodeGrue seperti yang disebutkan di atas
Mingwei Samuel
43

Satu peningkatan pada jawaban @takepara.

Suatu tempat dalam proyek:

public class NoTrimAttribute : Attribute { }

Dalam perubahan kelas TrimModelBinder

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

untuk

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

dan Anda dapat menandai properti yang dikecualikan dari pemangkasan dengan atribut [NoTrim].

Anton
sumber
1
Bagaimana kita bisa mengimplementasikan sesuatu seperti atribut ini ketika menggunakan pendekatan IModelBinder oleh @Korayem? Dalam beberapa aplikasi, saya menggunakan pengikat model (pihak ketiga) yang berbeda (misalnya, S # arp Archeticture's). Saya ingin menulis ini di DLL pribadi yang dibagikan di antara proyek, jadi itu perlu pendekatan IModelBinder.
Carl Bussema
1
@CarlBussema Inilah pertanyaan tentang mengakses Atribut dari dalam sebuah IModelBinder. stackoverflow.com/questions/6205176
Mac Attack
4
Saya pikir ini adalah tambahan yang bagus tapi saya akan mengganti .Cast<object>().Any(a => a.GetType() == typeof(NoTrimAttribute))dengan .OfType<NoTrimAttribute>().Any(). Sedikit lebih bersih.
DBueno
Saya meletakkan atribut saya di majelis bersama karena, seperti halnya anotasi data, atribut tersebut memiliki cakupan penggunaan yang lebih luas dari sekadar MVC, misalnya tingkat bisnis, klien. Satu pengamatan lain, "DisplayFormatAttribute (ConvertEmptyStringToNull)" mengontrol apakah string yang dipangkas akan disimpan sebagai null atau string kosong. Defaultnya adalah true (null) yang saya suka tetapi jika Anda membutuhkan string kosong dalam database Anda (semoga tidak) Anda dapat mengaturnya sebagai false untuk mendapatkannya. Bagaimanapun, ini semua adalah hal yang baik, semoga MS memperluas atribut mereka termasuk trimming dan padding dan banyak hal umum lainnya seperti itu.
Tony Wall
17

Dengan peningkatan pada C # 6, sekarang Anda dapat menulis binder model yang sangat ringkas yang akan memangkas semua input string:

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

Anda harus memasukkan baris ini di suatu tempat di Application_Start()dalam Global.asax.csfile Anda untuk menggunakan pengikat model saat mengikat strings:

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

Saya merasa lebih baik menggunakan pengikat model seperti ini, daripada mengganti pengikat model default, karena kemudian akan digunakan setiap kali Anda mengikat string, apakah itu secara langsung sebagai argumen metode atau sebagai properti pada kelas model. Namun, jika Anda mengganti pengikat model default seperti yang disarankan oleh jawaban lain di sini, itu hanya akan berfungsi saat mengikat properti pada model, bukan ketika Anda memiliki stringargumen sebagai metode tindakan

Sunting: seorang komentator bertanya tentang berurusan dengan situasi ketika sebuah bidang tidak boleh divalidasi. Jawaban asli saya dikurangi untuk menangani hanya dengan pertanyaan yang diajukan OP, tetapi bagi mereka yang tertarik, Anda dapat menangani validasi dengan menggunakan pengikat model tambahan berikut:

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
sumber
Sekali lagi, lihat komentar di atas. Contoh ini tidak menangani persyaratan skipValidation dari IUnvalidatedValueProvider.
Aaron Hudon
@adrian, Antarmuka IModelBinder hanya memiliki metode BindModel dengan tipe pengembalian bool. Lalu bagaimana Anda menggunakan objek tipe pengembalian di sini?
Magendran V
@ MagendranV Saya tidak yakin antarmuka mana yang Anda lihat, tetapi jawaban ini didasarkan pada IModelBinder di ASP.NET MVC 5, yang mengembalikan objek: docs.microsoft.com/en-us/previous-versions/aspnet / ...
adrian
1
@AaronHudon Saya telah memperbarui jawaban saya untuk menyertakan contoh untuk menangani melewatkan validasi
adrian
Jika bidang kata sandi Anda memiliki set datatype yang benar (yaitu [DataType (DataType.Password)])) maka Anda dapat memperbarui baris terakhir sebagai berikut sehingga tidak memangkas bidang-bidang ini: mengembalikan string. Kembali ke atas bindingContext.ModelMetadata.DataTypeName == "Kata Sandi"? triedtedValue: triedtedValue.Trim ();
trfletch
15

Di ASP.Net Core 2 ini bekerja untuk saya. Saya menggunakan [FromBody]atribut di pengontrol dan input JSON saya. Untuk mengganti penanganan string dalam deserialization JSON, saya mendaftarkan JsonConverter saya sendiri:

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

Dan ini konverternya:

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
sumber
Solusi Anda berfungsi dengan baik! Terima kasih. Saya mencoba solusi lain untuk .Net Core menggunakan IModelBinderProvider, itu tidak berhasil.
Cedric Arnould
Kecuali di startup.cs, ini juga dapat digunakan dalam Model sebagai [JsonConverter (typeof (TrimmingStringConverter))]. Btw. apakah ada alasan di balik penggunaan .Insert () sebagai gantinya .Add ()?
buang
@ast Saya kira saya baru saja melakukannya. Masukkan () bukan. Tambahkan () untuk memastikan itu dijalankan sebelum Konverter lainnya. Tidak ingat sekarang
Kai G
Apa overhead kinerja ini selama DefaultContractResolver?
Maulik Modi
13

Varian lain dari jawaban @ takepara tetapi dengan sentuhan berbeda:

1) Saya lebih suka mekanisme atribut opt-in "StringTrim" (bukan contoh "NoTrim" opt-out dari @Anton).

2) Panggilan tambahan ke SetModelValue diperlukan untuk memastikan ModelState diisi dengan benar dan pola validasi / terima / tolak standar dapat digunakan seperti biasa, yaitu TryUpdateModel (model) untuk menerapkan dan ModelState.Clear () untuk menerima semua perubahan.

Letakkan ini di entitas / perpustakaan bersama Anda:

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

Maka ini di aplikasi / perpustakaan MVC Anda:

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

Jika Anda tidak menetapkan nilai properti di binder, bahkan ketika Anda tidak ingin mengubah apa pun, Anda akan memblokir properti itu dari ModelState sama sekali! Ini karena Anda terdaftar sebagai pengikatan semua jenis string, jadi tampaknya (dalam pengujian saya) bahwa pengikat default tidak akan melakukannya untuk Anda saat itu.

Tony Wall
sumber
7

Info tambahan untuk siapa pun yang mencari cara melakukannya di ASP.NET Core 1.0. Logika sudah banyak berubah.

Saya menulis posting blog tentang bagaimana melakukannya , itu menjelaskan hal-hal secara lebih rinci

Jadi solusi ASP.NET Core 1.0:

Model pengikat untuk melakukan pemangkasan yang sebenarnya

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

Anda juga memerlukan Penyedia Pengikat Model dalam versi terbaru, ini memberitahu bahwa pengikat ini harus digunakan untuk model ini

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

Maka harus terdaftar di Startup.cs

 services.AddMvc().AddMvcOptions(options => {  
       options.ModelBinderProviders.Insert(0, new TrimmingModelBinderProvider());
 });
Tuukka Lindroos
sumber
Itu tidak berhasil untuk saya juga, semua bidang saya adalah nol sekarang
Cedric Arnould
5

Sambil membaca jawaban dan komentar luar biasa di atas, dan menjadi semakin bingung, tiba-tiba saya berpikir, hei, saya ingin tahu apakah ada solusi jQuery. Jadi untuk orang lain yang, seperti saya, menemukan ModelBinders sedikit membingungkan, saya menawarkan potongan jQuery berikut yang memangkas bidang input sebelum formulir dikirimkan.

    $('form').submit(function () {
        $(this).find('input:text').each(function () {
            $(this).val($.trim($(this).val()));
        })
    });
Eric Nelson
sumber
1
2 hal: 1 - Tembolok objek klien Anda (seperti $ (ini)), 2 - Anda tidak pernah bisa bergantung pada input klien, tetapi Anda pasti dapat mengandalkan kode server. Jadi jawaban Anda adalah jawaban untuk jawaban kode server :)
graumanoz
5

Dalam hal MVC Core

Bahan pengikat:

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

Pemberi:

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

Fungsi pendaftaran:

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

Daftar:

service.AddMvc(option => option.AddStringTrimmingProvider())
Vikash Kumar
sumber
+1. Persis apa yang saya cari. Apa tujuan dari kode "binderToFind" dalam fungsi Registrasi?
Brad
Saya hanya mencoba untuk menempatkan penyedia kustom dengan fallback SimpleTypeModelBinderProviderdengan mempertahankan indeks yang sama.
Vikash Kumar
Penjelasan lengkap dapat ditemukan di sini vikutech.blogspot.in/2018/02/...
Vikash Kumar
3

Terlambat ke pesta, tetapi berikut ini adalah ringkasan penyesuaian yang diperlukan untuk MVC 5.2.3 jika Anda ingin menangani skipValidationpersyaratan penyedia nilai bawaan.

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

Saya tidak setuju dengan solusinya. Anda harus mengganti GetPropertyValue karena data untuk SetProperty juga dapat diisi oleh ModelState. Untuk menangkap data mentah dari elemen input tulis ini:

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

}

Saring oleh propertyDescriptor PropertyType jika Anda benar-benar hanya tertarik pada nilai-nilai string tetapi itu tidak masalah karena semua yang ada pada dasarnya adalah sebuah string.

rudimenter
sumber
2

Untuk ASP.NET Core , ganti ComplexTypeModelBinderProviderdengan penyedia yang memotong string.

Dalam ConfigureServicesmetode kode startup Anda , tambahkan ini:

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

Definisikan TrimmingModelBinderProviderseperti ini:

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

Bagian buruk dari ini adalah copy dan paste dari GetBinderlogika ComplexTypeModelBinderProvider, tetapi tampaknya tidak ada kait untuk membiarkan Anda menghindari ini.

Edward Brey
sumber
Saya tidak tahu mengapa, tetapi tidak berfungsi untuk ASP.NET Core 1.1.1. Semua properti dari objek model yang saya dapatkan di action controller adalah null. Metode "SetProperty" disebut nerver.
Waldo
Tidak berfungsi untuk saya, ruang di awal properti saya masih ada.
Cedric Arnould
2

Saya membuat penyedia nilai untuk memotong nilai parameter string kueri dan nilai formulir. Ini diuji dengan ASP.NET Core 3 dan bekerja dengan sempurna.

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

Kemudian daftarkan pabrik penyedia nilai dalam ConfigureServices()fungsi di 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
sumber
0

Ada banyak posting yang menyarankan pendekatan atribut. Berikut adalah paket yang sudah memiliki atribut trim dan banyak lainnya: Dado.ComponentModel.Mutations atau 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();

Setelah panggilan ke Mutate (), user.UserName akan dimutasi ke m@x_speed.01!.

Contoh ini akan memangkas spasi dan huruf besar-kecil string. Itu tidak memperkenalkan validasi, tetapi System.ComponentModel.Annotationsdapat digunakan bersama Dado.ComponentModel.Mutations.

roydukkey
sumber
0

Saya memposting ini di utas lainnya. Di asp.net core 2, saya pergi ke arah yang berbeda. Saya menggunakan filter tindakan sebagai gantinya. Dalam hal ini pengembang dapat mengaturnya secara global atau menggunakan atribut untuk tindakan yang ingin diterapkannya pemangkasan string. Kode ini berjalan setelah pengikatan model telah terjadi, dan dapat memperbarui nilai-nilai dalam objek model.

Ini kode saya, pertama buat filter tindakan:

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

Untuk menggunakannya, daftarkan sebagai filter global atau hiasi tindakan Anda dengan atribut TrimInputStrings.

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