REST-метод для создания карточки с файлом, приложенным по шаблону
REST-метод для создания карточки с файлом, приложенным по шаблону¶
Требуется написать REST-контроллер с GET-методом, используемым для интеграции с другой системой. Метод не выполняет логина и не создаёт сессию. Он создаёт карточку типа “Протокол”, к которой тут же прикладывает файл, созданный по шаблону “Протокол совещания”.
Если карточка успешно создана, то возвращается её структура в текстовом формате JSON, а если произошла ошибка, то она выводится также в JSON-форме, но предваряется строкой “Error:”.
Класс контроллера расположите в проекте расширений Tessa.Extensions.Server.Web. Класс будет иметь GET-метод, доступный по пути protocols/with-file
относительно адреса веб-сервиса. Например: https://localhost/tessa/protocols/with-file
Каждый раз при обращении по этому адресу будет выведена структура созданной карточки в форме JSON. Открыв карточку в приложении, вы сможете просмотреть файл, сгенерированный по шаблону и приложенный к карточке.
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Tessa.Cards;
using Tessa.Cards.ComponentModel;
using Tessa.Files;
using Tessa.Platform;
using Tessa.Platform.Runtime;
using Tessa.Platform.Validation;
using Tessa.Web;
using Tessa.Web.Services;
using Unity;
namespace Tessa.Extensions.Server.Web.Services
{
[Route("protocols"), AllowAnonymous]
public sealed class ProtocolsController : TessaControllerBase
{
public ProtocolsController(ITessaWebScope scope) => this.scope = scope;
private readonly ITessaWebScope scope;
private T Resolve<T>(string name = null) => this.scope.UnityContainer.Resolve<T>(name);
// GET protocols/with-file
[HttpGet("with-file")]
public async Task<string> CreateWithFileFromTemplate()
{
// Внимание! Этот метод никак не защищён от доступа "извне",
// вы можете реализовать отдельный метод логина и пробрасывание токена, см. класс ServiceController
// типовой шаблон "Протокол совещания.docx"
Guid templateID = new Guid("49e340f6-c478-4a11-8b03-999aa22aa9fd");
var serverSettings = this.Resolve<ITessaServerSettings>();
var transactionStrategy = this.Resolve<ICardTransactionStrategy>();
var permissionsProvider = this.Resolve<ICardServerPermissionsProvider>();
var cardRepository = this.Resolve<ICardRepository>();
var cardStreamRepository = this.Resolve<ICardStreamServerRepository>();
var cardFileManager = this.Resolve<ICardFileManager>();
// код внутри будет выполняться от имени пользователя System
using (SessionContext.Create(Session.CreateSystemToken(SessionType.Server, serverSettings)))
{
Card resultingCard = null;
// код выполняем в транзакции, чтобы при наличии любых ошибок и исключений откатить состояние базы данных;
// зависимости cardRepository, cardStreamRepository и cardFileManager не резолвятся по имени
// ***WithoutTransaction, т.к. нам нужны блокировки на запись/чтение создаваемой карточки,
// при этом транзакцию система сама "подхватит" из transactionStrategy
var validationResult = new ValidationStorageResultBuilder();
bool success = await transactionStrategy.ExecuteInTransactionAsync(
validationResult,
async p =>
{
// создаём структуру карточки в памяти
var newRequest = new CardNewRequest { CardTypeName = "Protocol" };
permissionsProvider.SetFullPermissions(newRequest);
CardNewResponse newResponse = await cardRepository.NewAsync(newRequest, p.CancellationToken);
p.ValidationResult.Add(newResponse.ValidationResult);
if (!newResponse.ValidationResult.IsSuccessful())
{
// здесь и ниже установка ReportError приведёт к откату транзакции
// и возврату false из метода ExecuteInTransaction
p.ReportError = true;
return;
}
// заполняем обязательные поля в карточке
Card card = newResponse.Card;
card.ID = Guid.NewGuid();
card.DynamicEntries.DocumentCommonInfo.Subject = "Test subject";
// сохраняем карточку первый раз; чтобы можно было использовать
// объект card после сохранения - клонируем его)
Card cardToStore = card.Clone();
cardToStore.RemoveAllButChanged(cardToStore.StoreMode);
var storeRequest = new CardStoreRequest { Card = cardToStore };
CardStoreResponse storeResponse = await cardRepository.StoreAsync(
storeRequest, p.CancellationToken);
p.ValidationResult.Add(storeResponse.ValidationResult);
if (!storeResponse.ValidationResult.IsSuccessful())
{
p.ReportError = true;
return;
}
// здесь мы могли бы загрузить карточку из базы и продолжить работать уже с ней,
// но есть способ обойтись без загрузки, для этого устанавливаем актуальную версию карточки
// и удаляем все флаги по изменениям, чтобы они повторно
card.Version = storeResponse.CardVersion;
card.RemoveChanges();
// теперь создаём файл по шаблону, причём файл сможет использовать данные в базе
// (включая Subject, установленный выше)
ICardFileContentResult contentResult =
await cardStreamRepository.GenerateFileFromTemplateAsync(
templateID, card.ID, cancellationToken: p.CancellationToken);
p.ValidationResult.Add(contentResult.Response.ValidationResult);
if (!contentResult.Response.ValidationResult.IsSuccessful())
{
p.ReportError = true;
return;
}
// добавляем созданный файл в контейнер
await using (ICardFileContainer container =
await cardFileManager.CreateContainerAsync(card, cancellationToken: p.CancellationToken))
{
// часто файловый шаблон сразу предлагает нам имя файла в зависимости от настроек шаблона
string suggestedFileName = contentResult.Response.TryGetSuggestedFileName()
?? "Protocol.docx";
// размер файла необходимо знать для его сохранения
long size = contentResult.Response.Size;
// добавляем файл через функцию на контент Func<Stream>, в этом случае
// не используется временная папка на сервере для сохранения файлов
await container.FileContainer
.BuildFile(suggestedFileName)
.SetContent(contentResult.GetContentOrThrowAsync, async ct => size)
.AddWithNotificationAsync(cancellationToken: p.CancellationToken);
// для сохранения файла обязательно используем контейнер
CardStoreResponse fileStoreResponse = await container.StoreAsync(
async (c, request, ct) =>
{
permissionsProvider.SetFullPermissions(request);
},
cancellationToken: p.CancellationToken);
p.ValidationResult.Add(fileStoreResponse.ValidationResult);
if (!fileStoreResponse.ValidationResult.IsSuccessful())
{
p.ReportError = true;
return;
}
}
// если всё успешно, то мы договорились возвращать содержимое карточки в виде JSON
// т.к. после сохранений карточка могла сильно измениться - сначала загружаем её
var getRequest = new CardGetRequest { CardID = card.ID };
permissionsProvider.SetFullPermissions(getRequest);
CardGetResponse getResponse = await cardRepository.GetAsync(getRequest, p.CancellationToken);
p.ValidationResult.Add(getResponse.ValidationResult);
if (!getResponse.ValidationResult.IsSuccessful())
{
p.ReportError = true;
return;
}
resultingCard = getResponse.Card;
});
return success && resultingCard != null
? resultingCard.ToJson()
: "Error:\n" + validationResult.ToJson();
}
}
}
}