Связывание абстрактной модели классов в основном веб-интерфейсе asp.net 2 - PullRequest
0 голосов
/ 07 января 2019

Я пытался выяснить, как использовать привязку пользовательской модели с .net Core 2 web api, но не смог заставить ее работать.

Я прошел через несколько статей, как показано ниже http://www.palmmedia.de/Blog/2018/5/13/aspnet-core-model-binding-of-abstract-classes Asp net core rc2. Обязательная модель абстрактного класса

В моем случае bindingContext.ModelName всегда пусто. Кто-нибудь может объяснить, почему это может быть?

Пример реализации ниже

Контроллер

        public IActionResult SomeAction([ModelBinder(BinderType = typeof(BlahTypeModelBinder))][FromBody]TheBaseClass theBase)
    {
        return Ok();
    }

Модель

public abstract class TheBaseClass
{
    public abstract int WhatType { get; }
}

public class A : TheBaseClass
{
    public override int WhatType { get { return 1; }  }
}

public class B : TheBaseClass
{
    public override int WhatType { get { return 2; } }
}

Средний

public class BhalTypeBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context == null)
            throw new ArgumentNullException(nameof(context));

        if (context.Metadata.ModelType == typeof(TheBaseClass))
        {
            var assembly = typeof(TheBaseClass).Assembly;
            var abstractSearchClasses = assembly.GetExportedTypes()
                .Where(t => t.BaseType.Equals(typeof(TheBaseClass)))
                .Where(t => !t.IsAbstract)
                .ToList();

            var modelBuilderByType = new Dictionary<Type, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexTypeModelBinder>();

            foreach (var type in abstractSearchClasses)
            {
                var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();
                var metadata = context.MetadataProvider.GetMetadataForType(type);

                foreach (var property in metadata.Properties)
                {
                    propertyBinders.Add(property, context.CreateBinder(property));
                }

                modelBuilderByType.Add(type, new Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexTypeModelBinder(propertyBinders));
            }

            return new BlahTypeModelBinder(modelBuilderByType, context.MetadataProvider);
        }

        return null;
    }
}

Binder

public class BlahTypeModelBinder : IModelBinder
{
    private readonly IModelMetadataProvider _metadataProvider;
    private readonly IDictionary<Type, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexTypeModelBinder> _binders;

    public BlahTypeModelBinder(IDictionary<Type, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexTypeModelBinder> binders, IModelMetadataProvider metadataProvider)
    {
        _metadataProvider = metadataProvider;
        _binders = binders;
    }

    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
            throw new ArgumentNullException(nameof(bindingContext));

        var modelTypeValue = bindingContext.ValueProvider.GetValue(ModelNames.CreatePropertyModelName(bindingContext.ModelName, "WhatType"));
        if (modelTypeValue != null && modelTypeValue.FirstValue != null)
        {
            Type modelType = Type.GetType(modelTypeValue.FirstValue);
            if (this._binders.TryGetValue(modelType, out var modelBinder))
            {
                ModelBindingContext innerModelBindingContext = DefaultModelBindingContext.CreateBindingContext(
                    bindingContext.ActionContext,
                    bindingContext.ValueProvider,
                    this._metadataProvider.GetMetadataForType(modelType),
                    null,
                    bindingContext.ModelName);

                /*modelBinder*/
                this._binders.First().Value.BindModelAsync(innerModelBindingContext);

                bindingContext.Result = innerModelBindingContext.Result;
                return Task.CompletedTask;
            }
        }

       //More code
    }
}

Ответы [ 2 ]

0 голосов
/ 15 января 2019

Мне наконец удалось решить проблему. Вам не нужен поставщик. Просто следующее связующее работает

public class BlahTypeModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
            throw new ArgumentNullException(nameof(bindingContext));

        var json = ExtractRequestJson(bindingContext.ActionContext);
        var jObject = Newtonsoft.Json.Linq.JObject.Parse(json);
        var whatTypeInt = (int)jObject.SelectToken("WhatType");

        if (whatTypeInt == 1)
        {
            var obj = DeserializeObject<A>(json);
            bindingContext.Result = ModelBindingResult.Success(obj);
        }
        else if (whatTypeInt == 2)
        {
            var obj = DeserializeObject<B>(json);
            bindingContext.Result = ModelBindingResult.Success(obj);
        }
        else
        {
            bindingContext.Result = ModelBindingResult.Failed();
            return Task.CompletedTask;
        }

        return Task.CompletedTask;
    }

    private static string ExtractRequestJson(ActionContext actionContext)
    {
        var content = actionContext.HttpContext.Request.Body;
        return new StreamReader(content).ReadToEnd();
    }

    private static T DeserializeObject<T>(string json)
    {
        return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(json, new Newtonsoft.Json.JsonSerializerSettings
        {
            TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Auto
        });
    }
}
0 голосов
/ 12 января 2019

Примеры, на которые вы ссылались, чтобы использовать тип строки внешнего запроса для определения типа.

Если вы называете свое действие следующим образом : SomeAction?WhatType=YourNamespaceName.A, привязка работает как ожидалось.

bindingContext.ModelName быть пустым - это нормально, оно будет установлено после привязки модели. Вы можете установить его после установки bindingContext.Result, если хотите. Параметр WhatType взят из QueryStringValueProvider, поэтому префикс не подходит.

Как выполнить связывание абстрактной модели только на основе JSON

Для этого нам понадобится:

  1. Поставщик значений для чтения JSON и предоставления нам некоторого значения "WhatType" вместо QueryStringValueProvider.
  2. Некоторое отражение, чтобы отобразить извлеченные числа в Type -s.

1. ValueProvider

Подробную статью о создании ValueProviders можно найти здесь:

В качестве отправной точки приведем некоторый код, который успешно извлекает целые числа WhatType из тела json:

    public class BlahValueProvider : IValueProvider
{
    private readonly string _requestBody;

    public BlahValueProvider(string requestBody)
    {
        _requestBody = requestBody;
    }

    private const string PROPERTY_NAME = "WhatType";

    public bool ContainsPrefix(string prefix)
    {
        return prefix == PROPERTY_NAME;
    }

    public ValueProviderResult GetValue(string key)
    {
        if (key != PROPERTY_NAME)
            return ValueProviderResult.None;

        // parse json

        try
        {
            var json = JObject.Parse(_requestBody);
            return new ValueProviderResult(json.Value<int>("WhatType").ToString());
        }
        catch (Exception e)
        {
            // TODO: error handling
            throw;
        }
    }
}

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

        var request = context.ActionContext.HttpContext.Request;
        if (request.ContentType == "application/json")
        {
            return AddValueProviderAsync(context);
        }

        return Task.CompletedTask;
    }

    private Task AddValueProviderAsync(ValueProviderFactoryContext context)
    {
        using (StreamReader sr = new StreamReader(context.ActionContext.HttpContext.Request.Body))
        {
            string bodyString = sr.ReadToEnd();
            context.ValueProviders.Add(new BlahValueProvider(bodyString));
        }

        return Task.CompletedTask;
    }
}

Конечно, вы должны зарегистрировать эту фабрику в Startup.cs так же, как вы зарегистрировали подшивку модели. И это абсолютно не позволяет преобразовать извлеченное число в фактический тип (для этого см. Пункт 2 ниже), но если вы разместите точку останова на своей строке, начиная с if (modelTypeValue != null, вы увидите, что modelTypeValue теперь для вас, даже без отдельного GET параметр.

2. Отражение

Поймите, что вы пытаетесь определить тип на основе свойства, которое динамически вычисляется на существующем экземпляре (они не являются статическими). Хотя, зная текущую реализацию, я знаю, что это возможно (создать пустой экземпляр модели, проверить свойство WhatType, выбросить экземпляр), но это очень плохая практика, поскольку ничто не гарантирует, что свойство экземпляра является статически постоянным .

Чистым решением для этого будет атрибут , который содержит номер WhatType для этого класса. Затем мы можем подумать об этом атрибуте и построить карту, которая отображает int с Type с. Это выходит за рамки этого вопроса, но посмотрите учебник по любым пользовательским атрибутам, если вы не знакомы, и вы сможете собрать его очень быстро.

...