| File: FormMapping\Factories\CollectionConverterFactory.cs | Web Access |
| Project: src\aspnetcore\src\Components\Endpoints\src\Microsoft.AspNetCore.Components.Endpoints.csproj (Microsoft.AspNetCore.Components.Endpoints) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Components.Endpoints.FormMapping; internal class CollectionConverterFactory : IFormDataConverterFactory { public static readonly CollectionConverterFactory Instance = new(); [RequiresDynamicCode(FormMappingHelpers.RequiresDynamicCodeMessage)] [RequiresUnreferencedCode(FormMappingHelpers.RequiresUnreferencedCodeMessage)] public bool CanConvert(Type type, FormDataMapperOptions options) { var element = ResolveElementType(type); if (element == null) { return false; } if (Activator.CreateInstance(typeof(TypedCollectionConverterFactory<,>) .MakeGenericType(type, element!)) is not IFormDataConverterFactory factory) { return false; } return factory.CanConvert(type, options); } [RequiresDynamicCode(FormMappingHelpers.RequiresDynamicCodeMessage)] [RequiresUnreferencedCode(FormMappingHelpers.RequiresUnreferencedCodeMessage)] public static Type? ResolveElementType(Type type) { var enumerable = ClosedGenericMatcher.ExtractGenericInterface(type, typeof(IEnumerable<>)); if (enumerable == null && !(type.IsArray && type.GetArrayRank() == 1)) { return null; } return enumerable != null ? enumerable.GetGenericArguments()[0] : type.GetElementType()!; } [RequiresDynamicCode(FormMappingHelpers.RequiresDynamicCodeMessage)] [RequiresUnreferencedCode(FormMappingHelpers.RequiresUnreferencedCodeMessage)] public FormDataConverter CreateConverter(Type type, FormDataMapperOptions options) { ArgumentNullException.ThrowIfNull(type); ArgumentNullException.ThrowIfNull(options); // There is an assumption here that if the type is a bindable collection, it's going to implement // ICollection<T> and IEnumerable<T>. There could potentially be a type that implements ICollection<T> // multiple times for different T's explicitly, but that is not something we will support (nor something supported today). var enumerableType = ClosedGenericMatcher.ExtractGenericInterface(type, typeof(IEnumerable<>)); var elementType = enumerableType?.GetGenericArguments()[0]; // The collection converter heavily relies on generics to adapt to different collection types. // Since reflection gets a bit tricky with generics, we instead close over the generic collection and // element types to make it simpler to create the converter. var factory = Activator.CreateInstance(typeof(TypedCollectionConverterFactory<,>) .MakeGenericType(type, elementType!)) as IFormDataConverterFactory; if (factory == null) { throw new InvalidOperationException($"Unable to create converter for '{type.FullName}'."); } return factory.CreateConverter(type, options); } }