Add IContextCache to deploy connectors (#13287)

* Add IContextCache and implementations

* Update connector interfaces to use IContextCache

* Minor cleanup

* Move DeployContextCache prefix to constant

* Move default implementations to obsolete methods

* Remove DeployContextCache and DictionaryCache

Co-authored-by: Andy Butland <abutland73@gmail.com>
This commit is contained in:
Ronald Barendse
2022-10-25 18:40:11 +02:00
committed by GitHub
parent fc187596e3
commit 1560e84f14
10 changed files with 359 additions and 157 deletions

View File

@@ -1,50 +1,49 @@
namespace Umbraco.Cms.Core.Deploy
namespace Umbraco.Cms.Core.Deploy;
/// <summary>
/// Provides a base class to all artifacts.
/// </summary>
public abstract class ArtifactBase<TUdi> : IArtifact
where TUdi : Udi
{
/// <summary>
/// Provides a base class to all artifacts.
/// </summary>
public abstract class ArtifactBase<TUdi> : IArtifact
where TUdi : Udi
protected ArtifactBase(TUdi udi, IEnumerable<ArtifactDependency>? dependencies = null)
{
protected ArtifactBase(TUdi udi, IEnumerable<ArtifactDependency>? dependencies = null)
{
Udi = udi ?? throw new ArgumentNullException("udi");
Name = Udi.ToString();
Udi = udi ?? throw new ArgumentNullException("udi");
Name = Udi.ToString();
_dependencies = dependencies ?? Enumerable.Empty<ArtifactDependency>();
_checksum = new Lazy<string>(GetChecksum);
}
private readonly Lazy<string> _checksum;
private IEnumerable<ArtifactDependency> _dependencies;
protected abstract string GetChecksum();
Udi IArtifactSignature.Udi => Udi;
public TUdi Udi { get; set; }
public string Checksum => _checksum.Value;
/// <summary>
/// Prevents the <see cref="Checksum" /> property from being serialized.
/// </summary>
/// <remarks>
/// Note that we can't use <see cref="NonSerializedAttribute"/> here as that works only on fields, not properties. And we want to avoid using [JsonIgnore]
/// as that would require an external dependency in Umbraco.Cms.Core.
/// So using this method of excluding properties from serialized data, documented here: https://www.newtonsoft.com/json/help/html/ConditionalProperties.htm
/// </remarks>
public bool ShouldSerializeChecksum() => false;
public IEnumerable<ArtifactDependency> Dependencies
{
get => _dependencies;
set => _dependencies = value.OrderBy(x => x.Udi);
}
public string Name { get; set; }
public string Alias { get; set; } = string.Empty;
_dependencies = dependencies ?? Enumerable.Empty<ArtifactDependency>();
_checksum = new Lazy<string>(GetChecksum);
}
private readonly Lazy<string> _checksum;
private IEnumerable<ArtifactDependency> _dependencies;
protected abstract string GetChecksum();
Udi IArtifactSignature.Udi => Udi;
public TUdi Udi { get; set; }
public string Checksum => _checksum.Value;
/// <summary>
/// Prevents the <see cref="Checksum" /> property from being serialized.
/// </summary>
/// <remarks>
/// Note that we can't use <see cref="NonSerializedAttribute"/> here as that works only on fields, not properties. And we want to avoid using [JsonIgnore]
/// as that would require an external dependency in Umbraco.Cms.Core.
/// So using this method of excluding properties from serialized data, documented here: https://www.newtonsoft.com/json/help/html/ConditionalProperties.htm
/// </remarks>
public bool ShouldSerializeChecksum() => false;
public IEnumerable<ArtifactDependency> Dependencies
{
get => _dependencies;
set => _dependencies = value.OrderBy(x => x.Udi);
}
public string Name { get; set; }
public string Alias { get; set; } = string.Empty;
}

View File

@@ -44,3 +44,40 @@ public abstract class ArtifactDeployState
/// </remarks>
protected abstract IArtifact GetArtifactAsIArtifact();
}
/// <summary>
/// Represent the state of an artifact being deployed.
/// </summary>
/// <typeparam name="TArtifact">The type of the artifact.</typeparam>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
public class ArtifactDeployState<TArtifact, TEntity> : ArtifactDeployState
where TArtifact : IArtifact
{
/// <summary>
/// Initializes a new instance of the <see cref="ArtifactDeployState{TArtifact,TEntity}" /> class.
/// </summary>
/// <param name="art">The artifact.</param>
/// <param name="entity">The entity.</param>
/// <param name="connector">The service connector deploying the artifact.</param>
/// <param name="nextPass">The next pass number.</param>
public ArtifactDeployState(TArtifact art, TEntity? entity, IServiceConnector connector, int nextPass)
{
Artifact = art;
Entity = entity;
Connector = connector;
NextPass = nextPass;
}
/// <summary>
/// Gets or sets the artifact.
/// </summary>
public new TArtifact Artifact { get; set; }
/// <summary>
/// Gets or sets the entity.
/// </summary>
public TEntity? Entity { get; set; }
/// <inheritdoc />
protected sealed override IArtifact GetArtifactAsIArtifact() => Artifact;
}

View File

@@ -1,38 +0,0 @@
namespace Umbraco.Cms.Core.Deploy;
/// <summary>
/// Represent the state of an artifact being deployed.
/// </summary>
/// <typeparam name="TArtifact">The type of the artifact.</typeparam>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
public class ArtifactDeployState<TArtifact, TEntity> : ArtifactDeployState
where TArtifact : IArtifact
{
/// <summary>
/// Initializes a new instance of the <see cref="ArtifactDeployState{TArtifact,TEntity}" /> class.
/// </summary>
/// <param name="art">The artifact.</param>
/// <param name="entity">The entity.</param>
/// <param name="connector">The service connector deploying the artifact.</param>
/// <param name="nextPass">The next pass number.</param>
public ArtifactDeployState(TArtifact art, TEntity? entity, IServiceConnector connector, int nextPass)
{
Artifact = art;
Entity = entity;
Connector = connector;
NextPass = nextPass;
}
/// <summary>
/// Gets or sets the artifact.
/// </summary>
public new TArtifact Artifact { get; set; }
/// <summary>
/// Gets or sets the entity.
/// </summary>
public TEntity? Entity { get; set; }
/// <inheritdoc />
protected sealed override IArtifact GetArtifactAsIArtifact() => Artifact;
}

View File

@@ -0,0 +1,31 @@
namespace Umbraco.Cms.Core.Deploy;
/// <summary>
/// Represents a context cache used by Deploy operations.
/// </summary>
public interface IContextCache
{
/// <summary>
/// Creates the item on the context cache using the specified <paramref name="key" />.
/// </summary>
/// <typeparam name="T">The type of the cached item.</typeparam>
/// <param name="key">The key of the cached item.</param>
/// <param name="item">The item.</param>
void Create<T>(string key, T item);
/// <summary>
/// Gets an item from the context cache or creates and stores it using the specified <paramref name="key" />.
/// </summary>
/// <typeparam name="T">The type of the cached item.</typeparam>
/// <param name="key">The key of the cached item.</param>
/// <param name="factory">The factory method to create the item (if it doesn't exist yet).</param>
/// <returns>
/// The item.
/// </returns>
T? GetOrCreate<T>(string key, Func<T?> factory);
/// <summary>
/// Clears all cached items on this context.
/// </summary>
void Clear();
}

View File

@@ -1,37 +1,67 @@
using System.Diagnostics.CodeAnalysis;
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.Deploy;
/// <summary>
/// Defines methods that can convert data type configuration to / from an environment-agnostic string.
/// Defines methods that can convert data type configuration to / from an environment-agnostic string.
/// </summary>
/// <remarks>
/// Configuration may contain values such as content identifiers, that would be local
/// to one environment, and need to be converted in order to be deployed.
/// Configuration may contain values such as content identifiers, that would be local
/// to one environment, and need to be converted in order to be deployed.
/// </remarks>
[SuppressMessage(
"ReSharper",
"UnusedMember.Global",
Justification = "This is actual only used by Deploy, but we don't want third parties to have references on deploy, that's why this interface is part of core.")]
public interface IDataTypeConfigurationConnector
{
/// <summary>
/// Gets the property editor aliases that the value converter supports by default.
/// Gets the property editor aliases that the value converter supports by default.
/// </summary>
/// <value>
/// The property editor aliases.
/// </value>
IEnumerable<string> PropertyEditorAliases { get; }
/// <summary>
/// Gets the artifact datatype configuration corresponding to the actual datatype configuration.
/// Gets the artifact configuration value corresponding to a data type configuration and gather dependencies.
/// </summary>
/// <param name="dataType">The datatype.</param>
/// <param name="dataType">The data type.</param>
/// <param name="dependencies">The dependencies.</param>
string? ToArtifact(IDataType dataType, ICollection<ArtifactDependency> dependencies);
/// <returns>
/// The artifact configuration value.
/// </returns>
[Obsolete("Use the overload accepting IContextCache instead. This overload will be removed in a future version.")]
string? ToArtifact(IDataType dataType, ICollection<ArtifactDependency> dependencies)
=> ToArtifact(dataType, dependencies, PassThroughCache.Instance);
/// <summary>
/// Gets the actual datatype configuration corresponding to the artifact configuration.
/// Gets the artifact configuration value corresponding to a data type configuration and gather dependencies.
/// </summary>
/// <param name="dataType">The datatype.</param>
/// <param name="configuration">The artifact configuration.</param>
object? FromArtifact(IDataType dataType, string? configuration);
/// <param name="dataType">The data type.</param>
/// <param name="dependencies">The dependencies.</param>
/// <param name="contextCache">The context cache.</param>
/// <returns>
/// The artifact configuration value.
/// </returns>
string? ToArtifact(IDataType dataType, ICollection<ArtifactDependency> dependencies, IContextCache contextCache);
/// <summary>
/// Gets the data type configuration corresponding to an artifact configuration value.
/// </summary>
/// <param name="dataType">The data type.</param>
/// <param name="configuration">The artifact configuration value.</param>
/// <returns>
/// The data type configuration.
/// </returns>
[Obsolete("Use the overload accepting IContextCache instead. This overload will be removed in a future version.")]
object? FromArtifact(IDataType dataType, string? configuration)
=> FromArtifact(dataType, configuration, PassThroughCache.Instance);
/// <summary>
/// Gets the data type configuration corresponding to an artifact configuration value.
/// </summary>
/// <param name="dataType">The data type.</param>
/// <param name="configuration">The artifact configuration value.</param>
/// <param name="contextCache">The context cache.</param>
/// <returns>
/// The data type configuration.
/// </returns>
object? FromArtifact(IDataType dataType, string? configuration, IContextCache contextCache);
}

View File

@@ -3,34 +3,65 @@ using Umbraco.Cms.Core.Composing;
namespace Umbraco.Cms.Core.Deploy;
/// <summary>
/// Connects to an Umbraco service.
/// Connects to an Umbraco service.
/// </summary>
/// <seealso cref="Umbraco.Cms.Core.Composing.IDiscoverable" />
public interface IServiceConnector : IDiscoverable
{
/// <summary>
/// Gets an artifact.
/// Gets an artifact.
/// </summary>
/// <param name="udi">The entity identifier of the artifact.</param>
/// <returns>The corresponding artifact, or null.</returns>
IArtifact? GetArtifact(Udi udi);
/// <returns>
/// The corresponding artifact, or null.
/// </returns>
[Obsolete("Use the overload accepting IContextCache instead. This overload will be removed in a future version.")]
IArtifact? GetArtifact(Udi udi)
=> GetArtifact(udi, PassThroughCache.Instance);
/// <summary>
/// Gets an artifact.
/// Gets an artifact.
/// </summary>
/// <param name="udi">The entity identifier of the artifact.</param>
/// <param name="contextCache">The context cache.</param>
/// <returns>
/// The corresponding artifact, or null.
/// </returns>
IArtifact? GetArtifact(Udi udi, IContextCache contextCache);
/// <summary>
/// Gets an artifact.
/// </summary>
/// <param name="entity">The entity.</param>
/// <returns>The corresponding artifact.</returns>
IArtifact GetArtifact(object entity);
/// <returns>
/// The corresponding artifact.
/// </returns>
[Obsolete("Use the overload accepting IContextCache instead. This overload will be removed in a future version.")]
IArtifact GetArtifact(object entity)
=> GetArtifact(entity, PassThroughCache.Instance);
/// <summary>
/// Initializes processing for an artifact.
/// Gets an artifact.
/// </summary>
/// <param name="entity">The entity.</param>
/// <param name="contextCache">The context cache.</param>
/// <returns>
/// The corresponding artifact.
/// </returns>
IArtifact GetArtifact(object entity, IContextCache contextCache);
/// <summary>
/// Initializes processing for an artifact.
/// </summary>
/// <param name="art">The artifact.</param>
/// <param name="context">The deploy context.</param>
/// <returns>The mapped artifact.</returns>
/// <returns>
/// The mapped artifact.
/// </returns>
ArtifactDeployState ProcessInit(IArtifact art, IDeployContext context);
/// <summary>
/// Processes an artifact.
/// Processes an artifact.
/// </summary>
/// <param name="dart">The mapped artifact.</param>
/// <param name="context">The deploy context.</param>
@@ -38,46 +69,56 @@ public interface IServiceConnector : IDiscoverable
void Process(ArtifactDeployState dart, IDeployContext context, int pass);
/// <summary>
/// Explodes a range into udis.
/// Explodes a range into udis.
/// </summary>
/// <param name="range">The range.</param>
/// <param name="udis">The list of udis where to add the new udis.</param>
/// <remarks>Also, it's cool to have a method named Explode. Kaboom!</remarks>
/// <remarks>
/// Also, it's cool to have a method named Explode. Kaboom!
/// </remarks>
void Explode(UdiRange range, List<Udi> udis);
/// <summary>
/// Gets a named range for a specified udi and selector.
/// Gets a named range for a specified udi and selector.
/// </summary>
/// <param name="udi">The udi.</param>
/// <param name="selector">The selector.</param>
/// <returns>The named range for the specified udi and selector.</returns>
/// <returns>
/// The named range for the specified udi and selector.
/// </returns>
NamedUdiRange GetRange(Udi udi, string selector);
/// <summary>
/// Gets a named range for specified entity type, identifier and selector.
/// Gets a named range for specified entity type, identifier and selector.
/// </summary>
/// <param name="entityType">The entity type.</param>
/// <param name="sid">The identifier.</param>
/// <param name="selector">The selector.</param>
/// <returns>The named range for the specified entity type, identifier and selector.</returns>
/// <returns>
/// The named range for the specified entity type, identifier and selector.
/// </returns>
/// <remarks>
/// <para>This is temporary. At least we thought it would be, in sept. 2016. What day is it now?</para>
/// <para>
/// At the moment our UI has a hard time returning proper udis, mainly because Core's tree do
/// not manage guids but only ints... so we have to provide a way to support it. The string id here
/// can be either a real string (for string udis) or an "integer as a string", using the value "-1" to
/// indicate the "root" i.e. an open udi.
/// </para>
/// <para>This is temporary. At least we thought it would be, in sept. 2016. What day is it now?</para>
/// <para>
/// At the moment our UI has a hard time returning proper udis, mainly because Core's tree do
/// not manage guids but only ints... so we have to provide a way to support it. The string id here
/// can be either a real string (for string udis) or an "integer as a string", using the value "-1" to
/// indicate the "root" i.e. an open udi.
/// </para>
/// </remarks>
NamedUdiRange GetRange(string entityType, string sid, string selector);
/// <summary>
/// Compares two artifacts.
/// Compares two artifacts.
/// </summary>
/// <param name="art1">The first artifact.</param>
/// <param name="art2">The second artifact.</param>
/// <param name="differences">A collection of differences to append to, if not null.</param>
/// <returns>A boolean value indicating whether the artifacts are identical.</returns>
/// <remarks>ServiceConnectorBase{TArtifact} provides a very basic default implementation.</remarks>
/// <returns>
/// A boolean value indicating whether the artifacts are identical.
/// </returns>
/// <remarks>
/// ServiceConnectorBase{TArtifact} provides a very basic default implementation.
/// </remarks>
bool Compare(IArtifact? art1, IArtifact? art2, ICollection<Difference>? differences = null);
}

View File

@@ -3,35 +3,70 @@ using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.Deploy;
/// <summary>
/// Defines methods that can convert a property value to / from an environment-agnostic string.
/// Defines methods that can convert a property value to and from an environment-agnostic string.
/// </summary>
/// <remarks>
/// Property values may contain values such as content identifiers, that would be local
/// to one environment, and need to be converted in order to be deployed. Connectors also deal
/// with serializing to / from string.
/// Property values may contain values such as content identifiers, that would be local
/// to one environment and need to be converted in order to be deployed. Connectors also deal
/// with serializing and deserializing the content value to an environment-agnostic string.
/// </remarks>
public interface IValueConnector
{
/// <summary>
/// Gets the property editor aliases that the value converter supports by default.
/// Gets the property editor aliases that the value converter supports by default.
/// </summary>
/// <value>
/// The property editor aliases.
/// </value>
IEnumerable<string> PropertyEditorAliases { get; }
/// <summary>
/// Gets the deploy property value corresponding to a content property value, and gather dependencies.
/// Gets the deploy property value corresponding to a content property value, and gather dependencies.
/// </summary>
/// <param name="value">The content property value.</param>
/// <param name="propertyType">The value property type</param>
/// <param name="dependencies">The content dependencies.</param>
/// <returns>The deploy property value.</returns>
string? ToArtifact(object? value, IPropertyType propertyType, ICollection<ArtifactDependency> dependencies);
/// <returns>
/// The deploy property value.
/// </returns>
[Obsolete("Use the overload accepting IContextCache instead. This overload will be removed in a future version.")]
string? ToArtifact(object? value, IPropertyType propertyType, ICollection<ArtifactDependency> dependencies)
=> ToArtifact(value, propertyType, dependencies, PassThroughCache.Instance);
/// <summary>
/// Gets the content property value corresponding to a deploy property value.
/// Gets the deploy property value corresponding to a content property value, and gather dependencies.
/// </summary>
/// <param name="value">The content property value.</param>
/// <param name="propertyType">The value property type</param>
/// <param name="dependencies">The content dependencies.</param>
/// <param name="contextCache">The context cache.</param>
/// <returns>
/// The deploy property value.
/// </returns>
string? ToArtifact(object? value, IPropertyType propertyType, ICollection<ArtifactDependency> dependencies, IContextCache contextCache);
/// <summary>
/// Gets the content property value corresponding to a deploy property value.
/// </summary>
/// <param name="value">The deploy property value.</param>
/// <param name="propertyType">The value property type</param>
/// <param name="currentValue">The current content property value.</param>
/// <returns>The content property value.</returns>
object? FromArtifact(string? value, IPropertyType propertyType, object? currentValue);
/// <returns>
/// The content property value.
/// </returns>
[Obsolete("Use the overload accepting IContextCache instead. This overload will be removed in a future version.")]
object? FromArtifact(string? value, IPropertyType propertyType, object? currentValue)
=> FromArtifact(value, propertyType, currentValue, PassThroughCache.Instance);
/// <summary>
/// Gets the content property value corresponding to a deploy property value.
/// </summary>
/// <param name="value">The deploy property value.</param>
/// <param name="propertyType">The value property type</param>
/// <param name="currentValue">The current content property value.</param>
/// <param name="contextCache">The context cache.</param>
/// <returns>
/// The content property value.
/// </returns>
object? FromArtifact(string? value, IPropertyType propertyType, object? currentValue, IContextCache contextCache);
}

View File

@@ -0,0 +1,34 @@
namespace Umbraco.Cms.Core.Deploy;
/// <summary>
/// A pass through context cache that always creates the items.
/// </summary>
/// <seealso cref="Umbraco.Cms.Core.Deploy.IContextCache" />
public sealed class PassThroughCache : IContextCache
{
/// <summary>
/// Gets the instance.
/// </summary>
/// <value>
/// The instance.
/// </value>
public static PassThroughCache Instance { get; } = new PassThroughCache();
/// <summary>
/// Prevents a default instance of the <see cref="PassThroughCache"/> class from being created.
/// </summary>
private PassThroughCache()
{ }
/// <inheritdoc />
public void Create<T>(string key, T item)
{ }
/// <inheritdoc />
public T? GetOrCreate<T>(string key, Func<T?> factory)
=> factory();
/// <inheritdoc />
public void Clear()
{ }
}

View File

@@ -1,44 +1,77 @@
using Umbraco.Cms.Core.Models;
using static Umbraco.Cms.Core.Models.GridValue;
namespace Umbraco.Cms.Core.Deploy;
/// <summary>
/// Defines methods that can convert a grid cell value to / from an environment-agnostic string.
/// Defines methods that can convert a grid cell value to / from an environment-agnostic string.
/// </summary>
/// <remarks>
/// Grid cell values may contain values such as content identifiers, that would be local
/// to one environment, and need to be converted in order to be deployed.
/// Grid cell values may contain values such as content identifiers, that would be local
/// to one environment, and need to be converted in order to be deployed.
/// </remarks>
public interface IGridCellValueConnector
{
/// <summary>
/// Gets a value indicating whether the connector supports a specified grid editor view.
/// Gets a value indicating whether the connector supports a specified grid editor view.
/// </summary>
/// <param name="view">
/// The grid editor view. It needs to be the view instead of the alias as the view is really what
/// identifies what kind of connector should be used. Alias can be anything and you can have multiple different aliases
/// using the same kind of view.
/// </param>
/// <remarks>A value indicating whether the connector supports the grid editor view.</remarks>
/// <remarks>Note that <paramref name="view" /> can be string.Empty to indicate the "default" connector.</remarks>
/// <param name="view">The grid editor view. It needs to be the view instead of the alias as the view is really what
/// identifies what kind of connector should be used. Alias can be anything and you can have multiple different aliases
/// using the same kind of view.</param>
/// <returns>
/// <c>true</c> if the specified view is connector; otherwise, <c>false</c>.
/// </returns>
/// <remarks>
/// A value indicating whether the connector supports the grid editor view.
/// </remarks>
bool IsConnector(string view);
/// <summary>
/// Gets the value to be deployed from the control value as a string.
/// Gets the value to be deployed from the control value as a string.
/// </summary>
/// <param name="gridControl">The control containing the value.</param>
/// <param name="dependencies">The dependencies of the property.</param>
/// <returns>The grid cell value to be deployed.</returns>
/// <remarks>Note that </remarks>
string? GetValue(GridValue.GridControl gridControl, ICollection<ArtifactDependency> dependencies);
/// <returns>
/// The grid cell value to be deployed.
/// </returns>
/// <remarks>
/// Note that
/// </remarks>
[Obsolete("Use the overload accepting IContextCache instead. This overload will be removed in a future version.")]
string? GetValue(GridValue.GridControl gridControl, ICollection<ArtifactDependency> dependencies)
=> GetValue(gridControl, dependencies, PassThroughCache.Instance);
/// <summary>
/// Allows you to modify the value of a control being deployed.
/// Gets the value to be deployed from the control value as a string.
/// </summary>
/// <param name="gridControl">The control containing the value.</param>
/// <param name="dependencies">The dependencies of the property.</param>
/// <param name="contextCache">The context cache.</param>
/// <returns>
/// The grid cell value to be deployed.
/// </returns>
string? GetValue(GridValue.GridControl gridControl, ICollection<ArtifactDependency> dependencies, IContextCache contextCache);
/// <summary>
/// Allows you to modify the value of a control being deployed.
/// </summary>
/// <param name="gridControl">The control being deployed.</param>
/// <remarks>
/// Follows the pattern of the property value connectors (<see cref="IValueConnector" />). The SetValue method is
/// used to modify the value of the <paramref name="gridControl" />.
/// Follows the pattern of the property value connectors (<see cref="IValueConnector" />).
/// The SetValue method is used to modify the value of the <paramref name="gridControl" />.
/// </remarks>
void SetValue(GridValue.GridControl gridControl);
[Obsolete("Use the overload accepting IContextCache instead. This overload will be removed in a future version.")]
void SetValue(GridValue.GridControl gridControl)
=> SetValue(gridControl, PassThroughCache.Instance);
/// <summary>
/// Allows you to modify the value of a control being deployed.
/// </summary>
/// <param name="gridControl">The control being deployed.</param>
/// <param name="contextCache">The context cache.</param>
/// <remarks>
/// Follows the pattern of the property value connectors (<see cref="IValueConnector" />).
/// The SetValue method is used to modify the value of the <paramref name="gridControl" />.
/// </remarks>
void SetValue(GridValue.GridControl gridControl, IContextCache contextCache);
}

View File

@@ -307,9 +307,9 @@ public class UdiTests
[UdiDefinition("foo", UdiType.GuidUdi)]
public class FooConnector : IServiceConnector
{
public IArtifact GetArtifact(Udi udi) => throw new NotImplementedException();
public IArtifact GetArtifact(Udi udi, IContextCache contextCache) => throw new NotImplementedException();
public IArtifact GetArtifact(object entity) => throw new NotImplementedException();
public IArtifact GetArtifact(object entity, IContextCache contextCache) => throw new NotImplementedException();
public ArtifactDeployState ProcessInit(IArtifact art, IDeployContext context) =>
throw new NotImplementedException();