Как стать автором
Обновить

Комментарии 13

Нормальным людям хочется чтобы XAML в веб превращался, а у вас всё наоборот как-то.

Я правильно понял, что вы поднимаете локальный сервер и обращаетесь к нему только для того, чтобы воспользоваться Razor Pages как шаблонизатором?.. Зачем так сложно-то всё?


Из этой связки нужно выкинуть, как минимум, веб-сервер. Это довольно просто:


        public static RequestDelegate CreateRequestDelegate()
        {
            var host = Host.CreateDefaultBuilder()
                .ConfigureWebHost(builder => builder.ConfigureServices(services =>
                {
                    // тут делается Startup.ConfigureServices
                }))
                .ConfigureServices(services =>
                {
                    foreach (var sd in services.Where(sd => sd.ServiceType == typeof(IHostedService)).ToArray())
                        services.Remove(sd);
                })
                .Build();

            var features = new FeatureCollection();
            var app = host.Services.GetRequiredService<IApplicationBuilderFactory>().CreateBuilder(features);

            // тут делается Startup.Configure

            return app.Build();
        }

Всё, как только у нас есть RequestDelegate — ему можно скармливать настроенные экземпляры DefaultHttpContext:


            var rd = CreateRequestDelegate();

            var ctx = new DefaultHttpContext();
            ctx.Request.Protocol = "HTTP/1.0";
            ctx.Request.Method = "GET";
            ctx.Request.Scheme = "http";
            ctx.Request.Path = "/";
            ctx.Request.PathBase = "/";
            ctx.Response.Body = Console.OpenStandardOutput();

            await rd(ctx);

Не нужно никаких поисков свободного порта, никаких секретных заголовков. Даже запускать и останавливать ничего не требуется! При желании можно даже создать HttpMessageHandler на основе RequestDelegate и обернуть его в HttpClient.


А если копать дальше — то можно и RequestDelegate тут выкинуть, со всей маршрутизацией и хостом.

var ctx = new DefaultHttpContext();

Боюсь, этого будет недостаточно: не будет работать доступ из контекста к контейнеру сервисов (он же — DI-container) через свойство HttpContext.RequestServices. А контейнер сервисов для работы приложения с Razor Pages, как пить дать, нужен.
Свойство это по умолчанию реализуется (если игнорировать кэширование, ибо кэш по первому разу все равно надо заполнить) через IServiceProviderFactory, который присваивается свойству DefaultHttpContext.ServiceProviderFactory в DefaultHttpContextFactory.Initialize (а исходно это свойство равно null). Это нужно для того, чтобы поддерживать сервисы с временем жизни ограниченной области (Scoped) на время обработки одного запроса.
Поскольку в консольном приложении ограниченные области не нужны, то, возможно, этому свойству будет достаточно присвоить IServiceProvider самого контейнера сервисов:
ctx.RequestServices=host.Services;

Но вообще-то DefaultHttpContext инициализуется ещё и через содержимое параметра типа IFeatureCollection (в конструкторе или через Initialize()), и я не берусь сказать, что в эту коллекцию нужно добавить обязательно, а что — можно не добавлять.
А ещё DefaultHttpContextFactory.Initialize отдельно инициализует в DefaultHttpContext свойство HttpContext.FormOptions одноименного типа (оно передается с использованием options pattern, конкретно — через IOptions<FormOptions>.Value ). Где и как оно используется в веб-приложении — без понятия.

Да, спасибо за уточнения. Все нужные ему фичи DefaultHttpContext умеет создавать сам по требованию, кроме двух — IHttpRequestFeature и IHttpResponseFeature. Точнее, эти две он тоже умеет создавать, но только при создании через конструктор без параметров.


Вот этот код у меня заработал:


var factory = app.ApplicationServices.GetRequiredService<IHttpContextFactory>();

// …

var ctx = factory.Create(new FeatureCollection());
ctx.Features.Set<IHttpRequestFeature>(new HttpRequestFeature());
ctx.Features.Set<IHttpResponseFeature>(new HttpResponseFeature());

// …

factory.Dispose(ctx);

Однако, фабрика не обязательна, основные параметры можно и без неё присвоить:


var ctx = new DefaultHttpContext
{
    ServiceScopeFactory = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>(),
    FormOptions = app.ApplicationServices.GetRequiredService<IOptions<FormOptions>>().Value,
};

Помимо создания контекста, фабрика также настраивает IHttpContextAccessor — но эту штука вряд ли используется самим фреймворком, она больше похожа на костыль для тех кто не умеет scoped-сервисы писать. Опять-таки, если понадобится — настроить недолго.

Получилось — именно сделать HTML из .cshml?
Если так, то возьму на заметку.
Но вообще пытаться нетрадиционным образом использовать ASP.NET Core — оно стремно (мне, по крайней мере): там куча зависимостей, и просто так не поймешь, какая из них выстрелить в тебя может.

PS IHttpAccessor — он AFAIK для того, чтобы HttpContext раньше времени не потерялся (не был переиспользован под другой запрос, например). Здесь он явно не нужен.
Получилось — именно сделать HTML из .cshml?

Получилось получить ответ от шаблона пустого приложения. Для cshtml надо ещё с самими Razor Pages разбираться, а мне лень.


PS IHttpAccessor — он AFAIK для того, чтобы HttpContext раньше времени не потерялся (не был переиспользован под другой запрос, например). Здесь он явно не нужен.

Нет, он для обращения к HttpContext из синглтонов.

Получилось получить ответ от шаблона пустого приложения. Для cshtml надо ещё с самими Razor Pages разбираться, а мне лень.

Благодарю за информацию.
Так как генрация кода из реального шаблона Razor — это отдельная сложная работа, там может случиться многое. Так что взять на заметку просто так не получится: при случае придется все проверять самостоятельно.

Нет, он для обращения к HttpContext из синглтонов.

Меня интересовал не про где используется, про то, что он делает. Посмотрел, что раельно делает реализация (HttpContextAccessor): она сохраняет ссылку на HttpContext внутри ExecutionContext в AsyncLocal, судя по тамошнему комментарию — для корректной очистки всех ссылок на него во всех контекстах выполнения при его очистке.
Вердикт однако остается тем же самым — здесь он явно не нужен.

Тот комментарий относится к использованию HttpContextHolder, а не к использованию AsyncLocal.

А стоит ли вообще это делать с помощью Razor, который заточен под использование в ASP.NET MVC View и WebPages? Для этого есть гораздо более подходящие инструменты, например те же Handlebars.Net, Nustache, Fluid, DotLiquid, etc. На крайний случай есть просто RazorEngine (ныне полумертвый), который просто шаблонный движок и никакого ASP.NET ни в каком виде не требует.

есть же старый добрый t4... даже консольное преложение не придется запускать, все доступно из вижал студии. он чем-то вам не подошел?

Спасибо, для рассмотренного примера самое то, как мне показалось, от вас впервые об этом услышал, буду изучать )

T4 предназначен для генерации текстовых файлов (например исходников) во время сборки. Тут же, как я понял, речь идет о генерации во время выполнения.

Попробовал. В принципе, всё работает, но есть неприятность, что VS не подсвечивает и не предлагает код, какие-то плагины подсвечивают, но не нашел, чтобы предлагали. То, что есть, не все работает в VS2022. То есть razorpages элементарно удобнее писать.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории