如何在不使用Controllers基类的情况下为所有View设置ViewBag属性?


96

过去,我通过使所有Controller都继承自一个通用基本控制器,以全局方式将诸如当前用户之类的通用属性粘贴到ViewData / ViewBag上。

这使我可以在基本控制器上使用IoC,而不仅可以与此类数据进行全局共享。

我想知道是否存在将这种代码插入MVC管道的替代方法?

Answers:


22

我未尝试过,但是您可能会考虑注册视图,然后在激活过程中设置视图数据。

由于视图是动态注册的,因此注册语法无法帮助您连接到Activated事件,因此您需要在中进行设置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";
            };
        }
    }
}

这可能是我提出的“唯一的工具”。可能会有更简单的启用MVC的方法来实现。

编辑:替代方法,更少的代码方法-只需附加到Controller

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>();
            };
        }
    }
}

编辑2:直接从控制器注册代码直接工作的另一种方法:

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

正是我所需要的。更新了开箱即用的答案
Scott Weinstein,

很棒的东西-根据您的方法,我添加了另一个简化,这次不需要模块。
Nicholas Blumhardt

是什么Resolve部分e.Context.Resolve?我应该提到我习惯Ninject ...
drzaus 2014年

243

最好的方法是使用ActionFilterAttribute并在全局变量中注册您的自定义类。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;
        }
    }

}

在全球范围内注册您的自定义课程。asax(Application_Start)

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

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

    }

然后您可以在所有视图中使用它

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

还有另一种方式

在HtmlHelper上创建扩展方法

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

然后您可以在所有视图中使用它

@Html.MyTest()

9
我不明白为什么还没有更多地赞成这一点。与其他方法相比,它的侵入性要小得多
joshcomley 2014年

5
8小时的研究找到了这个……完美的答案。非常感谢。
2014年

3
+1整合全球数据的好方法。我使用这种技术在所有页面上注册了我的站点版本。
Will Bickford 2014年

4
出色,简单且不干扰的解决方案。
Eugen Timm

3
但是IoC在哪里?即您将如何退出MembershipService
drzaus 2014年

39

根据定义,由于ViewBag属性与视图表示以及可能需要的任何浅视图逻辑相关联,因此我将创建一个基本的WebViewPage并在页面初始化时设置属性。它与用于重复逻辑和通用功能的基本控制器的概念非常相似,但对于您的观点是:

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

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

然后在中\Views\Web.config,设置pageBaseType属性:

<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>

此设置的问题在于,如果您要在一个视图上将值设置为ViewBag中的一个属性,然后尝试在另一个视图(如共享的_Layout视图)上访问它,则在第一个视图上设置的值将是在版式视图中丢失。
Pedro

@Pedro绝对是对的,但是我认为ViewBag并不是要成为应用程序中持久的状态源。听起来好像您希望这些数据处于会话状态,然后可以在基础视图页面中将其拉出并在ViewBag中设置(如果存在)。
布兰登·林顿2012年

您有一个有效的观点,但几乎每个人都在一个视图中使用其他视图中的数据集。例如,当您在一个视图中设置页面标题,然后在共享布局视图中将其打印到html文档的<title>标签中时。我什至更喜欢通过在“子”视图中设置“ ViewBag.DataTablesJs”之类的布尔值使“主”布局视图在html头上包含正确的JS引用,从而更进一步。只要与布局相关,我认为可以这样做。
2012年

@Pedro在标题标签情况下很好,通常是通过在每个视图中设置ViewBag.Title属性来处理的,然后共享布局中唯一的内容是<title>@ViewBag.Title</title>。由于每个视图都是不同的,因此它实际上不适用于基本应用程序视图页面,而基本视图页面将用于所有视图之间真正通用的数据。
布兰登·林顿2012年

@Pedro我明白你在说什么,我认为布兰登错过了重点。我正在使用自定义WebViewPage,并且尝试使用自定义WebViewPage中的自定义属性将一些数据从其中一个视图传递到布局视图。当我在视图中设置属性时,它将更新我的自定义WebViewPage中的ViewData,但是当它到达布局视图时,ViewData条目已经丢失。我通过在自定义WebViewPage中使用ViewContext.Controller.ViewData [“ SomeValue”]来解决此问题。希望对您有所帮助。
Imran Rashid 2014年

17

布兰登的职位是对的钱。实际上,我将更进一步,并说您应该只将常见对象添加为基本WebViewPage的属性,这样就不必在每个View中都从ViewBag投射项目。我以这种方式设置CurrentUser。


我无法'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?)
使它

+1,这正是我为共享一个非静态实用程序类的实例所做的工作,该实例需要在所有视图中全局可用。
尼克·科德

9

您可以使用自定义ActionResult:

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

甚至是ActionFilter:

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

        base.OnActionExecuting(filterContext);
    }
}

已经打开了MVC 2项目,但是这两种技术仍然适用,但都做了些微更改。


5

您不必弄乱动作或更改模型,只需使用基本控制器,然后从布局viewcontext投射现有的控制器即可。

使用所需的公共数据(标题/页面/位置等)创建基本控制器并进行动作初始化...

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

    protected override void OnActionExecuting(ActionExecutingContext filterContext) {

        MyCommonValue = 12345;

        base.OnActionExecuting(filterContext);
    }
}

确保每个控制器都使用基本控制器...

public class UserController:_BaseController {...

_Layout.cshml页面的视图上下文中投射现有的基本控制器。

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

现在,您可以从布局页面引用基本控制器中的值。

@myController.MyCommonValue

3

如果要对视图中的属性进行编译时检查和智能感知,则ViewBag并非可行之路。

考虑一个BaseViewModel类,并让您的其他视图模型从该类继承,例如:

基本ViewModel

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

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

查看特定的ViewModel

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

现在,视图代码可以直接在视图中访问属性

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

2

我发现以下方法是最有效的,并在必要时利用_ViewStart.chtml文件和条件语句提供了出色的控制:

_ 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>;
 }

注意事项

PageData将在Views中完美运行;但是,对于PartialView,则需要将其从View传递给子Partial。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.