4

Я столкнулся с каким-то странным поведением после миграции MVC 6 RC1 в RC2.Сообщение формы никогда не выполняет действие и имеет ошибку памяти в MVC 6 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) 
{ 
    ... 
} 

Проблема, с которой я сталкиваюсь, заключается в том, что submit никогда не срабатывает rs действие. В браузере отображается счетчик, и что-то происходит в фоновом режиме, но программа никогда не приходит к действию. Хуже того - когда это происходит, Потребление ОЗУ в режиме dotnet начинает постепенно возрастать, пока оно не закончится. В последний раз, когда я позволял веб-сайту работать в этом состоянии, процесс dotnet использовал 7 ГБ оперативной памяти, и для достижения этой цели потребовалось всего 2 или 3 минуты! enter image description here

Это работало без проблем в RC1. Единственным решением, которое я нашел для этого до сих пор, является удаление свойства LocationViewModel из InstitutionViewModel. Если я это сделаю, POST достигнет действия без каких-либо проблем.

LocationViewModel, похоже, не является проблемой сам по себе, потому что то же самое происходит, если в классе есть какой-либо другой видModel в качестве свойства, независимо от того, что содержит viewModel.

Теперь я запутался в погоде, это ошибка в RC2, или я делаю что-то ужасно неправильно. Может быть, я забыл включить что-то или что-то сломал в Startup.cs и project.json при обновлении до RC2. У кого-нибудь есть идеи?

+0

Как вы знаете, он никогда не достигает действия? Похоже, клиент выдает много запросов, которые застревают на сервере одновременно. – usr

+0

Приостановите отладчик, пока это происходит. Что происходит? – usr

+0

Ну, я использую отладчик, и он никогда не попадает в первую строку действия. И даже если я запускаю его в выпуске без каких-либо отладочных точек, он делает то же самое. Если я проверю в Chrome, сетевой тег также показывает, что был отправлен только один запрос. –

ответ

3

Теперь я запутался в погоде, это ошибка в RC2, или я делаю что-то ужасно неправильно.

Это 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}')"; 
      } 
     } 
    } 
} 

Вы можете зарегистрировать его в контейнер DI от Startup.ConfigureServices:

services.AddSingleton<IModelBinderFactory, MyModelBinderFactory>(); 
+1

Спасибо! Я забыл эту проблему. Спасибо, что указали это. –

+1

Спасибо. Ты только что спас меня! Я пытаюсь проверить свой код на 2 дня – Layinka

 Смежные вопросы

  • Нет связанных вопросов^_^