API компиляции¶
Платформа содержит API для компиляции C# сценариев “на лету” на сервере приложений.
Компилятор¶
Низкоуровневым объектом, обеспечивающим связь с компилятором, является Tessa.Compilation.ICompiler
. Он обеспечивает вызов C# компилятора и первичную обработку результата.
В общем случае рекомендуется использовать объект Tessa.Compilation.ITessaCompiler<T>
. Он дополнительно предоставляет свойства для настройки параметров компиляции по умолчанию и вспомогательные методы для упрощения создания компилятора объекта заданного типа. Базовая реализация: TessaCompilerBase<T>
.
Основные члены TessaCompilerBase<T>
:
DefaultUsings
– список подключаемых по умолчанию пространств имён.DefaultStatics
– список подключаемых по умолчанию статических пространств имён.DefaultReferences
– список подключаемых по умолчанию зависимостей.DefaultIgnoreWarnings
– список кодов игнорируемых по умолчанию предупреждений.CreateContext
– создаёт контекст компилятора.CompileAsync
– выполняет компиляцию объектов системы в соответствии с заданным контекстом.PrepareCompilationContextAsync
– подготавливаетICompilationContext
к использованию.CreateTessaCompilationResultAsync
– создаёт объектITessaCompilationResult
с результатами компиляции.PrepareValidationResultAsync
– создаёт объектValidationResult
с результатами компиляции.
В платформе существует ряд компиляторов. Вот некоторые из них:
IKrCompiler
– объект, выполняющий компиляцию объектов подсистемы маршрутов.IKrVirtualFileCompiler
– объект, выполняющий компиляцию скриптов виртуальных файлов.IPlaceholderCompiler
– объект, выполняющий компиляцию текста с плейсхолдерами.
Пример создания компилятора¶
Рассмотрим пример создания компилятора скриптов виртуальных файлов.
Создадим контекст компилятора:
Tessa.Extensions.Default.Server\Files\VirtualFiles\Compilation\IKrVirtualFileCompilationContext.cs
#nullable enable
using System;
namespace Tessa.Extensions.Default.Server.Files.VirtualFiles.Compilation
{
/// <summary>
/// Контекст компиляции для компилятора <see cref="IKrVirtualFileCompiler"/>
/// </summary>
public interface IKrVirtualFileCompilationContext
{
/// <summary>
/// Идентификатор виртуального файла.
/// </summary>
Guid ID { get; set; }
/// <summary>
/// Название виртуального файла.
/// </summary>
string Name { get; set; }
/// <summary>
/// C# текст сценария инициализации виртуального файла.
/// </summary>
string? InitializationScenario { get; set; }
}
}
#nullable enable
using System;
namespace Tessa.Extensions.Default.Server.Files.VirtualFiles.Compilation
{
/// <inheritdoc cref="IKrVirtualFileCompilationContext"/>
public sealed class KrVirtualFileCompilationContext :
IKrVirtualFileCompilationContext
{
#region Fields
private string name = string.Empty;
#endregion
#region IKrVirtualFileCompilationContext Members
/// <inheritdoc/>
public Guid ID { get; set; }
/// <inheritdoc/>
public string Name
{
get => this.name;
set => this.name = NotNullOrThrow(value);
}
/// <inheritdoc/>
public string? InitializationScenario { get; set; }
#endregion
}
}
Определим интерфейс компилятора:
Tessa.Extensions.Default.Server\Files\VirtualFiles\Compilation\IKrVirtualFileCompiler.cs
#nullable enable
using Tessa.Compilation;
namespace Tessa.Extensions.Default.Server.Files.VirtualFiles.Compilation
{
/// <summary>
/// Объект, выполняющий компиляцию скриптов виртуальных файлов.
/// </summary>
public interface IKrVirtualFileCompiler :
ITessaCompiler<IKrVirtualFileCompilationContext>
{
}
}
Создадим реализацию компилятора:
Tessa.Extensions.Default.Server\Files\VirtualFiles\Compilation\KrVirtualFileCompiler.cs
#nullable enable
using System;
using System.Threading;
using System.Threading.Tasks;
using Tessa.Compilation;
using Tessa.Localization;
using Tessa.Platform.Collections;
using Tessa.Platform.Validation;
namespace Tessa.Extensions.Default.Server.Files.VirtualFiles.Compilation
{
/// <inheritdoc cref="IKrVirtualFileCompiler"/>
public sealed class KrVirtualFileCompiler :
TessaCompilerBase<IKrVirtualFileCompilationContext>,
IKrVirtualFileCompiler
{
#region Fields
private readonly ICompilationSourceProvider compilationSourceProvider;
/// <summary>
/// Параметры скрипта.
/// </summary>
private static readonly Tuple<string, string>[] parameters =
{
new Tuple<string, string>(nameof(IKrVirtualFileScriptContext), "context")
};
#endregion
#region Constructors
/// <summary>
/// Инициализирует новый экземпляр класса.
/// </summary>
/// <param name="compiler"><inheritdoc cref="ICompiler" path="/summary"/></param>
/// <param name="compilationSourceProvider"><inheritdoc cref="ICompilationSourceProvider" path="/summary"/></param>
public KrVirtualFileCompiler(
ICompiler compiler,
ICompilationSourceProvider compilationSourceProvider)
: base(compiler)
{
this.compilationSourceProvider = NotNullOrThrow(compilationSourceProvider);
this.DefaultStatics.AddRange(
CompilationHelper.TessaDefaultStatics);
this.DefaultReferences.AddRange(
CompilationHelper.TessaExtensionsDefaultReferences);
this.DefaultUsings.AddRange(
CompilationHelper.TessaDefaultUsings);
this.DefaultUsings.Add(
"Tessa.Extensions.Default.Server.Files.VirtualFiles");
this.DefaultUsings.Add(
"Tessa.Extensions.Default.Server.Files.VirtualFiles.Compilation");
}
#endregion
#region Base Overrides
/// <inheritdoc />
public override IKrVirtualFileCompilationContext CreateContext() =>
new KrVirtualFileCompilationContext();
/// <inheritdoc />
protected override ValueTask PrepareCompilationContextAsync(
IKrVirtualFileCompilationContext tessaCompilationContext,
ICompilationContext compilationContext,
CancellationToken cancellationToken = default)
{
if (!string.IsNullOrWhiteSpace(tessaCompilationContext.InitializationScenario))
{
compilationContext.Sources.Add(this.PrepareSource(
tessaCompilationContext.ID,
tessaCompilationContext.Name,
tessaCompilationContext.InitializationScenario));
}
return ValueTask.CompletedTask;
}
/// <inheritdoc />
protected override async ValueTask<ValidationResult> PrepareValidationResultAsync(
IKrVirtualFileCompilationContext tessaCompilerContext,
ICompilationResult compilationResult,
CancellationToken cancellationToken = default)
{
if (compilationResult.ValidationResult.IsSuccessful)
{
return await base.PrepareValidationResultAsync(
tessaCompilerContext,
compilationResult,
cancellationToken);
}
var result = new ValidationResultBuilder(
compilationResult.CompilerOutput.Count);
foreach (var compilerOutputItem in compilationResult.CompilerOutput)
{
var source = NotNullOrThrow(compilerOutputItem.Source);
var errorText = await LocalizationManager.FormatAsync(
"$KrVirtualFiles_CompilationErrorTemplate",
await LocalizationManager.LocalizeAsync(
source.Name,
cancellationToken),
compilerOutputItem.ToString(),
cancellationToken);
var sourceCode = CompilationHelper.FormatErrorIntoMember(
source,
compilerOutputItem,
CompilationHelper.GetMemberName(
source,
compilerOutputItem));
var validator = ValidationSequence
.Begin(result)
.SetObjectName(this);
if (compilerOutputItem.IsWarning)
{
validator.WarningDetails(errorText, sourceCode);
}
else
{
validator.ErrorDetails(errorText, sourceCode);
}
validator.End();
}
return result.Build();
}
#endregion
#region Private Methods
/// <summary>
/// Создаёт элемент компиляции.
/// </summary>
/// <param name="id">Идентификатор элемента компиляции.</param>
/// <param name="name">Имя элемента компиляции.</param>
/// <param name="source">C# сценарий.</param>
/// <returns><inheritdoc cref="ICompilationSource" path="/summary"/></returns>
private ICompilationSource PrepareSource(
Guid id,
string name,
string source)
{
var syntaxTreeBuilder = this.compilationSourceProvider
.AcquireSyntaxTree();
syntaxTreeBuilder
.SetID(id)
.SetName(name)
.Namespace("Tessa.Extensions.Default.Servier.Files.VirtualFiles.Generated")
.Class(
$"VirtualFile_{id:N}",
AccessModifier.Public,
new[] { nameof(IKrVirtualFileScript) },
guid: id)
.AddMethod(
nameof(Task),
nameof(IKrVirtualFileScript.InitializationScenarioAsync),
parameters,
source,
isAsync: true);
return syntaxTreeBuilder.Build();
}
#endregion
}
}
Кэш компиляции¶
Кэш компиляции используется для:
- хранения результатов в памяти и/или базе данных;
- формирования контекста компиляции единообразным способом для всех объектов, содержащихся в кэше;
- выполнения компиляции, с помощью соответствующего компилятора, при отсутствии доступного результата компиляции объекта;
- создания фабрики экземпляров объектов сценариев (
ITessaCompilationFactory<TKey, TInstance>
).
Существует три основных интерфейса, описывающих кэши компиляции:
ITessaCompilationObjectCacheCore<TKey, TInstance>
– описывает базовый объект кэша, предоставляющий результаты компиляции. Базовая реализация:TessaCompilationObjectCacheCoreBase<TCompilerContext, TKey, TInstance>
.ITessaCompilationObjectCache<TCompilerContext, TKey, TInstance>
– кэш, содержащий объекты результатов компиляции (ITessaCompilationObject<TKey, TInstance>
). Базовая реализация:TessaCompilationObjectCacheBase<TCompilerContext, TKey, TInstance>
.ITessaCompilationObjectInMemoryCache<TCompilerContext, TKey, TInstance>
– кэш, содержащий объекты результатов компиляции (ITessaCompilationObject<TKey, TInstance>
) в памяти, источником для которых является внешний код. Базовая реализация:TessaCompilationObjectInMemoryCacheBase<TCompilerContext, TKey, TInstance>
.
Кэш компиляции возвращает объект типа ITessaCompilationObject<TKey, TInstance>
, где TKey
– тип ключа, по которому можно получить доступ экземпляру объекта, TInstance
– тип объекта.
Result
– объект, содержащий результат компиляции объектов системы.Factory
– объект, предоставляющий экземпляры объектов типаTInstance
по ключу типаTKey
.
Фабрика экземпляров объектов (ITessaCompilationObject<TKey, TInstance>
)¶
Фабрика предназначена для создания экземпляров объектов, содержащихся в заданной сборке. Базовым классом является: TessaCompilationFactoryBase<TKey, TInstance>
, где TKey
– тип ключа, по которому можно получить доступ экземпляру объекта. TInstance
– тип объекта.
Поведение объекта определяется используемыми зависимостями:
- провайдером типов;
- провайдером идентификатора типа;
- стратегией создания экземпляров объектов;
- стратегией управления временем жизни созданного экземпляра объекта.
Рассмотрим пример создания фабрики экземпляров объектов:
// Массив байтов, который является образом в формате COFF, содержащим сборку.
byte[]? assemblyBytes = ...;
// Получение зависимостей по умолчанию.
var typeProvider = UnityContainer.Resolve<ITypeProvider>();
var typeIdentifierProvider = UnityContainer.Resolve<ITypeIdentifierProvider<Guid>>();
var instanceCreationStrategy = UnityContainer.Resolve<IInstanceCreationStrategy>();
var instanceLifetimeManager = UnityContainer.Resolve<IInstanceLifetimeManager>();
var factory = new TessaCompilationFactory<Guid, IKrVirtualFileScript>(
assemblyBytes,
typeProvider,
typeIdentifierProvider,
instanceCreationStrategy,
instanceLifetimeManager);
// Получение экземпляра объекта.
Guid keyID = ...; // Идентификатор `System.Type.GUID` типа объекта, экземпляр которого требуется создать.
var instance = factory.TryGet(keyID);
Провайдер типов (ITypeProvider
)¶
Предоставляет типы, удовлетворяющие некоторому условию. Реализация по умолчанию (TypeProvider
) возвращает все не абстрактные типы, которые наследуются от заданного базового типа.
Провайдер идентификатора типа (ITypeIdentifierProvider<T>
)¶
Предоставляет идентификатор для заданного типа. В типовом решении, есть две реализации:
TypeGuidIdentifierProvider
– объект, предоставляющий идентификатор типа (System.Type.GUID
). Объект можно получить из Unity-контейнера по интерфейсуITypeIdentifierProvider<Guid>
.TypeNameIdentifierProvider
– объект, предоставляющий имя типа (System.Reflection.MemberInfo.Name
). Объект можно получить из Unity-контейнера по интерфейсуITypeIdentifierProvider<string>
.
Стратегия создания экземпляров объектов (IInstanceCreationStrategy
)¶
Определяет способ создания экземпляра объекта заданного типа. Реализация по умолчанию (DefaultInstanceCreationStrategy
) создаёт экземпляр, используя конструктор без параметров.
Tip
Типовые названия объектов, по которым они зарегистрированы в unity-контейнере, содержатся в Tessa.Compilation.InstanceCreationStrategyNames
.
Стратегия управления временем жизни экземпляра объекта (IInstanceLifetimeManager
)¶
Определяет время жизни созданного экземпляра объекта, при использовании в фабрике TessaCompilationFactoryBase<TKey, TInstance>
.
В типовом решении есть две реализации:
TransientInstanceLifetimeManager
– объект, не управляющий временем жизни заданного значения.SingletonInstanceLifetimeManager
– объект, управляющий временем жизни заданного значения с помощью стратегии синглтон.
Tip
Типовые названия объектов, по которым они зарегистрированы в unity-контейнере, содержатся в Tessa.Compilation.InstanceLifetimeManagerNames
.
Пример создания кэша компиляции¶
В примере будет использован компилятор, созданный в п. Пример создания компилятора.
Определим интерфейс кэша компиляции:
Tessa.Extensions.Default.Server\Files\VirtualFiles\Compilation\IKrVirtualFileCompilationCache.cs
#nullable enable
using System;
using Tessa.Compilation;
using Tessa.Extensions.Default.Shared;
namespace Tessa.Extensions.Default.Server.Files.VirtualFiles.Compilation
{
/// <summary>
/// Кэш, содержащий результаты компиляции скриптов виртуальных файлов.
/// </summary>
/// <remarks>Категория кэша: <see cref="DefaultCompilationCacheNames.KrVirtualFile"/>.</remarks>
public interface IKrVirtualFileCompilationCache :
ITessaCompilationObjectCache<IKrVirtualFileCompilationContext, Guid, IKrVirtualFileScript>
{
}
}
Реализуем его:
Tessa.Extensions.Default.Server\Files\VirtualFiles\Compilation\KrVirtualFileCompilationCache.cs
#nullable enable
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Tessa.Compilation;
using Tessa.Extensions.Default.Shared;
using Tessa.Platform;
using Unity;
namespace Tessa.Extensions.Default.Server.Files.VirtualFiles.Compilation
{
/// <inheritdoc cref="IKrVirtualFileCompilationCache"/>
public sealed class KrVirtualFileCompilationCache :
TessaCompilationObjectCacheBase<IKrVirtualFileCompilationContext, Guid, IKrVirtualFileScript>,
IKrVirtualFileCompilationCache
{
#region Fields
private readonly IKrVirtualFileCache krVirtualFileCache;
private readonly ITypeProvider typeProvider;
private readonly ITypeIdentifierProvider<Guid> typeIdentifierProvider;
private readonly IInstanceCreationStrategy instanceCreationStrategy;
private readonly IInstanceLifetimeManager instanceLifetimeManager;
#endregion
#region Constructors
/// <summary>
/// Инициализирует новый экземпляр класса.
/// </summary>
/// <param name="tessaCompilationObjectGlobalCache"><inheritdoc cref="TessaCompilationObjectGlobalCache" path="/summary"/></param>
/// <param name="compiler"><inheritdoc cref="IKrVirtualFileCompiler" path="/summary"/></param>
/// <param name="tessaCompilationRepository"><inheritdoc cref="ITessaCompilationRepository" path="/summary"/></param>
/// <param name="krVirtualFileCache"><inheritdoc cref="IKrVirtualFileCache" path="/summary"/></param>
/// <param name="typeProvider"><inheritdoc cref="ITypeProvider" path="/summary"/></param>
/// <param name="typeIdentifierProvider"><inheritdoc cref="ITypeIdentifierProvider{T}" path="/summary"/></param>
/// <param name="instanceCreationStrategy"><inheritdoc cref="IInstanceCreationStrategy" path="/summary"/></param>
/// <param name="instanceLifetimeManager"><inheritdoc cref="IInstanceLifetimeManager" path="/summary"/></param>
/// <param name="unityDisposableContainer"><inheritdoc cref="IUnityDisposableContainer" path="/summary"/></param>
public KrVirtualFileCompilationCache(
TessaCompilationObjectGlobalCache tessaCompilationObjectGlobalCache,
IKrVirtualFileCompiler compiler,
ITessaCompilationRepository tessaCompilationRepository,
IKrVirtualFileCache krVirtualFileCache,
ITypeProvider typeProvider,
ITypeIdentifierProvider<Guid> typeIdentifierProvider,
IInstanceCreationStrategy instanceCreationStrategy,
[Dependency(InstanceLifetimeManagerNames.Singleton)] IInstanceLifetimeManager instanceLifetimeManager,
[OptionalDependency] IUnityDisposableContainer? unityDisposableContainer = null)
: base(
DefaultCompilationCacheNames.KrVirtualFile,
tessaCompilationObjectGlobalCache,
compiler,
tessaCompilationRepository,
unityDisposableContainer)
{
this.krVirtualFileCache = NotNullOrThrow(krVirtualFileCache);
this.typeProvider = NotNullOrThrow(typeProvider);
this.typeIdentifierProvider = NotNullOrThrow(typeIdentifierProvider);
this.instanceCreationStrategy = NotNullOrThrow(instanceCreationStrategy);
this.instanceLifetimeManager = NotNullOrThrow(instanceLifetimeManager);
}
#endregion
#region Base Overrides
/// <inheritdoc/>
protected override async Task<TessaCompilationContext<IKrVirtualFileCompilationContext>> GetCompilerContextAsync(
Guid id,
Func<IKrVirtualFileCompilationContext> getCompilerContextFunc,
CancellationToken cancellationToken = default)
{
var krVirtualFile = await this.krVirtualFileCache.TryGetAsync(
id,
cancellationToken);
if (krVirtualFile is null)
{
return new TessaCompilationContext<IKrVirtualFileCompilationContext>(
id)
{
ValidationResult = this.CreateSourceObjectNotFoundValidationResult(
id),
};
}
var context = getCompilerContextFunc();
context.ID = id;
context.Name = krVirtualFile.Name;
context.InitializationScenario = krVirtualFile.InitializationScenario;
return new TessaCompilationContext<IKrVirtualFileCompilationContext>(
id)
{
CompilerContext = context,
};
}
/// <inheritdoc/>
protected override async Task<IList<TessaCompilationContext<IKrVirtualFileCompilationContext>>> GetCompilerContextAsync(
Func<IKrVirtualFileCompilationContext> getCompilerContextFunc,
CancellationToken cancellationToken = default)
{
var krVirtualFiles = await this.krVirtualFileCache.GetAllAsync(
cancellationToken);
var contexts = new List<TessaCompilationContext<IKrVirtualFileCompilationContext>>(krVirtualFiles.Length);
foreach (var krVirtualFile in krVirtualFiles)
{
var context = getCompilerContextFunc();
context.ID = krVirtualFile.ID;
context.Name = krVirtualFile.Name;
context.InitializationScenario = krVirtualFile.InitializationScenario;
contexts.Add(
new TessaCompilationContext<IKrVirtualFileCompilationContext>(
context.ID)
{
CompilerContext = context,
});
}
return contexts;
}
/// <inheritdoc/>
protected override ValueTask<ITessaCompilationFactory<Guid, IKrVirtualFileScript>> CreateCompilationFactoryAsync(
ITessaCompilationResult tessaCompilationResult,
CancellationToken cancellationToken = default)
{
return ValueTask.FromResult<ITessaCompilationFactory<Guid, IKrVirtualFileScript>>(
new TessaCompilationFactory<Guid, IKrVirtualFileScript>(
tessaCompilationResult.AssemblyBytes,
this.typeProvider,
this.typeIdentifierProvider,
this.instanceCreationStrategy,
this.instanceLifetimeManager));
}
#endregion
}
}
Пример использования кэша компиляции¶
Рассмотрим пример использования кэша компиляции, созданного в предыдущем пункте:
var compilationCache = UnityContainer.Resolve<IKrVirtualFileCompilationCache>();
var validationResult = new ValidationResultBuilder();
Guid virtualFileID = ...;
// Получение объекта компиляции для виртуального файла с заданным идентификатором.
var compilationResult = await compilationCache.GetAsync(
virtualFileID,
cancellationToken: cancellationToken);
// Обработка ошибок компиляции.
validationResult.Add(compilationResult.Result.ValidationResult);
if (!compilationResult.Result.ValidationResult.IsSuccessful)
{
return;
}
// Создание экземпляра объекта скрипта.
// В данном примере объект компиляции содержит только один объект и его идентификатор совпадает с идентификатором объекта компиляции.
// В общем случае здесь может быть указан любой ключ, по которому запрашивается экземпляр объекта.
IKrVirtualFileScript scriptInstance = compilationResult.Factory.TryGet(virtualFileID);
// Проверка успешности создания экземпляра сценария.
if (scriptInstance is null)
{
validationResult.AddError(this, $"Class {virtualFileID} not found.");
return;
}
await scriptInstance.InitializationScenarioAsync(context);
Низкоуровневое API для управления хранением результатов компиляции в базе данных¶
Результаты компиляции всех объектов хранятся в базе данных в таблице CompilationCache
.
Note
Можно безопасно удалять объекты напрямую из базы данных.
Для доступа к её объектам существует репозиторий ITessaCompilationRepository
.
Основные методы:
GetAsync
– возвращает результат компиляции, имеющий указанный идентификатор.StoreAsync
– сохраняет результат компиляции по указанному идентификатору.DeleteAsync
– удаляет результаты компиляции.