Cách tốt nhất để cắt chuỗi sau khi nhập dữ liệu. Tôi có nên tạo một chất kết dính mô hình tùy chỉnh?


172

Tôi đang sử dụng ASP.NET MVC và tôi muốn tất cả người dùng nhập các trường chuỗi được cắt bớt trước khi chúng được chèn vào cơ sở dữ liệu. Và vì tôi có nhiều biểu mẫu nhập dữ liệu, tôi đang tìm kiếm một cách thanh lịch để cắt tất cả các chuỗi thay vì cắt xén rõ ràng mọi giá trị chuỗi do người dùng cung cấp. Tôi muốn biết làm thế nào và khi nào mọi người đang cắt dây.

Tôi nghĩ về việc có thể tạo ra một chất kết dính mô hình tùy chỉnh và cắt xén bất kỳ giá trị chuỗi nào ở đó ... theo cách đó, tất cả logic cắt xén của tôi được chứa ở một nơi. Đây có phải là một cách tiếp cận tốt? Có bất kỳ mẫu mã nào làm điều này?

Câu trả lời:


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

Làm thế nào về mã này?

ModelBinders.Binders.DefaultBinder = new TrimModelBinder();

Đặt sự kiện global.asax Application_Start.


3
Tôi chỉ cần thay thế mã trong phần lớn {} bên trong bằng giá trị brevity: string stringValue = (string); value = string.IsNullOrEmpty (stringValue)? chuỗiValue: stringValue.Trim ();
Simon_Weaver

4
Điều này xứng đáng được nâng cao hơn. Tôi thực sự ngạc nhiên khi nhóm MVC không chọn thực hiện điều này trong trình kết dính mô hình mặc định ...
Portman

1
@BreckFresen Tôi gặp vấn đề tương tự, bạn sẽ cần ghi đè phương thức BindModel và kiểm tra ràng buộcContext.ModelType cho một chuỗi sau đó cắt nếu có.
Kelly

3
Đối với bất kỳ ai như tôi nhận được sự mơ hồ trên DefaultModelBinder, thì chính xác là sử dụng System.Web.Mvc.
GeoffM

3
Làm thế nào bạn sẽ sửa đổi điều này để giữ cho type="password"đầu vào không bị ảnh hưởng?
Extragorey

77

Đây là @takepara cùng độ phân giải nhưng với tư cách là IModelBinder thay vì DefaultModelBinder để việc thêm modelbinder trong global.asax được thông qua

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

Lớp:

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

dựa trên bài đăng @haacked: http://haacked.com/archive/2011/03/19/fixing-binding-to-decimals.aspx


1
+1 cho một giải pháp sạch! Bạn có thể cải thiện khả năng đọc mã của mình hơn nữa bằng cách thay đổi thứ tự của các returncâu lệnh và bằng cách phủ nhận điều kiện:if (valueResult == null || string.IsNullOrEmpty(valueResult.AttemptedValue)) return null;
Marius Schulz

6
Điều này không xử lý thuộc tính bộ điều khiển [ValidateInput (false)]. Nó gây ra ngoại lệ "Yêu cầu nguy hiểm ...".
CodeGrue

2
Đối với những người đang nhận được 'Yêu cầu nguy hiểm ...' ngoại lệ, hãy tham khảo bài viết này - blogs.taiga.nl/martijn/2011/09/29/...
GurjeetSinghDB

2
Một đồng nghiệp của tôi đã thực hiện một biến thể của vấn đề này gây ra tất cả các loại vấn đề: problems.umbraco.org/su/U4-6665 Tôi khuyên bạn nên trả về null và trống khi thích hợp thay vì luôn thích cái khác hơn (trong trường hợp của bạn, bạn luôn trả về null ngay cả khi giá trị là một chuỗi rỗng).
Nicholas Westby

2
Điều này dường như phá vỡ [AllowHtml]thuộc tính trên các thuộc tính mô hình (cùng với [ValidateInput(false)]như CodeGrue đã đề cập ở trên
Mingwei Samuel

43

Một cải tiến cho câu trả lời @takepara.

Một phần nào đó trong dự án:

public class NoTrimAttribute : Attribute { }

Trong lớp TrimModelBinder thay đổi

if (propertyDescriptor.PropertyType == typeof(string))

đến

if (propertyDescriptor.PropertyType == typeof(string) && !propertyDescriptor.Attributes.Cast<object>().Any(a => a.GetType() == typeof(NoTrimAttribute)))

và bạn có thể đánh dấu các thuộc tính để loại trừ khỏi việc cắt xén với thuộc tính [NoTrim].


1
Làm thế nào chúng ta có thể thực hiện một cái gì đó giống như thuộc tính này khi sử dụng phương pháp IModelBinder của @Korayem? Trong một số ứng dụng, tôi sử dụng một chất kết dính mô hình (bên thứ ba) khác (ví dụ: S # arp Archeticture's). Tôi muốn viết điều này trong một DLL riêng được chia sẻ giữa các dự án, vì vậy nó cần phải là một cách tiếp cận IModelBinder.
Carl Bussema

1
@CarlBussema Đây là một câu hỏi về việc truy cập các Thuộc tính từ bên trong IModelBinder. stackoverflow.com/questions/6205176
Mac Attack

4
Tôi nghĩ rằng đó là một bổ sung tuyệt vời nhưng tôi sẽ thay thế .Cast<object>().Any(a => a.GetType() == typeof(NoTrimAttribute))bằng .OfType<NoTrimAttribute>().Any(). Chỉ cần sạch hơn một chút.
DBueno

Tôi đặt các thuộc tính của mình trong một cụm được chia sẻ bởi vì, như với các chú thích dữ liệu, các thuộc tính đó có phạm vi sử dụng rộng hơn so với chỉ MVC, ví dụ: tầng doanh nghiệp, máy khách. Một quan sát khác, "DisplayFormatAttribution (ConvertEmptyStringToNull)" kiểm soát xem chuỗi được cắt sẽ được lưu dưới dạng null hay chuỗi rỗng. Mặc định là true (null) mà tôi thích nhưng trong trường hợp bạn yêu cầu các chuỗi rỗng trong cơ sở dữ liệu của bạn (hy vọng là không), bạn có thể đặt nó sai để có được điều đó. Dù sao, đây là tất cả những thứ tốt, hy vọng MS mở rộng các thuộc tính của họ để bao gồm cắt tỉa và đệm và nhiều thứ phổ biến khác như thế.
Tony Wall

17

Với những cải tiến trong C # 6, giờ đây bạn có thể viết một chất kết dính mô hình rất nhỏ gọn sẽ cắt tất cả các đầu vào chuỗi:

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

Bạn cần bao gồm dòng này ở đâu đó trong tệp Application_Start()của mình Global.asax.csđể sử dụng chất kết dính mô hình khi liên kết strings:

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

Tôi thấy tốt hơn là sử dụng một chất kết dính mô hình như thế này, thay vì ghi đè lên chất kết dính mô hình mặc định, bởi vì sau đó nó sẽ được sử dụng bất cứ khi nào bạn ràng buộc một string, cho dù đó là đối số phương thức hoặc như một thuộc tính trên lớp mô hình. Tuy nhiên, nếu bạn ghi đè lên chất kết dính mô hình mặc định như các câu trả lời khác ở đây gợi ý, điều đó sẽ chỉ hoạt động khi ràng buộc các thuộc tính trên các mô hình, chứ không phải khi bạn có một stringđối số cho một phương thức hành động

Chỉnh sửa: một người bình luận hỏi về việc xử lý tình huống khi một trường không được xác nhận. Câu trả lời ban đầu của tôi đã được giảm xuống để giải quyết chỉ với câu hỏi mà OP đã đặt ra, nhưng đối với những người quan tâm, bạn có thể xử lý xác nhận bằng cách sử dụng chất kết dính mô hình mở rộng sau đây:

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

Một lần nữa, xem ý kiến ​​trên. Ví dụ này không xử lý yêu cầu SkipValidation của IUnvalidatedValueProvider.
Aaron Hudon

@adrian, Giao diện IModelBinder chỉ có phương thức BindModel với kiểu bool trả về. Sau đó, làm thế nào bạn đã sử dụng với đối tượng loại trả lại ở đây?
Magendran V

@MagendranV Tôi không chắc bạn đang xem giao diện nào, nhưng câu trả lời này dựa trên IModelBinder trong ASP.NET MVC 5, trả về một đối tượng: docs.microsoft.com/en-us/preingly-versions/aspnet /
Lôi

1
@AaronHudon Tôi đã cập nhật câu trả lời của mình để bao gồm một ví dụ để xử lý bỏ qua xác thực
adrian

Nếu các trường mật khẩu của bạn có bộ kiểu dữ liệu chính xác (ví dụ [DataType (DataType.Password)]) thì bạn có thể cập nhật dòng cuối cùng như sau để nó không cắt các trường này: return string.IsNullOrWhiteSpace (vededValue) || bindContext.ModelMetadata.DataTypeName == "Mật khẩu"? đã cố gắngValue: đã cố gắngValue.Trim ();
trfletch

15

Trong ASP.Net Core 2, điều này làm việc cho tôi. Tôi đang sử dụng [FromBody]thuộc tính trong bộ điều khiển và đầu vào JSON của mình. Để ghi đè xử lý chuỗi trong quá trình khử tuần tự JSON, tôi đã đăng ký JsonConverter của riêng mình:

services.AddMvcCore()
    .AddJsonOptions(options =>
        {
            options.SerializerSettings.Converters.Insert(0, new TrimmingStringConverter());
        })

Và đây là công cụ chuyển đổi:

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

Giải pháp của bạn hoạt động tốt! Cảm ơn. Tôi đã thử các giải pháp khác cho .Net Core bằng IModelBinderProvider, nó không hoạt động.
Cedric Arnould

Ngoại trừ trong startup.cs, nó cũng có thể được sử dụng trong Model dưới dạng [JsonConverter (typeof (TrinkleStringConverter))]. Btw. có một lý do đằng sau việc sử dụng .Insert () thay vì .Add ()?
lãng phí

@wast Tôi đoán tôi vừa mới làm .Insert () thay vì .Add () để đảm bảo nó được chạy trước các Trình chuyển đổi khác. Không thể nhớ bây giờ.
Kai G

Chi phí hoạt động của cái này trên DefaultContractResolver là gì?
Maulik Modi

13

Một biến thể khác của câu trả lời của @ Takepara nhưng với một cách khác:

1) Tôi thích cơ chế thuộc tính "StringTrim" chọn tham gia (hơn là ví dụ từ chối "NoTrim" của @Anton).

2) Yêu cầu một cuộc gọi bổ sung tới SetModelValue để đảm bảo ModelState được điền chính xác và mẫu xác nhận / chấp nhận / từ chối mặc định có thể được sử dụng như bình thường, ví dụ như TryUpdateModel (model) để áp dụng và ModelState.Clear () để chấp nhận mọi thay đổi.

Đặt cái này trong thư viện thực thể / chia sẻ của bạn:

/// <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
{
}

Sau đó, điều này trong ứng dụng / thư viện MVC của bạn:

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

Nếu bạn không đặt giá trị thuộc tính trong liên kết, ngay cả khi bạn không muốn thay đổi bất cứ điều gì, bạn sẽ chặn hoàn toàn tài sản đó khỏi ModelState! Điều này là do bạn đã đăng ký ràng buộc tất cả các loại chuỗi, vì vậy nó xuất hiện (trong thử nghiệm của tôi) rằng chất kết dính mặc định sẽ không làm điều đó cho bạn sau đó.


7

Thông tin thêm cho bất cứ ai đang tìm kiếm cách thực hiện điều này trong ASP.NET Core 1.0. Logic đã thay đổi khá nhiều.

Tôi đã viết một bài đăng trên blog về cách làm điều đó , nó giải thích mọi thứ chi tiết hơn một chút

Vì vậy, giải pháp ASP.NET Core 1.0:

Mô hình chất kết dính để thực hiện cắt tỉa thực tế

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

Ngoài ra, bạn cần Nhà cung cấp Binder Model trong phiên bản mới nhất, điều này cho biết rằng nên sử dụng chất kết dính này cho mô hình này

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

Sau đó, nó phải được đăng ký trong Startup.cs

 services.AddMvc().AddMvcOptions(options => {  
       options.ModelBinderProviders.Insert(0, new TrimmingModelBinderProvider());
 });

Nó cũng không hoạt động với tôi, tất cả các lĩnh vực của tôi đều không có giá trị
Cedric Arnould

5

Trong khi đọc qua các câu trả lời và nhận xét xuất sắc ở trên, và ngày càng bối rối, tôi đột nhiên nghĩ, này, tôi tự hỏi liệu có một giải pháp jQuery nào không. Vì vậy, đối với những người khác, giống như tôi, thấy ModelBinder hơi hoang mang, tôi cung cấp đoạn mã jQuery sau để cắt các trường đầu vào trước khi biểu mẫu được gửi.

    $('form').submit(function () {
        $(this).find('input:text').each(function () {
            $(this).val($.trim($(this).val()));
        })
    });

1
2 điều: 1 - Lưu trữ các đối tượng khách hàng của bạn (như $ (này)), 2 - Bạn không bao giờ có thể dựa vào đầu vào của khách hàng, nhưng bạn chắc chắn có thể dựa vào mã máy chủ. Vì vậy, câu trả lời của bạn là hoàn thành câu trả lời mã máy chủ :)
graumanoz 8/2/2016

5

Trong trường hợp MVC Core

Chất kết dính:

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

Các nhà cung cấp:

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

Chức năng đăng ký:

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

Đăng ký:

service.AddMvc(option => option.AddStringTrimmingProvider())

+1. Chính xác những gì tôi đang tìm kiếm. Mục đích của mã "binderToFind" trong chức năng Đăng ký là gì?
Brad

Tôi chỉ cố gắng đặt nhà cung cấp tùy chỉnh với dự phòng SimpleTypeModelBinderProviderbằng cách duy trì cùng một chỉ mục.
Vikash Kumar

Toàn bộ mô tả có thể được tìm thấy ở đây vikutech.blogspot.in/2018/02/ Khăn
Vikash Kumar

3

Đi dự tiệc muộn, nhưng sau đây là bản tóm tắt các điều chỉnh cần thiết cho MVC 5.2.3 nếu bạn muốn xử lý skipValidationyêu cầu của nhà cung cấp giá trị tích hợp.

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

Toàn cầu

    protected void Application_Start()
    {
        ...
        ModelBinders.Binders.Add(typeof(string), new TrimStringModelBinder());
        ...
    }

2

Tôi không đồng ý với giải pháp. Bạn nên ghi đè GetPropertyValue vì dữ liệu cho SetProperty cũng có thể được ModelState điền vào. Để bắt dữ liệu thô từ các yếu tố đầu vào, hãy viết điều này:

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

}

Lọc theo propertyDescriptor PropertyType nếu bạn thực sự chỉ quan tâm đến các giá trị chuỗi nhưng điều đó không quan trọng bởi vì mọi thứ về cơ bản là một chuỗi.


2

Đối với ASP.NET Core , thay thếComplexTypeModelBinderProvider bằng một nhà cung cấp cắt xâu chuỗi.

Trong ConfigureServicesphương pháp mã khởi động của bạn , thêm điều này:

services.AddMvc()
    .AddMvcOptions(s => {
        s.ModelBinderProviders[s.ModelBinderProviders.TakeWhile(p => !(p is ComplexTypeModelBinderProvider)).Count()] = new TrimmingModelBinderProvider();
    })

Xác định TrimmingModelBinderProvidernhư thế này:

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

Phần xấu xí của điều này là bản sao và dán GetBinderlogic từ đó ComplexTypeModelBinderProvider, nhưng dường như không có bất kỳ móc nối nào để cho phép bạn tránh điều này.


Tôi không biết tại sao, nhưng nó không hoạt động cho ASP.NET Core 1.1.1. Tất cả các thuộc tính của đối tượng mô hình mà tôi nhận được trong hành động của bộ điều khiển là null. Phương thức "SetProperty" được gọi là máy chủ.
Waldo

Không làm việc cho tôi, không gian ở đầu tài sản của tôi vẫn còn đó.
Cedric Arnould

2

Tôi đã tạo các nhà cung cấp giá trị để cắt các giá trị tham số chuỗi truy vấn và các giá trị biểu mẫu. Điều này đã được thử nghiệm với ASP.NET Core 3 và hoạt động hoàn hảo.

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

Sau đó đăng ký các nhà máy cung cấp giá trị trong ConfigureServices()hàm trong 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

Đã có rất nhiều bài viết đề xuất một cách tiếp cận thuộc tính. Đây là một gói đã có thuộc tính trim và nhiều thuộc tính khác: Dado.ComponentModel.Mutations hoặc 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();

Sau lệnh gọi Mutate (), user.UserName sẽ được chuyển thành m@x_speed.01!.

Ví dụ này sẽ cắt khoảng trắng và đặt chuỗi thành chữ thường. Nó không giới thiệu xác nhận, nhưng System.ComponentModel.Annotationscó thể được sử dụng cùng Dado.ComponentModel.Mutations.


0

Tôi đã đăng bài này trong một chủ đề khác. Trong asp.net core 2, tôi đã đi theo một hướng khác. Tôi đã sử dụng một bộ lọc hành động thay thế. Trong trường hợp này, nhà phát triển có thể đặt nó trên toàn cầu hoặc sử dụng làm thuộc tính cho các hành động mà anh ấy / cô ấy muốn áp dụng cắt xén chuỗi. Mã này chạy sau khi liên kết mô hình đã diễn ra và nó có thể cập nhật các giá trị trong đối tượng mô hình.

Đây là mã của tôi, đầu tiên tạo bộ lọc hành động:

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

Để sử dụng nó, hãy đăng ký làm bộ lọc toàn cầu hoặc trang trí hành động của bạn với thuộc tính TrimInputStrings.

[TrimInputStrings]
public IActionResult Register(RegisterViewModel registerModel)
{
    // Some business logic...
    return Ok();
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.