OrchardCMS / OrchardCore

Orchard Core is an open-source modular and multi-tenant application framework built with ASP.NET Core, and a content management system (CMS) built on top of that framework.
https://orchardcore.net
BSD 3-Clause "New" or "Revised" License
7.36k stars 2.37k forks source link

Layout not found from Razor Page with route #2385

Closed sebastienros closed 5 years ago

sebastienros commented 6 years ago

Repro: Create this

@page "{name}"

@functions {
public string Name {get;set;}
}
Hello

And in _ViewStart.cshtml

@ { Layout = "_Layout"; }

_Layout.cshtml is created in Views\Shared

Without the custom route, the layout is used.

jtkech commented 6 years ago

Okay, i will check it.

I'm working on other view locations related to #1921.

jtkech commented 6 years ago

@sebastienros


Just tried with the dev branch, it works on my side with or without the route.


What doesn't work is if you put in the _ViewStart : Layout = "../Views/Shared/_Layout.cshtml". When you put it in the page it works, the requested view path is still relative to the app's module virtual folder. But if you put it in the view start it fallbacks directly to /Views/Shared from the content root.

If i put this in the view start it works

Layout = "../.Modules/OrchardCore.Cms.Web/Views/Shared/_Layout.cshtml";

I think it's quite easy to fix this use case by also serving views when requested from the content root, not only through the app's module, but do you think it's worth to implement this case?

UPDATE i fixed this use case through #2386 (not yet committed). It was already working for other modules (not the application module).

psijkof commented 6 years ago

To make a Razor Pages App work as a module, I had to make the following changes:

Using version beta3-68457

Example: https://github.com/psijkof/OrchardCMSDecoupled/commit/f680e7fa63a9c55c30bab8587b991e433a712d0d

jtkech commented 6 years ago

@psijkof

When talking about the application as a module, it is the final application which is not a module but behaving as a module when defining things at the app level, controller / views, shapes, views, pages.

An OC app don't call directly things as .AddMvc(), AddRazorPages() ... This is done through helpers in the isolated context of each tenant (at least one named Default). So, normally app level mvc things can't work, this is why we made the app behave as a "module" whose virtual location is .Modules/ApplicationName.

After a quick look to your VanillaRazorPagesApp, is it an application? You use Sdk.Web, netcoreapp2.1, AspNetCore.App, but you reference Module.Targets. Oh yes, so you did an app and then you want to use it as a module in another application. Great idea, maybe to test it independently,

So this is not the same sense as described. Never tried, not sure it works with our msbuild scripts. Anyway, for the following tests, i assume that your app is in fact a regular module / theme.

Static files

/Pages/Shared

asp-page anchor tag helper attribute

jtkech commented 6 years ago

@sebastienros just for infos

For testing i renamed .Modules to Areas, it opens many possibilities. For pages under Areas razor remove Areas and Pages segments from pages names / paths without custom conventions. We could re-use the default page root directory /Pages so that razor don't want to watch them from the root /. I think we could also get rid or simplify some view location expanders and so on.

I read a little the RCL doc, their stategy is that e.g if a library defines pages under their Areas or Pages, they are respectively "imported" to the app Areas or Pages, where the app can override them. In my test this is what is defined in {module}/Pages which is "imported" in the app /Areas/{module}/Pages.

Maybe worth to discuss on this, but there would be many things to check.

psijkof commented 6 years ago

@jtkech Thanks for the explanation. I started with the App using Cms.Targets. But having a Razor Pages module, seemed more like I would actually use it. Starting with a default 'Web App' (dotnet default template) I showed what I had to change to make it work as a Module for OC. Perhaps all I should have referenced was Module.targets to avoid any confusion and still be able to use IOrchardHelper?

Anyway, great work. I know it's perhaps not feasible, but the less I should have to change to make a 'web app' work as a module, the better it would be, or at least as long it's clear why and what I need to change.

Thanks for the great work, hope soon we see the changes you made merged.

psijkof commented 5 years ago

Seems to work great in a razor pages module scenario. We're building the modern business theme with razor pages in repo If we however request for a non existent page, we get a image Exception: Shape type 'Layout' not found

Why is it trying to find the Layout in a ´404´ situation? What should we supply to create a decent (preferably styled according to the modern business template) error page?

Thanks for the insights!

psijkof commented 5 years ago

Follow up: We added a Layout.cshtml in the themes project, in location Pages\Shared. When a razor page from a module looks for 'Layout', that file is found and used. However, if a request comes for a non existent page, OC looks for a Layout shape in Views folder in the themes project. Would it be a good idea to display the 404 content in that file, or would that break the generic Standard CMS usage of OC (and therefore it wouldn't be a good idea)?

jtkech commented 5 years ago

So, i you have the cms with the theming engine and then a selected teme, normally you just have to use a regular layout which render the body, and then a not found page in the shared folder, as done in TheBlogTheme. So try to create and use this view YourTheme/Views/Shared/NotFound.cshtml

psijkof commented 5 years ago

@jtkech If I place the Layout.cshtml back in Views of the Theme project, I get a not found by the localization of the layout page for the razor pages

/Areas/ModernBusiness.Theme/Pages/ModernBusiness.Pages/Layout.cshtml
/Areas/ModernBusiness.Theme/Pages/Shared/Layout.cshtml
/Areas/ModernBusiness.Theme/Views/ModernBusiness.Pages/Shared/Layout.cshtml
/Areas/ModernBusiness.Theme/Views/Shared/Layout.cshtml

That's why I put it in Views\Shared.

But then, when I request a non existing page, OC complains it can't find the Layout shape (page)... It is looking in the theme project, Viewsfolder.

Also, putting the Layout.cshtml as is in Views, with RenderSection commands, it faults with the error:

An unhandled exception occurred while processing the request.
InvalidOperationException: RenderSection invocation in '/Areas/ModernBusiness.Theme/Views/Layout.cshtml' is invalid. RenderSection can only be called from a layout page.
Microsoft.AspNetCore.Mvc.Razor.RazorPage.EnsureMethodCanBeInvoked(string methodName)

Any idea what I should do here?

UPDATE: It's as if Layout in Views in the Themes project is not a real Layout view. Even if I make it as simple as

@RenderBody

It faults with: InvalidOperationException: RenderBody invocation in '/Areas/ModernBusiness.Theme/Views/Layout.cshtml' is invalid. RenderBody can only be called from a layout page.

UPDATE 2: If I supply a Layout.liquid with just a simple {% render_body %}, and a Views/Shared/NotFound.cshtml, things work like it should, I guess?

jtkech commented 5 years ago

Yes, with a RazorPages.Page you specify the layout, but when not found it acts as a "normal" themed Razor.RazorPage, that's why the layout is searched in the theme Views folder.

Then, i think you need to call our @await RenderBodyAsync(), not @RenderBody().