Создание движка представлений

Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core 7

Последнее обновление: 19.11.2019

Движки представлений - это классы, которые реализуют интерфейс IViewEngine, который определен в пространстве имен Microsoft.AspNetCore.Mvc.ViewEngines:

public interface IViewEngine
{
    ViewEngineResult FindView(ActionContext context, string viewName, bool isMainPage);
    ViewEngineResult GetView(string executingFilePath, string viewPath, bool isMainPage);
}

Когда для обработки ответа необходимо представление, то MVC вызывает у движка представления метод GetView(), который предоставляет представление. Если же метод GetView() не может предоставить представление, тогда вызывается метод FindView(), который выполняет поиск представления.

Оба метода возвращают не просто представление, а объект ViewEngineResult, для создания которого мы можем выполнить один из статических методов, определенных в этом же классе ViewEngineResult:

  • Found(name, view): вызов этого метода устанавливает представление через свойство View объекта ViewEngineResult, которое потом используется для обработки запроса

  • NotFound(name, locations): вызов этого метода говорит MVC, что представление не найдено. В качестве параметра передается значение locations, которое представляет перечисление путей, по которым движок искал представление.

Само же представление представляет объект интерфейса IView:

public interface IView 
{
	string Path { get;}
	Task RenderAsync(ViewContext context);
}

Свойство Path возвращает путь к файлу представления. А метод RenderAsync() вызывается для генерации ответа клиенту. В качестве параметра в метод передается объект ViewContext, функционал которого мы можем использовать для рендеринга представлений. В частности, ViewContext определяет следующие свойства:

  • ExecutingFilePath: путь к обрабатываемому файлу представления

  • TempData: возвращает объект TempData

  • ViewBag: возвращает динамический объект - все то, что передается в контроллере через ViewBag

  • ViewData: возвращает словарь ViewDataDictionary, который содержит переданные из контроллера значения, а также модель представления и ее состояние

  • Writer: возвращает объект TextWriter, который применяется для генерации содержимого из представления

Чтобы понять, как складывается взаимодействие всех этих классов, создадим небольшой пример. Создадим новый проект ASP.NET Core по типу Empty.

Проект для View Engine в ASP.NET Core MVC

Далее создадим в проекте новую папку, которую назовем Controllers и которая будет предназначена для контроллеров. Затем добавим в нее простенький контроллер:

using Microsoft.AspNetCore.Mvc;

namespace ViewEngineApp.Controllers
{
    public class HomeController : Controller
    {
        public ViewResult Index()
        {
            return View();
        }
        public ViewResult About()
        {
            return View("About");
        }
        public ViewResult Contact()
        {
            return View();
        }
    }
}

Все три метода контроллеров используют метод View для использования представлений для генерации ответа. Однако на данный момент у нас нет никаких представлений. Поэтому добавим в проект папку Views.

Затем в эту папку добавим два новых html-файла: index.html и about.html. Определим в этих файлах какое-нибудь содержимое. Например, в моем случае содержимое файла index.html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Главная</title>
</head>
<body>
    <h2>Главная</h2>
</body>
</html>

Итак, наши представления будут представлять обычные html-файлы.

Далее добавим в проект папку Util для вспомогательных классов.

Создание класса представления

Далее добавим в папку Util новый класс, который назовем CustomView:

using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using System;
using System.IO;
using System.Threading.Tasks;

namespace ViewEngineApp.Util
{
    public class CustomView : IView
    {
        public CustomView(string viewPath)
        {
            Path = viewPath;
        }
        public string Path { get; set; }
        public async Task RenderAsync(ViewContext context)
        {
            string content = String.Empty;
            using (StreamReader viewReader = new StreamReader(Path))
            {
                content = await viewReader.ReadToEndAsync();
            }
            await context.Writer.WriteAsync(content);
        }
    }
}

Класс представления должен реализовать интерфейс IView. Через конструктор он будет получать путь к файлу представления, а в методе RenderAsync() будет происходить считывание файла. Для создания результата используется параметр ViewContext, который с помощью свойства Writer пишет в выходной поток считанное из файла содержимое.

При необходимости можно производить дополнительную обработку файла, например, вводить в html-файлы выражения на C# подобно тому, как это делается в Razor, только в этом случае также придется писать более сложную логику обработки с соответствующим парсингом. В данном же случае ограничимся более простым примером.

Само представление CustomView пока никак не связано с контроллером и классом ViewResult, и чтобы их связать добавим в папку Util еще один класс CustomViewEngine:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using System;
using System.IO;

namespace ViewEngineApp.Util
{
    public class CustomViewEngine : IViewEngine
    {
        public ViewEngineResult GetView(string executingFilePath, string viewPath, bool isMainPage)
        {
            return ViewEngineResult.NotFound(viewPath, new string[] { });  
        }
        public ViewEngineResult FindView(ActionContext context, string viewName, bool isMainPage)
        {
            string viewPath = viewPath = $"Views/{viewName}.html"; ;
            if(String.IsNullOrEmpty(viewName))
            {
                viewPath = $"Views/{context.RouteData.Values["action"]}.html";
            }
            
            if (File.Exists(viewPath))
            {
                return ViewEngineResult.Found(viewPath, new CustomView(viewPath));
            }
            else
            {
                return ViewEngineResult.NotFound(viewName, new string[] { viewPath });
            }
        }
    }
}

Движок представлений реализует интерфейс IViewEngine. В методе GetView() просто возвращаем ViewEngineResult, который указывает, что представление не найдено. В метод ViewEngineResult.NotFound в качестве второго параметра нам надо передать путь, по которому осуществлялся поиск. В данном случае поиск нигде не выполнялся, поэтому массив пуст.

По факту метод GetView() во многом аналогичен методу FindView за тем исключением, что в GetView передается в качестве параметра executingFilePath путь к представлению. А в методе FindView вместо него для поиска представления передается контекст ActionResult. Если же логика поиска и создания представления не зависит от этих параметров, то мы можем использовать один и тот же код в обоих методах.

В методе FindView() формируем путь к представлению, которое у нас находится в папке Views и имеет в качестве расширения файла "html". Параметр "viewName", который применяется для формирования пути, - это название представления, которое передается в контроллере в вызове return View("название представления"). Но мы можем в контроллере и не указывать имя представления. И в этом случае параметр "viewName" будет пустым. Поэтому дополнительно получаем из контекста запроса название действия из параметров маршрута и используем его для поиска представления. Например, если мы обратились к методу Index, то здесь мы получим из параметров маршрута Index.

С учетом всех файлов получится следующий проект:

View Engine in ASP.NET Core

Теперь изменим класс Startup:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using ViewEngineApp.Util;

namespace ViewEngineApp
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<MvcViewOptions>(options => {
                options.ViewEngines.Clear();
                options.ViewEngines.Insert(0, new CustomViewEngine());
            });
            services.AddControllersWithViews();
        }

        public void Configure(IApplicationBuilder app)
        {
            app.UseDeveloperExceptionPage();
            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

Для установки движка представлений в методе ConfigureServices() применяется объект MvcViewOptions. В MVC может использоваться одновременно несколько движков представлений, чтобы использовать только один, сначала очищаем всю коллекцию движков:

options.ViewEngines.Clear();

Затем добавляем в начало коллекции наш движок:

options.ViewEngines.Insert(0, new CustomViewEngine());

И после запуска движок представлений будет предоставлять нужные html-файлы из папки views:

Движок представлений в ASP.NET Core

Если же нужный html-файл не будет найден, то мы увидим ошибку с перечислением путей, по которым велся поиск файла:

Реализация движка представлений в ASP.NET Core
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850