Комментарии 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-сервисы писать. Опять-таки, если понадобится — настроить недолго.
Если так, то возьму на заметку.
Но вообще пытаться нетрадиционным образом использовать 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, судя по тамошнему комментарию — для корректной очистки всех ссылок на него во всех контекстах выполнения при его очистке.
Вердикт однако остается тем же самым — здесь он явно не нужен.
А стоит ли вообще это делать с помощью Razor, который заточен под использование в ASP.NET MVC View и WebPages? Для этого есть гораздо более подходящие инструменты, например те же Handlebars.Net, Nustache, Fluid, DotLiquid, etc. На крайний случай есть просто RazorEngine (ныне полумертвый), который просто шаблонный движок и никакого ASP.NET ни в каком виде не требует.
есть же старый добрый t4... даже консольное преложение не придется запускать, все доступно из вижал студии. он чем-то вам не подошел?
Спасибо, для рассмотренного примера самое то, как мне показалось, от вас впервые об этом услышал, буду изучать )
T4 предназначен для генерации текстовых файлов (например исходников) во время сборки. Тут же, как я понял, речь идет о генерации во время выполнения.
Попробовал. В принципе, всё работает, но есть неприятность, что VS не подсвечивает и не предлагает код, какие-то плагины подсвечивают, но не нашел, чтобы предлагали. То, что есть, не все работает в VS2022. То есть razorpages элементарно удобнее писать.
О применении RazorPages в консольных и десктопных приложениях