Copied to clipboard

Flag this post as spam?

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


  • Alexander 34 posts 84 karma points
    Jan 05, 2024 @ 06:33
    Alexander
    0

    Umbraco custom mvc route doesn't set published content correctly

    Hi,

    I'm working on a wildcard page for Umbraco 13.0.3 The idea is the following: I have "connections" item in umbraco and and the url is /connections. Additionally, I have "details" item in umbraco under the "connections" item. So the default umbraco url is /connections/details. However, I want to have urls like this: /connections/123 where 123 is an id of the entity(it comes from different db).

    After reading the docs I've come up with the following setup.

    I have a composer to register a custom route:

    public class RoutesComposer : IComposer
    {
        public void Compose(IUmbracoBuilder builder)
        {
            builder.Services.Configure<UmbracoPipelineOptions>(
                options =>
                {
                    options.AddFilter(
                        new UmbracoPipelineFilter(nameof(ConnectionDetailsController))
                        {
                            Endpoints = app => app.UseEndpoints(
                                endpoints =>
                                {
                                    endpoints.MapControllerRoute(
                                        "Connection details controller",
                                        "/connections/{id}",
                                        new { Controller = "ConnectionDetails", Action = "Index" }
                                    );
                                }
                            )
                        }
                    );
                }
            );
        }
    }
    

    Here is my controller which inherits UmbracoPageController and IVirtualPageController

    public class ConnectionDetailsController : UmbracoPageController, IVirtualPageController
    {
        public ConnectionDetailsController(
            IUmbracoContextAccessor umbracoContextAccessor,
            ILogger<UmbracoPageController> baseLogger,
            ICompositeViewEngine compositeViewEngine
        ) : base(baseLogger, compositeViewEngine)
        {
            _umbracoContextAccessor = umbracoContextAccessor;
            _logger = logger;
            _publishedValueFallback = publishedValueFallback;
        }
    
        public IPublishedContent? FindContent(ActionExecutingContext actionExecutingContext)
        {
            if (_umbracoContextAccessor.TryGetUmbracoContext(out var umbracoContext))
            {
                var content = umbracoContext.Content.GetById(1387);
                if (content != null) return content;
            }
            return null;
        }
    
        [HttpGet]
        public IActionResult Index()
        {
            return View("AppPage", CurrentPage);
        }
    
        private readonly IUmbracoContextAccessor _umbracoContextAccessor;
        private readonly ILogger<ConnectionDetailsController> _logger;
        private readonly IPublishedValueFallback _publishedValueFallback;
    }
    

    It works as a charm, umbraco sends the request to my controller, then FindContent resolves the correct PublishedContent item and on Index action I have proper item. The interesting part comes when it comes to the View.

    I have a several ViewComponents to render seo tags and other vanilla page parts like header and footer. Here is an example:

    @await Component.InvokeAsync("PageSeo")
    @await Component.InvokeAsync("PageOgp")
    @await Component.InvokeAsync("PageTwitter")
    

    And the component code:

    [ViewComponent(Name = nameof(PageSeo))]
    public class PageSeo : ViewComponent
    {
        public PageSeo(
            IUmbracoContextAccessor context,
            IConfiguration configuration,
            IPublishedValueFallback publishedValueFallback
        )
        {
            _context = context;
            _umbracoContextFactory = umbracoContextFactory;
            _configuration = configuration;
            _publishedValueFallback = publishedValueFallback;
        }
    
        public async Task<IViewComponentResult> InvokeAsync()
        {
            var viewModel = GetViewModel();
            return await Task.FromResult((IViewComponentResult)View(viewModel));
        }
    
        private PageSeoViewModel GetViewModel()
        {
            var content = _context.GetRequiredUmbracoContext().PublishedRequest.PublishedContent; // PublishedContent is null
            var seo = new Models.Generated.PageSeo(content, _publishedValueFallback);
    
            var viewModel = new PageSeoViewModel();
            viewModel.Title = seo.SeoTitle
            viewModel.Description = seo.SeoDescription;
            viewModel.Keywords = seo.SeoKeywords;
            viewModel.Author = seo.SeoAuthor;
    
            return viewModel;
        }
    
        private readonly IUmbracoContextAccessor _context;
        private readonly IConfiguration _configuration;
        private readonly IPublishedValueFallback _publishedValueFallback;
    }
    

    As you can see, it's working in the context of the current Umbraco page. Here is a troublegiver:

    var content = _context.GetRequiredUmbracoContext().PublishedRequest.PublishedContent; // PublishedContent is null
    

    The content is always null. Seems like the context hasn't been set correctly, and I'm not sure whether it's me doing something wrong or it's an umbraco bug.

    Any ideas?

    Thanks, Alexander.

  • Marc Goodson 2157 posts 14411 karma points MVP 9x c-trib
    Jan 18, 2024 @ 23:14
    Marc Goodson
    0

    Hi Alexander

    You've done everything correctly... But this is an open bug on the tracker...

    https://github.com/umbraco/Umbraco-CMS/issues/12834

    There are a couple of workarounds in there (it looks like CreatePublishedRequest doesn't get called with the content found in your Find Content method)

    But other than those workarounds, you can pass parameters into a View Component, so you could have...

    public async Task<IViewComponentResult> InvokeAsync(IPublishedContent currentPage)

    and then pass in the current page when the view component is invoked

    Component.InvokeAsync("PageSeo", new { current Page = Model} )

    Regards

    Marc

Please Sign in or register to post replies

Write your reply to:

Draft