A melhor maneira de cortar seqüências de caracteres após a entrada de dados. Devo criar um fichário de modelo personalizado?

172

Estou usando o ASP.NET MVC e gostaria que todos os campos digitados pelo usuário fossem aparados antes de serem inseridos no banco de dados. E como tenho muitos formulários de entrada de dados, estou procurando uma maneira elegante de aparar todas as seqüências de caracteres, em vez de aparar explicitamente o valor de cada usuário fornecido. Estou interessado em saber como e quando as pessoas estão cortando as cordas.

Pensei em talvez criar um fichário de modelo personalizado e aparar qualquer valor de string lá ... dessa forma, toda a minha lógica de aparar está contida em um só lugar. Será esta uma boa abordagem? Existem exemplos de código que fazem isso?

Johnny Oshika
fonte

Respostas:

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

E esse código?

ModelBinders.Binders.DefaultBinder = new TrimModelBinder();

Defina o evento global.asax Application_Start.

takepara
fonte
3
eu apenas substituiria o código no mais interno {} por este por questões de brevidade: string stringValue = (string) value; valor = string.IsNullOrEmpty (stringValue)? stringValue: stringValue.Trim ();
Simon_Weaver
4
Isso merece mais votos. Na verdade, estou surpreso que a equipe MVC não tenha optado por implementar isso no fichário padrão do modelo ...
Portman
1
@BreckFresen Eu tive o mesmo problema, você precisará substituir o método BindModel e verificar o bindingContext.ModelType para obter uma string e, se necessário, aparar.
Kelly
3
Para alguém como eu, obtendo uma ambiguidade no DefaultModelBinder, o correto está usando o System.Web.Mvc.
21816 GeoffM
3
Como você modificaria isso para deixar as type="password"entradas intocadas?
Extragorey
77

Esta é a @takepara com a mesma resolução, mas como um IModelBinder em vez de DefaultModelBinder, de modo que a adição do modelbinder no global.asax seja feita através de

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

A classe:

public class TrimModelBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext,
    ModelBindingContext bindingContext)
    {
        ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
        if (valueResult== null || valueResult.AttemptedValue==null)
           return null;
        else if (valueResult.AttemptedValue == string.Empty)
           return string.Empty;
        return valueResult.AttemptedValue.Trim();
    }
}

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

Korayem
fonte
1
+1 para uma solução limpa! Você pode melhorar ainda mais a legibilidade do seu código alterando a ordem das returninstruções e negando a condição:if (valueResult == null || string.IsNullOrEmpty(valueResult.AttemptedValue)) return null;
Marius Schulz
6
Isso não lida com o atributo do controlador [ValidateInput (false)]. Isso causa a exceção "Solicitação perigosa ...".
CodeGrue 6/09/12
2
Para aqueles que estão recebendo a exceção 'Solicitação perigosa ...', consulte este artigo - blogs.taiga.nl/martijn/2011/09/29/…
GurjeetSinghDB
2
Um colega meu implementou uma variação disso que causou todo tipo de problemas: issues.umbraco.org/issue/U4-6665 Eu recomendaria retornar nulo e vazio conforme apropriado, em vez de sempre preferir um ao outro (no seu caso, você sempre retorne nulo mesmo quando o valor é uma sequência vazia).
Nicholas Westby
2
Este parece quebrar o [AllowHtml]atributo em propriedades modelo (juntamente com o [ValidateInput(false)]como CodeGrue mencionado acima
Mingwei Samuel
43

Uma melhoria na resposta do @takepara.

Em algum lugar no projeto:

public class NoTrimAttribute : Attribute { }

Na mudança de classe TrimModelBinder

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

para

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

e você pode marcar propriedades a serem excluídas do corte com o atributo [NoTrim].

Anton
fonte
1
Como podemos implementar algo como esse atributo ao usar a abordagem IModelBinder do @Korayem? Em alguns aplicativos, eu uso um fichário de modelo diferente (de terceiros) (por exemplo, S # arp Archeticture). Eu gostaria de escrever isso em uma DLL privada compartilhada entre projetos, por isso precisa ser uma abordagem do IModelBinder.
Carl Bussema 02/10/12
1
@CarlBussema Aqui está uma pergunta sobre como acessar atributos de dentro de um IModelBinder. stackoverflow.com/questions/6205176
Mac Attack
4
Eu acho que é uma ótima adição, mas eu substituiria o .Cast<object>().Any(a => a.GetType() == typeof(NoTrimAttribute))por .OfType<NoTrimAttribute>().Any(). Apenas um pouco mais limpo.
DBueno
Coloquei meus atributos em um assembly compartilhado porque, como nas anotações de dados, esses atributos têm um escopo de uso mais amplo do que apenas o MVC, por exemplo, camada de negócios, clientes. Outra observação, o "DisplayFormatAttribute (ConvertEmptyStringToNull)" controla se a sequência aparada será salva como nula ou vazia. O padrão é verdadeiro (nulo), o que eu gosto, mas no caso de você precisar de cadeias vazias no banco de dados (espero que não), você pode configurá-lo como falso para obter isso. Enfim, tudo isso é bom, espero que o MS estenda seus atributos para incluir recorte e preenchimento e muitas outras coisas comuns como essa.
Tony Muro
17

Com as melhorias no C # 6, agora você pode escrever um fichário de modelo muito compacto que cortará todas as entradas de 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();
    }
}

Você precisa incluir esta linha em algum lugar do Application_Start()seu Global.asax.csarquivo para usar o fichário do modelo ao ligar strings:

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

Acho que é melhor usar um fichário de modelo como esse, em vez de substituir o fichário de modelo padrão, porque ele será usado sempre que você estiver vinculando a string, seja diretamente como argumento de método ou como propriedade em uma classe de modelo. No entanto, se você substituir o fichário do modelo padrão, como sugerem outras respostas, isso funcionará apenas ao vincular propriedades nos modelos, e não quando você tiver umstring argumento para um método de ação

Editar: um comentarista perguntou sobre como lidar com a situação em que um campo não deve ser validado. Minha resposta original foi reduzida para lidar apenas com a pergunta que o OP havia feito, mas para os interessados, você pode lidar com a validação usando o seguinte fichário de modelo estendido:

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
fonte
Mais uma vez, veja os comentários acima. Este exemplo não trata do requisito skipValidation de IUnvalidatedValueProvider.
Aaron Hudon
@adrian, A interface IModelBinder possui apenas o método BindModel com o tipo de retorno bool. Então, como você usou o objeto de tipo de retorno aqui?
Magendran V
@ MagendranV Não tenho certeza de qual interface você está vendo, mas esta resposta é baseada no IModelBinder no ASP.NET MVC 5, que retorna um objeto: docs.microsoft.com/en-us/previous-versions/aspnet /…
adrian
1
@AaronHudon Eu atualizei a minha resposta para incluir um exemplo para lidar pular validação
adrian
Se os campos da sua senha tiverem o tipo de dados correto definido (por exemplo, [DataType (DataType.Password)])), você poderá atualizar a última linha da seguinte maneira, para que não apare esses campos: return string.IsNullOrWhiteSpace (tryedValue) || bindingContext.ModelMetadata.DataTypeName == "Senha"? tryedValue: tryedValue.Trim ();
trfletch
15

No ASP.Net Core 2, isso funcionou para mim. Estou usando o [FromBody]atributo em meus controladores e na entrada JSON. Para substituir a manipulação de string na desserialização JSON, registrei meu próprio JsonConverter:

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

E este é o conversor:

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
fonte
Sua solução funciona bem! Obrigado. Tentei as outras soluções para o .Net Core usando o IModelBinderProvider, não funcionou.
precisa
Exceto em startup.cs, ele também pode ser usado no modelo como [JsonConverter (typeof (TrimmingStringConverter))]. Btw. existe um motivo para usar .Insert () em vez disso .Add ()?
wast
@ wast Acho que acabei de inserir .Insert () em vez de .Add () para garantir que seja executado antes de outros conversores. Não me lembro agora.
Kai L
Qual é a sobrecarga de desempenho disso no DefaultContractResolver?
Maulik Modi 09/06
13

Outra variante da resposta da @ takepara, mas com um toque diferente:

1) Prefiro o mecanismo de atributo "StringTrim" de opt-in (em vez do exemplo "NoTrim" de opt-out de @Anton).

2) É necessária uma chamada adicional para SetModelValue para garantir que o ModelState seja preenchido corretamente e que o padrão padrão de validação / aceitação / rejeição possa ser usado normalmente, por exemplo, TryUpdateModel (model) para aplicar e ModelState.Clear () para aceitar todas as alterações.

Coloque isso em sua entidade / biblioteca compartilhada:

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

Então isso em seu aplicativo / biblioteca MVC:

/// <summary>
/// MVC model binder which trims string values decorated with the <see cref="StringTrimAttribute"/>.
/// </summary>
public class StringTrimModelBinder : IModelBinder
{
    /// <summary>
    /// Binds the model, applying trimming when required.
    /// </summary>
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        // Get binding value (return null when not present)
        var propertyName = bindingContext.ModelName;
        var originalValueResult = bindingContext.ValueProvider.GetValue(propertyName);
        if (originalValueResult == null)
            return null;
        var boundValue = originalValueResult.AttemptedValue;

        // Trim when required
        if (!String.IsNullOrEmpty(boundValue))
        {
            // Check for trim attribute
            if (bindingContext.ModelMetadata.ContainerType != null)
            {
                var property = bindingContext.ModelMetadata.ContainerType.GetProperties()
                    .FirstOrDefault(propertyInfo => propertyInfo.Name == bindingContext.ModelMetadata.PropertyName);
                if (property != null && property.GetCustomAttributes(true)
                    .OfType<StringTrimAttribute>().Any())
                {
                    // Trim when attribute set
                    boundValue = boundValue.Trim();
                }
            }
        }

        // Register updated "attempted" value with the model state
        bindingContext.ModelState.SetModelValue(propertyName, new ValueProviderResult(
            originalValueResult.RawValue, boundValue, originalValueResult.Culture));

        // Return bound value
        return boundValue;
    }
}

Se você não definir o valor da propriedade no fichário, mesmo quando não desejar alterar nada, bloqueará completamente essa propriedade do ModelState! Isso ocorre porque você está registrado como vinculando todos os tipos de cadeias, então parece (nos meus testes) que o fichário padrão não fará isso por você.

Tony Wall
fonte
7

Informações adicionais para quem procura como fazer isso no ASP.NET Core 1.0. A lógica mudou bastante.

Eu escrevi um post sobre como fazê-lo , explica as coisas um pouco mais detalhadamente

Então, a solução ASP.NET Core 1.0:

Fichário de modelo para fazer o corte real

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

Além disso, você precisa do Model Binder Provider na versão mais recente, isso indica que esse fichário deve ser usado para este modelo

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

Em seguida, ele deve ser registrado no Startup.cs

 services.AddMvc().AddMvcOptions(options => {  
       options.ModelBinderProviders.Insert(0, new TrimmingModelBinderProvider());
 });
Tuukka Lindroos
fonte
Ele não trabalho para mim também, todos os meus campos são nulos agora
Cedric Arnould
5

Ao ler as excelentes respostas e comentários acima e ficar cada vez mais confuso, de repente pensei: ei, me pergunto se existe uma solução jQuery. Portanto, para outras pessoas que, como eu, acham o ModelBinders um pouco desconcertante, ofereço o seguinte trecho de jQuery que corta os campos de entrada antes que o formulário seja enviado.

    $('form').submit(function () {
        $(this).find('input:text').each(function () {
            $(this).val($.trim($(this).val()));
        })
    });
Eric Nelson
fonte
1
2 coisas: 1 - Coloque em cache os objetos do cliente (como $ (this)), 2 - Você nunca pode confiar nas entradas do cliente, mas definitivamente no código do servidor. Portanto, sua resposta é uma conclusão para as respostas de código servidor :)
graumanoz
5

No caso do MVC Core

Encadernador:

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

Fornecedor:

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

Função de registro:

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

Registro:

service.AddMvc(option => option.AddStringTrimmingProvider())
Vikash Kumar
fonte
+1. Exatamente o que eu estava procurando. Qual é o objetivo do código "binderToFind" na função Registro?
Brad
Estou apenas tentando colocar o provedor personalizado com o fallback SimpleTypeModelBinderProvider, mantendo o mesmo índice.
Vikash Kumar
A descrição completa pode ser encontrada aqui vikutech.blogspot.in/2018/02/…
Vikash Kumar
3

Atrasado para a parte, mas a seguir há um resumo dos ajustes necessários para o MVC 5.2.3, se você deseja lidar com os skipValidationrequisitos dos provedores de valor integrados.

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

Eu discordo da solução. Você deve substituir GetPropertyValue porque os dados para SetProperty também podem ser preenchidos pelo ModelState. Para capturar os dados brutos dos elementos de entrada, escreva isto:

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

}

Filtre por propertyDescriptor PropertyType se você realmente estiver interessado apenas em valores de sequência, mas isso não deve importar, porque tudo o que entra é basicamente uma sequência.

rudimentador
fonte
2

Para o ASP.NET Core , substitua oComplexTypeModelBinderProvider por um provedor que apara seqüências de caracteres.

No seu ConfigureServicesmétodo de código de inicialização , adicione este:

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

Defina TrimmingModelBinderProviderassim:

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

A parte mais feia disso é a cópia e a colagem da GetBinderlógica ComplexTypeModelBinderProvider, mas não parece haver nenhum gancho para permitir que você evite isso.

Edward Brey
fonte
Não sei por que, mas não funciona no ASP.NET Core 1.1.1. Todas as propriedades do objeto de modelo que recebo na ação do controlador são nulas. O método "SetProperty" é chamado de Nerver.
Waldo
Não funcionou para mim, o espaço no início da minha propriedade ainda está lá.
precisa
2

Criei provedores de valor para aparar os valores dos parâmetros da string de consulta e os valores do formulário. Isso foi testado com o ASP.NET Core 3 e funciona perfeitamente.

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

Em seguida, registre as fábricas de provedores de valor na ConfigureServices()função em 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
fonte
0

Houve muitas postagens sugerindo uma abordagem de atributo. Aqui está um pacote que já possui um atributo trim e muitos outros: Dado.ComponentModel.Mutations ou 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();

Após a chamada para Mutate (), user.UserName será alterado para m@x_speed.01!.

Este exemplo irá aparar espaços em branco e colocar a string em minúsculas. Não introduz validação, mas System.ComponentModel.Annotationspode ser usado ao lado Dado.ComponentModel.Mutations.

roydukkey
fonte
0

Eu postei isso em outro tópico. No asp.net core 2, fui em uma direção diferente. Eu usei um filtro de ação. Nesse caso, o desenvolvedor pode configurá-lo globalmente ou usar como um atributo para as ações que deseja aplicar o corte de sequência. Esse código é executado após a ligação do modelo e pode atualizar os valores no objeto do modelo.

Aqui está o meu código, primeiro crie um filtro de ação:

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

Para usá-lo, registre-se como filtro global ou decore suas ações com o atributo TrimInputStrings.

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