加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > asp.Net > 正文

asp.net-mvc – 表单帖子永远不会执行操作,并且在MVC 6 RC2中有

发布时间:2020-12-16 09:56:04 所属栏目:asp.Net 来源:网络整理
导读:我在MVC 6 RC1到RC2迁移后遇到了一些奇怪的行为. 假设我们有一个表单的愚蠢版本,它会在提交时POST到一个动作: @model InstitutionViewModelform asp-controller="Institution" asp-action="Create" method="post" @Html.Hidden("companyId",ViewBag.Company
我在MVC 6 RC1到RC2迁移后遇到了一些奇怪的行为.

假设我们有一个表单的愚蠢版本,它会在提交时POST到一个动作:

@model InstitutionViewModel
<form asp-controller="Institution" asp-action="Create" method="post">
   @Html.Hidden("companyId",ViewBag.CompanyId)
   @Html.DropDownListFor(Model => Model.LocationId,(List<SelectListItem>)ViewBag.Locations,new { Class = "form-control" })
   @Html.TextAreaFor(model => model.Description,new { Class = "form-control" })
   <input type="submit" value="Submit" class="btn btn-success" />
</form>

然后我们有这个InstitutionViewModel

public class InstitutionViewModel
{
   public int Id { get; set; }
   public string Description { get; set; }
   public int LocationId { get; set; }
   public LocationViewModel Location { get; set; }
}

我们正在发布的动作,看起来像这样

[HttpPost]
public IActionResult Create(int companyId,InstitutionViewModel institution)
{
   ...
}

我遇到的问题是提交永远不会触发操作.浏览器显示微调器,后台发生了一些事情,但程序从未到达该操作.更糟糕的是 – 当发生这种情况时,dotnet进程的RAM消耗开始逐渐上升,直到它用完为止.我最后一次让网站在这种状态下运行,dotnet进程使用的是7GB内存,只需要2到3分钟就可以达到这一点!

enter image description here

这曾经在RC1中没有任何问题.到目前为止,我找到的唯一解决方案是从InstitutionViewModel中删除LocationViewModel属性.如果我这样做,POST就会毫无问题地完成操作.

LocationViewModel本身似乎也不是问题,因为如果类中有任何其他viewModel作为属性,则会发生同样的情况,无论viewModel包含什么.

现在我很困惑天气这是RC2中的一个错误,或者我正在做一些可怕的错误.也许我忘了包含一些东西,或者在升级到RC2时我在Startup.cs和project.json中破坏了一些东西.有人有什么想法吗?

解决方法

Now I’m confused weather this is a bug in RC2 or I’m doing something horribly wrong.

它是a known bug in ASP.NET Core MVC RC2,由默认模型绑定工厂中深度嵌套模型的错误处理引起.

建议的解决方法是to use a custom binder factory until it is fixed:

public class MyModelBinderFactory : IModelBinderFactory
{
    private readonly IModelMetadataProvider _metadataProvider;
    private readonly IModelBinderProvider[] _providers;

    private readonly ConcurrentDictionary<object,IModelBinder> _cache;

    /// <summary>
    /// Creates a new <see cref="ModelBinderFactory"/>.
    /// </summary>
    /// <param name="metadataProvider">The <see cref="IModelMetadataProvider"/>.</param>
    /// <param name="options">The <see cref="IOptions{TOptions}"/> for <see cref="MvcOptions"/>.</param>
    public MyModelBinderFactory(IModelMetadataProvider metadataProvider,IOptions<MvcOptions> options)
    {
        _metadataProvider = metadataProvider;
        _providers = options.Value.ModelBinderProviders.ToArray();

        _cache = new ConcurrentDictionary<object,IModelBinder>();
    }

    /// <inheritdoc />
    public IModelBinder CreateBinder(ModelBinderFactoryContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        // We perform caching in CreateBinder (not in CreateBinderCore) because we only want to
        // cache the top-level binder.
        IModelBinder binder;
        if (context.CacheToken != null && _cache.TryGetValue(context.CacheToken,out binder))
        {
            return binder;
        }

        var providerContext = new DefaultModelBinderProviderContext(this,context);
        binder = CreateBinderCore(providerContext,context.CacheToken);
        if (binder == null)
        {
            var message = $"Could not create model binder for {providerContext.Metadata.ModelType}.";
            throw new InvalidOperationException(message);
        }

        if (context.CacheToken != null)
        {
            _cache.TryAdd(context.CacheToken,binder);
        }

        return binder;
    }

    private IModelBinder CreateBinderCore(DefaultModelBinderProviderContext providerContext,object token)
    {
        if (!providerContext.Metadata.IsBindingAllowed)
        {
            return NoOpBinder.Instance;
        }

        // A non-null token will usually be passed in at the the top level (ParameterDescriptor likely).
        // This prevents us from treating a parameter the same as a collection-element - which could
        // happen looking at just model metadata.
        var key = new Key(providerContext.Metadata,token);

        // If we're currently recursively building a binder for this type,just return
        // a PlaceholderBinder. We'll fix it up later to point to the 'real' binder
        // when the stack unwinds.
        var collection = providerContext.Collection;

        IModelBinder binder;
        if (collection.TryGetValue(key,out binder))
        {
            if (binder != null)
            {
                return binder;
            }

            // Recursion detected,create a DelegatingBinder.
            binder = new PlaceholderBinder();
            collection[key] = binder;
            return binder;
        }

        // OK this isn't a recursive case (yet) so "push" an entry on the stack and then ask the providers
        // to create the binder.
        collection.Add(key,null);

        IModelBinder result = null;

        for (var i = 0; i < _providers.Length; i++)
        {
            var provider = _providers[i];
            result = provider.GetBinder(providerContext);
            if (result != null)
            {
                break;
            }
        }

        if (result == null && token == null)
        {
            // Use a no-op binder if we're below the top level. At the top level,we throw.
            result = NoOpBinder.Instance;
        }

        // If the DelegatingBinder was created,then it means we recursed. Hook it up to the 'real' binder.
        var delegatingBinder = collection[key] as PlaceholderBinder;
        if (delegatingBinder != null)
        {
            delegatingBinder.Inner = result;
        }

        collection[key] = result;
        return result;
    }

    private class DefaultModelBinderProviderContext : ModelBinderProviderContext
    {
        private readonly MyModelBinderFactory _factory;

        public DefaultModelBinderProviderContext(
            MyModelBinderFactory factory,ModelBinderFactoryContext factoryContext)
        {
            _factory = factory;
            Metadata = factoryContext.Metadata;
            BindingInfo = factoryContext.BindingInfo;

            MetadataProvider = _factory._metadataProvider;
            Collection = new Dictionary<Key,IModelBinder>();
        }

        private DefaultModelBinderProviderContext(
            DefaultModelBinderProviderContext parent,ModelMetadata metadata)
        {
            Metadata = metadata;

            _factory = parent._factory;
            MetadataProvider = parent.MetadataProvider;
            Collection = parent.Collection;

            BindingInfo = new BindingInfo()
            {
                BinderModelName = metadata.BinderModelName,BinderType = metadata.BinderType,BindingSource = metadata.BindingSource,PropertyFilterProvider = metadata.PropertyFilterProvider,};
        }

        public override BindingInfo BindingInfo { get; }

        public override ModelMetadata Metadata { get; }

        public override IModelMetadataProvider MetadataProvider { get; }

        // Not using a 'real' Stack<> because we want random access to modify the entries.
        public Dictionary<Key,IModelBinder> Collection { get; }

        public override IModelBinder CreateBinder(ModelMetadata metadata)
        {
            var nestedContext = new DefaultModelBinderProviderContext(this,metadata);
            return _factory.CreateBinderCore(nestedContext,token: null);
        }
    }

    [DebuggerDisplay("{ToString(),nq}")]
    private struct Key : IEquatable<Key>
    {
        private readonly ModelMetadata _metadata;
        private readonly object _token; // Explicitly using ReferenceEquality for tokens.

        public Key(ModelMetadata metadata,object token)
        {
            _metadata = metadata;
            _token = token;
        }

        public bool Equals(Key other)
        {
            return _metadata.Equals(other._metadata) && object.ReferenceEquals(_token,other._token);
        }

        public override bool Equals(object obj)
        {
            var other = obj as Key?;
            return other.HasValue && Equals(other.Value);
        }

        public override int GetHashCode()
        {
            return _metadata.GetHashCode() ^ RuntimeHelpers.GetHashCode(_token);
        }

        public override string ToString()
        {
            if (_metadata.MetadataKind == ModelMetadataKind.Type)
            {
                return $"{_token} (Type: '{_metadata.ModelType.Name}')";
            }
            else
            {
                return $"{_token} (Property: '{_metadata.ContainerType.Name}.{_metadata.PropertyName}' Type: '{_metadata.ModelType.Name}')";
            }
        }
    }
}

您可以从Startup.ConfigureServices在DI容器中注册它:

services.AddSingleton<IModelBinderFactory,MyModelBinderFactory>();

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读