Bagaimana cara menyetel properti ViewBag untuk semua Tampilan tanpa menggunakan kelas dasar untuk Pengontrol?

96

Di masa lalu saya telah menempelkan properti umum, seperti pengguna saat ini, ke ViewData / ViewBag secara global dengan membuat semua Pengontrol mewarisi dari pengontrol basis umum.

Ini memungkinkan saya untuk menggunakan IoC pada pengontrol dasar dan tidak hanya menjangkau global yang dibagikan untuk data semacam itu.

Saya ingin tahu apakah ada cara alternatif untuk memasukkan kode semacam ini ke dalam pipa MVC?

Scott Weinstein
sumber

Jawaban:

22

Belum dicoba oleh saya, tetapi Anda mungkin melihat mendaftarkan tampilan Anda dan kemudian menyetel data tampilan selama proses aktivasi.

Karena tampilan didaftarkan saat itu juga, sintaks pendaftaran tidak membantu Anda menghubungkan ke Activatedacara, jadi Anda perlu menyiapkannya di 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";
            };
        }
    }
}

Ini mungkin salah satu dari saran-jenis "satu-satunya alat adalah palu" dari saya; mungkin ada cara sederhana berkemampuan MVC untuk mendapatkannya.

Sunting: Alternatif, lebih sedikit pendekatan kode - cukup pasang ke 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: Pendekatan lain yang bekerja langsung dari kode registrasi pengontrol:

builder.RegisterControllers(asm)
    .OnActivated(e => {
        dynamic viewBag = ((Controller)e.Instance).ViewBag;
        viewBag.Config = e.Context.Resolve<Config>();
        viewBag.Identity = e.Context.Resolve<IIdentity>();
    });
Nicholas Blumhardt
sumber
Persis yang saya butuhkan. Memperbarui jawaban untuk bekerja di luar kotak
Scott Weinstein
Hal-hal hebat - berdasarkan pendekatan Anda, saya telah menambahkan penyederhanaan lain, kali ini tanpa memerlukan modul.
Nicholas Blumhardt
apa Resolvebagian dari e.Context.Resolve? Saya harus menyebutkan bahwa saya sudah terbiasa dengan Ninject ...
drzaus
244

Cara terbaik adalah menggunakan ActionFilterAttribute. Saya akan menunjukkan cara menggunakannya di .Net Core dan .Net Framework.

.Net Core 2.1 & 3.1

public class ViewBagActionFilter : ActionFilterAttribute
{
    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);
    }
}

Kemudian Anda perlu mendaftarkannya di startup.cs Anda.

.Net Core 3.1

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(options => { options.Filters.Add(new Components.ViewBagActionFilter()); });
}

.Net Core 2.1

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
        {
            options.Filters.Add(new Configs.ViewBagActionFilter());
        });
}

Kemudian Anda dapat menggunakannya di semua tampilan dan halaman

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

}

daftarkan kelas kustom Anda di global. asax (Application_Start)

protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        GlobalFilters.Filters.Add(new UserProfilePictureActionFilter(), 0);

    }

Kemudian Anda dapat menggunakannya di semua tampilan

@ViewBag.IsAdmin
@ViewBag.IsAuthenticated
@ViewBag.Avatar

Juga ada cara lain

Membuat metode ekstensi di HtmlHelper

[Extension()]
public string MyTest(System.Web.Mvc.HtmlHelper htmlHelper)
{
    return "This is a test";
}

Kemudian Anda dapat menggunakannya di semua tampilan

@Html.MyTest()
Mohammad Karimi
sumber
9
Saya tidak mengerti mengapa ini belum banyak disukai; ini adalah pendekatan yang jauh lebih tidak invasif daripada yang lain
joshcomley
5
8 jam penelitian untuk menemukan ini ... jawaban yang sempurna. Terima kasih banyak.
Deltree
3
+1 Cara yang bagus dan bersih untuk mengintegrasikan data global. Saya menggunakan teknik ini untuk mendaftarkan versi situs saya di semua halaman.
Will Bickford
4
Solusi brilian, mudah, dan tidak mengganggu.
Eugen Timm
3
Tapi di mana IoC-nya? yaitu bagaimana Anda akan beralih MembershipService?
drzaus
39

Karena properti ViewBag, menurut definisi, terkait dengan presentasi tampilan dan logika tampilan ringan apa pun yang mungkin diperlukan, saya akan membuat WebViewPage dasar dan mengatur properti pada inisialisasi halaman. Ini sangat mirip dengan konsep pengontrol dasar untuk logika berulang dan fungsionalitas umum, tetapi untuk tampilan Anda:

    public abstract class ApplicationViewPage<T> : WebViewPage<T>
    {
        protected override void InitializePage()
        {
            SetViewBagDefaultProperties();
            base.InitializePage();
        }

        private void SetViewBagDefaultProperties()
        {
            ViewBag.GlobalProperty = "MyValue";
        }
    }

Dan kemudian \Views\Web.config, setel pageBaseTypeproperti:

<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>
Brandon Linton
sumber
Masalah dengan penyiapan ini adalah jika Anda menyetel nilai ke properti di ViewBag pada satu tampilan dan kemudian mencoba mengaksesnya di tampilan lain (seperti tampilan _Layout bersama Anda), nilai nilai yang ditetapkan pada tampilan pertama akan menjadi hilang pada tampilan tata letak.
Pedro
@Pedro itu benar, tapi kemudian saya berpendapat bahwa ViewBag tidak dimaksudkan untuk menjadi sumber status persisten dalam aplikasi. Kedengarannya seperti Anda ingin data itu dalam keadaan sesi dan kemudian Anda bisa menariknya keluar di halaman tampilan dasar Anda dan mengaturnya di ViewBag jika ada.
Brandon Linton
Anda memiliki poin yang valid tetapi hampir semua orang menggunakan kumpulan data dalam satu tampilan di tampilan lain; seperti saat Anda menyetel judul halaman dalam satu tampilan dan tampilan tata letak bersama, lalu mencetaknya di tag <title> pada dokumen html. Saya bahkan ingin mengambil langkah lebih jauh dengan menyetel boolean seperti "ViewBag.DataTablesJs" dalam tampilan "anak" untuk membuat tampilan tata letak "master" menyertakan referensi JS yang tepat di kepala html. Selama itu terkait dengan tata letak, saya pikir tidak apa-apa untuk melakukan ini.
Pedro
@Pedro baik dalam situasi tag judul, biasanya itu ditangani dengan setiap tampilan yang mengatur ViewBag.Titleproperti dan kemudian satu-satunya hal dalam tata letak bersama adalah <title>@ViewBag.Title</title>. Ini tidak akan benar-benar sesuai untuk sesuatu seperti halaman tampilan aplikasi dasar karena setiap tampilan berbeda, dan halaman tampilan dasar akan untuk data yang benar-benar umum di semua tampilan.
Brandon Linton
@Pedro Saya mengerti apa yang Anda katakan dan saya pikir Brandon melewatkan intinya di sana. Saya menggunakan WebViewPage kustom dan mencoba meneruskan beberapa data dari salah satu tampilan ke tampilan tata letak menggunakan properti kustom di WebViewPage kustom. Ketika saya menyetel properti dalam tampilan, itu akan memperbarui ViewData di WebViewPage kustom saya, tetapi ketika sampai ke tampilan tata letak, entri ViewData sudah hilang. Saya mengatasinya dengan menggunakan ViewContext.Controller.ViewData ["SomeValue"] di WebViewPage kustom. Saya harap ini membantu seseorang.
Imran Rashid
17

Pos Brandon tepat tentang uang. Sebagai soal fakta, saya akan mengambil langkah lebih lanjut dan mengatakan bahwa Anda hanya harus menambahkan benda-benda umum Anda sebagai sifat dari dasar WebViewPage sehingga Anda tidak perlu item cor dari ViewBag di setiap tunggal View. Saya melakukan penyiapan CurrentUser dengan cara ini.

Michael Gagne
sumber
Saya tidak bisa mendapatkan ini untuk bekerja dengan kesalahan'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 tentang ini, inilah yang saya lakukan untuk berbagi instance kelas utilitas non-statis yang perlu tersedia secara global di semua tampilan.
Nick Coad
9

Anda dapat menggunakan ActionResult kustom:

public class  GlobalView : ActionResult 
{
    public override void ExecuteResult(ControllerContext context)
    {
        context.Controller.ViewData["Global"] = "global";
    }
}

Atau bahkan ActionFilter:

public class  GlobalView : ActionFilterAttribute 
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        filterContext.Result = new ViewResult() {ViewData = new ViewDataDictionary()};

        base.OnActionExecuting(filterContext);
    }
}

Memiliki proyek MVC 2 yang terbuka tetapi kedua teknik tersebut masih berlaku dengan perubahan kecil.

John Farrell
sumber
5

Anda tidak perlu mengotak-atik tindakan atau mengubah model, cukup gunakan pengontrol dasar dan transmisikan pengontrol yang ada dari tata letak konteks tampilan.

Buat pengontrol dasar dengan data umum yang diinginkan (judul / halaman / lokasi dll) dan inisialisasi tindakan ...

public abstract class _BaseController:Controller {
    public Int32 MyCommonValue { get; private set; }

    protected override void OnActionExecuting(ActionExecutingContext filterContext) {

        MyCommonValue = 12345;

        base.OnActionExecuting(filterContext);
    }
}

Pastikan setiap pengontrol menggunakan pengontrol dasar ...

public class UserController:_BaseController {...

Transmisikan pengontrol dasar yang ada dari konteks tampilan di _Layout.cshmlhalaman Anda ...

@{
    var myController = (_BaseController)ViewContext.Controller;
}

Sekarang Anda dapat merujuk ke nilai-nilai di pengontrol dasar dari halaman tata letak Anda.

@myController.MyCommonValue
Carter Medlin
sumber
3

Jika Anda ingin mengkompilasi pemeriksaan waktu dan kecerdasan untuk properti dalam tampilan Anda, maka ViewBag bukanlah cara yang tepat.

Pertimbangkan kelas BaseViewModel dan buat model tampilan Anda yang lain mewarisi dari kelas ini, misalnya:

Base ViewModel

public class BaseViewModel
{
    public bool IsAdmin { get; set; }

    public BaseViewModel(IUserService userService)
    {
        IsAdmin = userService.IsAdmin;
    }
}

Lihat ViewModel tertentu

public class WidgetViewModel : BaseViewModel
{
    public string WidgetName { get; set;}
}

Sekarang kode tampilan dapat mengakses properti secara langsung di tampilan

<p>Is Admin: @Model.IsAdmin</p>
Steven Cepat
sumber
2

Saya telah menemukan pendekatan berikut untuk menjadi yang paling efisien dan memberikan kontrol yang sangat baik menggunakan file _ViewStart.chtml dan pernyataan bersyarat bila diperlukan:

_ 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();
    }
}

TampilanA :

@{
   var UserProfile= PageData["Profile"] as List<string>;
 }

Catatan :

PageData akan bekerja dengan sempurna di Views; namun, dalam kasus PartialView, ini harus diteruskan dari View ke anak Partial.

Bermanfaat
sumber