diff --git a/Swan/DependencyInjection/DependencyContainer.cs b/Swan/DependencyInjection/DependencyContainer.cs
index 48d6461..efbe114 100644
--- a/Swan/DependencyInjection/DependencyContainer.cs
+++ b/Swan/DependencyInjection/DependencyContainer.cs
@@ -1,705 +1,541 @@
-namespace Swan.DependencyInjection
-{
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Reflection;
-
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+namespace Swan.DependencyInjection {
+ ///
+ /// The concrete implementation of a simple IoC container
+ /// based largely on TinyIoC (https://github.com/grumpydev/TinyIoC).
+ ///
+ ///
+ public partial class DependencyContainer : IDisposable {
+ private readonly Object _autoRegisterLock = new Object();
+
+ private Boolean _disposed;
+
+ static DependencyContainer() {
+ }
+
///
- /// The concrete implementation of a simple IoC container
- /// based largely on TinyIoC (https://github.com/grumpydev/TinyIoC).
+ /// Initializes a new instance of the class.
///
- ///
- public partial class DependencyContainer : IDisposable
- {
- private readonly object _autoRegisterLock = new object();
-
- private bool _disposed;
-
- static DependencyContainer()
- {
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- public DependencyContainer()
- {
- RegisteredTypes = new TypesConcurrentDictionary(this);
- Register(this);
- }
-
- private DependencyContainer(DependencyContainer parent)
- : this()
- {
- Parent = parent;
- }
-
- ///
- /// Lazy created Singleton instance of the container for simple scenarios.
- ///
- public static DependencyContainer Current { get; } = new DependencyContainer();
-
- internal DependencyContainer Parent { get; }
-
- internal TypesConcurrentDictionary RegisteredTypes { get; }
-
- ///
- public void Dispose()
- {
- if (_disposed) return;
-
- _disposed = true;
-
- foreach (var disposable in RegisteredTypes.Values.Select(item => item as IDisposable))
- {
- disposable?.Dispose();
- }
-
- GC.SuppressFinalize(this);
- }
-
- ///
- /// Gets the child container.
- ///
- /// A new instance of the class.
- public DependencyContainer GetChildContainer() => new DependencyContainer(this);
-
- #region Registration
-
- ///
- /// Attempt to automatically register all non-generic classes and interfaces in the current app domain.
- /// Types will only be registered if they pass the supplied registration predicate.
- ///
- /// What action to take when encountering duplicate implementations of an interface/base class.
- /// Predicate to determine if a particular type should be registered.
- public void AutoRegister(
- DependencyContainerDuplicateImplementationAction duplicateAction =
- DependencyContainerDuplicateImplementationAction.RegisterSingle,
- Func registrationPredicate = null)
- {
- AutoRegister(
- AppDomain.CurrentDomain.GetAssemblies().Where(a => !IsIgnoredAssembly(a)),
- duplicateAction,
- registrationPredicate);
- }
-
- ///
- /// Attempt to automatically register all non-generic classes and interfaces in the specified assemblies
- /// Types will only be registered if they pass the supplied registration predicate.
- ///
- /// Assemblies to process.
- /// What action to take when encountering duplicate implementations of an interface/base class.
- /// Predicate to determine if a particular type should be registered.
- public void AutoRegister(
- IEnumerable assemblies,
- DependencyContainerDuplicateImplementationAction duplicateAction =
- DependencyContainerDuplicateImplementationAction.RegisterSingle,
- Func registrationPredicate = null)
- {
- lock (_autoRegisterLock)
- {
- var types = assemblies
- .SelectMany(a => a.GetAllTypes())
- .Where(t => !IsIgnoredType(t, registrationPredicate))
- .ToList();
-
- var concreteTypes = types
- .Where(type =>
- type.IsClass && !type.IsAbstract &&
- (type != GetType() && (type.DeclaringType != GetType()) && !type.IsGenericTypeDefinition))
- .ToList();
-
- foreach (var type in concreteTypes)
- {
- try
- {
- RegisteredTypes.Register(type, string.Empty, GetDefaultObjectFactory(type, type));
- }
- catch (MethodAccessException)
- {
- // Ignore methods we can't access - added for Silverlight
- }
- }
-
- var abstractInterfaceTypes = types.Where(
- type =>
- ((type.IsInterface || type.IsAbstract) && (type.DeclaringType != GetType()) &&
- (!type.IsGenericTypeDefinition)));
-
- foreach (var type in abstractInterfaceTypes)
- {
- var localType = type;
- var implementations = concreteTypes
- .Where(implementationType => localType.IsAssignableFrom(implementationType)).ToList();
-
- if (implementations.Skip(1).Any())
- {
- if (duplicateAction == DependencyContainerDuplicateImplementationAction.Fail)
- throw new DependencyContainerRegistrationException(type, implementations);
-
- if (duplicateAction == DependencyContainerDuplicateImplementationAction.RegisterMultiple)
- {
- RegisterMultiple(type, implementations);
- }
- }
-
- var firstImplementation = implementations.FirstOrDefault();
-
- if (firstImplementation == null) continue;
-
- try
- {
- RegisteredTypes.Register(type, string.Empty, GetDefaultObjectFactory(type, firstImplementation));
- }
- catch (MethodAccessException)
- {
- // Ignore methods we can't access - added for Silverlight
- }
- }
- }
- }
-
- ///
- /// Creates/replaces a named container class registration with default options.
- ///
- /// Type to register.
- /// Name of registration.
- /// RegisterOptions for fluent API.
- public RegisterOptions Register(Type registerType, string name = "")
- => RegisteredTypes.Register(
- registerType,
- name,
- GetDefaultObjectFactory(registerType, registerType));
-
- ///
- /// Creates/replaces a named container class registration with a given implementation and default options.
- ///
- /// Type to register.
- /// Type to instantiate that implements RegisterType.
- /// Name of registration.
- /// RegisterOptions for fluent API.
- public RegisterOptions Register(Type registerType, Type registerImplementation, string name = "") =>
- RegisteredTypes.Register(registerType, name, GetDefaultObjectFactory(registerType, registerImplementation));
-
- ///
- /// Creates/replaces a named container class registration with a specific, strong referenced, instance.
- ///
- /// Type to register.
- /// Instance of RegisterType to register.
- /// Name of registration.
- /// RegisterOptions for fluent API.
- public RegisterOptions Register(Type registerType, object instance, string name = "") =>
- RegisteredTypes.Register(registerType, name, new InstanceFactory(registerType, registerType, instance));
-
- ///
- /// Creates/replaces a named container class registration with a specific, strong referenced, instance.
- ///
- /// Type to register.
- /// Type of instance to register that implements RegisterType.
- /// Instance of RegisterImplementation to register.
- /// Name of registration.
- /// RegisterOptions for fluent API.
- public RegisterOptions Register(
- Type registerType,
- Type registerImplementation,
- object instance,
- string name = "")
- => RegisteredTypes.Register(registerType, name, new InstanceFactory(registerType, registerImplementation, instance));
-
- ///
- /// Creates/replaces a container class registration with a user specified factory.
- ///
- /// Type to register.
- /// Factory/lambda that returns an instance of RegisterType.
- /// Name of registration.
- /// RegisterOptions for fluent API.
- public RegisterOptions Register(
- Type registerType,
- Func, object> factory,
- string name = "")
- => RegisteredTypes.Register(registerType, name, new DelegateFactory(registerType, factory));
-
- ///
- /// Creates/replaces a named container class registration with default options.
- ///
- /// Type to register.
- /// Name of registration.
- /// RegisterOptions for fluent API.
- public RegisterOptions Register(string name = "")
- where TRegister : class
- {
- return Register(typeof(TRegister), name);
- }
-
- ///
- /// Creates/replaces a named container class registration with a given implementation and default options.
- ///
- /// Type to register.
- /// Type to instantiate that implements RegisterType.
- /// Name of registration.
- /// RegisterOptions for fluent API.
- public RegisterOptions Register(string name = "")
- where TRegister : class
- where TRegisterImplementation : class, TRegister
- {
- return Register(typeof(TRegister), typeof(TRegisterImplementation), name);
- }
-
- ///
- /// Creates/replaces a named container class registration with a specific, strong referenced, instance.
- ///
- /// Type to register.
- /// Instance of RegisterType to register.
- /// Name of registration.
- /// RegisterOptions for fluent API.
- public RegisterOptions Register(TRegister instance, string name = "")
- where TRegister : class
- {
- return Register(typeof(TRegister), instance, name);
- }
-
- ///
- /// Creates/replaces a named container class registration with a specific, strong referenced, instance.
- ///
- /// Type to register.
- /// Type of instance to register that implements RegisterType.
- /// Instance of RegisterImplementation to register.
- /// Name of registration.
- /// RegisterOptions for fluent API.
- public RegisterOptions Register(TRegisterImplementation instance,
- string name = "")
- where TRegister : class
- where TRegisterImplementation : class, TRegister
- {
- return Register(typeof(TRegister), typeof(TRegisterImplementation), instance, name);
- }
-
- ///
- /// Creates/replaces a named container class registration with a user specified factory.
- ///
- /// Type to register.
- /// Factory/lambda that returns an instance of RegisterType.
- /// Name of registration.
- /// RegisterOptions for fluent API.
- public RegisterOptions Register(
- Func, TRegister> factory, string name = "")
- where TRegister : class
- {
- if (factory == null)
- throw new ArgumentNullException(nameof(factory));
-
- return Register(typeof(TRegister), factory, name);
- }
-
- ///
- /// Register multiple implementations of a type.
- ///
- /// Internally this registers each implementation using the full name of the class as its registration name.
- ///
- /// Type that each implementation implements.
- /// Types that implement RegisterType.
- /// MultiRegisterOptions for the fluent API.
- public MultiRegisterOptions RegisterMultiple(IEnumerable implementationTypes) =>
- RegisterMultiple(typeof(TRegister), implementationTypes);
-
- ///
- /// Register multiple implementations of a type.
- ///
- /// Internally this registers each implementation using the full name of the class as its registration name.
- ///
- /// Type that each implementation implements.
- /// Types that implement RegisterType.
- /// MultiRegisterOptions for the fluent API.
- public MultiRegisterOptions RegisterMultiple(Type registrationType, IEnumerable implementationTypes)
- {
- if (implementationTypes == null)
- throw new ArgumentNullException(nameof(implementationTypes), "types is null.");
-
- foreach (var type in implementationTypes.Where(type => !registrationType.IsAssignableFrom(type)))
- {
- throw new ArgumentException(
- $"types: The type {registrationType.FullName} is not assignable from {type.FullName}");
- }
-
- if (implementationTypes.Count() != implementationTypes.Distinct().Count())
- {
- var queryForDuplicatedTypes = implementationTypes
- .GroupBy(i => i)
- .Where(j => j.Count() > 1)
- .Select(j => j.Key.FullName);
-
- var fullNamesOfDuplicatedTypes = string.Join(",\n", queryForDuplicatedTypes.ToArray());
-
- throw new ArgumentException(
- $"types: The same implementation type cannot be specified multiple times for {registrationType.FullName}\n\n{fullNamesOfDuplicatedTypes}");
- }
-
- var registerOptions = implementationTypes
- .Select(type => Register(registrationType, type, type.FullName))
- .ToList();
-
- return new MultiRegisterOptions(registerOptions);
- }
-
- #endregion
-
- #region Unregistration
-
- ///
- /// Remove a named container class registration.
- ///
- /// Type to unregister.
- /// Name of registration.
- /// true if the registration is successfully found and removed; otherwise, false .
- public bool Unregister(string name = "") => Unregister(typeof(TRegister), name);
-
- ///
- /// Remove a named container class registration.
- ///
- /// Type to unregister.
- /// Name of registration.
- /// true if the registration is successfully found and removed; otherwise, false .
- public bool Unregister(Type registerType, string name = "") =>
- RegisteredTypes.RemoveRegistration(new TypeRegistration(registerType, name));
-
- #endregion
-
- #region Resolution
-
- ///
- /// Attempts to resolve a named type using specified options and the supplied constructor parameters.
- ///
- /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists).
- /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail.
- ///
- /// Type to resolve.
- /// Name of registration.
- /// Resolution options.
- /// Instance of type.
- /// Unable to resolve the type.
- public object Resolve(
- Type resolveType,
- string name = null,
- DependencyContainerResolveOptions options = null)
- => RegisteredTypes.ResolveInternal(new TypeRegistration(resolveType, name), options ?? DependencyContainerResolveOptions.Default);
-
- ///
- /// Attempts to resolve a named type using specified options and the supplied constructor parameters.
- ///
- /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists).
- /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail.
- ///
- /// Type to resolve.
- /// Name of registration.
- /// Resolution options.
- /// Instance of type.
- /// Unable to resolve the type.
- public TResolveType Resolve(
- string name = null,
- DependencyContainerResolveOptions options = null)
- where TResolveType : class
- {
- return (TResolveType)Resolve(typeof(TResolveType), name, options);
- }
-
- ///
- /// Attempts to predict whether a given type can be resolved with the supplied constructor parameters options.
- /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists).
- /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail.
- /// Note: Resolution may still fail if user defined factory registrations fail to construct objects when called.
- ///
- /// Type to resolve.
- /// The name.
- /// Resolution options.
- ///
- /// Bool indicating whether the type can be resolved.
- ///
- public bool CanResolve(
- Type resolveType,
- string name = null,
- DependencyContainerResolveOptions options = null) =>
- RegisteredTypes.CanResolve(new TypeRegistration(resolveType, name), options);
-
- ///
- /// Attempts to predict whether a given named type can be resolved with the supplied constructor parameters options.
- ///
- /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists).
- /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail.
- ///
- /// Note: Resolution may still fail if user defined factory registrations fail to construct objects when called.
- ///
- /// Type to resolve.
- /// Name of registration.
- /// Resolution options.
- /// Bool indicating whether the type can be resolved.
- public bool CanResolve(
- string name = null,
- DependencyContainerResolveOptions options = null)
- where TResolveType : class
- {
- return CanResolve(typeof(TResolveType), name, options);
- }
-
- ///
- /// Attempts to resolve a type using the default options.
- ///
- /// Type to resolve.
- /// Resolved type or default if resolve fails.
- /// true if resolved successfully, false otherwise.
- public bool TryResolve(Type resolveType, out object resolvedType)
- {
- try
- {
- resolvedType = Resolve(resolveType);
- return true;
- }
- catch (DependencyContainerResolutionException)
- {
- resolvedType = null;
- return false;
- }
- }
-
- ///
- /// Attempts to resolve a type using the given options.
- ///
- /// Type to resolve.
- /// Resolution options.
- /// Resolved type or default if resolve fails.
- /// true if resolved successfully, false otherwise.
- public bool TryResolve(Type resolveType, DependencyContainerResolveOptions options, out object resolvedType)
- {
- try
- {
- resolvedType = Resolve(resolveType, options: options);
- return true;
- }
- catch (DependencyContainerResolutionException)
- {
- resolvedType = null;
- return false;
- }
- }
-
- ///
- /// Attempts to resolve a type using the default options and given name.
- ///
- /// Type to resolve.
- /// Name of registration.
- /// Resolved type or default if resolve fails.
- /// true if resolved successfully, false otherwise.
- public bool TryResolve(Type resolveType, string name, out object resolvedType)
- {
- try
- {
- resolvedType = Resolve(resolveType, name);
- return true;
- }
- catch (DependencyContainerResolutionException)
- {
- resolvedType = null;
- return false;
- }
- }
-
- ///
- /// Attempts to resolve a type using the given options and name.
- ///
- /// Type to resolve.
- /// Name of registration.
- /// Resolution options.
- /// Resolved type or default if resolve fails.
- /// true if resolved successfully, false otherwise.
- public bool TryResolve(
- Type resolveType,
- string name,
- DependencyContainerResolveOptions options,
- out object resolvedType)
- {
- try
- {
- resolvedType = Resolve(resolveType, name, options);
- return true;
- }
- catch (DependencyContainerResolutionException)
- {
- resolvedType = null;
- return false;
- }
- }
-
- ///
- /// Attempts to resolve a type using the default options.
- ///
- /// Type to resolve.
- /// Resolved type or default if resolve fails.
- /// true if resolved successfully, false otherwise.
- public bool TryResolve(out TResolveType resolvedType)
- where TResolveType : class
- {
- try
- {
- resolvedType = Resolve();
- return true;
- }
- catch (DependencyContainerResolutionException)
- {
- resolvedType = default;
- return false;
- }
- }
-
- ///
- /// Attempts to resolve a type using the given options.
- ///
- /// Type to resolve.
- /// Resolution options.
- /// Resolved type or default if resolve fails.
- /// true if resolved successfully, false otherwise.
- public bool TryResolve(DependencyContainerResolveOptions options, out TResolveType resolvedType)
- where TResolveType : class
- {
- try
- {
- resolvedType = Resolve(options: options);
- return true;
- }
- catch (DependencyContainerResolutionException)
- {
- resolvedType = default;
- return false;
- }
- }
-
- ///
- /// Attempts to resolve a type using the default options and given name.
- ///
- /// Type to resolve.
- /// Name of registration.
- /// Resolved type or default if resolve fails.
- /// true if resolved successfully, false otherwise.
- public bool TryResolve(string name, out TResolveType resolvedType)
- where TResolveType : class
- {
- try
- {
- resolvedType = Resolve(name);
- return true;
- }
- catch (DependencyContainerResolutionException)
- {
- resolvedType = default;
- return false;
- }
- }
-
- ///
- /// Attempts to resolve a type using the given options and name.
- ///
- /// Type to resolve.
- /// Name of registration.
- /// Resolution options.
- /// Resolved type or default if resolve fails.
- /// true if resolved successfully, false otherwise.
- public bool TryResolve(
- string name,
- DependencyContainerResolveOptions options,
- out TResolveType resolvedType)
- where TResolveType : class
- {
- try
- {
- resolvedType = Resolve(name, options);
- return true;
- }
- catch (DependencyContainerResolutionException)
- {
- resolvedType = default;
- return false;
- }
- }
-
- ///
- /// Returns all registrations of a type.
- ///
- /// Type to resolveAll.
- /// Whether to include un-named (default) registrations.
- /// IEnumerable.
- public IEnumerable ResolveAll(Type resolveType, bool includeUnnamed = false)
- => RegisteredTypes.Resolve(resolveType, includeUnnamed);
-
- ///
- /// Returns all registrations of a type.
- ///
- /// Type to resolveAll.
- /// Whether to include un-named (default) registrations.
- /// IEnumerable.
- public IEnumerable ResolveAll(bool includeUnnamed = true)
- where TResolveType : class
- {
- return ResolveAll(typeof(TResolveType), includeUnnamed).Cast();
- }
-
- ///
- /// Attempts to resolve all public property dependencies on the given object using the given resolve options.
- ///
- /// Object to "build up".
- /// Resolve options to use.
- public void BuildUp(object input, DependencyContainerResolveOptions resolveOptions = null)
- {
- if (resolveOptions == null)
- resolveOptions = DependencyContainerResolveOptions.Default;
-
- var properties = input.GetType()
- .GetProperties()
- .Where(property => property.GetCacheGetMethod() != null && property.GetCacheSetMethod() != null &&
- !property.PropertyType.IsValueType);
-
- foreach (var property in properties.Where(property => property.GetValue(input, null) == null))
- {
- try
- {
- property.SetValue(
- input,
- RegisteredTypes.ResolveInternal(new TypeRegistration(property.PropertyType), resolveOptions),
- null);
- }
- catch (DependencyContainerResolutionException)
- {
- // Catch any resolution errors and ignore them
- }
- }
- }
-
- #endregion
-
- #region Internal Methods
-
- internal static bool IsValidAssignment(Type registerType, Type registerImplementation)
- {
- if (!registerType.IsGenericTypeDefinition)
- {
- if (!registerType.IsAssignableFrom(registerImplementation))
- return false;
- }
- else
- {
- if (registerType.IsInterface && registerImplementation.GetInterfaces().All(t => t.Name != registerType.Name))
- return false;
-
- if (registerType.IsAbstract && registerImplementation.BaseType != registerType)
- return false;
- }
-
- return true;
- }
-
- private static bool IsIgnoredAssembly(Assembly assembly)
- {
- // TODO - find a better way to remove "system" assemblies from the auto registration
- var ignoreChecks = new List>
- {
+ public DependencyContainer() {
+ this.RegisteredTypes = new TypesConcurrentDictionary(this);
+ _ = this.Register(this);
+ }
+
+ private DependencyContainer(DependencyContainer parent) : this() => this.Parent = parent;
+
+ ///
+ /// Lazy created Singleton instance of the container for simple scenarios.
+ ///
+ public static DependencyContainer Current { get; } = new DependencyContainer();
+
+ internal DependencyContainer Parent {
+ get;
+ }
+
+ internal TypesConcurrentDictionary RegisteredTypes {
+ get;
+ }
+
+ ///
+ public void Dispose() {
+ if(this._disposed) {
+ return;
+ }
+
+ this._disposed = true;
+
+ foreach(IDisposable disposable in this.RegisteredTypes.Values.Select(item => item as IDisposable)) {
+ disposable?.Dispose();
+ }
+
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Gets the child container.
+ ///
+ /// A new instance of the class.
+ public DependencyContainer GetChildContainer() => new DependencyContainer(this);
+
+ #region Registration
+
+ ///
+ /// Attempt to automatically register all non-generic classes and interfaces in the current app domain.
+ /// Types will only be registered if they pass the supplied registration predicate.
+ ///
+ /// What action to take when encountering duplicate implementations of an interface/base class.
+ /// Predicate to determine if a particular type should be registered.
+ public void AutoRegister(DependencyContainerDuplicateImplementationAction duplicateAction = DependencyContainerDuplicateImplementationAction.RegisterSingle, Func registrationPredicate = null) => this.AutoRegister(AppDomain.CurrentDomain.GetAssemblies().Where(a => !IsIgnoredAssembly(a)), duplicateAction, registrationPredicate);
+
+ ///
+ /// Attempt to automatically register all non-generic classes and interfaces in the specified assemblies
+ /// Types will only be registered if they pass the supplied registration predicate.
+ ///
+ /// Assemblies to process.
+ /// What action to take when encountering duplicate implementations of an interface/base class.
+ /// Predicate to determine if a particular type should be registered.
+ public void AutoRegister(IEnumerable assemblies, DependencyContainerDuplicateImplementationAction duplicateAction = DependencyContainerDuplicateImplementationAction.RegisterSingle, Func registrationPredicate = null) {
+ lock(this._autoRegisterLock) {
+ List types = assemblies.SelectMany(a => a.GetAllTypes()).Where(t => !IsIgnoredType(t, registrationPredicate)).ToList();
+
+ List concreteTypes = types.Where(type => type.IsClass && !type.IsAbstract && type != this.GetType() && type.DeclaringType != this.GetType() && !type.IsGenericTypeDefinition).ToList();
+
+ foreach(Type type in concreteTypes) {
+ try {
+ _ = this.RegisteredTypes.Register(type, String.Empty, GetDefaultObjectFactory(type, type));
+ } catch(MethodAccessException) {
+ // Ignore methods we can't access - added for Silverlight
+ }
+ }
+
+ IEnumerable abstractInterfaceTypes = types.Where(type => (type.IsInterface || type.IsAbstract) && type.DeclaringType != this.GetType() && !type.IsGenericTypeDefinition);
+
+ foreach(Type type in abstractInterfaceTypes) {
+ Type localType = type;
+ List implementations = concreteTypes.Where(implementationType => localType.IsAssignableFrom(implementationType)).ToList();
+
+ if(implementations.Skip(1).Any()) {
+ if(duplicateAction == DependencyContainerDuplicateImplementationAction.Fail) {
+ throw new DependencyContainerRegistrationException(type, implementations);
+ }
+
+ if(duplicateAction == DependencyContainerDuplicateImplementationAction.RegisterMultiple) {
+ _ = this.RegisterMultiple(type, implementations);
+ }
+ }
+
+ Type firstImplementation = implementations.FirstOrDefault();
+
+ if(firstImplementation == null) {
+ continue;
+ }
+
+ try {
+ _ = this.RegisteredTypes.Register(type, String.Empty, GetDefaultObjectFactory(type, firstImplementation));
+ } catch(MethodAccessException) {
+ // Ignore methods we can't access - added for Silverlight
+ }
+ }
+ }
+ }
+
+ ///
+ /// Creates/replaces a named container class registration with default options.
+ ///
+ /// Type to register.
+ /// Name of registration.
+ /// RegisterOptions for fluent API.
+ public RegisterOptions Register(Type registerType, String name = "") => this.RegisteredTypes.Register(registerType, name, GetDefaultObjectFactory(registerType, registerType));
+
+ ///
+ /// Creates/replaces a named container class registration with a given implementation and default options.
+ ///
+ /// Type to register.
+ /// Type to instantiate that implements RegisterType.
+ /// Name of registration.
+ /// RegisterOptions for fluent API.
+ public RegisterOptions Register(Type registerType, Type registerImplementation, String name = "") => this.RegisteredTypes.Register(registerType, name, GetDefaultObjectFactory(registerType, registerImplementation));
+
+ ///
+ /// Creates/replaces a named container class registration with a specific, strong referenced, instance.
+ ///
+ /// Type to register.
+ /// Instance of RegisterType to register.
+ /// Name of registration.
+ /// RegisterOptions for fluent API.
+ public RegisterOptions Register(Type registerType, Object instance, String name = "") => this.RegisteredTypes.Register(registerType, name, new InstanceFactory(registerType, registerType, instance));
+
+ ///
+ /// Creates/replaces a named container class registration with a specific, strong referenced, instance.
+ ///
+ /// Type to register.
+ /// Type of instance to register that implements RegisterType.
+ /// Instance of RegisterImplementation to register.
+ /// Name of registration.
+ /// RegisterOptions for fluent API.
+ public RegisterOptions Register(Type registerType, Type registerImplementation, Object instance, String name = "") => this.RegisteredTypes.Register(registerType, name, new InstanceFactory(registerType, registerImplementation, instance));
+
+ ///
+ /// Creates/replaces a container class registration with a user specified factory.
+ ///
+ /// Type to register.
+ /// Factory/lambda that returns an instance of RegisterType.
+ /// Name of registration.
+ /// RegisterOptions for fluent API.
+ public RegisterOptions Register(Type registerType, Func, Object> factory, String name = "") => this.RegisteredTypes.Register(registerType, name, new DelegateFactory(registerType, factory));
+
+ ///
+ /// Creates/replaces a named container class registration with default options.
+ ///
+ /// Type to register.
+ /// Name of registration.
+ /// RegisterOptions for fluent API.
+ public RegisterOptions Register(String name = "") where TRegister : class => this.Register(typeof(TRegister), name);
+
+ ///
+ /// Creates/replaces a named container class registration with a given implementation and default options.
+ ///
+ /// Type to register.
+ /// Type to instantiate that implements RegisterType.
+ /// Name of registration.
+ /// RegisterOptions for fluent API.
+ public RegisterOptions Register(String name = "") where TRegister : class where TRegisterImplementation : class, TRegister => this.Register(typeof(TRegister), typeof(TRegisterImplementation), name);
+
+ ///
+ /// Creates/replaces a named container class registration with a specific, strong referenced, instance.
+ ///
+ /// Type to register.
+ /// Instance of RegisterType to register.
+ /// Name of registration.
+ /// RegisterOptions for fluent API.
+ public RegisterOptions Register(TRegister instance, String name = "") where TRegister : class => this.Register(typeof(TRegister), instance, name);
+
+ ///
+ /// Creates/replaces a named container class registration with a specific, strong referenced, instance.
+ ///
+ /// Type to register.
+ /// Type of instance to register that implements RegisterType.
+ /// Instance of RegisterImplementation to register.
+ /// Name of registration.
+ /// RegisterOptions for fluent API.
+ public RegisterOptions Register(TRegisterImplementation instance, String name = "") where TRegister : class where TRegisterImplementation : class, TRegister => this.Register(typeof(TRegister), typeof(TRegisterImplementation), instance, name);
+
+ ///
+ /// Creates/replaces a named container class registration with a user specified factory.
+ ///
+ /// Type to register.
+ /// Factory/lambda that returns an instance of RegisterType.
+ /// Name of registration.
+ /// RegisterOptions for fluent API.
+ public RegisterOptions Register(Func, TRegister> factory, String name = "") where TRegister : class {
+ if(factory == null) {
+ throw new ArgumentNullException(nameof(factory));
+ }
+
+ return this.Register(typeof(TRegister), factory, name);
+ }
+
+ ///
+ /// Register multiple implementations of a type.
+ ///
+ /// Internally this registers each implementation using the full name of the class as its registration name.
+ ///
+ /// Type that each implementation implements.
+ /// Types that implement RegisterType.
+ /// MultiRegisterOptions for the fluent API.
+ public MultiRegisterOptions RegisterMultiple(IEnumerable implementationTypes) => this.RegisterMultiple(typeof(TRegister), implementationTypes);
+
+ ///
+ /// Register multiple implementations of a type.
+ ///
+ /// Internally this registers each implementation using the full name of the class as its registration name.
+ ///
+ /// Type that each implementation implements.
+ /// Types that implement RegisterType.
+ /// MultiRegisterOptions for the fluent API.
+ public MultiRegisterOptions RegisterMultiple(Type registrationType, IEnumerable implementationTypes) {
+ if(implementationTypes == null) {
+ throw new ArgumentNullException(nameof(implementationTypes), "types is null.");
+ }
+
+ foreach(Type type in implementationTypes.Where(type => !registrationType.IsAssignableFrom(type))) {
+ throw new ArgumentException($"types: The type {registrationType.FullName} is not assignable from {type.FullName}");
+ }
+
+ if(implementationTypes.Count() != implementationTypes.Distinct().Count()) {
+ IEnumerable queryForDuplicatedTypes = implementationTypes.GroupBy(i => i).Where(j => j.Count() > 1).Select(j => j.Key.FullName);
+
+ String fullNamesOfDuplicatedTypes = String.Join(",\n", queryForDuplicatedTypes.ToArray());
+
+ throw new ArgumentException($"types: The same implementation type cannot be specified multiple times for {registrationType.FullName}\n\n{fullNamesOfDuplicatedTypes}");
+ }
+
+ List registerOptions = implementationTypes.Select(type => this.Register(registrationType, type, type.FullName)).ToList();
+
+ return new MultiRegisterOptions(registerOptions);
+ }
+
+ #endregion
+
+ #region Unregistration
+
+ ///
+ /// Remove a named container class registration.
+ ///
+ /// Type to unregister.
+ /// Name of registration.
+ /// true if the registration is successfully found and removed; otherwise, false .
+ public Boolean Unregister(String name = "") => this.Unregister(typeof(TRegister), name);
+
+ ///
+ /// Remove a named container class registration.
+ ///
+ /// Type to unregister.
+ /// Name of registration.
+ /// true if the registration is successfully found and removed; otherwise, false .
+ public Boolean Unregister(Type registerType, String name = "") => this.RegisteredTypes.RemoveRegistration(new TypeRegistration(registerType, name));
+
+ #endregion
+
+ #region Resolution
+
+ ///
+ /// Attempts to resolve a named type using specified options and the supplied constructor parameters.
+ ///
+ /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists).
+ /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail.
+ ///
+ /// Type to resolve.
+ /// Name of registration.
+ /// Resolution options.
+ /// Instance of type.
+ /// Unable to resolve the type.
+ public Object Resolve(Type resolveType, String name = null, DependencyContainerResolveOptions options = null) => this.RegisteredTypes.ResolveInternal(new TypeRegistration(resolveType, name), options ?? DependencyContainerResolveOptions.Default);
+
+ ///
+ /// Attempts to resolve a named type using specified options and the supplied constructor parameters.
+ ///
+ /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists).
+ /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail.
+ ///
+ /// Type to resolve.
+ /// Name of registration.
+ /// Resolution options.
+ /// Instance of type.
+ /// Unable to resolve the type.
+ public TResolveType Resolve(String name = null, DependencyContainerResolveOptions options = null) where TResolveType : class => (TResolveType)this.Resolve(typeof(TResolveType), name, options);
+
+ ///
+ /// Attempts to predict whether a given type can be resolved with the supplied constructor parameters options.
+ /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists).
+ /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail.
+ /// Note: Resolution may still fail if user defined factory registrations fail to construct objects when called.
+ ///
+ /// Type to resolve.
+ /// The name.
+ /// Resolution options.
+ ///
+ /// Bool indicating whether the type can be resolved.
+ ///
+ public Boolean CanResolve(Type resolveType, String name = null, DependencyContainerResolveOptions options = null) => this.RegisteredTypes.CanResolve(new TypeRegistration(resolveType, name), options);
+
+ ///
+ /// Attempts to predict whether a given named type can be resolved with the supplied constructor parameters options.
+ ///
+ /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists).
+ /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail.
+ ///
+ /// Note: Resolution may still fail if user defined factory registrations fail to construct objects when called.
+ ///
+ /// Type to resolve.
+ /// Name of registration.
+ /// Resolution options.
+ /// Bool indicating whether the type can be resolved.
+ public Boolean CanResolve(String name = null, DependencyContainerResolveOptions options = null) where TResolveType : class => this.CanResolve(typeof(TResolveType), name, options);
+
+ ///
+ /// Attempts to resolve a type using the default options.
+ ///
+ /// Type to resolve.
+ /// Resolved type or default if resolve fails.
+ /// true if resolved successfully, false otherwise.
+ public Boolean TryResolve(Type resolveType, out Object resolvedType) {
+ try {
+ resolvedType = this.Resolve(resolveType);
+ return true;
+ } catch(DependencyContainerResolutionException) {
+ resolvedType = null;
+ return false;
+ }
+ }
+
+ ///
+ /// Attempts to resolve a type using the given options.
+ ///
+ /// Type to resolve.
+ /// Resolution options.
+ /// Resolved type or default if resolve fails.
+ /// true if resolved successfully, false otherwise.
+ public Boolean TryResolve(Type resolveType, DependencyContainerResolveOptions options, out Object resolvedType) {
+ try {
+ resolvedType = this.Resolve(resolveType, options: options);
+ return true;
+ } catch(DependencyContainerResolutionException) {
+ resolvedType = null;
+ return false;
+ }
+ }
+
+ ///
+ /// Attempts to resolve a type using the default options and given name.
+ ///
+ /// Type to resolve.
+ /// Name of registration.
+ /// Resolved type or default if resolve fails.
+ /// true if resolved successfully, false otherwise.
+ public Boolean TryResolve(Type resolveType, String name, out Object resolvedType) {
+ try {
+ resolvedType = this.Resolve(resolveType, name);
+ return true;
+ } catch(DependencyContainerResolutionException) {
+ resolvedType = null;
+ return false;
+ }
+ }
+
+ ///
+ /// Attempts to resolve a type using the given options and name.
+ ///
+ /// Type to resolve.
+ /// Name of registration.
+ /// Resolution options.
+ /// Resolved type or default if resolve fails.
+ /// true if resolved successfully, false otherwise.
+ public Boolean TryResolve(Type resolveType, String name, DependencyContainerResolveOptions options, out Object resolvedType) {
+ try {
+ resolvedType = this.Resolve(resolveType, name, options);
+ return true;
+ } catch(DependencyContainerResolutionException) {
+ resolvedType = null;
+ return false;
+ }
+ }
+
+ ///
+ /// Attempts to resolve a type using the default options.
+ ///
+ /// Type to resolve.
+ /// Resolved type or default if resolve fails.
+ /// true if resolved successfully, false otherwise.
+ public Boolean TryResolve(out TResolveType resolvedType) where TResolveType : class {
+ try {
+ resolvedType = this.Resolve();
+ return true;
+ } catch(DependencyContainerResolutionException) {
+ resolvedType = default;
+ return false;
+ }
+ }
+
+ ///
+ /// Attempts to resolve a type using the given options.
+ ///
+ /// Type to resolve.
+ /// Resolution options.
+ /// Resolved type or default if resolve fails.
+ /// true if resolved successfully, false otherwise.
+ public Boolean TryResolve(DependencyContainerResolveOptions options, out TResolveType resolvedType) where TResolveType : class {
+ try {
+ resolvedType = this.Resolve(options: options);
+ return true;
+ } catch(DependencyContainerResolutionException) {
+ resolvedType = default;
+ return false;
+ }
+ }
+
+ ///
+ /// Attempts to resolve a type using the default options and given name.
+ ///
+ /// Type to resolve.
+ /// Name of registration.
+ /// Resolved type or default if resolve fails.
+ /// true if resolved successfully, false otherwise.
+ public Boolean TryResolve(String name, out TResolveType resolvedType) where TResolveType : class {
+ try {
+ resolvedType = this.Resolve(name);
+ return true;
+ } catch(DependencyContainerResolutionException) {
+ resolvedType = default;
+ return false;
+ }
+ }
+
+ ///
+ /// Attempts to resolve a type using the given options and name.
+ ///
+ /// Type to resolve.
+ /// Name of registration.
+ /// Resolution options.
+ /// Resolved type or default if resolve fails.
+ /// true if resolved successfully, false otherwise.
+ public Boolean TryResolve(String name, DependencyContainerResolveOptions options, out TResolveType resolvedType) where TResolveType : class {
+ try {
+ resolvedType = this.Resolve(name, options);
+ return true;
+ } catch(DependencyContainerResolutionException) {
+ resolvedType = default;
+ return false;
+ }
+ }
+
+ ///
+ /// Returns all registrations of a type.
+ ///
+ /// Type to resolveAll.
+ /// Whether to include un-named (default) registrations.
+ /// IEnumerable.
+ public IEnumerable ResolveAll(Type resolveType, Boolean includeUnnamed = false) => this.RegisteredTypes.Resolve(resolveType, includeUnnamed);
+
+ ///
+ /// Returns all registrations of a type.
+ ///
+ /// Type to resolveAll.
+ /// Whether to include un-named (default) registrations.
+ /// IEnumerable.
+ public IEnumerable ResolveAll(Boolean includeUnnamed = true) where TResolveType : class => this.ResolveAll(typeof(TResolveType), includeUnnamed).Cast();
+
+ ///
+ /// Attempts to resolve all public property dependencies on the given object using the given resolve options.
+ ///
+ /// Object to "build up".
+ /// Resolve options to use.
+ public void BuildUp(Object input, DependencyContainerResolveOptions resolveOptions = null) {
+ if(resolveOptions == null) {
+ resolveOptions = DependencyContainerResolveOptions.Default;
+ }
+
+ IEnumerable properties = input.GetType().GetProperties().Where(property => property.GetCacheGetMethod() != null && property.GetCacheSetMethod() != null && !property.PropertyType.IsValueType);
+
+ foreach(PropertyInfo property in properties.Where(property => property.GetValue(input, null) == null)) {
+ try {
+ property.SetValue(input, this.RegisteredTypes.ResolveInternal(new TypeRegistration(property.PropertyType), resolveOptions), null);
+ } catch(DependencyContainerResolutionException) {
+ // Catch any resolution errors and ignore them
+ }
+ }
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal static Boolean IsValidAssignment(Type registerType, Type registerImplementation) {
+ if(!registerType.IsGenericTypeDefinition) {
+ if(!registerType.IsAssignableFrom(registerImplementation)) {
+ return false;
+ }
+ } else {
+ if(registerType.IsInterface && registerImplementation.GetInterfaces().All(t => t.Name != registerType.Name)) {
+ return false;
+ }
+
+ if(registerType.IsAbstract && registerImplementation.BaseType != registerType) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static Boolean IsIgnoredAssembly(Assembly assembly) {
+ // TODO - find a better way to remove "system" assemblies from the auto registration
+ List> ignoreChecks = new List>
+ {
asm => asm.FullName.StartsWith("Microsoft.", StringComparison.Ordinal),
asm => asm.FullName.StartsWith("System.", StringComparison.Ordinal),
asm => asm.FullName.StartsWith("System,", StringComparison.Ordinal),
@@ -708,36 +544,32 @@
asm => asm.FullName.StartsWith("CR_VSTest", StringComparison.Ordinal),
asm => asm.FullName.StartsWith("DevExpress.CodeRush", StringComparison.Ordinal),
asm => asm.FullName.StartsWith("xunit.", StringComparison.Ordinal),
- };
-
- return ignoreChecks.Any(check => check(assembly));
- }
-
- private static bool IsIgnoredType(Type type, Func registrationPredicate)
- {
- // TODO - find a better way to remove "system" types from the auto registration
- var ignoreChecks = new List>()
- {
+ };
+
+ return ignoreChecks.Any(check => check(assembly));
+ }
+
+ private static Boolean IsIgnoredType(Type type, Func registrationPredicate) {
+ // TODO - find a better way to remove "system" types from the auto registration
+ List> ignoreChecks = new List>()
+ {
t => t.FullName?.StartsWith("System.", StringComparison.Ordinal) ?? false,
t => t.FullName?.StartsWith("Microsoft.", StringComparison.Ordinal) ?? false,
t => t.IsPrimitive,
t => t.IsGenericTypeDefinition,
- t => (t.GetConstructors(BindingFlags.Instance | BindingFlags.Public).Length == 0) &&
+ t => t.GetConstructors(BindingFlags.Instance | BindingFlags.Public).Length == 0 &&
!(t.IsInterface || t.IsAbstract),
- };
-
- if (registrationPredicate != null)
- {
- ignoreChecks.Add(t => !registrationPredicate(t));
- }
-
- return ignoreChecks.Any(check => check(type));
- }
-
- private static ObjectFactoryBase GetDefaultObjectFactory(Type registerType, Type registerImplementation) => registerType.IsInterface || registerType.IsAbstract
- ? (ObjectFactoryBase)new SingletonFactory(registerType, registerImplementation)
- : new MultiInstanceFactory(registerType, registerImplementation);
-
- #endregion
- }
+ };
+
+ if(registrationPredicate != null) {
+ ignoreChecks.Add(t => !registrationPredicate(t));
+ }
+
+ return ignoreChecks.Any(check => check(type));
+ }
+
+ private static ObjectFactoryBase GetDefaultObjectFactory(Type registerType, Type registerImplementation) => registerType.IsInterface || registerType.IsAbstract ? (ObjectFactoryBase)new SingletonFactory(registerType, registerImplementation) : new MultiInstanceFactory(registerType, registerImplementation);
+
+ #endregion
+ }
}
diff --git a/Swan/DependencyInjection/DependencyContainerRegistrationException.cs b/Swan/DependencyInjection/DependencyContainerRegistrationException.cs
index 3890744..38dfae6 100644
--- a/Swan/DependencyInjection/DependencyContainerRegistrationException.cs
+++ b/Swan/DependencyInjection/DependencyContainerRegistrationException.cs
@@ -1,46 +1,34 @@
-namespace Swan.DependencyInjection
-{
- using System;
- using System.Collections.Generic;
- using System.Linq;
-
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Swan.DependencyInjection {
+ ///
+ /// Generic Constraint Registration Exception.
+ ///
+ ///
+ public class DependencyContainerRegistrationException : Exception {
+ private const String ConvertErrorText = "Cannot convert current registration of {0} to {1}";
+ private const String RegisterErrorText = "Cannot register type {0} - abstract classes or interfaces are not valid implementation types for {1}.";
+ private const String ErrorText = "Duplicate implementation of type {0} found ({1}).";
+
///
- /// Generic Constraint Registration Exception.
+ /// Initializes a new instance of the class.
///
- ///
- public class DependencyContainerRegistrationException : Exception
- {
- private const string ConvertErrorText = "Cannot convert current registration of {0} to {1}";
- private const string RegisterErrorText =
- "Cannot register type {0} - abstract classes or interfaces are not valid implementation types for {1}.";
- private const string ErrorText = "Duplicate implementation of type {0} found ({1}).";
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// Type of the register.
- /// The types.
- public DependencyContainerRegistrationException(Type registerType, IEnumerable types)
- : base(string.Format(ErrorText, registerType, GetTypesString(types)))
- {
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The type.
- /// The method.
- /// if set to true [is type factory].
- public DependencyContainerRegistrationException(Type type, string method, bool isTypeFactory = false)
- : base(isTypeFactory
- ? string.Format(RegisterErrorText, type.FullName, method)
- : string.Format(ConvertErrorText, type.FullName, method))
- {
- }
-
- private static string GetTypesString(IEnumerable types)
- {
- return string.Join(",", types.Select(type => type.FullName));
- }
- }
+ /// Type of the register.
+ /// The types.
+ public DependencyContainerRegistrationException(Type registerType, IEnumerable types) : base(String.Format(ErrorText, registerType, GetTypesString(types))) {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The type.
+ /// The method.
+ /// if set to true [is type factory].
+ public DependencyContainerRegistrationException(Type type, String method, Boolean isTypeFactory = false) : base(isTypeFactory ? String.Format(RegisterErrorText, type.FullName, method) : String.Format(ConvertErrorText, type.FullName, method)) {
+ }
+
+ private static String GetTypesString(IEnumerable types) => String.Join(",", types.Select(type => type.FullName));
+ }
}
\ No newline at end of file
diff --git a/Swan/DependencyInjection/DependencyContainerResolutionException.cs b/Swan/DependencyInjection/DependencyContainerResolutionException.cs
index da98665..63d6795 100644
--- a/Swan/DependencyInjection/DependencyContainerResolutionException.cs
+++ b/Swan/DependencyInjection/DependencyContainerResolutionException.cs
@@ -1,31 +1,25 @@
-namespace Swan.DependencyInjection
-{
- using System;
-
+using System;
+
+namespace Swan.DependencyInjection {
+ ///
+ /// An exception for dependency resolutions.
+ ///
+ ///
+ [Serializable]
+ public class DependencyContainerResolutionException : Exception {
///
- /// An exception for dependency resolutions.
+ /// Initializes a new instance of the class.
///
- ///
- [Serializable]
- public class DependencyContainerResolutionException : Exception
- {
- ///
- /// Initializes a new instance of the class.
- ///
- /// The type.
- public DependencyContainerResolutionException(Type type)
- : base($"Unable to resolve type: {type.FullName}")
- {
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The type.
- /// The inner exception.
- public DependencyContainerResolutionException(Type type, Exception innerException)
- : base($"Unable to resolve type: {type.FullName}", innerException)
- {
- }
- }
+ /// The type.
+ public DependencyContainerResolutionException(Type type) : base($"Unable to resolve type: {type.FullName}") {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The type.
+ /// The inner exception.
+ public DependencyContainerResolutionException(Type type, Exception innerException) : base($"Unable to resolve type: {type.FullName}", innerException) {
+ }
+ }
}
diff --git a/Swan/DependencyInjection/DependencyContainerResolveOptions.cs b/Swan/DependencyInjection/DependencyContainerResolveOptions.cs
index 28d5cb0..4bf2546 100644
--- a/Swan/DependencyInjection/DependencyContainerResolveOptions.cs
+++ b/Swan/DependencyInjection/DependencyContainerResolveOptions.cs
@@ -1,114 +1,106 @@
-namespace Swan.DependencyInjection
-{
- using System.Collections.Generic;
-
+using System.Collections.Generic;
+
+namespace Swan.DependencyInjection {
+ ///
+ /// Resolution settings.
+ ///
+ public class DependencyContainerResolveOptions {
///
- /// Resolution settings.
+ /// Gets the default options (attempt resolution of unregistered types, fail on named resolution if name not found).
///
- public class DependencyContainerResolveOptions
- {
- ///
- /// Gets the default options (attempt resolution of unregistered types, fail on named resolution if name not found).
- ///
- public static DependencyContainerResolveOptions Default { get; } = new DependencyContainerResolveOptions();
-
- ///
- /// Gets or sets the unregistered resolution action.
- ///
- ///
- /// The unregistered resolution action.
- ///
- public DependencyContainerUnregisteredResolutionAction UnregisteredResolutionAction { get; set; } =
- DependencyContainerUnregisteredResolutionAction.AttemptResolve;
-
- ///
- /// Gets or sets the named resolution failure action.
- ///
- ///
- /// The named resolution failure action.
- ///
- public DependencyContainerNamedResolutionFailureAction NamedResolutionFailureAction { get; set; } =
- DependencyContainerNamedResolutionFailureAction.Fail;
-
- ///
- /// Gets the constructor parameters.
- ///
- ///
- /// The constructor parameters.
- ///
- public Dictionary ConstructorParameters { get; } = new Dictionary();
-
- ///
- /// Clones this instance.
- ///
- ///
- public DependencyContainerResolveOptions Clone() => new DependencyContainerResolveOptions
- {
- NamedResolutionFailureAction = NamedResolutionFailureAction,
- UnregisteredResolutionAction = UnregisteredResolutionAction,
- };
- }
-
+ public static DependencyContainerResolveOptions Default { get; } = new DependencyContainerResolveOptions();
+
///
- /// Defines Resolution actions.
+ /// Gets or sets the unregistered resolution action.
///
- public enum DependencyContainerUnregisteredResolutionAction
- {
- ///
- /// Attempt to resolve type, even if the type isn't registered.
- ///
- /// Registered types/options will always take precedence.
- ///
- AttemptResolve,
-
- ///
- /// Fail resolution if type not explicitly registered
- ///
- Fail,
-
- ///
- /// Attempt to resolve unregistered type if requested type is generic
- /// and no registration exists for the specific generic parameters used.
- ///
- /// Registered types/options will always take precedence.
- ///
- GenericsOnly,
- }
-
+ ///
+ /// The unregistered resolution action.
+ ///
+ public DependencyContainerUnregisteredResolutionAction UnregisteredResolutionAction { get; set; } = DependencyContainerUnregisteredResolutionAction.AttemptResolve;
+
///
- /// Enumerates failure actions.
+ /// Gets or sets the named resolution failure action.
///
- public enum DependencyContainerNamedResolutionFailureAction
- {
- ///
- /// The attempt unnamed resolution
- ///
- AttemptUnnamedResolution,
-
- ///
- /// The fail
- ///
- Fail,
- }
-
+ ///
+ /// The named resolution failure action.
+ ///
+ public DependencyContainerNamedResolutionFailureAction NamedResolutionFailureAction { get; set; } = DependencyContainerNamedResolutionFailureAction.Fail;
+
///
- /// Enumerates duplicate definition actions.
+ /// Gets the constructor parameters.
///
- public enum DependencyContainerDuplicateImplementationAction
- {
- ///
- /// The register single
- ///
- RegisterSingle,
-
- ///
- /// The register multiple
- ///
- RegisterMultiple,
-
- ///
- /// The fail
- ///
- Fail,
- }
+ ///
+ /// The constructor parameters.
+ ///
+ public Dictionary ConstructorParameters { get; } = new Dictionary();
+
+ ///
+ /// Clones this instance.
+ ///
+ ///
+ public DependencyContainerResolveOptions Clone() => new DependencyContainerResolveOptions {
+ NamedResolutionFailureAction = NamedResolutionFailureAction,
+ UnregisteredResolutionAction = UnregisteredResolutionAction,
+ };
+ }
+
+ ///
+ /// Defines Resolution actions.
+ ///
+ public enum DependencyContainerUnregisteredResolutionAction {
+ ///
+ /// Attempt to resolve type, even if the type isn't registered.
+ ///
+ /// Registered types/options will always take precedence.
+ ///
+ AttemptResolve,
+
+ ///
+ /// Fail resolution if type not explicitly registered
+ ///
+ Fail,
+
+ ///
+ /// Attempt to resolve unregistered type if requested type is generic
+ /// and no registration exists for the specific generic parameters used.
+ ///
+ /// Registered types/options will always take precedence.
+ ///
+ GenericsOnly,
+ }
+
+ ///
+ /// Enumerates failure actions.
+ ///
+ public enum DependencyContainerNamedResolutionFailureAction {
+ ///
+ /// The attempt unnamed resolution
+ ///
+ AttemptUnnamedResolution,
+
+ ///
+ /// The fail
+ ///
+ Fail,
+ }
+
+ ///
+ /// Enumerates duplicate definition actions.
+ ///
+ public enum DependencyContainerDuplicateImplementationAction {
+ ///
+ /// The register single
+ ///
+ RegisterSingle,
+
+ ///
+ /// The register multiple
+ ///
+ RegisterMultiple,
+
+ ///
+ /// The fail
+ ///
+ Fail,
+ }
}
\ No newline at end of file
diff --git a/Swan/DependencyInjection/DependencyContainerWeakReferenceException.cs b/Swan/DependencyInjection/DependencyContainerWeakReferenceException.cs
index eb1e085..22953f4 100644
--- a/Swan/DependencyInjection/DependencyContainerWeakReferenceException.cs
+++ b/Swan/DependencyInjection/DependencyContainerWeakReferenceException.cs
@@ -1,22 +1,18 @@
-namespace Swan.DependencyInjection
-{
- using System;
-
+using System;
+
+namespace Swan.DependencyInjection {
+ ///
+ /// Weak Reference Exception.
+ ///
+ ///
+ public class DependencyContainerWeakReferenceException : Exception {
+ private const String ErrorText = "Unable to instantiate {0} - referenced object has been reclaimed";
+
///
- /// Weak Reference Exception.
+ /// Initializes a new instance of the class.
///
- ///
- public class DependencyContainerWeakReferenceException : Exception
- {
- private const string ErrorText = "Unable to instantiate {0} - referenced object has been reclaimed";
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The type.
- public DependencyContainerWeakReferenceException(Type type)
- : base(string.Format(ErrorText, type.FullName))
- {
- }
- }
+ /// The type.
+ public DependencyContainerWeakReferenceException(Type type) : base(String.Format(ErrorText, type.FullName)) {
+ }
+ }
}
diff --git a/Swan/DependencyInjection/ObjectFactoryBase.cs b/Swan/DependencyInjection/ObjectFactoryBase.cs
index 51ff96d..d2189ee 100644
--- a/Swan/DependencyInjection/ObjectFactoryBase.cs
+++ b/Swan/DependencyInjection/ObjectFactoryBase.cs
@@ -1,423 +1,352 @@
-namespace Swan.DependencyInjection
-{
- using System;
- using System.Collections.Generic;
- using System.Reflection;
-
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+
+namespace Swan.DependencyInjection {
+ ///
+ /// Represents an abstract class for Object Factory.
+ ///
+ public abstract class ObjectFactoryBase {
///
- /// Represents an abstract class for Object Factory.
+ /// Whether to assume this factory successfully constructs its objects
+ ///
+ /// Generally set to true for delegate style factories as CanResolve cannot delve
+ /// into the delegates they contain.
///
- public abstract class ObjectFactoryBase
- {
- ///
- /// Whether to assume this factory successfully constructs its objects
- ///
- /// Generally set to true for delegate style factories as CanResolve cannot delve
- /// into the delegates they contain.
- ///
- public virtual bool AssumeConstruction => false;
-
- ///
- /// The type the factory instantiates.
- ///
- public abstract Type CreatesType { get; }
-
- ///
- /// Constructor to use, if specified.
- ///
- public ConstructorInfo Constructor { get; private set; }
-
- ///
- /// Gets the singleton variant.
- ///
- ///
- /// The singleton variant.
- ///
- /// singleton.
- public virtual ObjectFactoryBase SingletonVariant =>
- throw new DependencyContainerRegistrationException(GetType(), "singleton");
-
- ///
- /// Gets the multi instance variant.
- ///
- ///
- /// The multi instance variant.
- ///
- /// multi-instance.
- public virtual ObjectFactoryBase MultiInstanceVariant =>
- throw new DependencyContainerRegistrationException(GetType(), "multi-instance");
-
- ///
- /// Gets the strong reference variant.
- ///
- ///
- /// The strong reference variant.
- ///
- /// strong reference.
- public virtual ObjectFactoryBase StrongReferenceVariant =>
- throw new DependencyContainerRegistrationException(GetType(), "strong reference");
-
- ///
- /// Gets the weak reference variant.
- ///
- ///
- /// The weak reference variant.
- ///
- /// weak reference.
- public virtual ObjectFactoryBase WeakReferenceVariant =>
- throw new DependencyContainerRegistrationException(GetType(), "weak reference");
-
- ///
- /// Create the type.
- ///
- /// Type user requested to be resolved.
- /// Container that requested the creation.
- /// The options.
- /// Instance of type.
- public abstract object GetObject(
- Type requestedType,
- DependencyContainer container,
- DependencyContainerResolveOptions options);
-
- ///
- /// Gets the factory for child container.
- ///
- /// The type.
- /// The parent.
- /// The child.
- ///
- public virtual ObjectFactoryBase GetFactoryForChildContainer(
- Type type,
- DependencyContainer parent,
- DependencyContainer child)
- {
- return this;
- }
- }
-
- ///
+ public virtual Boolean AssumeConstruction => false;
+
///
- /// IObjectFactory that creates new instances of types for each resolution.
+ /// The type the factory instantiates.
///
- internal class MultiInstanceFactory : ObjectFactoryBase
- {
- private readonly Type _registerType;
- private readonly Type _registerImplementation;
-
- public MultiInstanceFactory(Type registerType, Type registerImplementation)
- {
- if (registerImplementation.IsAbstract || registerImplementation.IsInterface)
- {
- throw new DependencyContainerRegistrationException(registerImplementation,
- "MultiInstanceFactory",
- true);
- }
-
- if (!DependencyContainer.IsValidAssignment(registerType, registerImplementation))
- {
- throw new DependencyContainerRegistrationException(registerImplementation,
- "MultiInstanceFactory",
- true);
- }
-
- _registerType = registerType;
- _registerImplementation = registerImplementation;
- }
-
- public override Type CreatesType => _registerImplementation;
-
- public override ObjectFactoryBase SingletonVariant =>
- new SingletonFactory(_registerType, _registerImplementation);
-
- public override ObjectFactoryBase MultiInstanceVariant => this;
-
- public override object GetObject(
- Type requestedType,
- DependencyContainer container,
- DependencyContainerResolveOptions options)
- {
- try
- {
- return container.RegisteredTypes.ConstructType(_registerImplementation, Constructor, options);
- }
- catch (DependencyContainerResolutionException ex)
- {
- throw new DependencyContainerResolutionException(_registerType, ex);
- }
- }
- }
-
- ///
+ public abstract Type CreatesType {
+ get;
+ }
+
///
- /// IObjectFactory that invokes a specified delegate to construct the object.
+ /// Constructor to use, if specified.
///
- internal class DelegateFactory : ObjectFactoryBase
- {
- private readonly Type _registerType;
-
- private readonly Func, object> _factory;
-
- public DelegateFactory(
- Type registerType,
- Func, object> factory)
- {
- _factory = factory ?? throw new ArgumentNullException(nameof(factory));
-
- _registerType = registerType;
- }
-
- public override bool AssumeConstruction => true;
-
- public override Type CreatesType => _registerType;
-
- public override ObjectFactoryBase WeakReferenceVariant => new WeakDelegateFactory(_registerType, _factory);
-
- public override ObjectFactoryBase StrongReferenceVariant => this;
-
- public override object GetObject(
- Type requestedType,
- DependencyContainer container,
- DependencyContainerResolveOptions options)
- {
- try
- {
- return _factory.Invoke(container, options.ConstructorParameters);
- }
- catch (Exception ex)
- {
- throw new DependencyContainerResolutionException(_registerType, ex);
- }
- }
- }
-
- ///
+ public ConstructorInfo Constructor {
+ get; private set;
+ }
+
///
- /// IObjectFactory that invokes a specified delegate to construct the object
- /// Holds the delegate using a weak reference.
+ /// Gets the singleton variant.
///
- internal class WeakDelegateFactory : ObjectFactoryBase
- {
- private readonly Type _registerType;
-
- private readonly WeakReference _factory;
-
- public WeakDelegateFactory(
- Type registerType,
- Func, object> factory)
- {
- if (factory == null)
- throw new ArgumentNullException(nameof(factory));
-
- _factory = new WeakReference(factory);
-
- _registerType = registerType;
- }
-
- public override bool AssumeConstruction => true;
-
- public override Type CreatesType => _registerType;
-
- public override ObjectFactoryBase StrongReferenceVariant
- {
- get
- {
- if (!(_factory.Target is Func, object> factory))
- throw new DependencyContainerWeakReferenceException(_registerType);
-
- return new DelegateFactory(_registerType, factory);
- }
- }
-
- public override ObjectFactoryBase WeakReferenceVariant => this;
-
- public override object GetObject(
- Type requestedType,
- DependencyContainer container,
- DependencyContainerResolveOptions options)
- {
- if (!(_factory.Target is Func, object> factory))
- throw new DependencyContainerWeakReferenceException(_registerType);
-
- try
- {
- return factory.Invoke(container, options.ConstructorParameters);
- }
- catch (Exception ex)
- {
- throw new DependencyContainerResolutionException(_registerType, ex);
- }
- }
- }
-
+ ///
+ /// The singleton variant.
+ ///
+ /// singleton.
+ public virtual ObjectFactoryBase SingletonVariant => throw new DependencyContainerRegistrationException(this.GetType(), "singleton");
+
///
- /// Stores an particular instance to return for a type.
+ /// Gets the multi instance variant.
///
- internal class InstanceFactory : ObjectFactoryBase, IDisposable
- {
- private readonly Type _registerType;
- private readonly Type _registerImplementation;
- private readonly object _instance;
-
- public InstanceFactory(Type registerType, Type registerImplementation, object instance)
- {
- if (!DependencyContainer.IsValidAssignment(registerType, registerImplementation))
- throw new DependencyContainerRegistrationException(registerImplementation, "InstanceFactory", true);
-
- _registerType = registerType;
- _registerImplementation = registerImplementation;
- _instance = instance;
- }
-
- public override bool AssumeConstruction => true;
-
- public override Type CreatesType => _registerImplementation;
-
- public override ObjectFactoryBase MultiInstanceVariant =>
- new MultiInstanceFactory(_registerType, _registerImplementation);
-
- public override ObjectFactoryBase WeakReferenceVariant =>
- new WeakInstanceFactory(_registerType, _registerImplementation, _instance);
-
- public override ObjectFactoryBase StrongReferenceVariant => this;
-
- public override object GetObject(
- Type requestedType,
- DependencyContainer container,
- DependencyContainerResolveOptions options)
- {
- return _instance;
- }
-
- public void Dispose()
- {
- var disposable = _instance as IDisposable;
-
- disposable?.Dispose();
- }
- }
-
+ ///
+ /// The multi instance variant.
+ ///
+ /// multi-instance.
+ public virtual ObjectFactoryBase MultiInstanceVariant => throw new DependencyContainerRegistrationException(this.GetType(), "multi-instance");
+
///
- /// Stores the instance with a weak reference.
+ /// Gets the strong reference variant.
///
- internal class WeakInstanceFactory : ObjectFactoryBase, IDisposable
- {
- private readonly Type _registerType;
- private readonly Type _registerImplementation;
- private readonly WeakReference _instance;
-
- public WeakInstanceFactory(Type registerType, Type registerImplementation, object instance)
- {
- if (!DependencyContainer.IsValidAssignment(registerType, registerImplementation))
- {
- throw new DependencyContainerRegistrationException(
- registerImplementation,
- "WeakInstanceFactory",
- true);
- }
-
- _registerType = registerType;
- _registerImplementation = registerImplementation;
- _instance = new WeakReference(instance);
- }
-
- public override Type CreatesType => _registerImplementation;
-
- public override ObjectFactoryBase MultiInstanceVariant =>
- new MultiInstanceFactory(_registerType, _registerImplementation);
-
- public override ObjectFactoryBase WeakReferenceVariant => this;
-
- public override ObjectFactoryBase StrongReferenceVariant
- {
- get
- {
- var instance = _instance.Target;
-
- if (instance == null)
- throw new DependencyContainerWeakReferenceException(_registerType);
-
- return new InstanceFactory(_registerType, _registerImplementation, instance);
- }
- }
-
- public override object GetObject(
- Type requestedType,
- DependencyContainer container,
- DependencyContainerResolveOptions options)
- {
- var instance = _instance.Target;
-
- if (instance == null)
- throw new DependencyContainerWeakReferenceException(_registerType);
-
- return instance;
- }
-
- public void Dispose() => (_instance.Target as IDisposable)?.Dispose();
- }
-
+ ///
+ /// The strong reference variant.
+ ///
+ /// strong reference.
+ public virtual ObjectFactoryBase StrongReferenceVariant => throw new DependencyContainerRegistrationException(this.GetType(), "strong reference");
+
///
- /// A factory that lazy instantiates a type and always returns the same instance.
+ /// Gets the weak reference variant.
///
- internal class SingletonFactory : ObjectFactoryBase, IDisposable
- {
- private readonly Type _registerType;
- private readonly Type _registerImplementation;
- private readonly object _singletonLock = new object();
- private object _current;
-
- public SingletonFactory(Type registerType, Type registerImplementation)
- {
- if (registerImplementation.IsAbstract || registerImplementation.IsInterface)
- {
- throw new DependencyContainerRegistrationException(registerImplementation, nameof(SingletonFactory), true);
- }
-
- if (!DependencyContainer.IsValidAssignment(registerType, registerImplementation))
- {
- throw new DependencyContainerRegistrationException(registerImplementation, nameof(SingletonFactory), true);
- }
-
- _registerType = registerType;
- _registerImplementation = registerImplementation;
- }
-
- public override Type CreatesType => _registerImplementation;
-
- public override ObjectFactoryBase SingletonVariant => this;
-
- public override ObjectFactoryBase MultiInstanceVariant =>
- new MultiInstanceFactory(_registerType, _registerImplementation);
-
- public override object GetObject(
- Type requestedType,
- DependencyContainer container,
- DependencyContainerResolveOptions options)
- {
- if (options.ConstructorParameters.Count != 0)
- throw new ArgumentException("Cannot specify parameters for singleton types");
-
- lock (_singletonLock)
- {
- if (_current == null)
- _current = container.RegisteredTypes.ConstructType(_registerImplementation, Constructor, options);
- }
-
- return _current;
- }
-
- public override ObjectFactoryBase GetFactoryForChildContainer(
- Type type,
- DependencyContainer parent,
- DependencyContainer child)
- {
- // We make sure that the singleton is constructed before the child container takes the factory.
- // Otherwise the results would vary depending on whether or not the parent container had resolved
- // the type before the child container does.
- GetObject(type, parent, DependencyContainerResolveOptions.Default);
- return this;
- }
-
- public void Dispose() => (_current as IDisposable)?.Dispose();
- }
+ ///
+ /// The weak reference variant.
+ ///
+ /// weak reference.
+ public virtual ObjectFactoryBase WeakReferenceVariant => throw new DependencyContainerRegistrationException(this.GetType(), "weak reference");
+
+ ///
+ /// Create the type.
+ ///
+ /// Type user requested to be resolved.
+ /// Container that requested the creation.
+ /// The options.
+ /// Instance of type.
+ public abstract Object GetObject(Type requestedType, DependencyContainer container, DependencyContainerResolveOptions options);
+
+ ///
+ /// Gets the factory for child container.
+ ///
+ /// The type.
+ /// The parent.
+ /// The child.
+ ///
+ public virtual ObjectFactoryBase GetFactoryForChildContainer(Type type, DependencyContainer parent, DependencyContainer child) => this;
+ }
+
+ ///
+ ///
+ /// IObjectFactory that creates new instances of types for each resolution.
+ ///
+ internal class MultiInstanceFactory : ObjectFactoryBase {
+ private readonly Type _registerType;
+ private readonly Type _registerImplementation;
+
+ public MultiInstanceFactory(Type registerType, Type registerImplementation) {
+ if(registerImplementation.IsAbstract || registerImplementation.IsInterface) {
+ throw new DependencyContainerRegistrationException(registerImplementation, "MultiInstanceFactory", true);
+ }
+
+ if(!DependencyContainer.IsValidAssignment(registerType, registerImplementation)) {
+ throw new DependencyContainerRegistrationException(registerImplementation, "MultiInstanceFactory", true);
+ }
+
+ this._registerType = registerType;
+ this._registerImplementation = registerImplementation;
+ }
+
+ public override Type CreatesType => this._registerImplementation;
+
+ public override ObjectFactoryBase SingletonVariant =>
+ new SingletonFactory(this._registerType, this._registerImplementation);
+
+ public override ObjectFactoryBase MultiInstanceVariant => this;
+
+ public override Object GetObject(Type requestedType, DependencyContainer container, DependencyContainerResolveOptions options) {
+ try {
+ return container.RegisteredTypes.ConstructType(this._registerImplementation, this.Constructor, options);
+ } catch(DependencyContainerResolutionException ex) {
+ throw new DependencyContainerResolutionException(this._registerType, ex);
+ }
+ }
+ }
+
+ ///
+ ///
+ /// IObjectFactory that invokes a specified delegate to construct the object.
+ ///
+ internal class DelegateFactory : ObjectFactoryBase {
+ private readonly Type _registerType;
+
+ private readonly Func, Object> _factory;
+
+ public DelegateFactory(
+ Type registerType,
+ Func, Object> factory) {
+ this._factory = factory ?? throw new ArgumentNullException(nameof(factory));
+
+ this._registerType = registerType;
+ }
+
+ public override Boolean AssumeConstruction => true;
+
+ public override Type CreatesType => this._registerType;
+
+ public override ObjectFactoryBase WeakReferenceVariant => new WeakDelegateFactory(this._registerType, this._factory);
+
+ public override ObjectFactoryBase StrongReferenceVariant => this;
+
+ public override Object GetObject(Type requestedType, DependencyContainer container, DependencyContainerResolveOptions options) {
+ try {
+ return this._factory.Invoke(container, options.ConstructorParameters);
+ } catch(Exception ex) {
+ throw new DependencyContainerResolutionException(this._registerType, ex);
+ }
+ }
+ }
+
+ ///
+ ///
+ /// IObjectFactory that invokes a specified delegate to construct the object
+ /// Holds the delegate using a weak reference.
+ ///
+ internal class WeakDelegateFactory : ObjectFactoryBase {
+ private readonly Type _registerType;
+
+ private readonly WeakReference _factory;
+
+ public WeakDelegateFactory(Type registerType, Func, Object> factory) {
+ if(factory == null) {
+ throw new ArgumentNullException(nameof(factory));
+ }
+
+ this._factory = new WeakReference(factory);
+
+ this._registerType = registerType;
+ }
+
+ public override Boolean AssumeConstruction => true;
+
+ public override Type CreatesType => this._registerType;
+
+ public override ObjectFactoryBase StrongReferenceVariant {
+ get {
+ if(!(this._factory.Target is Func, global::System.Object> factory)) {
+ throw new DependencyContainerWeakReferenceException(this._registerType);
+ }
+
+ return new DelegateFactory(this._registerType, factory);
+ }
+ }
+
+ public override ObjectFactoryBase WeakReferenceVariant => this;
+
+ public override Object GetObject(Type requestedType, DependencyContainer container, DependencyContainerResolveOptions options) {
+ if(!(this._factory.Target is Func, global::System.Object> factory)) {
+ throw new DependencyContainerWeakReferenceException(this._registerType);
+ }
+
+ try {
+ return factory.Invoke(container, options.ConstructorParameters);
+ } catch(Exception ex) {
+ throw new DependencyContainerResolutionException(this._registerType, ex);
+ }
+ }
+ }
+
+ ///
+ /// Stores an particular instance to return for a type.
+ ///
+ internal class InstanceFactory : ObjectFactoryBase, IDisposable {
+ private readonly Type _registerType;
+ private readonly Type _registerImplementation;
+ private readonly Object _instance;
+
+ public InstanceFactory(Type registerType, Type registerImplementation, Object instance) {
+ if(!DependencyContainer.IsValidAssignment(registerType, registerImplementation)) {
+ throw new DependencyContainerRegistrationException(registerImplementation, "InstanceFactory", true);
+ }
+
+ this._registerType = registerType;
+ this._registerImplementation = registerImplementation;
+ this._instance = instance;
+ }
+
+ public override Boolean AssumeConstruction => true;
+
+ public override Type CreatesType => this._registerImplementation;
+
+ public override ObjectFactoryBase MultiInstanceVariant => new MultiInstanceFactory(this._registerType, this._registerImplementation);
+
+ public override ObjectFactoryBase WeakReferenceVariant => new WeakInstanceFactory(this._registerType, this._registerImplementation, this._instance);
+
+ public override ObjectFactoryBase StrongReferenceVariant => this;
+
+ public override Object GetObject(Type requestedType, DependencyContainer container, DependencyContainerResolveOptions options) => this._instance;
+
+ public void Dispose() {
+ IDisposable disposable = this._instance as IDisposable;
+
+ disposable?.Dispose();
+ }
+ }
+
+ ///
+ /// Stores the instance with a weak reference.
+ ///
+ internal class WeakInstanceFactory : ObjectFactoryBase, IDisposable {
+ private readonly Type _registerType;
+ private readonly Type _registerImplementation;
+ private readonly WeakReference _instance;
+
+ public WeakInstanceFactory(Type registerType, Type registerImplementation, Object instance) {
+ if(!DependencyContainer.IsValidAssignment(registerType, registerImplementation)) {
+ throw new DependencyContainerRegistrationException(registerImplementation, "WeakInstanceFactory", true);
+ }
+
+ this._registerType = registerType;
+ this._registerImplementation = registerImplementation;
+ this._instance = new WeakReference(instance);
+ }
+
+ public override Type CreatesType => this._registerImplementation;
+
+ public override ObjectFactoryBase MultiInstanceVariant => new MultiInstanceFactory(this._registerType, this._registerImplementation);
+
+ public override ObjectFactoryBase WeakReferenceVariant => this;
+
+ public override ObjectFactoryBase StrongReferenceVariant {
+ get {
+ Object instance = this._instance.Target;
+
+ if(instance == null) {
+ throw new DependencyContainerWeakReferenceException(this._registerType);
+ }
+
+ return new InstanceFactory(this._registerType, this._registerImplementation, instance);
+ }
+ }
+
+ public override Object GetObject(Type requestedType, DependencyContainer container, DependencyContainerResolveOptions options) {
+ Object instance = this._instance.Target;
+
+ if(instance == null) {
+ throw new DependencyContainerWeakReferenceException(this._registerType);
+ }
+
+ return instance;
+ }
+
+ public void Dispose() => (this._instance.Target as IDisposable)?.Dispose();
+ }
+
+ ///
+ /// A factory that lazy instantiates a type and always returns the same instance.
+ ///
+ internal class SingletonFactory : ObjectFactoryBase, IDisposable {
+ private readonly Type _registerType;
+ private readonly Type _registerImplementation;
+ private readonly Object _singletonLock = new Object();
+ private Object _current;
+
+ public SingletonFactory(Type registerType, Type registerImplementation) {
+ if(registerImplementation.IsAbstract || registerImplementation.IsInterface) {
+ throw new DependencyContainerRegistrationException(registerImplementation, nameof(SingletonFactory), true);
+ }
+
+ if(!DependencyContainer.IsValidAssignment(registerType, registerImplementation)) {
+ throw new DependencyContainerRegistrationException(registerImplementation, nameof(SingletonFactory), true);
+ }
+
+ this._registerType = registerType;
+ this._registerImplementation = registerImplementation;
+ }
+
+ public override Type CreatesType => this._registerImplementation;
+
+ public override ObjectFactoryBase SingletonVariant => this;
+
+ public override ObjectFactoryBase MultiInstanceVariant =>
+ new MultiInstanceFactory(this._registerType, this._registerImplementation);
+
+ public override Object GetObject(
+ Type requestedType,
+ DependencyContainer container,
+ DependencyContainerResolveOptions options) {
+ if(options.ConstructorParameters.Count != 0) {
+ throw new ArgumentException("Cannot specify parameters for singleton types");
+ }
+
+ lock(this._singletonLock) {
+ if(this._current == null) {
+ this._current = container.RegisteredTypes.ConstructType(this._registerImplementation, this.Constructor, options);
+ }
+ }
+
+ return this._current;
+ }
+
+ public override ObjectFactoryBase GetFactoryForChildContainer(
+ Type type,
+ DependencyContainer parent,
+ DependencyContainer child) {
+ // We make sure that the singleton is constructed before the child container takes the factory.
+ // Otherwise the results would vary depending on whether or not the parent container had resolved
+ // the type before the child container does.
+ _ = this.GetObject(type, parent, DependencyContainerResolveOptions.Default);
+ return this;
+ }
+
+ public void Dispose() => (this._current as IDisposable)?.Dispose();
+ }
}
diff --git a/Swan/DependencyInjection/RegisterOptions.cs b/Swan/DependencyInjection/RegisterOptions.cs
index f8ebfe8..4c83ed2 100644
--- a/Swan/DependencyInjection/RegisterOptions.cs
+++ b/Swan/DependencyInjection/RegisterOptions.cs
@@ -1,131 +1,119 @@
-namespace Swan.DependencyInjection
-{
- using System;
- using System.Collections.Generic;
- using System.Linq;
-
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Swan.DependencyInjection {
+ ///
+ /// Registration options for "fluent" API.
+ ///
+ public sealed class RegisterOptions {
+ private readonly TypesConcurrentDictionary _registeredTypes;
+ private readonly DependencyContainer.TypeRegistration _registration;
+
///
- /// Registration options for "fluent" API.
+ /// Initializes a new instance of the class.
///
- public sealed class RegisterOptions
- {
- private readonly TypesConcurrentDictionary _registeredTypes;
- private readonly DependencyContainer.TypeRegistration _registration;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The registered types.
- /// The registration.
- public RegisterOptions(TypesConcurrentDictionary registeredTypes, DependencyContainer.TypeRegistration registration)
- {
- _registeredTypes = registeredTypes;
- _registration = registration;
- }
-
- ///
- /// Make registration a singleton (single instance) if possible.
- ///
- /// A registration options for fluent API.
- /// Generic constraint registration exception.
- public RegisterOptions AsSingleton()
- {
- var currentFactory = _registeredTypes.GetCurrentFactory(_registration);
-
- if (currentFactory == null)
- throw new DependencyContainerRegistrationException(_registration.Type, "singleton");
-
- return _registeredTypes.AddUpdateRegistration(_registration, currentFactory.SingletonVariant);
- }
-
- ///
- /// Make registration multi-instance if possible.
- ///
- /// A registration options for fluent API.
- /// Generic constraint registration exception.
- public RegisterOptions AsMultiInstance()
- {
- var currentFactory = _registeredTypes.GetCurrentFactory(_registration);
-
- if (currentFactory == null)
- throw new DependencyContainerRegistrationException(_registration.Type, "multi-instance");
-
- return _registeredTypes.AddUpdateRegistration(_registration, currentFactory.MultiInstanceVariant);
- }
-
- ///
- /// Make registration hold a weak reference if possible.
- ///
- /// A registration options for fluent API.
- /// Generic constraint registration exception.
- public RegisterOptions WithWeakReference()
- {
- var currentFactory = _registeredTypes.GetCurrentFactory(_registration);
-
- if (currentFactory == null)
- throw new DependencyContainerRegistrationException(_registration.Type, "weak reference");
-
- return _registeredTypes.AddUpdateRegistration(_registration, currentFactory.WeakReferenceVariant);
- }
-
- ///
- /// Make registration hold a strong reference if possible.
- ///
- /// A registration options for fluent API.
- /// Generic constraint registration exception.
- public RegisterOptions WithStrongReference()
- {
- var currentFactory = _registeredTypes.GetCurrentFactory(_registration);
-
- if (currentFactory == null)
- throw new DependencyContainerRegistrationException(_registration.Type, "strong reference");
-
- return _registeredTypes.AddUpdateRegistration(_registration, currentFactory.StrongReferenceVariant);
- }
- }
-
+ /// The registered types.
+ /// The registration.
+ public RegisterOptions(TypesConcurrentDictionary registeredTypes, DependencyContainer.TypeRegistration registration) {
+ this._registeredTypes = registeredTypes;
+ this._registration = registration;
+ }
+
///
- /// Registration options for "fluent" API when registering multiple implementations.
+ /// Make registration a singleton (single instance) if possible.
///
- public sealed class MultiRegisterOptions
- {
- private IEnumerable _registerOptions;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The register options.
- public MultiRegisterOptions(IEnumerable registerOptions)
- {
- _registerOptions = registerOptions;
- }
-
- ///
- /// Make registration a singleton (single instance) if possible.
- ///
- /// A registration multi-instance for fluent API.
- /// Generic Constraint Registration Exception.
- public MultiRegisterOptions AsSingleton()
- {
- _registerOptions = ExecuteOnAllRegisterOptions(ro => ro.AsSingleton());
- return this;
- }
-
- ///
- /// Make registration multi-instance if possible.
- ///
- /// A registration multi-instance for fluent API.
- /// Generic Constraint Registration Exception.
- public MultiRegisterOptions AsMultiInstance()
- {
- _registerOptions = ExecuteOnAllRegisterOptions(ro => ro.AsMultiInstance());
- return this;
- }
-
- private IEnumerable ExecuteOnAllRegisterOptions(
- Func action)
- {
- return _registerOptions.Select(action).ToList();
- }
- }
+ /// A registration options for fluent API.
+ /// Generic constraint registration exception.
+ public RegisterOptions AsSingleton() {
+ ObjectFactoryBase currentFactory = this._registeredTypes.GetCurrentFactory(this._registration);
+
+ if(currentFactory == null) {
+ throw new DependencyContainerRegistrationException(this._registration.Type, "singleton");
+ }
+
+ return this._registeredTypes.AddUpdateRegistration(this._registration, currentFactory.SingletonVariant);
+ }
+
+ ///
+ /// Make registration multi-instance if possible.
+ ///
+ /// A registration options for fluent API.
+ /// Generic constraint registration exception.
+ public RegisterOptions AsMultiInstance() {
+ ObjectFactoryBase currentFactory = this._registeredTypes.GetCurrentFactory(this._registration);
+
+ if(currentFactory == null) {
+ throw new DependencyContainerRegistrationException(this._registration.Type, "multi-instance");
+ }
+
+ return this._registeredTypes.AddUpdateRegistration(this._registration, currentFactory.MultiInstanceVariant);
+ }
+
+ ///
+ /// Make registration hold a weak reference if possible.
+ ///
+ /// A registration options for fluent API.
+ /// Generic constraint registration exception.
+ public RegisterOptions WithWeakReference() {
+ ObjectFactoryBase currentFactory = this._registeredTypes.GetCurrentFactory(this._registration);
+
+ if(currentFactory == null) {
+ throw new DependencyContainerRegistrationException(this._registration.Type, "weak reference");
+ }
+
+ return this._registeredTypes.AddUpdateRegistration(this._registration, currentFactory.WeakReferenceVariant);
+ }
+
+ ///
+ /// Make registration hold a strong reference if possible.
+ ///
+ /// A registration options for fluent API.
+ /// Generic constraint registration exception.
+ public RegisterOptions WithStrongReference() {
+ ObjectFactoryBase currentFactory = this._registeredTypes.GetCurrentFactory(this._registration);
+
+ if(currentFactory == null) {
+ throw new DependencyContainerRegistrationException(this._registration.Type, "strong reference");
+ }
+
+ return this._registeredTypes.AddUpdateRegistration(this._registration, currentFactory.StrongReferenceVariant);
+ }
+ }
+
+ ///
+ /// Registration options for "fluent" API when registering multiple implementations.
+ ///
+ public sealed class MultiRegisterOptions {
+ private IEnumerable _registerOptions;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The register options.
+ public MultiRegisterOptions(IEnumerable registerOptions) => this._registerOptions = registerOptions;
+
+ ///
+ /// Make registration a singleton (single instance) if possible.
+ ///
+ /// A registration multi-instance for fluent API.
+ /// Generic Constraint Registration Exception.
+ public MultiRegisterOptions AsSingleton() {
+ this._registerOptions = this.ExecuteOnAllRegisterOptions(ro => ro.AsSingleton());
+ return this;
+ }
+
+ ///
+ /// Make registration multi-instance if possible.
+ ///
+ /// A registration multi-instance for fluent API.
+ /// Generic Constraint Registration Exception.
+ public MultiRegisterOptions AsMultiInstance() {
+ this._registerOptions = this.ExecuteOnAllRegisterOptions(ro => ro.AsMultiInstance());
+ return this;
+ }
+
+ private IEnumerable ExecuteOnAllRegisterOptions(
+ Func action) => this._registerOptions.Select(action).ToList();
+ }
}
\ No newline at end of file
diff --git a/Swan/DependencyInjection/TypeRegistration.cs b/Swan/DependencyInjection/TypeRegistration.cs
index 0b196f8..2af00c0 100644
--- a/Swan/DependencyInjection/TypeRegistration.cs
+++ b/Swan/DependencyInjection/TypeRegistration.cs
@@ -1,67 +1,61 @@
-namespace Swan.DependencyInjection
-{
- using System;
-
- public partial class DependencyContainer
- {
- ///
- /// Represents a Type Registration within the IoC Container.
- ///
- public sealed class TypeRegistration
- {
- private readonly int _hashCode;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The type.
- /// The name.
- public TypeRegistration(Type type, string name = null)
- {
- Type = type;
- Name = name ?? string.Empty;
-
- _hashCode = string.Concat(Type.FullName, "|", Name).GetHashCode();
- }
-
- ///
- /// Gets the type.
- ///
- ///
- /// The type.
- ///
- public Type Type { get; }
-
- ///
- /// Gets the name.
- ///
- ///
- /// The name.
- ///
- public string Name { get; }
-
- ///
- /// Determines whether the specified , is equal to this instance.
- ///
- /// The to compare with this instance.
- ///
- /// true if the specified is equal to this instance; otherwise, false .
- ///
- public override bool Equals(object obj)
- {
- if (!(obj is TypeRegistration typeRegistration) || typeRegistration.Type != Type)
- return false;
-
- return string.Compare(Name, typeRegistration.Name, StringComparison.Ordinal) == 0;
- }
-
- ///
- /// Returns a hash code for this instance.
- ///
- ///
- /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
- ///
- public override int GetHashCode() => _hashCode;
- }
- }
+using System;
+
+namespace Swan.DependencyInjection {
+ public partial class DependencyContainer {
+ ///
+ /// Represents a Type Registration within the IoC Container.
+ ///
+ public sealed class TypeRegistration {
+ private readonly Int32 _hashCode;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The type.
+ /// The name.
+ public TypeRegistration(Type type, String name = null) {
+ this.Type = type;
+ this.Name = name ?? String.Empty;
+
+ this._hashCode = String.Concat(this.Type.FullName, "|", this.Name).GetHashCode();
+ }
+
+ ///
+ /// Gets the type.
+ ///
+ ///
+ /// The type.
+ ///
+ public Type Type {
+ get;
+ }
+
+ ///
+ /// Gets the name.
+ ///
+ ///
+ /// The name.
+ ///
+ public String Name {
+ get;
+ }
+
+ ///
+ /// Determines whether the specified , is equal to this instance.
+ ///
+ /// The to compare with this instance.
+ ///
+ /// true if the specified is equal to this instance; otherwise, false .
+ ///
+ public override Boolean Equals(Object obj) => !(obj is TypeRegistration typeRegistration) || typeRegistration.Type != this.Type ? false : String.Compare(this.Name, typeRegistration.Name, StringComparison.Ordinal) == 0;
+
+ ///
+ /// Returns a hash code for this instance.
+ ///
+ ///
+ /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
+ ///
+ public override Int32 GetHashCode() => this._hashCode;
+ }
+ }
}
\ No newline at end of file
diff --git a/Swan/DependencyInjection/TypesConcurrentDictionary.cs b/Swan/DependencyInjection/TypesConcurrentDictionary.cs
index dd9a590..143ffb1 100644
--- a/Swan/DependencyInjection/TypesConcurrentDictionary.cs
+++ b/Swan/DependencyInjection/TypesConcurrentDictionary.cs
@@ -1,351 +1,265 @@
-namespace Swan.DependencyInjection
-{
- using System;
- using System.Linq.Expressions;
- using System.Reflection;
- using System.Collections.Generic;
- using System.Linq;
- using System.Collections.Concurrent;
-
+#nullable enable
+using System;
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Collections.Generic;
+using System.Linq;
+using System.Collections.Concurrent;
+
+namespace Swan.DependencyInjection {
+ ///
+ /// Represents a Concurrent Dictionary for TypeRegistration.
+ ///
+ public class TypesConcurrentDictionary : ConcurrentDictionary {
+ private static readonly ConcurrentDictionary ObjectConstructorCache = new ConcurrentDictionary();
+
+ private readonly DependencyContainer _dependencyContainer;
+
+ internal TypesConcurrentDictionary(DependencyContainer dependencyContainer) => this._dependencyContainer = dependencyContainer;
+
///
- /// Represents a Concurrent Dictionary for TypeRegistration.
+ /// Represents a delegate to build an object with the parameters.
///
- public class TypesConcurrentDictionary : ConcurrentDictionary
- {
- private static readonly ConcurrentDictionary ObjectConstructorCache =
- new ConcurrentDictionary();
-
- private readonly DependencyContainer _dependencyContainer;
-
- internal TypesConcurrentDictionary(DependencyContainer dependencyContainer)
- {
- _dependencyContainer = dependencyContainer;
- }
-
- ///
- /// Represents a delegate to build an object with the parameters.
- ///
- /// The parameters.
- /// The built object.
- public delegate object ObjectConstructor(params object[] parameters);
-
- internal IEnumerable Resolve(Type resolveType, bool includeUnnamed)
- {
- var registrations = Keys.Where(tr => tr.Type == resolveType)
- .Concat(GetParentRegistrationsForType(resolveType)).Distinct();
-
- if (!includeUnnamed)
- registrations = registrations.Where(tr => !string.IsNullOrEmpty(tr.Name));
-
- return registrations.Select(registration =>
- ResolveInternal(registration, DependencyContainerResolveOptions.Default));
- }
-
- internal ObjectFactoryBase GetCurrentFactory(DependencyContainer.TypeRegistration registration)
- {
- TryGetValue(registration, out var current);
-
- return current;
- }
-
- internal RegisterOptions Register(Type registerType, string name, ObjectFactoryBase factory)
- => AddUpdateRegistration(new DependencyContainer.TypeRegistration(registerType, name), factory);
-
- internal RegisterOptions AddUpdateRegistration(DependencyContainer.TypeRegistration typeRegistration, ObjectFactoryBase factory)
- {
- this[typeRegistration] = factory;
-
- return new RegisterOptions(this, typeRegistration);
- }
-
- internal bool RemoveRegistration(DependencyContainer.TypeRegistration typeRegistration)
- => TryRemove(typeRegistration, out _);
-
- internal object ResolveInternal(
- DependencyContainer.TypeRegistration registration,
- DependencyContainerResolveOptions? options = null)
- {
- if (options == null)
- options = DependencyContainerResolveOptions.Default;
-
- // Attempt container resolution
- if (TryGetValue(registration, out var factory))
- {
- try
- {
- return factory.GetObject(registration.Type, _dependencyContainer, options);
- }
- catch (DependencyContainerResolutionException)
- {
- throw;
- }
- catch (Exception ex)
- {
- throw new DependencyContainerResolutionException(registration.Type, ex);
- }
- }
-
- // Attempt to get a factory from parent if we can
- var bubbledObjectFactory = GetParentObjectFactory(registration);
- if (bubbledObjectFactory != null)
- {
- try
- {
- return bubbledObjectFactory.GetObject(registration.Type, _dependencyContainer, options);
- }
- catch (DependencyContainerResolutionException)
- {
- throw;
- }
- catch (Exception ex)
- {
- throw new DependencyContainerResolutionException(registration.Type, ex);
- }
- }
-
- // Fail if requesting named resolution and settings set to fail if unresolved
- if (!string.IsNullOrEmpty(registration.Name) && options.NamedResolutionFailureAction ==
- DependencyContainerNamedResolutionFailureAction.Fail)
- throw new DependencyContainerResolutionException(registration.Type);
-
- // Attempted unnamed fallback container resolution if relevant and requested
- if (!string.IsNullOrEmpty(registration.Name) && options.NamedResolutionFailureAction ==
- DependencyContainerNamedResolutionFailureAction.AttemptUnnamedResolution)
- {
- if (TryGetValue(new DependencyContainer.TypeRegistration(registration.Type, string.Empty), out factory))
- {
- try
- {
- return factory.GetObject(registration.Type, _dependencyContainer, options);
- }
- catch (DependencyContainerResolutionException)
- {
- throw;
- }
- catch (Exception ex)
- {
- throw new DependencyContainerResolutionException(registration.Type, ex);
- }
- }
- }
-
- // Attempt unregistered construction if possible and requested
- var isValid = (options.UnregisteredResolutionAction ==
- DependencyContainerUnregisteredResolutionAction.AttemptResolve) ||
- (registration.Type.IsGenericType && options.UnregisteredResolutionAction ==
- DependencyContainerUnregisteredResolutionAction.GenericsOnly);
-
- return isValid && !registration.Type.IsAbstract && !registration.Type.IsInterface
- ? ConstructType(registration.Type, null, options)
- : throw new DependencyContainerResolutionException(registration.Type);
- }
-
- internal bool CanResolve(
- DependencyContainer.TypeRegistration registration,
- DependencyContainerResolveOptions? options = null)
- {
- if (options == null)
- options = DependencyContainerResolveOptions.Default;
-
- var checkType = registration.Type;
- var name = registration.Name;
-
- if (TryGetValue(new DependencyContainer.TypeRegistration(checkType, name), out var factory))
- {
- if (factory.AssumeConstruction)
- return true;
-
- if (factory.Constructor == null)
- return GetBestConstructor(factory.CreatesType, options) != null;
-
- return CanConstruct(factory.Constructor, options);
- }
-
- // Fail if requesting named resolution and settings set to fail if unresolved
- // Or bubble up if we have a parent
- if (!string.IsNullOrEmpty(name) && options.NamedResolutionFailureAction ==
- DependencyContainerNamedResolutionFailureAction.Fail)
- return _dependencyContainer.Parent?.RegisteredTypes.CanResolve(registration, options.Clone()) ?? false;
-
- // Attempted unnamed fallback container resolution if relevant and requested
- if (!string.IsNullOrEmpty(name) && options.NamedResolutionFailureAction ==
- DependencyContainerNamedResolutionFailureAction.AttemptUnnamedResolution)
- {
- if (TryGetValue(new DependencyContainer.TypeRegistration(checkType), out factory))
- {
- if (factory.AssumeConstruction)
- return true;
-
- return GetBestConstructor(factory.CreatesType, options) != null;
- }
- }
-
- // Check if type is an automatic lazy factory request or an IEnumerable
- if (IsAutomaticLazyFactoryRequest(checkType) || registration.Type.IsIEnumerable())
- return true;
-
- // Attempt unregistered construction if possible and requested
- // If we cant', bubble if we have a parent
- if ((options.UnregisteredResolutionAction ==
- DependencyContainerUnregisteredResolutionAction.AttemptResolve) ||
- (checkType.IsGenericType && options.UnregisteredResolutionAction ==
- DependencyContainerUnregisteredResolutionAction.GenericsOnly))
- {
- return (GetBestConstructor(checkType, options) != null) ||
- (_dependencyContainer.Parent?.RegisteredTypes.CanResolve(registration, options.Clone()) ?? false);
- }
-
- // Bubble resolution up the container tree if we have a parent
- return _dependencyContainer.Parent != null && _dependencyContainer.Parent.RegisteredTypes.CanResolve(registration, options.Clone());
- }
-
- internal object ConstructType(
- Type implementationType,
- ConstructorInfo constructor,
- DependencyContainerResolveOptions? options = null)
- {
- var typeToConstruct = implementationType;
-
- if (constructor == null)
- {
- // Try and get the best constructor that we can construct
- // if we can't construct any then get the constructor
- // with the least number of parameters so we can throw a meaningful
- // resolve exception
- constructor = GetBestConstructor(typeToConstruct, options) ??
- GetTypeConstructors(typeToConstruct).LastOrDefault();
- }
-
- if (constructor == null)
- throw new DependencyContainerResolutionException(typeToConstruct);
-
- var ctorParams = constructor.GetParameters();
- var args = new object?[ctorParams.Length];
-
- for (var parameterIndex = 0; parameterIndex < ctorParams.Length; parameterIndex++)
- {
- var currentParam = ctorParams[parameterIndex];
-
- try
- {
- args[parameterIndex] = options?.ConstructorParameters.GetValueOrDefault(currentParam.Name, ResolveInternal(new DependencyContainer.TypeRegistration(currentParam.ParameterType), options.Clone()));
- }
- catch (DependencyContainerResolutionException ex)
- {
- // If a constructor parameter can't be resolved
- // it will throw, so wrap it and throw that this can't
- // be resolved.
- throw new DependencyContainerResolutionException(typeToConstruct, ex);
- }
- catch (Exception ex)
- {
- throw new DependencyContainerResolutionException(typeToConstruct, ex);
- }
- }
-
- try
- {
- return CreateObjectConstructionDelegateWithCache(constructor).Invoke(args);
- }
- catch (Exception ex)
- {
- throw new DependencyContainerResolutionException(typeToConstruct, ex);
- }
- }
-
- private static ObjectConstructor CreateObjectConstructionDelegateWithCache(ConstructorInfo constructor)
- {
- if (ObjectConstructorCache.TryGetValue(constructor, out var objectConstructor))
- return objectConstructor;
-
- // We could lock the cache here, but there's no real side
- // effect to two threads creating the same ObjectConstructor
- // at the same time, compared to the cost of a lock for
- // every creation.
- var constructorParams = constructor.GetParameters();
- var lambdaParams = Expression.Parameter(typeof(object[]), "parameters");
- var newParams = new Expression[constructorParams.Length];
-
- for (var i = 0; i < constructorParams.Length; i++)
- {
- var paramsParameter = Expression.ArrayIndex(lambdaParams, Expression.Constant(i));
-
- newParams[i] = Expression.Convert(paramsParameter, constructorParams[i].ParameterType);
- }
-
- var newExpression = Expression.New(constructor, newParams);
-
- var constructionLambda = Expression.Lambda(typeof(ObjectConstructor), newExpression, lambdaParams);
-
- objectConstructor = (ObjectConstructor)constructionLambda.Compile();
-
- ObjectConstructorCache[constructor] = objectConstructor;
- return objectConstructor;
- }
-
- private static IEnumerable GetTypeConstructors(Type type)
- => type.GetConstructors().OrderByDescending(ctor => ctor.GetParameters().Length);
-
- private static bool IsAutomaticLazyFactoryRequest(Type type)
- {
- if (!type.IsGenericType)
- return false;
-
- var genericType = type.GetGenericTypeDefinition();
-
- // Just a func
- if (genericType == typeof(Func<>))
- return true;
-
- // 2 parameter func with string as first parameter (name)
- if (genericType == typeof(Func<,>) && type.GetGenericArguments()[0] == typeof(string))
- return true;
-
- // 3 parameter func with string as first parameter (name) and IDictionary as second (parameters)
- return genericType == typeof(Func<,,>) && type.GetGenericArguments()[0] == typeof(string) &&
- type.GetGenericArguments()[1] == typeof(IDictionary);
- }
-
- private ObjectFactoryBase? GetParentObjectFactory(DependencyContainer.TypeRegistration registration)
- {
- if (_dependencyContainer.Parent == null)
- return null;
-
- return _dependencyContainer.Parent.RegisteredTypes.TryGetValue(registration, out var factory)
- ? factory.GetFactoryForChildContainer(registration.Type, _dependencyContainer.Parent, _dependencyContainer)
- : _dependencyContainer.Parent.RegisteredTypes.GetParentObjectFactory(registration);
- }
-
- private ConstructorInfo? GetBestConstructor(
- Type type,
- DependencyContainerResolveOptions options)
- => type.IsValueType ? null : GetTypeConstructors(type).FirstOrDefault(ctor => CanConstruct(ctor, options));
-
- private bool CanConstruct(
- MethodBase ctor,
- DependencyContainerResolveOptions? options)
- {
- foreach (var parameter in ctor.GetParameters())
- {
- if (string.IsNullOrEmpty(parameter.Name))
- return false;
-
- var isParameterOverload = options.ConstructorParameters.ContainsKey(parameter.Name);
-
- if (parameter.ParameterType.IsPrimitive && !isParameterOverload)
- return false;
-
- if (!isParameterOverload &&
- !CanResolve(new DependencyContainer.TypeRegistration(parameter.ParameterType), options.Clone()))
- return false;
- }
-
- return true;
- }
-
- private IEnumerable GetParentRegistrationsForType(Type resolveType)
- => _dependencyContainer.Parent == null
- ? Array.Empty()
- : _dependencyContainer.Parent.RegisteredTypes.Keys.Where(tr => tr.Type == resolveType).Concat(_dependencyContainer.Parent.RegisteredTypes.GetParentRegistrationsForType(resolveType));
- }
+ /// The parameters.
+ /// The built object.
+ public delegate Object ObjectConstructor(params Object?[] parameters);
+
+ internal IEnumerable Resolve(Type resolveType, Boolean includeUnnamed) {
+ IEnumerable registrations = this.Keys.Where(tr => tr.Type == resolveType).Concat(this.GetParentRegistrationsForType(resolveType)).Distinct();
+
+ if(!includeUnnamed) {
+ registrations = registrations.Where(tr => !String.IsNullOrEmpty(tr.Name));
+ }
+
+ return registrations.Select(registration => this.ResolveInternal(registration, DependencyContainerResolveOptions.Default));
+ }
+
+ internal ObjectFactoryBase GetCurrentFactory(DependencyContainer.TypeRegistration registration) {
+ _ = this.TryGetValue(registration, out ObjectFactoryBase? current);
+
+ return current!;
+ }
+
+ internal RegisterOptions Register(Type registerType, String name, ObjectFactoryBase factory) => this.AddUpdateRegistration(new DependencyContainer.TypeRegistration(registerType, name), factory);
+
+ internal RegisterOptions AddUpdateRegistration(DependencyContainer.TypeRegistration typeRegistration, ObjectFactoryBase factory) {
+ this[typeRegistration] = factory;
+
+ return new RegisterOptions(this, typeRegistration);
+ }
+
+ internal Boolean RemoveRegistration(DependencyContainer.TypeRegistration typeRegistration) => this.TryRemove(typeRegistration, out _);
+
+ internal Object ResolveInternal(DependencyContainer.TypeRegistration registration, DependencyContainerResolveOptions? options = null) {
+ if(options == null) {
+ options = DependencyContainerResolveOptions.Default;
+ }
+
+ // Attempt container resolution
+ if(this.TryGetValue(registration, out ObjectFactoryBase? factory)) {
+ try {
+ return factory.GetObject(registration.Type, this._dependencyContainer, options);
+ } catch(DependencyContainerResolutionException) {
+ throw;
+ } catch(Exception ex) {
+ throw new DependencyContainerResolutionException(registration.Type, ex);
+ }
+ }
+
+ // Attempt to get a factory from parent if we can
+ ObjectFactoryBase? bubbledObjectFactory = this.GetParentObjectFactory(registration);
+ if(bubbledObjectFactory != null) {
+ try {
+ return bubbledObjectFactory.GetObject(registration.Type, this._dependencyContainer, options);
+ } catch(DependencyContainerResolutionException) {
+ throw;
+ } catch(Exception ex) {
+ throw new DependencyContainerResolutionException(registration.Type, ex);
+ }
+ }
+
+ // Fail if requesting named resolution and settings set to fail if unresolved
+ if(!String.IsNullOrEmpty(registration.Name) && options.NamedResolutionFailureAction == DependencyContainerNamedResolutionFailureAction.Fail) {
+ throw new DependencyContainerResolutionException(registration.Type);
+ }
+
+ // Attempted unnamed fallback container resolution if relevant and requested
+ if(!String.IsNullOrEmpty(registration.Name) && options.NamedResolutionFailureAction == DependencyContainerNamedResolutionFailureAction.AttemptUnnamedResolution) {
+ if(this.TryGetValue(new DependencyContainer.TypeRegistration(registration.Type, String.Empty), out factory)) {
+ try {
+ return factory.GetObject(registration.Type, this._dependencyContainer, options);
+ } catch(DependencyContainerResolutionException) {
+ throw;
+ } catch(Exception ex) {
+ throw new DependencyContainerResolutionException(registration.Type, ex);
+ }
+ }
+ }
+
+ // Attempt unregistered construction if possible and requested
+ Boolean isValid = options.UnregisteredResolutionAction == DependencyContainerUnregisteredResolutionAction.AttemptResolve || registration.Type.IsGenericType && options.UnregisteredResolutionAction == DependencyContainerUnregisteredResolutionAction.GenericsOnly;
+
+ return isValid && !registration.Type.IsAbstract && !registration.Type.IsInterface ? this.ConstructType(registration.Type, null, options) : throw new DependencyContainerResolutionException(registration.Type);
+ }
+
+ internal Boolean CanResolve(DependencyContainer.TypeRegistration registration, DependencyContainerResolveOptions? options = null) {
+ if(options == null) {
+ options = DependencyContainerResolveOptions.Default;
+ }
+
+ Type checkType = registration.Type;
+ String name = registration.Name;
+
+ if(this.TryGetValue(new DependencyContainer.TypeRegistration(checkType, name), out ObjectFactoryBase? factory)) {
+ return factory.AssumeConstruction ? true : factory.Constructor == null ? this.GetBestConstructor(factory.CreatesType, options) != null : this.CanConstruct(factory.Constructor, options);
+ }
+
+ // Fail if requesting named resolution and settings set to fail if unresolved
+ // Or bubble up if we have a parent
+ if(!String.IsNullOrEmpty(name) && options.NamedResolutionFailureAction == DependencyContainerNamedResolutionFailureAction.Fail) {
+ return this._dependencyContainer.Parent?.RegisteredTypes.CanResolve(registration, options.Clone()) ?? false;
+ }
+
+ // Attempted unnamed fallback container resolution if relevant and requested
+ if(!String.IsNullOrEmpty(name) && options.NamedResolutionFailureAction == DependencyContainerNamedResolutionFailureAction.AttemptUnnamedResolution) {
+ if(this.TryGetValue(new DependencyContainer.TypeRegistration(checkType), out factory)) {
+ return factory.AssumeConstruction ? true : this.GetBestConstructor(factory.CreatesType, options) != null;
+ }
+ }
+
+ // Check if type is an automatic lazy factory request or an IEnumerable
+ if(IsAutomaticLazyFactoryRequest(checkType) || registration.Type.IsIEnumerable()) {
+ return true;
+ }
+
+ // Attempt unregistered construction if possible and requested
+ // If we cant', bubble if we have a parent
+ if(options.UnregisteredResolutionAction == DependencyContainerUnregisteredResolutionAction.AttemptResolve || checkType.IsGenericType && options.UnregisteredResolutionAction == DependencyContainerUnregisteredResolutionAction.GenericsOnly) {
+ return this.GetBestConstructor(checkType, options) != null || (this._dependencyContainer.Parent?.RegisteredTypes.CanResolve(registration, options.Clone()) ?? false);
+ }
+
+ // Bubble resolution up the container tree if we have a parent
+ return this._dependencyContainer.Parent != null && this._dependencyContainer.Parent.RegisteredTypes.CanResolve(registration, options.Clone());
+ }
+
+ internal Object ConstructType(Type implementationType, ConstructorInfo? constructor, DependencyContainerResolveOptions? options = null) {
+ Type typeToConstruct = implementationType;
+
+ if(constructor == null) {
+ // Try and get the best constructor that we can construct
+ // if we can't construct any then get the constructor
+ // with the least number of parameters so we can throw a meaningful
+ // resolve exception
+ constructor = this.GetBestConstructor(typeToConstruct, options) ?? GetTypeConstructors(typeToConstruct).LastOrDefault();
+ }
+
+ if(constructor == null) {
+ throw new DependencyContainerResolutionException(typeToConstruct);
+ }
+
+ ParameterInfo[] ctorParams = constructor.GetParameters();
+ Object?[] args = new Object?[ctorParams.Length];
+
+ for(Int32 parameterIndex = 0; parameterIndex < ctorParams.Length; parameterIndex++) {
+ ParameterInfo currentParam = ctorParams[parameterIndex];
+
+ try {
+ args[parameterIndex] = options?.ConstructorParameters.GetValueOrDefault(currentParam.Name, this.ResolveInternal(new DependencyContainer.TypeRegistration(currentParam.ParameterType), options.Clone()));
+ } catch(DependencyContainerResolutionException ex) {
+ // If a constructor parameter can't be resolved
+ // it will throw, so wrap it and throw that this can't
+ // be resolved.
+ throw new DependencyContainerResolutionException(typeToConstruct, ex);
+ } catch(Exception ex) {
+ throw new DependencyContainerResolutionException(typeToConstruct, ex);
+ }
+ }
+
+ try {
+ return CreateObjectConstructionDelegateWithCache(constructor).Invoke(args);
+ } catch(Exception ex) {
+ throw new DependencyContainerResolutionException(typeToConstruct, ex);
+ }
+ }
+
+ private static ObjectConstructor CreateObjectConstructionDelegateWithCache(ConstructorInfo constructor) {
+ if(ObjectConstructorCache.TryGetValue(constructor, out ObjectConstructor? objectConstructor)) {
+ return objectConstructor;
+ }
+
+ // We could lock the cache here, but there's no real side
+ // effect to two threads creating the same ObjectConstructor
+ // at the same time, compared to the cost of a lock for
+ // every creation.
+ ParameterInfo[] constructorParams = constructor.GetParameters();
+ ParameterExpression lambdaParams = Expression.Parameter(typeof(Object[]), "parameters");
+ Expression[] newParams = new Expression[constructorParams.Length];
+
+ for(Int32 i = 0; i < constructorParams.Length; i++) {
+ BinaryExpression paramsParameter = Expression.ArrayIndex(lambdaParams, Expression.Constant(i));
+
+ newParams[i] = Expression.Convert(paramsParameter, constructorParams[i].ParameterType);
+ }
+
+ NewExpression newExpression = Expression.New(constructor, newParams);
+
+ LambdaExpression constructionLambda = Expression.Lambda(typeof(ObjectConstructor), newExpression, lambdaParams);
+
+ objectConstructor = (ObjectConstructor)constructionLambda.Compile();
+
+ ObjectConstructorCache[constructor] = objectConstructor;
+ return objectConstructor;
+ }
+
+ private static IEnumerable GetTypeConstructors(Type type) => type.GetConstructors().OrderByDescending(ctor => ctor.GetParameters().Length);
+
+ private static Boolean IsAutomaticLazyFactoryRequest(Type type) {
+ if(!type.IsGenericType) {
+ return false;
+ }
+
+ Type genericType = type.GetGenericTypeDefinition();
+
+ // Just a func
+ if(genericType == typeof(Func<>)) {
+ return true;
+ }
+
+ // 2 parameter func with string as first parameter (name)
+ if(genericType == typeof(Func<,>) && type.GetGenericArguments()[0] == typeof(String)) {
+ return true;
+ }
+
+ // 3 parameter func with string as first parameter (name) and IDictionary as second (parameters)
+ return genericType == typeof(Func<,,>) && type.GetGenericArguments()[0] == typeof(String) && type.GetGenericArguments()[1] == typeof(IDictionary);
+ }
+
+ private ObjectFactoryBase? GetParentObjectFactory(DependencyContainer.TypeRegistration registration) => this._dependencyContainer.Parent == null
+ ? null
+ : this._dependencyContainer.Parent.RegisteredTypes.TryGetValue(registration, out ObjectFactoryBase? factory) ? factory.GetFactoryForChildContainer(registration.Type, this._dependencyContainer.Parent, this._dependencyContainer) : this._dependencyContainer.Parent.RegisteredTypes.GetParentObjectFactory(registration);
+
+ private ConstructorInfo? GetBestConstructor(Type type, DependencyContainerResolveOptions? options) => type.IsValueType ? null : GetTypeConstructors(type).FirstOrDefault(ctor => this.CanConstruct(ctor, options));
+
+ private Boolean CanConstruct(MethodBase ctor, DependencyContainerResolveOptions? options) {
+ foreach(ParameterInfo parameter in ctor.GetParameters()) {
+ if(String.IsNullOrEmpty(parameter.Name)) {
+ return false;
+ }
+
+ Boolean isParameterOverload = options!.ConstructorParameters.ContainsKey(parameter.Name);
+
+ if(parameter.ParameterType.IsPrimitive && !isParameterOverload) {
+ return false;
+ }
+
+ if(!isParameterOverload && !this.CanResolve(new DependencyContainer.TypeRegistration(parameter.ParameterType), options.Clone())) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private IEnumerable GetParentRegistrationsForType(Type resolveType) => this._dependencyContainer.Parent == null ? Array.Empty() : this._dependencyContainer.Parent.RegisteredTypes.Keys.Where(tr => tr.Type == resolveType).Concat(this._dependencyContainer.Parent.RegisteredTypes.GetParentRegistrationsForType(resolveType));
+ }
}
diff --git a/Swan/Diagnostics/RealtimeClock.cs b/Swan/Diagnostics/RealtimeClock.cs
index d4ebb08..5d2648f 100644
--- a/Swan/Diagnostics/RealtimeClock.cs
+++ b/Swan/Diagnostics/RealtimeClock.cs
@@ -1,143 +1,128 @@
-namespace Swan.Diagnostics
-{
- using System;
- using System.Diagnostics;
- using Threading;
-
+#nullable enable
+using System;
+using System.Diagnostics;
+using Swan.Threading;
+
+namespace Swan.Diagnostics {
+ ///
+ /// A time measurement artifact.
+ ///
+ internal sealed class RealTimeClock : IDisposable {
+ private readonly Stopwatch _chrono = new Stopwatch();
+ private ISyncLocker? _locker = SyncLockerFactory.Create(useSlim: true);
+ private Int64 _offsetTicks;
+ private Double _speedRatio = 1.0d;
+ private Boolean _isDisposed;
+
///
- /// A time measurement artifact.
+ /// Initializes a new instance of the class.
+ /// The clock starts paused and at the 0 position.
///
- internal sealed class RealTimeClock : IDisposable
- {
- private readonly Stopwatch _chrono = new Stopwatch();
- private ISyncLocker? _locker = SyncLockerFactory.Create(useSlim: true);
- private long _offsetTicks;
- private double _speedRatio = 1.0d;
- private bool _isDisposed;
-
- ///
- /// Initializes a new instance of the class.
- /// The clock starts paused and at the 0 position.
- ///
- public RealTimeClock()
- {
- Reset();
- }
-
- ///
- /// Gets or sets the clock position.
- ///
- public TimeSpan Position
- {
- get
- {
- using (_locker?.AcquireReaderLock())
- {
- return TimeSpan.FromTicks(
- _offsetTicks + Convert.ToInt64(_chrono.Elapsed.Ticks * SpeedRatio));
- }
- }
- }
-
- ///
- /// Gets a value indicating whether the clock is running.
- ///
- public bool IsRunning
- {
- get
- {
- using (_locker?.AcquireReaderLock())
- {
- return _chrono.IsRunning;
- }
- }
- }
-
- ///
- /// Gets or sets the speed ratio at which the clock runs.
- ///
- public double SpeedRatio
- {
- get
- {
- using (_locker?.AcquireReaderLock())
- {
- return _speedRatio;
- }
- }
- set
- {
- using (_locker?.AcquireWriterLock())
- {
- if (value < 0d) value = 0d;
-
- // Capture the initial position se we set it even after the Speed Ratio has changed
- // this ensures a smooth position transition
- var initialPosition = Position;
- _speedRatio = value;
- Update(initialPosition);
- }
- }
- }
-
- ///
- /// Sets a new position value atomically.
- ///
- /// The new value that the position property will hold.
- public void Update(TimeSpan value)
- {
- using (_locker?.AcquireWriterLock())
- {
- var resume = _chrono.IsRunning;
- _chrono.Reset();
- _offsetTicks = value.Ticks;
- if (resume) _chrono.Start();
- }
- }
-
- ///
- /// Starts or resumes the clock.
- ///
- public void Play()
- {
- using (_locker?.AcquireWriterLock())
- {
- if (_chrono.IsRunning) return;
- _chrono.Start();
- }
- }
-
- ///
- /// Pauses the clock.
- ///
- public void Pause()
- {
- using (_locker?.AcquireWriterLock())
- {
- _chrono.Stop();
- }
- }
-
- ///
- /// Sets the clock position to 0 and stops it.
- /// The speed ratio is not modified.
- ///
- public void Reset()
- {
- using (_locker?.AcquireWriterLock())
- {
- _offsetTicks = 0;
- _chrono.Reset();
- }
- }
-
- ///
- public void Dispose()
- {
- if (_isDisposed) return;
- _isDisposed = true;
- _locker?.Dispose();
- _locker = null;
- }
- }
+ public RealTimeClock() => this.Reset();
+
+ ///
+ /// Gets or sets the clock position.
+ ///
+ public TimeSpan Position {
+ get {
+ using(this._locker?.AcquireReaderLock()) {
+ return TimeSpan.FromTicks(this._offsetTicks + Convert.ToInt64(this._chrono.Elapsed.Ticks * this.SpeedRatio));
+ }
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the clock is running.
+ ///
+ public Boolean IsRunning {
+ get {
+ using(this._locker?.AcquireReaderLock()) {
+ return this._chrono.IsRunning;
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the speed ratio at which the clock runs.
+ ///
+ public Double SpeedRatio {
+ get {
+ using(this._locker?.AcquireReaderLock()) {
+ return this._speedRatio;
+ }
+ }
+ set {
+ using(this._locker?.AcquireWriterLock()) {
+ if(value < 0d) {
+ value = 0d;
+ }
+
+ // Capture the initial position se we set it even after the Speed Ratio has changed
+ // this ensures a smooth position transition
+ TimeSpan initialPosition = this.Position;
+ this._speedRatio = value;
+ this.Update(initialPosition);
+ }
+ }
+ }
+
+ ///
+ /// Sets a new position value atomically.
+ ///
+ /// The new value that the position property will hold.
+ public void Update(TimeSpan value) {
+ using(this._locker?.AcquireWriterLock()) {
+ Boolean resume = this._chrono.IsRunning;
+ this._chrono.Reset();
+ this._offsetTicks = value.Ticks;
+ if(resume) {
+ this._chrono.Start();
+ }
+ }
+ }
+
+ ///
+ /// Starts or resumes the clock.
+ ///
+ public void Play() {
+ using(this._locker?.AcquireWriterLock()) {
+ if(this._chrono.IsRunning) {
+ return;
+ }
+
+ this._chrono.Start();
+ }
+ }
+
+ ///
+ /// Pauses the clock.
+ ///
+ public void Pause() {
+ using(this._locker?.AcquireWriterLock()) {
+ this._chrono.Stop();
+ }
+ }
+
+ ///
+ /// Sets the clock position to 0 and stops it.
+ /// The speed ratio is not modified.
+ ///
+ public void Reset() {
+ using(this._locker?.AcquireWriterLock()) {
+ this._offsetTicks = 0;
+ this._chrono.Reset();
+ }
+ }
+
+ ///
+ public void Dispose() {
+ if(this._isDisposed) {
+ return;
+ }
+
+ this._isDisposed = true;
+ this._locker?.Dispose();
+ this._locker = null;
+ }
+ }
}
diff --git a/Swan/Extensions.MimeMessage.cs b/Swan/Extensions.MimeMessage.cs
index 2f42f3d..a4a0ca9 100644
--- a/Swan/Extensions.MimeMessage.cs
+++ b/Swan/Extensions.MimeMessage.cs
@@ -1,56 +1,43 @@
-namespace Swan
-{
- using System;
- using System.IO;
- using System.Net.Mail;
- using System.Reflection;
-
+using System;
+using System.IO;
+using System.Net.Mail;
+using System.Reflection;
+
+namespace Swan {
+ ///
+ /// Extension methods.
+ ///
+ public static class SmtpExtensions {
+ private static readonly BindingFlags PrivateInstanceFlags = BindingFlags.Instance | BindingFlags.NonPublic;
+
///
- /// Extension methods.
+ /// The raw contents of this MailMessage as a MemoryStream.
///
- public static class SmtpExtensions
- {
- private static readonly BindingFlags PrivateInstanceFlags = BindingFlags.Instance | BindingFlags.NonPublic;
-
- ///
- /// The raw contents of this MailMessage as a MemoryStream.
- ///
- /// The caller.
- /// A MemoryStream with the raw contents of this MailMessage.
- public static MemoryStream ToMimeMessage(this MailMessage @this)
- {
- if (@this == null)
- throw new ArgumentNullException(nameof(@this));
-
- var result = new MemoryStream();
- var mailWriter = MimeMessageConstants.MailWriterConstructor.Invoke(new object[] { result });
- MimeMessageConstants.SendMethod.Invoke(
- @this,
- PrivateInstanceFlags,
- null,
- MimeMessageConstants.IsRunningInDotNetFourPointFive ? new[] { mailWriter, true, true } : new[] { mailWriter, true },
- null);
-
- result = new MemoryStream(result.ToArray());
- MimeMessageConstants.CloseMethod.Invoke(
- mailWriter,
- PrivateInstanceFlags,
- null,
- Array.Empty(),
- null);
- result.Position = 0;
- return result;
- }
-
- internal static class MimeMessageConstants
- {
+ /// The caller.
+ /// A MemoryStream with the raw contents of this MailMessage.
+ public static MemoryStream ToMimeMessage(this MailMessage @this) {
+ if(@this == null) {
+ throw new ArgumentNullException(nameof(@this));
+ }
+
+ MemoryStream result = new MemoryStream();
+ Object mailWriter = MimeMessageConstants.MailWriterConstructor.Invoke(new Object[] { result });
+ _ = MimeMessageConstants.SendMethod.Invoke(@this, PrivateInstanceFlags, null, MimeMessageConstants.IsRunningInDotNetFourPointFive ? new[] { mailWriter, true, true } : new[] { mailWriter, true }, null);
+
+ result = new MemoryStream(result.ToArray());
+ _ = MimeMessageConstants.CloseMethod.Invoke(mailWriter, PrivateInstanceFlags, null, Array.Empty(), null);
+ result.Position = 0;
+ return result;
+ }
+
+ internal static class MimeMessageConstants {
#pragma warning disable DE0005 // API is deprecated
- public static readonly Type MailWriter = typeof(SmtpClient).Assembly.GetType("System.Net.Mail.MailWriter");
+ public static readonly Type MailWriter = typeof(SmtpClient).Assembly.GetType("System.Net.Mail.MailWriter");
#pragma warning restore DE0005 // API is deprecated
- public static readonly ConstructorInfo MailWriterConstructor = MailWriter.GetConstructor(PrivateInstanceFlags, null, new[] { typeof(Stream) }, null);
- public static readonly MethodInfo CloseMethod = MailWriter.GetMethod("Close", PrivateInstanceFlags);
- public static readonly MethodInfo SendMethod = typeof(MailMessage).GetMethod("Send", PrivateInstanceFlags);
- public static readonly bool IsRunningInDotNetFourPointFive = SendMethod.GetParameters().Length == 3;
- }
- }
+ public static readonly ConstructorInfo MailWriterConstructor = MailWriter.GetConstructor(PrivateInstanceFlags, null, new[] { typeof(Stream) }, null);
+ public static readonly MethodInfo CloseMethod = MailWriter.GetMethod("Close", PrivateInstanceFlags);
+ public static readonly MethodInfo SendMethod = typeof(MailMessage).GetMethod("Send", PrivateInstanceFlags);
+ public static readonly Boolean IsRunningInDotNetFourPointFive = SendMethod.GetParameters().Length == 3;
+ }
+ }
}
\ No newline at end of file
diff --git a/Swan/Extensions.Network.cs b/Swan/Extensions.Network.cs
index 32ffef6..5c4955f 100644
--- a/Swan/Extensions.Network.cs
+++ b/Swan/Extensions.Network.cs
@@ -1,58 +1,58 @@
-namespace Swan
-{
- using System;
- using System.Linq;
- using System.Net;
- using System.Net.Sockets;
-
+using System;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+
+namespace Swan {
+ ///
+ /// Provides various extension methods for networking-related tasks.
+ ///
+ public static class NetworkExtensions {
///
- /// Provides various extension methods for networking-related tasks.
+ /// Determines whether the IP address is private.
///
- public static class NetworkExtensions
- {
- ///
- /// Determines whether the IP address is private.
- ///
- /// The IP address.
- ///
- /// True if the IP Address is private; otherwise, false.
- ///
- /// address.
- public static bool IsPrivateAddress(this IPAddress @this)
- {
- if (@this == null)
- throw new ArgumentNullException(nameof(@this));
-
- var octets = @this.ToString().Split(new[] { "." }, StringSplitOptions.RemoveEmptyEntries).Select(byte.Parse).ToArray();
- var is24Bit = octets[0] == 10;
- var is20Bit = octets[0] == 172 && (octets[1] >= 16 && octets[1] <= 31);
- var is16Bit = octets[0] == 192 && octets[1] == 168;
-
- return is24Bit || is20Bit || is16Bit;
- }
-
- ///
- /// Converts an IPv4 Address to its Unsigned, 32-bit integer representation.
- ///
- /// The address.
- ///
- /// A 32-bit unsigned integer converted from four bytes at a specified position in a byte array.
- ///
- /// address.
- /// InterNetwork - address.
- public static uint ToUInt32(this IPAddress @this)
- {
- if (@this == null)
- throw new ArgumentNullException(nameof(@this));
-
- if (@this.AddressFamily != AddressFamily.InterNetwork)
- throw new ArgumentException($"Address has to be of family '{nameof(AddressFamily.InterNetwork)}'", nameof(@this));
-
- var addressBytes = @this.GetAddressBytes();
- if (BitConverter.IsLittleEndian)
- Array.Reverse(addressBytes);
-
- return BitConverter.ToUInt32(addressBytes, 0);
- }
- }
+ /// The IP address.
+ ///
+ /// True if the IP Address is private; otherwise, false.
+ ///
+ /// address.
+ public static Boolean IsPrivateAddress(this IPAddress @this) {
+ if(@this == null) {
+ throw new ArgumentNullException(nameof(@this));
+ }
+
+ Byte[] octets = @this.ToString().Split(new[] { "." }, StringSplitOptions.RemoveEmptyEntries).Select(Byte.Parse).ToArray();
+ Boolean is24Bit = octets[0] == 10;
+ Boolean is20Bit = octets[0] == 172 && octets[1] >= 16 && octets[1] <= 31;
+ Boolean is16Bit = octets[0] == 192 && octets[1] == 168;
+
+ return is24Bit || is20Bit || is16Bit;
+ }
+
+ ///
+ /// Converts an IPv4 Address to its Unsigned, 32-bit integer representation.
+ ///
+ /// The address.
+ ///
+ /// A 32-bit unsigned integer converted from four bytes at a specified position in a byte array.
+ ///
+ /// address.
+ /// InterNetwork - address.
+ public static UInt32 ToUInt32(this IPAddress @this) {
+ if(@this == null) {
+ throw new ArgumentNullException(nameof(@this));
+ }
+
+ if(@this.AddressFamily != AddressFamily.InterNetwork) {
+ throw new ArgumentException($"Address has to be of family '{nameof(AddressFamily.InterNetwork)}'", nameof(@this));
+ }
+
+ Byte[] addressBytes = @this.GetAddressBytes();
+ if(BitConverter.IsLittleEndian) {
+ Array.Reverse(addressBytes);
+ }
+
+ return BitConverter.ToUInt32(addressBytes, 0);
+ }
+ }
}
diff --git a/Swan/Extensions.WindowsServices.cs b/Swan/Extensions.WindowsServices.cs
index 420f15d..b7e45a2 100644
--- a/Swan/Extensions.WindowsServices.cs
+++ b/Swan/Extensions.WindowsServices.cs
@@ -1,89 +1,81 @@
-namespace Swan
-{
- using Logging;
- using System;
- using System.Collections.Generic;
- using System.Reflection;
- using System.Threading;
-#if NET461
- using System.ServiceProcess;
-#else
- using Services;
-#endif
-
+using Swan.Logging;
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Threading;
+
+using Swan.Services;
+
+namespace Swan {
+ ///
+ /// Extension methods.
+ ///
+ public static class WindowsServicesExtensions {
///
- /// Extension methods.
+ /// Runs a service in console mode.
///
- public static class WindowsServicesExtensions
- {
- ///
- /// Runs a service in console mode.
- ///
- /// The service to run.
- /// The logger source.
- /// this.
- [Obsolete("This extension method will be removed in version 3.0")]
- public static void RunInConsoleMode(this ServiceBase @this, string loggerSource = null)
- {
- if (@this == null)
- throw new ArgumentNullException(nameof(@this));
-
- RunInConsoleMode(new[] { @this }, loggerSource);
- }
-
- ///
- /// Runs a set of services in console mode.
- ///
- /// The services to run.
- /// The logger source.
- /// this.
- /// The ServiceBase class isn't available.
- [Obsolete("This extension method will be removed in version 3.0")]
- public static void RunInConsoleMode(this ServiceBase[] @this, string loggerSource = null)
- {
- if (@this == null)
- throw new ArgumentNullException(nameof(@this));
-
- const string onStartMethodName = "OnStart";
- const string onStopMethodName = "OnStop";
-
- var onStartMethod = typeof(ServiceBase).GetMethod(onStartMethodName,
- BindingFlags.Instance | BindingFlags.NonPublic);
- var onStopMethod = typeof(ServiceBase).GetMethod(onStopMethodName,
- BindingFlags.Instance | BindingFlags.NonPublic);
-
- if (onStartMethod == null || onStopMethod == null)
- throw new InvalidOperationException("The ServiceBase class isn't available.");
-
- var serviceThreads = new List();
- "Starting services . . .".Info(loggerSource ?? SwanRuntime.EntryAssemblyName.Name);
-
- foreach (var service in @this)
- {
- var thread = new Thread(() =>
- {
- onStartMethod.Invoke(service, new object[] { Array.Empty() });
- $"Started service '{service.GetType().Name}'".Info(loggerSource ?? service.GetType().Name);
- });
-
- serviceThreads.Add(thread);
- thread.Start();
- }
-
- "Press any key to stop all services.".Info(loggerSource ?? SwanRuntime.EntryAssemblyName.Name);
- Terminal.ReadKey(true, true);
- "Stopping services . . .".Info(SwanRuntime.EntryAssemblyName.Name);
-
- foreach (var service in @this)
- {
- onStopMethod.Invoke(service, null);
- $"Stopped service '{service.GetType().Name}'".Info(loggerSource ?? service.GetType().Name);
- }
-
- foreach (var thread in serviceThreads)
- thread.Join();
-
- "Stopped all services.".Info(loggerSource ?? SwanRuntime.EntryAssemblyName.Name);
- }
- }
+ /// The service to run.
+ /// The logger source.
+ /// this.
+ [Obsolete("This extension method will be removed in version 3.0")]
+ public static void RunInConsoleMode(this ServiceBase @this, String loggerSource = null) {
+ if(@this == null) {
+ throw new ArgumentNullException(nameof(@this));
+ }
+
+ RunInConsoleMode(new[] { @this }, loggerSource);
+ }
+
+ ///
+ /// Runs a set of services in console mode.
+ ///
+ /// The services to run.
+ /// The logger source.
+ /// this.
+ /// The ServiceBase class isn't available.
+ [Obsolete("This extension method will be removed in version 3.0")]
+ public static void RunInConsoleMode(this ServiceBase[] @this, String loggerSource = null) {
+ if(@this == null) {
+ throw new ArgumentNullException(nameof(@this));
+ }
+
+ const String onStartMethodName = "OnStart";
+ const String onStopMethodName = "OnStop";
+
+ MethodInfo onStartMethod = typeof(ServiceBase).GetMethod(onStartMethodName, BindingFlags.Instance | BindingFlags.NonPublic);
+ MethodInfo onStopMethod = typeof(ServiceBase).GetMethod(onStopMethodName, BindingFlags.Instance | BindingFlags.NonPublic);
+
+ if(onStartMethod == null || onStopMethod == null) {
+ throw new InvalidOperationException("The ServiceBase class isn't available.");
+ }
+
+ List serviceThreads = new List();
+ "Starting services . . .".Info(loggerSource ?? SwanRuntime.EntryAssemblyName.Name);
+
+ foreach(ServiceBase service in @this) {
+ Thread thread = new Thread(() => {
+ _ = onStartMethod.Invoke(service, new Object[] { Array.Empty() });
+ $"Started service '{service.GetType().Name}'".Info(loggerSource ?? service.GetType().Name);
+ });
+
+ serviceThreads.Add(thread);
+ thread.Start();
+ }
+
+ "Press any key to stop all services.".Info(loggerSource ?? SwanRuntime.EntryAssemblyName.Name);
+ _ = Terminal.ReadKey(true, true);
+ "Stopping services . . .".Info(SwanRuntime.EntryAssemblyName.Name);
+
+ foreach(ServiceBase service in @this) {
+ _ = onStopMethod.Invoke(service, null);
+ $"Stopped service '{service.GetType().Name}'".Info(loggerSource ?? service.GetType().Name);
+ }
+
+ foreach(Thread thread in serviceThreads) {
+ thread.Join();
+ }
+
+ "Stopped all services.".Info(loggerSource ?? SwanRuntime.EntryAssemblyName.Name);
+ }
+ }
}
diff --git a/Swan/Messaging/IMessageHubMessage.cs b/Swan/Messaging/IMessageHubMessage.cs
index 6cc2869..c9a7908 100644
--- a/Swan/Messaging/IMessageHubMessage.cs
+++ b/Swan/Messaging/IMessageHubMessage.cs
@@ -1,13 +1,15 @@
-namespace Swan.Messaging
-{
+using System;
+
+namespace Swan.Messaging {
+ ///
+ /// A Message to be published/delivered by Messenger.
+ ///
+ public interface IMessageHubMessage {
///
- /// A Message to be published/delivered by Messenger.
+ /// The sender of the message, or null if not supported by the message implementation.
///
- public interface IMessageHubMessage
- {
- ///
- /// The sender of the message, or null if not supported by the message implementation.
- ///
- object Sender { get; }
- }
+ Object Sender {
+ get;
+ }
+ }
}
diff --git a/Swan/Messaging/IMessageHubSubscription.cs b/Swan/Messaging/IMessageHubSubscription.cs
index 0248b85..33bb877 100644
--- a/Swan/Messaging/IMessageHubSubscription.cs
+++ b/Swan/Messaging/IMessageHubSubscription.cs
@@ -1,26 +1,28 @@
-namespace Swan.Messaging
-{
+using System;
+
+namespace Swan.Messaging {
+ ///
+ /// Represents a message subscription.
+ ///
+ public interface IMessageHubSubscription {
///
- /// Represents a message subscription.
+ /// Token returned to the subscribed to reference this subscription.
///
- public interface IMessageHubSubscription
- {
- ///
- /// Token returned to the subscribed to reference this subscription.
- ///
- MessageHubSubscriptionToken SubscriptionToken { get; }
-
- ///
- /// Whether delivery should be attempted.
- ///
- /// Message that may potentially be delivered.
- /// true - ok to send, false - should not attempt to send.
- bool ShouldAttemptDelivery(IMessageHubMessage message);
-
- ///
- /// Deliver the message.
- ///
- /// Message to deliver.
- void Deliver(IMessageHubMessage message);
- }
+ MessageHubSubscriptionToken SubscriptionToken {
+ get;
+ }
+
+ ///
+ /// Whether delivery should be attempted.
+ ///
+ /// Message that may potentially be delivered.
+ /// true - ok to send, false - should not attempt to send.
+ Boolean ShouldAttemptDelivery(IMessageHubMessage message);
+
+ ///
+ /// Deliver the message.
+ ///
+ /// Message to deliver.
+ void Deliver(IMessageHubMessage message);
+ }
}
\ No newline at end of file
diff --git a/Swan/Messaging/MessageHub.cs b/Swan/Messaging/MessageHub.cs
index 7a1de9b..d914a0d 100644
--- a/Swan/Messaging/MessageHub.cs
+++ b/Swan/Messaging/MessageHub.cs
@@ -1,442 +1,371 @@
-// ===============================================================================
-// TinyIoC - TinyMessenger
-//
-// A simple messenger/event aggregator.
-//
-// https://github.com/grumpydev/TinyIoC/blob/master/src/TinyIoC/TinyMessenger.cs
-// ===============================================================================
-// Copyright © Steven Robbins. All rights reserved.
-// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
-// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
-// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
-// FITNESS FOR A PARTICULAR PURPOSE.
-// ===============================================================================
-
-namespace Swan.Messaging
-{
- using System.Threading.Tasks;
- using System;
- using System.Collections.Generic;
- using System.Linq;
-
- #region Message Types / Interfaces
-
+// ===============================================================================
+// TinyIoC - TinyMessenger
+//
+// A simple messenger/event aggregator.
+//
+// https://github.com/grumpydev/TinyIoC/blob/master/src/TinyIoC/TinyMessenger.cs
+// ===============================================================================
+// Copyright © Steven Robbins. All rights reserved.
+// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
+// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
+// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+// FITNESS FOR A PARTICULAR PURPOSE.
+// ===============================================================================
+#nullable enable
+using System.Threading.Tasks;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Swan.Messaging {
+ #region Message Types / Interfaces
+
+ ///
+ /// Message proxy definition.
+ ///
+ /// A message proxy can be used to intercept/alter messages and/or
+ /// marshal delivery actions onto a particular thread.
+ ///
+ public interface IMessageHubProxy {
///
- /// Message proxy definition.
+ /// Delivers the specified message.
+ ///
+ /// The message.
+ /// The subscription.
+ void Deliver(IMessageHubMessage message, IMessageHubSubscription subscription);
+ }
+
+ ///
+ /// Default "pass through" proxy.
+ ///
+ /// Does nothing other than deliver the message.
+ ///
+ public sealed class MessageHubDefaultProxy : IMessageHubProxy {
+ private MessageHubDefaultProxy() {
+ // placeholder
+ }
+
+ ///
+ /// Singleton instance of the proxy.
+ ///
+ public static MessageHubDefaultProxy Instance { get; } = new MessageHubDefaultProxy();
+
+ ///
+ /// Delivers the specified message.
+ ///
+ /// The message.
+ /// The subscription.
+ public void Deliver(IMessageHubMessage message, IMessageHubSubscription subscription) => subscription.Deliver(message);
+ }
+
+ #endregion
+
+ #region Hub Interface
+
+ ///
+ /// Messenger hub responsible for taking subscriptions/publications and delivering of messages.
+ ///
+ public interface IMessageHub {
+ ///
+ /// Subscribe to a message type with the given destination and delivery action.
+ /// Messages will be delivered via the specified proxy.
///
- /// A message proxy can be used to intercept/alter messages and/or
- /// marshal delivery actions onto a particular thread.
+ /// All messages of this type will be delivered.
///
- public interface IMessageHubProxy
- {
- ///
- /// Delivers the specified message.
- ///
- /// The message.
- /// The subscription.
- void Deliver(IMessageHubMessage message, IMessageHubSubscription subscription);
- }
-
+ /// Type of message.
+ /// Action to invoke when message is delivered.
+ /// Use strong references to destination and deliveryAction.
+ /// Proxy to use when delivering the messages.
+ /// MessageSubscription used to unsubscribing.
+ MessageHubSubscriptionToken Subscribe(Action deliveryAction, Boolean useStrongReferences, IMessageHubProxy proxy) where TMessage : class, IMessageHubMessage;
+
///
- /// Default "pass through" proxy.
+ /// Subscribe to a message type with the given destination and delivery action with the given filter.
+ /// Messages will be delivered via the specified proxy.
+ /// All references are held with WeakReferences
+ /// Only messages that "pass" the filter will be delivered.
+ ///
+ /// Type of message.
+ /// Action to invoke when message is delivered.
+ /// The message filter.
+ /// Use strong references to destination and deliveryAction.
+ /// Proxy to use when delivering the messages.
+ ///
+ /// MessageSubscription used to unsubscribing.
+ ///
+ MessageHubSubscriptionToken Subscribe(Action deliveryAction, Func messageFilter, Boolean useStrongReferences, IMessageHubProxy proxy) where TMessage : class, IMessageHubMessage;
+
+ ///
+ /// Unsubscribe from a particular message type.
///
- /// Does nothing other than deliver the message.
+ /// Does not throw an exception if the subscription is not found.
///
- public sealed class MessageHubDefaultProxy : IMessageHubProxy
- {
- private MessageHubDefaultProxy()
- {
- // placeholder
- }
-
- ///
- /// Singleton instance of the proxy.
- ///
- public static MessageHubDefaultProxy Instance { get; } = new MessageHubDefaultProxy();
-
- ///
- /// Delivers the specified message.
- ///
- /// The message.
- /// The subscription.
- public void Deliver(IMessageHubMessage message, IMessageHubSubscription subscription)
- => subscription.Deliver(message);
- }
-
- #endregion
-
- #region Hub Interface
-
+ /// Type of message.
+ /// Subscription token received from Subscribe.
+ void Unsubscribe(MessageHubSubscriptionToken subscriptionToken) where TMessage : class, IMessageHubMessage;
+
///
- /// Messenger hub responsible for taking subscriptions/publications and delivering of messages.
+ /// Publish a message to any subscribers.
///
- public interface IMessageHub
- {
- ///
- /// Subscribe to a message type with the given destination and delivery action.
- /// Messages will be delivered via the specified proxy.
- ///
- /// All messages of this type will be delivered.
- ///
- /// Type of message.
- /// Action to invoke when message is delivered.
- /// Use strong references to destination and deliveryAction.
- /// Proxy to use when delivering the messages.
- /// MessageSubscription used to unsubscribing.
- MessageHubSubscriptionToken Subscribe(
- Action deliveryAction,
- bool useStrongReferences,
- IMessageHubProxy proxy)
- where TMessage : class, IMessageHubMessage;
-
- ///
- /// Subscribe to a message type with the given destination and delivery action with the given filter.
- /// Messages will be delivered via the specified proxy.
- /// All references are held with WeakReferences
- /// Only messages that "pass" the filter will be delivered.
- ///
- /// Type of message.
- /// Action to invoke when message is delivered.
- /// The message filter.
- /// Use strong references to destination and deliveryAction.
- /// Proxy to use when delivering the messages.
- ///
- /// MessageSubscription used to unsubscribing.
- ///
- MessageHubSubscriptionToken Subscribe(
- Action deliveryAction,
- Func messageFilter,
- bool useStrongReferences,
- IMessageHubProxy proxy)
- where TMessage : class, IMessageHubMessage;
-
- ///
- /// Unsubscribe from a particular message type.
- ///
- /// Does not throw an exception if the subscription is not found.
- ///
- /// Type of message.
- /// Subscription token received from Subscribe.
- void Unsubscribe(MessageHubSubscriptionToken subscriptionToken)
- where TMessage : class, IMessageHubMessage;
-
- ///
- /// Publish a message to any subscribers.
- ///
- /// Type of message.
- /// Message to deliver.
- void Publish(TMessage message)
- where TMessage : class, IMessageHubMessage;
-
- ///
- /// Publish a message to any subscribers asynchronously.
- ///
- /// Type of message.
- /// Message to deliver.
- /// A task from Publish action.
- Task PublishAsync(TMessage message)
- where TMessage : class, IMessageHubMessage;
- }
-
+ /// Type of message.
+ /// Message to deliver.
+ void Publish(TMessage message) where TMessage : class, IMessageHubMessage;
+
+ ///
+ /// Publish a message to any subscribers asynchronously.
+ ///
+ /// Type of message.
+ /// Message to deliver.
+ /// A task from Publish action.
+ Task PublishAsync(TMessage message) where TMessage : class, IMessageHubMessage;
+ }
+
+ #endregion
+
+ #region Hub Implementation
+
+ ///
+ ///
+ /// The following code describes how to use a MessageHub. Both the
+ /// subscription and the message sending are done in the same place but this is only for explanatory purposes.
+ ///
+ /// class Example
+ /// {
+ /// using Swan;
+ /// using Swan.Components;
+ ///
+ /// static void Main()
+ /// {
+ /// // using DependencyContainer to create an instance of MessageHub
+ /// var messageHub = DependencyContainer
+ /// .Current
+ /// .Resolve<IMessageHub>() as MessageHub;
+ ///
+ /// // create an instance of the publisher class
+ /// // which has a string as its content
+ /// var message = new MessageHubGenericMessage<string>(new object(), "SWAN");
+ ///
+ /// // subscribe to the publisher's event
+ /// // and just print out the content which is a string
+ /// // a token is returned which can be used to unsubscribe later on
+ /// var token = messageHub
+ /// .Subscribe<MessageHubGenericMessage<string>>(m => m.Content.Info());
+ ///
+ /// // publish the message described above which is
+ /// // the string 'SWAN'
+ /// messageHub.Publish(message);
+ ///
+ /// // unsuscribe, we will no longer receive any messages
+ /// messageHub.Unsubscribe<MessageHubGenericMessage<string>>(token);
+ ///
+ /// Terminal.Flush();
+ /// }
+ ///
+ /// }
+ ///
+ ///
+ public sealed class MessageHub : IMessageHub {
+ #region Private Types and Interfaces
+
+ private readonly Object _subscriptionsPadlock = new Object();
+
+ private readonly Dictionary> _subscriptions = new Dictionary>();
+
+ private class WeakMessageSubscription : IMessageHubSubscription where TMessage : class, IMessageHubMessage {
+ private readonly WeakReference _deliveryAction;
+ private readonly WeakReference _messageFilter;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The subscription token.
+ /// The delivery action.
+ /// The message filter.
+ /// subscriptionToken
+ /// or
+ /// deliveryAction
+ /// or
+ /// messageFilter.
+ public WeakMessageSubscription(MessageHubSubscriptionToken subscriptionToken, Action deliveryAction, Func messageFilter) {
+ this.SubscriptionToken = subscriptionToken ?? throw new ArgumentNullException(nameof(subscriptionToken));
+ this._deliveryAction = new WeakReference(deliveryAction);
+ this._messageFilter = new WeakReference(messageFilter);
+ }
+
+ public MessageHubSubscriptionToken SubscriptionToken {
+ get;
+ }
+
+ public Boolean ShouldAttemptDelivery(IMessageHubMessage message) => this._deliveryAction.IsAlive && this._messageFilter.IsAlive && ((Func)this._messageFilter.Target!).Invoke((TMessage)message);
+
+ public void Deliver(IMessageHubMessage message) {
+ if(this._deliveryAction.IsAlive) {
+ ((Action)this._deliveryAction.Target!).Invoke((TMessage)message);
+ }
+ }
+ }
+
+ private class StrongMessageSubscription : IMessageHubSubscription where TMessage : class, IMessageHubMessage {
+ private readonly Action _deliveryAction;
+ private readonly Func _messageFilter;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The subscription token.
+ /// The delivery action.
+ /// The message filter.
+ /// subscriptionToken
+ /// or
+ /// deliveryAction
+ /// or
+ /// messageFilter.
+ public StrongMessageSubscription(MessageHubSubscriptionToken subscriptionToken, Action deliveryAction, Func messageFilter) {
+ this.SubscriptionToken = subscriptionToken ?? throw new ArgumentNullException(nameof(subscriptionToken));
+ this._deliveryAction = deliveryAction;
+ this._messageFilter = messageFilter;
+ }
+
+ public MessageHubSubscriptionToken SubscriptionToken {
+ get;
+ }
+
+ public Boolean ShouldAttemptDelivery(IMessageHubMessage message) => this._messageFilter.Invoke((TMessage)message);
+
+ public void Deliver(IMessageHubMessage message) => this._deliveryAction.Invoke((TMessage)message);
+ }
+
#endregion
-
- #region Hub Implementation
-
+
+ #region Subscription dictionary
+
+ private class SubscriptionItem {
+ public SubscriptionItem(IMessageHubProxy proxy, IMessageHubSubscription subscription) {
+ this.Proxy = proxy;
+ this.Subscription = subscription;
+ }
+
+ public IMessageHubProxy Proxy {
+ get;
+ }
+ public IMessageHubSubscription Subscription {
+ get;
+ }
+ }
+
+ #endregion
+
+ #region Public API
+
+ ///
+ /// Subscribe to a message type with the given destination and delivery action.
+ /// Messages will be delivered via the specified proxy.
+ ///
+ /// All messages of this type will be delivered.
+ ///
+ /// Type of message.
+ /// Action to invoke when message is delivered.
+ /// Use strong references to destination and deliveryAction.
+ /// Proxy to use when delivering the messages.
+ /// MessageSubscription used to unsubscribing.
+ public MessageHubSubscriptionToken Subscribe(Action deliveryAction, Boolean useStrongReferences = true, IMessageHubProxy? proxy = null) where TMessage : class, IMessageHubMessage => this.Subscribe(deliveryAction, m => true, useStrongReferences, proxy);
+
+
+ ///
+ /// Subscribe to a message type with the given destination and delivery action with the given filter.
+ /// Messages will be delivered via the specified proxy.
+ /// All references are held with WeakReferences
+ /// Only messages that "pass" the filter will be delivered.
+ ///
+ /// Type of message.
+ /// Action to invoke when message is delivered.
+ /// The message filter.
+ /// Use strong references to destination and deliveryAction.
+ /// Proxy to use when delivering the messages.
+ ///
+ /// MessageSubscription used to unsubscribing.
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0068:Empfohlenes Dispose-Muster verwenden", Justification = "")]
+ public MessageHubSubscriptionToken Subscribe(Action deliveryAction, Func messageFilter, Boolean useStrongReferences = true, IMessageHubProxy? proxy = null) where TMessage : class, IMessageHubMessage {
+ if(deliveryAction == null) {
+ throw new ArgumentNullException(nameof(deliveryAction));
+ }
+
+ if(messageFilter == null) {
+ throw new ArgumentNullException(nameof(messageFilter));
+ }
+
+ lock(this._subscriptionsPadlock) {
+ if(!this._subscriptions.TryGetValue(typeof(TMessage), out List? currentSubscriptions)) {
+ currentSubscriptions = new List();
+ this._subscriptions[typeof(TMessage)] = currentSubscriptions;
+ }
+
+ MessageHubSubscriptionToken subscriptionToken = new MessageHubSubscriptionToken(this, typeof(TMessage));
+
+ IMessageHubSubscription subscription = useStrongReferences ? new StrongMessageSubscription(subscriptionToken, deliveryAction, messageFilter) : (IMessageHubSubscription)new WeakMessageSubscription(subscriptionToken, deliveryAction, messageFilter);
+
+ currentSubscriptions.Add(new SubscriptionItem(proxy ?? MessageHubDefaultProxy.Instance, subscription));
+
+ return subscriptionToken;
+ }
+ }
+
///
- ///
- /// The following code describes how to use a MessageHub. Both the
- /// subscription and the message sending are done in the same place but this is only for explanatory purposes.
- ///
- /// class Example
- /// {
- /// using Swan;
- /// using Swan.Components;
- ///
- /// static void Main()
- /// {
- /// // using DependencyContainer to create an instance of MessageHub
- /// var messageHub = DependencyContainer
- /// .Current
- /// .Resolve<IMessageHub>() as MessageHub;
- ///
- /// // create an instance of the publisher class
- /// // which has a string as its content
- /// var message = new MessageHubGenericMessage<string>(new object(), "SWAN");
- ///
- /// // subscribe to the publisher's event
- /// // and just print out the content which is a string
- /// // a token is returned which can be used to unsubscribe later on
- /// var token = messageHub
- /// .Subscribe<MessageHubGenericMessage<string>>(m => m.Content.Info());
- ///
- /// // publish the message described above which is
- /// // the string 'SWAN'
- /// messageHub.Publish(message);
- ///
- /// // unsuscribe, we will no longer receive any messages
- /// messageHub.Unsubscribe<MessageHubGenericMessage<string>>(token);
- ///
- /// Terminal.Flush();
- /// }
- ///
- /// }
- ///
- ///
- public sealed class MessageHub : IMessageHub
- {
- #region Private Types and Interfaces
-
- private readonly object _subscriptionsPadlock = new object();
-
- private readonly Dictionary> _subscriptions =
- new Dictionary>();
-
- private class WeakMessageSubscription : IMessageHubSubscription
- where TMessage : class, IMessageHubMessage
- {
- private readonly WeakReference _deliveryAction;
- private readonly WeakReference _messageFilter;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The subscription token.
- /// The delivery action.
- /// The message filter.
- /// subscriptionToken
- /// or
- /// deliveryAction
- /// or
- /// messageFilter.
- public WeakMessageSubscription(
- MessageHubSubscriptionToken subscriptionToken,
- Action deliveryAction,
- Func messageFilter)
- {
- SubscriptionToken = subscriptionToken ?? throw new ArgumentNullException(nameof(subscriptionToken));
- _deliveryAction = new WeakReference(deliveryAction);
- _messageFilter = new WeakReference(messageFilter);
- }
-
- public MessageHubSubscriptionToken SubscriptionToken { get; }
-
- public bool ShouldAttemptDelivery(IMessageHubMessage message)
- {
- return _deliveryAction.IsAlive && _messageFilter.IsAlive &&
- ((Func) _messageFilter.Target).Invoke((TMessage) message);
- }
-
- public void Deliver(IMessageHubMessage message)
- {
- if (_deliveryAction.IsAlive)
- {
- ((Action) _deliveryAction.Target).Invoke((TMessage) message);
- }
- }
- }
-
- private class StrongMessageSubscription : IMessageHubSubscription
- where TMessage : class, IMessageHubMessage
- {
- private readonly Action _deliveryAction;
- private readonly Func _messageFilter;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The subscription token.
- /// The delivery action.
- /// The message filter.
- /// subscriptionToken
- /// or
- /// deliveryAction
- /// or
- /// messageFilter.
- public StrongMessageSubscription(
- MessageHubSubscriptionToken subscriptionToken,
- Action deliveryAction,
- Func messageFilter)
- {
- SubscriptionToken = subscriptionToken ?? throw new ArgumentNullException(nameof(subscriptionToken));
- _deliveryAction = deliveryAction;
- _messageFilter = messageFilter;
- }
-
- public MessageHubSubscriptionToken SubscriptionToken { get; }
-
- public bool ShouldAttemptDelivery(IMessageHubMessage message) => _messageFilter.Invoke((TMessage) message);
-
- public void Deliver(IMessageHubMessage message) => _deliveryAction.Invoke((TMessage) message);
- }
-
- #endregion
-
- #region Subscription dictionary
-
- private class SubscriptionItem
- {
- public SubscriptionItem(IMessageHubProxy proxy, IMessageHubSubscription subscription)
- {
- Proxy = proxy;
- Subscription = subscription;
- }
-
- public IMessageHubProxy Proxy { get; }
- public IMessageHubSubscription Subscription { get; }
- }
-
- #endregion
-
- #region Public API
-
- ///
- /// Subscribe to a message type with the given destination and delivery action.
- /// Messages will be delivered via the specified proxy.
- ///
- /// All messages of this type will be delivered.
- ///
- /// Type of message.
- /// Action to invoke when message is delivered.
- /// Use strong references to destination and deliveryAction.
- /// Proxy to use when delivering the messages.
- /// MessageSubscription used to unsubscribing.
- public MessageHubSubscriptionToken Subscribe(
- Action deliveryAction,
- bool useStrongReferences = true,
- IMessageHubProxy? proxy = null)
- where TMessage : class, IMessageHubMessage
- {
- return Subscribe(deliveryAction, m => true, useStrongReferences, proxy);
- }
-
- ///
- /// Subscribe to a message type with the given destination and delivery action with the given filter.
- /// Messages will be delivered via the specified proxy.
- /// All references are held with WeakReferences
- /// Only messages that "pass" the filter will be delivered.
- ///
- /// Type of message.
- /// Action to invoke when message is delivered.
- /// The message filter.
- /// Use strong references to destination and deliveryAction.
- /// Proxy to use when delivering the messages.
- ///
- /// MessageSubscription used to unsubscribing.
- ///
- public MessageHubSubscriptionToken Subscribe(
- Action deliveryAction,
- Func messageFilter,
- bool useStrongReferences = true,
- IMessageHubProxy? proxy = null)
- where TMessage : class, IMessageHubMessage
- {
- if (deliveryAction == null)
- throw new ArgumentNullException(nameof(deliveryAction));
-
- if (messageFilter == null)
- throw new ArgumentNullException(nameof(messageFilter));
-
- lock (_subscriptionsPadlock)
- {
- if (!_subscriptions.TryGetValue(typeof(TMessage), out var currentSubscriptions))
- {
- currentSubscriptions = new List();
- _subscriptions[typeof(TMessage)] = currentSubscriptions;
- }
-
- var subscriptionToken = new MessageHubSubscriptionToken(this, typeof(TMessage));
-
- IMessageHubSubscription subscription;
- if (useStrongReferences)
- {
- subscription = new StrongMessageSubscription(
- subscriptionToken,
- deliveryAction,
- messageFilter);
- }
- else
- {
- subscription = new WeakMessageSubscription(
- subscriptionToken,
- deliveryAction,
- messageFilter);
- }
-
- currentSubscriptions.Add(new SubscriptionItem(proxy ?? MessageHubDefaultProxy.Instance, subscription));
-
- return subscriptionToken;
- }
- }
-
- ///
- public void Unsubscribe(MessageHubSubscriptionToken subscriptionToken)
- where TMessage : class, IMessageHubMessage
- {
- if (subscriptionToken == null)
- throw new ArgumentNullException(nameof(subscriptionToken));
-
- lock (_subscriptionsPadlock)
- {
- if (!_subscriptions.TryGetValue(typeof(TMessage), out var currentSubscriptions))
- return;
-
- var currentlySubscribed = currentSubscriptions
- .Where(sub => ReferenceEquals(sub.Subscription.SubscriptionToken, subscriptionToken))
- .ToList();
-
- currentlySubscribed.ForEach(sub => currentSubscriptions.Remove(sub));
- }
- }
-
- ///
- /// Publish a message to any subscribers.
- ///
- /// Type of message.
- /// Message to deliver.
- public void Publish(TMessage message)
- where TMessage : class, IMessageHubMessage
- {
- if (message == null)
- throw new ArgumentNullException(nameof(message));
-
- List currentlySubscribed;
- lock (_subscriptionsPadlock)
- {
- if (!_subscriptions.TryGetValue(typeof(TMessage), out var currentSubscriptions))
- return;
-
- currentlySubscribed = currentSubscriptions
- .Where(sub => sub.Subscription.ShouldAttemptDelivery(message))
- .ToList();
- }
-
- currentlySubscribed.ForEach(sub =>
- {
- try
- {
- sub.Proxy.Deliver(message, sub.Subscription);
- }
- catch
- {
- // Ignore any errors and carry on
- }
- });
- }
-
- ///
- /// Publish a message to any subscribers asynchronously.
- ///
- /// Type of message.
- /// Message to deliver.
- /// A task with the publish.
- public Task PublishAsync(TMessage message)
- where TMessage : class, IMessageHubMessage
- {
- return Task.Run(() => Publish(message));
- }
-
- #endregion
- }
-
+ public void Unsubscribe(MessageHubSubscriptionToken subscriptionToken) where TMessage : class, IMessageHubMessage {
+ if(subscriptionToken == null) {
+ throw new ArgumentNullException(nameof(subscriptionToken));
+ }
+
+ lock(this._subscriptionsPadlock) {
+ if(!this._subscriptions.TryGetValue(typeof(TMessage), out List? currentSubscriptions)) {
+ return;
+ }
+
+ List currentlySubscribed = currentSubscriptions.Where(sub => ReferenceEquals(sub.Subscription.SubscriptionToken, subscriptionToken)).ToList();
+
+ currentlySubscribed.ForEach(sub => currentSubscriptions.Remove(sub));
+ }
+ }
+
+ ///
+ /// Publish a message to any subscribers.
+ ///
+ /// Type of message.
+ /// Message to deliver.
+ public void Publish(TMessage message) where TMessage : class, IMessageHubMessage {
+ if(message == null) {
+ throw new ArgumentNullException(nameof(message));
+ }
+
+ List currentlySubscribed;
+ lock(this._subscriptionsPadlock) {
+ if(!this._subscriptions.TryGetValue(typeof(TMessage), out List? currentSubscriptions)) {
+ return;
+ }
+
+ currentlySubscribed = currentSubscriptions.Where(sub => sub.Subscription.ShouldAttemptDelivery(message)).ToList();
+ }
+
+ currentlySubscribed.ForEach(sub => {
+ try {
+ sub.Proxy.Deliver(message, sub.Subscription);
+ } catch {
+ // Ignore any errors and carry on
+ }
+ });
+ }
+
+ ///
+ /// Publish a message to any subscribers asynchronously.
+ ///
+ /// Type of message.
+ /// Message to deliver.
+ /// A task with the publish.
+ public Task PublishAsync(TMessage message) where TMessage : class, IMessageHubMessage => Task.Run(() => this.Publish(message));
+
#endregion
+ }
+
+ #endregion
}
diff --git a/Swan/Messaging/MessageHubMessageBase.cs b/Swan/Messaging/MessageHubMessageBase.cs
index bdcdf6f..ff606b0 100644
--- a/Swan/Messaging/MessageHubMessageBase.cs
+++ b/Swan/Messaging/MessageHubMessageBase.cs
@@ -1,57 +1,50 @@
-namespace Swan.Messaging
-{
- using System;
-
+using System;
+
+namespace Swan.Messaging {
+ ///
+ /// Base class for messages that provides weak reference storage of the sender.
+ ///
+ public abstract class MessageHubMessageBase : IMessageHubMessage {
///
- /// Base class for messages that provides weak reference storage of the sender.
+ /// Store a WeakReference to the sender just in case anyone is daft enough to
+ /// keep the message around and prevent the sender from being collected.
///
- public abstract class MessageHubMessageBase
- : IMessageHubMessage
- {
- ///
- /// Store a WeakReference to the sender just in case anyone is daft enough to
- /// keep the message around and prevent the sender from being collected.
- ///
- private readonly WeakReference _sender;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The sender.
- /// sender.
- protected MessageHubMessageBase(object sender)
- {
- if (sender == null)
- throw new ArgumentNullException(nameof(sender));
-
- _sender = new WeakReference(sender);
- }
-
- ///
- public object Sender => _sender.Target;
- }
-
+ private readonly WeakReference _sender;
+
///
- /// Generic message with user specified content.
+ /// Initializes a new instance of the class.
///
- /// Content type to store.
- public class MessageHubGenericMessage
- : MessageHubMessageBase
- {
- ///
- /// Initializes a new instance of the class.
- ///
- /// The sender.
- /// The content.
- public MessageHubGenericMessage(object sender, TContent content)
- : base(sender)
- {
- Content = content;
- }
-
- ///
- /// Contents of the message.
- ///
- public TContent Content { get; protected set; }
- }
+ /// The sender.
+ /// sender.
+ protected MessageHubMessageBase(Object sender) {
+ if(sender == null) {
+ throw new ArgumentNullException(nameof(sender));
+ }
+
+ this._sender = new WeakReference(sender);
+ }
+
+ ///
+ public Object Sender => this._sender.Target;
+ }
+
+ ///
+ /// Generic message with user specified content.
+ ///
+ /// Content type to store.
+ public class MessageHubGenericMessage : MessageHubMessageBase {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The sender.
+ /// The content.
+ public MessageHubGenericMessage(Object sender, TContent content) : base(sender) => this.Content = content;
+
+ ///
+ /// Contents of the message.
+ ///
+ public TContent Content {
+ get; protected set;
+ }
+ }
}
diff --git a/Swan/Messaging/MessageHubSubscriptionToken.cs b/Swan/Messaging/MessageHubSubscriptionToken.cs
index 4cc6a71..d6bd50a 100644
--- a/Swan/Messaging/MessageHubSubscriptionToken.cs
+++ b/Swan/Messaging/MessageHubSubscriptionToken.cs
@@ -1,51 +1,43 @@
-namespace Swan.Messaging
-{
- using System;
-
+using System;
+using System.Reflection;
+
+namespace Swan.Messaging {
+ ///
+ /// Represents an active subscription to a message.
+ ///
+ public sealed class MessageHubSubscriptionToken : IDisposable {
+ private readonly WeakReference _hub;
+ private readonly Type _messageType;
+
///
- /// Represents an active subscription to a message.
+ /// Initializes a new instance of the class.
///
- public sealed class MessageHubSubscriptionToken
- : IDisposable
- {
- private readonly WeakReference _hub;
- private readonly Type _messageType;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The hub.
- /// Type of the message.
- /// hub.
- /// messageType.
- public MessageHubSubscriptionToken(IMessageHub hub, Type messageType)
- {
- if (hub == null)
- {
- throw new ArgumentNullException(nameof(hub));
- }
-
- if (!typeof(IMessageHubMessage).IsAssignableFrom(messageType))
- {
- throw new ArgumentOutOfRangeException(nameof(messageType));
- }
-
- _hub = new WeakReference(hub);
- _messageType = messageType;
- }
-
- ///
- public void Dispose()
- {
- if (_hub.IsAlive && _hub.Target is IMessageHub hub)
- {
- var unsubscribeMethod = typeof(IMessageHub).GetMethod(nameof(IMessageHub.Unsubscribe),
- new[] {typeof(MessageHubSubscriptionToken)});
- unsubscribeMethod = unsubscribeMethod.MakeGenericMethod(_messageType);
- unsubscribeMethod.Invoke(hub, new object[] {this});
- }
-
- GC.SuppressFinalize(this);
- }
- }
+ /// The hub.
+ /// Type of the message.
+ /// hub.
+ /// messageType.
+ public MessageHubSubscriptionToken(IMessageHub hub, Type messageType) {
+ if(hub == null) {
+ throw new ArgumentNullException(nameof(hub));
+ }
+
+ if(!typeof(IMessageHubMessage).IsAssignableFrom(messageType)) {
+ throw new ArgumentOutOfRangeException(nameof(messageType));
+ }
+
+ this._hub = new WeakReference(hub);
+ this._messageType = messageType;
+ }
+
+ ///
+ public void Dispose() {
+ if(this._hub.IsAlive && this._hub.Target is IMessageHub hub) {
+ MethodInfo unsubscribeMethod = typeof(IMessageHub).GetMethod(nameof(IMessageHub.Unsubscribe), new[] { typeof(MessageHubSubscriptionToken) });
+ unsubscribeMethod = unsubscribeMethod.MakeGenericMethod(this._messageType);
+ _ = unsubscribeMethod.Invoke(hub, new Object[] { this });
+ }
+
+ GC.SuppressFinalize(this);
+ }
+ }
}
\ No newline at end of file
diff --git a/Swan/Net/Connection.cs b/Swan/Net/Connection.cs
index ff25bee..fa504af 100644
--- a/Swan/Net/Connection.cs
+++ b/Swan/Net/Connection.cs
@@ -1,886 +1,836 @@
-namespace Swan.Net
-{
- using Logging;
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.Net;
- using System.Net.Security;
- using System.Net.Sockets;
- using System.Security.Cryptography.X509Certificates;
- using System.Text;
- using System.Threading;
- using System.Threading.Tasks;
-
+#nullable enable
+using Swan.Logging;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Security;
+using System.Net.Sockets;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Swan.Net {
+ ///
+ /// Represents a network connection either on the server or on the client. It wraps a TcpClient
+ /// and its corresponding network streams. It is capable of working in 2 modes. Typically on the server side
+ /// you will need to enable continuous reading and events. On the client side you may want to disable continuous reading
+ /// and use the Read methods available. In continuous reading mode Read methods are not available and will throw
+ /// an invalid operation exceptions if they are used.
+ /// Continuous Reading Mode: Subscribe to data reception events, it runs a background thread, don't use Read methods
+ /// Manual Reading Mode: Data reception events are NEVER fired. No background threads are used. Use Read methods to receive data.
+ ///
+ ///
+ ///
+ /// The following code explains how to create a TCP server.
+ ///
+ /// using System.Text;
+ /// using Swan.Net;
+ ///
+ /// class Example
+ /// {
+ /// static void Main()
+ /// {
+ /// // create a new connection listener on a specific port
+ /// var connectionListener = new ConnectionListener(1337);
+ ///
+ /// // handle the OnConnectionAccepting event
+ /// connectionListener.OnConnectionAccepted += async (s, e) =>
+ /// {
+ /// // create a new connection
+ /// using (var con = new Connection(e.Client))
+ /// {
+ /// await con.WriteLineAsync("Hello world!");
+ /// }
+ /// };
+ ///
+ /// connectionListener.Start();
+ /// Console.ReadLine)=ñ
+ /// }
+ /// }
+ ///
+ /// The following code describes how to create a TCP client.
+ ///
+ /// using System.Net.Sockets;
+ /// using System.Text;
+ /// using System.Threading.Tasks;
+ /// using Swan.Net;
+ ///
+ /// class Example
+ /// {
+ /// static async Task Main()
+ /// {
+ /// // create a new TcpClient object
+ /// var client = new TcpClient();
+ ///
+ /// // connect to a specific address and port
+ /// client.Connect("localhost", 1337);
+ ///
+ /// //create a new connection with specific encoding,
+ /// //new line sequence and continuous reading disabled
+ /// using (var cn = new Connection(client, Encoding.UTF8, "\r\n", true, 0))
+ /// {
+ /// var response = await cn.ReadTextAsync();
+ /// }
+ /// }
+ /// }
+ ///
+ ///
+ public sealed class Connection : IDisposable {
+ // New Line definitions for reading. This applies to both, events and read methods
+ private readonly String _newLineSequence;
+
+ private readonly Byte[] _newLineSequenceBytes;
+ private readonly Char[] _newLineSequenceChars;
+ private readonly String[] _newLineSequenceLineSplitter;
+ private readonly Byte[] _receiveBuffer;
+ private readonly TimeSpan _continuousReadingInterval = TimeSpan.FromMilliseconds(5);
+ private readonly Queue _readLineBuffer = new Queue();
+ private readonly ManualResetEvent _writeDone = new ManualResetEvent(true);
+
+ // Disconnect and Dispose
+ private Boolean _hasDisposed;
+
+ private Int32 _disconnectCalls;
+
+ // Continuous Reading
+ private Thread? _continuousReadingThread;
+
+ private Int32 _receiveBufferPointer;
+
+ // Reading and writing
+ private Task? _readTask;
+
///
- /// Represents a network connection either on the server or on the client. It wraps a TcpClient
- /// and its corresponding network streams. It is capable of working in 2 modes. Typically on the server side
- /// you will need to enable continuous reading and events. On the client side you may want to disable continuous reading
- /// and use the Read methods available. In continuous reading mode Read methods are not available and will throw
- /// an invalid operation exceptions if they are used.
- /// Continuous Reading Mode: Subscribe to data reception events, it runs a background thread, don't use Read methods
- /// Manual Reading Mode: Data reception events are NEVER fired. No background threads are used. Use Read methods to receive data.
+ /// Initializes a new instance of the class.
///
- ///
- ///
- /// The following code explains how to create a TCP server.
- ///
- /// using System.Text;
- /// using Swan.Net;
- ///
- /// class Example
- /// {
- /// static void Main()
- /// {
- /// // create a new connection listener on a specific port
- /// var connectionListener = new ConnectionListener(1337);
- ///
- /// // handle the OnConnectionAccepting event
- /// connectionListener.OnConnectionAccepted += async (s, e) =>
- /// {
- /// // create a new connection
- /// using (var con = new Connection(e.Client))
- /// {
- /// await con.WriteLineAsync("Hello world!");
- /// }
- /// };
- ///
- /// connectionListener.Start();
- /// Console.ReadLine)=ñ
- /// }
- /// }
- ///
- /// The following code describes how to create a TCP client.
- ///
- /// using System.Net.Sockets;
- /// using System.Text;
- /// using System.Threading.Tasks;
- /// using Swan.Net;
- ///
- /// class Example
- /// {
- /// static async Task Main()
- /// {
- /// // create a new TcpClient object
- /// var client = new TcpClient();
- ///
- /// // connect to a specific address and port
- /// client.Connect("localhost", 1337);
- ///
- /// //create a new connection with specific encoding,
- /// //new line sequence and continuous reading disabled
- /// using (var cn = new Connection(client, Encoding.UTF8, "\r\n", true, 0))
- /// {
- /// var response = await cn.ReadTextAsync();
- /// }
- /// }
- /// }
- ///
- ///
- public sealed class Connection : IDisposable
- {
- // New Line definitions for reading. This applies to both, events and read methods
- private readonly string _newLineSequence;
-
- private readonly byte[] _newLineSequenceBytes;
- private readonly char[] _newLineSequenceChars;
- private readonly string[] _newLineSequenceLineSplitter;
- private readonly byte[] _receiveBuffer;
- private readonly TimeSpan _continuousReadingInterval = TimeSpan.FromMilliseconds(5);
- private readonly Queue _readLineBuffer = new Queue();
- private readonly ManualResetEvent _writeDone = new ManualResetEvent(true);
-
- // Disconnect and Dispose
- private bool _hasDisposed;
-
- private int _disconnectCalls;
-
- // Continuous Reading
- private Thread _continuousReadingThread;
-
- private int _receiveBufferPointer;
-
- // Reading and writing
- private Task _readTask;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The client.
- /// The text encoding.
- /// The new line sequence used for read and write operations.
- /// if set to true [disable continuous reading].
- /// Size of the block. -- set to 0 or less to disable.
- public Connection(
- TcpClient client,
- Encoding textEncoding,
- string newLineSequence,
- bool disableContinuousReading,
- int blockSize)
- {
- // Setup basic properties
- Id = Guid.NewGuid();
- TextEncoding = textEncoding;
-
- // Setup new line sequence
- if (string.IsNullOrEmpty(newLineSequence))
- throw new ArgumentException("Argument cannot be null", nameof(newLineSequence));
-
- _newLineSequence = newLineSequence;
- _newLineSequenceBytes = TextEncoding.GetBytes(_newLineSequence);
- _newLineSequenceChars = _newLineSequence.ToCharArray();
- _newLineSequenceLineSplitter = new[] { _newLineSequence };
-
- // Setup Connection timers
- ConnectionStartTimeUtc = DateTime.UtcNow;
- DataReceivedLastTimeUtc = ConnectionStartTimeUtc;
- DataSentLastTimeUtc = ConnectionStartTimeUtc;
-
- // Setup connection properties
- RemoteClient = client;
- LocalEndPoint = client.Client.LocalEndPoint as IPEndPoint;
- NetworkStream = RemoteClient.GetStream();
- RemoteEndPoint = RemoteClient.Client.RemoteEndPoint as IPEndPoint;
-
- // Setup buffers
- _receiveBuffer = new byte[RemoteClient.ReceiveBufferSize * 2];
- ProtocolBlockSize = blockSize;
- _receiveBufferPointer = 0;
-
- // Setup continuous reading mode if enabled
- if (disableContinuousReading) return;
-
- ThreadPool.GetAvailableThreads(out var availableWorkerThreads, out _);
- ThreadPool.GetMaxThreads(out var maxWorkerThreads, out _);
-
- var activeThreadPoolTreads = maxWorkerThreads - availableWorkerThreads;
-
- if (activeThreadPoolTreads < Environment.ProcessorCount / 4)
- {
- ThreadPool.QueueUserWorkItem(PerformContinuousReading, this);
- }
- else
- {
- new Thread(PerformContinuousReading) { IsBackground = true }.Start();
- }
- }
-
- ///
- /// Initializes a new instance of the class in continuous reading mode.
- /// It uses UTF8 encoding, CRLF as a new line sequence and disables a protocol block size.
- ///
- /// The client.
- public Connection(TcpClient client)
- : this(client, Encoding.UTF8, "\r\n", false, 0)
- {
- // placeholder
- }
-
- ///
- /// Initializes a new instance of the class in continuous reading mode.
- /// It uses UTF8 encoding, disables line sequences, and uses a protocol block size instead.
- ///
- /// The client.
- /// Size of the block.
- public Connection(TcpClient client, int blockSize)
- : this(client, Encoding.UTF8, new string('\n', blockSize + 1), false, blockSize)
- {
- // placeholder
- }
-
- #region Events
-
- ///
- /// Occurs when the receive buffer has encounters a new line sequence, the buffer is flushed or the buffer is full.
- ///
- public event EventHandler DataReceived = (s, e) => { };
-
- ///
- /// Occurs when an error occurs while upgrading, sending, or receiving data in this client
- ///
- public event EventHandler ConnectionFailure = (s, e) => { };
-
- ///
- /// Occurs when a client is disconnected
- ///
- public event EventHandler ClientDisconnected = (s, e) => { };
-
- #endregion
-
- #region Properties
-
- ///
- /// Gets the unique identifier of this connection.
- /// This field is filled out upon instantiation of this class.
- ///
- ///
- /// The identifier.
- ///
- public Guid Id { get; }
-
- ///
- /// Gets the active stream. Returns an SSL stream if the connection is secure, otherwise returns
- /// the underlying NetworkStream.
- ///
- ///
- /// The active stream.
- ///
- public Stream ActiveStream => SecureStream ?? NetworkStream as Stream;
-
- ///
- /// Gets a value indicating whether the current connection stream is an SSL stream.
- ///
- ///
- /// true if this instance is active stream secure; otherwise, false .
- ///
- public bool IsActiveStreamSecure => SecureStream != null;
-
- ///
- /// Gets the text encoding for send and receive operations.
- ///
- ///
- /// The text encoding.
- ///
- public Encoding TextEncoding { get; }
-
- ///
- /// Gets the remote end point of this TCP connection.
- ///
- ///
- /// The remote end point.
- ///
- public IPEndPoint RemoteEndPoint { get; }
-
- ///
- /// Gets the local end point of this TCP connection.
- ///
- ///
- /// The local end point.
- ///
- public IPEndPoint LocalEndPoint { get; }
-
- ///
- /// Gets the remote client of this TCP connection.
- ///
- ///
- /// The remote client.
- ///
- public TcpClient RemoteClient { get; private set; }
-
- ///
- /// When in continuous reading mode, and if set to greater than 0,
- /// a Data reception event will be fired whenever the amount of bytes
- /// determined by this property has been received. Useful for fixed-length message protocols.
- ///
- ///
- /// The size of the protocol block.
- ///
- public int ProtocolBlockSize { get; }
-
- ///
- /// Gets a value indicating whether this connection is in continuous reading mode.
- /// Remark: Whenever a disconnect event occurs, the background thread is terminated
- /// and this property will return false whenever the reading thread is not active.
- /// Therefore, even if continuous reading was not disabled in the constructor, this property
- /// might return false.
- ///
- ///
- /// true if this instance is continuous reading enabled; otherwise, false .
- ///
- public bool IsContinuousReadingEnabled => _continuousReadingThread != null;
-
- ///
- /// Gets the start time at which the connection was started in UTC.
- ///
- ///
- /// The connection start time UTC.
- ///
- public DateTime ConnectionStartTimeUtc { get; }
-
- ///
- /// Gets the start time at which the connection was started in local time.
- ///
- ///
- /// The connection start time.
- ///
- public DateTime ConnectionStartTime => ConnectionStartTimeUtc.ToLocalTime();
-
- ///
- /// Gets the duration of the connection.
- ///
- ///
- /// The duration of the connection.
- ///
- public TimeSpan ConnectionDuration => DateTime.UtcNow.Subtract(ConnectionStartTimeUtc);
-
- ///
- /// Gets the last time data was received at in UTC.
- ///
- ///
- /// The data received last time UTC.
- ///
- public DateTime DataReceivedLastTimeUtc { get; private set; }
-
- ///
- /// Gets how long has elapsed since data was last received.
- ///
- public TimeSpan DataReceivedIdleDuration => DateTime.UtcNow.Subtract(DataReceivedLastTimeUtc);
-
- ///
- /// Gets the last time at which data was sent in UTC.
- ///
- ///
- /// The data sent last time UTC.
- ///
- public DateTime DataSentLastTimeUtc { get; private set; }
-
- ///
- /// Gets how long has elapsed since data was last sent.
- ///
- ///
- /// The duration of the data sent idle.
- ///
- public TimeSpan DataSentIdleDuration => DateTime.UtcNow.Subtract(DataSentLastTimeUtc);
-
- ///
- /// Gets a value indicating whether this connection is connected.
- /// Remarks: This property polls the socket internally and checks if it is available to read data from it.
- /// If disconnect has been called, then this property will return false.
- ///
- ///
- /// true if this instance is connected; otherwise, false .
- ///
- public bool IsConnected
- {
- get
- {
- if (_disconnectCalls > 0)
- return false;
-
- try
- {
- var socket = RemoteClient.Client;
- var pollResult = !((socket.Poll(1000, SelectMode.SelectRead)
- && (NetworkStream.DataAvailable == false)) || !socket.Connected);
-
- if (pollResult == false)
- Disconnect();
-
- return pollResult;
- }
- catch
- {
- Disconnect();
- return false;
- }
- }
- }
-
- private NetworkStream NetworkStream { get; set; }
-
- private SslStream SecureStream { get; set; }
-
- #endregion
-
- #region Read Methods
-
- ///
- /// Reads data from the remote client asynchronously and with the given timeout.
- ///
- /// The timeout.
- /// The cancellation token.
- /// A byte array containing the results of encoding the specified set of characters.
- /// Read methods have been disabled because continuous reading is enabled.
- /// Reading data from {ActiveStream} timed out in {timeout.TotalMilliseconds} m.
- public async Task ReadDataAsync(TimeSpan timeout, CancellationToken cancellationToken = default)
- {
- if (IsContinuousReadingEnabled)
- {
- throw new InvalidOperationException(
- "Read methods have been disabled because continuous reading is enabled.");
- }
-
- if (RemoteClient == null)
- {
- throw new InvalidOperationException("An open connection is required");
- }
-
- var receiveBuffer = new byte[RemoteClient.ReceiveBufferSize * 2];
- var receiveBuilder = new List(receiveBuffer.Length);
-
- try
- {
- var startTime = DateTime.UtcNow;
-
- while (receiveBuilder.Count <= 0)
- {
- if (DateTime.UtcNow.Subtract(startTime) >= timeout)
- {
- throw new TimeoutException(
- $"Reading data from {ActiveStream} timed out in {timeout.TotalMilliseconds} ms");
- }
-
- if (_readTask == null)
- _readTask = ActiveStream.ReadAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken);
-
- if (_readTask.Wait(_continuousReadingInterval))
- {
- var bytesReceivedCount = _readTask.Result;
- if (bytesReceivedCount > 0)
- {
- DataReceivedLastTimeUtc = DateTime.UtcNow;
- var buffer = new byte[bytesReceivedCount];
- Array.Copy(receiveBuffer, 0, buffer, 0, bytesReceivedCount);
- receiveBuilder.AddRange(buffer);
- }
-
- _readTask = null;
- }
- else
- {
- await Task.Delay(_continuousReadingInterval, cancellationToken).ConfigureAwait(false);
- }
- }
- }
- catch (Exception ex)
- {
- ex.Error(typeof(Connection).FullName, "Error while reading network stream data asynchronously.");
- throw;
- }
-
- return receiveBuilder.ToArray();
- }
-
- ///
- /// Reads data asynchronously from the remote stream with a 5000 millisecond timeout.
- ///
- /// The cancellation token.
- ///
- /// A byte array containing the results the specified sequence of bytes.
- ///
- public Task ReadDataAsync(CancellationToken cancellationToken = default)
- => ReadDataAsync(TimeSpan.FromSeconds(5), cancellationToken);
-
- ///
- /// Asynchronously reads data as text with the given timeout.
- ///
- /// The timeout.
- /// The cancellation token.
- ///
- /// A that contains the results of decoding the specified sequence of bytes.
- ///
- public async Task ReadTextAsync(TimeSpan timeout, CancellationToken cancellationToken = default)
- {
- var buffer = await ReadDataAsync(timeout, cancellationToken).ConfigureAwait(false);
- return buffer == null ? null : TextEncoding.GetString(buffer);
- }
-
- ///
- /// Asynchronously reads data as text with a 5000 millisecond timeout.
- ///
- /// The cancellation token.
- ///
- /// When this method completes successfully, it returns the contents of the file as a text string.
- ///
- public Task ReadTextAsync(CancellationToken cancellationToken = default)
- => ReadTextAsync(TimeSpan.FromSeconds(5), cancellationToken);
-
- ///
- /// Performs the same task as this method's overload but it defaults to a read timeout of 30 seconds.
- ///
- /// The cancellation token.
- ///
- /// A task that represents the asynchronous read operation. The value of the TResult parameter
- /// contains the next line from the stream, or is null if all the characters have been read.
- ///
- public Task ReadLineAsync(CancellationToken cancellationToken = default)
- => ReadLineAsync(TimeSpan.FromSeconds(30), cancellationToken);
-
- ///
- /// Reads the next available line of text in queue. Return null when no text is read.
- /// This method differs from the rest of the read methods because it keeps an internal
- /// queue of lines that are read from the stream and only returns the one line next in the queue.
- /// It is only recommended to use this method when you are working with text-based protocols
- /// and the rest of the read methods are not called.
- ///
- /// The timeout.
- /// The cancellation token.
- /// A task with a string line from the queue.
- /// Read methods have been disabled because continuous reading is enabled.
- public async Task ReadLineAsync(TimeSpan timeout, CancellationToken cancellationToken = default)
- {
- if (IsContinuousReadingEnabled)
- {
- throw new InvalidOperationException(
- "Read methods have been disabled because continuous reading is enabled.");
- }
-
- if (_readLineBuffer.Count > 0)
- return _readLineBuffer.Dequeue();
-
- var builder = new StringBuilder();
-
- while (true)
- {
- var text = await ReadTextAsync(timeout, cancellationToken).ConfigureAwait(false);
-
- if (string.IsNullOrEmpty(text))
- break;
-
- builder.Append(text);
-
- if (!text.EndsWith(_newLineSequence)) continue;
-
- var lines = builder.ToString().TrimEnd(_newLineSequenceChars)
- .Split(_newLineSequenceLineSplitter, StringSplitOptions.None);
- foreach (var item in lines)
- _readLineBuffer.Enqueue(item);
-
- break;
- }
-
- return _readLineBuffer.Count > 0 ? _readLineBuffer.Dequeue() : null;
- }
-
- #endregion
-
- #region Write Methods
-
- ///
- /// Writes data asynchronously.
- ///
- /// The buffer.
- /// if set to true [force flush].
- /// The cancellation token.
- /// A task that represents the asynchronous write operation.
- public async Task WriteDataAsync(byte[] buffer, bool forceFlush, CancellationToken cancellationToken = default)
- {
- try
- {
- _writeDone.WaitOne();
- _writeDone.Reset();
- await ActiveStream.WriteAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
- if (forceFlush)
- await ActiveStream.FlushAsync(cancellationToken).ConfigureAwait(false);
-
- DataSentLastTimeUtc = DateTime.UtcNow;
- }
- finally
- {
- _writeDone.Set();
- }
- }
-
- ///
- /// Writes text asynchronously.
- ///
- /// The text.
- /// The cancellation token.
- /// A task that represents the asynchronous write operation.
- public Task WriteTextAsync(string text, CancellationToken cancellationToken = default)
- => WriteTextAsync(text, TextEncoding, cancellationToken);
-
- ///
- /// Writes text asynchronously.
- ///
- /// The text.
- /// The encoding.
- /// The cancellation token.
- /// A task that represents the asynchronous write operation.
- public Task WriteTextAsync(string text, Encoding encoding, CancellationToken cancellationToken = default)
- => WriteDataAsync(encoding.GetBytes(text), true, cancellationToken);
-
- ///
- /// Writes a line of text asynchronously.
- /// The new line sequence is added automatically at the end of the line.
- ///
- /// The line.
- /// The encoding.
- /// The cancellation token.
- /// A task that represents the asynchronous write operation.
- public Task WriteLineAsync(string line, Encoding encoding, CancellationToken cancellationToken = default)
- => WriteDataAsync(encoding.GetBytes($"{line}{_newLineSequence}"), true, cancellationToken);
-
- ///
- /// Writes a line of text asynchronously.
- /// The new line sequence is added automatically at the end of the line.
- ///
- /// The line.
- /// The cancellation token.
- /// A task that represents the asynchronous write operation.
- public Task WriteLineAsync(string line, CancellationToken cancellationToken = default)
- => WriteLineAsync(line, TextEncoding, cancellationToken);
-
- #endregion
-
- #region Socket Methods
-
- ///
- /// Upgrades the active stream to an SSL stream if this connection object is hosted in the server.
- ///
- /// The server certificate.
- /// true if the object is hosted in the server; otherwise, false .
- public async Task UpgradeToSecureAsServerAsync(X509Certificate2 serverCertificate)
- {
- if (IsActiveStreamSecure)
- return true;
-
- _writeDone.WaitOne();
-
- SslStream? secureStream = null;
-
- try
- {
- secureStream = new SslStream(NetworkStream, true);
- await secureStream.AuthenticateAsServerAsync(serverCertificate).ConfigureAwait(false);
- SecureStream = secureStream;
- return true;
- }
- catch (Exception ex)
- {
- ConnectionFailure(this, new ConnectionFailureEventArgs(ex));
- secureStream?.Dispose();
-
- return false;
- }
- }
-
- ///
- /// Upgrades the active stream to an SSL stream if this connection object is hosted in the client.
- ///
- /// The hostname.
- /// The callback.
- /// A tasks with true if the upgrade to SSL was successful; otherwise, false .
- public async Task UpgradeToSecureAsClientAsync(
- string? hostname = null,
- RemoteCertificateValidationCallback? callback = null)
- {
- if (IsActiveStreamSecure)
- return true;
-
- var secureStream = callback == null
- ? new SslStream(NetworkStream, true)
- : new SslStream(NetworkStream, true, callback);
-
- try
- {
- await secureStream.AuthenticateAsClientAsync(hostname ?? Network.HostName.ToLowerInvariant()).ConfigureAwait(false);
- SecureStream = secureStream;
- }
- catch (Exception ex)
- {
- secureStream.Dispose();
- ConnectionFailure(this, new ConnectionFailureEventArgs(ex));
- return false;
- }
-
- return true;
- }
-
- ///
- /// Disconnects this connection.
- ///
- public void Disconnect()
- {
- if (_disconnectCalls > 0)
- return;
-
- _disconnectCalls++;
- _writeDone.WaitOne();
-
- try
- {
- ClientDisconnected(this, EventArgs.Empty);
- }
- catch
- {
- // ignore
- }
-
- try
- {
-#if !NET461
- RemoteClient.Dispose();
- SecureStream?.Dispose();
- NetworkStream?.Dispose();
-#else
- RemoteClient.Close();
- SecureStream?.Close();
- NetworkStream?.Close();
-#endif
- }
- finally
- {
- NetworkStream = null;
- SecureStream = null;
- RemoteClient = null;
- _continuousReadingThread = null;
- }
- }
-
- #endregion
-
- #region Dispose
-
- ///
- public void Dispose()
- {
- if (_hasDisposed)
- return;
-
- // Release managed resources
- Disconnect();
- _continuousReadingThread = null;
- _writeDone.Dispose();
-
- _hasDisposed = true;
- }
-
- #endregion
-
- #region Continuous Read Methods
-
- private void RaiseReceiveBufferEvents(IEnumerable receivedData)
- {
- var moreAvailable = RemoteClient.Available > 0;
-
- foreach (var data in receivedData)
- {
- ProcessReceivedBlock(data, moreAvailable);
- }
-
- // Check if we are left with some more stuff to handle
- if (_receiveBufferPointer <= 0)
- return;
-
- // Extract the segments split by newline terminated bytes
- var sequences = _receiveBuffer.Skip(0).Take(_receiveBufferPointer).ToArray()
- .Split(0, _newLineSequenceBytes);
-
- // Something really wrong happened
- if (sequences.Count == 0)
- throw new InvalidOperationException("Split function failed! This is terribly wrong!");
-
- // We only have one sequence and it is not newline-terminated
- // we don't have to do anything.
- if (sequences.Count == 1 && sequences[0].EndsWith(_newLineSequenceBytes) == false)
- return;
-
- // Process the events for each sequence
- for (var i = 0; i < sequences.Count; i++)
- {
- var sequenceBytes = sequences[i];
- var isNewLineTerminated = sequences[i].EndsWith(_newLineSequenceBytes);
- var isLast = i == sequences.Count - 1;
-
- if (isNewLineTerminated)
- {
- var eventArgs = new ConnectionDataReceivedEventArgs(
- sequenceBytes,
- ConnectionDataReceivedTrigger.NewLineSequenceEncountered,
- isLast == false);
- DataReceived(this, eventArgs);
- }
-
- // Depending on the last segment determine what to do with the receive buffer
- if (!isLast) continue;
-
- if (isNewLineTerminated)
- {
- // Simply reset the buffer pointer if the last segment was also terminated
- _receiveBufferPointer = 0;
- }
- else
- {
- // If we have not received the termination sequence, then just shift the receive buffer to the left
- // and adjust the pointer
- Array.Copy(sequenceBytes, _receiveBuffer, sequenceBytes.Length);
- _receiveBufferPointer = sequenceBytes.Length;
- }
- }
- }
-
- private void ProcessReceivedBlock(byte data, bool moreAvailable)
- {
- _receiveBuffer[_receiveBufferPointer] = data;
- _receiveBufferPointer++;
-
- // Block size reached
- if (ProtocolBlockSize > 0 && _receiveBufferPointer >= ProtocolBlockSize)
- {
- SendBuffer(moreAvailable, ConnectionDataReceivedTrigger.BlockSizeReached);
- return;
- }
-
- // The receive buffer is full. Time to flush
- if (_receiveBufferPointer >= _receiveBuffer.Length)
- {
- SendBuffer(moreAvailable, ConnectionDataReceivedTrigger.BufferFull);
- }
- }
-
- private void SendBuffer(bool moreAvailable, ConnectionDataReceivedTrigger trigger)
- {
- var eventBuffer = new byte[_receiveBuffer.Length];
- Array.Copy(_receiveBuffer, eventBuffer, eventBuffer.Length);
-
- DataReceived(this,
- new ConnectionDataReceivedEventArgs(
- eventBuffer,
- trigger,
- moreAvailable));
- _receiveBufferPointer = 0;
- }
-
- private void PerformContinuousReading(object threadContext)
- {
- _continuousReadingThread = Thread.CurrentThread;
-
- // Check if the RemoteClient is still there
- if (RemoteClient == null) return;
-
- var receiveBuffer = new byte[RemoteClient.ReceiveBufferSize * 2];
-
- while (IsConnected && _disconnectCalls <= 0)
- {
- var doThreadSleep = false;
-
- try
- {
- if (_readTask == null)
- _readTask = ActiveStream.ReadAsync(receiveBuffer, 0, receiveBuffer.Length);
-
- if (_readTask.Wait(_continuousReadingInterval))
- {
- var bytesReceivedCount = _readTask.Result;
- if (bytesReceivedCount > 0)
- {
- DataReceivedLastTimeUtc = DateTime.UtcNow;
- var buffer = new byte[bytesReceivedCount];
- Array.Copy(receiveBuffer, 0, buffer, 0, bytesReceivedCount);
- RaiseReceiveBufferEvents(buffer);
- }
-
- _readTask = null;
- }
- else
- {
- doThreadSleep = _disconnectCalls <= 0;
- }
- }
- catch (Exception ex)
- {
- ex.Log(nameof(PerformContinuousReading), "Continuous Read operation errored");
- }
- finally
- {
- if (doThreadSleep)
- Thread.Sleep(_continuousReadingInterval);
- }
- }
- }
-
- #endregion
- }
+ /// The client.
+ /// The text encoding.
+ /// The new line sequence used for read and write operations.
+ /// if set to true [disable continuous reading].
+ /// Size of the block. -- set to 0 or less to disable.
+ public Connection(TcpClient client, Encoding textEncoding, String newLineSequence, Boolean disableContinuousReading, Int32 blockSize) {
+ // Setup basic properties
+ this.Id = Guid.NewGuid();
+ this.TextEncoding = textEncoding;
+
+ // Setup new line sequence
+ if(String.IsNullOrEmpty(newLineSequence)) {
+ throw new ArgumentException("Argument cannot be null", nameof(newLineSequence));
+ }
+
+ this._newLineSequence = newLineSequence;
+ this._newLineSequenceBytes = this.TextEncoding.GetBytes(this._newLineSequence);
+ this._newLineSequenceChars = this._newLineSequence.ToCharArray();
+ this._newLineSequenceLineSplitter = new[] { this._newLineSequence };
+
+ // Setup Connection timers
+ this.ConnectionStartTimeUtc = DateTime.UtcNow;
+ this.DataReceivedLastTimeUtc = this.ConnectionStartTimeUtc;
+ this.DataSentLastTimeUtc = this.ConnectionStartTimeUtc;
+
+ // Setup connection properties
+ this.RemoteClient = client;
+ this.LocalEndPoint = client.Client.LocalEndPoint as IPEndPoint;
+ this.NetworkStream = this.RemoteClient.GetStream();
+ this.RemoteEndPoint = this.RemoteClient.Client.RemoteEndPoint as IPEndPoint;
+
+ // Setup buffers
+ this._receiveBuffer = new Byte[this.RemoteClient.ReceiveBufferSize * 2];
+ this.ProtocolBlockSize = blockSize;
+ this._receiveBufferPointer = 0;
+
+ // Setup continuous reading mode if enabled
+ if(disableContinuousReading) {
+ return;
+ }
+
+ ThreadPool.GetAvailableThreads(out Int32 availableWorkerThreads, out _);
+ ThreadPool.GetMaxThreads(out Int32 maxWorkerThreads, out _);
+
+ Int32 activeThreadPoolTreads = maxWorkerThreads - availableWorkerThreads;
+
+ if(activeThreadPoolTreads < Environment.ProcessorCount / 4) {
+ _ = ThreadPool.QueueUserWorkItem(this.PerformContinuousReading!, this);
+ } else {
+ new Thread(this.PerformContinuousReading!) { IsBackground = true }.Start();
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class in continuous reading mode.
+ /// It uses UTF8 encoding, CRLF as a new line sequence and disables a protocol block size.
+ ///
+ /// The client.
+ public Connection(TcpClient client) : this(client, Encoding.UTF8, "\r\n", false, 0) {
+ // placeholder
+ }
+
+ ///
+ /// Initializes a new instance of the class in continuous reading mode.
+ /// It uses UTF8 encoding, disables line sequences, and uses a protocol block size instead.
+ ///
+ /// The client.
+ /// Size of the block.
+ public Connection(TcpClient client, Int32 blockSize) : this(client, Encoding.UTF8, new String('\n', blockSize + 1), false, blockSize) {
+ // placeholder
+ }
+
+ #region Events
+
+ ///
+ /// Occurs when the receive buffer has encounters a new line sequence, the buffer is flushed or the buffer is full.
+ ///
+ public event EventHandler DataReceived = (s, e) => { };
+
+ ///
+ /// Occurs when an error occurs while upgrading, sending, or receiving data in this client
+ ///
+ public event EventHandler ConnectionFailure = (s, e) => { };
+
+ ///
+ /// Occurs when a client is disconnected
+ ///
+ public event EventHandler ClientDisconnected = (s, e) => { };
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets the unique identifier of this connection.
+ /// This field is filled out upon instantiation of this class.
+ ///
+ ///
+ /// The identifier.
+ ///
+ public Guid Id {
+ get;
+ }
+
+ ///
+ /// Gets the active stream. Returns an SSL stream if the connection is secure, otherwise returns
+ /// the underlying NetworkStream.
+ ///
+ ///
+ /// The active stream.
+ ///
+ public Stream? ActiveStream => this.SecureStream ?? this.NetworkStream as Stream;
+
+ ///
+ /// Gets a value indicating whether the current connection stream is an SSL stream.
+ ///
+ ///
+ /// true if this instance is active stream secure; otherwise, false .
+ ///
+ public Boolean IsActiveStreamSecure => this.SecureStream != null;
+
+ ///
+ /// Gets the text encoding for send and receive operations.
+ ///
+ ///
+ /// The text encoding.
+ ///
+ public Encoding TextEncoding {
+ get;
+ }
+
+ ///
+ /// Gets the remote end point of this TCP connection.
+ ///
+ ///
+ /// The remote end point.
+ ///
+ public IPEndPoint? RemoteEndPoint {
+ get;
+ }
+
+ ///
+ /// Gets the local end point of this TCP connection.
+ ///
+ ///
+ /// The local end point.
+ ///
+ public IPEndPoint? LocalEndPoint {
+ get;
+ }
+
+ ///
+ /// Gets the remote client of this TCP connection.
+ ///
+ ///
+ /// The remote client.
+ ///
+ public TcpClient? RemoteClient {
+ get; private set;
+ }
+
+ ///
+ /// When in continuous reading mode, and if set to greater than 0,
+ /// a Data reception event will be fired whenever the amount of bytes
+ /// determined by this property has been received. Useful for fixed-length message protocols.
+ ///
+ ///
+ /// The size of the protocol block.
+ ///
+ public Int32 ProtocolBlockSize {
+ get;
+ }
+
+ ///
+ /// Gets a value indicating whether this connection is in continuous reading mode.
+ /// Remark: Whenever a disconnect event occurs, the background thread is terminated
+ /// and this property will return false whenever the reading thread is not active.
+ /// Therefore, even if continuous reading was not disabled in the constructor, this property
+ /// might return false.
+ ///
+ ///
+ /// true if this instance is continuous reading enabled; otherwise, false .
+ ///
+ public Boolean IsContinuousReadingEnabled => this._continuousReadingThread != null;
+
+ ///
+ /// Gets the start time at which the connection was started in UTC.
+ ///
+ ///
+ /// The connection start time UTC.
+ ///
+ public DateTime ConnectionStartTimeUtc {
+ get;
+ }
+
+ ///
+ /// Gets the start time at which the connection was started in local time.
+ ///
+ ///
+ /// The connection start time.
+ ///
+ public DateTime ConnectionStartTime => this.ConnectionStartTimeUtc.ToLocalTime();
+
+ ///
+ /// Gets the duration of the connection.
+ ///
+ ///
+ /// The duration of the connection.
+ ///
+ public TimeSpan ConnectionDuration => DateTime.UtcNow.Subtract(this.ConnectionStartTimeUtc);
+
+ ///
+ /// Gets the last time data was received at in UTC.
+ ///
+ ///
+ /// The data received last time UTC.
+ ///
+ public DateTime DataReceivedLastTimeUtc {
+ get; private set;
+ }
+
+ ///
+ /// Gets how long has elapsed since data was last received.
+ ///
+ public TimeSpan DataReceivedIdleDuration => DateTime.UtcNow.Subtract(this.DataReceivedLastTimeUtc);
+
+ ///
+ /// Gets the last time at which data was sent in UTC.
+ ///
+ ///
+ /// The data sent last time UTC.
+ ///
+ public DateTime DataSentLastTimeUtc {
+ get; private set;
+ }
+
+ ///
+ /// Gets how long has elapsed since data was last sent.
+ ///
+ ///
+ /// The duration of the data sent idle.
+ ///
+ public TimeSpan DataSentIdleDuration => DateTime.UtcNow.Subtract(this.DataSentLastTimeUtc);
+
+ ///
+ /// Gets a value indicating whether this connection is connected.
+ /// Remarks: This property polls the socket internally and checks if it is available to read data from it.
+ /// If disconnect has been called, then this property will return false.
+ ///
+ ///
+ /// true if this instance is connected; otherwise, false .
+ ///
+ public Boolean IsConnected {
+ get {
+ if(this._disconnectCalls > 0) {
+ return false;
+ }
+
+ try {
+ Socket? socket = this.RemoteClient?.Client;
+ if(socket == null || this.NetworkStream == null) {
+ return false;
+ }
+ Boolean pollResult = !(socket.Poll(1000, SelectMode.SelectRead) && this.NetworkStream.DataAvailable == false || !socket.Connected);
+
+ if(pollResult == false) {
+ this.Disconnect();
+ }
+
+ return pollResult;
+ } catch {
+ this.Disconnect();
+ return false;
+ }
+ }
+ }
+
+ private NetworkStream? NetworkStream {
+ get; set;
+ }
+
+ private SslStream? SecureStream {
+ get; set;
+ }
+
+ #endregion
+
+ #region Read Methods
+
+ ///
+ /// Reads data from the remote client asynchronously and with the given timeout.
+ ///
+ /// The timeout.
+ /// The cancellation token.
+ /// A byte array containing the results of encoding the specified set of characters.
+ /// Read methods have been disabled because continuous reading is enabled.
+ /// Reading data from {ActiveStream} timed out in {timeout.TotalMilliseconds} m.
+ public async Task ReadDataAsync(TimeSpan timeout, CancellationToken cancellationToken = default) {
+ if(this.IsContinuousReadingEnabled) {
+ throw new InvalidOperationException("Read methods have been disabled because continuous reading is enabled.");
+ }
+
+ if(this.RemoteClient == null) {
+ throw new InvalidOperationException("An open connection is required");
+ }
+
+ Byte[] receiveBuffer = new Byte[this.RemoteClient.ReceiveBufferSize * 2];
+ List receiveBuilder = new List(receiveBuffer.Length);
+
+ try {
+ DateTime startTime = DateTime.UtcNow;
+
+ while(receiveBuilder.Count <= 0) {
+ if(DateTime.UtcNow.Subtract(startTime) >= timeout) {
+ throw new TimeoutException($"Reading data from {this.ActiveStream} timed out in {timeout.TotalMilliseconds} ms");
+ }
+
+ if(this._readTask == null) {
+ this._readTask = this.ActiveStream?.ReadAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken);
+ }
+
+ if(this._readTask != null && this._readTask.Wait(this._continuousReadingInterval)) {
+ Int32 bytesReceivedCount = this._readTask.Result;
+ if(bytesReceivedCount > 0) {
+ this.DataReceivedLastTimeUtc = DateTime.UtcNow;
+ Byte[] buffer = new Byte[bytesReceivedCount];
+ Array.Copy(receiveBuffer, 0, buffer, 0, bytesReceivedCount);
+ receiveBuilder.AddRange(buffer);
+ }
+
+ this._readTask = null;
+ } else {
+ await Task.Delay(this._continuousReadingInterval, cancellationToken).ConfigureAwait(false);
+ }
+ }
+ } catch(Exception ex) {
+ ex.Error(typeof(Connection).FullName, "Error while reading network stream data asynchronously.");
+ throw;
+ }
+
+ return receiveBuilder.ToArray();
+ }
+
+ ///
+ /// Reads data asynchronously from the remote stream with a 5000 millisecond timeout.
+ ///
+ /// The cancellation token.
+ ///
+ /// A byte array containing the results the specified sequence of bytes.
+ ///
+ public Task ReadDataAsync(CancellationToken cancellationToken = default) => this.ReadDataAsync(TimeSpan.FromSeconds(5), cancellationToken);
+
+ ///
+ /// Asynchronously reads data as text with the given timeout.
+ ///
+ /// The timeout.
+ /// The cancellation token.
+ ///
+ /// A that contains the results of decoding the specified sequence of bytes.
+ ///
+ public async Task ReadTextAsync(TimeSpan timeout, CancellationToken cancellationToken = default) {
+ Byte[] buffer = await this.ReadDataAsync(timeout, cancellationToken).ConfigureAwait(false);
+ return buffer == null ? null : this.TextEncoding.GetString(buffer);
+ }
+
+ ///
+ /// Asynchronously reads data as text with a 5000 millisecond timeout.
+ ///
+ /// The cancellation token.
+ ///
+ /// When this method completes successfully, it returns the contents of the file as a text string.
+ ///
+ public Task ReadTextAsync(CancellationToken cancellationToken = default) => this.ReadTextAsync(TimeSpan.FromSeconds(5), cancellationToken);
+
+ ///
+ /// Performs the same task as this method's overload but it defaults to a read timeout of 30 seconds.
+ ///
+ /// The cancellation token.
+ ///
+ /// A task that represents the asynchronous read operation. The value of the TResult parameter
+ /// contains the next line from the stream, or is null if all the characters have been read.
+ ///
+ public Task ReadLineAsync(CancellationToken cancellationToken = default) => this.ReadLineAsync(TimeSpan.FromSeconds(30), cancellationToken);
+
+ ///
+ /// Reads the next available line of text in queue. Return null when no text is read.
+ /// This method differs from the rest of the read methods because it keeps an internal
+ /// queue of lines that are read from the stream and only returns the one line next in the queue.
+ /// It is only recommended to use this method when you are working with text-based protocols
+ /// and the rest of the read methods are not called.
+ ///
+ /// The timeout.
+ /// The cancellation token.
+ /// A task with a string line from the queue.
+ /// Read methods have been disabled because continuous reading is enabled.
+ public async Task ReadLineAsync(TimeSpan timeout, CancellationToken cancellationToken = default) {
+ if(this.IsContinuousReadingEnabled) {
+ throw new InvalidOperationException("Read methods have been disabled because continuous reading is enabled.");
+ }
+
+ if(this._readLineBuffer.Count > 0) {
+ return this._readLineBuffer.Dequeue();
+ }
+
+ StringBuilder builder = new StringBuilder();
+
+ while(true) {
+ String? text = await this.ReadTextAsync(timeout, cancellationToken).ConfigureAwait(false);
+
+ if(String.IsNullOrEmpty(text)) {
+ break;
+ }
+
+ _ = builder.Append(text);
+
+ if(!text.EndsWith(this._newLineSequence)) {
+ continue;
+ }
+
+ String[] lines = builder.ToString().TrimEnd(this._newLineSequenceChars).Split(this._newLineSequenceLineSplitter, StringSplitOptions.None);
+ foreach(String item in lines) {
+ this._readLineBuffer.Enqueue(item);
+ }
+
+ break;
+ }
+
+ return this._readLineBuffer.Count > 0 ? this._readLineBuffer.Dequeue() : null;
+ }
+
+ #endregion
+
+ #region Write Methods
+
+ ///
+ /// Writes data asynchronously.
+ ///
+ /// The buffer.
+ /// if set to true [force flush].
+ /// The cancellation token.
+ /// A task that represents the asynchronous write operation.
+ public async Task WriteDataAsync(Byte[] buffer, Boolean forceFlush, CancellationToken cancellationToken = default) {
+ try {
+ _ = this._writeDone.WaitOne();
+ _ = this._writeDone.Reset();
+ if(this.ActiveStream == null) {
+ return;
+ }
+ await this.ActiveStream.WriteAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
+ if(forceFlush) {
+ await this.ActiveStream.FlushAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ this.DataSentLastTimeUtc = DateTime.UtcNow;
+ } finally {
+ _ = this._writeDone.Set();
+ }
+ }
+
+ ///
+ /// Writes text asynchronously.
+ ///
+ /// The text.
+ /// The cancellation token.
+ /// A task that represents the asynchronous write operation.
+ public Task WriteTextAsync(String text, CancellationToken cancellationToken = default) => this.WriteTextAsync(text, this.TextEncoding, cancellationToken);
+
+ ///
+ /// Writes text asynchronously.
+ ///
+ /// The text.
+ /// The encoding.
+ /// The cancellation token.
+ /// A task that represents the asynchronous write operation.
+ public Task WriteTextAsync(String text, Encoding encoding, CancellationToken cancellationToken = default) => this.WriteDataAsync(encoding.GetBytes(text), true, cancellationToken);
+
+ ///
+ /// Writes a line of text asynchronously.
+ /// The new line sequence is added automatically at the end of the line.
+ ///
+ /// The line.
+ /// The encoding.
+ /// The cancellation token.
+ /// A task that represents the asynchronous write operation.
+ public Task WriteLineAsync(String line, Encoding encoding, CancellationToken cancellationToken = default) => this.WriteDataAsync(encoding.GetBytes($"{line}{this._newLineSequence}"), true, cancellationToken);
+
+ ///
+ /// Writes a line of text asynchronously.
+ /// The new line sequence is added automatically at the end of the line.
+ ///
+ /// The line.
+ /// The cancellation token.
+ /// A task that represents the asynchronous write operation.
+ public Task WriteLineAsync(String line, CancellationToken cancellationToken = default) => this.WriteLineAsync(line, this.TextEncoding, cancellationToken);
+
+ #endregion
+
+ #region Socket Methods
+
+ ///
+ /// Upgrades the active stream to an SSL stream if this connection object is hosted in the server.
+ ///
+ /// The server certificate.
+ /// true if the object is hosted in the server; otherwise, false .
+ public async Task UpgradeToSecureAsServerAsync(X509Certificate2 serverCertificate) {
+ if(this.IsActiveStreamSecure) {
+ return true;
+ }
+
+ _ = this._writeDone.WaitOne();
+
+ SslStream? secureStream = null;
+
+ try {
+ secureStream = new SslStream(this.NetworkStream, true);
+ await secureStream.AuthenticateAsServerAsync(serverCertificate).ConfigureAwait(false);
+ this.SecureStream = secureStream;
+ return true;
+ } catch(Exception ex) {
+ ConnectionFailure(this, new ConnectionFailureEventArgs(ex));
+ secureStream?.Dispose();
+
+ return false;
+ }
+ }
+
+ ///
+ /// Upgrades the active stream to an SSL stream if this connection object is hosted in the client.
+ ///
+ /// The hostname.
+ /// The callback.
+ /// A tasks with true if the upgrade to SSL was successful; otherwise, false .
+ public async Task UpgradeToSecureAsClientAsync(String? hostname = null, RemoteCertificateValidationCallback? callback = null) {
+ if(this.IsActiveStreamSecure) {
+ return true;
+ }
+
+ SslStream secureStream = callback == null ? new SslStream(this.NetworkStream, true) : new SslStream(this.NetworkStream, true, callback);
+
+ try {
+ await secureStream.AuthenticateAsClientAsync(hostname ?? Network.HostName.ToLowerInvariant()).ConfigureAwait(false);
+ this.SecureStream = secureStream;
+ } catch(Exception ex) {
+ secureStream.Dispose();
+ ConnectionFailure(this, new ConnectionFailureEventArgs(ex));
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Disconnects this connection.
+ ///
+ public void Disconnect() {
+ if(this._disconnectCalls > 0) {
+ return;
+ }
+
+ this._disconnectCalls++;
+ _ = this._writeDone.WaitOne();
+
+ try {
+ ClientDisconnected(this, EventArgs.Empty);
+ } catch {
+ // ignore
+ }
+
+ try {
+ this.RemoteClient?.Dispose();
+ this.SecureStream?.Dispose();
+ this.NetworkStream?.Dispose();
+
+ } finally {
+ this.NetworkStream = null;
+ this.SecureStream = null;
+ this.RemoteClient = null;
+ this._continuousReadingThread = null;
+ }
+ }
+
+ #endregion
+
+ #region Dispose
+
+ ///
+ public void Dispose() {
+ if(this._hasDisposed) {
+ return;
+ }
+
+ // Release managed resources
+ this.Disconnect();
+ this._continuousReadingThread = null;
+ this._writeDone.Dispose();
+
+ this._hasDisposed = true;
+ }
+
+ #endregion
+
+ #region Continuous Read Methods
+
+ private void RaiseReceiveBufferEvents(IEnumerable receivedData) {
+ if(this.RemoteClient == null) {
+ return;
+ }
+ Boolean moreAvailable = this.RemoteClient.Available > 0;
+
+ foreach(Byte data in receivedData) {
+ this.ProcessReceivedBlock(data, moreAvailable);
+ }
+
+ // Check if we are left with some more stuff to handle
+ if(this._receiveBufferPointer <= 0) {
+ return;
+ }
+
+ // Extract the segments split by newline terminated bytes
+ List sequences = this._receiveBuffer.Skip(0).Take(this._receiveBufferPointer).ToArray().Split(0, this._newLineSequenceBytes);
+
+ // Something really wrong happened
+ if(sequences.Count == 0) {
+ throw new InvalidOperationException("Split function failed! This is terribly wrong!");
+ }
+
+ // We only have one sequence and it is not newline-terminated
+ // we don't have to do anything.
+ if(sequences.Count == 1 && sequences[0].EndsWith(this._newLineSequenceBytes) == false) {
+ return;
+ }
+
+ // Process the events for each sequence
+ for(Int32 i = 0; i < sequences.Count; i++) {
+ Byte[] sequenceBytes = sequences[i];
+ Boolean isNewLineTerminated = sequences[i].EndsWith(this._newLineSequenceBytes);
+ Boolean isLast = i == sequences.Count - 1;
+
+ if(isNewLineTerminated) {
+ ConnectionDataReceivedEventArgs eventArgs = new ConnectionDataReceivedEventArgs(sequenceBytes, ConnectionDataReceivedTrigger.NewLineSequenceEncountered, isLast == false);
+ DataReceived(this, eventArgs);
+ }
+
+ // Depending on the last segment determine what to do with the receive buffer
+ if(!isLast) {
+ continue;
+ }
+
+ if(isNewLineTerminated) {
+ // Simply reset the buffer pointer if the last segment was also terminated
+ this._receiveBufferPointer = 0;
+ } else {
+ // If we have not received the termination sequence, then just shift the receive buffer to the left
+ // and adjust the pointer
+ Array.Copy(sequenceBytes, this._receiveBuffer, sequenceBytes.Length);
+ this._receiveBufferPointer = sequenceBytes.Length;
+ }
+ }
+ }
+
+ private void ProcessReceivedBlock(Byte data, Boolean moreAvailable) {
+ this._receiveBuffer[this._receiveBufferPointer] = data;
+ this._receiveBufferPointer++;
+
+ // Block size reached
+ if(this.ProtocolBlockSize > 0 && this._receiveBufferPointer >= this.ProtocolBlockSize) {
+ this.SendBuffer(moreAvailable, ConnectionDataReceivedTrigger.BlockSizeReached);
+ return;
+ }
+
+ // The receive buffer is full. Time to flush
+ if(this._receiveBufferPointer >= this._receiveBuffer.Length) {
+ this.SendBuffer(moreAvailable, ConnectionDataReceivedTrigger.BufferFull);
+ }
+ }
+
+ private void SendBuffer(Boolean moreAvailable, ConnectionDataReceivedTrigger trigger) {
+ Byte[] eventBuffer = new Byte[this._receiveBuffer.Length];
+ Array.Copy(this._receiveBuffer, eventBuffer, eventBuffer.Length);
+
+ DataReceived(this, new ConnectionDataReceivedEventArgs(eventBuffer, trigger, moreAvailable));
+ this._receiveBufferPointer = 0;
+ }
+
+ private void PerformContinuousReading(Object threadContext) {
+ this._continuousReadingThread = Thread.CurrentThread;
+
+ // Check if the RemoteClient is still there
+ if(this.RemoteClient == null) {
+ return;
+ }
+
+ Byte[] receiveBuffer = new Byte[this.RemoteClient.ReceiveBufferSize * 2];
+
+ while(this.IsConnected && this._disconnectCalls <= 0) {
+ Boolean doThreadSleep = false;
+
+ try {
+ if(this._readTask == null) {
+ this._readTask = this.ActiveStream?.ReadAsync(receiveBuffer, 0, receiveBuffer.Length);
+ }
+
+ if(this._readTask != null && this._readTask.Wait(this._continuousReadingInterval)) {
+ Int32 bytesReceivedCount = this._readTask.Result;
+ if(bytesReceivedCount > 0) {
+ this.DataReceivedLastTimeUtc = DateTime.UtcNow;
+ Byte[] buffer = new Byte[bytesReceivedCount];
+ Array.Copy(receiveBuffer, 0, buffer, 0, bytesReceivedCount);
+ this.RaiseReceiveBufferEvents(buffer);
+ }
+
+ this._readTask = null;
+ } else {
+ doThreadSleep = this._disconnectCalls <= 0;
+ }
+ } catch(Exception ex) {
+ ex.Log(nameof(PerformContinuousReading), "Continuous Read operation errored");
+ } finally {
+ if(doThreadSleep) {
+ Thread.Sleep(this._continuousReadingInterval);
+ }
+ }
+ }
+ }
+
+ #endregion
+ }
}
diff --git a/Swan/Net/ConnectionDataReceivedTrigger.cs b/Swan/Net/ConnectionDataReceivedTrigger.cs
index e6d592d..3d883fb 100644
--- a/Swan/Net/ConnectionDataReceivedTrigger.cs
+++ b/Swan/Net/ConnectionDataReceivedTrigger.cs
@@ -1,28 +1,26 @@
-namespace Swan
-{
+namespace Swan {
+ ///
+ /// Enumerates the possible causes of the DataReceived event occurring.
+ ///
+ public enum ConnectionDataReceivedTrigger {
///
- /// Enumerates the possible causes of the DataReceived event occurring.
+ /// The trigger was a forceful flush of the buffer
///
- public enum ConnectionDataReceivedTrigger
- {
- ///
- /// The trigger was a forceful flush of the buffer
- ///
- Flush,
-
- ///
- /// The new line sequence bytes were received
- ///
- NewLineSequenceEncountered,
-
- ///
- /// The buffer was full
- ///
- BufferFull,
-
- ///
- /// The block size reached
- ///
- BlockSizeReached,
- }
+ Flush,
+
+ ///
+ /// The new line sequence bytes were received
+ ///
+ NewLineSequenceEncountered,
+
+ ///
+ /// The buffer was full
+ ///
+ BufferFull,
+
+ ///
+ /// The block size reached
+ ///
+ BlockSizeReached,
+ }
}
diff --git a/Swan/Net/ConnectionListener.cs b/Swan/Net/ConnectionListener.cs
index 1c4b2bb..76138b3 100644
--- a/Swan/Net/ConnectionListener.cs
+++ b/Swan/Net/ConnectionListener.cs
@@ -1,253 +1,226 @@
-namespace Swan.Net
-{
- using System;
- using System.Net;
- using System.Net.Sockets;
- using System.Threading;
- using System.Threading.Tasks;
-
+#nullable enable
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Swan.Net {
+ ///
+ /// TCP Listener manager with built-in events and asynchronous functionality.
+ /// This networking component is typically used when writing server software.
+ ///
+ ///
+ public sealed class ConnectionListener : IDisposable {
+ private readonly Object _stateLock = new Object();
+ private TcpListener? _listenerSocket;
+ private Boolean _cancellationPending;
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0069:Verwerfbare Felder verwerfen", Justification = "")]
+ private CancellationTokenSource? _cancelListening;
+ private Task? _backgroundWorkerTask;
+ private Boolean _hasDisposed;
+
+ #region Events
+
///
- /// TCP Listener manager with built-in events and asynchronous functionality.
- /// This networking component is typically used when writing server software.
+ /// Occurs when a new connection requests a socket from the listener.
+ /// Set Cancel = true to prevent the TCP client from being accepted.
///
- ///
- public sealed class ConnectionListener : IDisposable
- {
- private readonly object _stateLock = new object();
- private TcpListener _listenerSocket;
- private bool _cancellationPending;
- private CancellationTokenSource _cancelListening;
- private Task? _backgroundWorkerTask;
- private bool _hasDisposed;
+ public event EventHandler OnConnectionAccepting = (s, e) => { };
+
+ ///
+ /// Occurs when a new connection is accepted.
+ ///
+ public event EventHandler OnConnectionAccepted = (s, e) => { };
+
+ ///
+ /// Occurs when a connection fails to get accepted
+ ///
+ public event EventHandler OnConnectionFailure = (s, e) => { };
+
+ ///
+ /// Occurs when the listener stops.
+ ///
+ public event EventHandler OnListenerStopped = (s, e) => { };
+
+ #endregion
+
+ #region Constructors
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The listen end point.
+ public ConnectionListener(IPEndPoint listenEndPoint) {
+ this.Id = Guid.NewGuid();
+ this.LocalEndPoint = listenEndPoint ?? throw new ArgumentNullException(nameof(listenEndPoint));
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ /// It uses the loopback address for listening.
+ ///
+ /// The listen port.
+ public ConnectionListener(Int32 listenPort) : this(new IPEndPoint(IPAddress.Loopback, listenPort)) {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The listen address.
+ /// The listen port.
+ public ConnectionListener(IPAddress listenAddress, Int32 listenPort) : this(new IPEndPoint(listenAddress, listenPort)) {
+ }
+
+ ///
+ /// Finalizes an instance of the class.
+ ///
+ ~ConnectionListener() {
+ this.Dispose(false);
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets the local end point on which we are listening.
+ ///
+ ///
+ /// The local end point.
+ ///
+ public IPEndPoint LocalEndPoint {
+ get;
+ }
+
+ ///
+ /// Gets a value indicating whether this listener is active.
+ ///
+ ///
+ /// true if this instance is listening; otherwise, false .
+ ///
+ public Boolean IsListening => this._backgroundWorkerTask != null;
+
+ ///
+ /// Gets a unique identifier that gets automatically assigned upon instantiation of this class.
+ ///
+ ///
+ /// The unique identifier.
+ ///
+ public Guid Id {
+ get;
+ }
+
+ #endregion
+
+ #region Start and Stop
+
+ ///
+ /// Starts the listener in an asynchronous, non-blocking fashion.
+ /// Subscribe to the events of this class to gain access to connected client sockets.
+ ///
+ /// Cancellation has already been requested. This listener is not reusable.
+ public void Start() {
+ lock(this._stateLock) {
+ if(this._backgroundWorkerTask != null) {
+ return;
+ }
+
+ if(this._cancellationPending) {
+ throw new InvalidOperationException("Cancellation has already been requested. This listener is not reusable.");
+ }
+
+ this._backgroundWorkerTask = this.DoWorkAsync();
+ }
+ }
+
+ ///
+ /// Stops the listener from receiving new connections.
+ /// This does not prevent the listener from .
+ ///
+ public void Stop() {
+ lock(this._stateLock) {
+ this._cancellationPending = true;
+ this._listenerSocket?.Stop();
+ this._cancelListening?.Cancel();
+ this._backgroundWorkerTask?.Wait();
+ this._backgroundWorkerTask = null;
+ this._cancellationPending = false;
+ }
+ }
+
+ ///
+ /// Returns a that represents this instance.
+ ///
+ ///
+ /// A that represents this instance.
+ ///
+ public override String ToString() => this.LocalEndPoint.ToString();
+
+ ///
+ public void Dispose() {
+ this.Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Releases unmanaged and - optionally - managed resources.
+ ///
+ /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
+ private void Dispose(Boolean disposing) {
+ if(this._hasDisposed) {
+ return;
+ }
+
+ if(disposing) {
+ // Release managed resources
+ this.Stop();
+ }
+
+ this._hasDisposed = true;
+ }
+
+ ///
+ /// Continuously checks for client connections until the Close method has been called.
+ ///
+ /// A task that represents the asynchronous connection operation.
+ private async Task DoWorkAsync() {
+ this._cancellationPending = false;
+ this._listenerSocket = new TcpListener(this.LocalEndPoint);
+ this._listenerSocket.Start();
+ this._cancelListening = new CancellationTokenSource();
+
+ try {
+ while(this._cancellationPending == false) {
+ try {
+ TcpClient client = await Task.Run(() => this._listenerSocket.AcceptTcpClientAsync(), this._cancelListening.Token).ConfigureAwait(false);
+ ConnectionAcceptingEventArgs acceptingArgs = new ConnectionAcceptingEventArgs(client);
+ OnConnectionAccepting(this, acceptingArgs);
+
+ if(acceptingArgs.Cancel) {
+ client.Dispose();
- #region Events
-
- ///
- /// Occurs when a new connection requests a socket from the listener.
- /// Set Cancel = true to prevent the TCP client from being accepted.
- ///
- public event EventHandler OnConnectionAccepting = (s, e) => { };
-
- ///
- /// Occurs when a new connection is accepted.
- ///
- public event EventHandler OnConnectionAccepted = (s, e) => { };
-
- ///
- /// Occurs when a connection fails to get accepted
- ///
- public event EventHandler OnConnectionFailure = (s, e) => { };
-
- ///
- /// Occurs when the listener stops.
- ///
- public event EventHandler OnListenerStopped = (s, e) => { };
-
- #endregion
-
- #region Constructors
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The listen end point.
- public ConnectionListener(IPEndPoint listenEndPoint)
- {
- Id = Guid.NewGuid();
- LocalEndPoint = listenEndPoint ?? throw new ArgumentNullException(nameof(listenEndPoint));
- }
-
- ///
- /// Initializes a new instance of the class.
- /// It uses the loopback address for listening.
- ///
- /// The listen port.
- public ConnectionListener(int listenPort)
- : this(new IPEndPoint(IPAddress.Loopback, listenPort))
- {
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The listen address.
- /// The listen port.
- public ConnectionListener(IPAddress listenAddress, int listenPort)
- : this(new IPEndPoint(listenAddress, listenPort))
- {
- }
-
- ///
- /// Finalizes an instance of the class.
- ///
- ~ConnectionListener()
- {
- Dispose(false);
- }
-
- #endregion
-
- #region Public Properties
-
- ///
- /// Gets the local end point on which we are listening.
- ///
- ///
- /// The local end point.
- ///
- public IPEndPoint LocalEndPoint { get; }
-
- ///
- /// Gets a value indicating whether this listener is active.
- ///
- ///
- /// true if this instance is listening; otherwise, false .
- ///
- public bool IsListening => _backgroundWorkerTask != null;
-
- ///
- /// Gets a unique identifier that gets automatically assigned upon instantiation of this class.
- ///
- ///
- /// The unique identifier.
- ///
- public Guid Id { get; }
-
- #endregion
-
- #region Start and Stop
-
- ///
- /// Starts the listener in an asynchronous, non-blocking fashion.
- /// Subscribe to the events of this class to gain access to connected client sockets.
- ///
- /// Cancellation has already been requested. This listener is not reusable.
- public void Start()
- {
- lock (_stateLock)
- {
- if (_backgroundWorkerTask != null)
- {
- return;
- }
-
- if (_cancellationPending)
- {
- throw new InvalidOperationException(
- "Cancellation has already been requested. This listener is not reusable.");
- }
-
- _backgroundWorkerTask = DoWorkAsync();
- }
- }
-
- ///
- /// Stops the listener from receiving new connections.
- /// This does not prevent the listener from .
- ///
- public void Stop()
- {
- lock (_stateLock)
- {
- _cancellationPending = true;
- _listenerSocket?.Stop();
- _cancelListening?.Cancel();
- _backgroundWorkerTask?.Wait();
- _backgroundWorkerTask = null;
- _cancellationPending = false;
- }
- }
-
- ///
- /// Returns a that represents this instance.
- ///
- ///
- /// A that represents this instance.
- ///
- public override string ToString() => LocalEndPoint.ToString();
-
- ///
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- ///
- /// Releases unmanaged and - optionally - managed resources.
- ///
- /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
- private void Dispose(bool disposing)
- {
- if (_hasDisposed)
- return;
-
- if (disposing)
- {
- // Release managed resources
- Stop();
- }
-
- _hasDisposed = true;
- }
-
- ///
- /// Continuously checks for client connections until the Close method has been called.
- ///
- /// A task that represents the asynchronous connection operation.
- private async Task DoWorkAsync()
- {
- _cancellationPending = false;
- _listenerSocket = new TcpListener(LocalEndPoint);
- _listenerSocket.Start();
- _cancelListening = new CancellationTokenSource();
-
- try
- {
- while (_cancellationPending == false)
- {
- try
- {
- var client = await Task.Run(() => _listenerSocket.AcceptTcpClientAsync(), _cancelListening.Token).ConfigureAwait(false);
- var acceptingArgs = new ConnectionAcceptingEventArgs(client);
- OnConnectionAccepting(this, acceptingArgs);
-
- if (acceptingArgs.Cancel)
- {
-#if !NET461
- client.Dispose();
-#else
- client.Close();
-#endif
- continue;
- }
-
- OnConnectionAccepted(this, new ConnectionAcceptedEventArgs(client));
- }
- catch (Exception ex)
- {
- OnConnectionFailure(this, new ConnectionFailureEventArgs(ex));
- }
- }
-
- OnListenerStopped(this, new ConnectionListenerStoppedEventArgs(LocalEndPoint));
- }
- catch (ObjectDisposedException)
- {
- OnListenerStopped(this, new ConnectionListenerStoppedEventArgs(LocalEndPoint));
- }
- catch (Exception ex)
- {
- OnListenerStopped(this,
- new ConnectionListenerStoppedEventArgs(LocalEndPoint, _cancellationPending ? null : ex));
- }
- finally
- {
- _backgroundWorkerTask = null;
- _cancellationPending = false;
- }
- }
-
- #endregion
- }
+ continue;
+ }
+
+ OnConnectionAccepted(this, new ConnectionAcceptedEventArgs(client));
+ } catch(Exception ex) {
+ OnConnectionFailure(this, new ConnectionFailureEventArgs(ex));
+ }
+ }
+
+ OnListenerStopped(this, new ConnectionListenerStoppedEventArgs(this.LocalEndPoint));
+ } catch(ObjectDisposedException) {
+ OnListenerStopped(this, new ConnectionListenerStoppedEventArgs(this.LocalEndPoint));
+ } catch(Exception ex) {
+ OnListenerStopped(this,
+ new ConnectionListenerStoppedEventArgs(this.LocalEndPoint, this._cancellationPending ? null : ex));
+ } finally {
+ this._backgroundWorkerTask = null;
+ this._cancellationPending = false;
+ }
+ }
+
+ #endregion
+ }
}
diff --git a/Swan/Net/Dns/DnsClient.Interfaces.cs b/Swan/Net/Dns/DnsClient.Interfaces.cs
index 1d301f1..d869e38 100644
--- a/Swan/Net/Dns/DnsClient.Interfaces.cs
+++ b/Swan/Net/Dns/DnsClient.Interfaces.cs
@@ -1,62 +1,96 @@
-namespace Swan.Net.Dns
-{
- using System;
- using System.Threading.Tasks;
- using System.Collections.Generic;
-
- ///
- /// DnsClient public interfaces.
- ///
- internal partial class DnsClient
- {
- public interface IDnsMessage
- {
- IList Questions { get; }
-
- int Size { get; }
- byte[] ToArray();
- }
-
- public interface IDnsMessageEntry
- {
- DnsDomain Name { get; }
- DnsRecordType Type { get; }
- DnsRecordClass Class { get; }
-
- int Size { get; }
- byte[] ToArray();
- }
-
- public interface IDnsResourceRecord : IDnsMessageEntry
- {
- TimeSpan TimeToLive { get; }
- int DataLength { get; }
- byte[] Data { get; }
- }
-
- public interface IDnsRequest : IDnsMessage
- {
- int Id { get; set; }
- DnsOperationCode OperationCode { get; set; }
- bool RecursionDesired { get; set; }
- }
-
- public interface IDnsResponse : IDnsMessage
- {
- int Id { get; set; }
- IList AnswerRecords { get; }
- IList AuthorityRecords { get; }
- IList AdditionalRecords { get; }
- bool IsRecursionAvailable { get; set; }
- bool IsAuthorativeServer { get; set; }
- bool IsTruncated { get; set; }
- DnsOperationCode OperationCode { get; set; }
- DnsResponseCode ResponseCode { get; set; }
- }
-
- public interface IDnsRequestResolver
- {
- Task Request(DnsClientRequest request);
- }
- }
+using System;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+
+namespace Swan.Net.Dns {
+ ///
+ /// DnsClient public interfaces.
+ ///
+ internal partial class DnsClient {
+ public interface IDnsMessage {
+ IList Questions {
+ get;
+ }
+
+ Int32 Size {
+ get;
+ }
+ Byte[] ToArray();
+ }
+
+ public interface IDnsMessageEntry {
+ DnsDomain Name {
+ get;
+ }
+ DnsRecordType Type {
+ get;
+ }
+ DnsRecordClass Class {
+ get;
+ }
+
+ Int32 Size {
+ get;
+ }
+ Byte[] ToArray();
+ }
+
+ public interface IDnsResourceRecord : IDnsMessageEntry {
+ TimeSpan TimeToLive {
+ get;
+ }
+ Int32 DataLength {
+ get;
+ }
+ Byte[] Data {
+ get;
+ }
+ }
+
+ public interface IDnsRequest : IDnsMessage {
+ Int32 Id {
+ get; set;
+ }
+ DnsOperationCode OperationCode {
+ get; set;
+ }
+ Boolean RecursionDesired {
+ get; set;
+ }
+ }
+
+ public interface IDnsResponse : IDnsMessage {
+ Int32 Id {
+ get; set;
+ }
+ IList AnswerRecords {
+ get;
+ }
+ IList AuthorityRecords {
+ get;
+ }
+ IList AdditionalRecords {
+ get;
+ }
+ Boolean IsRecursionAvailable {
+ get; set;
+ }
+ Boolean IsAuthorativeServer {
+ get; set;
+ }
+ Boolean IsTruncated {
+ get; set;
+ }
+ DnsOperationCode OperationCode {
+ get; set;
+ }
+ DnsResponseCode ResponseCode {
+ get; set;
+ }
+ }
+
+ public interface IDnsRequestResolver {
+ Task Request(DnsClientRequest request);
+ }
+ }
}
diff --git a/Swan/Net/Dns/DnsClient.Request.cs b/Swan/Net/Dns/DnsClient.Request.cs
index e962844..8b9f396 100644
--- a/Swan/Net/Dns/DnsClient.Request.cs
+++ b/Swan/Net/Dns/DnsClient.Request.cs
@@ -1,681 +1,558 @@
-namespace Swan.Net.Dns
-{
- using Formatters;
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Threading.Tasks;
- using System.Linq;
- using System.Net;
- using System.Net.Sockets;
- using System.Runtime.InteropServices;
- using System.Text;
-
- ///
- /// DnsClient Request inner class.
- ///
- internal partial class DnsClient
- {
- public class DnsClientRequest : IDnsRequest
- {
- private readonly IDnsRequestResolver _resolver;
- private readonly IDnsRequest _request;
-
- public DnsClientRequest(IPEndPoint dns, IDnsRequest? request = null, IDnsRequestResolver? resolver = null)
- {
- Dns = dns;
- _request = request == null ? new DnsRequest() : new DnsRequest(request);
- _resolver = resolver ?? new DnsUdpRequestResolver();
- }
-
- public int Id
- {
- get => _request.Id;
- set => _request.Id = value;
- }
-
- public DnsOperationCode OperationCode
- {
- get => _request.OperationCode;
- set => _request.OperationCode = value;
- }
-
- public bool RecursionDesired
- {
- get => _request.RecursionDesired;
- set => _request.RecursionDesired = value;
- }
-
- public IList Questions => _request.Questions;
-
- public int Size => _request.Size;
-
- public IPEndPoint Dns { get; set; }
-
- public byte[] ToArray() => _request.ToArray();
-
- public override string ToString() => _request.ToString();
-
- ///
- /// Resolves this request into a response using the provided DNS information. The given
- /// request strategy is used to retrieve the response.
- ///
- /// Throw if a malformed response is received from the server.
- /// Thrown if a IO error occurs.
- /// Thrown if a the reading or writing to the socket fails.
- /// The response received from server.
- public async Task Resolve()
- {
- try
- {
- var response = await _resolver.Request(this).ConfigureAwait(false);
-
- if (response.Id != Id)
- {
- throw new DnsQueryException(response, "Mismatching request/response IDs");
- }
-
- if (response.ResponseCode != DnsResponseCode.NoError)
- {
- throw new DnsQueryException(response);
- }
-
- return response;
- }
- catch (Exception e)
- {
- if (e is ArgumentException || e is SocketException)
- throw new DnsQueryException("Invalid response", e);
-
- throw;
- }
- }
- }
-
- public class DnsRequest : IDnsRequest
- {
- private static readonly Random Random = new Random();
-
- private DnsHeader header;
-
- public DnsRequest()
- {
- Questions = new List();
- header = new DnsHeader
- {
- OperationCode = DnsOperationCode.Query,
- Response = false,
- Id = Random.Next(ushort.MaxValue),
- };
- }
-
- public DnsRequest(IDnsRequest request)
- {
- header = new DnsHeader();
- Questions = new List(request.Questions);
-
- header.Response = false;
-
- Id = request.Id;
- OperationCode = request.OperationCode;
- RecursionDesired = request.RecursionDesired;
- }
-
- public IList Questions { get; }
-
- public int Size => header.Size + Questions.Sum(q => q.Size);
-
- public int Id
- {
- get => header.Id;
- set => header.Id = value;
- }
-
- public DnsOperationCode OperationCode
- {
- get => header.OperationCode;
- set => header.OperationCode = value;
- }
-
- public bool RecursionDesired
- {
- get => header.RecursionDesired;
- set => header.RecursionDesired = value;
- }
-
- public byte[] ToArray()
- {
- UpdateHeader();
- using var result = new MemoryStream(Size);
-
- return result
- .Append(header.ToArray())
- .Append(Questions.Select(q => q.ToArray()))
- .ToArray();
- }
-
- public override string ToString()
- {
- UpdateHeader();
-
- return Json.Serialize(this, true);
- }
-
- private void UpdateHeader()
- {
- header.QuestionCount = Questions.Count;
- }
- }
-
- public class DnsTcpRequestResolver : IDnsRequestResolver
- {
- public async Task Request(DnsClientRequest request)
- {
- var tcp = new TcpClient();
-
- try
- {
-#if !NET461
- await tcp.Client.ConnectAsync(request.Dns).ConfigureAwait(false);
-#else
- tcp.Client.Connect(request.Dns);
-#endif
- var stream = tcp.GetStream();
- var buffer = request.ToArray();
- var length = BitConverter.GetBytes((ushort)buffer.Length);
-
- if (BitConverter.IsLittleEndian)
- Array.Reverse(length);
-
- await stream.WriteAsync(length, 0, length.Length).ConfigureAwait(false);
- await stream.WriteAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
-
- buffer = new byte[2];
- await Read(stream, buffer).ConfigureAwait(false);
-
- if (BitConverter.IsLittleEndian)
- Array.Reverse(buffer);
-
- buffer = new byte[BitConverter.ToUInt16(buffer, 0)];
- await Read(stream, buffer).ConfigureAwait(false);
-
- var response = DnsResponse.FromArray(buffer);
-
- return new DnsClientResponse(request, response, buffer);
- }
- finally
- {
-#if NET461
- tcp.Close();
-#else
- tcp.Dispose();
-#endif
- }
- }
-
- private static async Task Read(Stream stream, byte[] buffer)
- {
- var length = buffer.Length;
- var offset = 0;
- int size;
-
- while (length > 0 && (size = await stream.ReadAsync(buffer, offset, length).ConfigureAwait(false)) > 0)
- {
- offset += size;
- length -= size;
- }
-
- if (length > 0)
- {
- throw new IOException("Unexpected end of stream");
- }
- }
- }
-
- public class DnsUdpRequestResolver : IDnsRequestResolver
- {
- private readonly IDnsRequestResolver _fallback;
-
- public DnsUdpRequestResolver(IDnsRequestResolver fallback)
- {
- _fallback = fallback;
- }
-
- public DnsUdpRequestResolver()
- {
- _fallback = new DnsNullRequestResolver();
- }
-
- public async Task Request(DnsClientRequest request)
- {
- var udp = new UdpClient();
- var dns = request.Dns;
-
- try
- {
- udp.Client.SendTimeout = 7000;
- udp.Client.ReceiveTimeout = 7000;
-#if !NET461
- await udp.Client.ConnectAsync(dns).ConfigureAwait(false);
-#else
- udp.Client.Connect(dns);
-#endif
-
- await udp.SendAsync(request.ToArray(), request.Size).ConfigureAwait(false);
-
- var bufferList = new List