Псевдоним свойства модели для другого имени в asp.net-core - PullRequest
1 голос
/ 27 мая 2019

Я хочу присвоить псевдониму свойства моего объекта запроса, чтобы оба эти запроса работали и оба направлялись к одному и тому же контроллеру:

myapi/cars?colors=red&colors=blue&colors=green и myapi/cars?c=red&c=blue&c=green

для объекта запроса:

public class CarRequest {
    Colors string[] { get; set; }
}

Кто-нибудь смог использовать новые ModelBinder для решения этой проблемы без необходимости писать ModelBindings с нуля?

Здесь аналогичная проблема для более старой версии asp.net и также здесь

1 Ответ

1 голос
/ 27 мая 2019

Я написал модель для этого:

EDIT: Вот репо на github . Есть два пакета nuget, которые вы можете добавить в свой код, чтобы решить эту проблему. Подробности в файле readme

В основном он заменяет ComplexTypeModelBinder (я слишком труслив, чтобы заменить его, но я ставлю его впереди с идентичными критериями), за исключением того, что он пытается использовать мой новый атрибут для расширения полей, которые он ищет .

Связующее:

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MYDOMAIN.Client;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using Microsoft.Extensions.Logging;

namespace MYDOMAIN.Web.AliasModelBinder
{
    public class AliasModelBinder : ComplexTypeModelBinder
    {
        public AliasModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders, ILoggerFactory loggerFactory,
            bool allowValidatingTopLevelNodes)
            : base(propertyBinders, loggerFactory, allowValidatingTopLevelNodes)
        {
        }

        protected override Task BindProperty(ModelBindingContext bindingContext)
        {
            var containerType = bindingContext.ModelMetadata.ContainerType;
            if (containerType != null)
            {
                var propertyType = containerType.GetProperty(bindingContext.ModelMetadata.PropertyName);
                var attributes = propertyType.GetCustomAttributes(true);

                var aliasAttributes = attributes.OfType<BindingAliasAttribute>().ToArray();
                if (aliasAttributes.Any())
                {
                    bindingContext.ValueProvider = new AliasValueProvider(bindingContext.ValueProvider,
                        bindingContext.ModelName, aliasAttributes.Select(attr => attr.Alias));
                }
            }

            return base.BindProperty(bindingContext);
        }
    }
}

Поставщик:

using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using Microsoft.Extensions.Logging;

namespace MYDOMAIN.Web.AliasModelBinder
{
    public class AliasModelBinderProvider : IModelBinderProvider
    {
        public IModelBinder GetBinder(ModelBinderProviderContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType)
            {
                var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();

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

                return new AliasModelBinder(propertyBinders,
                    (ILoggerFactory) context.Services.GetService(typeof(ILoggerFactory)), true);
            }

            return null;
        }

        /// <summary>
        /// Setup the AliasModelBinderProvider Mvc project to use BindingAlias attribute, to allow for aliasing property names in query strings
        /// </summary>
        public static void Configure(MvcOptions options)
        {
            // Place in front of ComplexTypeModelBinderProvider to replace this binder type in practice
            for (int i = 0; i < options.ModelBinderProviders.Count; i++)
            {
                if (options.ModelBinderProviders[i] is ComplexTypeModelBinderProvider)
                {
                    options.ModelBinderProviders.Insert(i, new AliasModelBinderProvider());
                    return;
                }
            }

            options.ModelBinderProviders.Add(new AliasModelBinderProvider());
        }
    }
}

Значение провайдера:

using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Primitives;

namespace MYDOMAIN.Web.AliasModelBinder
{
    public class AliasValueProvider : IValueProvider
    {
        private readonly IValueProvider _provider;
        private readonly string _originalName;
        private readonly string[] _allNamesToBind;

        public AliasValueProvider(IValueProvider provider, string originalName, IEnumerable<string> aliases)
        {
            _provider = provider;
            _originalName = originalName;
            _allNamesToBind = new[] {_originalName}.Concat(aliases).ToArray();
        }

        public bool ContainsPrefix(string prefix)
        {
            if (prefix == _originalName)
            {
                return _allNamesToBind.Any(_provider.ContainsPrefix);
            }

            return _provider.ContainsPrefix(prefix);
        }

        public ValueProviderResult GetValue(string key)
        {
            if (key == _originalName)
            {
                var results = _allNamesToBind.Select(alias => _provider.GetValue(alias)).ToArray();
                StringValues values = results.Aggregate(values, (current, r) => StringValues.Concat(current, r.Values));

                return new ValueProviderResult(values, results.First().Culture);
            }

            return _provider.GetValue(key);
        }
    }
}

И атрибут для входа / ссылки клиентского проекта

using System;

namespace MYDOMAIN.Client
{
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
    public class BindingAliasAttribute : Attribute
    {
        public string Alias { get; }

        public BindingAliasAttribute(string alias)
        {
            Alias = alias;
        }
    }
}

Настраивается в Startup.cs

public void ConfigureServices(IServiceCollection services)
        {
            services
                ...
                .AddMvcOptions(options =>
                {
                    AliasModelBinderProvider.Configure(options);
                    ...
                })
                ...

Использование:

public class SomeRequest
{
    [BindingAlias("f")]
    public long[] SomeVeryLongNameForSomeKindOfFoo{ get; set; }
}

приводит к запросу, который выглядит так:

api/controller/action?SomeVeryLongNameForSomeKindOfFoo=1&SomeVeryLongNameForSomeKindOfFoo=2

или

api/controller/action?f=1&f=2

Я поместил большинство вещей в свой веб-проект, а атрибут - в свой клиентский проект.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...