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:
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.
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);
}
}
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.
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();
}
}
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?
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);
}
}
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;
I like that idea, but personally, I think it would be best to inject IUmbracoContextAccessor into the component or controller that you need it in, since I believe that TryGetUmbracoContext tries to obtain the UmbracoContext by first checking to see if one exists on the current thread. I am not sure how that trancends into a service that is getting injected without context in and of itself.
Since you still need to inject your new service into those classes the same way as injecting IUmbracoContextAccessor, you are not really saving too much repetition from a DRY sense in my opinion.
If it's a view component or controller, you are pretty much ensured that there is going to be context and I would just get the context via GetRequiredUmbracoContext().
var currentPage = _context.GetRequiredUmbracoContext().PublishedRequest.PublishedContent;
If it's a service that it's possible to instantiate without a component or controller, then I would go the full route and ensure that context is present using:
using (var cref = _context.EnsureUmbracoContext())
{
//Do stuff
}
Either way, if you use your service, you should probably test whether there is context returned to your component/controller anyway because if your service returns null, it's still going to break stuff.
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:
becomes this in umbraco 9:
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
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.
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:
thanks Benjamin! yes it was the IUmbracoContextAccessor documentation that i couldn't find. thank you
fyi it seems release candidate 2 has broken the above and reinstated the umb8 way of doing things:
:)
Broken it how? Does
IUmbracoContextAccessor
no longer work, or just not for the context you are working in? TheIUmbracoContextFactory
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 useIUmbracoContextFactory
. 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."broken it" as in the example you supplied worked in RC1 but has a compile error in RC2
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:
thanks
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?
The following works for me.
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....
and then in your View component:
[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:
Since you have to inject
IUmbracoContextAccessor
and then use the following code to to get UmbracoContext.I am thinking it may be better to create a service to get the UmbracoContext and then inject that wherever you need.
Now I can inject that service into another service, ViewComponent etc making code reusable and less cluttered.
Any downsides? Is there a better way to do this? Would be interested in opinions.
J
I like that idea, but personally, I think it would be best to inject IUmbracoContextAccessor into the component or controller that you need it in, since I believe that TryGetUmbracoContext tries to obtain the UmbracoContext by first checking to see if one exists on the current thread. I am not sure how that trancends into a service that is getting injected without context in and of itself.
Since you still need to inject your new service into those classes the same way as injecting IUmbracoContextAccessor, you are not really saving too much repetition from a DRY sense in my opinion.
If it's a view component or controller, you are pretty much ensured that there is going to be context and I would just get the context via GetRequiredUmbracoContext().
If it's a service that it's possible to instantiate without a component or controller, then I would go the full route and ensure that context is present using:
Either way, if you use your service, you should probably test whether there is context returned to your component/controller anyway because if your service returns null, it's still going to break stuff.
Anyway these are all personal opinions.
Cheers,
Jamie
is working on a reply...