コントローラーの基本クラスを使用せずに、すべてのビューのViewBagプロパティを設定する方法は?


96

以前は、すべてのコントローラーに共通の基本コントローラーを継承させることにより、現在のユーザーなどの共通のプロパティをグローバルな方法でViewData / ViewBagに固定していました。

これにより、ベースコントローラーでIoCを使用できるようになり、そのようなデータ用に共有されたグローバルに到達するだけではなくなりました。

この種類のコードをMVCパイプラインに挿入する別の方法があるかどうか疑問に思いますか?

回答:


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

これは私からの「唯一の道具はハンマー」タイプの提案の1つかもしれません。それを取得するためのより簡単な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>();
    });

まさに私が必要としたもの。すぐに使えるように回答を更新しました
Scott Weinstein

素晴らしいもの-あなたのアプローチに基づいて、今度はモジュールを必要とせずに、もう1つの簡略化を追加しました。
Nicholas Blumhardt、2011

Resolve一部はe.Context.Resolve何ですか?私はNinjectに慣れていることに言及する必要があります...
drzaus 14

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

5
これを見つけるための8時間の研究...完璧な答え。どうもありがとうございます。
deltree 2014年

3
+1グローバルデータを統合するすてきでクリーンな方法。この手法を使用して、すべてのページにサイトバージョンを登録しました。
ビックフォード

4
見事な、簡単で目立たないソリューション。
Eugen Timm

3
しかし、IoCはどこにありますか?つまり、どのように切り替えMembershipServiceますか?
drzaus 14

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.configpageBaseTypeプロパティを設定します。

<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は間違いなく真実ですが、ViewBagはアプリケーションの状態の永続的なソースになることを意図したものではないと主張します。セッション状態のデータが必要なように思えますが、ベースビューページでそのデータを引き出して、存在する場合はViewBagで設定できます。
ブランドンリントン'19年

あなたは有効なポイントを持っていますが、ほとんどの人が他のビューの1つのビューでデータセットを使用しています。1つのビューでページのタイトルを設定し、共有レイアウトビューでそれをHTMLドキュメントの<title>タグに出力する場合と同様です。「子」ビューで「ViewBag.DataTablesJs」などのブール値を設定して「マスター」レイアウトビューにHTMLの先頭に適切なJS参照を含めることで、これをさらに一歩進めたいと思っています。レイアウト関係であればこれでいいと思います。
ペドロ

@Pedroは、タイトルタグの状況でよく機能します。通常は、各ビューがViewBag.Titleプロパティを設定して処理され、共有レイアウトの唯一のものは<title>@ViewBag.Title</title>です。各ビューは個別であるため、ベースアプリケーションビューページのようなものにはあまり適していません。ベースビューページは、すべてのビューで本当に共通するデータ用です。
Brandon Linton

@ペドロ私はあなたが言っていることを理解しています、そして私はブランドンがそこでポイントを逃したと思います。カスタムWebViewPageを使用していて、カスタムWebViewPageのカスタムプロパティを使用して、ビューの1つからレイアウトビューにデータを渡そうとしました。ビューにプロパティを設定すると、カスタムWebViewPageのViewDataが更新されますが、レイアウトビューに到達すると、ViewDataエントリは既に失われています。カスタムWebViewPageでViewContext.Controller.ViewData ["SomeValue"]を使用して回避しました。誰かのお役に立てば幸いです。
Imran Rashid 14

17

ブランドンの投稿はまさにその通りです。実際のところ、私はさらに一歩これを取ると、あなただけとしてあなたの一般的なオブジェクトを追加する必要があることを言うのプロパティ基本WebViewPageあなたはすべての単一のビューで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?)
Sprintstar

これを+1して、これはすべてのビューでグローバルに利用できる必要がある非静的ユーティリティクラスのインスタンスを共有するために私がやっていることです。
Nick Coad

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

アクションをいじったりモデルを変更したりする必要はありません。基本コントローラーを使用して、レイアウトビューのコンテキストから既存のコントローラーをキャストするだけです。

必要な共通データ(タイトル/ページ/場所など)とアクションの初期化を使用して基本コントローラーを作成します...

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クラスを検討し、他のビューモデルにこのクラスを継承させます。例:

ベースビューモデル

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はビューで完全に機能します。ただし、PartialViewの場合は、ビューから子Partialに渡す必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.