Dans le passé, je bloquais des propriétés communes, telles que l'utilisateur actuel, sur ViewData/ViewBag de manière globale en faisant en sorte que tous les contrôleurs héritent d'un contrôleur de base commun.
Cela m'a permis d'utiliser IoC sur le contrôleur de base et de ne pas me limiter au partage global de ces données.
Je me demande s'il existe une autre façon d'insérer ce type de code dans le pipeline MVC?
Non essayé par moi, mais vous pourriez regarder enregistrer vos vues puis définir les données de vue pendant le processus d'activation.
Comme les vues sont enregistrées à la volée, la syntaxe d'enregistrement ne vous aide pas à vous connecter à l'événement Activated
. Vous devez donc le configurer dans un fichier 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";
};
}
}
}
C’est peut-être l’une de ces suggestions du type "seul outil, c’est un marteau"; il existe peut-être des moyens plus simples d'activer MVC.
Edit: Autre méthode, moins de code - attachez-la simplement au contrôleur
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: Une autre approche qui fonctionne directement à partir du code d'enregistrement du contrôleur:
builder.RegisterControllers(asm)
.OnActivated(e => {
dynamic viewBag = ((Controller)e.Instance).ViewBag;
viewBag.Config = e.Context.Resolve<Config>();
viewBag.Identity = e.Context.Resolve<IIdentity>();
});
Le meilleur moyen consiste à utiliser ActionFilterAttribute et à enregistrer votre classe personnalisée dans votre global. 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;
}
}
}
inscrivez votre classe personnalisée dans votre global. asax (Application_Start)
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalFilters.Filters.Add(new UserProfilePictureActionFilter(), 0);
}
Ensuite, vous pouvez l'utiliser dans toutes les vues
@ViewBag.IsAdmin
@ViewBag.IsAuthenticated
@ViewBag.Avatar
Il y a aussi une autre façon
Créer une méthode d'extension sur HtmlHelper
[Extension()]
public string MyTest(System.Web.Mvc.HtmlHelper htmlHelper)
{
return "This is a test";
}
Ensuite, vous pouvez l'utiliser dans toutes les vues
@Html.MyTest()
Puisque les propriétés ViewBag sont, par définition, liées à la présentation de la vue et à toute logique de vue claire qui pourrait être nécessaire, je créerais une WebViewPage de base et définirais les propriétés lors de l'initialisation de la page. C'est très similaire au concept d'un contrôleur de base pour la logique répétée et les fonctionnalités communes, mais pour vos vues:
public abstract class ApplicationViewPage<T> : WebViewPage<T>
{
protected override void InitializePage()
{
SetViewBagDefaultProperties();
base.InitializePage();
}
private void SetViewBagDefaultProperties()
{
ViewBag.GlobalProperty = "MyValue";
}
}
Et puis dans \Views\Web.config
, définissez la propriété 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>
Le message de Brandon est juste sur l'argent. En fait, je voudrais aller un peu plus loin et dire que vous devriez simplement ajouter vos objets communs comme propriétés du base WebViewPage vous n'avez donc pas à lancer d'éléments de ViewBag dans chaque vue. Je fais ma configuration CurrentUser de cette façon.
Vous pouvez utiliser un ActionResult personnalisé:
public class GlobalView : ActionResult
{
public override void ExecuteResult(ControllerContext context)
{
context.Controller.ViewData["Global"] = "global";
}
}
Ou même un ActionFilter:
public class GlobalView : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Result = new ViewResult() {ViewData = new ViewDataDictionary()};
base.OnActionExecuting(filterContext);
}
}
Un projet MVC 2 était-il ouvert, mais les deux techniques s'appliquent toujours avec des modifications mineures.
Vous n'avez pas à jouer avec les actions ni à changer le modèle, utilisez simplement un contrôleur de base et convertissez le contrôleur existant à partir du contexte de la vue de présentation.
Créez un contrôleur de base avec les données communes souhaitées (titre/page/emplacement, etc.) et l'initialisation de l'action ...
public abstract class _BaseController:Controller {
public Int32 MyCommonValue { get; private set; }
protected override void OnActionExecuting(ActionExecutingContext filterContext) {
MyCommonValue = 12345;
base.OnActionExecuting(filterContext);
}
}
Assurez-vous que chaque contrôleur utilise le contrôleur de base ...
public class UserController:_BaseController {...
Transformez le contrôleur de base existant à partir du contexte de la vue dans votre _Layout.cshml
page ...
@{
var myController = (_BaseController)ViewContext.Controller;
}
Vous pouvez maintenant faire référence aux valeurs de votre contrôleur de base à partir de votre page de présentation.
@myController.MyCommonValue
Si vous voulez vérifier le temps de compilation et intellisense pour les propriétés de vos vues, le ViewBag n'est pas la solution.
Considérez une classe BaseViewModel et laissez vos autres modèles de vue hériter de cette classe, par exemple:
Base ViewModel
public class BaseViewModel
{
public bool IsAdmin { get; set; }
public BaseViewModel(IUserService userService)
{
IsAdmin = userService.IsAdmin;
}
}
Voir ViewModel spécifique
public class WidgetViewModel : BaseViewModel
{
public string WidgetName { get; set;}
}
Maintenant, le code de vue peut accéder à la propriété directement dans la vue
<p>Is Admin: @Model.IsAdmin</p>
J'ai trouvé l'approche suivante la plus efficace et offre un excellent contrôle en utilisant le fichier _ViewStart.chtml et les instructions conditionnelles lorsque cela est nécessaire:
_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>;
}
Note:
PageData fonctionnera parfaitement dans Views; Toutefois, dans le cas d'une vue partielle, il faudra passer de la vue à la vue partielle enfant.