LinkItem causing On-Page Edit to fail while saving because it can't be deserialized if it's null: "Could not save property, and it has been reverted"

Vote:
 

We have a block as a property for the page hero. Inside that HeroBlock, we have a LinkItem for a CTA button. If we don't have any LinkItems (call-to-actions), we can't save updates to the other properties like Title and Description when using on-page editing. The all-properties view works; however, we're prioritizing the on-page editing experience.

The error received is exactly "Could not save property, and it has been reverted. Please try again. Object reference not set to an instance of an object". When I check the console, there's an error that has the following stack trace and headers:

"System.NullReferenceException: Object reference not set to an instance of an object.
   at EPiServer.Cms.Shell.UI.ObjectEditing.InternalMetadata.LinkModel.ToServerModel(Object clientModel)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.DeserializeConvertable(JsonConverter converter, JsonReader reader, Type objectType, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
   at EPiServer.Cms.Shell.Json.Internal.BlockDataConverter.ReadJson(JsonReader reader, Type objectType, Object existingValue, JsonSerializer serializer)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.DeserializeConvertable(JsonConverter converter, JsonReader reader, Type objectType, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
   at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
   at EPiServer.Cms.Shell.UI.Controllers.Internal.PropertyRenderController.DeserializeValue(String value, Type propertyType)
   at EPiServer.Cms.Shell.UI.Controllers.Internal.PropertyRenderController.Render(String propertyName, String propertyValue, String id, String renderSettings, String epslanguage)
   at lambda_method1062(Closure , Object , Object[] )
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.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.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Geta.NotFoundHandler.Infrastructure.Initialization.NotFoundHandlerMiddleware.InvokeAsync(HttpContext context, RequestHandler requestHandler)
   at Microsoft.AspNetCore.Authorization.Policy.AuthorizationMiddlewareResultHandler.HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.StatusCodePagesMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

HEADERS
=======
Accept: */*
Host: xxx
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36
:method: POST
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Content-Type: application/x-www-form-urlencoded
Cookie: EPiStateMarker=true,.AspNetCore.Antiforgery.qiRbH7GbOSI=CfDJ8EBfCkc7QupHuVX3hKLON-aUfWHn7mamYmLPgssyhRt0ETpLif-5bvh-PFEPq4iWz9uB8fHam2a6G3l4S4Wh_YJ9nG5J7b5rOT3lNxRfce_E0oL0em5KXlpLlTwu-fGoF8ZHbvTqEHhBbt5H8XZoQxA,iv=d1826bae-faa6-429a-a732-51002259b89f,.AspNetCore.Identity.Application=CfDJ8EBfCkc7QupHuVX3hKLON-YAER-c0nigwjOet7_VjDvuta4cciMoWGSIh82FPzhuXlBYekUdrmh4Ff6Iija_Hj523ap7b8jtijVWZnWKUFmhJgxeQ7mUO3XLCeqI74wDY3n82pUBH5AUeSWdVlkDvvaOnlWFM4HwmehKP41rIkZtYnbs5JtgA_cnn3v4uQGQ1dlhhj5eLUrkxbZx8oljwHDOU9d4H7hhya2Jmo_9SQSBSIUthiIwVcdqNKdxI_kOryr2O9SpVnjTOaQL7TuPLOXeWkj6uzkZ8Cv4aHrg43hm9ayaw_WBu58qb-QZ-nngZ2a-WpFEux-y6qxcrW_GMfHmZSIDWdqsx2fnNjMdouZiWWqIyk7yAZp93HenbgZpjO0WQXpLP2TQpFUQ3jYhD2SVp9ArDUO01zg3QB21M_j33-cNFnQJhzUho5Pb502TCey9RRryUq5GdO1GJK2Luts8DjOH84WXkmurihsvpTAMz9I7GPmYJmlu1nBlnhxxKcMxXYxU6CxiWB_cS4uzvWrvJzg1nXRYnKt83S6bEzJQ65eeLaawp70i8P206KALHT2mG7SwQMHln4WBDQTf8QpnhemObCJck4PdvMsE8uVjYLaz8jGqjER6rpDN6S4vwH2deik3r_1Oe_It6h_bhUfn8wJnAmzzlB8Z7Kx5j1uzwevj1fiF3QIAg-1AJu0BJs5oZQ2R43f_bYKk9GJFQwAKinXRQ2UKX9FmOo4ZXwnFdlIHflvmK0uNLTbNDg_zQ1-628FObw5ksFbcv8yeFmhxCExKEcaP0jAAmtUblEq1AG8EvP16WqT4Teyyp0PA1a8l8y3edR-IhJByBtDtviAmR9IEIbr6QC7WyvQPKAy47l7k4g0lx0iO1IZ_pu3cgrJZWjRSgrw_J0GYQVMuzq7z09GQjbzIDzEuie0AFTX39OVky19wuJorxYDA8ac94A,apt.uid=AP-9R2ZHMD84Z2N-2-4-1681755478514-36546698.0.2.0efd141c-2cc6-4075-88e0-3d3bcef04448,apt.sid=AP-9R2ZHMD84Z2N-2-4-1682038611318-24971463
Origin: xxx
Referer: xxx
Content-Length: 316
sec-ch-ua: "Chromium";v="112", "Google Chrome";v="112", "Not:A-Brand";v="99"
requestverificationtoken: CfDJ8EBfCkc7QupHuVX3hKLON-atRcIUUNZydVpPjtFOHEeeh2gVH4HcliN_v8pY_YGe6Uk-3_RaM5FysBIQgTVQbSv4Yvz_rv7LFxRQtxHwgXhqsaQgbg0Qz-XsNA7Yx-YHkTj8sl7DGaeBLUC8HpDGGD3niywnFQGDe_6wUsxmUHueT2D_el7npjmfUMSmqp-fvQ
x-epicurrentcontentcontext: 1153_1438
sec-ch-ua-mobile: ?0
X-Requested-With: XMLHttpRequest
x-epicontentlanguage: en
sec-ch-ua-platform: "Windows"
sec-fetch-site: same-origin
sec-fetch-mode: cors
sec-fetch-dest: empty
"


I have identified the problem as an attempt to deserialize a null value from the LinkItem object. Here is the Ajax payload being sent:

propertyValue: {"indexInContentAreas":false,"image":null,"headerTitle":"Site Search","description":"<p>This is a description</p>","callToAction":null}

If a LinkItem is added to the CallToAction property everything works. Here is an Ajax payload with a LinkItem in the CallToAction property that allows me to update the Title or Description:

propertyValue: {"indexInContentAreas":false,"image":null,"headerTitle":"Site Search","description":"<p>This is a description!!</p>","callToAction":{"text":"Test Link","href":"http://google.com","target":null,"title":"Test Link","publicUrl":null,"typeIdentifier":""}}

Finally, here is how the block is set up, in case that sparks an idea.

[ContentType(
    GroupName = GroupNames.Content,
    DisplayName = "Hero",
    GUID = "39483886-ba15-45ca-a868-12cb2e4971a8",
    Description = "Hero component for Carousel Blocks, Section Hero, and Banner"
)]
public class HeroBlock : BaseBlock, IHeroBlock, IPageContentBlock
{

    [Display(
        Name = "Title",
        Order = 10)]
    [Required]
    [CultureSpecific]
    public virtual string HeaderTitle { get; set; }

    [Display(
        Name = "Image",
        GroupName = SystemTabNames.Content,
        Order = 20
    )]
    [CultureSpecific]
    [AllowedTypes(new[] { typeof(ImageMediaData) })]
    [DefaultDragAndDropTarget]
    [UIHint(UIHint.Image)]
    [OptionBarItem]
    [FullRefresh]
    public virtual ContentReference Image { get; set; }

    [Display(
        GroupName = SystemTabNames.Content,
        Order = 30)]
    [CultureSpecific]
    [Searchable]
    public virtual XhtmlString Description { get; set; }

    [Display(
        GroupName = SystemTabNames.Content,
        Name = "Call To Action ",
        Order = 40)]
    [CultureSpecific]
    public virtual LinkItem CallToAction { get; set; }
}

To get it to work, I'm trying to find where it gets deserialized to add a null check, but I have no idea where that could be. I've also seen this same issue in other unsolved forum threads going back to 2015.

https://world.optimizely.com/forum/developer-forum/CMS/Thread-Container/2017/10/json-serialization-error-when-creating-a-link-item/

https://world.optimizely.com/forum/developer-forum/CMS/Thread-Container/2015/11/unable-to-publish-linkitemcollection-properties/

#300485
Apr 21, 2023 2:37
Vote:
 

The error is happening in EPiServer.Cms.Shell.UI.ObjectEditing.InternalMetadata.LinkModel.ToServerModel(object clientModel). If there isn't a LinkItem, clientModel is null, and therefore the line that reads if (linkModel.Attributes != null) will throw a System.NullReferenceException because linkModel is null. There needs to be a null-check after LinkModel linkModel = (LinkModel) clientModel;

Code in case the image doesn't work:

public virtual object ToServerModel(object clientModel)
{
  LinkModel linkModel = (LinkModel) clientModel;
  LinkItem serverModel = new LinkItem();  
  if (linkModel.Attributes != null)  // <!!-- This is where it throws the System.NullReferenceException
  {
    foreach (KeyValuePair<string, string> attribute in linkModel.Attributes)
      serverModel.Attributes.Add(attribute.Key, attribute.Value);
  }
  serverModel.Text = linkModel.Text;
  serverModel.Title = linkModel.Title;
  LinkItem linkItem = serverModel;
...
#300502
Edited, Apr 21, 2023 15:53
This topic was created over six months ago and has been resolved. If you have a similar question, please create a new topic and refer to this one.
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.