Làm cách nào để đặt thuộc tính ViewBag cho tất cả các Chế độ xem mà không cần sử dụng lớp cơ sở cho Bộ điều khiển?


96

Trước đây, tôi đã mắc kẹt các thuộc tính chung, chẳng hạn như người dùng hiện tại, vào ViewData / ViewBag theo kiểu toàn cầu bằng cách để tất cả các Bộ điều khiển kế thừa từ một bộ điều khiển cơ sở chung.

Điều này cho phép tôi sử dụng IoC trên bộ điều khiển cơ sở và không chỉ tiếp cận với dữ liệu được chia sẻ toàn cầu.

Tôi tự hỏi liệu có cách nào khác để chèn loại mã này vào đường ống MVC không?

Câu trả lời:


22

Chưa thử bởi tôi, nhưng bạn có thể xem đăng ký chế độ xem của mình và sau đó thiết lập dữ liệu chế độ xem trong quá trình kích hoạt.

Bởi vì lượt xem được đăng ký nhanh chóng, cú pháp đăng ký không giúp bạn kết nối với Activatedsự kiện, vì vậy bạn cần thiết lập nó trong Module:

class SetViewBagItemsModule : Module
{
    protected override void AttachToComponentRegistration(
        IComponentRegistration registration,
        IComponentRegistry registry)
    {
        if (typeof(WebViewPage).IsAssignableFrom(registration.Activator.LimitType))
        {
            registration.Activated += (s, e) => {
                ((WebViewPage)e.Instance).ViewBag.Global = "global";
            };
        }
    }
}

Đây có thể là một trong những gợi ý kiểu "công cụ duy nhất là một cái búa" từ tôi; có thể có những cách đơn giản hơn hỗ trợ MVC để thực hiện nó.

Chỉnh sửa: Cách tiếp cận thay thế, ít mã hơn - chỉ cần đính kèm vào Bộ điều khiển

public class SetViewBagItemsModule: Module
{
    protected override void AttachToComponentRegistration(IComponentRegistry cr,
                                                      IComponentRegistration reg)
    {
        Type limitType = reg.Activator.LimitType;
        if (typeof(Controller).IsAssignableFrom(limitType))
        {
            registration.Activated += (s, e) =>
            {
                dynamic viewBag = ((Controller)e.Instance).ViewBag;
                viewBag.Config = e.Context.Resolve<Config>();
                viewBag.Identity = e.Context.Resolve<IIdentity>();
            };
        }
    }
}

Chỉnh sửa 2: Một cách tiếp cận khác hoạt động trực tiếp từ mã đăng ký bộ điều khiển:

builder.RegisterControllers(asm)
    .OnActivated(e => {
        dynamic viewBag = ((Controller)e.Instance).ViewBag;
        viewBag.Config = e.Context.Resolve<Config>();
        viewBag.Identity = e.Context.Resolve<IIdentity>();
    });

Chính xác những gì tôi cần. Cập nhật câu trả lời để giải quyết vấn đề
Scott Weinstein

Công cụ tuyệt vời - dựa trên cách tiếp cận của bạn, tôi đã thêm một đơn giản hóa khác, lần này không cần mô-đun.
Nicholas Blumhardt

là những gì Resolvemột phần của e.Context.Resolve? Tôi nên đề cập đến Tôi đang sử dụng để Ninject ...
drzaus

243

Cách tốt nhất là sử dụng ActionFilterAttribute và đăng ký lớp tùy chỉnh của bạn trong toàn cầu của bạn. asax (Application_Start)

public class UserProfilePictureActionFilter : ActionFilterAttribute
{

    public override void OnResultExecuting(ResultExecutingContext filterContext)
    {
        filterContext.Controller.ViewBag.IsAuthenticated = MembershipService.IsAuthenticated;
        filterContext.Controller.ViewBag.IsAdmin = MembershipService.IsAdmin;

        var userProfile = MembershipService.GetCurrentUserProfile();
        if (userProfile != null)
        {
            filterContext.Controller.ViewBag.Avatar = userProfile.Picture;
        }
    }

}

đăng ký lớp tùy chỉnh của bạn trong toàn cầu của bạn. asax (Application_Start)

protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        GlobalFilters.Filters.Add(new UserProfilePictureActionFilter(), 0);

    }

Sau đó, bạn có thể sử dụng nó trong tất cả các chế độ xem

@ViewBag.IsAdmin
@ViewBag.IsAuthenticated
@ViewBag.Avatar

Ngoài ra có một cách khác

Tạo một phương thức mở rộng trên HtmlHelper

[Extension()]
public string MyTest(System.Web.Mvc.HtmlHelper htmlHelper)
{
    return "This is a test";
}

Sau đó, bạn có thể sử dụng nó trong tất cả các chế độ xem

@Html.MyTest()

9
Tôi không hiểu tại sao điều này không được ủng hộ nhiều hơn; đó là một cách tiếp cận nhiều ít xâm lấn hơn so với những người khác
joshcomley

5
8 giờ nghiên cứu để tìm ra đây ... câu trả lời hoàn hảo. Cảm ơn bạn rất nhiều.
deltree

3
+1 Cách tốt và sạch để tích hợp dữ liệu toàn cầu. Tôi đã sử dụng kỹ thuật này để đăng ký phiên bản trang web của mình trên tất cả các trang.
Will Bickford

4
Giải pháp tuyệt vời, dễ dàng và không phô trương.
Eugen Timm

3
Nhưng IoC ở đâu? tức là bạn sẽ chuyển ra MembershipServicenhư thế nào?
drzaus

39

Vì các thuộc tính ViewBag, theo định nghĩa, được gắn với bản trình bày dạng xem và bất kỳ logic dạng xem nhẹ nào có thể cần thiết, tôi sẽ tạo một WebViewPage cơ sở và đặt các thuộc tính khi khởi tạo trang. Nó rất giống với khái niệm bộ điều khiển cơ sở cho logic lặp lại và chức năng chung, nhưng đối với quan điểm của bạn:

    public abstract class ApplicationViewPage<T> : WebViewPage<T>
    {
        protected override void InitializePage()
        {
            SetViewBagDefaultProperties();
            base.InitializePage();
        }

        private void SetViewBagDefaultProperties()
        {
            ViewBag.GlobalProperty = "MyValue";
        }
    }

Và sau đó \Views\Web.config, đặt thuộc pageBaseTypetính:

<system.web.webPages.razor>
    <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <pages pageBaseType="MyNamespace.ApplicationViewPage">
      <namespaces>
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Routing" />
      </namespaces>
    </pages>
  </system.web.webPages.razor>

Vấn đề với thiết lập này là nếu bạn đang đặt giá trị cho một thuộc tính trong ViewBag trên một chế độ xem và sau đó cố gắng truy cập nó trên một chế độ xem khác (như chế độ xem _Layout được chia sẻ của bạn), thì giá trị giá trị được đặt trên chế độ xem đầu tiên sẽ là bị mất trên chế độ xem bố cục.
Pedro

@Pedro điều đó chắc chắn đúng, nhưng sau đó tôi tranh luận rằng ViewBag không có nghĩa là một nguồn trạng thái liên tục trong ứng dụng. Có vẻ như bạn muốn dữ liệu đó ở trạng thái phiên và sau đó bạn có thể kéo dữ liệu đó ra trong trang xem cơ sở của mình và đặt nó trong ViewBag nếu nó tồn tại.
Brandon Linton

Bạn có một điểm hợp lệ nhưng hầu hết mọi người đều sử dụng tập dữ liệu trong một chế độ xem trong các chế độ xem khác; như khi bạn đặt tiêu đề của trang trong một chế độ xem và chế độ xem bố cục được chia sẻ của bạn sau đó in nó ra trong các thẻ <title> của tài liệu html. Tôi thậm chí còn muốn tiến thêm một bước nữa bằng cách đặt các boolean như "ViewBag.DataTablesJs" trong chế độ xem "con" để làm cho chế độ xem bố cục "chính" bao gồm các tham chiếu JS thích hợp trên đầu html. Miễn là nó liên quan đến bố cục, tôi nghĩ nó là ok để làm điều này.
Pedro

@Pedro tốt trong tình huống thẻ tiêu đề, thường được xử lý với mỗi chế độ xem thiết lập một thuộc ViewBag.Titletính và sau đó điều duy nhất trong bố cục được chia sẻ là <title>@ViewBag.Title</title>. Nó sẽ không thực sự thích hợp cho một cái gì đó giống như trang xem ứng dụng cơ sở vì mỗi chế độ xem là khác nhau và trang chế độ xem cơ sở sẽ dành cho dữ liệu thực sự phổ biến trên tất cả các chế độ xem.
Brandon Linton

@Pedro Tôi hiểu bạn đang nói gì và tôi nghĩ Brandon đã bỏ sót ý ở đó. Tôi đang sử dụng Trang WebViewPage tùy chỉnh và tôi đã cố chuyển một số dữ liệu từ một trong các chế độ xem sang chế độ xem bố cục bằng cách sử dụng thuộc tính tùy chỉnh trong Trang WebViewPage tùy chỉnh. Khi tôi đặt thuộc tính trong dạng xem, nó sẽ cập nhật ViewData trong WebViewPage tùy chỉnh của tôi nhưng khi nó đến dạng xem bố cục, mục ViewData đã bị mất. Tôi đã vượt qua nó bằng cách sử dụng ViewContext.Controller.ViewData ["SomeValue"] trong WebViewPage tùy chỉnh. Tôi hi vọng nó giúp ích cho ai đó.
Imran Rashid

17

Bài đăng của Brandon rất đúng về tiền. Trên thực tế, tôi sẽ tiến hành thêm một bước nữa và nói rằng bạn chỉ nên thêm các đối tượng chung của mình làm thuộc tính của WebViewPage cơ sở để bạn không phải truyền các mục từ ViewBag trong mỗi Chế độ xem. Tôi thiết lập CurrentUser theo cách này.


Tôi không thể có được điều này để làm việc với các lỗi'ASP._Page_Views_Shared__Layout_cshtml' does not contain a definition for 'MyProp' and no extension method 'MyProp' accepting a first argument of type 'ASP._Page_Views_Shared__Layout_cshtml' could be found (are you missing a using directive or an assembly reference?)
Sprintstar

+1 về điều này, đây chính xác là những gì tôi đang làm để chia sẻ một phiên bản của lớp tiện ích không tĩnh cần có sẵn trên toàn cầu trong tất cả các chế độ xem.
Nick Coad

9

Bạn có thể sử dụng một ActionResult tùy chỉnh:

public class  GlobalView : ActionResult 
{
    public override void ExecuteResult(ControllerContext context)
    {
        context.Controller.ViewData["Global"] = "global";
    }
}

Hoặc thậm chí một ActionFilter:

public class  GlobalView : ActionFilterAttribute 
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        filterContext.Result = new ViewResult() {ViewData = new ViewDataDictionary()};

        base.OnActionExecuting(filterContext);
    }
}

Có một dự án MVC 2 đang mở nhưng cả hai kỹ thuật vẫn được áp dụng với những thay đổi nhỏ.


5

Bạn không phải thực hiện các thao tác hay thay đổi mô hình, chỉ cần sử dụng một bộ điều khiển cơ sở và truyền bộ điều khiển hiện có từ văn bản xem bố cục.

Tạo bộ điều khiển cơ sở với dữ liệu chung mong muốn (tiêu đề / trang / vị trí, v.v.) và khởi tạo hành động ...

public abstract class _BaseController:Controller {
    public Int32 MyCommonValue { get; private set; }

    protected override void OnActionExecuting(ActionExecutingContext filterContext) {

        MyCommonValue = 12345;

        base.OnActionExecuting(filterContext);
    }
}

Đảm bảo mọi bộ điều khiển đều sử dụng bộ điều khiển cơ bản ...

public class UserController:_BaseController {...

Truyền bộ điều khiển cơ sở hiện có từ ngữ cảnh xem trong _Layout.cshmltrang của bạn ...

@{
    var myController = (_BaseController)ViewContext.Controller;
}

Bây giờ bạn có thể tham khảo các giá trị trong bộ điều khiển cơ sở của mình từ trang bố cục của bạn.

@myController.MyCommonValue

3

Nếu bạn muốn kiểm tra thời gian biên dịch và kiểm tra intellisense cho các thuộc tính trong dạng xem của mình thì ViewBag không phải là cách để thực hiện.

Hãy xem xét một lớp BaseViewModel và để các mô hình xem khác của bạn kế thừa từ lớp này, ví dụ:

Chế độ xem cơ sở

public class BaseViewModel
{
    public bool IsAdmin { get; set; }

    public BaseViewModel(IUserService userService)
    {
        IsAdmin = userService.IsAdmin;
    }
}

Xem ViewModel cụ thể

public class WidgetViewModel : BaseViewModel
{
    public string WidgetName { get; set;}
}

Bây giờ mã chế độ xem có thể truy cập trực tiếp vào thuộc tính trong chế độ xem

<p>Is Admin: @Model.IsAdmin</p>

2

Tôi đã nhận thấy cách tiếp cận sau là hiệu quả nhất và mang lại khả năng kiểm soát tuyệt vời bằng cách sử dụng tệp _ViewStart.chtml và các câu lệnh điều kiện khi cần thiết:

_ ViewStart :

@{
 Layout = "~/Views/Shared/_Layout.cshtml";

 var CurrentView = ViewContext.Controller.ValueProvider.GetValue("controller").RawValue.ToString();

 if (CurrentView == "ViewA" || CurrentView == "ViewB" || CurrentView == "ViewC")
    {
      PageData["Profile"] = db.GetUserAccessProfile();
    }
}

ViewA :

@{
   var UserProfile= PageData["Profile"] as List<string>;
 }

Ghi chú :

PageData sẽ hoạt động hoàn hảo trong Chế độ xem; tuy nhiên, trong trường hợp của Chế độ xem một phần, nó sẽ cần được chuyển từ Chế độ xem sang Phần con.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.