Closed ItaloFSS closed 5 months ago
Apparently we are missing it. You can still render something without a theme manually in the meantime:
// Build shape
var displayManager = serviceProvider.GetRequiredService<IContentItemDisplayManager>();
var updateModelAccessor = serviceProvider.GetRequiredService<IUpdateModelAccessor>();
var model = await displayManager.BuildDisplayAsync(context.Source, updateModelAccessor.ModelUpdater);
var displayHelper = serviceProvider.GetRequiredService<IDisplayHelper>();
var htmlEncoder = serviceProvider.GetRequiredService<HtmlEncoder>();
using (var sb = StringBuilderPool.GetInstance())
{
using (var sw = new StringWriter(sb.Builder))
{
var htmlContent = await displayHelper.ShapeExecuteAsync(model);
htmlContent.WriteTo(sw, htmlEncoder);
await sw.FlushAsync();
return sw.ToString();
}
}
var model = await displayManager.BuildDisplayAsync(context.Source, updateModelAccessor.ModelUpdater);
BuildDisplay uses theme layout to inject zones defined in shape.
@ItaloFSS right, as @ns8482e mentions, don't use BuildDisplay
from my sample, just use the shape you created.
Was this implemented in 1.0?
/cc @netwavebe
Also wondering what it would mean to not use any theme, since shapes can only render things into zones. From an API point of view we could just render every piece on a custom property of json:
{
"content": "<p>Hello</p>",
"footer": "<script>...</script>",
"meta": "<link .../>"
}
Even if you just want the content zone, "Content"
is just a convention between the Layout and Placement from drivers. So I could imagine an method to take the zone name into account, or just return all the zones in a Dictionary, the same way I was suggesting the Json API.
Makes sense to be able to retrieve every rendered shape individually if you are doing a headless website. I remember getting a request for this by the guys from Air Whistle Media. Generally, when you create a headless website you don't want to retrieve the entire rendered page but only some parts. In this case, you want to use the flowpart
to build the page content and retrieve its rendered HTML from the GraphQL endpoint for example. The only solution right now is to get the entire rendered page which doesn't mix well with an Angular app that already has its own Layout and else. The idea is to keep using the Orchard admin to create content and to render it inside a SPA zone.
@sebastienros I agree there are complex scenario's, but that's not always the case. In Orchard Dashboard I have a liquid template that renders the summary of a product (Design > Templates > Content_Summary__Product
). The template has no scripts and there's no need for zone/placement stuff. A product gets rendered to a simpel html fragment, that's it.
I've used this to render some featured products on the homepage, works perfect.
Now I want to use this template in Angular (like @Skrypt's example). I have an API controller that gets a list of products. I can do two things there:
I did the first option on https://t-shirtskempen.be/bestellen. This has several downsides: there's a lot a databinding going on in Angular, you would need to maintain two version of the same 'view', etc...
So for a new project I was looking at option two, but there seems to be no way to achieve this currently?
@netwavebe Have you tried with creating Views/Shared/_ViewStart.cshtml
with Layout = null ?.
This should work but I think he wants to still have a Layout for the "Full CMS" website. So, we need an option to remove the Layout explicitly when rendering the shape with the GraphQL endpoint.
@netwavebe I haven't fully followed the issue, but Seb said you were concerned about the site layout?
You can inject the layout accessor in your api controller, and add an alternate to it, alternate would be an different layout (largley empty)
Maybe some ideas around the LayoutAccessor to allow this scenario, like disabling, or setting a custom one. Actually, what if the Layout was resolved in the API, then altered before the BuildDisplay is called?
@ns8482e Yes, but @Skrypt is right: 90% of the code depends on theming. It's just the ApiController where I don't want that.
Come to think of it... If a controller is named AdminController
, it will render the Admin theme instead of the frontend theme. What mechanisme is driving that behaviour?
@deanmarcussen Could you share some code on how to add an alternate with a different layout?
Come to think of it... If a controller is named
AdminController
, it will render the Admin theme instead of the frontend theme. What mechanisme is driving that behaviour?
It's theme selector that implements IThemeSelector
Sure remind me tomorrow on gitter. Also useful is a PartialViewResult. I use that to dynamically fetch and then compile as vue components sometimes
I think it would be fair to have some generic method to support that instead of requiring to create custom API endpoints.
It's not a problem If using controller View - as you can set Layout or ThemeLayout or ViewLayout
before shape execute. Issue is with API - when shape execution creates a fake actioncontext
Theme can be removed by Creating filter something like below
public class NoThemeFilter : IAsyncViewActionFilter
{
public Task OnActionExecutionAsync(ActionContext context)
{
var razorViewFeature = context.HttpContext.Features.Get<RazorViewFeature>();
// Add your filter condition here
if (razorViewFeature?.Theme != null)
{
razorViewFeature.Theme = null;
}
return Task.CompletedTask;
}
}
and in startup
services.AddScoped<IAsyncViewActionFilter, NoThemeFilter>();
Notice that we are not adding the filter to MvcOptions
@ns8482e I registered your example in startup without a filter condition, so I would expect theming to be gone everywhere but that's not the case. Breakpoints in NoThemeFilter
are not hit. How do I 'apply' this filter?
it should be called by DisplayManagement - when you render a shape https://github.com/OrchardCMS/OrchardCore/blob/339d74b251b53393dd510f8ad3027aa17d78500d/src/OrchardCore/OrchardCore.DisplayManagement/Razor/RazorShapeTemplateViewEngine.cs#L157-L162
@netwavebe try adding services.Configure<MvcOptions>(c => c.Filters.Add<NoThemeFilter>())
I guess you still need to use Theme to identify which shape binding to render, so don't set theme to null, instead set ThemeLayout to null as suggested by @sebastienros
if (razorViewFeature?.ThemeLayout != null)
{
razorViewFeature.ThemeLayout = null;
}
Mmmm, it seems it's working correctly IF there is a custom template... Here's what I did/tried...
This is a part of my code:
public async Task<IEnumerable<string>> RenderProducts(IEnumerable<ContentItem> products)
{
var renderedProducts = new List<string>();
foreach (var product in products)
{
var productShape = await _contentItemDisplayManager.BuildDisplayAsync(product, _updateModelAccessor.ModelUpdater, "Summary");
var renderedProduct = await _displayHelper.ShapeExecuteAsync(productShape);
using (var stringBuilder = StringBuilderPool.GetInstance())
{
using (var stringWriter = new StringWriter(stringBuilder.Builder))
{
renderedProduct.WriteTo(stringWriter, HtmlEncoder.Default);
await stringWriter.FlushAsync();
renderedProducts.Add(stringWriter.ToString());
}
}
}
return renderedProducts;
}
I'm calling this method in an API controller with a bunch of product content items. The result is send in JSON to an Angular client.
This is what Angular receives:
First = "Summary" view of a product, embedded in the theme Second = "Summary" view of a product (no theming applied)
This changes when creating a custom template:
This is what Angular receives with the above template in place:
This is exactly what I want. I'm not sure why this happens? Why does it render theming for the "default" template, but not for my custom template? Not that I'm complaining... 😉
I assume the default template is a Razor file?
yes -
but would it mean that if the Blog theme for instance was built in Razor, and we were to render the list of Blog Posts with Summary, then it would render the layout for each? That seems odd. I will have to debug it to understand how it works again.
You are correct, I was testing in blog theme
@sebastienros @ns8482e Almost, it will not render the theme for each, only for the first. I tried this in my project: removed the liquid template and placed a view Content-Product.Summary.cshtml
in my module. This is what Angular gets:
When I rename the view to Content-Product.Summary.liquid
the results are the same:
So cshtml or liquid makes no difference. But when I create a template in the UI (Design > Templates), I get this:
For the record, I'm running on OC 1.0.
Exactly the same issue here.
The rendered shape is embedded within the theme, the second, third etc. are without surrounding theme as expected.
Any ideas how to solve this?
@dpomt Create a template via the admin dashboard and it will work correctly.
It seems to me that the questions were discussed. If there are concrete improvement suggestions, please open issues for them.
I'm trying to render a shape in a controller action without the website theme. In orchard 1, there was a Theme attribute that did this. Is there a way in core?