|
// 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.AspNetCore.Components.Endpoints.FormMapping.Metadata;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Components.Endpoints.FormMapping;
// This factory is registered last, which means, dictionaries and collections, have already
// been processed by the time we get here.
internal class ComplexTypeConverterFactory(FormDataMapperOptions options, ILoggerFactory loggerFactory) : IFormDataConverterFactory
{
internal FormDataMetadataFactory MetadataFactory { get; } = new FormDataMetadataFactory(options.Factories, loggerFactory);
[RequiresDynamicCode(FormMappingHelpers.RequiresDynamicCodeMessage)]
[RequiresUnreferencedCode(FormMappingHelpers.RequiresUnreferencedCodeMessage)]
public bool CanConvert(Type type, FormDataMapperOptions options)
{
// Create the metadata for the type. This walks the graph and creates metadata for all the types
// in the reference graph, detecting and identifying recursive types.
var metadata = MetadataFactory.GetOrCreateMetadataFor(type, options);
// If we can create metadata for the type, then we can convert it.
return metadata != null;
}
// We are going to compile a function that maps all the properties for the type.
// Beware that the code below is not the actual exact code, just a simplification to understand what is happening at a high level.
// The general flow is as follows. For a type like Address { Street, City, Country, ZipCode }
// we will generate a function that looks like:
// public bool TryRead(ref FormDataReader reader, Type type, FormDataSerializerOptions options, out Address? result, out bool found)
// {
// bool foundProperty;
// bool succeeded = true;
// string street;
// string city;
// string country;
// string zipCode;
// FormDataConveter<string> streetConverter;
// FormDataConveter<string> cityConverter;
// FormDataConveter<string> countryConverter;
// FormDataConveter<string> zipCodeConverter;
// var streetConverter = options.ResolveConverter(typeof(string));
// reader.PushPrefix("Street");
// succeeded &= streetConverter.TryRead(ref reader, typeof(string), options, out street, out foundProperty);
// found ||= foundProperty;
// reader.PopPrefix("Street");
//
// var cityConverter = options.ResolveConverter(typeof(string));
// reader.PushPrefix("City");
// succeeded &= ciryConverter.TryRead(ref reader, typeof(string), options, out street, out foundProperty);
// found ||= foundProperty;
// reader.PopPrefix("City");
//
// var countryConverter = options.ResolveConverter(typeof(string));
// reader.PushPrefix("Country");
// succeeded &= countryConverter.TryRead(ref reader, typeof(string), options, out street, out foundProperty);
// found ||= foundProperty;
// reader.PopPrefix("Country");
//
// var zipCodeConverter = options.ResolveConverter(typeof(string));
// reader.PushPrefix("ZipCode");
// succeeded &= zipCodeConverter.TryRead(ref reader, typeof(string), options, out street, out foundProperty);
// found ||= foundProperty;
// reader.PopPrefix("ZipCode");
//
// if(found)
// {
// result = new Address();
// result.Street = street;
// result.City = city;
// result.Country = country;
// result.ZipCode = zipCode;
// }
// else
// {
// result = null;
// }
//
// return succeeded;
// }
//
// The actual blocks above are going to be generated using System.Linq.Expressions.
// Instead of resolving the property converters every time, we might consider caching the converters in a dictionary and passing an
// extra parameter to the function with them in it.
// The final converter is something like
// internal class CompiledComplexTypeConverter
// (ConverterDelegate<FormDataReader, Type, FormDataSerializerOptions, out object, out bool> converterFunc)
// {
// public bool TryRead(ref FormDataReader reader, Type type, FormDataSerializerOptions options, out object? result, out bool found)
// {
// return converterFunc(ref reader, type, options, out result, out found);
// }
// }
[RequiresDynamicCode(FormMappingHelpers.RequiresDynamicCodeMessage)]
[RequiresUnreferencedCode(FormMappingHelpers.RequiresUnreferencedCodeMessage)]
public FormDataConverter CreateConverter(Type type, FormDataMapperOptions options)
{
if (Activator.CreateInstance(typeof(ComplexTypeExpressionConverterFactory<>).MakeGenericType(type), MetadataFactory)
is not ComplexTypeExpressionConverterFactory factory)
{
throw new InvalidOperationException($"Could not create a converter factory for type {type}.");
}
return factory.CreateConverter(type, options);
}
}
|