データ入力後に文字列をトリムする最良の方法。カスタムモデルバインダーを作成する必要がありますか?


172

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

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

回答:


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を確認し、そうである場合はトリミングする必要があります。
ケリー

3
私のようなDefaultModelBinderのあいまいさがわかる人にとっては、正しいものはSystem.Web.Mvcを使用することです。
GeoffM 2016年

3
これをどのように変更して、type="password"入力を変更しないようにしますか?
Extragorey 2018年

77

これは@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


1
クリーンなソリューションの+1!あなたも、それ以上のオーダー変更することで、コードの可読性を向上させることができreturn文を、条件を否定することによって:if (valueResult == null || string.IsNullOrEmpty(valueResult.AttemptedValue)) return null;
マリウス・シュルツ

6
これは[ValidateInput(false)]コントローラー属性を処理しません。「危険なリクエスト...」例外が発生します。
CodeGrue 2012

2
「危険な要求...」の例外を取得している人のために、この記事を参照してください- blogs.taiga.nl/martijn/2011/09/29/...
GurjeetSinghDB

2
私の同僚はこれのバリエーションを実装し、あらゆる種類の問題を引き起こしました:issues.umbraco.org/issue/U4-6665 私は常に他のものを優先するのではなく、適切にnullと空を返すことをお勧めします(あなたのケースでは、値が空の文字列であっても常にnullを返します)。
Nicholas Westby

2
これは[AllowHtml]、モデルプロパティの属性を壊すようです([ValidateInput(false)]上記のようにCodeGrue と一緒に
Mingwei Samuel

43

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


1
@KorayemによるIModelBinderアプローチを使用する場合、この属性のようなものをどのように実装できますか?一部のアプリケーションでは、別の(サードパーティの)モデルバインダー(S#arp Archetictureなど)を使用しています。これをプロジェクト間で共有されるプライベートDLLに記述したいので、IModelBinderアプローチにする必要があります。
Carl Bussema、2012年

1
@CarlBussema IModelBinder内から属性にアクセスすることについての質問です。stackoverflow.com/questions/6205176
Mac攻撃

4
私はそれは素晴らしい加えてだと思うが、私は交換するでしょう.Cast<object>().Any(a => a.GetType() == typeof(NoTrimAttribute)).OfType<NoTrimAttribute>().Any()。ほんの少しクリーナー。
DBueno

データ注釈と同様に、そのような属性はビジネスティア、クライアントなど、MVCだけよりも広い使用範囲を持っているため、私は属性を共有アセンブリに配置しました。もう1つの観察として、「DisplayFormatAttribute(ConvertEmptyStringToNull)」は、トリミングされた文字列をnullまたは空の文字列のどちらとして保存するかを制御します。デフォルトは私が好きなtrue(null)ですが、データベースに空の文字列が必要な場合(できれば不要)は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();
    }
}

sをバインドするときにモデルバインダーを使用するにApplication_Start()は、Global.asax.csファイルのどこかにこの行を含める必要がありますstring

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

デフォルトのモデルバインダーをオーバーライドするよりも、このようにモデルバインダーを使用する方が良いと思います。これはstring、メソッドの引数として直接であれ、モデルクラスのプロパティとしてであれ、をバインドするときに常に使用されるためです。ただし、ここでの他の回答が示唆しているようにデフォルトのモデルバインダーをオーバーライドすると、アクションメソッドの引数としてaがある場合ではなく、モデルのプロパティをバインドするときにのみ機能します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要件を処理しません。
アーロンヒュードン2017年

@ adrian、IModelBinderインターフェイスには、戻り値の型がboolのメソッドBindModelしかありません。次に、ここで戻り型オブジェクトをどのように使用しましたか?
Magendran V

@MagendranVどのインターフェイスを見ているのかはわかりませんが、この回答は、オブジェクトを返すASP.NET MVC 5のIModelBinderに基づいています:docs.microsoft.com/en-us/previous-versions/aspnet /…
エイドリアン

1
@AaronHudon私は私の回答を更新して、検証のスキップを処理する例を含めました
エイドリアン

パスワードフィールドに正しいデータ型が設定されている場合(つまり、[DataType(DataType.Password)])、最後の行を次のように更新して、これらのフィールドがトリムされないようにすることができます。bindingContext.ModelMetadata.DataTypeName == "Password"?attemptedValue:attemptedValue.Trim();
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();
    }
}

あなたの解決策はうまくいきます!ありがとう。IModelBinderProviderを使用して.Net Coreの他のソリューションを試しましたが、機能しませんでした。
セドリックアーノールド2018年

startup.csを除いて、モデルでは[JsonConverter(typeof(TrimmingStringConverter))]としても使用できます。ところで .Add()の代わりに.Insert()を使用する理由はありますか?
ワスト

@wast他のコンバータの前に実行されるように、.Add()ではなく.Insert()を実行したと思います。今は思い出せません。
カイG

DefaultContractResolverに対するこれのパフォーマンスオーバーヘッドは何ですか?
Maulik Modi

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からそのプロパティを完全にブロックします。これは、すべての文字列タイプをバインドするように登録されているため、(私のテストでは)デフォルトのバインダーがそれを行わないように見えます。


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

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

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、2018年

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コアの場合

バインダー:

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。まさに私が探していたもの。Registration関数の「binderToFind」コードの目的は何ですか?
ブラッド

私はSimpleTypeModelBinderProvider同じインデックスを維持することで、カスタムプロバイダーをのフォールバックで配置しようとしています。
Vikash Kumar

全体の説明はここで見つけることができますvikutech.blogspot.in/2018/02/...
Vikashクマー

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

私のために働きませんでした、私の財産の最初のスペースはまだそこにあります。
セドリックアーノールド

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

属性アプローチを提案する多くの投稿がありました。以下は、既にtrim属性と他の多くの属性を持っているパッケージです: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コア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.