ASP.NET MVCを使用していますが、すべてのユーザーが入力した文字列フィールドを、データベースに挿入する前にトリミングする必要があります。また、多くのデータ入力フォームがあるため、ユーザーが指定したすべての文字列値を明示的にトリミングするのではなく、すべての文字列をトリミングするエレガントな方法を探しています。人々が文字列をどのように、いつトリミングするかを知りたいです。
おそらくカスタムモデルバインダーを作成し、そこにある文字列値をトリミングすることを考えました...そのようにして、トリミングロジックはすべて1か所に含まれます。これは良いアプローチですか?これを行うコードサンプルはありますか?
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の回答に対する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]属性を使用して、トリミングから除外するプロパティをマークできます。
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();
}
}
_
string
sをバインドするときにモデルバインダーを使用するには、_Global.asax.cs
_ファイルのApplication_Start()
のどこかにこの行を含める必要があります。
_ModelBinders.Binders.Add(typeof(string), new TrimStringModelBinder());
_
デフォルトのモデルバインダーをオーバーライドするよりも、このようなモデルバインダーを使用する方がよいことがわかります。これは、string
をバインドするときに常に使用されるためです。モデルクラス。ただし、ここでの他の回答が示唆するようにデフォルトのモデルバインダーをオーバーライドすると、モデルのプロパティをバインドするときにonlyが機能し、string
があるときにnotが機能しますアクションメソッドの引数として
編集:コメント者が、フィールドを検証すべきでない状況への対処について尋ねました。私の元の答えは、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でこれはうまくいきました。コントローラーとJSON入力で[FromBody]
属性を使用しています。 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)(@Antonのオプトアウトの「NoTrim」の例ではなく)オプトインの「StringTrim」属性メカニズムを使用します。
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からそのプロパティを完全にブロックします!これは、すべての文字列タイプをバインドするように登録されているため、デフォルトのバインダーはそれを実行しないように見えます(私のテストでは)。
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());
});
上記の優れた回答とコメントを読んで、ますます混乱している間に、私は突然jQueryソリューションがあるかどうかと思いました。私のようにModelBindersが少し戸惑うと思う他の人のために、フォームが送信される前に入力フィールドをトリミングする次のjQueryスニペットを提供します。
$('form').submit(function () {
$(this).find('input:text').each(function () {
$(this).val($.trim($(this).val()));
})
});
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())
パーティーに遅れましたが、ビルトイン値プロバイダーの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());
...
}
私は解決策に同意しません。 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でフィルタリングしますが、入ってくるものはすべて基本的に文字列なので、問題ではありません。
ASP.NET Coreの場合、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
ロジックのコピーと貼り付けですが、これを避けるためのフックはないようです。
属性アプローチを示唆する多くの投稿がありました。以下に、すでに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
とともに使用できます。