In some cases you might experience that a circular dependency is preventing your Umbraco installing from starting up.
An example of this could be a circular dependency on IUmbracoContextFactory. This would happen if your service interacts with third-party code that also depends on an IUmbracoContextFactory instance.
In this situation, you can request a lazy version of the dependency so it won't evaluate during boot, only when accessed:
publicclassSiteService:ISiteService{privatereadonlyLazy<IUmbracoContextFactory> _umbracoContextFactory;publicSiteService(Lazy<IUmbracoContextFactory> umbracoContextFactory) { _umbracoContextFactory = umbracoContextFactory; }publicIPublishedContentGetNewsSection() {using (UmbracoContextReference umbracoContextReference =_umbracoContextFactory.Value.EnsureUmbracoContext()) { // Do your thing } }}
Services and Helpers
Umbraco has a range of 'Core' Services and Helpers that act as a 'gateway' to Umbraco data and functionality to use when extending or implementing an Umbraco site
Umbraco has a range of 'Core' Services and Helpers that act as a 'gateway' to Umbraco data and functionality to use when extending or implementing an Umbraco site.
The general rule of thumb is that management Services provide access to allow the modification of Umbraco data (and therefore aren't optimised for displaying data). Helpers on the other hand provide access to readonly data with performance of displaying data taken into consideration.
Although there is a management Service named the IContentService - only use this to modify content - do not use the IContentService in a View/Template to pull back data to display, this will make requests to the database and be slow - here instead inject the IPublishedContentQueryAccessor interface and get the IPublishedContentQuery that operate against a cache of published content items, and are significantly quicker.
The management Services and Helpers are all registered with Umbraco's underlying DI framework. This article aims to show examples of gaining access to utilise these resources in multiple different scenarios. There are subtle differences to be aware of depending on what part of Umbraco is being extended.
This article will also suggest how to follow a similar pattern to encapsulate custom 'site specific' implementation logic, in similar services and helpers, registered with the underlying DI contain. This would be to avoid repetition and promote consistency and readability within an Umbraco site solution.
Accessing Management Services and Helpers in a Template/View
Inside a view/template or partial view, access is also provided by the DI framework, by using the @inject keyword.
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<ContentModels.Root>@using ContentModels =Umbraco.Cms.Web.Common.PublishedModels;@using Umbraco.Cms.Core.Services;@using Umbraco.Cms.Web.Common;@* it isreally 'unlikely' to need to use a management Service in a view:*@@inject IRelationService RelationService@inject UmbracoHelper Umbraco@{ Layout =null; // retrieve an item from Umbraco's published cache with id 123IPublishedContent publishedContentItem =Umbraco.Content(123);}
Accessing Core Services and Helpers in a Controller
usingMicrosoft.AspNetCore.Mvc;usingMicrosoft.AspNetCore.Mvc.ViewEngines;usingMicrosoft.Extensions.Logging;usingUmbraco.Cms.Core;usingUmbraco.Cms.Core.Models.PublishedContent;usingUmbraco.Cms.Core.Services;usingUmbraco.Cms.Core.Web;usingUmbraco.Cms.Web.Common.Controllers;namespaceUmbraco9.Controllers{publicclassBlogPostController:RenderController {privatereadonlyILogger<BlogPostController> _logger;privatereadonlyIPublishedContentQuery _publishedContentQuery;privatereadonlyIRelationService _relationService;publicBlogPostController(ILogger<BlogPostController> logger,ICompositeViewEngine compositeViewEngine,IUmbracoContextAccessor umbracoContextAccessor,IPublishedContentQuery publishedContentQuery,IRelationService relationService): base(logger, compositeViewEngine, umbracoContextAccessor) { _logger = logger; _publishedContentQuery = publishedContentQuery; _relationService = relationService; }publicoverrideIActionResultIndex() { // write helpful messages to the Umbraco logs to aid with debugging_logger.LogInformation("Using core logger implementation"); // retrieve an item from Umbraco's published cache with id 123IPublishedContent publishedContentItem =_publishedContentQuery.Content(123); // it is unlikely to use a management service when rendering content from a custom controller //(when using relationService like this you would want to provide a layer of caching)var allRelatedUmbracoItems =_relationService.GetByParentId(CurrentPage.Id);return base.Index(); } }}
Accessing core Services and Helpers when there is no 'UmbracoContext' eg in a Component or C# Class
Controllers and Views can access an IUmbracoContext by injecting the IUmbracoContextAccessor, however this is not always the case 'everywhere in Umbraco', for example common extension points: Components,ContentFinders or Custom C# Classes.
IUmbracoContext, UmbracoHelper, IPublishedContentQuery - are all based on an HttpRequest - their lifetime is controlled by an HttpRequest. So if you are not operating within an actual request, you cannot inject these parameters and if you try to ... Umbraco will report an error on startup.
Injecting Services into a Component
It's possible to inject management Services that do not rely on the UmbracoContext into the constructor of a component. This example shows injecting the IMediaService in a Notification Handler to create a corresponding Media Folder for every 'landing page' that is saved in the Content Section, by subscribing to the 'Content Saved' notification.
usingSystem.Linq;usingUmbraco.Cms.Core.Composing;usingUmbraco.Cms.Core.DependencyInjection;usingUmbraco.Cms.Core.Events;usingUmbraco.Cms.Core.Models;usingUmbraco.Cms.Core.Notifications;usingUmbraco.Cms.Core.Services;namespaceUmbraco9.Components{publicclassSubscribeToContentSavedEventComposer:IComposer {publicvoidCompose(IUmbracoBuilder builder) {builder.AddNotificationHandler<ContentSavedNotification,SubscribeToContentSavedNotification>(); } }publicclassSubscribeToContentSavedNotification:INotificationHandler<ContentSavedNotification> {privatereadonlyIMediaService _mediaService;publicSubscribeToContentSavedNotification(IMediaService mediaService) { _mediaService = mediaService; }publicvoidHandle(ContentSavedNotification notification) {foreach (var contentItem innotification.SavedEntities) { // if this is a new landing page create a folder for associated media in the media sectionif (contentItem.ContentType.Alias=="landingPage") { // we have injected in the mediaService in the constructor for the component see above.bool hasExistingFolder =_mediaService.GetByLevel(1).Any(f =>f.Name==contentItem.Name);if (!hasExistingFolder) { // let's create one (-1 indicates the root of the media section)IMedia newFolder =_mediaService.CreateMedia(contentItem.Name,-1,"Folder");_mediaService.Save(newFolder); } } } } }}
See documentation on Composing for further examples and information on Components and Composition.
Accessing Published Content outside of a Http Request
Trying to inject types that are based on an Http Request such as UmbracoHelper or IPublishedContentQuery into classes that are not based on an Http Request will trigger an error. However, there is a technique that allows the querying of the Umbraco Published Content, using the UmbracoContextFactory and calling EnsureUmbracoContext().
In this example, when a page is unpublished, instead of a 404 occurring for the content when the url is requested in the future, we might want to serve a 410 'page gone' status code instead. We handle the Unpublishing notification of the ContentService, access the Published Content Cache, determine it's 'published url' and then store for later use in any 'serving the 410' mechanism.
An IContentFinder could be placed in the ContentFinder ordered collection, right before a 404 is served. This could be done to lookup the incoming request against the stored location of 410 urls, and serve the 410 status request code if a match is found for the previously published item.
usingSystem;usingUmbraco.Cms.Core;usingUmbraco.Cms.Core.Composing;usingUmbraco.Cms.Core.DependencyInjection;usingUmbraco.Cms.Core.Events;usingUmbraco.Cms.Core.Models.PublishedContent;usingUmbraco.Cms.Core.Notifications;usingUmbraco.Cms.Core.PublishedCache;usingUmbraco.Cms.Core.Web;usingUmbraco.Extensions;namespaceUmbraco9.Components{publicclassHandleUnPublishingEventComposer:IComposer {publicvoidCompose(IUmbracoBuilder builder) {builder.AddNotificationHandler<ContentUnpublishedNotification,HandleUnPublishingHandler>(); } }publicclassHandleUnPublishingHandler:INotificationHandler<ContentUnpublishedNotification> {privatereadonlyIUmbracoContextFactory _umbracoContextFactory;publicHandleUnPublishingHandler(IUmbracoContextFactory umbracoContextFactory) { _umbracoContextFactory = umbracoContextFactory; }publicvoidHandle(ContentUnpublishedNotification notification) { // for each unpublished item, we want to find the url that it was previously 'published under' and store in a database table or similar
using (UmbracoContextReference umbracoContextReference =_umbracoContextFactory.EnsureUmbracoContext()) { // the UmbracoContextReference provides access to the ContentCacheIPublishedContentCache contentCache =umbracoContextReference.UmbracoContext.Content;foreach (var item innotification.UnpublishedEntities) {if (item.ContentType.Alias=="blogpost") { // item being unpublished will still be in the cache, as unpublishing event fires before the cache is updated.
IPublishedContent soonToBeUnPublishedItem =contentCache.GetById(item.Id);if (soonToBeUnPublishedItem !=null) {string previouslyPublishedUrl =soonToBeUnPublishedItem.Url();if (!string.IsNullOrEmpty(previouslyPublishedUrl) && previouslyPublishedUrl !="#") {_customFourTenService.InsertFourTenUrl(previouslyPublishedUrl,DateTime.UtcNow); } } } } } } }}
Accessing the Published Content Cache from a Content Finder / UrlProvider
Inside a ContentFinder access to the content cache is possible by injecting IUmbracoContextAccessor into the constructor and provided via the PublishedRequest object:
It is still possible to inject services into IContentFinder's. IContentFinders are singletons, but the example is showing you do not 'need to' in order to access the Published Content Cache.
Customizing Services and Helpers
When implementing an Umbraco site, it is likely to have to execute similar code that accesses or operates on Umbraco data, in multiple places, perhaps using the core management Services or Umbraco Helpers.
For example; Getting a list of the latest News Articles, or building a link to the site's News Section or Contact Us page. Repeating this kind of logic in multiple places, Views, Partial Views / Controllers etc, is possible, but it's generally considered good practice to consolidate this logic into a single place.
Extension methods
One option is to add 'Extension Methods' to the UmbracoHelper class or IPublishedContentQuery interface.
usingSystem.Linq;usingUmbraco.Cms.Core;usingUmbraco.Cms.Core.Models.PublishedContent;usingUmbraco.Extensions;namespaceUmbraco9.Components{publicstaticclassPublishedContentQueryExtensions {publicstaticIPublishedContentGetNewsSection(thisIPublishedContentQuery publishedContentQuery) { // assuming a single site with a single News Section at the top levelIPublishedContent siteRoot =publishedContentQuery.ContentAtRoot().FirstOrDefault(); // make sure siteRoot isn't null, then locate first child content item with alias 'newsSection'returnsiteRoot?.FirstChild(f =>f.ContentType.Alias=="newsSection") ??null; } }