187

ASP.NET MVC を使用していますが、ユーザーが入力したすべての文字列フィールドを、データベースに挿入する前にトリミングしたいと考えています。私は多くのデータ入力フォームを持っているので、ユーザーが指定したすべての文字列値を明示的にトリミングするのではなく、すべての文字列をトリミングするエレガントな方法を探しています。人々がいつ、どのように弦を整えているのか知りたいです。

カスタム モデル バインダーを作成し、そこにある文字列値をトリミングすることを考えました。そうすれば、すべてのトリミング ロジックが 1 か所に含まれます。これは良いアプローチですか?これを行うコード サンプルはありますか?

4

16 に答える 16

79

これは @takepara と同じ解像度ですが、DefaultModelBinder ではなく IModelBinder であるため、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

于 2011-05-28T20:39:26.837 に答える
47

@takeparaの回答に対する1つの改善。

プロジェクトの一部:

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] 属性を使用して、トリミングから除外するプロパティをマークできます。

于 2011-05-30T19:38:18.213 に答える
20

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();
    }
}
于 2018-03-28T03:37:26.753 に答える
13

@takeparaの回答の別の変形ですが、ひねりが異なります:

1)オプトインの「StringTrim」属性メカニズムを好みます(@Antonのオプトアウトの「NoTrim」の例ではなく)。

2) ModelState が正しく入力され、デフォルトの検証/受け入れ/拒否パターンが通常どおり使用できるようにするには、SetModelValue への追加の呼び出しが必要です。つまり、TryUpdateModel(model) を適用し、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 から完全にブロックされます。これは、すべての文字列型をバインドするように登録されているためです。そのため、(私のテストでは) デフォルトのバインダーはそれを実行しないようです。

于 2013-12-03T15:24:15.083 に答える
8

ASP.NET Core 1.0 でこれを行う方法を探している人のための追加情報。ロジックはかなり変わりました。

私はそれを行う方法についてブログ投稿を書きました。それは物事をもう少し詳しく説明しています

したがって、ASP.NET Core 1.0 ソリューション:

実際のトリミングを行うモデル バインダー

public class TrimmingModelBinder : ComplexTypeModelBinder  
{
    public TrimmingModelBinder(IDictionary propertyBinders) : base(propertyBinders)
    {
    }

    protected override void SetProperty(ModelBindingContext bindingContext, string modelName, ModelMetadata propertyMetadata, ModelBindingResult result)
    {
        if(result.Model is string)
        {
            string resultStr = (result.Model as string).Trim();
            result = ModelBindingResult.Success(resultStr);
        }

        base.SetProperty(bindingContext, modelName, propertyMetadata, result);
    }
}

また、最新バージョンのモデル バインダー プロバイダーが必要です。これは、このバインダーをこのモデルに使用する必要があることを示しています。

public class TrimmingModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType)
        {
            var propertyBinders = new Dictionary();
            foreach (var property in context.Metadata.Properties)
            {
                propertyBinders.Add(property, context.CreateBinder(property));
            }

            return new TrimmingModelBinder(propertyBinders);
        }

        return null;
    }
}

次に、Startup.cs に登録する必要があります。

 services.AddMvc().AddMvcOptions(options => {  
       options.ModelBinderProviders.Insert(0, new TrimmingModelBinderProvider());
 });
于 2015-08-22T16:05:15.290 に答える
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 でフィルター処理しますが、基本的にはすべてが文字列であるため問題にはなりません。

于 2012-10-02T15:42:03.850 に答える
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

于 2018-03-14T01:11:46.503 に答える