Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • andrew shearer 437 posts 548 karma points
    Jul 22, 2021 @ 04:33
    andrew shearer
    0

    Umbraco context in ViewComponent: best practice

    Hi everyone

    Partials don’t exist in the .net core world, now its all about View Components. So If my scenario was having a “header” View Component in my Layout file (so its shared across all site templates), and the menu shown in this header was managed on a “homepage” node in the CMS, what would be the best way of obtaining this property for the viewModel? i.e. this in my umbraco 8 site:

    @Html.Action("Header", "MyController")
    

    becomes this in umbraco 9:

    <vc:header />
    

    Do I use dependency injection to give it some Umbraco Context? https://our.umbraco.com/documentation/reference/using-ioc/ hasn’t been updated for umbraco 9 so haven’t found a good example for doing this yet.

    Or can I access the ModelsBuilder classes somehow? i.e. the homepage model and the “menu” property from that

    Or another better method?

    Thanks

    Andrew

  • Garcia562 1 post 71 karma points
    Jul 22, 2021 @ 05:29
    Garcia562
    0

    So it seems like ViewComponents could be pretty cool within the context of Umbraco, you can inject services into their constructor using di (think umbraco services) and pass in additonal parameters (like macros) and they aren't part of the request eg like child actions - but it seems using either invoke or invokeasync, you can conjure up a model in C# and send it through to a View.

  • Benjamin Carleski 32 posts 270 karma points MVP c-trib
    Jul 22, 2021 @ 17:34
    Benjamin Carleski
    101

    Yes, you can use dependency injection. According to the Microsoft Docs, all view components can use dependency injection in the constructor, so you would inject an Umbraco Context just like you would in any other location. You'd have a constructor argument of type IUmbracoContextAccessor, and then save that to a private, readonly field. Then in your InvokeAsync method, you would get the Umbraco Context from the accessor field and use it to get your homepage node. You could cast the IPublishedContent for the home page node to the Models Builder type to make it simpler to access the properties you needed, or you could use the .Value method on IPublishedContent. Something like the following should work:

    public class MyViewComponent : ViewComponent
    {
        private readonly IUmbracoContextAccessor _umbracoContextAccessor;
    
        public MyViewComponent(IUmbracoContextAccessor umbracoContextAccessor)
        {
            _umbracoContextAccessor= umbracoContextAccessor;
        }
    
        public async Task<IViewComponentResult> InvokeAsync()
        {
            var ctx = _umbracoContextAccessor;
            var home = ctx.Content.GetAtRoot().FirstOrDefault() as Homepage;
            var menu = home?.Menu;
            return View(menu);
        }
    }
    
  • andrew shearer 437 posts 548 karma points
    Jul 22, 2021 @ 23:22
    andrew shearer
    0

    thanks Benjamin! yes it was the IUmbracoContextAccessor documentation that i couldn't find. thank you

  • andrew shearer 437 posts 548 karma points
    Aug 19, 2021 @ 22:20
    andrew shearer
    1

    fyi it seems release candidate 2 has broken the above and reinstated the umb8 way of doing things:

        public class MyViewComponent : ViewComponent
    {
        private readonly IUmbracoContextFactory _context;
    
        public MyViewComponent(IUmbracoContextFactory context)
        {
            _context = context;
        }
    
        public async Task<IViewComponentResult> InvokeAsync()
        {
            using (var cref = _context.EnsureUmbracoContext())
            {
                //use context here
                return View(viewModel);
            }   
        }
    }
    

    :)

  • Benjamin Carleski 32 posts 270 karma points MVP c-trib
    Aug 19, 2021 @ 22:39
    Benjamin Carleski
    0

    Broken it how? Does IUmbracoContextAccessor no longer work, or just not for the context you are working in? The IUmbracoContextFactory was always an option, so I'm not seeing what was reverted to the v8 way.

    Also, I don't know if it is the same with v9, but I know in v8 you should generally use a Scope if you were going to use IUmbracoContextFactory. Shannon talked about it at https://our.umbraco.com/forum/using-umbraco-and-getting-started/102676-triggering-index-rebuild-via-hangfire-causes-objectdisposedexception-in-nucache#comment-321129, but I'm not sure if that applies anymore or not.

  • andrew shearer 437 posts 548 karma points
    Aug 19, 2021 @ 22:50
    andrew shearer
    0

    "broken it" as in the example you supplied worked in RC1 but has a compile error in RC2

    enter image description here

  • andrew shearer 437 posts 548 karma points
    Aug 20, 2021 @ 04:05
    andrew shearer
    0

    It would still be great if https://our.umbraco.com/documentation/reference/using-ioc/ could be updated for Umbraco 9. I'm still trying to figure out the best-practice way of getting the "current" umbraco content page within my custom ViewComponent. When I try to inject UmbracoHelper via DI and use "AssignedContentItem" i get the following runtime error:

    InvalidOperationException: Cannot return the IPublishedContent because the UmbracoHelper was not constructed with an IPublishedCont
    

    thanks

        public class MetaTagsViewComponent : ViewComponent
    {
        private readonly UmbracoHelper _umbraco;
    
        public MetaTagsViewComponent(UmbracoHelper umbraco)
        {
            _umbraco = umbraco;
        }
    
        public async Task<IViewComponentResult> InvokeAsync()
        {
    
            var current = _umbraco.AssignedContentItem;
            return View();
    
        }
    }
    
  • andrew shearer 437 posts 548 karma points
    Aug 23, 2021 @ 03:00
    andrew shearer
    0

    What would actually be great in .net 5 is if there was a base ViewComponent class ("UmbracoViewComponent"?) that automatically provided Umbraco context and helpers for free, similar to how "RenderMvcController" did in the .net 4 world. Thoughts?

  • AbsolutelyN 49 posts 310 karma points
    Oct 05, 2021 @ 08:21
    AbsolutelyN
    0

    The following works for me.

    public class MyComponent : ViewComponent
    {
    
        private readonly IUmbracoContextAccessor _umbracoContextAccessor;
    
        public MyComponent(IUmbracoContextAccessor umbracoContextAccessor)
        {
            _umbracoContextAccessor = umbracoContextAccessor;
        }
    
    
        public IViewComponentResult Invoke()
        {
            var myModel = new MyModel();
            if (_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext context))
            {
                // use context here
            }
    
            return View(myModel);
        }
    
    }
    
  • Jamie Attwood 156 posts 383 karma points c-trib
    4 days ago
    Jamie Attwood
    0

    I understand best practices of MVC Patterns as that relates to testability and separation of concerns, but it's unlikely that you would ever be using a viewcomponent without there being umbraco context.

    Can't you just pass in your current page model as an IPublishedContent parameter to your ViewComponent like this? (Using my breadcrumb ViewComponent here as an example). This way you feed it everything it needs to know from the view without having to jump through hoops in your VC... seems a lot simpler to me. Just not sure of the extra overhead if any....

       @(await Component.InvokeAsync("Breadcrumb", new { currentPage = Model}))
    

    and then in your View component:

    public IViewComponentResult Invoke(IPublishedContent currentPage)
            {
                List<Crumb> crumbsList = CrumbBuilder(currentPage);
    
                var model = new BreadcrumbViewModel()
                {
                    Crumbs = crumbsList,
                    CurrentPageName = currentPage.Value<string>("browserTitle") != null ? currentPage.Value<string>("browserTitle") : currentPage.Name
                };
                return View("Index", model);
            }
    

    [Edit] Playing around with this further, if you are working with a view model inside your view component that inherits from a models builder model, to inject the content into the model, you now need to pass the PublishedValueFallback as well. So just passing the model from the view as a parameter won't work, so back to DI. If you inject the IUmbracoContextAccessor then you can get the current page in one line like this:

    var currentPage = _context.GetRequiredUmbracoContext().PublishedRequest.PublishedContent;
    
Please Sign in or register to post replies

Write your reply to:

Draft