데이터 입력 후 문자열을 자르는 가장 좋은 방법입니다. 커스텀 모델 바인더를 만들어야합니까?


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; value = string.IsNullOrEmpty (stringValue)? stringValue : stringValue.Trim ();
Simon_Weaver

4
이로 인해 더 많은 투표가 필요합니다. 실제로 MVC 팀이 기본 모델 바인더로 이것을 구현하지 않았다는 것에 놀랐습니다 ...
Portman

1
@BreckFresen 같은 문제가 있었으므로 BindModel 메서드를 재정의하고 bindingContext.ModelType에서 문자열을 확인한 다음 문자열을 잘라야합니다.
Kelly

3
DefaultModelBinder에 대한 애매한 점이있는 사람은 System.Web.Mvc를 사용하고 있습니다.
GeoffM

3
type="password"입력을 그대로 유지 하기 위해 어떻게 이것을 수정 하시겠습니까?
Extragorey 2016 년

77

이것은 @takepara와 같은 해상도이지만 DefaultModelBinder 대신 IModelBinder와 같이 global.asax에 modelbinder를 추가하는 것이

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;
Marius Schulz

6
[ValidateInput (false)] 컨트롤러 속성은 처리하지 않습니다. "위험한 요청 ..."예외가 발생합니다.
CodeGrue

2
'위험한 요청 ...'예외를 받고있는 사람들은이 기사를 참조하십시오 -blogs.taiga.nl/martijn/2011/09/29/…
GurjeetSinghDB

2
내 동료가 모든 종류의 문제를 일으킨 변형을 구현했습니다. issues.umbraco.org/issue/U4-6665 항상 서로를 선호하지 않고 null과 비어있는 것을 적절하게 반환하는 것이 좋습니다 (귀하의 경우, 값이 빈 문자열 인 경우에도 항상 null을 반환합니다.
Nicholas Westby

2
이것은 위에서 언급 한 CodeGrue [AllowHtml]와 함께 모델 속성에 대한 속성 을 깨뜨리는 것 같습니다[ValidateInput(false)]
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
@Korayem의 IModelBinder 접근 방식을 사용할 때 어떻게이 속성과 같은 것을 구현할 수 있습니까? 일부 응용 프로그램에서는 다른 (타사) 모델 바인더 (예 : S # arp Archeticture 's)를 사용합니다. 프로젝트간에 공유되는 개인 DLL로 이것을 작성하고 싶습니다. 따라서 IModelBinder 접근 방식이어야합니다.
Carl Bussema

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 (널)이지만 데이터베이스에 빈 문자열이 필요한 경우 (그렇지 않은 경우) 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바인딩 할 때 모델 바인더를 사용하는 파일 string들 :

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

기본 모델 바인더를 재정의하는 대신 이와 같은 모델 바인더를 사용하는 것이 좋습니다. 그러면 string메서드 인수 또는 모델 클래스의 속성으로 바인딩 여부에 관계 없이을 바인딩 할 때마다 사용되기 때문 입니다. 다른 답변은 여기 제안으로 기본 모델 바인더를 오버라이드 (override)하는 경우 그 것이다 그러나, 단지 모델의 속성을 바인딩 작업을 할 때, 아니 당신이있을 때 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();
    }
}

다시 한 번 위의 주석을 참조하십시오. 이 예는 IUnvalidatedValueProvider의 skipValidation 요구 사항을 처리하지 않습니다.
Aaron Hudon

@adrian, IModelBinder 인터페이스에는 반환 유형이 bool 인 BindModel 메서드 만 있습니다. 그렇다면 여기서 리턴 타입 객체를 어떻게 사용 했습니까?
Magendran V

@MagendranV 어떤 인터페이스를보고 있는지 잘 모르겠지만이 답변은 ASP.NET MVC 5의 IModelBinder를 기반으로 객체를 반환합니다. docs.microsoft.com/en-us/previous-versions/aspnet
아드리안

1
@AaronHudon 나는 건너 뛰는 유효성 검사를 처리하는 예제를 포함하도록 답변을 업데이트했습니다.
adrian

비밀번호 필드에 올바른 데이터 유형 세트 (예 : [DataType (DataType.Password)])가있는 경우 다음 필드를 트리밍하지 않도록 마지막 행을 다음과 같이 업데이트 할 수 있습니다. return string.IsNullOrWhiteSpace (attemptedValue) || bindingContext.ModelMetadata.DataTypeName == "비밀번호"? triesedValue : tryedValue.Trim ();
trfletch 2015 년

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

귀하의 솔루션은 잘 작동합니다! 감사. IModelBinderProvider를 사용하여 .Net Core에 대한 다른 솔루션을 시도했지만 작동하지 않았습니다.
Cedric Arnould

startup.cs를 제외하고 Model에서 [JsonConverter (typeof (TrimmingStringConverter))]로도 사용할 수 있습니다. Btw. .Insert () 대신 .Add ()를 사용하는 이유가 있습니까?
wast

@wast 다른 변환기보다 먼저 실행되도록 .Add () 대신 .Insert ()를 수행 한 것 같습니다. 지금 기억이 안나
Kai G

DefaultContractResolver에 비해 이것의 성능 오버 헤드는 무엇입니까?
Maulik Modi

13

@takepara의 답변의 또 다른 변형이지만 다른 변형이 있습니다.

1) 옵트 인 "StringTrim"속성 메커니즘 (@Anton의 옵트 아웃 "NoTrim"예제 대신)을 선호합니다.

2) ModelState가 올바르게 채워지고 기본 유효성 검사 / 수락 / 거부 패턴이 정상적으로 사용되도록 (예 : TryUpdateModel (model) 적용 및 ModelState.Clear () 모든 변경 사항을 적용하려면 SetModelValue에 대한 추가 호출이 필요합니다.

이것을 엔티티 / 공유 라이브러리에 넣으십시오.

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

그것은 나를 위해 작동하지 않았다, 내 모든 분야는 지금 null
Cedric Arnould

5

위의 훌륭한 답변과 의견을 읽고 점점 혼란스러워지면서 갑자기 jQuery 솔루션이 있는지 궁금합니다. 따라서 나와 같은 ModelBinders를 조금 당황스럽게 생각하는 다른 사람들을 위해 양식을 제출하기 전에 입력 필드를 자르는 다음 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"코드의 목적은 무엇입니까?
Brad

SimpleTypeModelBinderProvider동일한 인덱스를 유지 관리 하여 사용자 지정 공급자를 대체하려고합니다 .
Vikash Kumar

전체 설명은 여기에서 확인할
Vikash Kumar

3

파티에 늦었지만 다음은 빌트인 skipValidation가치 제공 업체 의 요구 사항 을 처리해야하는 경우 MVC 5.2.3에 필요한 조정 사항을 요약 한 것입니다 .

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

나는 해결책에 동의하지 않는다. SetProperty의 데이터를 ModelState로 채울 수도 있으므로 GetPropertyValue를 재정의해야합니다. 입력 요소에서 원시 데이터를 포착하려면 다음을 작성하십시오.

 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 코어 의 대체 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에서는 작동하지 않습니다. 컨트롤러 작업에서 얻은 모델 객체의 모든 속성이 null입니다. "SetProperty"메소드는 더 신경을 쓴다.
Waldo

나를 위해 일하지 않았지만 내 재산의 시작 부분에는 여전히 공간이 있습니다.
Cedric Arnould

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

속성 접근법을 제안하는 많은 게시물이 있습니다. 다음은 이미 다듬기 속성이있는 패키지입니다. 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.


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();
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.