MVC-ビュー間でコンテキスト情報を共有する


8

長い投稿は申し訳ありません。質問があります。ただ我慢してください。

少しのコンテキスト

私たちは、さまざまなユーザー設定、ユーザーが所属するグループ、ユーザーの出身地などに基づいて大幅に適応する必要があるサイトを持っています。以前はページのモデルに関連ビットを含めていたため、ページに、ユーザーが特定の年齢を超えているかどうかを示すテーブルがある場合、モデルでは次のようにします。

//model
public PageModel
{
    public bool ShowTable {get;set;}
}

//controller
public PageController
{
    public ActionResult ShowPage()
    {
        var model = new PageModel() {
            ShowTable = User.Age > 21
        };
        return View(model);
    }
}

//view
@if(Model.ShowTable)
{ 
    <table>Some Html here</table>
}

これは、どのユーザーに何を表示すべきかを知るためにすぐに非常に複雑になりました。この問題に対処するために、特定のものが表示または非表示になるタイミングに関するすべてのロジックを一元化しました。このクラスを呼び出したUserConfigurationところ、(ほとんどの場合)何を表示すべきかを示すブール値を返す一連の関数が含まれていました。これにより、ユーザーに表示する必要がある一連の仕様とテストを設定できました。UserConfigratuion次に、これは、すべてのページモデルが継承する必要がある基本クラスに配置されました。そのため、現在、次のようになっています。

//UserConfiguration 
public UserConfiguration
{
    private readonly User _user;

    public UserConfiguration(User user) {
        _user = user
    }

    public bool ShowTable() {
        return _user.Age > 21;
    }
}

//model base
public ModelBase
{
    public UserConfiguration {get;set;}
}

//model
public PageModel : ModelBase
{
    // whatever data is needed for the page
}

//controller
public PageController
{
    public ActionResult ShowPage()
    {
        var userConfiguration = new UserConfiguration(User);
        var model = new PageModel {
            UserConfiguration = userConfiguration
        };
        return View(model);
    }
}

//view
@if(Model.UserConfiguration.ShowTable())
{ 
    <table>Some Html here</table>
}

これは主に、ユーザーが表示すべきものと表示すべきでないものの一連のテストを作成できるようになったために役立ちました。しかし、この追加のクラスをまとめてモデルに含める必要があるため、あまりクリーンなソリューションではありません。また、部分ビューのレンダリングにも影響があります。モデルにプロパティIEnumerable<Foo> Foosがあり、パーシャルでレンダリングしたいが、そのパーシャルユーザー設定に依存している場合、問題があります。パーシャルはにアクセスできないため、モデルとしてパーシャルにfoosを渡すことはできませんUserConfiguration。したがって、この情報にアクセスするための最良の方法は何でしょうか。私の見たところ、asp.net MVCのコンテキストでは、4つの方法が利用できます。

1)パーシャルなどの新しいモデルを用意する

// parent view
@{
    var foosModel = new foosModel {
        Foos = Model.Foos,
        UserConfiguration = Model.UserConfiguration
    }
}

@Html.RenderPartial("FooList", foosModel)

// child partial view
@if(Model.UserConfiguration.ShowTable) {
    foreach(var foo in Model.Foos) {
        //Some HTML
    }
}

これはおそらく「最も純粋な」ソリューションであり、MVCの原則に最も忠実に従いますが、多くの(おそらく不必要な)モデルを含み、プロジェクトの肥大化を引き起こします。

2)ViewDataを介してUserConfigurationを公開します。例:

// parent view
@Html.RenderPartial("FooList", Model.Foos, new ViewDataDictionary { { "UserConfiguration", Model.UserConfiguration } })

// child partial view
@{ 
    var userConfig = (UserConfiguration)ViewData["UserConfiguration"];
}
@if(userConfig.ShowTable) {
    foreach(var foo in Model) {
        //Some HTML
    }
}

これはタイプセーフではなく、ViewDataから取得するために魔法の文字列に依存しているので、私は本当にこれが好きではありません。

3)UserConfigurationをViewBagに配置します。上記と同じ問題は本当に

4)ページモデルを変更し、http: //haacked.com/archive/2011/02/21/changing-base-type-of-a-razor-viewのように、ページ自体のプロパティを介してUserConfigurationを公開します。.aspx /

UserConfigurationはアンビエントコンテキスト情報であるため、上記のオプション4のようにクラスを介して公開することは理にかなっています。この種のデータを公開するためにMVCで一般に受け入れられているベストプラクティスはありますか?誰かが過去にオプション4のようなことを試みたことがありますか?

tl; dr:一般的なMVCまたは特にasp.net MVCで、コンテキスト情報をサイトのビューに公開する最良の方法は何ですか?

回答:


2

あなたは#5で行くべきです:上記のどれも。

IPrincipalインターフェースの拡張メソッドの作成を開始しました。これにより、現在のユーザーが実行できることの厳密に型指定された宣言ができます。UserConfigurationこれらの拡張メソッドで使用するためにセッションに入れるDTOを作成することもできます。

まず、拡張メソッド:

namespace YourApplication.Helpers
{
    public static class UserConfigurationExtensions
    {
        private HttpContext CurrentContext
        {
            get
            {
                return System.Web.HttpContext.Current;
            }
        }

        private static UserConfiguration Config
        {
            get
            {
                if (CurrentContext == null)
                    return null;

                return CurrentContext.Session["UserConfiguration"] as UserConfiguration;
            }
        }

        public static bool CanViewTable(this IPrincipal user)
        {
            return Config.ShowTable;
        }
    }
}

ユーザーが正常にログインしUserConfigurationたら、のインスタンスを作成し、次の場所に隠しておきますSession

public class AccountController : Controller
{
    [HttpPost]
    public ActionResult Login(LoginFormModel model)
    {
        if (ModelState.IsValid)
        {
            // Log in
            Session["UserConfiguration"] = new UserConfiguration(...);
        }

        return RedirectToAction("Index", "Home");
    }
}

次に、拡張メソッドが存在する名前空間をRazorテンプレートのデフォルトの名前空間に追加します。

YourApplication / Views / Web.config

<?xml version="1.0"?>

<configuration>
  <!-- ... -->

  <system.web.webPages.razor>
    <namespaces>
      <add namespace="YourApplication.Helpers"/>
    </namespaces>
  </system.web.webPages.razor>

  <!-- ... -->
</configuration>

次に、Visual Studioソリューションを閉じて再度開きます。次に、Razorテンプレートに新しいメソッドが利用可能になります。

@if (User.CanViewTable())
{
    foreach(var foo in Model)
    {
        //Some HTML
    }
}

0

私はあなたの最初のオプションで、モデルクラスに必要な変更を加えます。オプション4、ページモデルの変更は、かみそりビューでヘルパー参照とモデル参照を交換することになるようです。オプション1はオプション4に比べて保守が簡単なようです。これは、必要なコードが少なくなり、より多くのMVC開発者が理解できるためです。


0

私の意見では、コンテキストデータサービスによってViewBag / ViewDataが構成されたコンテキストデータです。すべてのものが必要ない新しい薄いビューを追加する必要がない限り、すべてのビューがそれを必要とするため、「BaseController」が「GodModel」からすべてのものを設定する柔軟性がないか、非常に悪いことに悩みました。もちろん、「GodModel」はビュー内のモデルの基本クラスでもありました。

「すべての」ビューで本当に必要なものがあるかどうかを前もって判断するのは難しく、多くのものを必須にすると、オプションにするよりもはるかに難しくなります。動的なため、たまに設定を忘れてしまいます。 。

もちろん、すべてのビュー固有で本当に必須なものはモデル化し、強く型付けして検証する必要があります。しかし、「すべてを強く型付けする必要があると誰かが思ったためにパフォーマンスを低下させる可能性のある一般的なものは良くありません。


0

調査する必要がある情報のほとんどはユーザー中心であり、アクションベースではないようです。セッションにUserConfigurationを保存しないのはなぜですか?ユーザーの認証/管理の方法に応じた別のアプローチは、ClaimsPrincipal(以下のサンプル)に必​​要なすべての情報を保持することです...

    private ClaimsPrincipal CurrentClaimsPrincipal
    {
        get { return System.Security.Claims.ClaimsPrincipal.Current; }
    }

    public string Firstname
    {
        get { return (null != CurrentClaimsPrincipal) ? CurrentClaimsPrincipal.FindFirst(Helpers.Constants.GIVEN_NAME_KEY)?.Value : string.Empty; }
    }

    public string Lastname
    {
        get { return (null != CurrentClaimsPrincipal) ? CurrentClaimsPrincipal.FindFirst(Helpers.Constants.FAMILY_NAME_KEY)?.Value : string.Empty; }
    }

    public string AccessLevel
    {
        get { return (null != CurrentClaimsPrincipal) ? CurrentClaimsPrincipal.FindFirst(Helpers.Constants.ACCESS_LEVEL_KEY)?.Value : string.Empty; }
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.