Структура "Feature Folders" ASP.NET Core MVC



Перша версія ASP.NET MVC з'явилася ще в 2009 році, а перший перезапуск платформи (ASP.NET Core) почав поставлятися з минулого літа. Протягом цього часу структура проекту за замовчуванням залишилася майже незмінною: папки для контролерів, уявлень (views) і часто для моделей (або, можливо, ViewModels). Такий підхід називається Tech folders. Після створення нового проекту ASP.NET Core MVC організаційна структура папок має наступний вигляд:


У чому проблема зі структурою папок за замовчуванням?
Великі веб-додатки вимагають кращої організації ніж маленькі. Коли є великий проект, організаційна структура папок, що використовується за умовчанням в ASP.NET MVC (і Core MVC), перестає працювати на вас.

Tech folders має свої переваги, ось деякі з них:

  • знайома структура, якщо ви працювали з проектом ASP.NET MVC, ви відразу зможете зорієнтуватися у проекті
  • логічна організація
  • зручність, якщо потрібно знайти контролер або View, то ви добре знаєте з чого почати
Коли стартує новий проект, Tech folders працює досить добре, поки не великий функціонал і немає багато файлів. Як тільки проект починає рости, стає досить важко шукати потрібний контролер або View у великій кількості файлів.

Наведемо простий приклад. Уявіть, що ви організували свої файли на комп'ютері по цій же структурі. Замість того, щоб мати окремі папки для різноманітних проектів, у вас є тільки папки які організовані за типами файлів. Наприклад, папка для текстових документів, PDF-файлів, електронних таблиць і т. д. При роботі на конкретної завданням, яке включає в себе зміни в декількох типах, потрібно буде стрибати між різними папками і скролити або шукати потрібний файл у великій кількості файлів по кожній з папок. Виглядає не дуже зручно, не чи правда? Але це саме той підхід, який використовує ASP.NET MVC.

Основний недолік полягає в тому, що група файлів, організована за типом а не по цілі (features). І цих файлів не вистачає пов'язаності (high cohesion). У типовому проекті ASP.NET MVC, контролер буде пов'язаний з одним або більше View (в папці, яка відповідає імені контролера). Контролер має зв'язок з моделями (і / або ViewModels). Models / ViewModels будуть використовуватися View і т. д. Для того, щоб зробити зміни, доведеться шукати потрібні файли по всьому проекту.

Простий приклад
Розглянемо простий проект, в завдання якого входить управління чотирма слабо пов'язаними компонентами: User, Customer, Client та Payment. Організаційна структура папок за замовчуванням для цього проекту буде виглядати приблизно так:


Для того, щоб додати нове поле модель Client, відобразити його на View і додати деякі перевірки перед збереженням, потрібно переміститися в папку Models, знайти підходящу модель, потім перейти в Controllers і знайти ClientController, далі в папку Views. Навіть тільки з чотирма контролерами можна помітити, що потрібно робити багато навігації по проекту. В основному проект включає в себе набагато більше папок.

Альтернативним підходом до організації файлів за їх типом є організація файлів за того, що робить додаток (features). Замість папок для контролерів, моделей і Views, ваш проект буде складатися з папок організованих навколо певних features. При роботі над багом, який пов'язаний з конкретним feature, вам потрібно буде тримати менше папок відкритими, так як відповідні файли можуть бути збережені в одному місці.

Це може бути реалізоване кількома шляхами. Ми можемо використовувати Areas, але на мою думку вони не вирішують головної проблеми, або створити свою власну структура папок з features.

Feature Folders в ASP.NET Core MVC
Останнім часом великою популярністю користується новий підхід в організації структури папок для великих проектів, який називається Feature Folders. Це особливо актуально для команд використовуються підхід Vertical slice.

При організації проекту з features, створюється, як правило, коренева папка (наприклад, Features), в якій ви будете мати вкладені папки для кожної з features. Це дуже схоже на те, як організовані Areas. Однак, кожна папка з feature, буде включати в себе всі необхідні контролери, View, ViewModel і т. д. В більшості випадків у результаті ми отримаємо папку, можливо, від 5 до 15 файлів, які є всі тісно пов'язані один з одним. Весь вміст папки feature легко тримати в фокусі в Solution Explorer. Приклад цієї організації:


Переваги використання Feature Folders:

  • на відміну від Areas, нам не потрібні додаткові роуты
  • зменшується час на навігацію та пошук файлів за проектом
  • можна легко масштабувати, змінювати незалежно від інших features
  • дозволяє тримати менше відкритих папок в Solution Explorer
  • дає розуміння того, що саме робить додаток і які для цього потрібні
  • дає нам можливість повторного використання feature в інших проектах, шляхом простого копіювання папки
  • в системі контролю версій можна подивитися всі зміни, які стосуються конкретної feature
  • підвищує зв'язаність файлів
Реалізація Feature Folders в ASP.NET MVC
Для того щоб реалізувати таку організацію папок потрібно мати кастомний реалізацію інтерфейсів IViewLocationExpander і IControllerModelConvention. За конвеншену очікується, що контролер знаходиться у namespace з назвою «Features» і для наступного елемента в ієрархії namespace після «Features», має бути ім'я конкретного feature. Приклад реалізації IControllerModelConvention для пошуку контролерів:

FeatureConvention
public class FeatureConvention : IControllerModelConvention
{
public void Apply(ControllerModel controller) 
{
controller.Properties.Add("feature", GetFeatureName(controller.ControllerType));
}

private static string GetFeatureName(TypeInfo controllerType) 
{
var tokens = controllerType.FullName.Split('.');
if (tokens.All(t => t != "Features"))
return "";
var featureName = tokens
.SkipWhile(t => !t.Equals("features", StringComparison.CurrentCultureIgnoreCase))
.Skip(1)
.Take(1)
.FirstOrDefault();

return featureName;
}
}


Інтерфейс IViewLocationExpander надає метод, ExpandViewLocations, який використовується для того, щоб ідентифікувати папки, що містять Views.

FeatureFoldersRazorViewEngine
public class FeatureFoldersRazorViewEngine : IViewLocationExpander
{
public IEnumerable < string> ExpandViewLocations(ViewLocationExpanderContext context, 
IEnumerable < string> viewLocations) 
{
if (context == null) 
{
throw new ArgumentNullException(nameof(context));
}

if (viewLocations == null) 
{
throw new ArgumentNullException(nameof(viewLocations));
}

var controllerActionDescriptor = context.ActionContext.ActionDescriptor as ControllerActionDescriptor;
if (controllerActionDescriptor == null) 
{
throw new NullReferenceException("ControllerActionDescriptor cannot be null.");
}

string featureName = controllerActionDescriptor.Properties["feature"] as string;
foreach (var location in viewLocations) 
{
yield return location.Replace("{3}", featureName);
}
}

public void PopulateValues(ViewLocationExpanderContext context) { }
}


<habracut/>
Залишилося тільки використовувати реалізації інтерфейсів і додати деякі параметри в Startup класі:

Startup.cs
public void ConfigureServices(IServiceCollection services) 
{
services.AddMvc(o => o.Conventions.Add(new FeatureConvention()))
.AddRazorOptions(options =>
{
// {0} - Action Name
// {1} - Controller Name
// {2} - Feature Name
// Replace normal view location entirely
options.ViewLocationFormats.Clear();
options.ViewLocationFormats.Add("/Features/{2}/{1}/{0}.cshtml");
options.ViewLocationFormats.Add("/Features/{2}/{0}.cshtml");
options.ViewLocationFormats.Add("/Features/Shared/{0}.cshtml");
options.ViewLocationExpanders.Add(new FeatureFoldersRazorViewEngine());
});
}


<habracut/>
А як щодо моделей?
Тут потрібно зробити виняток для моєї попередньої структури. В реальному світі, ваша модель предметної області буде набагато складніше. Традиційна тришарова архітектура (data, business logic, presentation) до цих пір є одним з найбільш важливих концепцій для структурування програмного забезпечення. Важливо розуміти, що ASP.NET MVC не дає ніякої вбудованої підтримки для «моделей». ASP.NET MVC орієнтований на шар подання і не повинен покривати відповідальність від інших верств. З цієї причини, ми повинні перемістити файли моделей (Client.cs, ClientAddress.cs, Customer.cs, Payment.cs, User.cs) в окрему бібліотеку.

Спасибі за увагу!
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.