Skip to content

Commit

Permalink
Merge pull request #444 from castleproject/arguments-refactor
Browse files Browse the repository at this point in the history
Arguments class and Resolve methods refactor
  • Loading branch information
jonorossi authored Oct 25, 2018
2 parents 5968b90 + 49aa6d2 commit 6900bbf
Show file tree
Hide file tree
Showing 58 changed files with 750 additions and 845 deletions.
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

0 comments on commit 6900bbf

Please sign in to comment.