blipson89 / Synthesis

Synthesis is a universal object mapper for Sitecore
MIT License
75 stars 25 forks source link

Unpublished datasources results in exception in debug mode #96

Closed UffeHammer closed 3 years ago

UffeHammer commented 3 years ago

Hello

We have had a lot of issues when we copy the production database down to our development environment, the editors sometimes choose to unpublish datasource items which works fine in production but fails when we try to run it on develpoment sites. It turns out that unpublished datasources results in an exception in debug mode when using Synthesis.mvc library, if debug="false" in the web.config compilation setting then everything works as expected.

The error occurs in RenderingDisplayPathResolver.ResolveRenderingPath on this line: return rendering.Item.Paths.FullPath;

Adding a nullcheck fixes the problem: // Added custom check unpublished datasources if (rendering.Item == null) return null;

And removing the assertion in RenderingDiagnostics.RenderingDiagnostics(): //Assert.IsNotNull(renderingName, "Rendering name cannot be null");

Sitecore version is 9.3 and synthesis is 9.1.0.2

Here is the callstack: Exception: System.NullReferenceException Message: Object reference not set to an instance of an object. Source: Synthesis.Mvc at Synthesis.Mvc.RenderingDisplayPathResolver.ResolveRenderingPath(Rendering rendering) at Synthesis.Mvc.Pipelines.GetRenderer.RenderingDiagnosticsInjector.DiagnosticsRenderer.Render(TextWriter writer) at Sitecore.Mvc.Pipelines.Response.RenderRendering.ExecuteRenderer.Render(Renderer renderer, TextWriter writer, RenderRenderingArgs args)

6308 14:57:15 ERROR Error occurred rendering a view. Exception: System.NullReferenceException Message: Object reference not set to an instance of an object. Source: Synthesis.Mvc at Synthesis.Mvc.RenderingDisplayPathResolver.ResolveRenderingPath(Rendering rendering) at Synthesis.Mvc.Pipelines.GetRenderer.RenderingDiagnosticsInjector.DiagnosticsRenderer.Render(TextWriter writer) at Sitecore.Mvc.Pipelines.Response.RenderRendering.ExecuteRenderer.Render(Renderer renderer, TextWriter writer, RenderRenderingArgs args) at Sitecore.Mvc.Pipelines.Response.RenderRendering.ExecuteRenderer.Process(RenderRenderingArgs args) at Synthesis.Mvc.Pipelines.RenderRendering.ResilientExecuteRenderer.Process(RenderRenderingArgs args)

6308 14:57:15 ERROR Failed to render rendering Exception: System.InvalidOperationException Message: Error while rendering view: '/Views/Common/Sublayouts/Grids/1 Kolonne.cshtml' (model: 'Sitecore.Mvc.Presentation.RenderingModel, Sitecore.Mvc').

Source: Sitecore.Mvc at Sitecore.Mvc.Presentation.ViewRenderer.Render(TextWriter writer) at Synthesis.Mvc.Pipelines.GetRenderer.RenderingDiagnosticsInjector.DiagnosticsRenderer.Render(TextWriter writer) at Sitecore.Mvc.Pipelines.Response.RenderRendering.ExecuteRenderer.Render(Renderer renderer, TextWriter writer, RenderRenderingArgs args)

Nested Exception

Exception: System.NullReferenceException Message: Object reference not set to an instance of an object. Source: Synthesis.Mvc at Synthesis.Mvc.RenderingDisplayPathResolver.ResolveRenderingPath(Rendering rendering) at Synthesis.Mvc.Pipelines.RenderRendering.ResilientExecuteRenderer.CreateRenderingErrorModel(RenderRenderingArgs args, Exception ex) at Synthesis.Mvc.Pipelines.RenderRendering.ResilientExecuteRenderer.Process(RenderRenderingArgs args) at (Object , Object ) at Sitecore.Pipelines.CorePipeline.Run(PipelineArgs args) at Sitecore.Pipelines.DefaultCorePipelineManager.Run(String pipelineName, PipelineArgs args, String pipelineDomain, Boolean failIfNotExists) at Sitecore.Pipelines.DefaultCorePipelineManager.Run(String pipelineName, PipelineArgs args, String pipelineDomain) at Sitecore.Mvc.Pipelines.PipelineService.RunPipeline[TArgs](String pipelineName, TArgs args) at Sitecore.Mvc.Pipelines.Response.RenderPlaceholder.PerformRendering.Render(String placeholderName, TextWriter writer, RenderPlaceholderArgs args) at (Object , Object ) at Sitecore.Pipelines.CorePipeline.Run(PipelineArgs args) at Sitecore.Pipelines.DefaultCorePipelineManager.Run(String pipelineName, PipelineArgs args, String pipelineDomain, Boolean failIfNotExists) at Sitecore.Pipelines.DefaultCorePipelineManager.Run(String pipelineName, PipelineArgs args, String pipelineDomain) at Sitecore.Mvc.Pipelines.PipelineService.RunPipeline[TArgs](String pipelineName, TArgs args) at Sitecore.Mvc.Helpers.SitecoreHelper.RenderPlaceholderCore(String placeholderName, TextWriter writer) at Sitecore.Mvc.Helpers.SitecoreHelper.Placeholder(String placeholderName) at Sitecore.Mvc.Helpers.SitecoreHelper.DynamicPlaceholder(DynamicPlaceholderDefinition definition) at ASP._Page_Views_Common_Sublayouts_Grids_1_Kolonne_cshtml.Execute() in c:\udvikling\3fdk\www\cm\Views\Common\Sublayouts\Grids\1 Kolonne.cshtml:line 4 at System.Web.WebPages.WebPageBase.ExecutePageHierarchy() at System.Web.Mvc.WebViewPage.ExecutePageHierarchy() at System.Web.WebPages.WebPageBase.ExecutePageHierarchy(WebPageContext pageContext, TextWriter writer, WebPageRenderingBase startPage) at System.Web.Mvc.Html.PartialExtensions.Partial(HtmlHelper htmlHelper, String partialViewName, Object model, ViewDataDictionary viewData) at Sitecore.Mvc.Presentation.ViewRenderer.Render(TextWriter writer)

blipson89 commented 3 years ago

the idea behind this is to avoid breaking the live site, but make the error obvious to developers for troubleshooting.

That said, I agree - this should be handled more gracefully than a yellow screen. I'll try to reproduce this on my end and get a fix in.

blipson89 commented 3 years ago

hi @UffeHammer - I'm having trouble reproducing this issue. I see the stack trace you're referring to, but it was only present in the logs. The page loaded without errors.

Synthesis replaces the Sitecore mvc renderer with the ResilientExecuteRenderer. This renderer prevents the entire page from crashing if there's a model issue. Has that been disabled on your site?

Please take a peek at my test cases below and let me know if there's a case that's missing and I can try that.


Test Case 1:

  1. set <compilation debug="true">
  2. Have a page that has a View Rendering on it with a datasource
  3. ensure the page loads fine
  4. delete the data source item from the web db, but leave the link there (so it's a broken link)
  5. test page again

Result:

  1. Page loads fine, but the rendering is missing some information in it
  2. the errors you mentioned above are present, but only in the logs.

Test Case 2:

  1. set <compilation debug="true">
  2. Have a page that has a View Rendering on it with a datasource
  3. ensure the page loads fine
  4. delete the data source item from the web db and remove the link (so the datasource field is blank)
  5. test page again

Result:

  1. Page loads fine, but the rendering is missing some information in it
  2. the errors you mentioned above are present, but only in the logs.

Test Case 3:

  1. set <compilation debug="true">
  2. Have a page that has a Controller Rendering on it with a datasource
  3. ensure the page loads fine
  4. delete the data source item from the web db, but leave the link there (so it's a broken link)
  5. test page again

Result:

  1. Page yellow screens
  2. however, when I put <compilation debug="false"> the page also yellow screened.
UffeHammer commented 3 years ago

Ours is test case 3 we use Controller renderings in most places.

When i set <compilation debug="false"> the problem disappears, but we are also running debug version of our code on the developer site, maybe that makes a difference?

Also i set the "Do not publish element" property on the datasource and publish the change, but the result should be identical to manually deleting the element in the web database.

blipson89 commented 3 years ago

Can you please check to see if the ResilientExecuteRenderer is present in the ShowConfig? If it was disabled, that could explain some things.

UffeHammer commented 3 years ago

It is present in the mvc.renderRendering section:

<mvc.renderRendering patch:source="Sitecore.Mvc.config">
<processor type="Sitecore.Mvc.Pipelines.Response.RenderRendering.InitializeProfiling, Sitecore.Mvc"/>
<processor type="Sitecore.Mvc.Pipelines.Response.RenderRendering.StartStatisticRecording, Sitecore.Mvc"/>
<processor type="Sitecore.Mvc.Pipelines.Response.RenderRendering.ResolveArea, Sitecore.Mvc">
<param type="Sitecore.Mvc.Pipelines.Response.RenderRendering.ChainedAreaResolveStrategy, Sitecore.Mvc" desc="areaResolver">
<Resolvers hint="list">
<resolver type="Sitecore.Mvc.Pipelines.Response.RenderRendering.RenderingDefinitionAreaResolveStrategy, Sitecore.Mvc"/>
<resolver type="Sitecore.Mvc.Pipelines.Response.RenderRendering.RenderingParametersAreaResolveStrategy, Sitecore.Mvc"/>
<resolver type="Sitecore.Mvc.Pipelines.Response.RenderRendering.RenderingLayoutAreaResolveStrategy, Sitecore.Mvc"/>
</Resolvers>
</param>
<param type="Sitecore.Mvc.AreaNamespaceRegistry, Sitecore.Mvc" desc="areaNamespaceRegistry"/>
</processor>
<processor type="Sitecore.Mvc.Pipelines.Response.RenderRendering.SetCacheability, Sitecore.Mvc"/>
<processor type="Sitecore.Mvc.Pipelines.Response.RenderRendering.EnterRenderingContext, Sitecore.Mvc"/>
<processor type="Foundation.Security.Pipelines.Renderings.GenerateCacheKey, Foundation.Security" resolve="true" patch:source="Foundation.Security.config"/>
<processor type="Sitecore.Mvc.Pipelines.Response.RenderRendering.RenderFromCache, Sitecore.Mvc" resolve="true"/>
<processor type="Sitecore.Mvc.Pipelines.Response.RenderRendering.StartRecordingOutput, Sitecore.Mvc"/>
<processor type="Sitecore.Mvc.ExperienceEditor.Pipelines.Response.RenderRendering.AddWrapper, Sitecore.Mvc.ExperienceEditor" resolve="true" patch:source="Sitecore.MVC.ExperienceEditor.config"/>
<!--  Prevents an invalid model type from crashing the whole page  -->
<processor type="Synthesis.Mvc.Pipelines.RenderRendering.ResilientExecuteRenderer, Synthesis.Mvc" patch:source="Synthesis.Mvc.config"/>
<processor type="Sitecore.Mvc.Pipelines.Response.RenderRendering.AddRecordedHtmlToCache, Sitecore.Mvc"/>
<processor type="Sitecore.Mvc.Pipelines.Response.RenderRendering.RecordStatistic, Sitecore.Mvc"/>
</mvc.renderRendering>
<mvc.getRenderer patch:source="Sitecore.Mvc.config">
blipson89 commented 3 years ago

@UffeHammer here's where I'm a bit puzzled:

public virtual string ResolveRenderingPath(Rendering rendering)
{
    var renderer = rendering.Renderer;

    var diags = renderer as RenderingDiagnosticsInjector.DiagnosticsRenderer;
    if (diags != null) renderer = diags.InnerRenderer;

    var view = renderer as ViewRenderer;
    if (view != null) return view.ViewPath;

    var controller = renderer as ControllerRenderer;
    if (controller != null) return controller.ControllerName + "::" + controller.ActionName;

    var method = renderer as MethodRenderer;
    if (method != null) return method.TypeName + "." + method.MethodName + "()";

    return rendering.Item.Paths.FullPath;
}

You wrote that adding a null check on rendering.Item fixed it. For it to get to that point, it means the failing rendering wasn't a View Rendering, Controller Rendering, or Method Rendering.

Could you please go to the rendering item itself and just double check that the template type is actually Controller Rendering? image

The resilient renderer should be handling the error gracefully and letting the page load. I can add the null check you mentioned and remove the Assert, but I want to make sure that the underlying issue is accounted for. If there's a test case I'm missing, I want to ensure I'm actually fixing the problem and not just covering it up 🙂

UffeHammer commented 3 years ago

I just confirmed that it is a controller rendering that fails when datasource is unpublished.

However i also noticed that if i try to recreate the problem manually in development environment, then it turns out that i cannot reproduce the problem by unpublishing an item, even though it is unpublished with exactly the same parameters as those coming from the production environment. So the data from production are somehow different from the one generated in development, even though the only general difference should be that production data has debug=false and development has debug=true.

blipson89 commented 3 years ago

Well, that probably explains why I'm having such a hard time reproducing it.

I'm going to do a release later this week which will have a way of suppressing the error. It'll also contain some better logging in this area which may help figure out how this can happen in the first place.

blipson89 commented 3 years ago

Hi @UffeHammer - sorry for the delay. I had a family emergency and I'm just getting back to things.

Attached are updated NuGet packages for Synthesis.Mvc.Core. These packages were built in Debug mode and should be backwards compatible with Synthesis 9.1.0.2. The .symbols package contains the .pdb in case you need to use the debugger. These packages are beta so you'll likely need to check "Include Prerelease" in the NuGet Manager. Alternatively, you can just open them up with 7zip and extract the dll

In a config patch file, add <setting name="Synthesis.RenderingDisplayPathResolver.SuppressNullItemException" value="true" /> to the <settings>...</settings> element. This should fix your issue. Since I can't reproduce this on my own, I have not been able to test it.

Please also note that I added additional logging. Next time you're testing this scenario, please check the Sitecore logs and look for any warnings that have [ResolveRenderingPath] in the message. Please let me know if you see any of those in the logs, and what the messages said. These warnings will pop up even if you don't see the yellow screen.

Please let me know if this fixes the issue, and then I'll publish the changes live.

9.1.3-beta.1.zip

UffeHammer commented 3 years ago

I can confirm that your fix is working as expected, however i cannot se why you would not want to have the fix enabled all the time?

Here is the log result: 4348 15:39:02 WARN [ResolveRenderingPath] Unhandled renderer passed to ResolveRenderingPath: Sitecore.Mvc.Analytics.Presentation.EmptyRenderer 4348 15:39:02 WARN [ResolveRenderingPath] Unhandled renderer passed to ResolveRenderingPath: Sitecore.Mvc.Analytics.Presentation.EmptyRenderer 4348 15:39:02 WARN [ResolveRenderingPath] Unhandled renderer passed to ResolveRenderingPath: Sitecore.Mvc.Analytics.Presentation.EmptyRenderer 4348 15:39:02 WARN [ResolveRenderingPath] Unhandled renderer passed to ResolveRenderingPath: Sitecore.Mvc.Analytics.Presentation.EmptyRenderer 4348 15:39:02 WARN [ResolveRenderingPath] Unhandled renderer passed to ResolveRenderingPath: Sitecore.Mvc.Analytics.Presentation.EmptyRenderer 4348 15:39:02 WARN [ResolveRenderingPath] Unhandled renderer passed to ResolveRenderingPath: Sitecore.Mvc.Analytics.Presentation.EmptyRenderer 4348 15:39:02 ERROR [ResolveRenderingPath] Rendering item is null 4348 15:39:02 WARN [ResolveRenderingPath] Unhandled renderer passed to ResolveRenderingPath: Sitecore.Mvc.Analytics.Presentation.EmptyRenderer

blipson89 commented 3 years ago

Fixed in Release 9.1.4. You no longer need that Synthesis setting, I made it the default.