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
|
@ -1,4 +1,4 @@
|
||||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
|
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<ProjectGuid>{DFFEAB70-51B8-4714-BCA6-79B733BBC520}</ProjectGuid>
|
<ProjectGuid>{DFFEAB70-51B8-4714-BCA6-79B733BBC520}</ProjectGuid>
|
||||||
<ProjectTypeGuids>{2DF5C3F4-5A5F-47a9-8E94-23B4456F55E2};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
<ProjectTypeGuids>{2DF5C3F4-5A5F-47a9-8E94-23B4456F55E2};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||||
|
@ -118,6 +118,15 @@
|
||||||
<Compile Include="Source\Collections\ReverseComparer.Test.cs">
|
<Compile Include="Source\Collections\ReverseComparer.Test.cs">
|
||||||
<DependentUpon>ReverseComparer.cs</DependentUpon>
|
<DependentUpon>ReverseComparer.cs</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="Source\Services\Instancing.cs" />
|
||||||
|
<Compile Include="Source\Services\ProgressTracking\IProgressPublishingService.cs" />
|
||||||
|
<Compile Include="Source\Services\ProgressTracking\IProgressTrackingService.cs" />
|
||||||
|
<Compile Include="Source\Services\ProgressTracking\ITrackedProcess.cs" />
|
||||||
|
<Compile Include="Source\Services\ProgressTracking\ProgressTrackingComponent.cs" />
|
||||||
|
<Compile Include="Source\Services\ServiceManager.cs" />
|
||||||
|
<Compile Include="Source\Services\ServiceManager.For.cs">
|
||||||
|
<DependentUpon>ServiceManager.cs</DependentUpon>
|
||||||
|
</Compile>
|
||||||
<Compile Include="Source\IO\PartialStream.cs" />
|
<Compile Include="Source\IO\PartialStream.cs" />
|
||||||
<Compile Include="Source\IO\PartialStream.Test.cs">
|
<Compile Include="Source\IO\PartialStream.Test.cs">
|
||||||
<DependentUpon>PartialStream.cs</DependentUpon>
|
<DependentUpon>PartialStream.cs</DependentUpon>
|
||||||
|
@ -286,6 +295,10 @@
|
||||||
<Compile Include="Source\WeakReference.Test.cs">
|
<Compile Include="Source\WeakReference.Test.cs">
|
||||||
<DependentUpon>WeakReference.cs</DependentUpon>
|
<DependentUpon>WeakReference.cs</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="Source\XmlHelper.cs" />
|
||||||
|
<Compile Include="Source\XmlHelper.Test.cs">
|
||||||
|
<DependentUpon>XmlHelper.cs</DependentUpon>
|
||||||
|
</Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="Documents\CommandLine.txt" />
|
<Content Include="Documents\CommandLine.txt" />
|
||||||
|
|
|
@ -100,6 +100,15 @@
|
||||||
<Compile Include="Source\Collections\ReverseComparer.Test.cs">
|
<Compile Include="Source\Collections\ReverseComparer.Test.cs">
|
||||||
<DependentUpon>ReverseComparer.cs</DependentUpon>
|
<DependentUpon>ReverseComparer.cs</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="Source\Services\Instancing.cs" />
|
||||||
|
<Compile Include="Source\Services\ProgressTracking\IProgressPublishingService.cs" />
|
||||||
|
<Compile Include="Source\Services\ProgressTracking\IProgressTrackingService.cs" />
|
||||||
|
<Compile Include="Source\Services\ProgressTracking\ITrackedProcess.cs" />
|
||||||
|
<Compile Include="Source\Services\ProgressTracking\ProgressTrackingComponent.cs" />
|
||||||
|
<Compile Include="Source\Services\ServiceManager.cs" />
|
||||||
|
<Compile Include="Source\Services\ServiceManager.For.cs">
|
||||||
|
<DependentUpon>ServiceManager.cs</DependentUpon>
|
||||||
|
</Compile>
|
||||||
<Compile Include="Source\IO\PartialStream.cs" />
|
<Compile Include="Source\IO\PartialStream.cs" />
|
||||||
<Compile Include="Source\IO\PartialStream.Test.cs">
|
<Compile Include="Source\IO\PartialStream.Test.cs">
|
||||||
<DependentUpon>PartialStream.cs</DependentUpon>
|
<DependentUpon>PartialStream.cs</DependentUpon>
|
||||||
|
@ -268,6 +277,10 @@
|
||||||
<Compile Include="Source\WeakReference.Test.cs">
|
<Compile Include="Source\WeakReference.Test.cs">
|
||||||
<DependentUpon>WeakReference.cs</DependentUpon>
|
<DependentUpon>WeakReference.cs</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="Source\XmlHelper.cs" />
|
||||||
|
<Compile Include="Source\XmlHelper.Test.cs">
|
||||||
|
<DependentUpon>XmlHelper.cs</DependentUpon>
|
||||||
|
</Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="Documents\CommandLine.txt" />
|
<Content Include="Documents\CommandLine.txt" />
|
||||||
|
|
|
@ -28,6 +28,8 @@ using NUnit.Framework;
|
||||||
|
|
||||||
namespace Nuclex.Support.Plugins {
|
namespace Nuclex.Support.Plugins {
|
||||||
|
|
||||||
|
#if XBOX360
|
||||||
|
|
||||||
/// <summary>Unit Test for the assembly load event argument container</summary>
|
/// <summary>Unit Test for the assembly load event argument container</summary>
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class AssemblyLoadEventArgsTest {
|
public class AssemblyLoadEventArgsTest {
|
||||||
|
@ -46,6 +48,8 @@ namespace Nuclex.Support.Plugins {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif // XBOX360
|
||||||
|
|
||||||
} // namespace Nuclex.Support.Plugins
|
} // namespace Nuclex.Support.Plugins
|
||||||
|
|
||||||
#endif // UNITTEST
|
#endif // UNITTEST
|
||||||
|
|
|
@ -24,6 +24,8 @@ using System.Reflection;
|
||||||
|
|
||||||
namespace Nuclex.Support.Plugins {
|
namespace Nuclex.Support.Plugins {
|
||||||
|
|
||||||
|
#if XBOX360
|
||||||
|
|
||||||
/// <summary>Signature for the AssemblyLoad event</summary>
|
/// <summary>Signature for the AssemblyLoad event</summary>
|
||||||
/// <param name="sender">Object that is reporting that an assembly was loaded</param>
|
/// <param name="sender">Object that is reporting that an assembly was loaded</param>
|
||||||
/// <param name="arguments">Contains the loaded assembly</param>
|
/// <param name="arguments">Contains the loaded assembly</param>
|
||||||
|
@ -50,4 +52,6 @@ namespace Nuclex.Support.Plugins {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif // XBOX360
|
||||||
|
|
||||||
} // namespace Nuclex.Support.Plugins
|
} // namespace Nuclex.Support.Plugins
|
||||||
|
|
|
@ -37,8 +37,8 @@ namespace Nuclex.Support.Plugins {
|
||||||
|
|
||||||
/// <summary>Initializes a plugin host using a new repository</summary>
|
/// <summary>Initializes a plugin host using a new repository</summary>
|
||||||
/// <param name="employer">Employer used assess and employ the plugin types</param>
|
/// <param name="employer">Employer used assess and employ the plugin types</param>
|
||||||
public PluginHost(Employer employer)
|
public PluginHost(Employer employer) :
|
||||||
: this(employer, new PluginRepository()) { }
|
this(employer, new PluginRepository()) { }
|
||||||
|
|
||||||
/// <summary>Initializes the plugin using an existing repository</summary>
|
/// <summary>Initializes the plugin using an existing repository</summary>
|
||||||
/// <param name="employer">Employer used assess and employ the plugin types</param>
|
/// <param name="employer">Employer used assess and employ the plugin types</param>
|
||||||
|
@ -47,8 +47,9 @@ namespace Nuclex.Support.Plugins {
|
||||||
this.employer = employer;
|
this.employer = employer;
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
|
|
||||||
foreach(Assembly assembly in this.repository.LoadedAssemblies)
|
foreach(Assembly assembly in this.repository.LoadedAssemblies) {
|
||||||
employAssemblyTypes(assembly);
|
employAssemblyTypes(assembly);
|
||||||
|
}
|
||||||
|
|
||||||
this.repository.AssemblyLoaded += new AssemblyLoadEventHandler(assemblyLoadHandler);
|
this.repository.AssemblyLoaded += new AssemblyLoadEventHandler(assemblyLoadHandler);
|
||||||
}
|
}
|
||||||
|
@ -75,7 +76,9 @@ namespace Nuclex.Support.Plugins {
|
||||||
private void employAssemblyTypes(Assembly assembly) {
|
private void employAssemblyTypes(Assembly assembly) {
|
||||||
|
|
||||||
// Iterate all types contained in the assembly
|
// Iterate all types contained in the assembly
|
||||||
foreach(Type type in assembly.GetTypes()) {
|
Type[] types = assembly.GetTypes();
|
||||||
|
for(int index = 0; index < types.Length; ++index) {
|
||||||
|
Type type = types[index];
|
||||||
|
|
||||||
// We'll ignore abstract and non-public types
|
// We'll ignore abstract and non-public types
|
||||||
if(!type.IsPublic || type.IsAbstract) {
|
if(!type.IsPublic || type.IsAbstract) {
|
||||||
|
@ -97,7 +100,6 @@ namespace Nuclex.Support.Plugins {
|
||||||
catch(Exception exception) {
|
catch(Exception exception) {
|
||||||
Trace.WriteLine("Could not employ " + type.ToString() + ": " + exception.Message);
|
Trace.WriteLine("Could not employ " + type.ToString() + ": " + exception.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
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
|
|
@ -198,6 +198,18 @@ namespace Nuclex.Support.Tracking {
|
||||||
/// Allows the specific request implementation to re-throw an exception if
|
/// Allows the specific request implementation to re-throw an exception if
|
||||||
/// the background process finished unsuccessfully
|
/// the background process finished unsuccessfully
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
protected override void ReraiseExceptions() {
|
||||||
|
// Request and discard the result, so the implementor can do all error handling
|
||||||
|
// in the GatherResults() method. This is a good default implementation as long
|
||||||
|
// as the returned object does not require IDispose. It if does, this method
|
||||||
|
// needs to be overridden.
|
||||||
|
GatherResults();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allows the specific request to return the results of the Request to the
|
||||||
|
/// caller of the Join() method
|
||||||
|
/// </summary>
|
||||||
protected abstract ResultType GatherResults();
|
protected abstract ResultType GatherResults();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
307
Source/XmlHelper.Test.cs
Normal file
307
Source/XmlHelper.Test.cs
Normal file
|
@ -0,0 +1,307 @@
|
||||||
|
#region CPL License
|
||||||
|
/*
|
||||||
|
Nuclex Framework
|
||||||
|
Copyright (C) 2002-2009 Nuclex Development Labs
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the IBM Common Public License as
|
||||||
|
published by the IBM Corporation; either version 1.0 of the
|
||||||
|
License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
IBM Common Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the IBM Common Public
|
||||||
|
License along with this library
|
||||||
|
*/
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#if UNITTEST
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Xml;
|
||||||
|
using System.Xml.Schema;
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace Nuclex.Support {
|
||||||
|
|
||||||
|
/// <summary>Unit Test for the XML helper class</summary>
|
||||||
|
[TestFixture]
|
||||||
|
public class XmlHelperTest {
|
||||||
|
|
||||||
|
/// <summary>A broken XML schema</summary>
|
||||||
|
private const string brokenSchemaXml =
|
||||||
|
"This is not a valid schema";
|
||||||
|
|
||||||
|
/// <summary>An XML schema with a syntax error</summary>
|
||||||
|
private const string syntaxErrorSchemaXml =
|
||||||
|
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>" +
|
||||||
|
"<xs:schema" +
|
||||||
|
" elementFormDefault=\"qualified\"" +
|
||||||
|
" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">" +
|
||||||
|
" <xs:attribute minOccurs=\"0\" maxOccurs=\"2\" name=\"x\" type=\"xs:double\" />" +
|
||||||
|
" <xs:attribute minOccurs=\"0\" maxOccurs=\"2\" name=\"y\" type=\"xs:double\" />" +
|
||||||
|
"</xs:schema>";
|
||||||
|
|
||||||
|
/// <summary>A valid XML schema for a list of 2D points</summary>
|
||||||
|
private const string pointsSchemaXml =
|
||||||
|
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>" +
|
||||||
|
"<xs:schema" +
|
||||||
|
" elementFormDefault=\"qualified\"" +
|
||||||
|
" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">" +
|
||||||
|
" <xs:complexType name=\"vectorType\">" +
|
||||||
|
" <xs:attribute name=\"x\" type=\"xs:double\" />" +
|
||||||
|
" <xs:attribute name=\"y\" type=\"xs:double\" />" +
|
||||||
|
" </xs:complexType>" +
|
||||||
|
" <xs:complexType name=\"pointsType\">" +
|
||||||
|
" <xs:sequence minOccurs=\"0\" maxOccurs=\"unbounded\">" +
|
||||||
|
" <xs:element name=\"point\" type=\"vectorType\" />" +
|
||||||
|
" </xs:sequence>" +
|
||||||
|
" </xs:complexType>" +
|
||||||
|
" <xs:element name=\"points\" type=\"pointsType\" />" +
|
||||||
|
"</xs:schema>";
|
||||||
|
|
||||||
|
/// <summary>A broken XML document</summary>
|
||||||
|
private const string brokenXml =
|
||||||
|
"This is not a valid XML file";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Well-formed XML document that is not conformant to the schema above
|
||||||
|
/// </summary>
|
||||||
|
private const string unconformantXml =
|
||||||
|
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>" +
|
||||||
|
"<points" +
|
||||||
|
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" +
|
||||||
|
" xsi:noNamespaceSchemaLocation=\"skin.xsd\">" +
|
||||||
|
" <point x=\"10\" y=\"20\" z=\"30\" />" +
|
||||||
|
" <point x=\"1\" y=\"2\" />" +
|
||||||
|
"</points>";
|
||||||
|
|
||||||
|
/// <summary>Well-formed XML document that is conformant to the schema</summary>
|
||||||
|
private const string conformantXml =
|
||||||
|
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>" +
|
||||||
|
"<points" +
|
||||||
|
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" +
|
||||||
|
" xsi:noNamespaceSchemaLocation=\"skin.xsd\">" +
|
||||||
|
" <point x=\"1\" y=\"2\" />" +
|
||||||
|
" <point x=\"10\" y=\"20\" />" +
|
||||||
|
"</points>";
|
||||||
|
|
||||||
|
#region TempFileKeeper
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a temporary file and automatically deletes it on dispose
|
||||||
|
/// </summary>
|
||||||
|
private class TempFileKeeper : IDisposable {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a temporary file with the specified contents using the UTF8 encoding
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileContents">
|
||||||
|
/// Contents that will be written into the temporary file
|
||||||
|
/// </param>
|
||||||
|
public TempFileKeeper(string fileContents) : this(fileContents, Encoding.UTF8) { }
|
||||||
|
|
||||||
|
/// <summary>Creates a temporary file with the specified contents</summary>
|
||||||
|
/// <param name="fileContents">
|
||||||
|
/// Contents that will be written into the temporary file
|
||||||
|
/// </param>
|
||||||
|
/// <param name="encoding">
|
||||||
|
/// Encoding to use for writing the contents into the file
|
||||||
|
/// </param>
|
||||||
|
public TempFileKeeper(string fileContents, Encoding encoding) {
|
||||||
|
string tempFile = Path.GetTempFileName();
|
||||||
|
try {
|
||||||
|
using(
|
||||||
|
FileStream tempFileStream = new FileStream(
|
||||||
|
tempFile, FileMode.Truncate, FileAccess.Write, FileShare.None
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
StreamWriter writer = new StreamWriter(tempFileStream, encoding);
|
||||||
|
writer.Write(fileContents);
|
||||||
|
writer.Flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception) {
|
||||||
|
File.Delete(tempFile);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tempFilePath = tempFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Called when the instance is collected by the GC</summary>
|
||||||
|
~TempFileKeeper() {
|
||||||
|
Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Immediately releases all resources used by the instance</summary>
|
||||||
|
public void Dispose() {
|
||||||
|
if(this.tempFilePath != null) {
|
||||||
|
File.Delete(this.tempFilePath);
|
||||||
|
this.tempFilePath = null;
|
||||||
|
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Implicitely converts a TempFileKeeper into a file path</summary>
|
||||||
|
/// <param name="tempFileKeeper">TempFileKeeper that will be converted</param>
|
||||||
|
/// <returns>The path to the temporary file managed by the TempFileKeeper</returns>
|
||||||
|
public static implicit operator string(TempFileKeeper tempFileKeeper) {
|
||||||
|
return tempFileKeeper.tempFilePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Path to the temporary file the TempFileKeeper is managing</summary>
|
||||||
|
private string tempFilePath;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion // class TempFileKeeper
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that an exception is thrown when a schema fails to load
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestThrowOnInvalidSchema() {
|
||||||
|
using(
|
||||||
|
TempFileKeeper tempFile = new TempFileKeeper(brokenSchemaXml)
|
||||||
|
) {
|
||||||
|
Assert.Throws<XmlException>(delegate() { XmlHelper.LoadSchema(tempFile); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that an exception is thrown when a schema contains a syntax error
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestThrowOnSyntaxErrorSchema() {
|
||||||
|
using(
|
||||||
|
TempFileKeeper tempFile = new TempFileKeeper(syntaxErrorSchemaXml)
|
||||||
|
) {
|
||||||
|
Assert.Throws<XmlSchemaException>(delegate() { XmlHelper.LoadSchema(tempFile); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verfifies that TryLoadSchema() can fail without throwing an exception
|
||||||
|
/// when the schema is not a valid XML document
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestFailOnTryLoadNonExistingFile() {
|
||||||
|
XmlSchema schema;
|
||||||
|
Assert.IsFalse(XmlHelper.TryLoadSchema("-- hello world --", out schema));
|
||||||
|
Assert.IsNull(schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verfifies that TryLoadSchema() can fail without throwing an exception
|
||||||
|
/// when the schema is not a valid XML document
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestFailOnTryLoadBrokenSchema() {
|
||||||
|
using(
|
||||||
|
TempFileKeeper tempFile = new TempFileKeeper(brokenSchemaXml)
|
||||||
|
) {
|
||||||
|
XmlSchema schema;
|
||||||
|
Assert.IsFalse(XmlHelper.TryLoadSchema(tempFile, out schema));
|
||||||
|
Assert.IsNull(schema);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verfifies that TryLoadSchema() can fail without throwing an exception
|
||||||
|
/// when the schema contains a syntax error
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestFailOnTryLoadSyntaxErrorSchema() {
|
||||||
|
using(
|
||||||
|
TempFileKeeper tempFile = new TempFileKeeper(syntaxErrorSchemaXml)
|
||||||
|
) {
|
||||||
|
XmlSchema schema;
|
||||||
|
Assert.IsFalse(XmlHelper.TryLoadSchema(tempFile, out schema));
|
||||||
|
Assert.IsNull(schema);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Tests whether a normal, valid schema can be loaded successfully</summary>
|
||||||
|
[Test]
|
||||||
|
public void TestLoadSchema() {
|
||||||
|
using(
|
||||||
|
TempFileKeeper tempFile = new TempFileKeeper(pointsSchemaXml)
|
||||||
|
) {
|
||||||
|
XmlHelper.LoadSchema(tempFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Tests whether a normal, valid schema can be loaded successfully</summary>
|
||||||
|
[Test]
|
||||||
|
public void TestTryLoadSchema() {
|
||||||
|
using(
|
||||||
|
TempFileKeeper tempFile = new TempFileKeeper(pointsSchemaXml)
|
||||||
|
) {
|
||||||
|
XmlSchema schema;
|
||||||
|
Assert.IsTrue(XmlHelper.TryLoadSchema(tempFile, out schema));
|
||||||
|
Assert.NotNull(schema);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that an exception is thrown when an invalid XML document is loaded
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestFailOnLoadInvalidDocument() {
|
||||||
|
using(TextReader schemaReader = new StringReader(pointsSchemaXml)) {
|
||||||
|
XmlSchema schema = XmlHelper.LoadSchema(schemaReader);
|
||||||
|
using(
|
||||||
|
TempFileKeeper tempFile = new TempFileKeeper(brokenXml)
|
||||||
|
) {
|
||||||
|
Assert.Throws<XmlException>(
|
||||||
|
delegate() { XmlHelper.LoadDocument(schema, tempFile); }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that an exception is thrown when a nonconformant XML document is loaded
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestFailOnLoadNonConformingDocument() {
|
||||||
|
using(TextReader schemaReader = new StringReader(pointsSchemaXml)) {
|
||||||
|
XmlSchema schema = XmlHelper.LoadSchema(schemaReader);
|
||||||
|
using(
|
||||||
|
TempFileKeeper tempFile = new TempFileKeeper(unconformantXml)
|
||||||
|
) {
|
||||||
|
Assert.Throws<XmlSchemaValidationException>(
|
||||||
|
delegate() { XmlHelper.LoadDocument(schema, tempFile); }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests whether a normal, conformant XML document can be loaded successfully
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestLoadConformingDocument() {
|
||||||
|
using(TextReader schemaReader = new StringReader(pointsSchemaXml)) {
|
||||||
|
XmlSchema schema = XmlHelper.LoadSchema(schemaReader);
|
||||||
|
using(
|
||||||
|
TempFileKeeper tempFile = new TempFileKeeper(conformantXml)
|
||||||
|
) {
|
||||||
|
XmlDocument document = XmlHelper.LoadDocument(schema, tempFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Nuclex.Support
|
||||||
|
|
||||||
|
#endif // UNITTEST
|
212
Source/XmlHelper.cs
Normal file
212
Source/XmlHelper.cs
Normal file
|
@ -0,0 +1,212 @@
|
||||||
|
#region CPL License
|
||||||
|
/*
|
||||||
|
Nuclex Framework
|
||||||
|
Copyright (C) 2002-2009 Nuclex Development Labs
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the IBM Common Public License as
|
||||||
|
published by the IBM Corporation; either version 1.0 of the
|
||||||
|
License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
IBM Common Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the IBM Common Public
|
||||||
|
License along with this library
|
||||||
|
*/
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Xml;
|
||||||
|
using System.Xml.Schema;
|
||||||
|
|
||||||
|
namespace Nuclex.Support {
|
||||||
|
|
||||||
|
/// <summary>Helper routines for handling XML code</summary>
|
||||||
|
public static class XmlHelper {
|
||||||
|
|
||||||
|
#region class ValidationEventProcessor
|
||||||
|
|
||||||
|
/// <summary>Handles any events occurring when an XML schema is loaded</summary>
|
||||||
|
private class ValidationEventProcessor {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Callback for notifications being sent by the XmlSchema.Read() method
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender">Not used</param>
|
||||||
|
/// <param name="arguments">Contains the notification being sent</param>
|
||||||
|
public void OnValidationEvent(object sender, ValidationEventArgs arguments) {
|
||||||
|
// We're only interested in the first error, so if we already got
|
||||||
|
// an exception on record, skip it!
|
||||||
|
if(this.OccurredException != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only errors count as reasons to assume loading has failed
|
||||||
|
if(arguments.Severity == XmlSeverityType.Error) {
|
||||||
|
|
||||||
|
// This code uses the ternary operator because I don't know how to
|
||||||
|
// stimlate a validation error that has no exception and couldn't achieve
|
||||||
|
// 100% test coverage otherwise. MSDN does not tell whether a validation
|
||||||
|
// error without an exception can even occur.
|
||||||
|
this.OccurredException = (arguments.Exception != null) ?
|
||||||
|
arguments.Exception :
|
||||||
|
new XmlSchemaValidationException("Unspecified schema validation error");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Exception that has occurred during schema loading</summary>
|
||||||
|
public Exception OccurredException;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion // class ValidationEventProcessor
|
||||||
|
|
||||||
|
/// <summary>Loads a schema from a file</summary>
|
||||||
|
/// <param name="schemaPath">Path to the file containing the schema</param>
|
||||||
|
/// <returns>The loaded schema</returns>
|
||||||
|
public static XmlSchema LoadSchema(string schemaPath) {
|
||||||
|
using(FileStream schemaStream = openFileForSharedReading(schemaPath)) {
|
||||||
|
return LoadSchema(schemaStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Loads a schema from the provided stream</summary>
|
||||||
|
/// <param name="schemaStream">Stream containing the schema that will be loaded</param>
|
||||||
|
/// <returns>The loaded schema</returns>
|
||||||
|
public static XmlSchema LoadSchema(Stream schemaStream) {
|
||||||
|
return LoadSchema(new StreamReader(schemaStream));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Loads a schema from a text reader</summary>
|
||||||
|
/// <param name="schemaReader">Text reader through which the schema can be read</param>
|
||||||
|
/// <returns>The loaded schema</returns>
|
||||||
|
public static XmlSchema LoadSchema(TextReader schemaReader) {
|
||||||
|
ValidationEventProcessor eventProcessor = new ValidationEventProcessor();
|
||||||
|
XmlSchema schema = XmlSchema.Read(schemaReader, eventProcessor.OnValidationEvent);
|
||||||
|
if(eventProcessor.OccurredException != null) {
|
||||||
|
throw eventProcessor.OccurredException;
|
||||||
|
}
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Attempts to load a schema from a file</summary>
|
||||||
|
/// <param name="schemaPath">Path to the file containing the schema</param>
|
||||||
|
/// <param name="schema">Output parameter that will receive the loaded schema</param>
|
||||||
|
/// <returns>True if the schema was loaded successfully, otherwise false</returns>
|
||||||
|
public static bool TryLoadSchema(string schemaPath, out XmlSchema schema) {
|
||||||
|
FileStream schemaStream;
|
||||||
|
if(!tryOpenFileForSharedReading(schemaPath, out schemaStream)) {
|
||||||
|
schema = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
using(schemaStream) {
|
||||||
|
return TryLoadSchema(schemaStream, out schema);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Attempts to load a schema from the provided stream</summary>
|
||||||
|
/// <param name="schemaStream">Stream containing the schema that will be loaded</param>
|
||||||
|
/// <param name="schema">Output parameter that will receive the loaded schema</param>
|
||||||
|
/// <returns>True if the schema was loaded successfully, otherwise false</returns>
|
||||||
|
public static bool TryLoadSchema(Stream schemaStream, out XmlSchema schema) {
|
||||||
|
return TryLoadSchema(new StreamReader(schemaStream), out schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Attempts to load a schema from the provided text reader</summary>
|
||||||
|
/// <param name="schemaReader">Reader from which the schema can be read</param>
|
||||||
|
/// <param name="schema">Output parameter that will receive the loaded schema</param>
|
||||||
|
/// <returns>True if the schema was loaded successfully, otherwise false</returns>
|
||||||
|
public static bool TryLoadSchema(TextReader schemaReader, out XmlSchema schema) {
|
||||||
|
try {
|
||||||
|
ValidationEventProcessor eventProcessor = new ValidationEventProcessor();
|
||||||
|
schema = XmlSchema.Read(schemaReader, eventProcessor.OnValidationEvent);
|
||||||
|
if(eventProcessor.OccurredException == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception) {
|
||||||
|
// Munch!
|
||||||
|
}
|
||||||
|
|
||||||
|
schema = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Loads an XML document from a file</summary>
|
||||||
|
/// <param name="schema">Schema to use for validating the XML document</param>
|
||||||
|
/// <param name="documentPath">
|
||||||
|
/// Path to the file containing the XML document that will be loaded
|
||||||
|
/// </param>
|
||||||
|
/// <returns>The loaded XML document</returns>
|
||||||
|
public static XmlDocument LoadDocument(XmlSchema schema, string documentPath) {
|
||||||
|
using(FileStream documentStream = openFileForSharedReading(documentPath)) {
|
||||||
|
return LoadDocument(schema, documentStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Loads an XML document from a stream</summary>
|
||||||
|
/// <param name="schema">Schema to use for validating the XML document</param>
|
||||||
|
/// <param name="documentStream">
|
||||||
|
/// Stream from which the XML document will be read
|
||||||
|
/// </param>
|
||||||
|
/// <returns>The loaded XML document</returns>
|
||||||
|
public static XmlDocument LoadDocument(XmlSchema schema, Stream documentStream) {
|
||||||
|
XmlReaderSettings settings = new XmlReaderSettings();
|
||||||
|
settings.Schemas.Add(schema);
|
||||||
|
|
||||||
|
using(XmlReader reader = XmlReader.Create(documentStream, settings)) {
|
||||||
|
XmlDocument document = new XmlDocument();
|
||||||
|
document.Schemas.Add(schema);
|
||||||
|
document.Load(reader);
|
||||||
|
|
||||||
|
ValidationEventProcessor eventProcessor = new ValidationEventProcessor();
|
||||||
|
document.Validate(eventProcessor.OnValidationEvent);
|
||||||
|
if(eventProcessor.OccurredException != null) {
|
||||||
|
throw eventProcessor.OccurredException;
|
||||||
|
}
|
||||||
|
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Opens a file for shared reading</summary>
|
||||||
|
/// <param name="path">Path to the file that will be opened</param>
|
||||||
|
/// <returns>The opened file's stream</returns>
|
||||||
|
private static FileStream openFileForSharedReading(string path) {
|
||||||
|
return new FileStream(
|
||||||
|
path, FileMode.Open, FileAccess.Read, FileShare.Read
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Opens a file for shared reading</summary>
|
||||||
|
/// <param name="path">Path to the file that will be opened</param>
|
||||||
|
/// <param name="fileStream">
|
||||||
|
/// Output parameter that receives the opened file's stream
|
||||||
|
/// </param>
|
||||||
|
/// <returns>True if the file was opened successfully</returns>
|
||||||
|
private static bool tryOpenFileForSharedReading(string path, out FileStream fileStream) {
|
||||||
|
try {
|
||||||
|
fileStream = new FileStream(
|
||||||
|
path, FileMode.Open, FileAccess.Read, FileShare.Read
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch(Exception) {
|
||||||
|
// Munch!
|
||||||
|
}
|
||||||
|
|
||||||
|
fileStream = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Nuclex.Support
|
Loading…
Reference in New Issue
Block a user