How to set ViewBag properties for all Views without using a base class for Controllers?
Solution 1:
The best way is using the ActionFilterAttribute. I'll show you how to use it in .Net Core and .Net Framework.
.Net Core 2.1 & 3.1
public class ViewBagActionFilter : ActionFilterAttribute
{
public ViewBagActionFilter(IOptions<Settings> settings){
//DI will inject what you need here
}
public override void OnResultExecuting(ResultExecutingContext context)
{
// for razor pages
if (context.Controller is PageModel)
{
var controller = context.Controller as PageModel;
controller.ViewData.Add("Avatar", $"~/avatar/empty.png");
// or
controller.ViewBag.Avatar = $"~/avatar/empty.png";
//also you have access to the httpcontext & route in controller.HttpContext & controller.RouteData
}
// for Razor Views
if (context.Controller is Controller)
{
var controller = context.Controller as Controller;
controller.ViewData.Add("Avatar", $"~/avatar/empty.png");
// or
controller.ViewBag.Avatar = $"~/avatar/empty.png";
//also you have access to the httpcontext & route in controller.HttpContext & controller.RouteData
}
base.OnResultExecuting(context);
}
}
Then you need to register this in your startup.cs.
.Net Core 3.1
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews(options => {
options.Filters.Add<Components.ViewBagActionFilter>();
});
}
.Net Core 2.1
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.Filters.Add<Configs.ViewBagActionFilter>();
});
}
Then you can use it in all views and pages
@ViewData["Avatar"]
@ViewBag.Avatar
.Net Framework (ASP.NET MVC .Net Framework)
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;
}
}
}
register your custom class in the global. asax (Application_Start)
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalFilters.Filters.Add(new UserProfilePictureActionFilter(), 0);
}
Then you can use it in all views
@ViewBag.IsAdmin
@ViewBag.IsAuthenticated
@ViewBag.Avatar
Also there is another way
Creating an extension method on HtmlHelper
[Extension()]
public string MyTest(System.Web.Mvc.HtmlHelper htmlHelper)
{
return "This is a test";
}
Then you can use it in all views
@Html.MyTest()
Solution 2:
Since ViewBag properties are, by definition, tied to the view presentation and any light view logic that may be necessary, I'd create a base WebViewPage and set the properties on page initialization. It's very similar to the concept of a base controller for repeated logic and common functionality, but for your views:
public abstract class ApplicationViewPage<T> : WebViewPage<T>
{
protected override void InitializePage()
{
SetViewBagDefaultProperties();
base.InitializePage();
}
private void SetViewBagDefaultProperties()
{
ViewBag.GlobalProperty = "MyValue";
}
}
And then in \Views\Web.config
, set the pageBaseType
property:
<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>
Solution 3:
Un-tried by me, but you might look at registering your views and then setting the view data during the activation process.
Because views are registered on-the-fly, the registration syntax doesn't help you with connecting to the Activated
event, so you'd need to set it up in a 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";
};
}
}
}
This might be one of those "only tool's a hammer"-type suggestions from me; there may be simpler MVC-enabled ways to get at it.
Edit: Alternate, less code approach - just attach to the 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>();
};
}
}
}
Edit 2: Another approach that works directly from the controller registration code:
builder.RegisterControllers(asm)
.OnActivated(e => {
dynamic viewBag = ((Controller)e.Instance).ViewBag;
viewBag.Config = e.Context.Resolve<Config>();
viewBag.Identity = e.Context.Resolve<IIdentity>();
});