Що насправді MVC?


202

Як серйозний програміст, як ви відповідаєте на питання Що таке MVC?

На мій погляд, MVC - це якась туманна тема - і через це, якщо ваша аудиторія є студентом, то ви можете вільно описати це, що навряд чи буде суперечливим.

Однак, якщо ви розмовляєте з знаючою аудиторією, особливо з інтерв'юером, мені важко думати про напрямок, який слід взяти, який не ризикує отримати реакцію "добре це не так! ...". Усі ми маємо різний досвід у реальному світі, і я справді двічі не зустрічав однакову схему впровадження MVC.

Зокрема, начебто існують розбіжності щодо суворості, визначення компонентів, розділення частин (яка деталь підходить куди) тощо.

Отже, як я можу пояснити MVC правильним, стислим та безперечним?


4
Примітка: Якщо ви працюєте в ASP.NET, MVC має друге, неясне значення: ASP.NET MVC
Brian

MVC добре пояснили тут codepeaker.com/blogs/…
smzapp

Відповіді:


156

MVC - це архітектура програмного забезпечення - структура системи, яка відокремлює домен / додаток / бізнес (що завгодно) логіки від решти користувальницького інтерфейсу. Це робиться, розділяючи додаток на три частини: модель, вид і контролер.

Модель керує фундаментальною поведінкою та даними програми. Він може відповідати на запити про інформацію, відповідати на вказівки щодо зміни стану своєї інформації та навіть сповіщати спостерігачів у системах, керованих подіями, коли інформація змінюється. Це може бути база даних або будь-яка кількість структур даних або систем зберігання даних. Словом, це дані та управління даними програми.

Перегляд ефективно забезпечує елемент інтерфейсу користувача програми. Він переведе дані з моделі у форму, що підходить для користувальницького інтерфейсу.

Контролер отримує введення користувача та здійснює дзвінки до модельних об'єктів та подання для виконання відповідних дій.

Загалом ці три компоненти працюють разом, щоб створити три основні компоненти MVC.


7
+1 Я дійсно вважаю за краще MVC вважати архітектурою трьох (або більше) шаблонів, ніж шаблон дизайну. Не існує канонічної реалізації, вона просто не така вже й мала, і всі реалізації матимуть трохи більше, ніж основні компоненти, що маються на увазі.
Янніс

51
Хоча ця відповідь має 21 відгук, я вважаю, що речення "Це може бути база даних або будь-яка кількість структур даних або систем зберігання даних (tl; dr: це дані та управління даними програми)" жахливо. Модель є чистою логікою бізнесу / домену. І це може і повинно бути набагато більше, ніж управління даними програми. Я також розрізняю логіку домену та логіку додатків. Контролер ніколи не повинен містити логіку бізнесу / домену або спілкуватися з базою даних безпосередньо.
Сокіл

9
Я не можу більше погодитися з цією відповіддю просто тому, що він стверджує, що mvc є раціональним поза шаром презентації. Решта відповіді в порядку. MVC повинен починатися і закінчуватися на вашому презентаційному шарі і абсолютно не повинен містити у вас бізнес-логіку та сховище. Це ефективно розміщує всю вашу програму у вашому шарі презентацій і не робить доступного API, який би дозволяв прямий доступ до вашої бізнес-логіки чи чистих даних, не будучи призначеним для вихідного додатка. Це не відкрито для розширюваності, перегляньте моделі, які ви наближаєтесь, але ви все одно не вистачаєте сипучої муфти
Jimmy Hoffa

6
@Jimmy: У багатьох конструкціях MVC моделі можуть бути повторно використані в API, оскільки вони не мають залежностей від інтерфейсу користувача - про це береться розділення між поданням та моделлю. Але це, звичайно, залежить від того, як ви вирішите визначити "модель". Якщо ви збираєтесь приймати судження про MVC, спочатку слід пояснити, яку інтерпретацію MVC ви використовуєте.
Оуен С.

5
@Yannis: Це просто ставить питання: що таке архітектура шаблонів? Чому б ти не назвав це просто іншим дизайнерським малюнком? У самому визначенні шаблону дизайну в GoF (і Олександрі) чітко видно, що шаблони не повинні прописати одну канонічну реалізацію (хоча популярність обох книг трохи занижує це поняття).
Оуен С.

136

Аналогія

Я пояснив MVC татові так:

MVC (Model, View, Controller) - це схема організації коду в додатку для покращення технічного обслуговування.

Уявіть фотографа зі своїм фотоапаратом у студії. Замовник просить його сфотографувати коробку.

Коробка - модель , фотограф - контролер, а камера - вид .

Оскільки вікно не знає ні про камеру, ні про фотографа, вона абсолютно незалежна. Такий поділ дозволяє фотографу ходити по коробці і наводити камеру під будь-яким кутом, щоб отримати знімок / вигляд, який він хоче.

Архітектури, що не належать до MVC, як правило, тісно інтегровані разом. Якби коробка, контролер і камера були одним і тим самим об'єктом, тоді нам доведеться роз'єднатись, а потім перестроювати і коробку, і камеру кожного разу, коли ми хотіли отримати новий вигляд. Також фотографувати завжди було б як спробувати зробити селфі - і це не завжди дуже просто.


Детальне пояснення

Лише після прочитання наступного запитання / відповіді, який я передавав, я відчув, що розумію MVC. Цитата: https://mail.python.org/pipermail/python-list/2006-January/394968.html

bwaha написав:

Автор посилається на mvctree.py в wxPython як приклад дизайну MVC. Однак я все ще занадто зелений, тому вважаю, що конкретний приклад є занадто складним, і я не розумію розлуки, яку рекомендує автор.

MVC - це все, що стосується відокремлення проблем.

Модель несе відповідальність за управління даними програми (як приватними, так і клієнтськими). Перегляд / контролер відповідає за забезпечення зовнішнього світу засобами взаємодії з клієнтськими даними програми.

Модель пропонує внутрішній інтерфейс (API), що дозволяє іншим частинам програми взаємодіяти з нею. Контролер View / Controller пропонує зовнішній інтерфейс (GUI / CLI / веб-форма / IPC високого рівня / тощо), що дозволяє всім поза програмою спілкуватися з нею.

Модель несе відповідальність за збереження цілісності даних програми, тому що якщо це пошкоджується, то це гра закінчена для всіх. Перегляд / контролер відповідає за збереження цілісності інтерфейсу користувача, переконуючись, що всі перегляди тексту відображають актуальні значення, вимикають пункти меню, які не стосуються поточного фокусу тощо.

Модель не містить коду перегляду / контролера; немає класів віджетів GUI, немає коду для розміщення діалогових вікон або отримання вводу користувача. Перегляд / контролер не містить коду моделі; відсутній код для перевірки URL-адрес або виконання запитів SQL, а також вихідний стан: будь-які дані, що зберігаються віджетами, призначені лише для відображення, а лише відображення справжніх даних, що зберігаються в Моделі.

Тепер ось тест справжнього дизайну MVC: програма, по суті, повинна бути повністю функціональною навіть без доданого View / Controller. Гаразд, зовнішній світ матиме проблеми з взаємодією з ним у такому вигляді, але поки хтось знає відповідні заклики API API, програма буде зберігати та обробляти дані як звичайні.

Чому це можливо? Ну, проста відповідь полягає в тому, що все це завдяки низькій зв'язці між шарами Model і View / Controller. Однак це ще не повна історія. Ключове значення для всього шаблону MVC - це напрямок, в якому йде це з'єднання: ВСІ інструкції надходять від View / Controller до Model. Модель НІКОЛИ не повідомляє View / Controller що робити.

Чому? Тому що в MVC, хоча View / Controller дозволено трохи знати про Модель (конкретно, API моделі), але Моделі заборонено знати нічого про View / Controller.

Чому? Тому що MVC збирається створити чітке розмежування проблем.

Чому? Щоб запобігти складності програми, яка вийшла з-під контролю і поховала вас, розробника, під нею. Чим більша програма, тим більша кількість компонентів у цій програмі. І чим більше зв'язків існує між цими компонентами, тим складніше розробникам підтримувати / розширювати / замінювати окремі компоненти або навіть просто слідкувати за тим, як працює вся система. Задайте собі запитання: дивлячись на схему структури програми, ви б краще побачили дерево або котячу колиску? Шаблон MVC дозволяє уникнути останнього, забороняючи кругові з'єднання: B може з'єднуватися з A, але A не може з'єднуватися з B. У цьому випадку A - модель, а B - перегляд / контролер.

До речі, якщо ви різкі, ви помітите проблему із щойно описаним обмеженням "в одну сторону": як Модель може повідомити Перегляд / Контролер про зміни в даних користувачів моделі, коли Моделі навіть не дозволено знаєте, що Перегляд / Контролер, незважаючи на те, щоб надсилати на нього повідомлення? Але не хвилюйтеся: для цього є рішення, і воно досить акуратне, навіть якщо спочатку це здається трохи крутим. Ми повернемося до цього через мить.

На практиці, об'єкт View / Controller може через API моделі 1. сказати Моделі робити речі (виконувати команди) та 2. сказати Моделі дати їй речі (повернути дані). Шар Перегляд / Контролер висуває інструкції до шару Модель і витягує інформацію з шару Модель.

І ось тут ваш перший приклад MyCoolListControl піде не так, оскільки API цього класу вимагає, щоб в нього була спрямована інформація , тож ви повертаєтесь до двостороннього з’єднання між шарами, порушуючи правила MVC і скидаючи вас прямо назад у котяча архітектура колиски, якої ви [імовірно] намагалися уникнути в першу чергу.

Натомість клас MyCoolListControl повинен йти разом із потоком, витягуючи потрібні йому дані із шару нижче, коли це потрібно. Що стосується віджета списку, це, як правило, означає запитати, скільки значень існує, а потім по черзі запитувати кожен з цих елементів, оскільки це про найпростіший і найбідніший спосіб зробити це, і тому зводить мінімальну зв'язок до мінімуму. І якщо віджет хоче, скажімо, подати ці значення користувачеві в приємному алфавітному порядку, то це вже його негатив; і його відповідальність, звичайно.

Тепер, остання головоломка, як я натякав раніше: як ви зберігаєте дисплей інтерфейсу синхронізований із станом моделі в системі на базі MVC?

Ось проблема: багато об’єктів View є стаціонарними, наприклад, прапорець може бути галочкою або відмічено, текстове поле може містити текст, який можна редагувати. Однак MVC диктує, що всі користувацькі дані зберігаються на рівні Model, тому будь-які дані, що зберігаються іншими шарами для цілей відображення (стан прапорця, поточний текст текстового поля), таким чином, повинні бути допоміжною копією цих первинних даних моделі. Але якщо стан Моделі зміниться, копія перегляду цього стану вже не буде точною і потребує оновлення.

Але як? Шаблон MVC перешкоджає, щоб Модель штовхала свіжу копію цієї інформації в шар Перегляд. Чорт забирає, вона навіть не дозволяє Моделі надсилати Погляд повідомлення, щоб сказати, що його стан змінився.

Ну, майже. Гаразд, шару Model не дозволяється спілкуватися безпосередньо з іншими шарами, оскільки для цього потрібно, щоб він знав щось про ці шари, і правила MVC цьому перешкоджають. Однак, якщо дерево падає в лісі і його ніхто не чує, це звучить?

Відповідь, ви бачите, це створити систему сповіщень, забезпечивши шару "Модель" місце, яке він може оголосити нікому, зокрема, що він щойно зробив щось цікаве. Потім інші шари можуть публікувати слухачів із цією системою сповіщень, щоб слухати ті повідомлення, які їх насправді цікавлять. Шар моделі не повинен нічого знати про те, хто слухає (або навіть якщо хтось взагалі слухає!); він просто розміщує оголошення, а потім забуває про нього. І якщо хтось почує це оголошення і захоче щось робити після цього - як-от запитати у Моделі якісь нові дані, щоб він міг оновити її екранне відображення - то чудово. Модель просто перераховує, які сповіщення він надсилає як частину свого визначення API; і те, що хтось ще робить з цим знанням, залежить від них.

MVC збережений, і всі радіють. Ваша програма застосунків може забезпечити вбудовану систему сповіщень, або ви можете написати свою, якщо ні (див. "Шаблон спостерігача").

...

У будь-якому випадку, сподіваюся, що це допомагає. Як тільки ви зрозумієте мотивацію, що стоїть за MVC, причини, чому все робиться так, як вони є, починають мати сенс, навіть коли - на перший погляд - вони здаються складнішими, ніж потрібно.

Ура,

має


Як щодо MVVM та MVCS, я почув вашу відповідь MVC від softwareengineering.stackexchange.com/questions/184396/…
dengApro

86

MVC - це переважно казкове слово.

Раніше це вважалося зразком, але його первісне визначення 1979 року було скинуто, передано, неправильно витлумачено, виведено поза вихідного контексту. Це було погано переосмислено до того моменту, як воно починає нагадувати релігію, і хоча це, безумовно, допомагає його культовим вантажникам захищати його, його ім'я вже не асоціюється з твердим набором настанов . Таким чином, його вже не можна вважати зразком.

MVC ніколи не мав на меті описувати веб-програми.
Ні сучасні операційні системи, ні мови.
(деякі з них фактично зробили визначення 1979 року зайвим)

Це було зроблено до. І це не вийшло.

Зараз ми маємо справу з нецензурним гібридом web-mvc, який своїм жахливим статусом мовлення , неправильним визначенням та наявністю напівграмотних програмістів як цільової демографічної групи дає загалом погану рекламу програмних моделей взагалі.

MVC, таким чином, став відокремленням концернів для людей, які не дуже хочуть над цим думати.

  • Модель даних обробляється одним способом,
  • вид в інший,
  • решта просто називається "контролером" і залишається на розсуд читача.

Веб-сайти / веб-додатки у 90-х насправді не використовували для розділення проблем.

Вони були жахливими сучками змішаного коду спагетті.
Зміни інтерфейсу, оновлення та перестановка даних були неймовірно важкими, дорогими, довгими, гнітючими, нещасними.

Веб-технології, такі як ASP, JSP і PHP, занадто легко змішують проблеми перегляду даних і проблем із додатками. Прибулі на поле зазвичай випромінюють нерозривні кодові грязі, як у ті старі часи.

Таким чином, все більше людей почали повторювати "використовувати MVC" у нескінченних петлях на форумах підтримки. Кількість людей розширилася до того, що включають менеджерів та маркетологів (до деяких термін уже був знайомий, з тих часів програмування gui, в якому цей зразок мав сенс), і це стало темою моторошного слова, з яким ми маємо стикатися зараз .

На жаль, це здоровий глузд , а не методологія .
Це відправна точка , а не рішення .
Це як казати людям дихати повітрям або робити сухарики , а не ліки від раку .


22
Це, звичайно, не в основному казкове слово. Це правда, що MVC, як правило, більш поширений і менш чіткий, ніж інші шаблони дизайну, тому ви можете вважати це як організаційний принцип або парадигму. Але як би ви його не називали, це фундаментальна концепція в ряді дуже успішних об'єктно-орієнтованих рамок. Зробити вигляд, що це просто казкове слово, тобто модна фраза, яка не означає дуже багато, - це зробити послугу перед ОП.
Калеб

23
It's a fancy word for pre-existing concepts that didn't really need one.І який шаблон дизайну / архітектура не відповідає цьому опису?
янніс

8
+1 Відверто кажучи, більша частина цього матеріалу очевидна, коли ви зрозумієте основи (згуртованість, зв'язок, читабельність, ортоганальність тощо) та поєднаєте це з можливостями сучасних мов.
lorean

12
The data model is handled one way, the view in another, the rest is just named "controller"+1
c69

33
-1. Я хочу, щоб я міг -10 за всі ідіотичні +1 коментарі. Як будь-який із цього "очевидного" враховує основні принципи зв'язку та згуртованості? Архітектури інтерфейсу мають багато, включаючи MVC, MVP, MVVM, Forms та модель Smalltalk. Деякі компанії також підштовхують архітектуру Composite Application до крайнього рівня, наприклад, у WS-CAF. Сказати, що «здоровий глузд» автоматично призводить вас до того, що MVC містить приблизно стільки води, скільки так званий доказ Декарта Божого. Це, очевидно, те, що ви знаєте, але ваша відповідь демонструє або незнання інших методів, або неможливість розширити власний кругозір.
Aaronaught

39

Найкращий спосіб визначити це - перейти до оригінальних творів Тригве Реенскауга , який його винайшов: http://heim.ifi.uio.no/~trygver/themes/mvc/mvc-index.html

Цей документ, зокрема, вважається текстом, що визначається: http://heim.ifi.uio.no/~trygver/1979/mvc-2/1979-12-MVC.pdf

МОДЕЛІ

Моделі представляють знання. Модель може бути одним об’єктом (досить нецікавим), або це може бути якась структура об'єктів ...

Між моделлю та її частинами, з одного боку, та представленим світом, як сприймається власником моделі, з іншого боку, повинно бути одне на одне. Отже, вузли моделі повинні представляти ідентифікуючу частину проблеми.

Усі вузли моделі повинні бути на одному рівні проблеми, це заплутане і вважається поганою формою змішування вузлів, орієнтованих на проблеми (наприклад, призначення календаря) з деталями реалізації (наприклад, абзацами).

ОГЛЯДИ

Вид - це (візуальне) представлення його моделі. Зазвичай це підкреслює певні атрибути моделі та пригнічує інші. Таким чином, він виступає як презентаційний фільтр .

Вигляд додається до його моделі (або частини моделі) і отримує дані, необхідні для презентації від моделі, задаючи питання. Він також може оновити модель, надсилаючи відповідні повідомлення. Усі ці запитання та повідомлення повинні містити термінологію моделі, тому погляд повинен знати семантику атрибутів моделі, яку вона представляє. (Наприклад, він може запитати ідентифікатор моделі і очікувати екземпляра Text; він може не вважати, що модель класу Text.)

КОНТРОЛЕРИ

Контролер - це зв'язок між користувачем та системою. Він надає користувачеві можливість введення даних, організувавши відповідні види, щоб представити себе у відповідних місцях на екрані. Він забезпечує засоби для вихідних даних, представляючи користувачеві меню або інші засоби передачі команд і даних. Контролер отримує такий вихід користувача, переводить його у відповідні повідомлення та передає ці повідомлення на. До одного чи декількох переглядів.

Контролер ніколи не повинен доповнювати представлення даних, наприклад, він ніколи не повинен з'єднувати погляди вузлів, малюючи стрілки між ними.

І навпаки, представлення ніколи не повинно знати про введення користувача, наприклад, операції з мишею та натискання клавіш. Завжди має бути можливість записати метод у контролер, який надсилає повідомлення переглядам, які точно відтворюють будь-яку послідовність команд користувача.

РЕДАКТОРИ

Контролер пов'язаний з усіма його видами, їх називають частинами контролера. У деяких представленнях передбачений спеціальний контролер, редактор , який дозволяє користувачеві змінювати інформацію, яку подає представлення. Такі редактори можуть бути сплановані на шлях між контролером та його поданням, і вони будуть діяти як розширення контролера. Після завершення процесу редагування редактор видаляється зі шляху та відкидається.

Зауважте, що редактор спілкується з користувачем за допомогою метафор підключеного перегляду, тому редактор тісно пов'язаний з поданням. Контролер знайдеться за редактора, запитуючи перегляд його - іншого відповідного джерела немає.


11

MVC - це модель дизайну, що використовується для ізоляції бізнес-логіки від презентації.

Він відрізняється від багатьох інших моделей дизайну тим, що зазвичай він не реалізований лаконічно, а є основою рамки.

Хоча програма, що реалізує шаблон стратегії, є лише невеликою деталлю про це, кажучи, що веб-додаток використовує шаблон дизайну MVC дуже визначає його архітектуру .


2
Це не дуже суттєво, є дуже специфічні вимоги до впровадження схеми MVC, що робить її відмінною від MVP, MP, MVVM. Він також відрізняється цільовою аудиторією від інших моделей презентації.
Ян

8

MVC - це програмне забезпечення, яке розділяє такі компоненти системи або підсистеми:

  1. Модель - дані про стан програми або її компонентів. Може включати в себе підпрограми для зміни або доступу.
  2. Вид - інтерпретація даних (модель). Це обмежується лише візуальним поданням, але може бути аудіо, похідною інформацією (наприклад, статистика, що передається в інший об'єкт моделі) тощо. Крім того, одна модель може мати кілька переглядів.
  3. Управління - обробляє зовнішній вхід до системи, що викликає модифікації моделі. Керування / перегляд можуть бути тісно пов'язані (у випадку з інтерфейсом користувача). Однак можуть бути оброблені інші зовнішні входи (наприклад, мережеві команди), які повністю не залежать від перегляду.

6

Я б сказав, що MVC - це концепція чи сім'я подібних моделей.

Я думаю, цю статтю варто прочитати. Архітектури GUI Мартіна Фаулера


5
Ця стаття Фоулера відмінна, і кожен, хто використовує термін MVC, повинен її прочитати. Два моменти, які мені здаються особливо цікавими, полягають у тому, що оригінальне використання терміна MVC в графічних інтерфейсах досить відрізняється від використання в веб-рамках, а в графічних інтерфейсах поділ між представленням та контролером виявилося менш корисним, ніж передбачалося.
Том Андерсон

3

По-перше, ви повинні визначити, хто є запитувачем питання, і яку відповідь він шукає. Ви відповідаєте на це запитання ще одним запитанням, таким як "У якому сенсі?"

Ви можете запитати, якщо вони взагалі посилаються на MVC, конкретну реалізацію MVC (тобто asp.net MVC, весняний MVC, smalltalk MVC тощо), що це технічно, що це філізофічно (так, це філософія) та ін.

Якщо це питання на тесті, і ви не можете попросити запитувача уточнити, вам доведеться здогадуватися, виходячи з контексту.

Хороша, проста відповідь:

MVC - це архітектура користувальницького інтерфейсу, що використовується для відокремлення структурних та поведінкових проблем з метою полегшення програмного забезпечення.

Ви також можете сказати:

Відокремлюючи погляд від контролера від моделі, це заохочує ізоляцію компонентів, виходячи з їхніх обов'язків. Теоретично, і зазвичай на практиці, це допомагає поліпшити ремонтопридатність, не дозволяючи різним частинам системи спільно змішуватися та створювати більш складні системи.

Але, врешті-решт, ви будете судити про те, чи дасте ви відповідь, яку вони очікують. Єдине рішення проблеми - з’ясувати, яку відповідь вони очікують.


2

Ось, що я б сказав про це. Я б спробував пояснити це з точки зору мобільних додатків, тому що це те, що мені найбільше знайоме і тому, що я не думаю, що я повністю зрозумів це, перш ніж почати робити мобільні додатки.
Візьмемо для прикладу Android.
Презентаційний шар, тобто. користувальницький інтерфейс може (слід, найчастіше, є) повністю вказати у xml. Для простоти скажімо, що один xml-файл описує один екран у програмі. XML-файл визначає елементи керування, компонування елементів керування, позиціонування, кольори, розмір, мітки рядків ... все, що стосується презентації. Але він нічого не знає про те, коли він буде викликаний, коли він буде розміщений на екрані. Це буде окремий макет або частина якогось більшого макета? Там у вас це є: ваш ідеальний ПОГЛЯД .

Тепер погляд очевидно потрібно розмістити на екрані в якийсь момент, так як це робити? Ваш CONTROLLER в Android під назвою Діяльність. Як випливає з назви, діяльність виконує деяку діяльність. Навіть якщо його єдиною метою є відображення подання, визначеного на кроці 1, воно виконає певну дію. Отже, активність отримує подання та відображає його на екрані. Оскільки перегляд нічого не знає про діяльність, так само діяльність нічого не знає про фактичну презентацію. Ми (програмісти) могли кілька разів переставляти макет перегляду, не змінюючи навіть одного рядка коду в нашій діяльності.

Зараз, не дуже багато користі для представлення вашого приємного блискучого, чітко визначеного формату XML, фактично нічого не роблячи. Скажімо, ми хочемо зберігати дані, введені користувачем. Діяльність повинна вирішити цей процес: від передачі даних у користувача, передачі їх комусь іншому для обробки (обробляти, зберігати, видаляти). Кому це передасть? Ну, до МОДЕЛІ . Мені подобається думати про модель як про чисту. клас java, який нічого не знає про контекст програми, в якому він живе. (На практиці це майже ніколи не буде).

Скажімо, у мене є особа класу, яка має три властивості: ім'я, адресу, вік. Мій визначений XML макет містить 3 поля для введення користувачем: ім'я, адресу, вік. Моя діяльність бере три значення з введення користувачем, створює новий об'єкт Person і викликає на ньому якийсь метод, який знає, як обробляти певну логіку, характерну для Особи. Там у вас є. Модель-перегляд-контролер.


1

Я завжди починаю з того, щоб сказати їм, що модель не є новим і існує вже багато років ... на цьому етапі вони надають мені допитливий вигляд і БАМ !, вони підключені:

І тоді я б дуже багато говорив про різні моменти, як попередні відповіді, але я думаю, що важливо бути і контекстуальним, як сказав JB King, ASP.NET MVC тощо,

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.