Найкращий спосіб обрізки рядків після введення даних. Чи варто створити палітурку для власної моделі?


172

Я використовую ASP.NET MVC, і я хотів би, щоб усі введені користувачем рядкові поля були обрізані перед тим, як їх вставити в базу даних. А оскільки у мене є багато форм для введення даних, я шукаю елегантний спосіб обрізати всі рядки, а не явно обрізати значення кожного рядка, що надається користувачем. Мені цікаво знати, як і коли люди обрізають струни.

Я думав про те, можливо, створити прив'язку спеціальної моделі та обрізати там будь-які значення рядків ... таким чином, вся моя логіка обрізки міститься в одному місці. Це хороший підхід? Чи є зразки коду, які роблять це?

Відповіді:


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

Як щодо цього коду?

ModelBinders.Binders.DefaultBinder = new TrimModelBinder();

Встановити подія global.asax Application_Start.


3
Я просто замінив би код у внутрішній частині {} цим на стислість: string stringValue = (string) значення; value = string.IsNullOrEmpty (stringValue)? stringValue: stringValue.Trim ();
Simon_Weaver

4
Це заслуговує більшої кількості результатів. Я насправді здивований, що команда MVC не вирішила реалізувати це у в'яжучій моделі за замовчуванням ...
Портман

1
@BreckFresen У мене була така ж проблема, вам потрібно буде перевстановити метод BindModel і перевірити зв'язуванняContext.ModelType на рядок, а потім обрізати, якщо він є.
Келлі

3
Для тих, хто як я отримую неоднозначність у DefaultModelBinder, правильним є System.Web.Mvc.
GeoffM

3
Як би ви змінили це, щоб type="password"залишки вторкань залишилися недоторканими?
Екстрагорей

77

Це @takepara така ж роздільна здатність, але як IModelBinder замість DefaultModelBinder, так що додавання Modelbinder у global.asax відбувається через

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

Клас:

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

на основі @haacked публікації: http://haacked.com/archive/2011/03/19/fixing-binding-to-decimals.aspx


1
+1 за чистий розчин! Ви можете ще більше покращити читабельність свого коду, змінивши порядок returnвисловлювань та відкинувши умову:if (valueResult == null || string.IsNullOrEmpty(valueResult.AttemptedValue)) return null;
Маріус Шульц,

6
Це не обробляє атрибут контролера [ValidateInput (false)]. Це викликає виняток "Небезпечний запит ...".
CodeGrue

2
Для тих, хто отримує виняток "Небезпечний запит ...", зверніться до цієї статті - blogs.taiga.nl/martijn/2011/09/29/…
GurjeetSinghDB

2
Мій колега здійснив варіацію цього, що спричинило всілякі проблеми: issues.umbraco.org/issue/U4-6665 Я рекомендував би повернути null та empty як слід, а не завжди віддавати перевагу одному над іншим (у вашому випадку ви завжди повертати null, навіть якщо значення є порожнім рядком).
Ніколас Вестбі

2
Це, здається, порушує [AllowHtml]атрибут властивостей моделі (разом із [ValidateInput(false)]зазначеним вище CodeGrue
Mingwei Samuel

43

Одне вдосконалення відповіді на @takepara.

Деякі були в проекті:

public class NoTrimAttribute : Attribute { }

У зміні класу TrimModelBinder

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

до

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

і ви можете позначити властивості, які слід виключити з обрізки за допомогою атрибута [NoTrim].


1
Як ми можемо реалізувати щось подібне до цього атрибуту, використовуючи підхід IModelBinder від @Korayem? У деяких програмах я використовую іншу (сторонню) модель в'яжучого (наприклад, S # arp Archeticture's). Я хотів би записати це в приватну DLL, поділену між проектами, тому це має бути підхід IModelBinder.
Карл Буссема

1
@CarlBussema Ось питання про доступ до атрибутів всередині IModelBinder. stackoverflow.com/questions/6205176
Mac Attack

4
Я думаю , що це велике доповнення , але я хотів би замінити .Cast<object>().Any(a => a.GetType() == typeof(NoTrimAttribute))з .OfType<NoTrimAttribute>().Any(). Просто трішки чистіше.
DBueno

Я розміщую свої атрибути в спільній збірці, оскільки, як і в примітках про дані, такі атрибути мають сферу використання ширше, ніж просто MVC, наприклад, бізнес-рівень, клієнти. Ще одне спостереження "DisplayFormatAttribute (ConvertEmptyStringToNull)" контролює, чи буде обрізана рядок збережена як null чи порожня рядок. За замовчуванням є true (null), що мені подобається, але у випадку, якщо вам потрібні порожні рядки у вашій базі даних (сподіваємось, що не), ви можете встановити це false, щоб отримати це. У будь-якому випадку, це все добре, сподіваюся, що MS поширюють свої атрибути, включаючи обрізки та набивки та багато інших подібних матеріалів.
Tony Wall

17

Вдосконалюючи C # 6, тепер ви можете написати дуже компактну палітурку моделі, яка буде обрізати всі рядкові входи:

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

Вам потрібно включити цей рядок десь у Application_Start()своєму Global.asax.csфайлі, щоб використовувати палітурку моделі при прив'язці strings:

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

Я вважаю, що краще використовувати в'яжучу модель на зразок цього, а не переосмислювати палітурку моделі за замовчуванням, тому що вона буде використовуватися щоразу, коли ви прив'язуєте a string, чи це безпосередньо як аргумент методу, або як властивість класу моделі. Однак, якщо ви заміните палітурку моделі за замовчуванням, як підказують інші відповіді, це працюватиме лише тоді, коли властивості прив'язки на моделях, а не тоді, коли у вас є stringаргумент методу дії

Редагувати: коментатор запитав про те, щоб розібратися з ситуацією, коли поле не слід перевіряти. Моя оригінальна відповідь зводилася лише до питання, яке поставив ОП, але для тих, хто цікавиться, ви можете розібратися з валідацією, скориставшись наступним розширеним в'яжучим зразком:

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

Знову дивіться коментарі вище. Цей приклад не обробляє вимогу пропуску валідації IUnvalidatedValueProvider.
Аарон Гудон

@adrian, Інтерфейс IModelBinder має лише метод BindModel з bool типу return. Тоді як ви тут використовували об’єкт типу return?
Магендран V

@MagendranV Я не впевнений, на який інтерфейс ви дивитесь, але ця відповідь заснована на IModelBinder в ASP.NET MVC 5, який повертає об’єкт: docs.microsoft.com/en-us/previous-versions/aspnet /…
Адріан

1
@AaronHudon Я оновив свою відповідь, щоб включити приклад обробки пропускної перевірки
Адріан

Якщо поля вашого пароля мають правильний набір типів даних (тобто [DataType (DataType.Password)]), то ви можете оновити останній рядок так, щоб він не обрізав ці поля: return string.IsNullOrWhiteSpace (спробаValue) || indingContext.ModelMetadata.DataTypeName == "Пароль"? спробаValue: спробаValue.Trim ();
trfletch

15

У ASP.Net Core 2 це працювало для мене. Я використовую [FromBody]атрибут у своїх контролерах та вході JSON. Щоб перекрити обробку рядків у десеріалізації JSON, я зареєстрував власний JsonConverter:

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

А це перетворювач:

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

Ваше рішення працює чудово! Дякую. Я спробував інші рішення для .Net Core за допомогою IModelBinderProvider, він не працював.
Седрік Арннул

За винятком startup.cs, він також може використовуватися в моделі як [JsonConverter (typeof (TrimmingStringConverter))]. Btw. Чи є причина, щоб замість цього використовувати .Insert () .Add ()?
Ти

@wast Я думаю, що я щойно зробив .Insert () замість .Add (), щоб переконатися, що він запускається перед іншими перетворювачами. Не можу зараз згадати.
Кай Г

Яка ефективність роботи над цим DefaultContractResolver?
Maulik Modi

13

Ще один варіант відповіді @ takepara, але з іншим поворотом:

1) Я віддаю перевагу механізму атрибутів "StringTrim" для відмови (а не прикладу відмови "NoTrim" @Anton).

2) Додатковий виклик до SetModelValue необхідний, щоб переконатися, що ModelState заповнений правильно, і шаблон перевірки / прийняття / відхилення за замовчуванням може використовуватися як звичайний, тобто TryUpdateModel (модель) для застосування та ModelState.Clear () для прийняття всіх змін.

Помістіть це у вашій особі / спільній бібліотеці:

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

Потім це у вашому додатку / бібліотеці 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;
    }
}

Якщо ви не встановите значення властивості в палітурці, навіть коли ви не хочете нічого змінювати, ви повністю заблокуєте це властивість у ModelState! Це пояснюється тим, що ви зареєстровані як прив'язка всіх типів рядків, тому виявляється (в моєму тестуванні), що палітурка за замовчуванням не зробить це для вас тоді.


7

Додаткова інформація для тих, хто шукає, як це зробити в ASP.NET Core 1.0. Логіка змінилася досить сильно.

Я написав пост в блозі про те, як це зробити , він пояснює речі дещо детальніше

Отже рішення ASP.NET Core 1.0:

Модельне в’яжуче для фактичної обрізки

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

Також вам потрібен постачальник моделей палітурки в останній версії, це говорить про те, що слід використовувати це сполучна для цієї моделі

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

Потім його потрібно зареєструвати в Startup.cs

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

Це теж не працювало для мене, усі мої поля тепер
недійсні

5

Читаючи вище відмінні відповіді та коментарі та все більше плутаючись, я раптом подумав, ей, мені цікаво, чи є рішення jQuery. Тож для інших, хто, як я, вважаю ModelBinders трохи здивованим, я пропоную наступний фрагмент jQuery, який обрізає поля введення до надсилання форми.

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

1
2 речі: 1 - Кешуйте об’єкти клієнта (наприклад, $ (це)), 2 - Ви ніколи не можете розраховувати на введення клієнта, але ви точно можете покластися на код сервера. Тож ваша відповідь є доповненням до коду сервера відповідей :)
graumanoz

5

У випадку MVC Core

Біндер:

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

Постачальник:

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

Функція реєстрації:

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

Зареєструватися:

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

+1. Саме те, що я шукав. Яке призначення коду "binderToFind" у функції реєстрації?
Бред

Я просто намагаюся поставити користувальницький провайдер із резервним запасом SimpleTypeModelBinderProvider, підтримуючи той самий індекс.
Вікаш Кумар

Цілий опис можна знайти тут vikutech.blogspot.in/2018/02/…
Вікаш Кумар,

3

Пізно до партії, але нижче наведено короткий виклад коригувань, необхідних для MVC 5.2.3, якщо ви маєте справу з skipValidationвимогами постачальників вбудованих цінностей.

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

Я не згоден з рішенням. Вам слід перекрити GetPropertyValue, оскільки дані для SetProperty також можуть бути заповнені 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();
    }

}

Фільтруйте по propertyDescriptor PropertyType, якщо вас дійсно цікавлять лише значення рядків, але це не має значення, оскільки все, що потрапляє, - це в основному рядок.


2

Для ASP.NET Core замініть ComplexTypeModelBinderProviderпостачальника, який обробляє рядки.

У своєму ConfigureServicesспособі запуску додайте це:

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

Визначте TrimmingModelBinderProviderтак:

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

Потворна частина цього - це копія та вставка GetBinderлогіки ComplexTypeModelBinderProvider, але, здається, не існує жодного гачка, який дозволить вам уникнути цього.


Я не знаю чому, але це не працює для ASP.NET Core 1.1.1. Усі властивості об'єкта моделі, який я отримую в дії контролера, є нульовими. Метод "SetProperty" називається нервером.
Уолдо

Не працював для мене, місце на початку мого майна все ще є.
Седрік Арнульд

2

Я створив постачальників значень для обрізки значень параметрів рядка запиту та значень форми. Це було протестовано на ASP.NET Core 3 і працює чудово.

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

Потім зареєструйте фабрики постачальників цінностей у ConfigureServices()функції в 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

Було багато публікацій, які пропонують атрибутивний підхід. Ось пакет, у якого вже є атрибут trim та багато інших: Dado.ComponentModel.Mutations або 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();

Після дзвінка на Mutate (), користувач.UserName буде змінено m@x_speed.01!.

Цей приклад дозволить обрізати пробіл і змінити рядок у малі регістри. Він не вводить перевірку, але System.ComponentModel.Annotationsможе бути використаний поряд Dado.ComponentModel.Mutations.


0

Я розмістив це в іншій темі. У ядрах asp.net я пішов в іншому напрямку. Я замість цього застосував фільтр дій. У цьому випадку розробник може встановити його глобально або використовувати як атрибут для дій, які він хоче застосувати обрізки рядків. Цей код запускається після того, як відбулося прив'язування моделі, і він може оновлювати значення в об'єкті моделі.

Ось мій код, спочатку створіть фільтр дій:

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

Щоб використовувати його, або зареєструйтесь як глобальний фільтр або прикрасьте свої дії атрибутом TrimInputStrings.

[TrimInputStrings]
public IActionResult Register(RegisterViewModel registerModel)
{
    // Some business logic...
    return Ok();
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.