Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow configuring current UI framework to avoid exceptions during dependency registrations #2396

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 49 additions & 8 deletions src/ReactiveUI.Tests/Resolvers/DependencyResolverTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public void AllDefaultServicesShouldBeRegistered()
{
using (_resolver.WithResolver())
{
foreach (var shouldRegistered in GetServicesThatShouldBeRegistered())
foreach (var shouldRegistered in GetServicesThatShouldBeRegistered(DependencyResolverMixins.DefaultRegistrationNamespaces))
{
IEnumerable<object> resolvedServices = _resolver.GetServices(shouldRegistered.Key);
Assert.Equal(shouldRegistered.Value.Count, resolvedServices.Count());
Expand All @@ -41,12 +41,58 @@ public void AllDefaultServicesShouldBeRegistered()
}
}

[Fact]
public void AllDefaultServicesShouldBeRegisteredPerRegistrationNamespace()
{
using (_resolver.WithResolver())
{
var registrationNamespaces = new[] { DependencyResolverMixins.RegistrationNamespace.Wpf };

DependencyResolverMixins.SetRegistrationNamespaces(registrationNamespaces);

foreach (var shouldRegistered in GetServicesThatShouldBeRegistered(registrationNamespaces))
{
IEnumerable<object> resolvedServices = _resolver.GetServices(shouldRegistered.Key);
Assert.Equal(shouldRegistered.Value.Count, resolvedServices.Count());
foreach (Type implementationType in shouldRegistered.Value)
{
var isRegistered = resolvedServices.Any(rs => rs.GetType() == implementationType);
Assert.Equal(true, isRegistered);
}
}

DependencyResolverMixins.SetRegistrationNamespaces(DependencyResolverMixins.DefaultRegistrationNamespaces.ToArray());
}
}

public void Dispose()
{
_resolver?.Dispose();
}

private static Dictionary<Type, List<Type>> GetServicesThatShouldBeRegistered()
private static IEnumerable<string> GetServiceRegistrationTypeNames(
IEnumerable<DependencyResolverMixins.RegistrationNamespace> registrationNamespaces)
{
foreach (DependencyResolverMixins.RegistrationNamespace registrationNamespace in registrationNamespaces)
{
if (registrationNamespace == DependencyResolverMixins.RegistrationNamespace.Wpf)
{
yield return "ReactiveUI.Wpf.Registrations, ReactiveUI.Wpf";
}

if (registrationNamespace == DependencyResolverMixins.RegistrationNamespace.XamForms)
{
yield return "ReactiveUI.XamForms.Registrations, ReactiveUI.XamForms";
}

if (registrationNamespace == DependencyResolverMixins.RegistrationNamespace.Winforms)
{
yield return "ReactiveUI.Winforms.Registrations, ReactiveUI.Winforms";
}
}
}

private static Dictionary<Type, List<Type>> GetServicesThatShouldBeRegistered(IReadOnlyList<DependencyResolverMixins.RegistrationNamespace> onlyNamespaces)
{
Dictionary<Type, List<Type>> serviceTypeToImplementationTypes = new Dictionary<Type, List<Type>>();

Expand All @@ -72,12 +118,7 @@ private static Dictionary<Type, List<Type>> GetServicesThatShouldBeRegistered()
implementationTypes.Add(factory().GetType());
});

var typeNames = new[]
{
"ReactiveUI.XamForms.Registrations, ReactiveUI.XamForms",
"ReactiveUI.Winforms.Registrations, ReactiveUI.Winforms",
"ReactiveUI.Wpf.Registrations, ReactiveUI.Wpf"
};
var typeNames = GetServiceRegistrationTypeNames(onlyNamespaces);

typeNames.ForEach(typeName => GetRegistrationsForPlatform(typeName, serviceTypeToImplementationTypes));

Expand Down
88 changes: 81 additions & 7 deletions src/ReactiveUI/Mixins/DependencyResolverMixins.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// See the LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
Expand All @@ -19,6 +20,73 @@ namespace ReactiveUI
/// </summary>
public static class DependencyResolverMixins
{
private static IReadOnlyList<RegistrationNamespace> defaultRegistrationNamespaces =
(RegistrationNamespace[])Enum.GetValues(typeof(RegistrationNamespace));

private static HashSet<RegistrationNamespace> registrationNamespacesToInitialize =
new HashSet<RegistrationNamespace>(defaultRegistrationNamespaces);

/// <summary>
/// Platforms or other registration namespaces for the dependency resolver to consider when initializing.
/// </summary>
public enum RegistrationNamespace
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would prefer this to be a separate non-nested enum.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered that and didn't know in which file it would live - is a neighboring file okay or should it live in some other folder?

{
/// <summary>
/// Xamarin Forms
/// </summary>
XamForms,

/// <summary>
/// Windows Forms
/// </summary>
Winforms,

/// <summary>
/// WPF
/// </summary>
Wpf,

/// <summary>
/// Uno
/// </summary>
Uno,

/// <summary>
/// Blazor
/// </summary>
Blazor,

/// <summary>
/// Splat.Drawing
/// </summary>
Drawing
}

/// <summary>
/// Gets the default registration namespaces for the dependency resolver to consider, consisting of all registration namespaces.
/// </summary>
public static IReadOnlyList<RegistrationNamespace> DefaultRegistrationNamespaces =>
defaultRegistrationNamespaces;

// The reason SetRegistrationNamespaces is a separate method and not a parameter to InitializeReactiveUI
// is because InitializeReactiveUI is called from within the RxApp static constructor, and there's no
// way to directly pass it parameters.

/// <summary>
/// This method allows you to initialize which platforms <see cref="InitializeReactiveUI"/>
/// attempts to discover registrations for. If this method is not called, all platforms are assumed.
/// </summary>
/// <remarks>Call this before <see cref="InitializeReactiveUI"/>.</remarks>
/// <param name="registrationNamespaces">Which platforms to use.</param>
public static void SetRegistrationNamespaces(params RegistrationNamespace[] registrationNamespaces)
{
registrationNamespacesToInitialize.Clear();
foreach (var platform in registrationNamespaces)
{
registrationNamespacesToInitialize.Add(platform);
}
}

/// <summary>
/// This method allows you to initialize resolvers with the default
/// ReactiveUI types. All resolvers used as the default
Expand All @@ -28,16 +96,22 @@ public static class DependencyResolverMixins
[SuppressMessage("Globalization", "CA1307: operator could change based on locale settings", Justification = "Replace() does not have third parameter on all platforms")]
public static void InitializeReactiveUI(this IMutableDependencyResolver resolver)
{
var extraNs = new[]
var possibleNamespaces = new Dictionary<RegistrationNamespace, string>
{
"ReactiveUI.XamForms",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The order we previously looked was used as part of our previous tests. It may be best to instead of using a dictionary to use a List. It's only 6 entries so a dictionary probably wouldn't perform better anyway.

"ReactiveUI.Winforms",
"ReactiveUI.Wpf",
"ReactiveUI.Uno",
"ReactiveUI.Blazor",
"ReactiveUI.Drawing"
{ RegistrationNamespace.XamForms, "ReactiveUI.XamForms" },
{ RegistrationNamespace.Winforms, "ReactiveUI.Winforms" },
{ RegistrationNamespace.Wpf, "ReactiveUI.Wpf" },
{ RegistrationNamespace.Uno, "ReactiveUI.Uno" },
{ RegistrationNamespace.Blazor, "ReactiveUI.Blazor" },
{ RegistrationNamespace.Drawing, "ReactiveUI.Drawing" }
};

var extraNs =
possibleNamespaces
.Where(kvp => registrationNamespacesToInitialize.Contains(kvp.Key))
.Select(kvp => kvp.Value)
.ToArray();

// Set up the built-in registration
new Registrations().Register((f, t) => resolver.RegisterConstant(f(), t));
new PlatformRegistrations().Register((f, t) => resolver.RegisterConstant(f(), t));
Expand Down