Создание серверного расширения Web-клиента
В описателе элемента управления был объявлен конвертер для значения элемента — RefCasesConverter
(свойство Binding
), который должен приводить настоящее значение (идентификатор строки Дела в Справочнике номенклатуры дел 5) к виду, удобному для пользователя — в данном примере к строке вида: Год. Название разделов. Название дела
.
Также для работы клиентской части понадобятся методы, предоставляющие список лет, дерево разделов и список дел из Справочника номенклатуры дел 5, а также функцию для поиска дела по названию (если нужно реализовать функцию поиска на клиенте).
Сервис для работы со Справочником номенклатуры дел 5
Прежде всего необходимо реализовать сервис для работы со Справочником номенклатуры дел 5, методы которого должны:
-
Возвращать список лет.
-
Возвращать год для определённой секции — нужно, если в программе Конструктор Web-разметок был выбран раздел, из которого выбираются дела.
-
Возвращать все разделы для определённого года, а если требуется, только разделы из корневого раздела, выбранного в программе Конструктор Web-разметок.
-
Возвращать все дела из определённого раздела.
-
Возвращать отображаемое название дела — для конвертера значений
RefCasesConverter
. -
Возвращать список дел с искомым названием, а если требуется, только дела из корневого раздела, выбранного в программе Конструктор Web-разметок.
Ниже представлен интерфейс сервиса, описывающего установленные требования. Первые два условия для сервиса объединены в один метод GetYears
.
RefCasesServerExtension\Services\IRefCasesService.cs
using RefCasesServerExtension.Models;
using System;
using System.Collections.Generic;
namespace RefCasesServerExtension.Services
{
public interface IRefCasesService
{
List<Year> GetYears(Guid? rootSection = null);
List<Section> GetSections(Guid yearID, Guid? rootSection = null);
List<Case> GetCases(Guid sectionID);
string GetCaseTitle(Guid caseID);
List<CaseClientModel> SearchCases(string caseName, Guid? rootSection = null);
}
}
И реализация сервиса IRefCasesService
:
RefCasesServerExtension\Services\RefCasesService.cs
using DocsVision.Platform.ObjectManager;
using DocsVision.Platform.WebClient;
using DocsVision.Platform.WebClient.Managers;
using RefCasesServerExtension.Models;
using System;
using System.Collections.Generic;
using System.Linq;
namespace RefCasesServerExtension.Services
{
public class RefCasesService : IRefCasesService
{
private Guid refCasesId = new Guid("246197EA-846A-44DA-9EA3-0BCAE5500388"); (1)
private Guid yearsSectionId = new Guid("BD44C786-6E41-450E-BD60-66919657E51B"); (2)
private Guid sectionsSectionID = new Guid("319E425D-543C-45DB-BD51-955B58476EDB"); (3)
private Guid caseSectionID = new Guid("56af8231-b918-42d4-ac15-90ec2e9a0725"); (4)
(5)
private const string Year = "Year";
private const string DisplayIndex = "DisplayIndex";
private const string Case_Name = "Case_Name";
private const string Case_Index = "Case_Index";
private const string Case_SectionDisplayIndex = "Case_SectionDisplayIndex";
private const string Name = "Name";
ICurrentObjectContextProvider objectContextProvider;
private AdvancedCardManager cardManager => objectContextProvider.GetOrCreateCurrentSessionContext().AdvancedCardManager;
public RefCasesService(ICurrentObjectContextProvider objectContextProvider) => this.objectContextProvider = objectContextProvider;
public List<Year> GetYears(Guid? rootSection) (6)
{
CardData refCasesData = cardManager.GetDictionaryData(refCasesId);
SectionData yearSection = refCasesData.Sections[yearsSectionId];
if (rootSection.HasValue && refCasesData.Sections[sectionsSectionID].RowExists(rootSection.Value)) (7)
{
var sectionRow = refCasesData.Sections[sectionsSectionID].GetRow(rootSection.Value);
var yearRow = sectionRow.SubSection.ParentRow;
return new List<Year>() {
new Year() { ID = yearRow.Id, DisplayValue = yearRow[Year].ToString() }
};
}
return yearSection.Rows.Select<RowData, Year>(row => new Year() (8)
{
ID = row.Id,
DisplayValue = row[Year].ToString()
}).ToList();
}
public List<Section> GetSections(Guid yearID, Guid? rootSection) (9)
{
CardData refCasesData = cardManager.GetDictionaryData(refCasesId);
SectionData sectionsSection = refCasesData.Sections[sectionsSectionID];
if (rootSection.HasValue && sectionsSection.RowExists(rootSection.Value)) (10)
{
RowData row = sectionsSection.GetRow(rootSection.Value);
return GetSectionsFromRows(new List<RowData>() { row });
}
SectionData yearSection = refCasesData.Sections[yearsSectionId];
if (yearSection.RowExists(yearID)) (11)
{
RowDataCollection sectionRows = yearSection.GetRow(yearID).ChildSections[sectionsSectionID].Rows;
return GetSectionsFromRows(sectionRows);
}
return new List<Section>();
}
public List<Case> GetCases(Guid sectionID) (12)
{
CardData refCasesData = cardManager.GetDictionaryData(refCasesId);
SectionData sectionsSection = refCasesData.Sections[sectionsSectionID];
if (sectionsSection.RowExists(sectionID))
{
RowDataCollection sectionRows = sectionsSection.GetRow(sectionID).ChildSections[caseSectionID].Rows;
return GetCasesFromRows(sectionRows);
}
return new List<Case>();
}
public string GetCaseTitle(Guid caseID) (13)
{
CardData refCasesData = cardManager.GetDictionaryData(refCasesId);
SectionData section = refCasesData.Sections[caseSectionID];
if (section.RowExists(caseID))
{
RowData caseRow = section.GetRow(caseID); (14)
var sectionRow = caseRow.SubSection.ParentRow;
var yearRow = sectionRow.SubSection.ParentRow;
return string.Format("{0}, {1}, {2}", yearRow[Year], sectionRow[DisplayIndex], caseRow[Case_Name]);
}
return "Ошибка!";
}
public List<CaseClientModel> SearchCases(string caseName, Guid? rootSection = null) (15)
{
CardData refCasesData = cardManager.GetDictionaryData(refCasesId);
RowDataCollection allRows;
if (rootSection.HasValue && refCasesData.Sections[sectionsSectionID].RowExists(rootSection.Value)) (16)
allRows = refCasesData.Sections[caseSectionID].GetAllRows(rootSection.Value, true);
else
allRows = refCasesData.Sections[caseSectionID].GetAllRows();
var results = new List<CaseClientModel>();
foreach (var caseRow in allRows)
{
if (caseRow[Case_Name].ToString().IndexOf(caseName, StringComparison.InvariantCultureIgnoreCase) > -1 ||
(caseRow[Case_SectionDisplayIndex].ToString() + "-" + caseRow[Case_Index].ToString()).IndexOf(caseName, StringComparison.InvariantCultureIgnoreCase) > -1) (17)
{
var sectionRow = caseRow.SubSection.ParentRow;
var yearRow = sectionRow.SubSection.ParentRow;
results.Add(new CaseClientModel()
{
Id = caseRow.Id,
Name = string.Format("{0}, {1}, {2}", yearRow[Year], sectionRow[DisplayIndex], caseRow[Case_Name]) (18)
});
}
}
return results;
}
List<Section> GetSectionsFromRows(IEnumerable<RowData> rows) (19)
{
var nodes = new List<Section>();
foreach (var row in rows)
{
var node = new Section() { ID = row.Id, DisplayValue = row[Name].ToString() };
if (row.HasChildRows)
node.Sections = GetSectionsFromRows(row.ChildRows);
nodes.Add(node);
}
return nodes;
}
List<Case> GetCasesFromRows(RowDataCollection rows) (20)
{
var nodes = new List<Case>();
foreach (var row in rows)
{
var node = new Case() { ID = row.Id, DisplayValue = row[Case_Name].ToString() };
if (row.HasChildRows)
node.Cases = GetCasesFromRows(row.ChildRows);
nodes.Add(node);
}
return nodes;
}
}
}
1 | Справочник номенклатуры дел 5. |
2 | Секция Года справочника. |
3 | Секция Разделы справочника. |
4 | Секция Дела справочника. |
5 | Названия полей. |
6 | Возвращает список лет из Справочника номенклатуры дел 5. |
7 | Если выбран корневой раздел, нужно вернуть только его Год. |
8 | Иначе возвращаем все Года. |
9 | Возвращает дерево разделов указанного Года из Справочника номенклатуры дел 5. |
10 | Если выбран корневой раздел, вернуть только его подразделы. |
11 | Иначе вернуть все разделы указанного Года. |
12 | Вернуть все дела указанного Раздела. |
13 | Вернуть отображаемое название Дела. |
14 | Получаем для дела родительские строки раздела и Года. |
15 | Поиск дела по названию и по идентификатору Дела. |
16 | Если указан коневой Раздел, поиск только в нём, иначе — во всех Разделах. |
17 | Проверяем название Раздела — поле Case_Name . |
18 | Возвращаем сразу отображаемое название. |
19 | Возвращаем список Разделов для строк секции справочника. |
20 | Возвращаем список Дел для строк справочника. |
Конвертер "RefCasesConverter"
Как уже было сказано ранее, конвертер нужен для формирования отображаемого значения элемента управления, показываемого при инициализации элемента. Иначе в элементе будет показан идентификатор выбранного дела.
Для разрабатываемого элемента управления название конвертера (RefCasesConverter
) было объявлено в описателе элемента управления. Теперь его нужно реализовать и зарегистрировать в серверном расширении Web-клиента.
В данном пример конвертер использует метод IRefCasesService.GetCaseTitle
для получения отображаемого названия Дела.
RefCasesServerExtension\BindingConverters\RefCasesConverter.cs
using DocsVision.WebClientLibrary.Layout.IL;
using DocsVision.WebClientLibrary.ObjectModel;
using DocsVision.WebClientLibrary.ObjectModel.Services.BindingConverters;
using DocsVision.WebClientLibrary.ObjectModel.Services.LayoutModel;
using RefCasesServerExtension.Models;
using RefCasesServerExtension.Services;
using System;
namespace RefCasesServerExtension.BindingConverters
{
public class RefCasesConverter : BaseBindingConverter (1)
{
private IRefCasesService refCasesService;
public RefCasesConverter(IServiceProvider serviceProvider, IRefCasesService refCasesService) : base(serviceProvider, "RefCasesConverter") (2)
{
this.refCasesService = refCasesService; (3)
}
public override BindingResult ConvertForDisplay(ControlContext controlContext, LayoutBinding binding, BindingResult bindingResult) (4)
{
var itemId = bindingResult.Value != null ? (Guid)bindingResult.Value : Guid.Empty;
var name = itemId == Guid.Empty ? "" : refCasesService.GetCaseTitle(itemId);
var model = new CaseClientModel() { Id = itemId, Name = name }; (5)
return bindingResult.Clone(model);
}
}
}
1 | Класс конвертера может быть производным от BaseBindingConverter или полностью реализовывать интерфейс IBindingConverter . |
2 | В базовый класс нужно передать название конвертера, указанного в описателе RefCasesConverter . |
3 | Получаем реализованный ранее сервис для работы со Справочником номенклатуры дел 5. |
4 | Основной метод, возвращающий отображаемое значение (а точнее модель) для значения элемента управления — bindingResult.Value . |
5 | Клиент ожидает модель, включающую идентификатор и название Дела. |
Веб-методы серверного расширения
Клиентской части элемента управления нужны данные из Справочника номенклатуры дел 5: для показа списка лет, разделов, а также дел. Предоставить доступ к данным можно с помощью веб-методов серверного расширения.
Далее приведён код контроллера, предоставляющего такие веб-методы.
RefCasesServerExtension\Controllers\RefCasesOperationController.cs
using RefCasesServerExtension.Models;
using RefCasesServerExtension.Services;
using System;
using System.Linq;
using DocsVision.Platform.WebClient.Models;
using DocsVision.Platform.WebClient.Models.Generic;
using Microsoft.AspNetCore.Mvc;
namespace RefCasesServerExtension.Controllers
{
public class RefCasesOperationController : ControllerBase
{
private readonly IRefCasesService refCasesService;
public RefCasesOperationController(IRefCasesService refCasesService) (1)
{
this.refCasesService = refCasesService;
}
[HttpPost]
public Year[] GetYears([FromQuery]Guid? rootSectionID) (2)
{
return refCasesService.GetYears(rootSectionID).ToArray();
}
[HttpPost]
public Section[] GetSections([FromQuery]Guid yearID, [FromQuery]Guid? rootSectionID) { (3)
return refCasesService.GetSections(yearID, rootSectionID).ToArray();
}
[HttpPost]
public Case[] GetCases([FromQuery]Guid sectionID) (4)
{
return refCasesService.GetCases(sectionID).ToArray();
}
[HttpPost]
public string GetCaseDisplayName([FromQuery]Guid caseID) (5)
{
return refCasesService.GetCaseTitle(caseID);
}
[HttpPost]
public CaseSearchResult SearchCase([FromQuery]string caseName, [FromQuery]int skipCount, [FromQuery]int maxCount, [FromQuery]Guid? rootSectionID) (6)
{
var rows = refCasesService.SearchCases(caseName, rootSectionID); (7)
var result = new CaseSearchResult
{
Items = rows.Skip(skipCount).Take(maxCount).ToArray(), (8) (9)
HasMore = rows.Count > skipCount + maxCount
}; (10)
return result;
}
}
}
1 | В конструкторе контроллера получаем ссылку на реализованный сервис для работы со Справочником номенклатуры дел 5. |
2 | Возвращает список лет. |
3 | Возвращает список разделов. |
4 | Возвращает список дел. |
5 | Возвращает отображаемое название дела. |
6 | Поиск дела по имени. |
7 | Получаем все подходящие дела. |
8 | Оставляем только количество запрошенных клиентом. |
9 | Т. к. количество результатов поискового запроса SearchCase может быть достаточно большим, клиенту предоставлена возможность ограничивать его с помощью параметров skipCount (количество пропускаемых результатов) и maxCount (максимальное количество результатов, принимаемых клиентом за один раз). |
10 | Устанавливаем флаг наличия дополнительных результатов. |
Ядро серверного расширения
Реализованные сущности необходимо зарегистрировать в серверном расширении.
RefCasesServerExtension\RefCasesServerExtension.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Resources;
using Autofac;
using Autofac.Extras.Ordering;
using DocsVision.WebClient.Extensibility;
using DocsVision.WebClient.Helpers;
using DocsVision.WebClientLibrary.ObjectModel.Services.BindingConverters;
using Microsoft.Extensions.DependencyInjection;
using RefCasesServerExtension.BindingConverters;
using RefCasesServerExtension.Services;
namespace RefCasesServerExtension
{
public class RefCasesServerExtension : WebClientExtension
{
public RefCasesServerExtension(IServiceProvider serviceProvider)
: base()
{
}
public override string ExtensionName
{
get { return Assembly.GetAssembly(typeof(RefCasesServerExtension)).GetName().Name; }
}
public override Version ExtensionVersion
{
get { return new Version(FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion); }
}
#region WebClientExtension Overrides
public override void InitializeServiceCollection(IServiceCollection services) (1)
{
services.AddSingleton<IBindingConverter, RefCasesConverter>(); (2)
services.AddSingleton<IRefCasesService, RefCasesService>(); (3)
}
protected override List<ResourceManager> GetLayoutExtensionResourceManagers() (4)
{
return new List<ResourceManager>
{
{ Resources.ResourceManager}
};
}
#endregion
}
}
1 | Регистрация компонентов, реализованных в расширении. |
2 | Регистрируем конвертер. |
3 | Регистрируем сервисы |
4 | Также для примера добавлено несколько локализованных текстовых ресурсов, которые будут использоваться в клиентском расширении. |
Контроллеры регистрировать не требуется. |