วิธีการตั้งค่าคุณสมบัติ 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";
            };
        }
    }
}

นี่อาจเป็นหนึ่งในคำแนะนำประเภท "only tool's a hammer" จากฉัน อาจมีวิธีที่ง่ายกว่าในการเปิดใช้งาน 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

สิ่งที่ยอดเยี่ยม - ตามแนวทางของคุณฉันได้เพิ่มการทำให้เข้าใจง่ายอีกครั้งคราวนี้โดยไม่ต้องใช้โมดูล
Nicholas Blumhardt

สิ่งที่เป็นResolveส่วนหนึ่งของe.Context.Resolve? ฉันควรพูดถึงฉันคุ้นเคยกับ Ninject ...
drzaus

244

วิธีที่ดีที่สุดคือใช้ ActionFilterAttribute ฉันจะแสดงวิธีใช้ใน. Net Core และ. 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);
    }
}

จากนั้นคุณต้องลงทะเบียนสิ่งนี้ใน startup.cs ของคุณ

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

จากนั้นคุณสามารถใช้งานได้ในทุกมุมมองและเพจ

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

}

ลงทะเบียนคลาสที่กำหนดเองของคุณในส่วนกลาง 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

3
+1 วิธีที่ดีและสะอาดในการรวมข้อมูลทั่วโลก ฉันใช้เทคนิคนี้เพื่อลงทะเบียนเวอร์ชันไซต์ของฉันในทุกหน้า
Will Bickford

4
โซลูชันที่ยอดเยี่ยมง่ายและไม่สร้างความรำคาญ
Eugen Timm

3
แต่ IoC อยู่ที่ไหน? เช่นคุณจะเปลี่ยนMembershipServiceอย่างไร?
drzaus

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.configตั้งค่าpageBaseTypeคุณสมบัติ:

<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

@Pedro นั่นเป็นความจริงแน่นอน แต่ฉันก็เถียงว่า ViewBag ไม่ได้หมายถึงแหล่งที่มาของสถานะถาวรในแอปพลิเคชัน ดูเหมือนว่าคุณต้องการให้ข้อมูลนั้นอยู่ในสถานะเซสชันจากนั้นคุณสามารถดึงข้อมูลนั้นออกมาในหน้ามุมมองพื้นฐานของคุณและตั้งค่าใน ViewBag ได้หากมีอยู่
Brandon Linton

คุณมีจุดที่ถูกต้อง แต่ทุกคนใช้ชุดข้อมูลในมุมมองเดียวในมุมมองอื่น ๆ เช่นเดียวกับเมื่อคุณตั้งหัวเรื่องของเพจในมุมมองเดียวและมุมมองเค้าโครงที่ใช้ร่วมกันของคุณจะพิมพ์ออกมาในแท็ก <title> ของเอกสาร html ฉันอยากจะก้าวไปอีกขั้นด้วยการตั้งค่าบูลีนเช่น "ViewBag.DataTablesJs" ในมุมมอง "ลูก" เพื่อสร้างมุมมองเค้าโครง "ต้นแบบ" รวมการอ้างอิง JS ที่เหมาะสมบนส่วนหัวของ html ตราบใดที่มันเกี่ยวข้องกับการจัดวางฉันคิดว่ามันก็โอเคที่จะทำเช่นนี้
Pedro

@Pedro ดีในสถานการณ์แท็กชื่อมักจะว่าจัดการกับการตั้งค่ามุมมองแต่ละสถานที่ให้บริการแล้วเพียงสิ่งเดียวในรูปแบบที่ใช้ร่วมกันคือViewBag.Title <title>@ViewBag.Title</title>มันจะไม่เหมาะสมจริงๆสำหรับบางอย่างเช่นหน้ามุมมองแอปพลิเคชันพื้นฐานเนื่องจากแต่ละมุมมองมีความแตกต่างกันและหน้ามุมมองฐานจะเป็นข้อมูลที่พบได้บ่อยในทุกมุมมอง
Brandon Linton

@Pedro ฉันเข้าใจสิ่งที่คุณพูดและฉันคิดว่าแบรนดอนพลาดตรงนั้น ฉันใช้ WebViewPage ที่กำหนดเองและพยายามส่งข้อมูลบางส่วนจากมุมมองใดมุมมองหนึ่งไปยังมุมมองเค้าโครงโดยใช้คุณสมบัติที่กำหนดเองใน WebViewPage ที่กำหนดเอง เมื่อฉันตั้งค่าคุณสมบัติในมุมมองมันจะอัปเดต ViewData ใน WebViewPage ที่กำหนดเองของฉัน แต่เมื่อถึงมุมมองเค้าโครงรายการ ViewData ก็หายไปแล้ว ฉันเข้าใจมันโดยใช้ ViewContext.Controller.ViewData ["SomeValue"] ใน WebViewPage ที่กำหนดเอง ฉันหวังว่ามันจะช่วยใครสักคน
Imran Rashid

17

โพสต์ของแบรนดอนอยู่บนเงิน ตามความเป็นจริงฉันจะก้าวไปอีกขั้นและบอกว่าคุณควรเพิ่มวัตถุทั่วไปของคุณเป็นคุณสมบัติของWebViewPage พื้นฐานเพื่อที่คุณจะได้ไม่ต้องแคสต์รายการจาก ViewBag ในทุกๆ View ฉันตั้งค่า 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

หากคุณต้องการตรวจสอบเวลารวบรวมและ Intellisense สำหรับคุณสมบัติในมุมมองของคุณ ViewBag ไม่ใช่ทางที่จะไป

พิจารณาคลาส BaseViewModel และให้โมเดลมุมมองอื่น ๆ ของคุณสืบทอดมาจากคลาสนี้เช่น:

Base ViewModel

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 จะทำงานได้อย่างสมบูรณ์ใน Views; อย่างไรก็ตามในกรณีของ PartialView จะต้องส่งต่อจาก View ไปยัง Partial ย่อย

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.