MVC - Berbagi Informasi Kontekstual antar tampilan

8

Maafkan kiriman panjang. Ada pertanyaan, tetap saja bersamaku.

Konteks Kecil

Kami memiliki situs yang diharuskan untuk beradaptasi berdasarkan berbagai pengaturan pengguna, grup milik pengguna, dari mana mereka berasal dan hal-hal lain. Kami dulu memasukkan bit yang relevan pada model untuk halaman, jadi jika halaman memiliki tabel yang akan menunjukkan jika pengguna lebih dari usia tertentu, maka pada model kami akan melakukan sesuatu seperti:

//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>
}

Ini dengan cepat menjadi sangat rumit untuk mengetahui apa yang harus kami tunjukkan kepada pengguna mana. Untuk mencoba dan mengatasi masalah ini, kami memusatkan semua logika tentang kapan hal tertentu harus ditampilkan atau disembunyikan. Kami memanggil kelas ini UserConfigurationdan itu (kebanyakan) hanya berisi serangkaian fungsi mengembalikan boolean yang menunjukkan apa yang harus ditampilkan. Ini memungkinkan kami untuk menyiapkan serangkaian Spesifikasi dan Tes tentang apa yang harus ditampilkan oleh pengguna. Ini UserConfigratuionkemudian diletakkan di kelas dasar, yang mewarisi dari semua model halaman, jadi yang kita miliki saat ini adalah sesuatu seperti ini:

//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>
}

Ini telah membantu, sebagian besar karena memungkinkan kami untuk membuat serangkaian tes tentang apa yang harus dan tidak boleh dilihat oleh pengguna, dll. Namun, ini bukan solusi yang sangat bersih, harus mengumpulkan kelas tambahan ini dan memasukkannya pada model. Ini juga memiliki konsekuensi untuk menampilkan Tampilan Parsial. Jika model memiliki properti IEnumerable<Foo> Foosdi atasnya, yang ingin kami render secara parsial, tetapi sebagian itu juga bergantung pada konfigurasi pengguna, kami memiliki masalah. Anda tidak bisa hanya meneruskan foos ke Partial sebagai model, karena sebagian tidak memiliki akses ke UserConfiguration. Jadi apa yang akan menjadi cara terbaik untuk mengakses informasi ini. Cara saya melihatnya, dalam konteks asp.net MVC ada 4 cara yang tersedia:

1) Memiliki model baru untuk contoh parsial

// 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
    }
}

Ini mungkin solusi "paling murni", yang paling sesuai dengan prinsip-prinsip MVC, tetapi melibatkan banyak model (yang bisa dibilang tidak perlu), menyebabkan proyek menggembung.

2) Paparkan Konfigurasi Pengguna melalui ViewData. misalnya:

// 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
    }
}

Saya tidak terlalu suka ini karena ini bukan tipe aman dan bergantung pada string ajaib untuk mendapatkannya dari ViewData.

3) Masukkan UserConfiguration di ViewBag. Masalah yang sama seperti di atas kok

4) Ubah model halaman, dan paparkan Konfigurasi Pengguna melalui properti halaman itu sendiri, sesuai http://haacked.com/archive/2011/02/21/changing-base-type-of-a-razor-view .aspx /

Saya merasa karena Konfigurasi Pengguna adalah informasi kontekstual ambient, masuk akal untuk mengeksposnya melalui kelas seperti pada opsi 4 di atas. Apakah ada praktik terbaik yang diterima secara umum di MVC untuk mengekspos data semacam ini? Adakah yang pernah mencoba sesuatu seperti opsi 4 di masa lalu dan apakah ada 'gotcha'

tl; dr: Apa cara terbaik untuk mengungkapkan informasi kontekstual ke tampilan di situs Anda, di MVC secara umum atau MVC asp.net pada khususnya?

Anduril
sumber

Jawaban:

2

Anda harus pergi dengan # 5: Tidak ada yang di atas.

Saya sudah mulai membuat metode ekstensi untuk IPrincipalantarmuka, yang memberi saya deklarasi yang sangat diketik tentang apa yang dapat dilakukan pengguna saat ini. Anda bahkan bisa membuat UserConfigurationDTO yang Anda isi di Sesi untuk digunakan dengan metode ekstensi ini.

Pertama, metode ekstensi:

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

Sekarang ketika pengguna telah berhasil masuk, buat instance dari UserConfigurationdan simpan di Session:

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

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

Selanjutnya, tambahkan namespace di mana metode ekstensi ada untuk ruang nama default Anda di template Razor.

Aplikasi / Tampilan / Web.config Anda

<?xml version="1.0"?>

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

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

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

Sekarang tutup dan buka kembali solusi Visual Studio. Kemudian template Razor Anda memiliki metode baru yang tersedia:

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

Saya akan memilih opsi pertama Anda, membuat modifikasi yang diperlukan untuk kelas model Anda. Tampaknya opsi 4 Anda, memodifikasi model halaman, akan memperdagangkan referensi Model untuk referensi pembantu dalam tampilan pisau cukur Anda. Opsi 1 tampaknya lebih mudah dipertahankan daripada opsi 4 karena tampaknya membutuhkan lebih sedikit kode dan karena lebih banyak pengembang MVC akan memahaminya.

Gpersell
sumber
0

Pendapat saya adalah, data kontekstual dengan ViewBag / ViewData dikonfigurasi oleh layanan data kontekstual. Saya merasa kesal tanpa fleksibilitas atau sangat buruk memiliki "BaseController" yang mengatur semua hal dari "GodModel" karena setiap tampilan perlu memilikinya kecuali saya perlu menambahkan beberapa tampilan tipis baru di mana saya tidak memerlukan semua hal itu. Di mana tentu saja "GodModel" juga merupakan kelas dasar untuk model dalam pandangan.

Sulit untuk mengatakan di muka jika ada sesuatu yang benar-benar dibutuhkan oleh pandangan "semua" dan membuat banyak hal wajib menjadikannya jauh lebih sulit daripada membuat barang opsional dan ketika sesekali saya lupa mengaturnya karena dinamis .

Tentu saja semua tampilan hal-hal spesifik dan benar-benar wajib harus menjadi model dan sangat diketik dan memiliki validasi. Tetapi hal-hal umum yang dapat menghambat kinerja karena seseorang berpikir bahwa "semuanya harus sangat diketik" tidak baik.

Mateusz
sumber
0

kedengarannya seperti PALING dari informasi yang Anda butuhkan untuk memeriksa adalah sentris-pengguna dan tidak berbasis tindakan. mengapa tidak menyimpan UserConfiguration Anda di sesi? pendekatan lain, tergantung pada bagaimana Anda melakukan auth pengguna / manajemen, adalah untuk menyimpan semua informasi yang Anda butuhkan dalam Klaim Klaim (contoh di bawah) ...

    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; }
    }
jklDev
sumber