Лучший способ обрезать строки после ввода данных. Должен ли я создать пользовательскую модель связующего?

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

Установить глобальное.asax событие Application_Start.

takepara
источник
3
я бы просто заменил код во внутренней части {} на краткость: string stringValue = (string) value; value = string.IsNullOrEmpty (stringValue)? stringValue: stringValue.Trim ();
Simon_Weaver
4
Это заслуживает большего количества голосов. Я на самом деле удивлен, что команда MVC не решила реализовать это в связывателе модели по умолчанию ...
Portman
1
@BreckFresen У меня была та же проблема, вам нужно переопределить метод BindModel и проверить для привязки stringContext.ModelType строку, а затем обрезать, если она есть.
Келли
3
Для любого, как я, получая неоднозначность в DefaultModelBinder, правильный использует System.Web.Mvc.
GeoffM
3
Как бы вы изменили это, чтобы оставить type="password"входы нетронутыми?
Extragorey
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

Korayem
источник
1
+1 за чистое решение! Вы могли бы еще больше улучшить читабельность своего кода, изменив порядок returnоператоров и отменив условие:if (valueResult == null || string.IsNullOrEmpty(valueResult.AttemptedValue)) return null;
Marius Schulz
6
Это не обрабатывает атрибут контроллера [ValidateInput (false)]. Это вызывает исключение «Опасный запрос ...».
CodeGrue
2
Для тех, кто получает исключение «Опасный запрос ...», обратитесь к этой статье - blogs.taiga.nl/martijn/2011/09/29/…
GurjeetSinghDB
2
Коллега моего реализована вариантом этого, вызвавший все виды вопросов: issues.umbraco.org/issue/U4-6665 я рекомендовал бы возвращение нуля и сливать в зависимости от обстоятельств , а не всегда предпочитая один над другими (в вашем случае, вам всегда возвращать ноль, даже если значение является пустой строкой).
Николас Уэстби
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)» определяет, будет ли усеченная строка сохранена как пустая или пустая. По умолчанию это true (null), что мне нравится, но в случае, если вам нужны пустые строки в вашей базе данных (надеюсь, нет), вы можете установить его в false, чтобы получить это. В любом случае, это все хорошо, надеюсь, MS расширит свои атрибуты, добавив обрезку и отступы, и много других подобных вещей.
Тони Уолл
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());

Я считаю, что лучше использовать связыватель модели, подобный этому, а не переопределять связыватель модели по умолчанию, потому что тогда он будет использоваться всякий раз, когда вы связываете объект string, будь то непосредственно как аргумент метода или как свойство класса модели. Однако, если вы переопределите связыватель модели по умолчанию, как предлагают другие ответы, это будет работать только при привязке свойств к моделям, а не когда у вас есть stringаргумент метода действия

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

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();
    }
}
Адриан
источник
Опять же, см. Комментарии выше. В этом примере не обрабатывается требование skipValidation IUnvalidatedValueProvider.
Аарон Худон
@adrian, интерфейс IModelBinder имеет только метод BindModel с возвращаемым типом bool. Тогда как вы использовали здесь объект возвращаемого типа?
Магендран V
@MagendranV Я не уверен, какой интерфейс вы смотрите, но этот ответ основан на IModelBinder в ASP.NET MVC 5, который возвращает объект: docs.microsoft.com/en-us/previous-versions/aspnet /…
Адриан
1
@AaronHudon Я обновил свой ответ, включив в него пример для обработки пропуска проверки
Адриан
Если в ваших полях пароля установлен правильный тип данных (т. Е. [DataType (DataType.Password)]), вы можете обновить последнюю строку следующим образом, чтобы она не обрезала эти поля: возвращаемая строка. bindingContext.ModelMetadata.DataTypeName == "Пароль"? предпринимаемое значение: предпринятое значение.Трим ();
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, он также может использоваться в Model как [JsonConverter (typeof (TrimmingStringConverter))]]. Btw. Есть ли причина использования .Insert () вместо .Add ()?
еси
@ Wast Я думаю, я просто сделал .Insert () вместо .Add (), чтобы убедиться, что он запускается перед другими конвертерами. Не могу вспомнить сейчас.
Кай Г
Каковы издержки производительности по сравнению с DefaultContractResolver?
Маулик Моди
13

Другой вариант ответа @ takepara, но с другим поворотом:

1) Я предпочитаю opt-in "StringTrim" механизм атрибута (а не opt-out "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);
    }
}

Кроме того, вам нужен Model Binder Provider в последней версии, это говорит о том, что следует использовать это связующее для этой модели

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. Поэтому для других, которые, как и я, находят ModelBinder'ов немного смущающими, я предлагаю следующий фрагмент jQuery, который обрезает поля ввода перед отправкой формы.

    $('form').submit(function () {
        $(this).find('input:text').each(function () {
            $(this).val($.trim($(this).val()));
        })
    });
Эрик Нельсон
источник
1
2 вещи: 1 - кэшировать ваши клиентские объекты (например, $ (this)), 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, если вас действительно интересуют только строковые значения, но это не должно иметь значения, потому что все, что входит, в основном является строкой.

rudimenter
источник
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();
});
Bassem
источник
0

Было много постов, предлагающих атрибутивный подход. Вот пакет, который уже имеет атрибут обрезки и многие другие: 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 (), user.UserName будет преобразовано в m@x_speed.01!.

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

roydukkey
источник
0

Я разместил это в другой теме. В asp.net core 2 я пошел в другом направлении. Вместо этого я использовал фильтр действий. В этом случае разработчик может установить его глобально или использовать в качестве атрибута для действий, которые он / она хочет применить для обрезки строки. Этот код выполняется после привязки модели и может обновлять значения в объекте модели.

Вот мой код, сначала создайте фильтр действий:

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();
}
Маркос Агиар
источник