ASP.NET4.5のモデルバインディングでカンマ編集された数値をエラーにしない方法

WebフォームのASP.NET4.5で追加されたモデルバインディングでは、"1,000"のようなカンマ編集された数値はエラーとして扱われます。ASP.NET MVCでも同じことが起こりますが、こちらはモデルバインダーをカスタマイズすることで割と簡単に解決できます。しかし、Webフォームに同じやり方をしてもうまくいきません。編集時は数値をカンマ編集しない運用にすればよいのですが、カンマ編集しておきたいことも多々あると思います。
そこでWebフォームのモデルバインディングをカスタマイズして解決する方法を考えました。.NETのソースが公開されているので、それを拝借して必要な修正を行っていきます。
まずはGlobal.asax.csに２行追加します。

```csharp:Global.asax.cs
using System.Web.ModelBinding;    //←追加

namespace WebApplication1
{
    public class Global : HttpApplication
    {
        void Application_Start(object sender, EventArgs e)
        {
            // アプリケーションのスタートアップで実行するコードです
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);

            //↓追加
            ((DefaultModelBinder)ModelBinders.Binders.DefaultBinder).Providers[7] = new CustomTypeConverterModelBinderProvider();
        }
    }
}
```
次にTypeConverteModelBinderProviderをカスタマイズします。.NETのソースから該当箇所を持ってきて、internalで定義されていて使用できない箇所について、書き換えたり、動作に支障がなければ削除します。

```csharp:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.ModelBinding;　　　　　　　//←追加
using System.ComponentModel;　　　　　　　　//←追加
using System.Diagnostics.CodeAnalysis;　　//←追加
using System.Globalization;　　　　　　　　//←追加

namespace WebApplication1
{
    // Returns a binder that can perform conversions using a .NET TypeConverter.
    public sealed class CustomTypeConverterModelBinderProvider : ModelBinderProvider
    {

        public override IModelBinder GetBinder(ModelBindingExecutionContext modelBindingExecutionContext, ModelBindingContext bindingContext)
        {
            //ModelBinderUtil.ValidateBindingContext(bindingContext);　　//←削除

            //ValueProviderResult vpResult = bindingContext.UnvalidatedValueProvider.GetValue(bindingContext.ModelName, skipValidation: !bindingContext.ValidateRequest);　　//←書き換え
            ValueProviderResult vpResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
            if (vpResult == null)
            {
                return null; // no value to convert
            }

            if (!TypeDescriptor.GetConverter(bindingContext.ModelType).CanConvertFrom(typeof(string)))
            {
                return null; // this type cannot be converted
            }

            return new CustomTypeConverterModelBinder();　　//←書き換え
        }

    }

    public sealed class CustomTypeConverterModelBinder : IModelBinder　　//←書き換え
    {

        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The exception is recorded to be acted upon later.")]
        [SuppressMessage("Microsoft.Globalization", "CA1304:SpecifyCultureInfo", MessageId = "System.Web.ModelBinding.ValueProviderResult.ConvertTo(System.Type)", Justification = "The ValueProviderResult already has the necessary context to perform a culture-aware conversion.")]
        public bool BindModel(ModelBindingExecutionContext modelBindingExecutionContext, ModelBindingContext bindingContext)
        {
            //ModelBinderUtil.ValidateBindingContext(bindingContext);　　//←削除

            //ValueProviderResult vpResult = bindingContext.UnvalidatedValueProvider.GetValue(bindingContext.ModelName, skipValidation: !bindingContext.ValidateRequest);　　//←書き換え
            ValueProviderResult vpResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
            if (vpResult == null)
            {
                return false; // no entry
            }

            object newModel;
            bindingContext.ModelState.SetModelValue(bindingContext.ModelName, vpResult);
            try
            {
                //Decimalの場合、カンマがあっても数値に変換するコードを追加する。
                if (bindingContext.ModelType == typeof(Decimal) || bindingContext.ModelType == typeof(Nullable<Decimal>))
                {
                    newModel = Decimal.Parse(vpResult.AttemptedValue, NumberStyles.Any);
                }
                else
                {
                    newModel = vpResult.ConvertTo(bindingContext.ModelType);
                }
            }
            catch (Exception ex)
            {
                if (IsFormatException(ex))
                {
                    // there was a type conversion failure
                    string errorString = ModelBinderErrorMessageProviders.TypeConversionErrorMessageProvider(modelBindingExecutionContext, bindingContext.ModelMetadata, vpResult.AttemptedValue);
                    if (errorString != null)
                    {
                        bindingContext.ModelState.AddModelError(bindingContext.ModelName, errorString);
                    }
                }
                else
                {
                    bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex);
                }
                return false;
            }

            //ModelBinderUtil.ReplaceEmptyStringWithNull(bindingContext.ModelMetadata, ref newModel);　　//←書き換え
            ReplaceEmptyStringWithNull(bindingContext.ModelMetadata, ref newModel);
            bindingContext.Model = newModel;
            return true;
        }

        private static bool IsFormatException(Exception ex)
        {
            for (; ex != null; ex = ex.InnerException)
            {
                if (ex is FormatException)
                {
                    return true;
                }
            }
            return false;
        }

        public static void ReplaceEmptyStringWithNull(ModelMetadata modelMetadata, ref object model)
        {
            if (modelMetadata.ConvertEmptyStringToNull && StringIsEmptyOrWhitespace(model as string))
            {
                model = null;
            }
        }

        // Based on String.IsNullOrWhitespace
        private static bool StringIsEmptyOrWhitespace(string s)
        {
            if (s == null)
            {
                return false;
            }

            if (s.Length != 0)
            {
                for (int i = 0; i < s.Length; i++)
                {
                    if (!Char.IsWhiteSpace(s[i]))
                    {
                        return false;
                    }
                }
            }
            return true;
        }
     }
}

```
これでカンマ編集された数値もエラーなくモデルバインディングされます。
以上