public override Task RouteAsync(RouteContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            var startPage = context.HttpContext.Items[RoutingKeys.StartPage] as IStartPage;//стартовая страница, проставляется в RoutingMiddleware

            if (startPage == null)
            {
                return(Task.CompletedTask);
            }

            var targetingFilter = TargetingProvider?.Get();//все зарегистрированные фильтры структуры сайта, объединенные в один

            //вычислим PathData текущего запроса - какая страница в структуре сайта должна быть активирована
            PathData data = null;

            if (SavePathDataInHttpContext)
            {
                //может быть в рамках текущего запроса мы уже вычислили PathData и сохранили его в HttpContext
                //тогда получим его оттуда и не будем повторно вычислять
                data = context.HttpContext.Items[RoutingKeys.PathData] as PathData;
            }

            if (data == null)
            {
                //особый кейс: хотим открыть страницу сайта по её id, например, из админки структуры сайта
                //в этом случае мы находимся в режиме onScreen и передан id страницы (abstractItem'а)
                var onScreenContext     = context.HttpContext.Items[OnScreenModeKeys.OnScreenContext] as OnScreenContext;
                var abstractItemStorage = context.HttpContext.Items[RoutingKeys.AbstractItemStorage] as AbstractItemStorage;

                if (onScreenContext?.Enabled == true && onScreenContext?.PageId.HasValue == true && abstractItemStorage != null)
                {
                    var abstractItem         = abstractItemStorage.Get(onScreenContext.PageId.Value);
                    var isContainInStartPage = IsStartPageContainAbstractItem(startPage, abstractItem);
                    if (isContainInStartPage)
                    {
                        data = new PathData(abstractItem, "");
                    }
                }
            }

            var path = context.HttpContext.Request.Path;

            if (data == null)
            {
                //вычислим PathData разбирая урл запроса, и сопоставляя сегменты со структурой сайта
                data = CreatePathFinder().Find(path, startPage, targetingFilter, HeadUrlResolver);
            }

            if (data == null)
            {
                return(Task.CompletedTask);
            }

            if (SavePathDataInHttpContext)
            {
                context.HttpContext.Items[RoutingKeys.PathData] = data;
            }

            var controllerName = ControllerMapper.Map(data.AbstractItem);

            if (string.IsNullOrEmpty(controllerName))
            {
                return(Task.CompletedTask);
            }

            //проставим AbstractItem в RouteData
            context.RouteData.Values[RoutingKeys.CurrentItem]     = data.AbstractItem.Id;
            context.RouteData.DataTokens[RoutingKeys.CurrentItem] = data.AbstractItem;

            //подменим HttpContext.Request.Path, т.к. он используется в RouteBase при матчинге с шаблоном роута
            context.HttpContext.Items["original-path"] = context.HttpContext.Request.Path;
            context.HttpContext.Request.Path           = $"/{controllerName}{data.RemainingUrl}";

            //альтернативно можно вместо использования механизма c матчингом шаблона из RouteBase
            //самому наполнить context.RouteData.Values, но тогда при регистрации роутов придётся отказаться от "{controller}/{action=Index}/{id?}"

            return(base.RouteAsync(context));
        }