Copied to clipboard

Flag this post as spam?

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


  • Peter Laurie 50 posts 124 karma points
    1 week ago
    Peter Laurie
    0

    Umbraco 15 - DefaultUrlProvider - ILocalizationService is obsolete

    HI, I am looking at recoding our Umbraco 13 website to Umbraco 15.

    To change URLSs, like using a "virtual folder" in the CMS or remove category segments from news urls, we use a class ShortenUrlsUrlProvider, which derives from DefaultUrlProvider

    Looking at the v15 documentation code after getting a warning in Visual Studio, saying the ILocalizationService is obsolete, I looked at the following documentation page for further advise.

    https://docs.umbraco.com/umbraco-cms/reference/routing/request-pipeline/outbound-pipeline

    All it says is this:

    The below example is using ILocalizationService which is currently obsolete and will be removed in v15. Use ILanguageService or IDictionaryItemService (for dictionary item operations) instead.

    public class ShortenUrlsUrlProvider : DefaultUrlProvider {
    

    The article does not show the changes needed, to use ILanguageService or IDictionaryService, as it seems DefaultUrlProvider or NewDefaultUrlProvider does not seem to of been updated to use either of these two alternatives. I also downloaded the Umbraco-CMS source code from GitHub to take a look. Please can you advise me, or any of the core Umbraco coders advise on what changes to make?

    Thank you,

    Kind regards,

    Pete

  • Huw Reddick 1932 posts 6720 karma points MVP 2x c-trib
    1 week ago
    Huw Reddick
    0

    Hi Peter,

    What does your current code look like?

    You should be able to use the DictionaryItemService

    _dictionaryService.GetAsync("YourItem")
    
  • Peter Laurie 50 posts 124 karma points
    1 week ago
    Peter Laurie
    0

    Hi Huw, First off, thank you for any help you can provide. I did resign myself to just using the Obsolete key word for the time being until the full change is made in the code.

    Here is the Umbraco 13 code for the ShortenUrlsUrlProvider:

    It was this:

    using Umbraco.Cms.Core.Models.PublishedContent;
    using Umbraco.Cms.Core.Routing;
    using Umbraco.Cms.Core.Services;
    using Umbraco.Cms.Core.Web;
    using Umbraco.Cms.Web.Common;
    using Umbraco.Extensions;
    
    namespace ThisNamespace.Code.Features.ShortenUrls
    {
    
    
        public class ShortenUrlsUrlProvider : DefaultUrlProvider {
        private readonly IUmbracoHelperAccessor _umbracoHelperAccessor;
        public ShortenUrlsUrlProvider(IUmbracoHelperAccessor umbracoHelperAccessor, IOptionsMonitor<RequestHandlerSettings> requestSettings, 
          ILogger<DefaultUrlProvider> logger, 
          ISiteDomainMapper siteDomainMapper, 
          IUmbracoContextAccessor umbracoContextAccessor, 
          UriUtility uriUtility, ILocalizationService localizationService) : base(requestSettings, logger, siteDomainMapper, umbracoContextAccessor, uriUtility, localizationService) 
        {
          _umbracoHelperAccessor = umbracoHelperAccessor;
        }
    
        public override IEnumerable<UrlInfo> GetOtherUrls(int id, Uri current) {
          // Add custom logic to return 'additional urls' - this method populates a list of additional urls for the node to display in the Umbraco backoffice
          return base.GetOtherUrls(id, current);
        }
    
    // rest of the code...
    
    }
    

    And worked really well. In the appSettings I had a value with document types for "Virtual folders" types which, this was part of the classes that removed them from the page url. So pages can be organised in folders, within sub folders in the CMS and they were removed from the url of the displayed page, or segments like /news/ or news categories are removed to shorten the url.

    Anyway, I changed it to this as I found NewDefaultUrlProvider

    using Microsoft.Extensions.Options;
    using Umbraco.Cms.Core.Configuration.Models;
    using Umbraco.Cms.Core.Models.PublishedContent;
    using Umbraco.Cms.Core.PublishedCache;
    using Umbraco.Cms.Core.Routing;
    using Umbraco.Cms.Core.Services;
    using Umbraco.Cms.Core.Services.Navigation;
    using Umbraco.Cms.Core.Web;
    using Umbraco.Cms.Web.Common;
    using Umbraco.Extensions;
    
    namespace ThisNamespace.Code.Features.ShortenUrls
    {
        public class ShortenUrlsUrlProvider : NewDefaultUrlProvider
        {
            private readonly IUmbracoHelperAccessor _umbracoHelperAccessor;
    
            [Obsolete]
            public ShortenUrlsUrlProvider(IUmbracoHelperAccessor umbracoHelperAccessor, IOptionsMonitor<RequestHandlerSettings> requestSettings,
              ILogger<DefaultUrlProvider> logger,
              ISiteDomainMapper siteDomainMapper,
              IUmbracoContextAccessor umbracoContextAccessor,
              UriUtility uriUtility, ILocalizationService localizationService,
              IPublishedContentCache publishedContentCache, IDomainCache domainCache,
              IIdKeyMap idKeyMap, IDocumentUrlService documentUrlService, IDocumentNavigationQueryService documentNavigationQueryService)
              : base(requestSettings, logger, siteDomainMapper, umbracoContextAccessor, uriUtility, localizationService,
                    publishedContentCache, domainCache, idKeyMap, documentUrlService, documentNavigationQueryService)
            {
                _umbracoHelperAccessor = umbracoHelperAccessor;
            }
    
            public override IEnumerable<UrlInfo> GetOtherUrls(int id, Uri current)
            {
                // Add custom logic to return 'additional urls' - this method populates a list of additional urls for the node to display in the Umbraco backoffice
                return base.GetOtherUrls(id, current);
            }
    
            public override UrlInfo? GetUrl(IPublishedContent content, UrlMode mode = UrlMode.Default, string? culture = null, Uri? current = null)
            {
    
                // If this is a virtual node itself, no need to handle it - should return normal URL
                var hasShortenedUrlInPath = false;
    
                foreach (var item in content.Ancestors())
                {
                    if (item.IsShortenedUrl())
                    {
                        hasShortenedUrlInPath = true;
    
                        break;
                    }
                }
    
                // Only apply this to category pages
                if (content.ContentType.Alias == "newsCategory")
                {
                    // Get the original base url that the DefaultUrlProvider would have returned,
                    // it's important to call this via the base, rather than .Url, or UrlProvider.GetUrl to avoid cyclically calling this same provider in an infinite loop!!)
                    UrlInfo? defaultUrlInfo = base.GetUrl(content, mode, culture, current);
                    if (defaultUrlInfo is null)
                    {
                        return null;
                    }
    
                    if (!defaultUrlInfo.IsUrl)
                    {
                        // This is a message (eg published but not visible because the parent is unpublished or similar)
                        return defaultUrlInfo;
                    }
                    else
                    {
                        // Manipulate the url somehow in a custom fashion:
                        var originalUrl = defaultUrlInfo.Text;
    
                        if (!string.IsNullOrEmpty(originalUrl))
                        {
                            var adjustedUrl = originalUrl.Replace("/news-content/news-categories", "");
                            var customUrl = $"/news{adjustedUrl}";
                            return new UrlInfo(customUrl, true, defaultUrlInfo.Culture);
                        }
                    }
                }
    
                return hasShortenedUrlInPath ? ConstructUrl(content, mode, culture!, current!) : base.GetUrl(content, mode, culture, current);
            }
    
    
            private UrlInfo ConstructUrl(IPublishedContent content, UrlMode mode, string culture, Uri current)
            {
                string path = content.Path;
    
                // Keep path items in par with path segments in url
                // If we are hiding the top node from path, then we'll have to skip one path item (the root). 
                // If we are not, then we'll have to skip two path items (root and home)
                //var hideTopNode = ConfigurationManager.AppSettings.Get("Umbraco.Core.HideTopLevelNodeFromPath");
                bool hideTopNode = true;
    
                if (String.IsNullOrEmpty(hideTopNode.ToString()))
                {
                    hideTopNode = false;
                }
    
                var pathItemsToSkip = ((hideTopNode) ? 2 : 1);
    
                // Get the path ids but skip what's needed in order to have the same number of elements in url and path ids
                var pathIds = path.Split(',').Skip(pathItemsToSkip).Reverse().ToArray();
    
    
                // Get the default url 
                // DO NOT USE THIS - RECURSES: string url = content.Url;
                // https://our.umbraco.org/forum/developers/extending-umbraco/73533-custom-url-provider-stackoverflowerror
                // https://our.umbraco.org/forum/developers/extending-umbraco/66741-iurlprovider-cannot-evaluate-expression-because-the-current-thread-is-in-a-stack-overflow-state
                UrlInfo? defaultUrlInfo = base.GetUrl(content, mode, culture, current);
    
                if (defaultUrlInfo is not null)
                {
                    var urlText = defaultUrlInfo.Text;
    
                    // If we come from an absolute URL, strip the host part and keep it so that we can append
                    // it again when returing the URL. 
                    var hostPart = "";
    
                    if (urlText.StartsWith("http"))
                    {
                        var uri = new Uri(defaultUrlInfo.Text);
    
                        urlText = urlText.Replace(uri.GetLeftPart(UriPartial.Authority), "");
                        hostPart = uri.GetLeftPart(UriPartial.Authority);
                    }
    
                    // Strip leading and trailing slashes 
                    if (urlText.EndsWith("/"))
                    {
                        urlText = urlText.Substring(0, urlText.Length - 1);
                    }
    
                    if (urlText.StartsWith("/"))
                    {
                        urlText = urlText.Substring(1, urlText.Length - 1);
                    }
    
                    // Now split the url. We should have as many elements as those in pathIds.
                    string[] urlParts = urlText.Split('/').Reverse().ToArray();
    
                    // Iterate the url parts. Check the corresponding path id and if the document that corresponds there
                    // is of a type that must be excluded from the path, just make that url part an empty string.
                    var i = 0;
    
                    foreach (var urlPart in urlParts)
                    {
    
                        var currentItem = GetContentbyId(int.Parse(pathIds[i]));
    
                        // Omit any virtual node unless it's leaf level (we still need this otherwise it will be pointing to parent's URL)
                        if (currentItem != null)
                        {
                            if (currentItem.Name == "News categories")
                            {
                                urlParts[i] = "news";
                            }
                            else if (currentItem.IsShortenedUrl() && i > 0)
                            {
                                urlParts[i] = "";
                            }
                        }
    
                        i++;
                    }
    
                    // Reconstruct the url, leaving out all parts that we emptied above. This 
                    // will be our final url, without the parts that correspond to excluded nodes.
                    string finalUrl = String.Join("/", urlParts.Reverse().Where(x => x != "").ToArray());
    
                    // Just in case - check if there are trailing and leading slashes and add them if not
                    if (!finalUrl.EndsWith("/"))
                    {
                        finalUrl += "/";
                    }
    
                    if (!finalUrl.StartsWith("/"))
                    {
                        finalUrl = "/" + finalUrl;
                    }
    
                    finalUrl = String.Concat(hostPart, finalUrl);
    
                    // Voila
                    return new UrlInfo(finalUrl, true, culture);
                }
                else
                {
                    return base.GetUrl(content, mode, culture, current)!;
                }
            }
    
            public IPublishedContent? GetContentbyId(int Id)
            {
                // Try and get the umbraco helper
                var success = _umbracoHelperAccessor.TryGetUmbracoHelper(out var umbracoHelper);
                if (success is false)
                {
                    // Failed to get UmbracoHelper, probably because it was accessed outside of a scoped/transient service.
                    return null;
                }
    
                return umbracoHelper?.Content(Id);
    
            }
        }
    }
    

    This is all part of a few ShortenUrl classes, for Content Saving, ShortenUrls helper and ShortenUrlsContentFinder.

    The issue is with ILocalizationService in code above. If on the Umbraco Documentation site they say it had been deprecated, I thought they would of published a solution.

    I thought, wrongly, that NewDefaultUrlProvider might have ILocalization removed and been replaced with ILangaugeService or IDictionaryItemService in the code as an example.

    Thank you for any help in the right direction to go.

    Kind regards,

    Pete

  • Huw Reddick 1932 posts 6720 karma points MVP 2x c-trib
    5 days ago
    Huw Reddick
    0

    Not sure off the top of my head, but will take a look later

Please Sign in or register to post replies

Write your reply to:

Draft