วิธีที่ดีที่สุดในการตัดแต่งสตริงหลังจากป้อนข้อมูล ฉันควรสร้างตัวยึดโมเดลที่กำหนดเองหรือไม่


172

ฉันกำลังใช้ ASP.NET MVC และฉันต้องการให้ผู้ใช้ทั้งหมดป้อนเขตข้อมูลสตริงที่จะตัดแต่งก่อนที่จะแทรกลงในฐานข้อมูล และเนื่องจากฉันมีแบบฟอร์มการป้อนข้อมูลมากมายฉันกำลังมองหาวิธีที่สวยงามในการตัดแต่งสตริงทั้งหมดแทนที่จะตัดค่าสตริงที่ผู้ใช้ทุกคนมอบให้อย่างชัดเจน ฉันสนใจที่จะรู้ว่าผู้คนกำลังตัดแต่งสตริงอย่างไรและเมื่อไหร่

ฉันคิดว่าบางทีการสร้างแบบจำลองที่กำหนดเองและตัดค่าสตริงใด ๆ ที่นั่น ... ด้วยวิธีนี้ตรรกะการตัดของฉันทั้งหมดจะอยู่ในที่เดียว นี่เป็นวิธีที่ดีหรือไม่? มีตัวอย่างโค้ดที่ทำสิ่งนี้หรือไม่?

คำตอบ:


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 = string.IsNullOrEmpty (stringValue)? stringValue: stringValue.Trim ();
Simon_Weaver

4
สิ่งนี้สมควรได้รับการโหวตมากขึ้น ฉันประหลาดใจจริงทีม MVC ไม่ได้เลือกที่จะดำเนินการนี้ในรูปแบบเริ่มต้นเครื่องผูก ...
พอร์ทแมน

1
@BreckFresen ฉันมีปัญหาเดียวกันคุณจะต้องแทนที่วิธี BindModel และตรวจสอบ bindingContext.ModelType สำหรับสตริงแล้วตัดถ้ามันเป็น
เคลลี่

3
สำหรับใครที่ชอบฉันเริ่มมีความกำกวมใน DefaultModelBinder คนที่ถูกต้องคือใช้ System.Web.Mvc
GeoffM

3
คุณจะปรับเปลี่ยนสิ่งนี้อย่างไรเพื่อให้type="password"อินพุตไม่ถูกแตะต้อง?
Extragorey

77

นี่คือ @takepara ความละเอียดเดียวกัน แต่เป็น IModelBinder แทน DefaultModelBinder เพื่อให้การเพิ่ม modelbinder ใน 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;
Marius Schulz

6
สิ่งนี้ไม่จัดการแอตทริบิวต์ตัวควบคุม [ValidateInput (false)] มันทำให้เกิดข้อยกเว้น "คำขอที่เป็นอันตราย ... "
CodeGrue

2
สำหรับผู้ที่ได้รับ 'คำขอที่เป็นอันตราย ... ' โปรดอ้างถึงบทความนี้ - blogs.taiga.nl/martijn/2011/09/29/29
GurjeetSinghDB

2
เพื่อนร่วมงานของฉันได้ทำการเปลี่ยนแปลงสิ่งนี้ซึ่งก่อให้เกิดปัญหาทุกประเภท: problems.umbraco.org/issue/U4-6665 ฉันขอแนะนำให้ส่งคืน null และว่างเปล่าตามความเหมาะสมแทนที่จะเลือกที่อื่นมากกว่าเสมอ (ในกรณีของคุณคุณ ส่งคืนค่า null เสมอแม้ว่าค่าจะเป็นสตริงว่าง)
นิโคลัส Westby

2
สิ่งนี้ดูเหมือนจะทำลาย[AllowHtml]คุณสมบัติของคุณสมบัติของโมเดล (พร้อมด้วย[ValidateInput(false)]ตามที่ CodeGrue กล่าวไว้ข้างต้น
Mingwei Samuel

43

การปรับปรุงหนึ่งใน @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]


1
เราจะใช้บางอย่างเช่นแอตทริบิวต์นี้เมื่อใช้วิธี IModelBinder โดย @Korayem ได้อย่างไร ในบางแอปพลิเคชันฉันใช้ตัวประสานโมเดล (บุคคลที่สาม) อื่น (เช่น S # arp Archeticture) ฉันต้องการเขียนสิ่งนี้ใน DLL ส่วนตัวที่แชร์ระหว่างโครงการดังนั้นจึงจำเป็นต้องมีวิธีการ IModelBinder
Carl Bussema

1
@CarlBussema นี่คือคำถามเกี่ยวกับการเข้าถึงคุณสมบัติจากภายใน IModelBinder stackoverflow.com/questions/6205176
Mac Attack

4
ฉันคิดว่ามันยังดี แต่ผมจะเข้ามาแทนที่ด้วย.Cast<object>().Any(a => a.GetType() == typeof(NoTrimAttribute)) .OfType<NoTrimAttribute>().Any()ทำความสะอาดนิดหน่อย
DBueno

ฉันใส่คุณลักษณะของฉันในแอสเซมบลีที่ใช้ร่วมกันเพราะเช่นเดียวกับบันทึกย่อข้อมูลแอตทริบิวต์ดังกล่าวมีขอบเขตการใช้งานที่กว้างกว่า MVC เช่นระดับธุรกิจลูกค้า อีกข้อสังเกตหนึ่งที่ว่า "DisplayFormatAttribute (ConvertEmptyStringToNull)" จะควบคุมว่าจะตัดสายอักขระที่ตัดขอบให้เป็นโมฆะหรือสตริงว่าง ค่าเริ่มต้นคือจริง (null) ที่ฉันชอบ แต่ในกรณีที่คุณต้องการสตริงว่างในฐานข้อมูลของคุณ (หวังว่าจะไม่) คุณสามารถตั้งค่าเป็นเท็จเพื่อให้ได้ อย่างไรก็ตามนี่คือสิ่งที่ดีทั้งหมดหวังว่า 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();
    }
}

คุณต้องรวมบรรทัดนี้ไว้Application_Start()ในGlobal.asax.csไฟล์ของคุณเพื่อใช้ model binder เมื่อทำการผูกstrings:

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

ฉันคิดว่ามันจะดีกว่าถ้าใช้ model binder เช่นนี้แทนที่จะเขียนทับโมเดลเริ่มต้นเพราะมันจะถูกใช้ทุกครั้งที่คุณมีผลผูกพัน a stringไม่ว่าจะเป็นอาร์กิวเมนต์ของเมธอดโดยตรงหรือเป็นคุณสมบัติในคลาสโมเดล แต่ถ้าคุณแทนที่เครื่องผูกรูปแบบเริ่มต้นเป็นคำตอบอื่น ๆ ที่นี่ขอแนะนำว่าจะมีเพียงการทำงานเมื่อมีผลผูกพันคุณสมบัติในรุ่นไม่ได้เมื่อคุณมี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();
    }
}

ดูความคิดเห็นด้านบนอีกครั้ง ตัวอย่างนี้ไม่จัดการข้อกำหนด skipValidation ของ IUnvalidatedValueProvider
Aaron Hudon

@adrian อินเตอร์เฟส IModelBinder มีเพียงเมธอด BindModel ที่มีชนิดบูลส่งคืน แล้วคุณใช้กับวัตถุประเภทส่งคืนที่นี่ได้อย่างไร
Magendran V

@MagendranV ฉันไม่แน่ใจว่าอินเทอร์เฟซใดที่คุณกำลังดูอยู่ แต่คำตอบนี้อิงกับ IModelBinder ใน ASP.NET MVC 5 ซึ่งส่งคืนวัตถุ: docs.microsoft.com/en-us/previous-versions/aspnet / …
adrian

1
@AaronHudon ฉันได้อัปเดตคำตอบของฉันเพื่อรวมตัวอย่างเพื่อจัดการกับการตรวจสอบข้าม
adrian

หากเขตข้อมูลรหัสผ่านของคุณมีชุดข้อมูลประเภทที่ถูกต้อง (เช่น [DataType (DataType.Password)]) คุณสามารถอัปเดตบรรทัดสุดท้ายดังนี้ดังนั้นจึงไม่ตัดขอบเขตข้อมูลเหล่านี้: ส่งคืนสตริง bindingContext.ModelMetadata.DataTypeName == "รหัสผ่าน"? พยายามValue: พยายามValue.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();
    }
}

โซลูชันของคุณใช้งานได้ดี! ขอบคุณ ฉันลองโซลูชันอื่น ๆ สำหรับ. Net Core โดยใช้ IModelBinderProvider มันไม่ทำงาน
Cedric Arnould

ยกเว้นใน startup.cs มันยังสามารถใช้ใน Model เป็น [JsonConverter (typeof (TrimmingStringConverter)) Btw มีเหตุผลเบื้องหลังการใช้. แทรก () แทน. เพิ่ม () หรือไม่
เจ้า

@ ฉันเดาว่าฉันเพิ่งทำ. แทรก () แทน. เพิ่ม () เพื่อให้แน่ใจว่าจะได้รับการเรียกใช้ก่อนที่จะแปลงอื่น ๆ จำไม่ได้ตอนนี้
Kai G

อะไรคือค่าใช้จ่ายในการดำเนินการนี้มากกว่า DefaultContractResolver?
Maulik Modi

13

อีกคำตอบหนึ่งของ @ takepara แต่แตกต่างกัน:

1) ฉันชอบกลไกการเลือกใช้ "StringTrim" (แทนที่จะเป็นตัวอย่างการยกเลิก "NoTrim" ของ @Anton)

2) ต้องใช้การเรียกเพิ่มเติมไปยัง SetModelValue เพื่อให้แน่ใจว่า ModelState จะถูกเติมอย่างถูกต้องและสามารถใช้รูปแบบการตรวจสอบ / ยอมรับ / ปฏิเสธเริ่มต้นตามปกติเช่น 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:

Model binder ทำการตัดแต่งจริง

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

มันไม่ได้ผลสำหรับฉันเหมือนกันทุกสาขาของฉันเป็นโมฆะตอนนี้
เซดริกอาร์นอล

5

ในขณะที่อ่านคำตอบและความคิดเห็นที่ยอดเยี่ยมข้างบนและเกิดความสับสนมากขึ้นฉันก็คิดว่าเดี๋ยวก่อนฉันสงสัยว่ามีวิธีแก้ปัญหา jQuery หรือไม่ ดังนั้นสำหรับคนอื่น ๆ ที่ชอบฉันค้นหา ModelBinders ซึ่งทำให้สับสนเล็กน้อย

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

1
2 สิ่ง: 1 - แคชวัตถุไคลเอ็นต์ของคุณ (เช่น $ (นี่)), 2 - คุณไม่สามารถพึ่งพาอินพุตลูกค้าได้ แต่คุณสามารถพึ่งพารหัสเซิร์ฟเวอร์ได้แน่นอน ดังนั้นคำตอบของคุณเสร็จสิ้นคำตอบรหัสเซิร์ฟเวอร์ :)
graumanoz

5

ในกรณีของ MVC Core

Binder:

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 สิ่งที่ฉันกำลังมองหา จุดประสงค์ของรหัส "binderToFind" ในฟังก์ชันการลงทะเบียนคืออะไร
แบรด

ฉันแค่พยายามที่จะทำให้ผู้ให้บริการที่กำหนดเองกับทางเลือกSimpleTypeModelBinderProviderโดยการรักษาดัชนีเดียวกัน
Vikash Kumar

รายละเอียดทั้งหมดสามารถดูได้ที่นี่vikutech.blogspot.in/2018/02/…
Vikash Kumar

3

มาสายปาร์ตี้ แต่ต่อไปนี้เป็นบทสรุปของการปรับเปลี่ยนที่จำเป็นสำหรับ MVC 5.2.3 หากคุณต้องจัดการกับskipValidationความต้องการของผู้ให้บริการมูลค่าบิวด์อิน

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

ฉันไม่เห็นด้วยกับการแก้ปัญหา คุณควรแทนที่ GetPropertyValue เนื่องจากข้อมูลสำหรับ SetProperty อาจถูกกรอกโดย ModelState ในการดักจับข้อมูลดิบจากองค์ประกอบอินพุตให้เขียนสิ่งนี้:

 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 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แต่ดูเหมือนจะไม่มีตะขอใด ๆ ที่จะให้คุณหลีกเลี่ยงสิ่งนี้


ฉันไม่รู้ว่าทำไม แต่ใช้ไม่ได้กับ ASP.NET Core 1.1.1 คุณสมบัติทั้งหมดของ model object ที่ฉันได้รับใน action action เป็น null เมธอด "SetProperty" เป็น nerver ชื่อ
วัลโด

ไม่ได้ผลสำหรับฉันพื้นที่ที่จุดเริ่มต้นของคุณสมบัติของฉันยังคงอยู่ที่นั่น
Cedric Arnould

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

มีการโพสต์จำนวนมากที่แนะนำวิธีการคุณลักษณะ นี่คือแพ็คเกจที่มีแอตทริบิวต์การตัดแต่งและอื่น ๆ อีกมากมาย: 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();

หลังจากที่การเรียกร้องให้เปลี่ยนรูปแบบ () user.UserName m@x_speed.01!จะกลายพันธุ์

ตัวอย่างนี้จะตัดช่องว่างและกรณีสตริงเพื่อตัวพิมพ์เล็ก มันไม่ได้แนะนำการตรวจสอบ แต่สามารถนำมาใช้ควบคู่ไปกับSystem.ComponentModel.AnnotationsDado.ComponentModel.Mutations


0

ฉันโพสต์สิ่งนี้ในหัวข้ออื่น ใน asp.net core 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.