ASP.NET 将具有异常键名的表单 POST 反序列化为正确的 DataContract 模型?

ASP.NET Deserialize Form POST with unusual key names into proper DataContract model?

我对 C# 和 ASP.NET 有点陌生,我的任务是用新的 ASP.NET Core MVC API 服务替换应用程序。我还有一个遗留应用程序需要 POST 一些数据,但我无法控制它的代码,也无法改变它的行为。它希望以 application/x-www-form-urlencoded 形式发送数据,并且在映射回 POCO 数据契约对象时使用包含 "invalid" 字符的大写参数名称,例如 .,或以数字开头的名称。

来自旧版应用的示例请求可能如下所示:

POST /something HTTP/1.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 42

USERNAME=somebody&ACCOUNT=123&APP.SESSID=acbd18db4cc2f85cedef654fccc4a4d8&1STCOLOR=BLUE

因为我不能,而且有些不想,像这个无效代码一样创建 DataContract 模型,所以我不确定如何继续。

class InputModel
{
    public string USERNAME { get; set; }
    public int ACCOUNT { get; set; }
    public string APP.SESSID { get; set; }
    public string 1STCOLOR { get; set; }
}

我希望尽可能人性化数据模型成员名称(例如,APP.SESSIDApplicationSessionID),并能够将遗留参数名称绑定到人性化成员表单数据反序列化或模型绑定期间的名称,等等。

我只是不知道在 ASP.NET 核心代码中查找或搜索什么来帮助我确定我应该做什么来覆盖默认表单反序列化行为。有经验的人可以帮忙吗?感谢您的宝贵时间!

我想我明白了。我首先必须创建一个 ValueProvider 对象。这是我想出来的。

public class MyValueProvider : BindingSourceValueProvider, IValueProvider
{
    private IEnumerable<KeyValuePair<string, string>> _keyMap;
    private readonly CultureInfo _culture;
    private PrefixContainer _prefixContainer;
    private IFormCollection _values;

    public MyValueProvider(BindingSource form, IFormCollection values, CultureInfo culture) : this(form, values, null, culture) { }

    public MyValueProvider(BindingSource form, IFormCollection values, IEnumerable<KeyValuePair<string, string>> keyMap, CultureInfo culture) : base(form)
    {
        if (form == null)
        {
            throw new ArgumentNullException(nameof(form));
        }

        if (values == null)
        {
            throw new ArgumentNullException(nameof(values));
        }


        _values = values;
        _culture = culture;
        _keyMap = keyMap;
    }

    public CultureInfo Culture
    {
        get { return _culture; }
    }

    protected PrefixContainer PrefixContainer
    {
        get
        {
            if (_prefixContainer == null)
            {
                _prefixContainer = new PrefixContainer(_values.Keys);
            }
            return _prefixContainer;
        }
    }

    public override bool ContainsPrefix(string prefix)
    {
        return PrefixContainer.ContainsPrefix(prefix);
    }

    public virtual IDictionary<string, string> GetKeysFromPrefix(string prefix)
    {
        if (prefix == null)
        {
            throw new ArgumentNullException(nameof(prefix));
        }

        return PrefixContainer.GetKeysFromPrefix(prefix);
    }

    public override ValueProviderResult GetValue(string key)
    {
        if (key == null)
        {
            throw new ArgumentNullException(nameof(key));
        }

        var pair = _keyMap.FirstOrDefault(map => map.Key.EndsWith(key));
        if (pair.Value == null)
        {
            return ValueProviderResult.None;
        }
        var values = _values[pair.Value];
        if (values.Count == 0)
        {
            return ValueProviderResult.None;
        }

        return new ValueProviderResult(values, Culture);
    }
}

然后是该值提供者的工厂。

public class MyValueProviderFactory : IValueProviderFactory
{
    private readonly IEnumerable<KeyValuePair<string,string>> keyMap;

    public MyValueProviderFactory(IConfigurationSection section)
    {
        keyMap = section.AsEnumerable();
    }

    public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        if (context.ActionContext.HttpContext.Request.HasFormContentType)
        {
            return AddValueProviderAsync(context, keyMap);
        }

        return TaskCache.CompletedTask;
    }

    private static async Task AddValueProviderAsync(ValueProviderFactoryContext context, IEnumerable<KeyValuePair<string, string>> keyMap)
    {
        var request = context.ActionContext.HttpContext.Request;
        var valueProvider = new MyValueProvider(
                BindingSource.Form,
                await request.ReadFormAsync(),
                keyMap,
                CultureInfo.CurrentCulture);

        context.ValueProviders.Add(valueProvider);
    }
}

最后,在我的 Startup class 中,我加载了 appsettings.json 配置的一部分作为键映射。这只是一个 JSON 对象,原始键名作为 JSON 属性,新键名作为值。我将它作为一组键值传递给我的 ValueProviderFactory,然后我将这个值提供者工厂插入到系统提供的工厂列表的前面。

public class Startup
{
    public Startup(IHostingEnvironment env)
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json")
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true)
            .AddEnvironmentVariables();
        Configuration = builder.Build();
    }

    public IConfigurationRoot Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc(options =>
        {
            options.ValueProviderFactories.Insert(0, new MyValueProviderFactory(Configuration.GetSection("MyValueProviderKeyMap")));
        });
    }
}

最后,它非常简单。我只是花了一点时间才弄清楚这个过程。