Console.WriteLine("Привет, МИР!");На связи команда отдела разработки лучшей онлайн школы программирования IRON PROGRAMMER!
Примерно год назад мы начали работу над своим внутренним аналитическим порталом для управления нашими курсами и пришли к выводу, что нам есть, чем поделиться с сообществом. За минувший год мы написали много, очень много строк кода, реализовали разные задачи, использовали множество технологий и продолжаем развиваться и совершенствоваться.
А здесь мы будем рассказывать обо всем интересном и необычном, с чем столкнулись в своей работе. Наверняка что-то из рассказанного нами пригодится кому-то или просто будет интересно посмотреть, как бывает.
Присоединяйтесь!
🔥1🤮1🤡1🤣1
Что мы делаем?
Если кто не знает, IRON PROGRAMMER - онлайн школа программирования. Основной язык программирования, который мы преподаем - C#. Но кроме него в школе есть курсы по
Go, Kotlin и... впрочем, когда будут другие языки, мы обязательно об этом расскажем.Наши курсы на текущий момент размещаются на Степике. Курсов много, курсы разные, но все вместе они дают людям знания, достаточные для того, чтобы стать
.Net разработчиком и устроиться на работу. Обо всём, что происходит в школе, а так же отзывы, активности, интервью с учениками и многое-многое другое можно почитать в канале, добавляйтесь, если ещё не.Расскажем немного непосредственно об отделе разработки. Мы все, а нас сейчас трое, закончили курсы Иосифа (Иосиф Дзеранов - автор и основатель школы). Наша текущая задача заключается в создании и совершенствовании внутреннего портала для аналитики и управления курсами. Что это означает? Курсов в школе много. Их все нужно поддерживать, обновлять, улучшать, отвечать на комментарии и в целом приводить к единому высокому стандарту качества, то, за что многие тысячи наших учеников ценят и любят, с удовольствием проходят наши курсы. До определенного момента можно было выполнять все операции вручную, однако, с течением времени, курсов стало много и появилась необходимость автоматизировать процессы. Собственно, то, чем занимаются программисты в целом - автоматизируют процессы путём написания программных продуктов.
Мы уже разработали и внедрили 10 стандартов для наших курсов и это далеко не предел. О том, что это за стандарты и для чего они, а самое главное, какие технические решения применяли, когда их реализовывали, мы расскажем позже.
Пока же вернёмся к началу. Раз уж мы все тут
.Net разработчики и, более того, бекендеры, а особой необходимости создавать мега приложение со сложным интерфейсом не было, начали с относительно простого, и в то же время очевидного решения - MVC приложения. Фронтэндеров у нас нет, а одна мысль о том, что придётся углубляться в дебри UX/UI разработки вызывала неприятные ощущения. Поэтому первые итерации были полностью на MVC с применением минимального количества js-кода. Но, с течением времени скриптов становилось больше, а интерфейсы (под стать задачам) сложнее. И вот уже на многих страницах фронтом заведует vue.js . Неожиданно? Может быть, но для текущих задач его возможностей в связке с ASP приложением более чем достаточно. Кстати, о том, как это всё вместе работает расскажем обязательно в следующих постах.Итого. Мы пишем приложения на
C# и внутри управляем курсами, собираем статистику, проверяем, всё ли хорошо на курсах и даже внедрили свои собственные анкеты (они же формы обратной связи) в наши курсы на Степике. В каждом модуле каждого курса вы можете найти форму обратной связи. Это целиком наша разработка, которая пришла на смену не чему нибудь, а самим гугл-формам! Чем мы, по-честному, гордимся!👍1🤮1🤡1🤣1
О - #оптимизация
Этой теме посвятим еще много постов, потому что тема важна, нужная, актуальная и иногда больная ))
Предыстория:
Когда пишется проект с нуля и нужно сделать что-то уже вчера, порой реализуются самые простые и неоптимальные решения, которые просто должны выполнять свою функцию. Со временем, проект разрастается и такие вот "что-то" начинают вылезать и мешать.
Проблема:
Был такой метод:
Пара слов о структуре моделей. Эти модельки приходят из Степика - те самые ваши решения задач - Submissions и попытки Attempts. У каждого решения может быть одна попытка, но несколько решений могут быть для одной попытки. Объяснить это сложно, но так задумано: при отправке нового решения перезаписывается только попытка - она получается одна для одной задачи.
Продолжение...
Этой теме посвятим еще много постов, потому что тема важна, нужная, актуальная и иногда больная ))
Предыстория:
Когда пишется проект с нуля и нужно сделать что-то уже вчера, порой реализуются самые простые и неоптимальные решения, которые просто должны выполнять свою функцию. Со временем, проект разрастается и такие вот "что-то" начинают вылезать и мешать.
Проблема:
Был такой метод:
public async Task<List<long>> GetMissingIdsAsync(bool useStartDate)
{
await using var context = await _contextFactory.CreateDbContextAsync();
var missingAttemptIds = new List<long?>();
if (useStartDate)
{
var existingIds = await context.Attempts
.Where(at => at.Time >= _startDate)
.Select(at => at.Id)
.ToHashSetAsync();
var fromSubmissions = await context.Submissions
.Where(s => s.Time >= _startDate)
.Select(s => s.Attempt)
.ToListAsync();
missingAttemptIds.AddRange(fromSubmissions
.Where(id => id.HasValue && !existingIds.Contains((long)id))
.ToList());
}
else
{
var existingIds = await context.Attempts
.Select(at => at.Id)
.ToHashSetAsync();
var fromSubmissions = await context.Submissions
.Select(s => s.Attempt)
.ToListAsync();
missingAttemptIds.AddRange(fromSubmissions
.Where(id => id.HasValue && !existingIds.Contains((long)id))
.ToList());
}
return missingAttemptIds.Select(x => (long)x!).ToList();
}
Пара слов о структуре моделей. Эти модельки приходят из Степика - те самые ваши решения задач - Submissions и попытки Attempts. У каждого решения может быть одна попытка, но несколько решений могут быть для одной попытки. Объяснить это сложно, но так задумано: при отправке нового решения перезаписывается только попытка - она получается одна для одной задачи.
Продолжение...
🔥1
Начало...
Так вот, нам для актуализации данных на своей стороне нужно загружать и попытки и решения. Сначала мы загружаем решения, потом берем список Attempt и загружаем те, которых у нас еще нет.
Метод выше, работал без малого год и проблем не было, потому что работало.
Работало, пока мы не поймали
Из-за того, что выгружались списки, состоящие из нескольких миллионов элементов, объем занимаемой этими списками памяти стал критичным и в конечном итоге закончился.
Логирование помогло локализовать проблему достаточно быстро. Но, что не так и что делать?
В изначальном методе мы извлекаем сначала огромное количество значений в один список, потом еще большее число значений во второй список и затем формируем третий. Ужас-ужас. Особенно, если учесть, что речь идет о списках по несколько миллионов значений в каждом. В конечном итоге, размер потребляемой памяти мог запросто занимать ~200 мб.
Решение:
Решение на самом деле простое - перенести вычисления на сторону БД. Там все операции выполнятся быстрее и не займут столько места, плюс нет ограничений CLR на размер памяти, тем более, что ее там столько не потребуется.
Здесь мы формируем список id прямо в БД и получаем уникальные значения без вычислений на стороне приложения. Более того, код стал понятнее и лаконичнее.
*заодно исправили ещё момент с некорректной выборкой элементов, из-за которых каждый раз получали "лишние" данные для загрузки.
Итого: сортировка и выборка данных на стороне БД работает в большинстве случаев быстрее и занимает меньше ресурсов + не отражается на производительности самого приложения. Точнее, не ухудшает его.
Так вот, нам для актуализации данных на своей стороне нужно загружать и попытки и решения. Сначала мы загружаем решения, потом берем список Attempt и загружаем те, которых у нас еще нет.
Метод выше, работал без малого год и проблем не было, потому что работало.
Работало, пока мы не поймали
OutOfMemoryException )).Из-за того, что выгружались списки, состоящие из нескольких миллионов элементов, объем занимаемой этими списками памяти стал критичным и в конечном итоге закончился.
Логирование помогло локализовать проблему достаточно быстро. Но, что не так и что делать?
В изначальном методе мы извлекаем сначала огромное количество значений в один список, потом еще большее число значений во второй список и затем формируем третий. Ужас-ужас. Особенно, если учесть, что речь идет о списках по несколько миллионов значений в каждом. В конечном итоге, размер потребляемой памяти мог запросто занимать ~200 мб.
Решение:
Решение на самом деле простое - перенести вычисления на сторону БД. Там все операции выполнятся быстрее и не займут столько места, плюс нет ограничений CLR на размер памяти, тем более, что ее там столько не потребуется.
public async Task<List<long>> GetMissingIdsAsync(bool useStartDate)
{
await using var context = await _contextFactory.CreateDbContextAsync();
try
{
var missingAttempts = context.Submissions!
.Where(s => !useStartDate || s.Time >= _startDate)
.Where(s => s.Attempt != null)
.Where(s => !context.Attempts!
.Any(a => a.Id == s.Attempt))
.Select(s => (long)s.Attempt!)
.Distinct();
return await missingAttempts.ToListAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Произошла ошибка при получении id отсутствующих попыток!");
return [];
}
}
Здесь мы формируем список id прямо в БД и получаем уникальные значения без вычислений на стороне приложения. Более того, код стал понятнее и лаконичнее.
*заодно исправили ещё момент с некорректной выборкой элементов, из-за которых каждый раз получали "лишние" данные для загрузки.
Итого: сортировка и выборка данных на стороне БД работает в большинстве случаев быстрее и занимает меньше ресурсов + не отражается на производительности самого приложения. Точнее, не ухудшает его.
🔥2
Проблемы с миграциями или как все вернуть, когда казалось бы ничего не получается.
Как сказал один умный человек:
Бывает такое, что в процессе работы приходится по несколько раз менять одну и ту же модель, выполнять миграции, потом снова менять и так далее по кругу. А потом, в какой-то момент, EF уже не справляется и создать миграцию не может, не видит изменений в моделях, а в БД этих изменений и нет. Начинаются проблемы, ошибки и поиск решения.
Но на самом деле всё не так уж страшно и сложно.
Первое, что нужно понимать - если в БД нет изменений (таблицы, колонки) и нет миграции в
Смоделируем ситуацию, когда в таблице
Итак, по порядку:
1️⃣ . Все наши проблемы скрываются в файле
2️⃣ . Или ищем руками названия столбца и таблицы и удаляем те изменения, которые должны были попасть в миграцию, но не попали,
3️⃣ . Либо сравниваем текущую ветку с той, в которой точно не было этих потерянных изменений и там, в сравнении, находим нужные строки и удаляем.
4️⃣ . Рекомендуем начинать удалять снизу вверх - тогда номера строк в сравнении и в файле будет совпадать и искать будет проще.
5️⃣ . После того, как зачистили файл, убедились, что в истории миграций в БД нет лишних миграций и у себя в проекте нет лишних миграций, создаем новую.
6️⃣ . Вот в общем-то и всё. Миграция создается успешно со всеми нужными изменениями. ( если нет — возвращаемся к шагу 1 и повторяем все заново, значит где-то что-то осталось не удаленным)
На всякий случай, хотя нет - всегда (!) проверяйте, что в файле с созданной миграцией написано: все ли изменения там присутствуют. И если чего-то не хватает, какого-то изменения или, например, индекса, всегда можно дописать нужное руками.
✅ Запускаемся => смотрим, что все работает => больше не боимся поломок миграций!
С заботой, команда отдела разработки!
Как сказал один умный человек:
опыт — сын ошибок трудных.
Бывает такое, что в процессе работы приходится по несколько раз менять одну и ту же модель, выполнять миграции, потом снова менять и так далее по кругу. А потом, в какой-то момент, EF уже не справляется и создать миграцию не может, не видит изменений в моделях, а в БД этих изменений и нет. Начинаются проблемы, ошибки и поиск решения.
Но на самом деле всё не так уж страшно и сложно.
Первое, что нужно понимать - если в БД нет изменений (таблицы, колонки) и нет миграции в
_EFMigrationsHistory тогда нужно выполнить ряд манипуляций руками и все будет работать как должно!Смоделируем ситуацию, когда в таблице
Users (EF если не указать имя таблицы руками называет таблицы в PascalCase) не появился столбец, например Courses а таблица UserCourses была переименована из StudentCourses, но в бд этих изменений так же нет. Итак, по порядку:
DbContextModelSnapshot.cs (имя контекста может отличаться, но ModelSnaphot будет все равно)На всякий случай, хотя нет - всегда (!) проверяйте, что в файле с созданной миграцией написано: все ли изменения там присутствуют. И если чего-то не хватает, какого-то изменения или, например, индекса, всегда можно дописать нужное руками.
✅ Запускаемся => смотрим, что все работает => больше не боимся поломок миграций!
С заботой, команда отдела разработки!
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Как не надо делать
В процессе разработки периодически приходится работать с публичными api разных сервисов. И периодически сталкиваемся с плохой, а иногда и откровенно ужасной документации api.
Пока "лидирует" по числу недоуменных взглядов и разочарованных вздохов документация к публичному api Stepik.
В чём вообще суть вопроса? А вот пример:
Что тут можно понять?
1. Нет версии api. Работать — работает, но версия должна быть
2. Только один параметр запроса? Серьёзно? Мы точно знаем, что их как минимум 2 (как минимум page)
3. Модель. Какая модель-то на выходе?
Это прям основное, то, что бросается в глаза. И на самом деле это (плохая документация) может быть проблемой, особенно когда api платное, а для того, чтобы разобраться, приходится экспериментировать за свой счёт.
Поэтому, рекомендация простая: если пишите публичный api, поддерживайте документацию в актуальном состоянии. Задайте себе вопрос: а не пропустил ли я чего-нибудь? Могу ли я понять из документации, что получу и при каком запросе?
В процессе разработки периодически приходится работать с публичными api разных сервисов. И периодически сталкиваемся с плохой, а иногда и откровенно ужасной документации api.
Пока "лидирует" по числу недоуменных взглядов и разочарованных вздохов документация к публичному api Stepik.
В чём вообще суть вопроса? А вот пример:
{
"apiVersion": "",
"swaggerVersion": "1.2",
"basePath": "https://stepik.org",
"resourcePath": "/api/step-snapshots",
"apis": [
{
"denoscription": "StepSnapshot resource",
"path": "/api/step-snapshots",
"operations": [
{
"method": "GET",
"summary": "StepSnapshot resource",
"nickname": "Step_Snapshot_list",
"notes": "StepSnapshot resource.",
"type": "object",
"parameters": [
{
"paramType": "query",
"name": "step",
"denoscription": "id of the step to show snapshots for",
"type": "string",
"format": "string",
"required": false,
"defaultValue": null
}
]
}
]
}
],
"models": {}
}Что тут можно понять?
1. Нет версии api. Работать — работает, но версия должна быть
2. Только один параметр запроса? Серьёзно? Мы точно знаем, что их как минимум 2 (как минимум page)
3. Модель. Какая модель-то на выходе?
Это прям основное, то, что бросается в глаза. И на самом деле это (плохая документация) может быть проблемой, особенно когда api платное, а для того, чтобы разобраться, приходится экспериментировать за свой счёт.
Поэтому, рекомендация простая: если пишите публичный api, поддерживайте документацию в актуальном состоянии. Задайте себе вопрос: а не пропустил ли я чего-нибудь? Могу ли я понять из документации, что получу и при каком запросе?
👍1
2025 год для отдела разработки
Это был очень интересный и не менее насыщенный год!
Мы начали свой путь в самом конце 2024 года и примерно в эти же, предновогодние дни впервые зарелизили наш внутренний портал.
С тех пор кодовая база росла, штат увеличивался, а количество применяемых технологий и качество кода в целом сделали ощутимый рывок вперед!
Подводя итоги, захотелось как-то формализовать успехи и достижения, чтобы их можно было (внутри команды) посмотреть в читаемом виде. Так появилось небольшое приложение, считающее метрики из репозитория. В предновогодней суете еще не оформили его в полноценное открытое решение, но обязательно сделаем это в самом начале следующего года. Оно поможет собрать статистику с любого репозитория, к которому у пользователя есть доступ и посмотреть, что сделано за нужный период: количество коммитов и строк кода, наиболее активные дни недели и время суток и т.д.
К посту прикрепляем некоторые детали из получившегося отчета, можно оценить нашу продуктивность (и режим дня )))
А еще мы в уходящем году множество раз обновляли библиотеку для работы со Stepik api, написали свое приложение с формами обратной связи (которое как минимум видели все ученики, а многие и воспользовались, заполнив анкету/ты) и готовим еще много всего важного, полезного и интересного как для команды, так и, в большей степени, для наших дорогих учеников!
И, конечно, будем делиться своими наработками и наблюдениями здесь в канале. Обмен опытом всегда интересен, и прикладную информацию по изучаемому языку читать, на наш взгляд, вдвойне интересней.
С наступающим Новым годом!
Это был очень интересный и не менее насыщенный год!
Мы начали свой путь в самом конце 2024 года и примерно в эти же, предновогодние дни впервые зарелизили наш внутренний портал.
С тех пор кодовая база росла, штат увеличивался, а количество применяемых технологий и качество кода в целом сделали ощутимый рывок вперед!
Подводя итоги, захотелось как-то формализовать успехи и достижения, чтобы их можно было (внутри команды) посмотреть в читаемом виде. Так появилось небольшое приложение, считающее метрики из репозитория. В предновогодней суете еще не оформили его в полноценное открытое решение, но обязательно сделаем это в самом начале следующего года. Оно поможет собрать статистику с любого репозитория, к которому у пользователя есть доступ и посмотреть, что сделано за нужный период: количество коммитов и строк кода, наиболее активные дни недели и время суток и т.д.
К посту прикрепляем некоторые детали из получившегося отчета, можно оценить нашу продуктивность (и режим дня )))
А еще мы в уходящем году множество раз обновляли библиотеку для работы со Stepik api, написали свое приложение с формами обратной связи (которое как минимум видели все ученики, а многие и воспользовались, заполнив анкету/ты) и готовим еще много всего важного, полезного и интересного как для команды, так и, в большей степени, для наших дорогих учеников!
И, конечно, будем делиться своими наработками и наблюдениями здесь в канале. Обмен опытом всегда интересен, и прикладную информацию по изучаемому языку читать, на наш взгляд, вдвойне интересней.
С наступающим Новым годом!
❤🔥3
Кастомная авторизация: быстро и просто
В эпоху, когда микросервисы захватывают умы и сердца разработчиков (Нет. Нет же? ))) зачастую встает вопрос о том, а как организовать авторизацию между несколькими приложениями без необходимости усложнения процесса?
То есть, буквально: есть несколько приложений, работающих вместе, на одном сервере, например, и им надо между собой обмениваться данными, при этом не открывая доступ к эндпоинтам во внешний мир и не заморачиваясь с конфигурацией сервера для каждого маршрута для каждого приложения. Можно организовать авторизацию с
И вот зачем изобретать сложную систему секретов, ключей, авторизации друг у друга? И какое решение можно применить для упрощения этого процесса?
Выход на самом деле очень простой. На помощь в данном вопросе приходит
Что тут происходит: получаем из переменной окружения значение ключа, сравниваем со значением в заголовке (если такой заголовок существует в запросе) и если все ок - пропускаем запрос дальше, если нет нужного заголовка или значения не совпадают, то запрос даже не доходит до контроллера.
Максимально просто и надежно.
В коде контроллера применение атрибута будет выглядеть так:
При старте, например если несколько приложений запускаются в
Для большей надежности:
1. Генерируйте сложные ключи.
Делается это одной командой:
Эта команда сгенерирует 256-битный ключ в шестнадцатеричном формате, его можно смело использовать в качестве ключа.
*в Windows ни в командной строке ни в
2. Регулярно меняйте ключи.
Это можно сделать множеством способов, например скриптом по таймеру или если приложение часто обновляется, добавить скрипт прямо в
В эпоху, когда микросервисы захватывают умы и сердца разработчиков (Нет. Нет же? ))) зачастую встает вопрос о том, а как организовать авторизацию между несколькими приложениями без необходимости усложнения процесса?
То есть, буквально: есть несколько приложений, работающих вместе, на одном сервере, например, и им надо между собой обмениваться данными, при этом не открывая доступ к эндпоинтам во внешний мир и не заморачиваясь с конфигурацией сервера для каждого маршрута для каждого приложения. Можно организовать авторизацию с
JWT токенами, но тогда нужно будет писать кучу сервисов, хранить секреты, обновлять эти самые секреты и т.д.И вот зачем изобретать сложную систему секретов, ключей, авторизации друг у друга? И какое решение можно применить для упрощения этого процесса?
Выход на самом деле очень простой. На помощь в данном вопросе приходит
Middleware - промежуточный слой в приложении, который еще до контроллера проверит, авторизован ли запрос. В общем-то, выполнит примерно такое же действие, что и стандартный [Authorize] , только ключ мы зададим сами, равно как и заголовок запроса./// <summary>
/// Класс-атрибут для защиты контроллера TargetController от несанкционированного доступа
/// Работает через фильтр <see cref="IAsyncActionFilter"/>.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class TargetAttribute : Attribute, IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var expectedTargetToken = Environment.GetEnvironmentVariable("TARGET_KEY");
if (!context.HttpContext.Request.Headers.TryGetValue("TARGET-TOKEN", out var headerKey)
|| headerKey != expectedTargetToken)
{
context.Result = new UnauthorizedResult();
return;
}
_ = await next();
}
}
Что тут происходит: получаем из переменной окружения значение ключа, сравниваем со значением в заголовке (если такой заголовок существует в запросе) и если все ок - пропускаем запрос дальше, если нет нужного заголовка или значения не совпадают, то запрос даже не доходит до контроллера.
Максимально просто и надежно.
В коде контроллера применение атрибута будет выглядеть так:
[ApiController]
[Target] // здесь мог быть, например, стандартный [Authorize]
[Route("target")]
public class TargetController(
ILogger<TargetController> logger,
ITargetService targetService
) : Controller
{
// код контроллера
}
При старте, например если несколько приложений запускаются в
docker-compose, все они получат значение ключа из .env файла (или другим способом, которым выполняется добавление в переменные окружения значений). Извне к этим значениям доступа ни у кого нет, а приложения спокойно обмениваются данными (обращаются к конечным точкам контроллеров).Для большей надежности:
1. Генерируйте сложные ключи.
Делается это одной командой:
openssl rand -hex 32
Эта команда сгенерирует 256-битный ключ в шестнадцатеричном формате, его можно смело использовать в качестве ключа.
*в Windows ни в командной строке ни в
PowerShell openssl по человечески не работает, но зато нормально работает в WSL и git bash.2. Регулярно меняйте ключи.
Это можно сделать множеством способов, например скриптом по таймеру или если приложение часто обновляется, добавить скрипт прямо в
docker-compose.yml👍3
Атрибут, снимающий головную боль
В период, когда основной проблемой является запуск приложения и попытки заставить все вместе работать как хочется (это про период обучения, если что) о всяких мелочах можно и не задумываться и не углубляться во всякое, казалось бы, не очень то и важное.
А вот когда проект разрастается, и логика выполнения некоторых действий в приложении затрагивает одним вызовом сразу множество сервисов, найти проблему на фронте бывает непросто.
Вот ситуация: на страницу добавляется функционал, меняется или добавляется какой-нибудь стиль и... в приложении ничего не меняется. Локально - возможно, а в проде никак. Куда смотреть, если ошибок нет? Как найти проблему?
Такая же история с
Тут-то и вспоминаем про атрибуты. Речь сегодня про один, но важный и не всегда очевидный -
Как и что он делает
Если не добавить этот атрибут к импорту файла (например стилей) то это будет выглядеть как:
Если если внести изменения в стили и перезапустить приложение, то в браузере может остаться кэшированная версия файла и стили просто не применятся.
Соответственно, для того, чтобы таких проблем избежать, нужно использовать атрибут:
Что здесь меняется и что вообще происходит
Имя файла в конечной версии может выглядеть, например, вот так:
Вывод простой: в MVC приложениях используйте версионирование файлов стилей и скриптов. Это гарантирует, что пользователи будут использовать только актуальные версии продукта!
В период, когда основной проблемой является запуск приложения и попытки заставить все вместе работать как хочется (это про период обучения, если что) о всяких мелочах можно и не задумываться и не углубляться во всякое, казалось бы, не очень то и важное.
А вот когда проект разрастается, и логика выполнения некоторых действий в приложении затрагивает одним вызовом сразу множество сервисов, найти проблему на фронте бывает непросто.
Вот ситуация: на страницу добавляется функционал, меняется или добавляется какой-нибудь стиль и... в приложении ничего не меняется. Локально - возможно, а в проде никак. Куда смотреть, если ошибок нет? Как найти проблему?
Такая же история с
js файлами. Вносим изменения, собираем проект, запускаем на сервере и ничего не меняется.Тут-то и вспоминаем про атрибуты. Речь сегодня про один, но важный и не всегда очевидный -
asp-append-versionКак и что он делает
Если не добавить этот атрибут к импорту файла (например стилей) то это будет выглядеть как:
<link rel="stylesheet" href="~/css/site.css"/>
Если если внести изменения в стили и перезапустить приложение, то в браузере может остаться кэшированная версия файла и стили просто не применятся.
Соответственно, для того, чтобы таких проблем избежать, нужно использовать атрибут:
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
Что здесь меняется и что вообще происходит
ASP при сборке добавляет в имя файла хэш, соответственно при входе на сайт браузер видит различие в используемых именах файлов и если что-то изменилось, значит изменился хэш, значит у файла другое имя и браузер не будет использовать кэшированную версию.Имя файла в конечной версии может выглядеть, например, вот так:
site.css?v=NnrPGoz4r8WDnFcIkhEcsQCwLzXQq3MNsNyDxAOBfX4
Вывод простой: в MVC приложениях используйте версионирование файлов стилей и скриптов. Это гарантирует, что пользователи будут использовать только актуальные версии продукта!
🔥5
