Перейти к содержанию

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); } } } }

Back to top