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– удаляет результаты компиляции.