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

Arguments class and Resolve methods refactor #444

Merged
merged 4 commits into from
Oct 25, 2018
Merged
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
18 changes: 15 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,22 @@ Breaking Changes:
- Removed obsolete ActAs, Parameters, Properties and ServiceOverrides methods from component registration (@fir3pho3nixx, #338)
- Removed obsolete indexer, AddComponent*, AddFacility and Resolve methods from IKernel and IWindsorContainer (@fir3pho3nixx, #338)
- Facility XML configuration specifying an 'id' attribute will now throw, it has been ignored since v3.0 (@fir3pho3nixx, #338)
- Removal of deprecated classes AllTypes and AllTypesOf (@fir3pho3nixx, #338)
- Removal of deprecated BasedOn methods that reset registrations when fluently chained (@fir3pho3nixx, #338)
- Removal of deprecated member LifestyleHandlerType on CustomLifestyleAttribute (@fir3pho3nixx, #338)
- Removed deprecated classes `AllTypes` and `AllTypesOf` (@fir3pho3nixx, #338)
- Removed deprecated `BasedOn` methods that reset registrations when fluently chained (@fir3pho3nixx, #338)
- Removed deprecated member `LifestyleHandlerType` on `CustomLifestyleAttribute` (@fir3pho3nixx, #338)
- Removed Event Wiring, Factory Support and Synchronize facilities (@jonorossi, #403)
- Arguments class and Resolve overloads refactor (@fir3pho3nixx, @jonorossi, #444)
- Removed `WindsorContainer.Resolve(object/IDictionary)` overloads in favour of new `WindsorContainer.Resolve(Arguments)`
- Reworked `Arguments` class, including to no longer implement `IDictionary`
- Removed `IArgumentsComparer[]` constructors from `Arguments`
- Added `WindsorContainer.Resolve(IEnumerable<KeyValuePair<string, object>>)` extension methods
- Changed `CreationContext.AdditionalArguments` to use `Arguments` instead of `IDictionary`
- Replaced `ComponentDependencyRegistrationExtensions(Insert, InsertAnonymous, InsertTyped, InsertTypedCollection)` with `Add`, `AddNamed` and `AddTyped` `Arguments` instance methods
- Changed `ComponentRegistration.DependsOn` and `ComponentRegistration.DynamicParameters` to use `Arguments` via `DynamicParametersDelegate`
- Added `ComponentRegistration.DependsOn(Arguments)` overload
- Changed `ComponentModel` `CustomDependencies` and `ExtendedProperties` to use `Arguments` instead of `IDictionary`
- Changed `IComponentModelBuilder.BuildModel` to use `Arguments` instead of `IDictionary`
- Changed `ILazyComponentLoader.Load` to use `Arguments` instead of `IDictionary`

## 4.1.1 (2018-10-15)

Expand Down
63 changes: 42 additions & 21 deletions docs/arguments.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,68 @@

## The `Arguments` class

The `Arguments` class is used by Windsor to pass arguments [down the invocation pipeline](how-dependencies-are-resolved.md). The class is a simple implementation of non-generic `IDictionary`, but it has some useful capabilities.
The `Arguments` class is used by Windsor to pass arguments [down the invocation pipeline](how-dependencies-are-resolved.md). The class is a simple dictionary-like class, but it has some useful capabilities.

:information_source: **Custom `IDictionary` is respected:** When you call `container.Resolve` passing your own custom implementation of `IDictionary` Windsor will respect that and not replace it with `Arguments`. It is advised that you use `Arguments` though.
Resolution arguments are key/value pairs, keyed either by name (`string`) or type (`System.Type`). How the arguments are added to the `Arguments` collections determines how they are bound to dependencies during resolution.

### Constructors

The class has several constructors:
```csharp
new Arguments(); // Empty collection
new Arguments(new Arguments())); // Copy constructor
```

#### `namedArgumentsAsAnonymousType`

You can pass named arguments as properties on anonymous type:
Collection initializers are supported, both named and typed arguments can be provided:

```csharp
new Arguments(new { logLevel = LogLevel.High });
new Arguments {
{ "Name", "John" },
{ "Age", 18 },
{ typeof(IService), new MyService() }
};
```

:information_source: **Named arguments are not case sensitive:** You can specify `logLevel`, `LogLevel` or even `LOGLEVEL` as property name. All named arguments are matched in case insensitive manner so in all cases the behavior will be the same.
### Fluent Interface

#### Array of `typedArguments`
#### Named Arguments

When you don't care about names of the dependencies you can pass them as array, in which case they will be matched by their type.
Arguments will be matched by a string key in a case insensitive manner. For example, `logLevel`, `LogLevel` and even `LOGLEVEL` as property names are all treated as one key.

```csharp
new Arguments()
.AddNamed("key", 123456)
.AddNamed(new Dictionary<string, string> { { "string-key", "string-value" } });
```

Named arguments can also be added from a plain old C# object or from properties of an anonymous type:
```csharp
new Arguments(new[] { LogLevel.High });
new Arguments()
.AddProperties(myPOCO) // plain old C# object with public properties
.AddProperties(new { logLevel = LogLevel.High }); // anonymous type
```

:information_source: **Typed arguments are matched exactly:** When you don't specify type of the dependency it's exact type will be used. So if you pass for example `MemoryStream` Windsor will try to match it to dependency of type `MemoryStream`, but not of the base type `Stream`. If you want to match it to `Stream` use `Insert` extension method.
#### Typed Arguments

#### Custom dictionary of arguments
Arguments can be matched by type as dependencies:

You can also pass already populated dictionary to `Arguments` in which case its values will be copied over.
```csharp
new Arguments()
.AddTyped(LogLevel.High, new AppConfig()) // params array
.AddTyped(typeof(MyClass), new MyClass())
.AddTyped<IService>(new MyService());
```

### `Insert` extension method
:information_source: **Typed arguments are matched exactly:** When you don't specify the type of the argument, its concrete type will be used. For example, if you pass a `MemoryStream` it will only match to a dependency of type `MemoryStream`, but not of the base type `Stream`. If you want to match it to `Stream` specify the type explicitly.

In addition to `Arguments` class there's also `Insert` extension method that extends `IDictionary`. It has several overloads that let you fluently insert values into the dictionary.
#### Named and/or Typed Arguments Collection

A collection implementing the generic `IDictionary<TKey, TValue>` containing named and/or typed arguments can be added to an `Arguments` instance:
```csharp
args.Insert<Stream>(someMemoryStream)
.Insert("name", someNamedArgument)
.Insert(new[] { multiple, typed, arguments})
.Insert(new { multiple = typed, arguments = also});
```
var map = new Dictionary<object, object>();
map.Add("string-key", 123456);
map.Add(typeof(MyType), 123456);

:information_source: **Insert overwrites:** When item under given key already exists `Insert` will overwrite it.
new Arguments()
.Add(map);
```
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ namespace Castle.Facilities.WcfIntegration.Tests
using Castle.Facilities.WcfIntegration.Demo;
using Castle.Facilities.WcfIntegration.Tests.Behaviors;
using Castle.Facilities.WcfIntegration.Tests.Components;
using Castle.MicroKernel;
using Castle.MicroKernel.Facilities;
using Castle.MicroKernel.Registration;
using Castle.Services.Logging.Log4netIntegration;
Expand Down Expand Up @@ -243,23 +244,23 @@ public void CanResolveClientAssociatedWithChannelUsingSuppliedModel()
))
{
var client1 = clientContainer.Resolve<IOperations>("operations",
new
Arguments.FromProperties(new
{
Model = new DefaultClientModel
{
Endpoint = WcfEndpoint.BoundTo(new NetTcpBinding())
.At("net.tcp://localhost/Operations2")
}
});
}));
var client2 = clientContainer.Resolve<IOperations>("operations",
new
Arguments.FromProperties(new
{
Model = new DefaultClientModel()
{
Endpoint = WcfEndpoint.BoundTo(new NetTcpBinding())
.At("net.tcp://localhost/Operations2")
}
});
}));
Assert.AreEqual(28, client1.GetValueFromConstructor());
Assert.AreEqual(28, client2.GetValueFromConstructor());
clientContainer.Release(client1);
Expand Down Expand Up @@ -291,10 +292,10 @@ public void CanResolveClientAssociatedWithChannelUsingSuppliedEndpoint()
))
{
var tracker = ReferenceTracker.Track(() => clientContainer.Resolve<IOperations>("operations",
new { Endpoint = WcfEndpoint.At("net.tcp://localhost/Operations2") }));
Arguments.FromProperties(new { Endpoint = WcfEndpoint.At("net.tcp://localhost/Operations2") })));

var client2 = clientContainer.Resolve<IOperations>("operations",
new { Endpoint = WcfEndpoint.At("net.tcp://localhost/Operations2") });
Arguments.FromProperties(new { Endpoint = WcfEndpoint.At("net.tcp://localhost/Operations2") }));

Assert.AreEqual(28, tracker.AssertStillReferencedAndDo(client1 => client1.GetValueFromConstructor()));
Assert.AreEqual(28, client2.GetValueFromConstructor());
Expand Down Expand Up @@ -325,23 +326,23 @@ public void CanLazilyResolveClientAssociatedWithChannelUsingSuppliedModel()
.AddFacility<WcfFacility>(f => f.CloseTimeout = TimeSpan.Zero))
{
var client1 = clientContainer.Resolve<IOperations>(
new
Arguments.FromProperties(new
{
Model = new DefaultClientModel
{
Endpoint = WcfEndpoint.BoundTo(new NetTcpBinding())
.At("net.tcp://localhost/Operations2")
}
});
}));
var client2 = clientContainer.Resolve<IOperations>(
new
Arguments.FromProperties(new
{
Model = new DefaultClientModel()
{
Endpoint = WcfEndpoint.BoundTo(new NetTcpBinding())
.At("net.tcp://localhost/Operations2")
}
});
}));
Assert.AreEqual(28, client1.GetValueFromConstructor());
Assert.AreEqual(28, client2.GetValueFromConstructor());
clientContainer.Release(client1);
Expand All @@ -368,9 +369,9 @@ public void CanLazilyResolveClientAssociatedWithChannelUsingSuppliedEndpoint()
.AddFacility<WcfFacility>(f => f.CloseTimeout = TimeSpan.Zero))
{
var tracker = ReferenceTracker.Track(() => clientContainer.Resolve<IOperations>("operations",
new { Endpoint = WcfEndpoint.At("net.tcp://localhost/Operations2") }));
Arguments.FromProperties(new { Endpoint = WcfEndpoint.At("net.tcp://localhost/Operations2") })));
var client2 = clientContainer.Resolve<IOperations>("operations",
new { Endpoint = WcfEndpoint.At("net.tcp://localhost/Operations2") });
Arguments.FromProperties(new { Endpoint = WcfEndpoint.At("net.tcp://localhost/Operations2") }));

Assert.AreEqual(28, tracker.AssertStillReferencedAndDo(client1 => client1.GetValueFromConstructor()));
Assert.AreEqual(28, client2.GetValueFromConstructor());
Expand Down Expand Up @@ -536,14 +537,14 @@ public void WillRecoverFromAnUnhandledExceptionWithChannelUsingSuppliedModel()
))
{
var client = clientContainer.Resolve<IOperationsEx>("operations",
new
Arguments.FromProperties(new
{
Model = new DefaultClientModel
{
Endpoint = WcfEndpoint.BoundTo(new NetTcpBinding())
.At("net.tcp://localhost/Operations2")
}
});
}));
try
{
client.ThrowException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@
namespace Castle.Facilities.WcfIntegration
{
using System;
using System.Collections;
using System.Linq;
using Castle.Facilities.WcfIntegration.Internal;
using Castle.MicroKernel;
using Castle.MicroKernel.Facilities;
using Castle.MicroKernel.Registration;
using Castle.MicroKernel.Resolvers;

public class WcfClientComponentLoader : ILazyComponentLoader
{
public IRegistration Load(string key, Type service, IDictionary arguments)
public IRegistration Load(string key, Type service, Arguments arguments)
{
if (service == typeof(IWcfClientFactory))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public Func<IKernelInternal, IReleasePolicy, object> SelectComponent(MethodInfo
argument = WcfEndpoint.At((Uri)argument);
}

var args = new HybridDictionary { { Guid.NewGuid().ToString(), argument } };
var args = new Arguments { { Guid.NewGuid().ToString(), argument } };

if (key == null)
{
Expand Down
8 changes: 5 additions & 3 deletions src/Castle.Facilities.WcfIntegration/Internal/WcfUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -255,17 +255,19 @@ public static bool IsBehaviorExtension(object behavior)
behavior is IOperationBehavior || behavior is IEndpointBehavior;
}

public static IEnumerable<T> FindDependencies<T>(IDictionary dependencies)
public static IEnumerable<T> FindDependencies<T>(Arguments dependencies)
{
return FindDependencies<T>(dependencies, null);
}

public static IEnumerable<T> FindDependencies<T>(IDictionary dependencies, Predicate<T> test)
public static IEnumerable<T> FindDependencies<T>(Arguments dependencies, Predicate<T> test)
{
if (dependencies != null)
{
foreach (object dependency in dependencies.Values)
foreach (var pair in dependencies)
{
var dependency = pair.Value;

if (dependency is T)
{
var candidate = (T)dependency;
Expand Down
13 changes: 8 additions & 5 deletions src/Castle.Windsor.Tests/Bugs/IoC-138.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ namespace Castle.Windsor.Tests.Bugs
{
using System.Collections.Generic;

using Castle.MicroKernel;
using Castle.MicroKernel.Registration;

using NUnit.Framework;
Expand All @@ -28,10 +29,12 @@ public void TestResolveSubComponentInConstructorWithParameters()
{
var container = new WindsorContainer();
ServiceLocator.Container = container;
container.Register(Component.For<UsesServiceLocaator>().Named("A"),
container.Register(Component.For<UsesServiceLocator>().Named("A"),
Component.For<DependsOnStringTest2>().Named("B"));

var component = container.Resolve<UsesServiceLocaator>(new Dictionary<string, string> { { "test", "bla" } });
var component = container.Resolve<UsesServiceLocator>(
new Arguments { { "test", "bla" } });

Assert.IsNotNull(component.Other);
}

Expand All @@ -47,13 +50,13 @@ public static class ServiceLocator
public static IWindsorContainer Container { get; set; }
}

public class UsesServiceLocaator
public class UsesServiceLocator
{
private readonly DependsOnStringTest2 other;

public UsesServiceLocaator(string test)
public UsesServiceLocator(string test)
{
other = ServiceLocator.Container.Resolve<DependsOnStringTest2>(new Dictionary<string, string> { { "test2", "bla" } });
other = ServiceLocator.Container.Resolve<DependsOnStringTest2>(new Arguments { { "test2", "bla" } });
}

public DependsOnStringTest2 Other
Expand Down
5 changes: 3 additions & 2 deletions src/Castle.Windsor.Tests/ByRefDependenciesTestCase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

namespace Castle.Windsor.Tests
{
using Castle.MicroKernel;
using Castle.MicroKernel.Registration;
using Castle.Windsor.Tests.ClassComponents;

Expand All @@ -38,15 +39,15 @@ public void Can_resolve_type_with_by_ref_dependency_provided_inline()
{
Container.Register(Component.For<HasByRefCtorArgument>());

Container.Resolve<HasByRefCtorArgument>(new { a = new A() });
Container.Resolve<HasByRefCtorArgument>(Arguments.FromProperties(new { a = new A() }));
}

[Test]
public void Can_resolve_type_with_by_ref_dependency_provided_inline_via_anonymous_type()
{
Container.Register(Component.For<HasByRefCtorArgument>());

Container.Resolve<HasByRefCtorArgument>(new { a = new A() });
Container.Resolve<HasByRefCtorArgument>(Arguments.FromProperties(new { a = new A() }));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
namespace CastleTests.Facilities.TypedFactory.Selectors
{
using System;
using System.Collections;
using System.Reflection;

using Castle.Facilities.TypedFactory;
Expand All @@ -29,10 +28,10 @@ public SelectorByClosedArgumentType()
FallbackToResolveByTypeIfNameNotFound = true;
}

protected override IDictionary GetArguments(MethodInfo method, object[] arguments)
protected override Arguments GetArguments(MethodInfo method, object[] arguments)
{
//a condition checking if it's actually a method we want to be in should go here
return new Arguments(arguments);
return new Arguments().AddTyped(arguments);
}

protected override Type GetComponentType(MethodInfo method, object[] arguments)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ namespace Castle.Windsor.Tests.Facilities.TypedFactory.Selectors

public class SelectorById : DefaultTypedFactoryComponentSelector
{
protected override IDictionary GetArguments(MethodInfo method, object[] arguments)
protected override Arguments GetArguments(MethodInfo method, object[] arguments)
{
if (method.Name.Equals("ComponentNamed"))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public void Can_resolve_component_depending_on_delegate_when_inline_argumens_are
Container.Register(Component.For<Foo>(),
Component.For<UsesFooDelegateAndInt>());

Container.Resolve<UsesFooDelegateAndInt>(new { additionalArgument = 5 });
Container.Resolve<UsesFooDelegateAndInt>(Arguments.FromProperties(new { additionalArgument = 5 }));
}

[Test]
Expand Down Expand Up @@ -149,7 +149,7 @@ public void Can_resolve_two_services_depending_on_identical_delegates()
{
Container.Register(Component.For<Foo>().LifeStyle.Transient,
Component.For<UsesFooDelegate>(),
Component.For<UsesFooDelegateAndInt>().DependsOn(new Arguments().InsertTyped(5)));
Component.For<UsesFooDelegateAndInt>().DependsOn(new Arguments().AddTyped(5)));
var one = Container.Resolve<UsesFooDelegate>();
var two = Container.Resolve<UsesFooDelegateAndInt>();
one.GetFoo();
Expand All @@ -161,7 +161,7 @@ public void Can_resolve_two_services_depending_on_identical_delegates_via_interf
{
Container.Register(Component.For<Foo>().LifeStyle.Transient,
Component.For<UsesFooDelegate>(),
Component.For<UsesFooDelegateAndInt>().DependsOn(new Arguments().InsertTyped(5)),
Component.For<UsesFooDelegateAndInt>().DependsOn(new Arguments().AddTyped(5)),
Component.For<IGenericComponentsFactory>().AsFactory());

var factory = Container.Resolve<IGenericComponentsFactory>();
Expand Down
Loading