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

时间:2011-03-27 23:51:00

标签: asp.net-mvc asp.net-mvc-3 autofac viewdata

过去,我通过让所有控制器继承公共基本控制器,以全局方式将常见属性(例如当前用户)粘贴到ViewData / ViewBag上。

这使我可以在基本控制器上使用IoC,而不仅仅是为这些数据扩展到全局共享。

我想知道是否有另一种方法可以将这种代码插入到MVC管道中?

8 个答案:

答案 0 :(得分:234)

最好的方法是使用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()

答案 1 :(得分: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>

答案 2 :(得分:22)

我没有尝试过,但您可以查看registering your views,然后在激活过程中设置视图数据。

由于视图是即时注册的,因此注册语法无法帮助您连接Activated事件,因此您需要将其设置为Module:< / p>

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启用方式。

编辑:备用,少代码方法 - 只需连接到控制器

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

答案 3 :(得分:17)

布兰登的帖子是正确的。事实上,我会更进一步说,您应该将您的公共对象添加为基础WebViewPage 属性,这样您就不必在每个视图中从ViewBag中转换项目。我这样做我的CurrentUser设置。

答案 4 :(得分:8)

您可以使用自定义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 :(得分:3)

如果您希望对视图中的属性进行编译时检查和智能感知,那么ViewBag就不是一种方法。

考虑一个BaseViewModel类,让你的其他视图模型继承自这个类,例如:

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

答案 6 :(得分:3)

您不必乱用操作或更改模型,只需使用基本控制器并从布局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

答案 7 :(得分: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中完美运行;但是,在a的情况下   PartialView,需要从View传递给   孩子偏爱。