Added prototype code for an inversion-of-control container and a progress tracking service; moved XmlHelper class from Nuclex.UserInterface to Nuclex.Support; Nuclex.Support now uses the .NET Framework's own AssemblyLoadEventArgs class if it is compiled for the full framework; fixed a bug in the Request class that would cause exceptions to not be reported if Join() was called on a Request<x> was casted to a plain Request
git-svn-id: file:///srv/devel/repo-conversion/nusu@138 d2e56fa2-650e-0410-a79f-9358c0239efd
This commit is contained in:
parent
d0fe47239e
commit
41dcfa34d0
15 changed files with 1030 additions and 6 deletions
18
Source/Services/Instancing.cs
Normal file
18
Source/Services/Instancing.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Nuclex.Support.Plugins;
|
||||
|
||||
namespace Nuclex.Support.Services {
|
||||
|
||||
/// <summary>Modes in which services can be instantiated</summary>
|
||||
public enum Instancing {
|
||||
/// <summary>There will only be one service in the whole process</summary>
|
||||
Singleton,
|
||||
/// <summary>Each thread will be assigned its own service</summary>
|
||||
InstancePerThread,
|
||||
/// <summary>A new service will be created each time it is queried for</summary>
|
||||
Factory
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.DependencyInjection
|
|
@ -0,0 +1,34 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Nuclex.Support.Tracking;
|
||||
|
||||
namespace Nuclex.Support.Services.ProgressTracking {
|
||||
|
||||
/// <summary>Reports the progress of tracked background processes</summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This service is intended for the consumer of progress reports. It will notify
|
||||
/// subscribers when background processes start, when progress is achieved and
|
||||
/// when they finish.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Usually, this interface, together with the IProgressTrackingService interface,
|
||||
/// is provided by a single service component that tracks the progress of
|
||||
/// transactions taking place asynchronously and reports it back this way.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
interface IProgressPublishingService {
|
||||
|
||||
/// <summary>Fired when the overall progress changes</summary>
|
||||
event EventHandler<ProgressReportEventArgs> ProgressChanged;
|
||||
|
||||
/// <summary>The overall progress of all tracked background processes</summary>
|
||||
float TotalProgress { get; }
|
||||
|
||||
/// <summary>Currently active background processes</summary>
|
||||
IEnumerable<ITrackedProcess> TrackedProcesses { get; }
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.DependencyInjection.ProgressTracking
|
68
Source/Services/ProgressTracking/IProgressTrackingService.cs
Normal file
68
Source/Services/ProgressTracking/IProgressTrackingService.cs
Normal file
|
@ -0,0 +1,68 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Nuclex.Support.Tracking;
|
||||
|
||||
namespace Nuclex.Support.Services.ProgressTracking {
|
||||
|
||||
/// <summary>Allows application-wide tracking of progress</summary>
|
||||
interface IProgressTrackingService {
|
||||
|
||||
/// <summary>Tracks the progress of the specified transaction</summary>
|
||||
/// <param name="transaction">
|
||||
/// Transaction whose progress will be tracked
|
||||
/// </param>
|
||||
/// <param name="processIdentifier">
|
||||
/// Identifier unique to the tracked background process. Can be null.
|
||||
/// </param>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Using the process identifier allows you to track the progress of multiple
|
||||
/// transactions that are working independently of each other. This could,
|
||||
/// for example, result in multiple progress bars being displayed in a GUI.
|
||||
/// The global progress always is a combination of all transactions tracked
|
||||
/// by the service.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// A good analogy for this might be a download manager which uses several
|
||||
/// threads to download multiple sections of a file at the same time. You
|
||||
/// want a progress bar per file, but not one for each downloading thread.
|
||||
/// Specifying the name object as a process identifer, all transactions
|
||||
/// belonging to the same file would be merged into a single progress source.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The process identifier can be a string or any object. Note however that
|
||||
/// as common practice, this object's ToString() method should return
|
||||
/// something reasonable, like "Downloading xy.txt". Localization can be
|
||||
/// achieved by implementing an interface (eg. ILocalizableToString) which
|
||||
/// provides a string and some parameters - or you could return the already
|
||||
/// translated versions of the string if you prefer to have localized versions
|
||||
/// of internal assemblies.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
void Track(Transaction transaction, object processIdentifier);
|
||||
|
||||
/// <summary>Tracks the progress of the specified transaction</summary>
|
||||
/// <param name="transaction">
|
||||
/// Transaction whose progress will be tracked
|
||||
/// </param>
|
||||
/// <remarks>
|
||||
/// Tracks the transaction as if it was added with the process identifier
|
||||
/// set to null.
|
||||
/// </remarks>
|
||||
void Track(Transaction transaction);
|
||||
|
||||
/// <summary>Stops tracking the specified transaction</summary>
|
||||
/// <param name="transaction">Transaction that will no longer be tracked</param>
|
||||
/// <remarks>
|
||||
/// Untracking a transaction is optional. The service will automatically
|
||||
/// remove finished transactions from its list of tracked transactions. Calling
|
||||
/// this method is only required if you drop a transaction in a way that
|
||||
/// prevents its AsyncEnded event from being fired (eg. by not executing it
|
||||
/// at all, dispite adding it to the progress tracking service).
|
||||
/// </remarks>
|
||||
void Untrack(Transaction transaction);
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.DependencyInjection.ProgressTracking
|
29
Source/Services/ProgressTracking/ITrackedProcess.cs
Normal file
29
Source/Services/ProgressTracking/ITrackedProcess.cs
Normal file
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Nuclex.Support.Tracking;
|
||||
|
||||
namespace Nuclex.Support.Services.ProgressTracking {
|
||||
|
||||
/// <summary>Process whose progress is being tracked</summary>
|
||||
public interface ITrackedProcess {
|
||||
|
||||
/// <summary>Fired whenever the progress of the process changes</summary>
|
||||
event EventHandler<ProgressReportEventArgs> ProgressChanged;
|
||||
|
||||
/// <summary>Unique identifier of the overall process</summary>
|
||||
/// <remarks>
|
||||
/// As a convention, using this object's ToString() method should return
|
||||
/// something usable, either a string that can be displayed in the user
|
||||
/// interface or, depending on your architecture, the object could
|
||||
/// implement certain interfaces that allow a localized version of
|
||||
/// the string to be created.
|
||||
/// </remarks>
|
||||
object ProcessIdentifier { get; }
|
||||
|
||||
/// <summary>Progress that process has achieved so far</summary>
|
||||
float CurrentProgress { get; }
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.DependencyInjection.ProgressTracking
|
|
@ -0,0 +1,56 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Nuclex.Support.Tracking;
|
||||
|
||||
namespace Nuclex.Support.Services.ProgressTracking {
|
||||
|
||||
#if false
|
||||
/// <summary>Tracks the progress of running background processes</summary>
|
||||
public class ProgressTrackingComponent :
|
||||
IProgressTrackingService,
|
||||
IProgressPublishingService {
|
||||
|
||||
/// <summary>Fired when the overall progress changes</summary>
|
||||
public event EventHandler<ProgressReportEventArgs> ProgressChanged;
|
||||
|
||||
/// <summary>Initializes a new progress tracking component</summary>
|
||||
public ProgressTrackingComponent() {
|
||||
}
|
||||
|
||||
/// <summary>Tracks the progress of the specified transaction</summary>
|
||||
/// <param name="transaction">
|
||||
/// Transaction whose progress will be tracked
|
||||
/// </param>
|
||||
/// <param name="processIdentifier">
|
||||
/// Identifier unique to the tracked background process. Can be null.
|
||||
/// </param>
|
||||
public void Track(Transaction transaction, object processIdentifier) {
|
||||
}
|
||||
|
||||
/// <summary>Tracks the progress of the specified transaction</summary>
|
||||
/// <param name="transaction">
|
||||
/// Transaction whose progress will be tracked
|
||||
/// </param>
|
||||
public void Track(Transaction transaction) {
|
||||
}
|
||||
|
||||
/// <summary>Stops tracking the specified transaction</summary>
|
||||
/// <param name="transaction">Transaction that will no longer be tracked</param>
|
||||
public void Untrack(Transaction transaction) {
|
||||
}
|
||||
|
||||
/// <summary>The overall progress of all tracked background processes</summary>
|
||||
public float TotalProgress {
|
||||
get { return 0.0f; }
|
||||
}
|
||||
|
||||
/// <summary>Currently active background processes</summary>
|
||||
public IEnumerable<ITrackedProcess> TrackedProcesses {
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace Nuclex.Support.Services.ProgressTracking
|
96
Source/Services/ServiceManager.For.cs
Normal file
96
Source/Services/ServiceManager.For.cs
Normal file
|
@ -0,0 +1,96 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Nuclex.Support.Services {
|
||||
|
||||
#if false
|
||||
partial class ServiceManager {
|
||||
|
||||
#region class ForContext
|
||||
|
||||
// TODO: Rename to "On" to avoid confusion with concept of for loop?
|
||||
/// <summary>Manages the context of the "For" modifier</summary>
|
||||
public class ForContext {
|
||||
|
||||
/// <summary>Initializes a new "For" context of the service manager</summary>
|
||||
/// <param name="serviceManager">Service manager the context operates on</param>
|
||||
/// <param name="contractType">Contract that is being modified</param>
|
||||
internal ForContext(ServiceManager serviceManager, Type contractType) {
|
||||
this.serviceManager = serviceManager;
|
||||
this.contractType = contractType;
|
||||
}
|
||||
|
||||
/// <summary>Uses the specified implementation for the contract</summary>
|
||||
/// <param name="contractImplementation">
|
||||
/// Implementation that will be used for the contract
|
||||
/// </param>
|
||||
public void Use(object contractImplementation) { }
|
||||
|
||||
/// <summary>
|
||||
/// Uses the provided object as a prototype for the contract implementation
|
||||
/// </summary>
|
||||
/// <param name="contractImplementationPrototype">
|
||||
/// Contract implementation that will be used as a prototype
|
||||
/// </param>
|
||||
public void UsePrototype(object contractImplementationPrototype) { }
|
||||
|
||||
/// <summary>Selects the default implementation to use for the contract</summary>
|
||||
/// <param name="implementationType">
|
||||
/// Implementation that will be used as the default for the contract
|
||||
/// </param>
|
||||
public void UseDefault(Type implementationType) { }
|
||||
|
||||
/// <summary>Service manager the "For" context operates on</summary>
|
||||
protected ServiceManager serviceManager;
|
||||
/// <summary>Contract that is being modified</summary>
|
||||
protected Type contractType;
|
||||
|
||||
}
|
||||
|
||||
#endregion // class ForContext
|
||||
|
||||
#region class ForContext<>
|
||||
|
||||
// TODO: Rename to "On" to avoid confusion with concept of for loop?
|
||||
/// <summary>Manages the context of the "For" modifier</summary>
|
||||
public class ForContext<ContractType> : ForContext {
|
||||
|
||||
/// <summary>Initializes a new "For" context of the service manager</summary>
|
||||
/// <param name="serviceManager">Service manager the context operates on</param>
|
||||
internal ForContext(ServiceManager serviceManager) :
|
||||
base(serviceManager, typeof(ContractType)) { }
|
||||
|
||||
/// <summary>Uses the specified implementation for the contract</summary>
|
||||
/// <param name="implementation">
|
||||
/// Implementation that will be used for the contract
|
||||
/// </param>
|
||||
public void Use(ContractType implementation) { }
|
||||
|
||||
/// <summary>
|
||||
/// Uses the provided object as a prototype for the contract implementation
|
||||
/// </summary>
|
||||
/// <typeparam name="PrototypeType">
|
||||
/// Type of the implementation that will be used as a prototype
|
||||
/// </typeparam>
|
||||
/// <param name="contractImplementationPrototype">
|
||||
/// Contract implementation that will be used as a prototype
|
||||
/// </param>
|
||||
public void UsePrototype<PrototypeType>(PrototypeType contractImplementationPrototype)
|
||||
where PrototypeType : ContractType, ICloneable { }
|
||||
|
||||
/// <summary>Selects the default implementation to use for the contract</summary>
|
||||
/// <typeparam name="ImplementationType">
|
||||
/// Implementation that will be used as the default for the contract
|
||||
/// </typeparam>
|
||||
public void UseDefault<ImplementationType>()
|
||||
where ImplementationType : ContractType { }
|
||||
|
||||
}
|
||||
|
||||
#endregion // class ForContext<>
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace Nuclex.Support.DependencyInjection
|
156
Source/Services/ServiceManager.cs
Normal file
156
Source/Services/ServiceManager.cs
Normal file
|
@ -0,0 +1,156 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
using Nuclex.Support.Plugins;
|
||||
|
||||
namespace Nuclex.Support.Services {
|
||||
|
||||
#if false
|
||||
/// <summary>
|
||||
/// Inversion of Control container that manages the services of an application
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This is a very lightweight and simple inversion of control container that
|
||||
/// relieves components of their duty to track down implementations for the services
|
||||
/// they require to function. It will help with lazy initialization and prevent
|
||||
/// components from becoming cross-linked balls of spaghetti references.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Here's a short list of the terms used throughout this container and their
|
||||
/// specific meaning in this context.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <term>Service</term>
|
||||
/// <description>
|
||||
/// Defined by an interface (service contract) and provided by a component
|
||||
/// that implements the service contract. A service provides some kind of
|
||||
/// utility to the application, for example it could provide access to
|
||||
/// a data base or allow other components to control certain aspects of
|
||||
/// the application.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>Contract</term>
|
||||
/// <description>
|
||||
/// Interface defining the behavior that a service implementation has to
|
||||
/// follow. In order for a component to become a suitable candidate for
|
||||
/// providing a specific service, it has to implement the service contract
|
||||
/// interface and should rigorously follow its specifications.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>Component</term>
|
||||
/// <description>
|
||||
/// A component is simply a class that implements one or more service
|
||||
/// contracts. The service manager will created instances of these classes
|
||||
/// when all their dependencies can be provided for and an implementation
|
||||
/// of their service contract is requested.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public partial class ServiceManager : IServiceProvider {
|
||||
|
||||
/// <summary>Initializes a new service manager</summary>
|
||||
public ServiceManager() {
|
||||
this.pluginRepository = new PluginRepository();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all available implementations for the specified contract
|
||||
/// </summary>
|
||||
/// <param name="completeOnly">
|
||||
/// If true, only services whose dependencies can be completely
|
||||
/// satisfied by the container are returned.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A new enumerator for the available contract implementations
|
||||
/// </returns>
|
||||
IEnumerable<ContractType> GetImplementations<ContractType>(bool completeOnly)
|
||||
where ContractType : class {
|
||||
Type contractType = typeof(ContractType);
|
||||
|
||||
Assembly[] loadedAssemblies = this.pluginRepository.LoadedAssemblies.ToArray();
|
||||
for(int index = 0; index < loadedAssemblies.Length; ++index) {
|
||||
Type[] assemblyTypes = loadedAssemblies[index].GetTypes();
|
||||
|
||||
for(int typeIndex = 0; typeIndex < assemblyTypes.Length; ++typeIndex) {
|
||||
Type checkedType = assemblyTypes[typeIndex];
|
||||
if(contractType.IsAssignableFrom(checkedType)) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
private struct CachedContractLookUp {
|
||||
public Type[] ValidComponents;
|
||||
public int Version;
|
||||
}
|
||||
private Dictionary<Type, CachedContractLookUp> cachedContracts;
|
||||
private int version;
|
||||
|
||||
/// <summary>
|
||||
/// Allows the adjustment of the container's behavior in regard to
|
||||
/// the specified contract
|
||||
/// </summary>
|
||||
/// <typeparam name="ContractType">
|
||||
/// Contract for which the behavior will be adjusted
|
||||
/// </typeparam>
|
||||
/// <returns>
|
||||
/// A context object through which the behavior of the container can be
|
||||
/// adjusted for the specified type
|
||||
/// </returns>
|
||||
public ForContext<ContractType> For<ContractType>() where ContractType : class {
|
||||
return new ForContext<ContractType>(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows the adjustment of the container's behavior in regard to
|
||||
/// the specified contract
|
||||
/// </summary>
|
||||
/// <param name="contractType">
|
||||
/// Contract for which the behavior will be adjusted
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A context object through which the behavior of the container can be
|
||||
/// adjusted for the specified type
|
||||
/// </returns>
|
||||
public ForContext For(Type contractType) {
|
||||
return new ForContext(this, contractType);
|
||||
}
|
||||
|
||||
// Allow Dependency on Container
|
||||
// public Foo(IServiceProvider serviceProvider)
|
||||
// public Foo(IserviceLocator serviceLocator)
|
||||
// public Foo(Container container)
|
||||
|
||||
public ContractType GetService<ContractType>() where ContractType : class {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>Retrieves the service of the specified type</summary>
|
||||
/// <param name="contractType">
|
||||
/// Contract for which the service will be retrieved
|
||||
/// </param>
|
||||
/// <returns>The service for the specified contract</returns>
|
||||
object IServiceProvider.GetService(Type contractType) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains all assemblies partaking in the dependency injection scheme
|
||||
/// </summary>
|
||||
private PluginRepository pluginRepository;
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace Nuclex.Support.DependencyInjection
|
Loading…
Add table
Add a link
Reference in a new issue