diff --git a/build.cake b/build.cake index 013d3246b..a3a839a21 100644 --- a/build.cake +++ b/build.cake @@ -77,11 +77,13 @@ var packagesArtifactDirectory = artifactDirectory + "packages/"; var packageWhitelist = new[] { "Splat", + "Splat.Autofac", }; var packageTestWhitelist = new[] { "Splat.Tests", + "Splat.Autofac.Tests", }; var testFrameworks = new[] { "netcoreapp2.1", "net472" }; diff --git a/src/Splat.Autofac.Tests/DependencyResolverTests.cs b/src/Splat.Autofac.Tests/DependencyResolverTests.cs new file mode 100644 index 000000000..055428dac --- /dev/null +++ b/src/Splat.Autofac.Tests/DependencyResolverTests.cs @@ -0,0 +1,88 @@ +// Copyright (c) 2019 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Autofac; +using ReactiveUI; +using Shouldly; +using Xunit; + +namespace Splat.Autofac.Tests +{ + /// + /// Tests to show the works correctly. + /// + public class DependencyResolverTests + { + /// + /// Shoulds the resolve views. + /// + [Fact] + public void AutofacDependencyResolver_Should_Resolve_Views() + { + var builder = new ContainerBuilder(); + builder.RegisterType().As>(); + builder.RegisterType().As>(); + builder.Build().UseAutofacDependencyResolver(); + + var viewOne = Locator.Current.GetService(typeof(IViewFor)); + var viewTwo = Locator.Current.GetService(typeof(IViewFor)); + + viewOne.ShouldNotBeNull(); + viewOne.ShouldBeOfType(); + viewTwo.ShouldNotBeNull(); + viewTwo.ShouldBeOfType(); + } + + /// + /// Shoulds the resolve views. + /// + [Fact] + public void AutofacDependencyResolver_Should_Resolve_Named_View() + { + var builder = new ContainerBuilder(); + builder.RegisterType().Named>("Other"); + builder.Build().UseAutofacDependencyResolver(); + + var viewTwo = Locator.Current.GetService(typeof(IViewFor), "Other"); + + viewTwo.ShouldNotBeNull(); + viewTwo.ShouldBeOfType(); + } + + /// + /// Shoulds the resolve view models. + /// + [Fact] + public void AutofacDependencyResolver_Should_Resolve_View_Models() + { + var builder = new ContainerBuilder(); + builder.RegisterType().AsSelf(); + builder.RegisterType().AsSelf(); + builder.Build().UseAutofacDependencyResolver(); + + var vmOne = Locator.Current.GetService(); + var vmTwo = Locator.Current.GetService(); + + vmOne.ShouldNotBeNull(); + vmTwo.ShouldNotBeNull(); + } + + /// + /// Shoulds the resolve screen. + /// + [Fact] + public void AutofacDependencyResolver_Should_Resolve_Screen() + { + var builder = new ContainerBuilder(); + builder.RegisterType().As().SingleInstance(); + builder.Build().UseAutofacDependencyResolver(); + + var screen = Locator.Current.GetService(); + + screen.ShouldNotBeNull(); + screen.ShouldBeOfType(); + } + } +} diff --git a/src/Splat.Autofac.Tests/Mocks/MockScreen.cs b/src/Splat.Autofac.Tests/Mocks/MockScreen.cs new file mode 100644 index 000000000..775a8fc43 --- /dev/null +++ b/src/Splat.Autofac.Tests/Mocks/MockScreen.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2019 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using ReactiveUI; + +namespace Splat.Autofac.Tests +{ + /// + /// An implementation. + /// + /// + public class MockScreen : IScreen + { + /// + /// Gets the Router associated with this Screen. + /// + public RoutingState Router { get; } + } +} diff --git a/src/Splat.Autofac.Tests/Mocks/ViewModelOne.cs b/src/Splat.Autofac.Tests/Mocks/ViewModelOne.cs new file mode 100644 index 000000000..74379e18c --- /dev/null +++ b/src/Splat.Autofac.Tests/Mocks/ViewModelOne.cs @@ -0,0 +1,23 @@ +// Copyright (c) 2019 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using ReactiveUI; + +namespace Splat.Autofac.Tests +{ + /// + /// View Model One. + /// + /// + /// + public class ViewModelOne : ReactiveObject, IRoutableViewModel + { + /// + public string UrlPathSegment { get; } + + /// + public IScreen HostScreen { get; } + } +} diff --git a/src/Splat.Autofac.Tests/Mocks/ViewModelTwo.cs b/src/Splat.Autofac.Tests/Mocks/ViewModelTwo.cs new file mode 100644 index 000000000..96d2e1a6c --- /dev/null +++ b/src/Splat.Autofac.Tests/Mocks/ViewModelTwo.cs @@ -0,0 +1,23 @@ +// Copyright (c) 2019 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using ReactiveUI; + +namespace Splat.Autofac.Tests +{ + /// + /// View Model Two. + /// + /// + /// + public class ViewModelTwo : ReactiveObject, IRoutableViewModel + { + /// + public string UrlPathSegment { get; } + + /// + public IScreen HostScreen { get; } + } +} diff --git a/src/Splat.Autofac.Tests/Mocks/ViewOne.cs b/src/Splat.Autofac.Tests/Mocks/ViewOne.cs new file mode 100644 index 000000000..91e92ab23 --- /dev/null +++ b/src/Splat.Autofac.Tests/Mocks/ViewOne.cs @@ -0,0 +1,26 @@ +// Copyright (c) 2019 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using ReactiveUI; + +namespace Splat.Autofac.Tests +{ + /// + /// View One. + /// + /// + public class ViewOne : IViewFor + { + /// + object IViewFor.ViewModel + { + get => ViewModel; + set => ViewModel = (ViewModelOne)value; + } + + /// + public ViewModelOne ViewModel { get; set; } + } +} diff --git a/src/Splat.Autofac.Tests/Mocks/ViewTwo.cs b/src/Splat.Autofac.Tests/Mocks/ViewTwo.cs new file mode 100644 index 000000000..4fe1a8274 --- /dev/null +++ b/src/Splat.Autofac.Tests/Mocks/ViewTwo.cs @@ -0,0 +1,27 @@ +// Copyright (c) 2019 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using ReactiveUI; + +namespace Splat.Autofac.Tests +{ + /// + /// View Two. + /// + /// + [ViewContract("Other")] + public class ViewTwo : IViewFor + { + /// + object IViewFor.ViewModel + { + get => ViewModel; + set => ViewModel = (ViewModelTwo)value; + } + + /// + public ViewModelTwo ViewModel { get; set; } + } +} diff --git a/src/Splat.Autofac.Tests/Splat.Autofac.Tests.csproj b/src/Splat.Autofac.Tests/Splat.Autofac.Tests.csproj new file mode 100644 index 000000000..7c86cacb7 --- /dev/null +++ b/src/Splat.Autofac.Tests/Splat.Autofac.Tests.csproj @@ -0,0 +1,25 @@ + + + + netcoreapp2.1;net472 + + false + $(NoWarn);1591;CA1707;SA1633 + + + + + + + + + + + + + + + Always + + + diff --git a/src/Splat.Autofac.Tests/xunit.runner.json b/src/Splat.Autofac.Tests/xunit.runner.json new file mode 100644 index 000000000..42db7ef95 --- /dev/null +++ b/src/Splat.Autofac.Tests/xunit.runner.json @@ -0,0 +1,3 @@ +{ + "shadowCopy": false +} diff --git a/src/Splat.Autofac/AutofacDependencyResolver.cs b/src/Splat.Autofac/AutofacDependencyResolver.cs new file mode 100644 index 000000000..539e966cf --- /dev/null +++ b/src/Splat.Autofac/AutofacDependencyResolver.cs @@ -0,0 +1,139 @@ +// Copyright (c) 2019 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Autofac; +using Autofac.Core; + +namespace Splat.Autofac +{ + /// + /// Autofac implementation for . + /// + public class AutofacDependencyResolver : IMutableDependencyResolver + { + private IContainer _container; + + /// + /// Initializes a new instance of the class. + /// + /// The container. + public AutofacDependencyResolver(IContainer container) + { + _container = container; + } + + /// + public virtual object GetService(Type serviceType, string contract = null) + { + try + { + return string.IsNullOrEmpty(contract) + ? _container.Resolve(serviceType) + : _container.ResolveNamed(contract, serviceType); + } + catch (DependencyResolutionException) + { + return null; + } + } + + /// + public virtual IEnumerable GetServices(Type serviceType, string contract = null) + { + try + { + var enumerableType = typeof(IEnumerable<>).MakeGenericType(serviceType); + object instance = string.IsNullOrEmpty(contract) + ? _container.Resolve(enumerableType) + : _container.ResolveNamed(contract, enumerableType); + return ((IEnumerable)instance).Cast(); + } + catch (DependencyResolutionException) + { + return null; + } + } + + /// + /// Register a function with the resolver which will generate a object + /// for the specified service type. + /// Optionally a contract can be registered which will indicate + /// that that registration will only work with that contract. + /// Most implementations will use a stack based approach to allow for multile items to be registered. + /// + /// The factory function which generates our object. + /// The type which is used for the registration. + /// A optional contract value which will indicates to only generate the value if this contract is specified. + public virtual void Register(Func factory, Type serviceType, string contract = null) + { + var builder = new ContainerBuilder(); + if (string.IsNullOrEmpty(contract)) + { + builder.Register(x => factory()).As(serviceType).AsImplementedInterfaces(); + } + else + { + builder.Register(x => factory()).Named(contract, serviceType).AsImplementedInterfaces(); + } + } + + /// + /// Unregisters the current item based on the specified type and contract. + /// https://stackoverflow.com/questions/5091101/is-it-possible-to-remove-an-existing-registration-from-autofac-container-builder. + /// + /// The service type to unregister. + /// The optional contract value, which will only remove the value associated with the contract. + /// This is not implemented by default. + /// + public virtual void UnregisterCurrent(Type serviceType, string contract = null) + { + throw new NotImplementedException(); + } + + /// + /// Unregisters all the values associated with the specified type and contract. + /// https://stackoverflow.com/questions/5091101/is-it-possible-to-remove-an-existing-registration-from-autofac-container-builder. + /// + /// The service type to unregister. + /// The optional contract value, which will only remove the value associated with the contract. + /// This is not implemented by default. + /// + public virtual void UnregisterAll(Type serviceType, string contract = null) + { + throw new NotImplementedException(); + } + + /// + public virtual IDisposable ServiceRegistrationCallback(Type serviceType, string contract, Action callback) + { + // this method is not used by RxUI + throw new NotImplementedException(); + } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Disposes of the instance. + /// + /// Whether or not the instance is disposing. + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _container?.Dispose(); + _container = null; + } + } + } +} diff --git a/src/Splat.Autofac/Splat.Autofac.csproj b/src/Splat.Autofac/Splat.Autofac.csproj new file mode 100644 index 000000000..c0902ea27 --- /dev/null +++ b/src/Splat.Autofac/Splat.Autofac.csproj @@ -0,0 +1,15 @@ + + + + netstandard2.0 + + + + + + + + + + + diff --git a/src/Splat.Autofac/SplatAutofacExtension.cs b/src/Splat.Autofac/SplatAutofacExtension.cs new file mode 100644 index 000000000..bd5a5f1e3 --- /dev/null +++ b/src/Splat.Autofac/SplatAutofacExtension.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2019 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Autofac; + +namespace Splat.Autofac +{ + /// + /// Extension methods for the Autofac adapter. + /// + public static class SplatAutofacExtension + { + /// + /// Initializes an instance of that overrides the default . + /// + /// Autofac container. + public static void UseAutofacDependencyResolver(this IContainer container) => + Locator.Current = new AutofacDependencyResolver(container); + + /// + /// Initializes an instance of that overrides the default . + /// + /// Autofac container builder. + public static void UseAutofacDependencyResolver(this ContainerBuilder containerBuilder) => + Locator.Current = new AutofacDependencyResolver(containerBuilder.Build()); + } +} diff --git a/src/Splat.sln b/src/Splat.sln index d278eaf27..c225ac681 100644 --- a/src/Splat.sln +++ b/src/Splat.sln @@ -13,6 +13,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Splat.Tests", "Splat.Tests\Splat.Tests.csproj", "{6CAD2584-AA69-4A36-8AD4-A90D040003CA}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Splat.Autofac", "Splat.Autofac\Splat.Autofac.csproj", "{8447DF8A-882C-4CA8-A7FB-85D66F12D378}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Splat.Autofac.Tests", "Splat.Autofac.Tests\Splat.Autofac.Tests.csproj", "{1D8068E4-7F85-4322-BC06-3D901F392CF1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +31,14 @@ Global {6CAD2584-AA69-4A36-8AD4-A90D040003CA}.Debug|Any CPU.Build.0 = Debug|Any CPU {6CAD2584-AA69-4A36-8AD4-A90D040003CA}.Release|Any CPU.ActiveCfg = Release|Any CPU {6CAD2584-AA69-4A36-8AD4-A90D040003CA}.Release|Any CPU.Build.0 = Release|Any CPU + {8447DF8A-882C-4CA8-A7FB-85D66F12D378}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8447DF8A-882C-4CA8-A7FB-85D66F12D378}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8447DF8A-882C-4CA8-A7FB-85D66F12D378}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8447DF8A-882C-4CA8-A7FB-85D66F12D378}.Release|Any CPU.Build.0 = Release|Any CPU + {1D8068E4-7F85-4322-BC06-3D901F392CF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1D8068E4-7F85-4322-BC06-3D901F392CF1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1D8068E4-7F85-4322-BC06-3D901F392CF1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1D8068E4-7F85-4322-BC06-3D901F392CF1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE