Denetleyiciler için temel bir sınıf kullanmadan tüm Görünümler için ViewBag özellikleri nasıl ayarlanır?


96

Geçmişte, mevcut kullanıcı gibi ortak özellikleri, tüm Denetleyicilerin ortak bir temel denetleyiciden devralmasını sağlayarak genel bir şekilde ViewData / ViewBag'e yapıştırdım.

Bu, temel denetleyicide IoC kullanmamı sağladı ve bu tür veriler için yalnızca küresel paylaşıma ulaşmamı sağladı.

Bu tür bir kodu MVC ardışık düzenine eklemenin alternatif bir yolu olup olmadığını merak ediyorum.

Yanıtlar:


22

Benim tarafımdan denenmedi, ancak görünümlerinizi kaydetmeye ve ardından aktivasyon işlemi sırasında görünüm verilerini ayarlamaya bakabilirsiniz .

Görünümler anında kaydedildiğinden, kayıt sözdizimi Activatedetkinliğe bağlanmanıza yardımcı olmaz , bu nedenle aşağıdakileri ayarlamanız gerekir 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";
            };
        }
    }
}

Bu, benden gelen "tek alet bir çekiç" tipi önerilerden biri olabilir; ona ulaşmanın daha basit MVC etkin yolları olabilir.

Düzenleme: Alternatif, daha az kod yaklaşımı - sadece Denetleyiciye ekleyin

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

Düzenleme 2: Doğrudan denetleyici kayıt kodundan çalışan başka bir yaklaşım:

builder.RegisterControllers(asm)
    .OnActivated(e => {
        dynamic viewBag = ((Controller)e.Instance).ViewBag;
        viewBag.Config = e.Context.Resolve<Config>();
        viewBag.Identity = e.Context.Resolve<IIdentity>();
    });

Tam olarak ihtiyacım olan şey. Yanıt kutudan çıktığı gibi güncellendi
Scott Weinstein

Harika şeyler - yaklaşımınıza göre, bu sefer bir modüle ihtiyaç duymadan başka bir basitleştirme ekledim.
Nicholas Blumhardt

ne Resolvebir parçası e.Context.Resolve? Ninject'e
alıştığımı söylemeliyim

244

En iyi yol ActionFilterAttribute kullanmaktır. Net Core ve .Net Framework'te nasıl kullanılacağını göstereceğim.

.Net Core 2.1 ve 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);
    }
}

Sonra bunu startup.cs dosyanıza kaydetmeniz gerekir.

.Net Çekirdek 3.1

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

.Net Çekirdek 2.1

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

Daha sonra tüm görünümlerde ve sayfalarda kullanabilirsiniz

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

}

özel sınıfınızı global olarak kaydedin. asax (Application_Start)

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

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

    }

Sonra tüm görünümlerde kullanabilirsiniz

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

Ayrıca başka bir yol var

HtmlHelper'da bir uzantı yöntemi oluşturma

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

Sonra tüm görünümlerde kullanabilirsiniz

@Html.MyTest()

9
Neden buna daha fazla oy verilmediğini anlamıyorum; bu diğerlerinden çok daha az invaziv bir yaklaşım
joshcomley

5
Bunu bulmak için 8 saatlik araştırma ... mükemmel cevabı. Çok teşekkür ederim.
deltree

3
+1 Küresel verileri entegre etmenin güzel ve temiz yolu. Bu tekniği site versiyonumu tüm sayfalarda kaydetmek için kullandım.
Will Bickford

4
Parlak, kolay ve göze çarpmayan çözüm.
Eugen Timm

3
Ama IoC nerede? yani nasıl geçiş yapardınız MembershipService?
drzaus

39

ViewBag özellikleri, tanım gereği, görünüm sunumuna ve gerekli olabilecek herhangi bir hafif görünüm mantığına bağlı olduğundan, temel bir WebViewPage oluşturur ve sayfa başlatmada özellikleri ayarlardım . Tekrarlanan mantık ve ortak işlevsellik için temel denetleyici konseptine çok benzer, ancak görüşleriniz için:

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

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

Ve sonra \Views\Web.config, pageBaseTypeözelliği ayarlayın :

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

Bu kurulumla ilgili sorun, değeri bir görünümde ViewBag'deki bir mülke ayarlıyorsanız ve daha sonra başka bir görünümde buna erişmeye çalışıyorsanız (paylaşılan _Layout görünümünüz gibi), ilk görünümde ayarlanan değer değerinin düzen görünümünde kayboldu.
Pedro

@Pedro bu kesinlikle doğru, ancak o zaman ViewBag'in uygulamada kalıcı bir durum kaynağı olmadığını iddia ediyorum. Bu verileri oturum durumunda isteyeceğinize benziyor ve ardından bunu temel görünüm sayfanızda çekip, varsa ViewBag'de ayarlayabilirsiniz.
Brandon Linton

Geçerli bir noktaya sahipsiniz ancak hemen hemen herkes veri kümesini diğer görünümlerde bir görünümde kullanır; Örneğin, sayfanın başlığını bir görünümde ayarladığınızda ve paylaşılan düzen görünümünüz, bunu html belgesinin <title> etiketlerinde yazdırır. Hatta "ana" düzen görünümünün html başlığındaki uygun JS referanslarını içermesini sağlamak için "alt" görünümde "ViewBag.DataTablesJs" gibi boole'ları ayarlayarak bunu bir adım daha ileri götürmeyi seviyorum. Düzen ile ilgili olduğu sürece, bunu yapmanın sorun olmadığını düşünüyorum.
Pedro

@Pedro, başlık etiketleri durumunda iyi, genellikle her görünümün bir ViewBag.Titleözelliği ayarlamasıyla işlenir ve sonra paylaşılan düzendeki tek şey <title>@ViewBag.Title</title>. Her görünüm farklı olduğundan ve temel görünüm sayfası, tüm görünümlerde gerçekten ortak olan veriler için geçerli olduğundan, temel uygulama görünümü sayfası gibi bir şey için gerçekten uygun olmaz.
Brandon Linton

@Pedro Ne dediğini anlıyorum ve sanırım Brandon oradaki noktayı kaçırdı. Özel bir WebViewPage kullanıyordum ve görünümlerden birinden bazı verileri özel WebViewPage'de özel bir özellik kullanarak düzen görünümüne geçirmeye çalıştım. Özelliği görünümde ayarladığımda, özel WebViewPage'imdeki ViewData'yı güncellerdi, ancak düzen görünümüne geldiğinde, ViewData girişi zaten kaybedildi. Özel WebViewPage'de ViewContext.Controller.ViewData ["SomeValue"] kullanarak bunu aştım. Umarım birine yardımcı olur.
Imran Rashid

17

Brandon'ın görevi paranın üstünde. Nitekim olarak, ayrıca bu bir adım atmak ve sadece gibi ortak nesneler eklemek gerektiğini söyleyebilirim özellikleri arasında taban WebViewPage Eğer her Görünümünde ViewBag gelen döküm öğelere zorunda kalmamak. CurrentUser kurulumumu bu şekilde yapıyorum.


Bunu hatayla çalıştıramadım'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

Bu +1, tam olarak tüm görünümlerde global olarak mevcut olması gereken statik olmayan bir yardımcı program sınıfının bir örneğini paylaşmak için yaptığım şey.
Nick Coad

9

Özel bir ActionResult kullanabilirsiniz:

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

Veya bir ActionFilter bile:

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

        base.OnActionExecuting(filterContext);
    }
}

Bir MVC 2 projesi açılmıştı ancak her iki teknik de küçük değişikliklerle hala geçerli.


5

Eylemlerle uğraşmak veya modeli değiştirmek zorunda değilsiniz, sadece bir temel kontrolör kullanın ve mevcut kontrolcüyü düzen görünümü bağlamından yayınlayın.

İstenen ortak verilerle (başlık / sayfa / konum vb.) Ve eylem başlatma ile bir temel kontrolör oluşturun ...

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

    protected override void OnActionExecuting(ActionExecutingContext filterContext) {

        MyCommonValue = 12345;

        base.OnActionExecuting(filterContext);
    }
}

Her denetleyicinin temel denetleyiciyi kullandığından emin olun ...

public class UserController:_BaseController {...

Mevcut temel denetleyiciyi _Layout.cshmlsayfanızdaki görünüm bağlamından yayınlayın ...

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

Artık düzen sayfanızdan temel denetleyicinizdeki değerlere başvurabilirsiniz.

@myController.MyCommonValue

3

Görünümlerinizdeki özellikler için derleme zamanı kontrolü ve zeka istiyorsanız, o zaman ViewBag gitmenin yolu değildir.

Bir BaseViewModel sınıfı düşünün ve diğer görünüm modellerinizin bu sınıftan miras almasını sağlayın, örneğin:

Temel Görünüm Modeli

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

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

Belirli ViewModel'i görüntüleyin

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

Artık görünüm kodu, mülke doğrudan görünümden erişebilir

<p>Is Admin: @Model.IsAdmin</p>

2

Aşağıdaki yaklaşımı en verimli buldum ve _ViewStart.chtml dosyasını ve gerektiğinde koşullu ifadeleri kullanarak mükemmel bir kontrol sağladım:

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

Not :

PageData, Views'da mükemmel şekilde çalışacaktır; ancak, bir PartialView olması durumunda, Görünüm'den Kısmi alt öğeye aktarılması gerekecektir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.