umbraco / Umbraco-CMS

Umbraco is a free and open source .NET content management system helping you deliver delightful digital experiences.
https://umbraco.com
MIT License
4.42k stars 2.67k forks source link

Value cannot be null. (Parameter 'key1') when trying to get .Value in RecurringHostedService (or other background services) #15801

Closed remkovb closed 5 months ago

remkovb commented 7 months ago

Which Umbraco version are you using? (Please write the exact version, example: 10.1.0)

13.1.1

Bug summary

When trying to get .Value from any IPublishedContent item this doesn't work when not passing culture, even if this property doesn't have the checkbox for "Vary by culture" checked.

So whenever we try to get value we always have to pass culture to .Value("propertyKey", "culture") extension.

This is already from U10+ to latest version while before this never happened. When upgrading sites we discovered lot's of this "Value cannot be null. (Parameter 'key1')" errors in our logs.

Specifics

No response

Steps to reproduce

image

Message, exception: image

When doing this, everything is ok: var umbracoNaviHideValue = node.Value("umbracoNaviHide", "nl-NL");

Expected result / actual result

I should expect I don't have to pass culture argument if this isn't necessary. I mean, when trying to get value from property that doesnt have culture variant, this should not be needed right?

github-actions[bot] commented 7 months ago

Hi there @remkovb!

Firstly, a big thank you for raising this issue. Every piece of feedback we receive helps us to make Umbraco better.

We really appreciate your patience while we wait for our team to have a look at this but we wanted to let you know that we see this and share with you the plan for what comes next.

We wish we could work with everyone directly and assess your issue immediately but we're in the fortunate position of having lots of contributions to work with and only a few humans who are able to do it. We are making progress though and in the meantime, we will keep you in the loop and let you know when we have any questions.

Thanks, from your friendly Umbraco GitHub bot :robot: :slightly_smiling_face:

remkovb commented 7 months ago

I guess this is also related to this: https://github.com/umbraco/Umbraco-CMS/issues/14954

But the other issue talks about segment, while this is about culture that needs be passed to bypass this error?

SandraGeisha commented 7 months ago

Also faced this one, as I understood it and mentioned in https://github.com/umbraco/Umbraco-CMS/issues/15749 it's actually because the document itself is variant. Meaning that you'll have to specify a culture. If the doctype and the property isn't variant you should be able to get it without having to specify the value. As mentioned in the original PR it was to fix something to do with a caching issue. Not sure if they'll revert this one as it does make some logical sense.

RosenPetrovFFW commented 5 months ago

We also experience that error when we plug code in Examine indexing.

I think it is really breaking at least for Models Builder and should be fixed in there since we cannot use it in these scenarios because we cannot pass culture.

Other than that it makes sense if the doctype is varied by culture to pass always culture.. Before I had some checks per field and now I won't need that which is better.

Migaroez commented 5 months ago

Just tried out (in v13.3) something similar using a more conventional entry point (apicontroller) and it seems to work just fine.

using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Web;

namespace Umbraco.Cms.Web.UI.App_Data;

[ApiController]
public class ApiController : ControllerBase
{
    private readonly IUmbracoContextAccessor _umbracoContextAccessor;

    public ApiController(IUmbracoContextAccessor umbracoContextAccessor)
    {
        _umbracoContextAccessor = umbracoContextAccessor;
    }

    [HttpGet("api/invariant")]
    public IActionResult Get()
    {
        if (_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? context) is false)
        {
            return BadRequest("Could not build UmbracoContext");
        }

        IPublishedContent document = context.Content!.GetAtRoot().First(d => d.ContentType.Alias == "invariantPage");

        return Ok(document.Value<string>("title"));
    }
}

result image

Migaroez commented 5 months ago

Seems to work just fine in a RecurringHost too

using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Web;
using Umbraco.Cms.Infrastructure.HostedServices;

namespace Umbraco.Cms.Web.UI.App_Data;

public class RecurringHost : RecurringHostedServiceBase
{
    private readonly ILogger<RecurringHost> _logger;
    private readonly IRuntime _runtime;
    private readonly IUmbracoContextFactory _umbracoContextFactory;

    private static TimeSpan HowOftenWeRepeat => TimeSpan.FromSeconds(30);

    private static TimeSpan DelayBeforeWeStart => TimeSpan.FromSeconds(10);

    public RecurringHost(
        ILogger<RecurringHost> logger,
        IRuntime runtime,
        IUmbracoContextFactory umbracoContextFactory) : base(logger, HowOftenWeRepeat, DelayBeforeWeStart)
    {
        _logger = logger;
        _runtime = runtime;
        _umbracoContextFactory = umbracoContextFactory;
    }

    public override async Task PerformExecuteAsync(object? state)
    {
        if (_runtime.State.Level is not RuntimeLevel.Run)
        {
            await Task.CompletedTask;
        }

        using UmbracoContextReference umbracoContextReference = _umbracoContextFactory.EnsureUmbracoContext();

        IPublishedContent document = umbracoContextReference.UmbracoContext.Content!.GetAtRoot().First(d => d.ContentType.Alias == "invariantPage");

        var value = document.Value<string>("title");

        _logger.LogInformation("the value is {Value}", value);
        await Task.CompletedTask;
    }
}

public class MyComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder) => builder.Services.AddHostedService<RecurringHost>();
}

Result {"@t":"2024-04-29T08:50:45.5930282Z","@mt":"the value is {Value}","Value":"The Title","SourceContext":"Umbraco.Cms.Web.UI.App_Data.RecurringHost","ProcessId":33952,"ProcessName":"Umbraco.Web.UI","ThreadId":19,"ApplicationId":"4236a0fa0bb1fb0fa2fd7f216a2c9f0d5111a2c9","MachineName":"RAVNIKA","Log4NetLevel":"INFO "}

Migaroez commented 5 months ago

As I cannot reproduce it with the supplied information, I will be closing this issue. Feel free to reopen it if you have more information