From c951cfbb106b7b3bf129f0a05d33c83b4ff5f698 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 18 Jan 2024 11:48:12 +0100 Subject: [PATCH 1/6] Replace image source service resolution logic --- .../IImageSourceServiceCollection.cs | 6 ++- .../ImageSourceServiceCollectionExtensions.cs | 2 + .../ImageSourceServiceProvider.cs | 37 +++---------------- .../ImageSourcesMauiAppBuilderExtensions.cs | 28 ++++++++++++++ 4 files changed, 41 insertions(+), 32 deletions(-) diff --git a/src/Core/src/Hosting/ImageSources/IImageSourceServiceCollection.cs b/src/Core/src/Hosting/ImageSources/IImageSourceServiceCollection.cs index 5154f0f98b02..37e8627a5398 100644 --- a/src/Core/src/Hosting/ImageSources/IImageSourceServiceCollection.cs +++ b/src/Core/src/Hosting/ImageSources/IImageSourceServiceCollection.cs @@ -1,8 +1,12 @@ -using Microsoft.Extensions.DependencyInjection; +using System; namespace Microsoft.Maui.Hosting { public interface IImageSourceServiceCollection : IMauiServiceCollection { +#pragma warning disable RS0016 // Symbol '...' is not part of the declared public API + (Type ImageSourceType, Type ImageSourceServiceType) FindImageSourceToImageSourceServiceTypeMapping(Type imageSourceType); + void AddImageSourceToImageSourceServiceTypeMapping(Type imageSourceType, Type imageSourceServiceType); +#pragma warning restore RS0016 } } \ No newline at end of file diff --git a/src/Core/src/Hosting/ImageSources/ImageSourceServiceCollectionExtensions.cs b/src/Core/src/Hosting/ImageSources/ImageSourceServiceCollectionExtensions.cs index c967e59b3d58..4eb22a8595cc 100644 --- a/src/Core/src/Hosting/ImageSources/ImageSourceServiceCollectionExtensions.cs +++ b/src/Core/src/Hosting/ImageSources/ImageSourceServiceCollectionExtensions.cs @@ -17,6 +17,7 @@ public static class ImageSourceServiceCollectionExtensions where TImageSource : IImageSource where TImageSourceService : class, IImageSourceService { + services.AddImageSourceToImageSourceServiceTypeMapping(typeof(TImageSource), typeof(IImageSourceService)); #pragma warning disable RS0030 // Do not use banned APIs, the current method is also banned services.AddSingleton, TImageSourceService>(); #pragma warning restore RS0030 // Do not use banned APIs @@ -34,6 +35,7 @@ public static class ImageSourceServiceCollectionExtensions public static IImageSourceServiceCollection AddService(this IImageSourceServiceCollection services, Func> implementationFactory) where TImageSource : IImageSource { + services.AddImageSourceToImageSourceServiceTypeMapping(typeof(TImageSource), typeof(IImageSourceService)); services.AddSingleton(provider => implementationFactory(((IImageSourceServiceProvider)provider).HostServiceProvider)); return services; diff --git a/src/Core/src/Hosting/ImageSources/ImageSourceServiceProvider.cs b/src/Core/src/Hosting/ImageSources/ImageSourceServiceProvider.cs index 4e1d7800169f..9afe5756b8c6 100644 --- a/src/Core/src/Hosting/ImageSources/ImageSourceServiceProvider.cs +++ b/src/Core/src/Hosting/ImageSources/ImageSourceServiceProvider.cs @@ -13,10 +13,12 @@ sealed class ImageSourceServiceProvider : MauiFactory, IImageSourceServiceProvid readonly ConcurrentDictionary _imageSourceCache = new ConcurrentDictionary(); readonly ConcurrentDictionary _serviceCache = new ConcurrentDictionary(); + readonly IImageSourceServiceCollection _collection; public ImageSourceServiceProvider(IImageSourceServiceCollection collection, IServiceProvider hostServiceProvider) : base(collection) { + _collection = collection; HostServiceProvider = hostServiceProvider; } @@ -25,37 +27,10 @@ public ImageSourceServiceProvider(IImageSourceServiceCollection collection, ISer public IImageSourceService? GetImageSourceService(Type imageSource) => (IImageSourceService?)GetService(GetImageSourceServiceType(imageSource)); - public Type GetImageSourceServiceType(Type imageSource) => - _serviceCache.GetOrAdd(imageSource, type => - { - var genericConcreteType = ImageSourceServiceType.MakeGenericType(type); + public Type GetImageSourceServiceType(Type imageSource) => _serviceCache.GetOrAdd(imageSource, type => + _collection.FindImageSourceToImageSourceServiceTypeMapping(type).ImageSourceServiceType); - if (genericConcreteType != null && GetServiceDescriptor(genericConcreteType) != null) - return genericConcreteType; - - return ImageSourceServiceType.MakeGenericType(GetImageSourceType(type)); - }); - - public Type GetImageSourceType(Type imageSource) => - _imageSourceCache.GetOrAdd(imageSource, CreateImageSourceTypeCacheEntry); - - Type CreateImageSourceTypeCacheEntry(Type type) - { - if (type.IsInterface) - { - if (type.GetInterface(ImageSourceInterface) != null) - return type; - } - else - { - foreach (var directInterface in type.GetInterfaces()) - { - if (directInterface.GetInterface(ImageSourceInterface) != null) - return directInterface; - } - } - - throw new InvalidOperationException($"Unable to find the image source type because none of the interfaces on {type.Name} were derived from {nameof(IImageSource)}."); - } + public Type GetImageSourceType(Type imageSource) => _imageSourceCache.GetOrAdd(imageSource, type => + _collection.FindImageSourceToImageSourceServiceTypeMapping(type).ImageSourceType); } } \ No newline at end of file diff --git a/src/Core/src/Hosting/ImageSources/ImageSourcesMauiAppBuilderExtensions.cs b/src/Core/src/Hosting/ImageSources/ImageSourcesMauiAppBuilderExtensions.cs index f9d2d31c0f4a..3bcfb882fe8a 100644 --- a/src/Core/src/Hosting/ImageSources/ImageSourcesMauiAppBuilderExtensions.cs +++ b/src/Core/src/Hosting/ImageSources/ImageSourcesMauiAppBuilderExtensions.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Maui.Hosting; @@ -51,6 +53,8 @@ internal void AddRegistration(IImageSourceServiceCollection builder) class ImageSourceServiceBuilder : MauiServiceCollection, IImageSourceServiceCollection { + private Dictionary _typeMappings { get; } = new(); + public ImageSourceServiceBuilder(IEnumerable registrationActions) { if (registrationActions != null) @@ -61,6 +65,30 @@ public ImageSourceServiceBuilder(IEnumerable registrati } } } + + public (Type ImageSourceType, Type ImageSourceServiceType) FindImageSourceToImageSourceServiceTypeMapping(Type type) + { + foreach (var mapping in _typeMappings) + { + var imageSourceType = mapping.Key; + var imageSourceServiceType = mapping.Value; + + if (imageSourceType.IsAssignableFrom(type) || type.IsAssignableFrom(imageSourceType)) + { + return (imageSourceType, imageSourceServiceType); + } + } + + throw new InvalidOperationException($"No image source service found for {type.FullName}"); + } + + public void AddImageSourceToImageSourceServiceTypeMapping(Type imageSourceType, Type imageSourceServiceType) + { + Debug.Assert(typeof(IImageSource).IsAssignableFrom(imageSourceType)); + Debug.Assert(typeof(IImageSourceService).IsAssignableFrom(imageSourceServiceType)); + + _typeMappings[imageSourceType] = imageSourceServiceType; + } } } } From 02d18ef269e4253380e00d1de8b8978e71335b1b Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 18 Jan 2024 13:10:20 +0100 Subject: [PATCH 2/6] Match the most specific image source type --- .../ImageSourcesMauiAppBuilderExtensions.cs | 39 +++++++++++++++++-- .../ImageSource/ImageSourceServiceTests.cs | 2 +- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/Core/src/Hosting/ImageSources/ImageSourcesMauiAppBuilderExtensions.cs b/src/Core/src/Hosting/ImageSources/ImageSourcesMauiAppBuilderExtensions.cs index 3bcfb882fe8a..5446247975e0 100644 --- a/src/Core/src/Hosting/ImageSources/ImageSourcesMauiAppBuilderExtensions.cs +++ b/src/Core/src/Hosting/ImageSources/ImageSourcesMauiAppBuilderExtensions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Maui.Hosting; @@ -68,20 +69,27 @@ public ImageSourceServiceBuilder(IEnumerable registrati public (Type ImageSourceType, Type ImageSourceServiceType) FindImageSourceToImageSourceServiceTypeMapping(Type type) { + List<(Type ImageSourceType, Type ImageSourceServiceType)> matches = new(); + foreach (var mapping in _typeMappings) { var imageSourceType = mapping.Key; - var imageSourceServiceType = mapping.Value; - if (imageSourceType.IsAssignableFrom(type) || type.IsAssignableFrom(imageSourceType)) { - return (imageSourceType, imageSourceServiceType); + var imageSourceServiceType = mapping.Value; + matches.Add((imageSourceType, imageSourceServiceType)); } } - throw new InvalidOperationException($"No image source service found for {type.FullName}"); + if (matches.Count == 0) + { + throw new InvalidOperationException($"Unable to find any configured {nameof(IImageSource)} corresponding to {type.Name}."); + } + + return SelectTheMostSpecificMatch(matches); } + public void AddImageSourceToImageSourceServiceTypeMapping(Type imageSourceType, Type imageSourceServiceType) { Debug.Assert(typeof(IImageSource).IsAssignableFrom(imageSourceType)); @@ -89,6 +97,29 @@ public void AddImageSourceToImageSourceServiceTypeMapping(Type imageSourceType, _typeMappings[imageSourceType] = imageSourceServiceType; } + + private static (Type ImageSourceType, Type ImageSourceServiceType) SelectTheMostSpecificMatch(List<(Type ImageSourceType, Type ImageSourceServiceType)> matches) + { + var bestImageSourceTypeMatch = matches[0].ImageSourceType; + var bestImageSourceServiceTypeMatch = matches[0].ImageSourceServiceType; + + foreach (var (imageSourceType, imageSourceServiceType) in matches.Skip(1)) + { + if (imageSourceType.IsAssignableFrom(bestImageSourceTypeMatch) + || bestImageSourceTypeMatch.IsInterface && imageSourceType.IsClass) + { + bestImageSourceTypeMatch = imageSourceType; + bestImageSourceServiceTypeMatch = imageSourceServiceType; + } + + // TODO we could still improve this to detect truly ambiguous cases, like: + // - FileImageSourceA (implements IFileImageSource) -> X + // - FileImageSourceB (implements IFileImageSource) -> Y + // -> find closest match to `IFileImageSource` + } + + return (bestImageSourceTypeMatch, bestImageSourceServiceTypeMatch); + } } } } diff --git a/src/Core/tests/DeviceTests/Services/ImageSource/ImageSourceServiceTests.cs b/src/Core/tests/DeviceTests/Services/ImageSource/ImageSourceServiceTests.cs index 759a3dc934fe..798c7512d777 100644 --- a/src/Core/tests/DeviceTests/Services/ImageSource/ImageSourceServiceTests.cs +++ b/src/Core/tests/DeviceTests/Services/ImageSource/ImageSourceServiceTests.cs @@ -48,7 +48,7 @@ public void ThrowsWhenMissingService() var ex = Assert.Throws(() => provider.GetRequiredImageSourceService(new FileImageSourceStub())); - Assert.Contains(nameof(IFileImageSource), ex.Message, StringComparison.Ordinal); + Assert.Contains(nameof(FileImageSourceStub), ex.Message, StringComparison.Ordinal); } [Fact] From b55ab2787a52ab428e06b9c649a42f8b4f8db1f1 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Mon, 22 Jan 2024 10:30:26 +0100 Subject: [PATCH 3/6] Move code to a sparate class to avoid exposing new public API --- .../IImageSourceServiceCollection.cs | 4 - .../ImageSourceServiceCollectionExtensions.cs | 13 +- .../ImageSourceServiceProvider.cs | 12 +- ...geSourceToImageSourceServiceTypeMapping.cs | 81 ++++++++++++ .../ImageSourcesMauiAppBuilderExtensions.cs | 56 --------- ...rceToImageSourceServiceTypeMappingTests.cs | 115 ++++++++++++++++++ 6 files changed, 212 insertions(+), 69 deletions(-) create mode 100644 src/Core/src/Hosting/ImageSources/ImageSourceToImageSourceServiceTypeMapping.cs create mode 100644 src/Core/tests/UnitTests/ImageSource/ImageSourceToImageSourceServiceTypeMappingTests.cs diff --git a/src/Core/src/Hosting/ImageSources/IImageSourceServiceCollection.cs b/src/Core/src/Hosting/ImageSources/IImageSourceServiceCollection.cs index 37e8627a5398..58d5b822e6e9 100644 --- a/src/Core/src/Hosting/ImageSources/IImageSourceServiceCollection.cs +++ b/src/Core/src/Hosting/ImageSources/IImageSourceServiceCollection.cs @@ -4,9 +4,5 @@ namespace Microsoft.Maui.Hosting { public interface IImageSourceServiceCollection : IMauiServiceCollection { -#pragma warning disable RS0016 // Symbol '...' is not part of the declared public API - (Type ImageSourceType, Type ImageSourceServiceType) FindImageSourceToImageSourceServiceTypeMapping(Type imageSourceType); - void AddImageSourceToImageSourceServiceTypeMapping(Type imageSourceType, Type imageSourceServiceType); -#pragma warning restore RS0016 } } \ No newline at end of file diff --git a/src/Core/src/Hosting/ImageSources/ImageSourceServiceCollectionExtensions.cs b/src/Core/src/Hosting/ImageSources/ImageSourceServiceCollectionExtensions.cs index 4eb22a8595cc..4a561d948664 100644 --- a/src/Core/src/Hosting/ImageSources/ImageSourceServiceCollectionExtensions.cs +++ b/src/Core/src/Hosting/ImageSources/ImageSourceServiceCollectionExtensions.cs @@ -17,7 +17,7 @@ public static class ImageSourceServiceCollectionExtensions where TImageSource : IImageSource where TImageSourceService : class, IImageSourceService { - services.AddImageSourceToImageSourceServiceTypeMapping(typeof(TImageSource), typeof(IImageSourceService)); + services.GetImageSourceTypeMapping().Add(); #pragma warning disable RS0030 // Do not use banned APIs, the current method is also banned services.AddSingleton, TImageSourceService>(); #pragma warning restore RS0030 // Do not use banned APIs @@ -35,10 +35,19 @@ public static class ImageSourceServiceCollectionExtensions public static IImageSourceServiceCollection AddService(this IImageSourceServiceCollection services, Func> implementationFactory) where TImageSource : IImageSource { - services.AddImageSourceToImageSourceServiceTypeMapping(typeof(TImageSource), typeof(IImageSourceService)); + services.GetImageSourceTypeMapping().Add(); services.AddSingleton(provider => implementationFactory(((IImageSourceServiceProvider)provider).HostServiceProvider)); return services; } + + internal static Type FindImageSourceServiceType(this IImageSourceServiceCollection services, Type imageSourceType) + => services.GetImageSourceTypeMapping().FindImageSourceServiceType(imageSourceType); + + internal static Type FindImageSourceType(this IImageSourceServiceCollection services, Type imageSourceType) + => services.GetImageSourceTypeMapping().FindImageSourceType(imageSourceType); + + private static ImageSourceToImageSourceServiceTypeMapping GetImageSourceTypeMapping(this IImageSourceServiceCollection services) + => ImageSourceToImageSourceServiceTypeMapping.GetInstance(services); } } \ No newline at end of file diff --git a/src/Core/src/Hosting/ImageSources/ImageSourceServiceProvider.cs b/src/Core/src/Hosting/ImageSources/ImageSourceServiceProvider.cs index 9afe5756b8c6..e14cd8aea72d 100644 --- a/src/Core/src/Hosting/ImageSources/ImageSourceServiceProvider.cs +++ b/src/Core/src/Hosting/ImageSources/ImageSourceServiceProvider.cs @@ -8,9 +8,6 @@ namespace Microsoft.Maui.Hosting { sealed class ImageSourceServiceProvider : MauiFactory, IImageSourceServiceProvider { - static readonly string ImageSourceInterface = typeof(IImageSource).FullName!; - static readonly Type ImageSourceServiceType = typeof(IImageSourceService<>); - readonly ConcurrentDictionary _imageSourceCache = new ConcurrentDictionary(); readonly ConcurrentDictionary _serviceCache = new ConcurrentDictionary(); readonly IImageSourceServiceCollection _collection; @@ -27,10 +24,11 @@ public ImageSourceServiceProvider(IImageSourceServiceCollection collection, ISer public IImageSourceService? GetImageSourceService(Type imageSource) => (IImageSourceService?)GetService(GetImageSourceServiceType(imageSource)); - public Type GetImageSourceServiceType(Type imageSource) => _serviceCache.GetOrAdd(imageSource, type => - _collection.FindImageSourceToImageSourceServiceTypeMapping(type).ImageSourceServiceType); + public Type GetImageSourceServiceType(Type imageSource) => + _serviceCache.GetOrAdd(imageSource, _collection.FindImageSourceServiceType); - public Type GetImageSourceType(Type imageSource) => _imageSourceCache.GetOrAdd(imageSource, type => - _collection.FindImageSourceToImageSourceServiceTypeMapping(type).ImageSourceType); + // TODO: this public method isn't used anywhere in the MAUI codebase anymore. Make it obsolete? + public Type GetImageSourceType(Type imageSource) => + _imageSourceCache.GetOrAdd(imageSource, _collection.FindImageSourceType); } } \ No newline at end of file diff --git a/src/Core/src/Hosting/ImageSources/ImageSourceToImageSourceServiceTypeMapping.cs b/src/Core/src/Hosting/ImageSources/ImageSourceToImageSourceServiceTypeMapping.cs new file mode 100644 index 000000000000..b0ee8c263db9 --- /dev/null +++ b/src/Core/src/Hosting/ImageSources/ImageSourceToImageSourceServiceTypeMapping.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +using TypePair = (System.Type ImageSource, System.Type ImageSourceService); + +namespace Microsoft.Maui.Hosting +{ + internal sealed class ImageSourceToImageSourceServiceTypeMapping + { + private static readonly ConcurrentDictionary s_instances = new(); + + internal static ImageSourceToImageSourceServiceTypeMapping GetInstance(IImageSourceServiceCollection collection) => + s_instances.GetOrAdd(collection, static _ => new ImageSourceToImageSourceServiceTypeMapping()); + + private ConcurrentDictionary _typeMappings { get; } = new(); + + public void Add() where TImageSource : IImageSource => + _typeMappings[typeof(TImageSource)] = typeof(IImageSourceService); + + public Type FindImageSourceType(Type imageSourceType) => + FindImageSourceToImageSourceServiceTypeMapping(imageSourceType).ImageSource; + + public Type FindImageSourceServiceType(Type imageSourceType) => + FindImageSourceToImageSourceServiceTypeMapping(imageSourceType).ImageSourceService; + + private TypePair FindImageSourceToImageSourceServiceTypeMapping(Type type) + { + Debug.Assert(typeof(IImageSource).IsAssignableFrom(type)); + + // If there's an exact match for the type, just return it. + if (_typeMappings.TryGetValue(type, out var imageSourceService)) + { + return (type, imageSourceService); + } + + List matches = new(); + foreach (var mapping in _typeMappings) + { + var imageSource = mapping.Key; + if (imageSource.IsAssignableFrom(type) || type.IsAssignableFrom(imageSource)) + { + matches.Add((imageSource, mapping.Value)); + } + } + + return SelectBestMatch(matches, type); + } + + private static TypePair SelectBestMatch(List matches, Type type) + { + if (matches.Count == 0) + { + throw new InvalidOperationException($"Unable to find any configured {nameof(IImageSource)} corresponding to {type.Name}."); + } + + var bestImageSourceMatch = matches[0].ImageSource; + var bestImageSourceServiceMatch = matches[0].ImageSourceService; + + for (int i = 1; i < matches.Count; i++) + { + var (imageSource, imageSourceService) = matches[i]; + + if (!bestImageSourceMatch.IsAssignableFrom(imageSource) && !imageSource.IsAssignableFrom(bestImageSourceMatch)) + { + throw new InvalidOperationException($"Ambiguous image source services for {type} ({bestImageSourceMatch} and {imageSource})."); + } + + if (bestImageSourceMatch.IsAssignableFrom(imageSource) || (bestImageSourceMatch.IsInterface && imageSource.IsClass)) + { + bestImageSourceMatch = imageSource; + bestImageSourceServiceMatch = imageSourceService; + } + } + + return (bestImageSourceMatch, bestImageSourceServiceMatch); + } + } +} diff --git a/src/Core/src/Hosting/ImageSources/ImageSourcesMauiAppBuilderExtensions.cs b/src/Core/src/Hosting/ImageSources/ImageSourcesMauiAppBuilderExtensions.cs index 5446247975e0..8ffa6362501a 100644 --- a/src/Core/src/Hosting/ImageSources/ImageSourcesMauiAppBuilderExtensions.cs +++ b/src/Core/src/Hosting/ImageSources/ImageSourcesMauiAppBuilderExtensions.cs @@ -54,8 +54,6 @@ internal void AddRegistration(IImageSourceServiceCollection builder) class ImageSourceServiceBuilder : MauiServiceCollection, IImageSourceServiceCollection { - private Dictionary _typeMappings { get; } = new(); - public ImageSourceServiceBuilder(IEnumerable registrationActions) { if (registrationActions != null) @@ -66,60 +64,6 @@ public ImageSourceServiceBuilder(IEnumerable registrati } } } - - public (Type ImageSourceType, Type ImageSourceServiceType) FindImageSourceToImageSourceServiceTypeMapping(Type type) - { - List<(Type ImageSourceType, Type ImageSourceServiceType)> matches = new(); - - foreach (var mapping in _typeMappings) - { - var imageSourceType = mapping.Key; - if (imageSourceType.IsAssignableFrom(type) || type.IsAssignableFrom(imageSourceType)) - { - var imageSourceServiceType = mapping.Value; - matches.Add((imageSourceType, imageSourceServiceType)); - } - } - - if (matches.Count == 0) - { - throw new InvalidOperationException($"Unable to find any configured {nameof(IImageSource)} corresponding to {type.Name}."); - } - - return SelectTheMostSpecificMatch(matches); - } - - - public void AddImageSourceToImageSourceServiceTypeMapping(Type imageSourceType, Type imageSourceServiceType) - { - Debug.Assert(typeof(IImageSource).IsAssignableFrom(imageSourceType)); - Debug.Assert(typeof(IImageSourceService).IsAssignableFrom(imageSourceServiceType)); - - _typeMappings[imageSourceType] = imageSourceServiceType; - } - - private static (Type ImageSourceType, Type ImageSourceServiceType) SelectTheMostSpecificMatch(List<(Type ImageSourceType, Type ImageSourceServiceType)> matches) - { - var bestImageSourceTypeMatch = matches[0].ImageSourceType; - var bestImageSourceServiceTypeMatch = matches[0].ImageSourceServiceType; - - foreach (var (imageSourceType, imageSourceServiceType) in matches.Skip(1)) - { - if (imageSourceType.IsAssignableFrom(bestImageSourceTypeMatch) - || bestImageSourceTypeMatch.IsInterface && imageSourceType.IsClass) - { - bestImageSourceTypeMatch = imageSourceType; - bestImageSourceServiceTypeMatch = imageSourceServiceType; - } - - // TODO we could still improve this to detect truly ambiguous cases, like: - // - FileImageSourceA (implements IFileImageSource) -> X - // - FileImageSourceB (implements IFileImageSource) -> Y - // -> find closest match to `IFileImageSource` - } - - return (bestImageSourceTypeMatch, bestImageSourceServiceTypeMatch); - } } } } diff --git a/src/Core/tests/UnitTests/ImageSource/ImageSourceToImageSourceServiceTypeMappingTests.cs b/src/Core/tests/UnitTests/ImageSource/ImageSourceToImageSourceServiceTypeMappingTests.cs new file mode 100644 index 000000000000..f32a1f1a2e1f --- /dev/null +++ b/src/Core/tests/UnitTests/ImageSource/ImageSourceToImageSourceServiceTypeMappingTests.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Maui.Hosting; +using Xunit; + +namespace Microsoft.Maui.UnitTests.ImageSource +{ + [Category(TestCategory.Core, TestCategory.ImageSource)] + public class ImageSourceToImageSourceServiceTypeMappingTests + { + [Fact] + public void FindsCorrespondingImageSourceType() + { + var mapping = new ImageSourceToImageSourceServiceTypeMapping(); + mapping.Add(); + mapping.Add(); + + var type = mapping.FindImageSourceType(typeof(StreamImageSourceStub)); + Assert.Equal(typeof(IStreamImageSource), type); + } + + [Fact] + public void FindsCorrespondingImageSourceServiceType() + { + var mapping = new ImageSourceToImageSourceServiceTypeMapping(); + mapping.Add(); + mapping.Add(); + + var type = mapping.FindImageSourceServiceType(typeof(StreamImageSourceStub)); + Assert.Equal(typeof(IImageSourceService), type); + } + + [Fact] + public void PrefersConcreteTypesOverInterfaces() + { + var mapping = new ImageSourceToImageSourceServiceTypeMapping(); + mapping.Add(); + mapping.Add(); + + var imageSourceType = mapping.FindImageSourceType(typeof(IStreamImageSource)); + var imageSourceServiceType = mapping.FindImageSourceServiceType(typeof(IStreamImageSource)); + + Assert.Equal(typeof(StreamImageSourceStub), imageSourceType); + Assert.Equal(typeof(IImageSourceService), imageSourceServiceType); + } + + [Fact] + public void PrefersExactMatches() + { + var mapping = new ImageSourceToImageSourceServiceTypeMapping(); + mapping.Add(); + mapping.Add(); + + var imageSourceType = mapping.FindImageSourceType(typeof(IStreamImageSource)); + var imageSourceServiceType = mapping.FindImageSourceServiceType(typeof(IStreamImageSource)); + + Assert.Equal(typeof(IStreamImageSource), imageSourceType); + Assert.Equal(typeof(IImageSourceService), imageSourceServiceType); + } + + [Fact] + public void FindsMoreDerivedTypes() + { + var mapping = new ImageSourceToImageSourceServiceTypeMapping(); + mapping.Add(); + mapping.Add(); + + var imageSourceType = mapping.FindImageSourceType(typeof(IStreamImageSource)); + var imageSourceServiceType = mapping.FindImageSourceServiceType(typeof(IStreamImageSource)); + + Assert.Equal(typeof(DerivedStreamImageSourceStub), imageSourceType); + Assert.Equal(typeof(IImageSourceService), imageSourceServiceType); + } + + [Fact] + public void ThrowsInCaseOfAmbiguity() + { + var mapping = new ImageSourceToImageSourceServiceTypeMapping(); + mapping.Add(); + mapping.Add(); + + Assert.Throws(() => mapping.FindImageSourceType(typeof(IFileImageSource))); + } + + private interface ICustomStreamImageSource : IStreamImageSource + { + } + + private class StreamImageSourceStub : ICustomStreamImageSource + { + public Task GetStreamAsync(CancellationToken cancellationToken = default) => Task.FromException(new NotImplementedException()); + public bool IsEmpty => true; + } + + private class DerivedStreamImageSourceStub : StreamImageSourceStub + { + } + + private class FileImageSourceA : IFileImageSource + { + public string File => throw new NotImplementedException(); + public bool IsEmpty => true; + } + + private class FileImageSourceB : IFileImageSource + { + public string File => throw new NotImplementedException(); + public bool IsEmpty => true; + } + } +} From e9dd56f0acdcdf9af8acf9e98f84d5ce33d226df Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Mon, 22 Jan 2024 10:30:51 +0100 Subject: [PATCH 4/6] Remove fixed trimming warnings --- .../Utilities/BuildWarningsUtilities.cs | 42 ------------------- 1 file changed, 42 deletions(-) diff --git a/src/TestUtils/src/Microsoft.Maui.IntegrationTests/Utilities/BuildWarningsUtilities.cs b/src/TestUtils/src/Microsoft.Maui.IntegrationTests/Utilities/BuildWarningsUtilities.cs index 496b7dae5f2d..095ce399d8e6 100644 --- a/src/TestUtils/src/Microsoft.Maui.IntegrationTests/Utilities/BuildWarningsUtilities.cs +++ b/src/TestUtils/src/Microsoft.Maui.IntegrationTests/Utilities/BuildWarningsUtilities.cs @@ -398,48 +398,6 @@ public static void AssertWarnings(this List actualWarnings, Lis } }, new WarningsPerFile - { - File = "src/Core/src/Hosting/ImageSources/ImageSourceServiceProvider.cs", - WarningsPerCode = new List - { - new WarningsPerCode - { - Code = "IL2070", - Messages = new List - { - "Microsoft.Maui.Hosting.ImageSourceServiceProvider.CreateImageSourceTypeCacheEntry(Type): 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.Interfaces' in call to 'System.Type.GetInterface(String)'. The parameter 'type' of method 'Microsoft.Maui.Hosting.ImageSourceServiceProvider.CreateImageSourceTypeCacheEntry(Type)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.", - "Microsoft.Maui.Hosting.ImageSourceServiceProvider.CreateImageSourceTypeCacheEntry(Type): 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.Interfaces' in call to 'System.Type.GetInterfaces()'. The parameter 'type' of method 'Microsoft.Maui.Hosting.ImageSourceServiceProvider.CreateImageSourceTypeCacheEntry(Type)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.", - } - }, - new WarningsPerCode - { - Code = "IL2065", - Messages = new List - { - "Microsoft.Maui.Hosting.ImageSourceServiceProvider.CreateImageSourceTypeCacheEntry(Type): Value passed to implicit 'this' parameter of method 'System.Type.GetInterface(String)' can not be statically determined and may not meet 'DynamicallyAccessedMembersAttribute' requirements.", - } - }, - new WarningsPerCode - { - Code = "IL2055", - Messages = new List - { - "Microsoft.Maui.Hosting.ImageSourceServiceProvider.b__9_0(Type): Call to 'System.Type.MakeGenericType(Type[])' can not be statically analyzed. It's not possible to guarantee the availability of requirements of the generic type.", - "Microsoft.Maui.Hosting.ImageSourceServiceProvider.b__9_0(Type): Call to 'System.Type.MakeGenericType(Type[])' can not be statically analyzed. It's not possible to guarantee the availability of requirements of the generic type.", - } - }, - new WarningsPerCode - { - Code = "IL3050", - Messages = new List - { - "Microsoft.Maui.Hosting.ImageSourceServiceProvider.b__9_0(Type): Using member 'System.Type.MakeGenericType(Type[])' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. The native code for this instantiation might not be available at runtime.", - "Microsoft.Maui.Hosting.ImageSourceServiceProvider.b__9_0(Type): Using member 'System.Type.MakeGenericType(Type[])' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. The native code for this instantiation might not be available at runtime.", - } - }, - } - }, - new WarningsPerFile { File = "src/Core/src/Platform/ReflectionExtensions.cs", WarningsPerCode = new List From bd98ddd7e694f5ba63ba4e8f6c33c3279ec3c3bb Mon Sep 17 00:00:00 2001 From: GitHub Actions Autoformatter Date: Mon, 22 Jan 2024 09:39:34 +0000 Subject: [PATCH 5/6] Auto-format source code --- .../ImageSourceToImageSourceServiceTypeMapping.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Core/src/Hosting/ImageSources/ImageSourceToImageSourceServiceTypeMapping.cs b/src/Core/src/Hosting/ImageSources/ImageSourceToImageSourceServiceTypeMapping.cs index b0ee8c263db9..c35bff6f0c83 100644 --- a/src/Core/src/Hosting/ImageSources/ImageSourceToImageSourceServiceTypeMapping.cs +++ b/src/Core/src/Hosting/ImageSources/ImageSourceToImageSourceServiceTypeMapping.cs @@ -50,7 +50,7 @@ private TypePair FindImageSourceToImageSourceServiceTypeMapping(Type type) } private static TypePair SelectBestMatch(List matches, Type type) - { + { if (matches.Count == 0) { throw new InvalidOperationException($"Unable to find any configured {nameof(IImageSource)} corresponding to {type.Name}."); @@ -60,8 +60,8 @@ private static TypePair SelectBestMatch(List matches, Type type) var bestImageSourceServiceMatch = matches[0].ImageSourceService; for (int i = 1; i < matches.Count; i++) - { - var (imageSource, imageSourceService) = matches[i]; + { + var (imageSource, imageSourceService) = matches[i]; if (!bestImageSourceMatch.IsAssignableFrom(imageSource) && !imageSource.IsAssignableFrom(bestImageSourceMatch)) { From 199946bec44fb5e73a4eaa99678879917d25d84c Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Mon, 22 Jan 2024 10:45:59 +0100 Subject: [PATCH 6/6] Revert unnecessary changes --- .../src/Hosting/ImageSources/IImageSourceServiceCollection.cs | 2 +- .../src/Hosting/ImageSources/ImageSourceServiceProvider.cs | 1 - .../ImageSources/ImageSourcesMauiAppBuilderExtensions.cs | 3 --- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Core/src/Hosting/ImageSources/IImageSourceServiceCollection.cs b/src/Core/src/Hosting/ImageSources/IImageSourceServiceCollection.cs index 58d5b822e6e9..5154f0f98b02 100644 --- a/src/Core/src/Hosting/ImageSources/IImageSourceServiceCollection.cs +++ b/src/Core/src/Hosting/ImageSources/IImageSourceServiceCollection.cs @@ -1,4 +1,4 @@ -using System; +using Microsoft.Extensions.DependencyInjection; namespace Microsoft.Maui.Hosting { diff --git a/src/Core/src/Hosting/ImageSources/ImageSourceServiceProvider.cs b/src/Core/src/Hosting/ImageSources/ImageSourceServiceProvider.cs index e14cd8aea72d..31f879739a0b 100644 --- a/src/Core/src/Hosting/ImageSources/ImageSourceServiceProvider.cs +++ b/src/Core/src/Hosting/ImageSources/ImageSourceServiceProvider.cs @@ -27,7 +27,6 @@ public ImageSourceServiceProvider(IImageSourceServiceCollection collection, ISer public Type GetImageSourceServiceType(Type imageSource) => _serviceCache.GetOrAdd(imageSource, _collection.FindImageSourceServiceType); - // TODO: this public method isn't used anywhere in the MAUI codebase anymore. Make it obsolete? public Type GetImageSourceType(Type imageSource) => _imageSourceCache.GetOrAdd(imageSource, _collection.FindImageSourceType); } diff --git a/src/Core/src/Hosting/ImageSources/ImageSourcesMauiAppBuilderExtensions.cs b/src/Core/src/Hosting/ImageSources/ImageSourcesMauiAppBuilderExtensions.cs index 8ffa6362501a..f9d2d31c0f4a 100644 --- a/src/Core/src/Hosting/ImageSources/ImageSourcesMauiAppBuilderExtensions.cs +++ b/src/Core/src/Hosting/ImageSources/ImageSourcesMauiAppBuilderExtensions.cs @@ -1,8 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Maui.Hosting;