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

ASP.NET MVC를 사용하고 있으며 모든 사용자가 입력 한 문자열 필드가 데이터베이스에 삽입되기 전에 잘 리기를 원합니다. 그리고 많은 데이터 입력 양식이 있기 때문에 모든 사용자 제공 문자열 값을 명시 적으로 트리밍하는 대신 모든 문자열을 트리밍하는 우아한 방법을 찾고 있습니다. 사람들이 줄을 자르는 방법과시기를 알고 싶습니다.

아마도 사용자 정의 모델 바인더를 만들고 문자열 값을 트리밍하는 방법에 대해 생각했습니다. 그러면 모든 트리밍 로직이 한 곳에 포함됩니다. 이것이 좋은 접근입니까? 이를 수행하는 코드 샘플이 있습니까?



답변

  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 이벤트를 설정하십시오.


답변

이것은 @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


답변

@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] 속성을 사용하여 트리밍에서 제외 할 속성을 표시 할 수 있습니다.


답변

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


답변

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


답변

@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에서 해당 속성을 모두 차단합니다! 이것은 모든 문자열 유형을 바인딩하는 것으로 등록되어 있기 때문에 (내 테스트에서) 기본 바인더가 대신하지 않는 것으로 나타납니다.


답변

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