Essentially when the Url is generated for a content in the backoffice then a series of UrlProviders attempt to take responsibility for generating a Url, the first one in the queue to do so wins... Therefore you can create your own provider that acts only on your article doc type and works your custom maguc for generating the alternative url pattern...
However when people visit this Url, umbraco won't know how to match the pattern to find the content item...
This is where content finders come into play...
You can create your own custom content finder that knows how to match the new url pattern to the content and add this to your sites collection of content finders...
So the combination of the two should give you control over how urls are.generated and routed for your site...
I created the url provider, and the content finder specific for the article entity content.
public class ArticlePageUrlProvider : DefaultUrlProvider
{
public ArticlePageUrlProvider(
IOptionsMonitor<RequestHandlerSettings> requestSettings,
ILogger<DefaultUrlProvider> logger,
ISiteDomainMapper siteDomainMapper,
IUmbracoContextAccessor umbracoContextAccessor,
UriUtility uriUtility)
: base(requestSettings, logger, siteDomainMapper, umbracoContextAccessor, uriUtility){}
public override IEnumerable<UrlInfo> GetOtherUrls(int id, Uri current)
{
return base.GetOtherUrls(id, current);
}
public override UrlInfo? GetUrl(IPublishedContent content, UrlMode mode, string? culture, Uri current)
{
if (content is null)
{
return null;
}
if (content.ContentType.Alias == "articolo")
{
UrlInfo? defaultUrlInfo = base.GetUrl(content, mode, culture, current);
if (defaultUrlInfo is null)
{
return null;
}
if (!defaultUrlInfo.IsUrl)
{
return defaultUrlInfo;
}
else
{
var originalUrl = defaultUrlInfo.Text;
var customUrl = $"articoli{originalUrl}";
return new UrlInfo(customUrl, true, defaultUrlInfo.Culture);
}
}
return base.GetUrl(content, mode, culture, current);
}
}
public class ArticleContentFinder : IContentFinder
{
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
public ArticleContentFinder(IUmbracoContextAccessor umbracoContextAccessor)
{
_umbracoContextAccessor = umbracoContextAccessor;
}
public Task<bool> TryFindContent(IPublishedRequestBuilder contentRequest)
{
var path = contentRequest.Uri.GetAbsolutePathDecoded();
if (path.StartsWith("/articoli") is false)
{
return Task.FromResult(false); // Not found
}
if (!_umbracoContextAccessor.TryGetUmbracoContext(out var umbracoContext))
{
return Task.FromResult(false);
}
var content = umbracoContext.Content.GetByRoute("/" + path.Split("/").Last());
if (content is null)
{
return Task.FromResult(false);
}
contentRequest.SetPublishedContent(content);
return Task.FromResult(true);
}
}
And then added them to the main composer:
public class SubscribeToContentServiceSavingComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.Dashboards().Remove<ContentDashboard>();
builder.UrlProviders().Insert<ArticlePageUrlProvider>();
builder.ContentFinders().InsertBefore<ContentFinderByUrl, ArticleContentFinder>();
}
}
And it works.
But now in the backend, when i try to edit an article, it gives me an error:
Stacktrace:
at System.Uri.CreateThis(String uri, Boolean dontEscape, UriKind uriKind, UriCreationOptions& creationOptions)
at System.Uri..ctor(String uriString)
at Umbraco.Extensions.UriExtensions.MakeAbsolute(Uri uri, Uri baseUri)
at Umbraco.Extensions.UrlProviderExtensions.DetectCollisionAsync(ILogger logger, IContent content, String url, String culture, IUmbracoContext umbracoContext, IPublishedRouter publishedRouter, ILocalizedTextService textService, IVariationContextAccessor variationContextAccessor, UriUtility uriUtility)
at Umbraco.Extensions.UrlProviderExtensions.GetContentUrlsByCultureAsync(IContent content, IEnumerable`1 cultures, IPublishedRouter publishedRouter, IUmbracoContext umbracoContext, IContentService contentService, ILocalizedTextService textService, IVariationContextAccessor variationContextAccessor, ILogger logger, UriUtility uriUtility, IPublishedUrlProvider publishedUrlProvider)
at Umbraco.Extensions.UrlProviderExtensions.GetContentUrlsAsync(IContent content, IPublishedRouter publishedRouter, IUmbracoContext umbracoContext, ILocalizationService localizationService, ILocalizedTextService textService, IContentService contentService, IVariationContextAccessor variationContextAccessor, ILogger`1 logger, UriUtility uriUtility, IPublishedUrlProvider publishedUrlProvider)
at Umbraco.Cms.Web.BackOffice.Mapping.ContentMapDefinition.GetUrls(IContent source)
at Umbraco.Cms.Web.BackOffice.Mapping.ContentMapDefinition.Map[TVariant](IContent source, ContentItemDisplay`1 target, MapperContext context)
at Umbraco.Cms.Core.Mapping.UmbracoMapper.Map[TTarget](Object source, Type sourceType, MapperContext context)
at Umbraco.Cms.Core.Mapping.UmbracoMapper.Map[TTarget](Object source, MapperContext context)
at Umbraco.Cms.Web.BackOffice.Controllers.ContentController.MapToDisplayWithSchedule(IContent content)
at Umbraco.Cms.Web.BackOffice.Controllers.ContentController.GetById(Int32 id)
at lambda_method623(Closure , Object , Object[] )
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|26_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
It does this only on article content. I don't know what to do.
I am guessing you already resolved this however for anyone else that stumbles across this I believe the problem lies here:
var originalUrl = defaultUrlInfo.Text;
var customUrl = $"articoli{originalUrl}";
return new UrlInfo(customUrl, true, defaultUrlInfo.Culture);
You are creating a UrlInfo object with a relative url - there is no hostname or base url for it to be relative to and so it can't make an absolute url using it.
Content Node URL customization
Hi,
I'm having trouble finding a way to update the url of the the node content.
This is the structure.
If i go to an article ( articolo ) the route results in www.domain.com/articolo1_test
But i want to appear the father of the node, before the name of it.
Like: www.domain.com/articoli/articolo1_test
I already tried to use umbracoUrlName and umbracoUrlAlias, but the shorter url still works, i want to override it.
Is it possible?
Thank you in advance.
Hi Riccardo
Have a look at the documentation around custom UrlProviders
https://our.umbraco.com/Documentation/Reference/Routing/Request-Pipeline/outbound-pipeline#defaulturlprovider
And custom ContentFinders
https://our.umbraco.com/Documentation/Reference/Routing/Request-Pipeline/IContentFinder
Essentially when the Url is generated for a content in the backoffice then a series of UrlProviders attempt to take responsibility for generating a Url, the first one in the queue to do so wins... Therefore you can create your own provider that acts only on your article doc type and works your custom maguc for generating the alternative url pattern...
However when people visit this Url, umbraco won't know how to match the pattern to find the content item...
This is where content finders come into play...
You can create your own custom content finder that knows how to match the new url pattern to the content and add this to your sites collection of content finders...
So the combination of the two should give you control over how urls are.generated and routed for your site...
Regards
Marc
First of all thank you, it works... kind of
I created the url provider, and the content finder specific for the article entity content.
And then added them to the main composer:
And it works.
But now in the backend, when i try to edit an article, it gives me an error:
Stacktrace:
It does this only on article content. I don't know what to do.
Thank you in advance.
For now, i resolved the problem like this...
I don't think it's the best way to do it, but it works.
I would love to hear if there's a better way.
Thank you in advance.
I am guessing you already resolved this however for anyone else that stumbles across this I believe the problem lies here:
You are creating a
UrlInfo
object with a relative url - there is no hostname or base url for it to be relative to and so it can't make an absolute url using it.is working on a reply...