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.Http;
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;
namespace Tessa.Extensions.Server.Web.Services
{
[Route("protocols"), ApiController, AllowAnonymous]
public sealed class ProtocolsController : Controller
{
public ProtocolsController(
ITessaServerSettings serverSettings,
ICardTransactionStrategy transactionStrategy,
ICardServerPermissionsProvider permissionsProvider,
ICardRepository cardRepository,
ICardStreamServerRepository cardStreamRepository,
ICardFileManager cardFileManager)
{
this.serverSettings = serverSettings;
this.transactionStrategy = transactionStrategy;
this.permissionsProvider = permissionsProvider;
this.cardRepository = cardRepository;
this.cardStreamRepository = cardStreamRepository;
this.cardFileManager = cardFileManager;
}
private readonly ITessaServerSettings serverSettings;
private readonly ICardTransactionStrategy transactionStrategy;
private readonly ICardServerPermissionsProvider permissionsProvider;
private readonly ICardRepository cardRepository;
private readonly ICardStreamServerRepository cardStreamRepository;
private readonly ICardFileManager cardFileManager;
// GET protocols/with-file
[HttpGet("with-file")]
public async Task<ActionResult<Card>> CreateWithFileFromTemplate()
{
// Внимание! Этот метод никак не защищён от доступа "извне",
// вы можете реализовать отдельный метод логина и пробрасывание токена, см. класс ServiceController
// типовой шаблон "Протокол совещания.docx"
Guid templateID = new Guid("49e340f6-c478-4a11-8b03-999aa22aa9fd");
// код внутри будет выполняться от имени пользователя System
await using (SessionContext.Create(Session.CreateSystemToken(SessionType.Server, serverSettings)))
{
Card resultingCard = null;
// код выполняем в транзакции, чтобы при наличии любых ошибок и исключений откатить состояние базы данных;
// зависимости cardRepository, cardStreamRepository и cardFileManager не резолвятся по имени
// ***WithoutTransaction, т.к. нам нужны блокировки на запись/чтение создаваемой карточки,
// при этом транзакцию система сама "подхватит" из transactionStrategy
var validationResult = new ValidationStorageResultBuilder();
bool success = await this.transactionStrategy.ExecuteInTransactionAsync(
validationResult,
async p =>
{
// создаём структуру карточки в памяти
var newRequest = new CardNewRequest { CardTypeName = "Protocol" };
this.permissionsProvider.SetFullPermissions(newRequest);
CardNewResponse newResponse = await this.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 this.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 this.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 this.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) =>
{
this.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 };
this.permissionsProvider.SetFullPermissions(getRequest);
CardGetResponse getResponse = await this.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
? this.TypedJson(resultingCard)
: this.TypedJson(validationResult, StatusCodes.Status400BadRequest);
}
}
}
}