Додаток про загальний код у проектах MVC.
На цієї сторінці я хотів би додати пару методів розміщення загального коду, які я детально проаналізував на своей сторінці від 2008-року - Базовые странички ASP.NET - але тепер вже у проектах MVC. Тобто як причепити свій код, який би виконувався на кожної сторінці проекту MVC?
Ось перший засіб.
У класі System.Web.Mvc.Controller е дуже корисний та цікавий зворотній виклик OnActionExecuting - без якого я не знайшов можливості обійтися в жодному своєму проєкті на MVC. Ось, наприклад, одна сторінка від проєктів вотпуска у мене опублікована тут - Проекты нового Вотпуска. Там цей засіб розміщення загального коду використовується для утворення деякої структури даних TxtNodes - яка потім використовується для побудування навігації по сторінках сайту.
А тут я покажу про побудування за допомогою цього засобу контексту виконання поточного юзеру, тобто по загальному призначенню це дуже схоже за тим, що у мене описано для проєкта http://arenda.votpusk.ru/ на сторінці Базовые странички ASP.NET.
Отже, код контролерів побудований якось так:
1: Namespace OCMR
2: Public Class AdminController
3: Inherits System.Web.Mvc.Controller
4:
5: Dim CurrentUser As Global.OCMR.User
6:
7: Protected Overrides Sub OnActionExecuting(ctx As System.Web.Mvc.ActionExecutingContext)
8: MyBase.OnActionExecuting(ctx)
9: CurrentUser = CabAu.GetCurrentUser
10: If CurrentUser Is Nothing Then ctx.HttpContext.Response.RedirectPermanent("/Home/Index")
11: End Sub
12:
13: Function Index() As ActionResult
14: Return View()
15: End Function
16:
17: .....
18:
19: <HttpPost()> _
20: Function Logout(Prm As FormCollection) As ActionResult
21: FormsAuthentication.SignOut()
22: Return RedirectToActionPermanent("Index", "Home")
23: End Function
24:
25: End Class
26: End Namespace
І у цьому контроллері CurrentUser - це завжди поточний юзер:
1: Public Class CabAu
2:
3: Public Shared Function GetCurrentUser() As Global.OCMR.User
4:
5: If Not HttpContext.Current.User.Identity.IsAuthenticated Then Return Nothing
6: Dim db1 As New OCMRDataContext
7: Dim CurUser = (From X In db1.Users Select X Where X.Email.Trim = HttpContext.Current.User.Identity.Name).ToList
8: If CurUser.Count = 0 Then
9: Return Nothing
10: Else
11: Return CurUser(0)
12: End If
13: End Function
14:
15: End Class
Як бачите, це дуже зручно! Особливо у маленьких проєктах, де взагалі усіх юзерів можно взяти у кеш і взагалі не торкатися до бази щоб знайти контекст поточного юзеру. Крім того, як бачите, це автоматично захищає код контролеру від анонімів.
І тепер другий цікавий засіб, якого теж не було раніше у проєктах ASP.NET - який з'явився лише у ASP.NET MVC. У проектах ASP.NET необхідності у цьому засобі не було, так як студія автоматично при утворенні Master Page утворювала класс з кодом, на якому можна було помістити загальний код (це четвертий описаний мною засіб на цієї сторінці Базовые странички ASP.NET.
У проєктах MVC такий класс з кодом треба утворювати самому (вручну). Для цього потрібно у першій стрічці Master Page зробити наслідок не від мікрософтовского класу System.Web.Mvc.ViewMasterPage а від свого классу (у данному випадку OCMR.MasterShared):
1: <%@ Master Language="VB" Inherits="OCMR.MasterShared" %>
2:
3: <!DOCTYPE html>
4: <html>....
51: <div class="main">
52: <asp:ContentPlaceHolder ID="MainContent" runat="server">
53: </asp:ContentPlaceHolder>
54: </div>....
61: </body>
62: </html>
Таким чином з'являється загальний код Master Page, який буде виконуватися на усіх сторінках:
1: Public Class MasterShared
2: Inherits System.Web.Mvc.ViewMasterPage
3:
4:
5: Private Sub Page_Load(sender As Object, e As System.EventArgs) Handles Me.Load....
10: End Sub
11:
12: End Class
У цьому класі можна робити що завгодно, наприклад, найбільш зручно перевіряти аутентифікацію та будувати навігацію саме у цьому місці.
І третій, неможливий у простому ASP.NET засіб - наслідування одного контроллеру від іншого. З якоїсь точки зору увесь ASP.NET - це один контроллер, а якщо з'являється декілька контролерів, то навіщо ж розміщати загальний код проєкту у кожному?
Спочатку я поясню загальну постанову задачі, у якій цей метод можливо застосувати. Ось, наприклад я маю проект, у якому є звичайний юзер, потім редактор, який має трошки більше повноважень, потім супервізор, який має ще більше повноважень. Пункти меню цих користувачив сайту виглядають ось так:
Як бачите, супервізор має усі функції редактора, а редактор має усі функції звичайного юзера. Але "у лоб" зробити наслідування одного контролера від іншого не вийде, так працює System.Web.Mvc.WebFormViewEngine - навіть коли я явним чином задаю контролер, якщо System.Web.Mvc.WebFormViewEngine побачить однакові методи у обох контролерах, він видасть помилку:
Можливо навіть зробити власній якось так, але й це не врятує. Тому метод повинен буде один на всі контролери проекту, тобто у данному випадку метод EULA повинен присутній тільки в контролері Home, а у контролерах Admin та Supervisor повинні бути лише додаткові MVC-методи.
Зрозуміло, що стандартний механізм пошуку VIEW потребує, щоб VIEW знайшлося або у папці з контролером, або у папці SHARED. Тоді двигун MVC зможе заштовхнути дані, які видав контролер у VIEW і таким чином сформувати HTML, як це у мене описано на сторінці - Кешування вхідної сторінки сайту.
Але справа в тому, що у мене є аж чотири MasterPage, і MasterPage трошки відрізняються у кожного типу юзера, наприклад загальним фоном та підвалом сайту. Тому для одного ж того метода (наприклад СROP), у кожному контролері мені потрібно перемикати MasterPage.
Зробити це можливо ось таким кодом:
1: Namespace OCMR
2:
3: <NoCache()>
4: Public Class CabController
5: Inherits System.Web.Mvc.Controller
6:
7: Public MasterName As String
8: Public RefName As String
9:
10: Protected Overrides Sub OnActionExecuting(ctx As System.Web.Mvc.ActionExecutingContext)
11: ...
12: If CurrentUser Is Nothing Then ctx.HttpContext.Response.Redirect("/Home/Index")
13: Select Case ControllerContext.Controller.GetType().Name
14: Case "CabController"
15: MasterName = "MC"
16: Case "AdminController"
17: MasterName = "MA"
18: Case "SupervisorController"
19: MasterName = "MS"
20: Case Else
21: MasterName = "M1"
22: End Select
23: If Request.UrlReferrer IsNot Nothing Then
24: If Request.UrlReferrer.AbsolutePath.Contains("Supervisor") Then
25: RefName = "Supervisor"
26: ElseIf Request.UrlReferrer.AbsolutePath.Contains("Admin") Then
27: RefName = "Admin"
28: ElseIf Request.UrlReferrer.AbsolutePath.Contains("Cab") Then
29: RefName = "Cab"
30: End If
31: End If
32: End Sub
33: ....
Тобто тут у полях MasterName та RefName буде зберігатися ім'я контролеру. І якщо необхідно змінювати логіку роботи метода (який розміщується лише в одному місці - у базовому класі контролеру), то логіку спільного у всіх контролерах методу можно змінювати залежно від контексту контролера ось таким чином:
More How to create Razor html-helper in VB.NET, Define common code as Function and Helpers (directly in View and in the codeBehind)