Возврат сгенерированного CSS из действия контроллера MVC5 или Web API 2
В нашем мультитенантном приложении нам необходимо настроить стили, используемые для каждого арендатора.
В настоящее время мы планируем сделать это с помощью LESS и переменных следующим образом на клиенте:
- Загрузите зависимые файлы LESS с сервера
- Вызовите веб-сервис для получения объекта конфигурации
- Форма строки действительного LESS с определенными переменными
- использование
less.js
компилятор для компиляции LESS на основе этих переменных и фиксированных файлов LESS с шага 1
Этот подход имеет ряд недостатков:
- Клиенты могут вести себя плохо
- Некоторые браузеры имеют проблемы с
less.js
- Компиляция требует времени
Вместо этого мы хотели бы позаботиться об этой работе на сервере, так что, грубо говоря, это происходит на сервере:
- Клиент просит загрузить одну большую скомпилированную таблицу стилей -
GET content/styles/{tenantName}.css
- С помощью
tenantName
сервер выбирает конфигурацию - Используя шаблон и соответствующие переменные (возможно,
string.Format
или что то более изощренное) - Сервер компилирует LESS в строку CSS
- Сервер возвращает строку CSS с соответствующей
Content-Type
Вот мои вопросы:
- Это необычный или нежелательный способ достижения указанного результата?
- Если не считать настройки архитектуры для серверного JavaScript, как я могу скомпилировать LESS в CSS?
- Что я должен сделать в действии контроллера или в конфигурации маршрута, чтобы заставить клиента думать, что сервер возвращает обычный старый файл CSS с полным контролем кэша, без изменений?
1 ответ
Вы можете использовать BundleTransformer для компиляции вашей стороны LESS.
Это может зависеть от того, как вы хотите обслуживать файл. Если вы знаете всех арендаторов, просто добавьте URL-адрес пакета для каждого приложения клиента в конфигурацию пакета.
var themeStyles = new CustomStyleBundle("~bundles/theme/tenant").Include("~/Content/theme.less");
themeStyles.Builder = new ThemeBuilder();
BundleTable.Bundles.Add(themeStyles);
Если вы этого не сделаете, и арендаторы будут гибкими, как это было в нашей ситуации, добавьте следующее действие контроллера для ваших тем.
[Route("bundles/theme/{id}")]
public ContentResult Theme(string id)
{
var tenantThemePath = string.Format("~/bundles/theme/{0}", id);
// Check that bundle has not already been added.
if (BundleTable.Bundles.All(x => x.Path != tenantThemePath))
{
var themeStyles = new CustomStyleBundle(tenantThemePath ).Include("~/Content/theme.less");
themeStyles.Builder = new ThemeBuilder();
BundleTable.Bundles.Add(themeStyles);
}
var context = new BundleContext(HttpContext, BundleTable.Bundles, institutionPath);
var response = BundleTable.Bundles.GetBundleFor(tenantThemePath).GenerateBundleResponse(context);
Response.Cache.SetCacheability(response.Cacheability);
return Content(response.Content, response.ContentType);
}
Реализация ThemeBuilder для BundleTransformer
public class ThemeBuilder : IBundleBuilder
{
public string BuildBundleContent(Bundle bundle, BundleContext context, IEnumerable<BundleFile> files)
{
var lessTranslator = bundle.Transforms.OfType<StyleTransformer>()
.Where(x => x != null)
.Select(x => x.Translators.OfType<LessTranslator>().FirstOrDefault())
.FirstOrDefault();
if (lessTranslator == null)
{
return string.Empty;
}
lessTranslator.GlobalVariables = GetThemeVariables();
return string.Empty;
}
private string GetThemeVariables()
{
// Simplified for brevity
// This will be translated to less variables by the BundleTransformer
// themeColour should correspond to a variable name in your less file.
return string.Format("themeColour={0}", themeColour);
}
}
Вам не понадобится выводить цвета темы, мы спрятали эти переменные в хранилищах HttpContext, чтобы мы могли извлечь их с помощью метода расширения в методе GetThemeVariables.
Надеюсь, это поможет.
ОБНОВЛЕНИЕ Я расширил свой первоначальный ответ и создал более удобный способ включения тем.
Демо-сайт здесь: http://bundletransformer-theme-builder.azurewebsites.net/
GitHub репо здесь: https://github.com/benembery/bundle-transformer-theme-builder