TagHelper для класса подстановкой


Я построение RESTful-сервис, который отправляет письма с телом, оказываемых частичным видом. Он заменяет мой старый раствор , что используется фиксированное шаблоны IHtmlElement внутри каждого приложения.

Одной из его особенностей является встроенный classES как style. Я использую class потому что легче спроектировать смотреть в эту сторону. Так как речь идет о письмах, я не ожидал fency селекторов и сделать его простым я использую только classЭс.


InlineClassTagHelper

Подстановкой управляется InlineClassTagHelper. Он останавливается на каждом элементе с class атрибут и проверяет, если есть классы с префиксом m- (это мой пользовательский префикс, который стоит на почте). Он потом ищет стиль в анализируемой .css файл. Его имя всегда wwwroot/css/Conroller.Action.css. Когда нашли, он устанавливает style атрибут и удаляет class один.

[HtmlTargetElement(Attributes = "class")]
public class InlineClassTagHelper : TagHelper
{
    private readonly CssProvider _cssProvider;

    public InlineClassTagHelper(CssProvider cssProvider)
    {
        _cssProvider = cssProvider;
    }

    [HtmlAttributeNotBound, ViewContext]
    public ViewContext ViewContext { get; set; }

    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        var classNames =
            output
                .Attributes["class"]
                ?.Value
                .ToString()
                .Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

        if (classNames is null)
        {
            return;
        }

        var inlineableClassNames =
            (from className in classNames
             where className.StartsWith("m-")
             select SoftString.Create(className)).ToImmutableHashSet();

        if (inlineableClassNames.None())
        {
            return;
        }

        var cssFileName =
            $"wwwroot/css/" +
            $"{ViewContext.RouteData.Values["controller"]}." +
            $"{ViewContext.RouteData.Values["action"]}.css";

        var css = await _cssProvider.GetCss(cssFileName);

        var declarations =
            from ruleset in css
            from selector in ruleset.Selectors
            join className in inlineableClassNames on selector equals className
            select ruleset.Declarations.TrimEnd(';');

        var style = declarations.Join("; ");

        if (style.IsNullOrEmpty())
        {
            // Make debugging of missing styles easier by highlighting the element with a red border.
            output.Attributes.SetAttribute("style", "border: 1px solid #ff6666; border-radius: 3px;");
        }
        else
        {
            output.Attributes.SetAttribute("style", style);
            output.Attributes.RemoveAll("class");
        }
    }
}

Кэширование

Чтобы избежать многократных нагрузок (для каждого class атрибут совпадение) в .css файл кэшируется в течение жизненного цикла запроса и загружен другим вспомогательные услуги я предоставляю ICssProvider.

public interface ICssProvider
{
    Task<Css> GetCss(string fileName);
}

public class CssProvider : ICssProvider
{
    private readonly IFileProvider _fileProvider;

    private Css _css;

    public CssProvider(IFileProvider fileProvider)
    {
        _fileProvider = fileProvider;
    }

    public async Task<Css> GetCss(string fileName)
    {
        if (_css is null)
        {
            var cssFile = _fileProvider.GetFileInfo(fileName);
            using (var reader = new StreamReader(cssFile.CreateReadStream()))
            {
                var cssString = await reader.ReadToEndAsync();
                _css = CssParser.Default.Parse(cssString);
                Debug.WriteLine($"{fileName} loaded.");
            }
        }

        return _css;
    }
}

Это зарегистрировано в Startup как

services.AddScoped<CssProvider>();

Пример

Когда тег-помощник находит элемент, как этот:

<h2 class="m-title">@ViewData["Title"]</h2>

это превращает его в этом:

<h2 style="color: blueviolet;">About</h2>

где .css это:

m-title {
    color: blueviolet;
}

Вы скажете, что это хорошее решение, или вы видите какие-то возможности для улучшения?



397
1
задан 24 февраля 2018 в 11:02 Источник Поделиться
Комментарии
1 ответ

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

Что и было сделано. Однако, добытых помощником реализации сервиса был зарегистрирован как

services.AddScoped<CssProvider>();

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

Абстракция существует, поэтому я предлагаю рефакторинг помощник явно зависят от ICssProvider абстракция

//...code removed for brevity

private readonly ICssProvider _cssProvider;

public InlineClassTagHelper(ICssProvider cssProvider) {
_cssProvider = cssProvider;
}

//...code removed for brevity

и зарегистрировать абстракции с его реализацией в корневой состав
(Запуск) с следующим расширением

services.AddScoped<ICssProvider, CssProvider>();

2
ответ дан 27 февраля 2018 в 02:02 Источник Поделиться