Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation notes for decoupled CMS #3553

Open
dodyg opened this issue May 1, 2019 · 52 comments

Comments

Projects
None yet
7 participants
@dodyg
Copy link

commented May 1, 2019

I am going to write a running commentary and notes for a decoupled CMS website implementation so it can be a reference for official documentation/guide.

I am using

  <PackageReference Include="OrchardCore.Application.Cms.Targets" Version="1.0.0-beta3-71077" />

The website I am building contains:

  • Home Page
  • A blog
  • A contact us form
  • A client listing page
  • A generic single page

I am not using any themes in this decoupled CMS implementation because it's not really obvious how to use it or whether it is beneficial at all.

I prefer the decoupled CMS approach at this moment because as a developer, using the traditional approach requires higher learning curve than using decoupled approach.

There are things like Zone, Placement or whatnot that are simply not obvious and I assume necessary when using traditional approach. Using decoupled CMS, I just need to worry about getting the content and I can put them exactly where I want.

@dodyg

This comment has been minimized.

Copy link
Author

commented May 1, 2019

It's important to inherit @inherits OrchardCore.DisplayManagement.Razor.RazorPage<TModel> in all your cshtml shape templates.

shape

Note: I am not sure whether the CMS will pick up the shape templates anywhere or it has to be under /Views.

@dodyg

This comment has been minimized.

Copy link
Author

commented May 1, 2019

The concept of Shape and Shape Templates needs better discussion in the current documentation. All the shape discussion is based on Orchard Core 1.0 documentation.

@dodyg

This comment has been minimized.

Copy link
Author

commented May 1, 2019

Do not precompile your Razor otherwise your Shape templates won't work

This is how to prevent Razor precompilation

  <PropertyGroup>
    <TargetFramework>netcoreapp2.2</TargetFramework>
    <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
    <RazorCompileOnPublish>false</RazorCompileOnPublish>
  </PropertyGroup>

shapes-folder

The Views folder contains the Shapes templates. If you precompile your Razor views, this 'Views' folder won't be copied over and your Shapes rendering customization won't work.

Example
When Shape templates are available
shapes-output-1

When Shape templates are missing because the Views folder isn't at the published folder
shapes-output-2

@dodyg

This comment has been minimized.

Copy link
Author

commented May 1, 2019

SQL Queries module is kinda useless in comparison to Lucene Queries module because the thing that you can sort and filter in SQL Queries is very limited. You can't for example filter/sort by your Content Types field in SQL Queries. That just makes it a deal breaker. Lucene Queries doesn't have this limitation.

@dodyg

This comment has been minimized.

Copy link
Author

commented May 1, 2019

All you need is love and

private readonly OrchardCore.IOrchardHelper _helper;
private readonly OrchardCore.Queries.IQueryManager _queryManager;

or @inject them at your views.

These two are pretty much all you need to implement decoupled CMS AFAIK.

@dodyg

This comment has been minimized.

Copy link
Author

commented May 1, 2019

Get by Alias

await _helper.GetContentItemByAliasAsync("alias:blog");

Get by Slug

await _helper.GetContentItemByAliasAsync("slug:blog/my-awesome-content");
@dodyg

This comment has been minimized.

Copy link
Author

commented May 1, 2019

Taxonomy module sounds cool but I have no idea how to use it from the Admin or how it relates to content.

OK this article is really useful: https://www.davidhayden.me/blog/taxonomies-in-orchard-core-cms

@dodyg

This comment has been minimized.

Copy link
Author

commented May 1, 2019

I wish there's an option to modify the /admin path for OrchardCore admin UI.

@dodyg

This comment has been minimized.

Copy link
Author

commented May 1, 2019

Admin Menu should be enabled by default in an empty installation. It's super useful and it's not obvious for people implementing OrchardCore for the first time.

@dodyg

This comment has been minimized.

Copy link
Author

commented May 1, 2019

Content Types and Content Parts need discussions at the documentation. Right now people have to rely on Orchard 1 documentation.

This whole Orchard Core basic concepts from v1 need to be migrated and updated.

@dodyg

This comment has been minimized.

Copy link
Author

commented May 1, 2019

I still can't find a way how to obtain the site name and display it in Razor page.

@agriffard

This comment has been minimized.

Copy link
Member

commented May 1, 2019

@inject OrchardCore.Settings.ISiteService SiteService

string siteName = (await SiteService.GetSiteSettingsAsync()).SiteName;

@dodyg

This comment has been minimized.

Copy link
Author

commented May 1, 2019

It would be super nice if there's a version of _helper.GetContentItemByAliasAsync; that takes multiple aliases.

@dodyg

This comment has been minimized.

Copy link
Author

commented May 1, 2019

Who would have thought that the humble TextField has awesome list of editors!

predefined-list

@dodyg

This comment has been minimized.

Copy link
Author

commented May 1, 2019

@agriffard how does one load Widget in decoupled CMS? Just this _helper.GetContentItemByIdAsync?

@dodyg

This comment has been minimized.

Copy link
Author

commented May 1, 2019

Once some keys concepts are understood, developing decoupled CMS with OrchardCore is a very pleasant experience.

@dodyg

This comment has been minimized.

Copy link
Author

commented May 1, 2019

display-json-model

This JSON rendering of a Content Item is just so invaluable. In this case this is a taxonomy content item. @Model.Topics.Content.

@sebastienros

This comment has been minimized.

Copy link
Member

commented May 2, 2019

It's important to inherit @inherits OrchardCore.DisplayManagement.Razor.RazorPage in all your cshtml shape templates.

No. It has lots of helpers but you don't need them if you are not using a theme usually. You just need to inject @inject OrchardCore.IOrchardHelper OrchardHelper and you have access to most of the things you need in a decoupled CMS.

Do not precompile your Razor otherwise your Shape templates won't work

You don't need shapes either. You should precompile your views.

SQL Queries module is kinda useless in comparison to Lucene Queries

There is a PR that provides it already, I will blame @Skrypt if it's not ready yet

I still can't find a way how to obtain the site name and display it in Razor page.

We definitely need to add an helper to IOrchardHelper for this

how does one load Widget in decoupled CMS

My advice is to create a "Section" content type that is based on FlowPart and you give it an alias. then you can load it and render it. You can also just use it as a data structure and render it by yourself.

json

Yes, we need helpers to render a json tree that can be navigated in html directly

something like this: https://www.jqueryscript.net/other/Beautiful-JSON-Viewer-Editor.html
But we should do it in the admin first, simpler and would solve most issues

@dodyg

This comment has been minimized.

Copy link
Author

commented May 3, 2019

Helper.QueryCategorizedContentItemsAsync usage isn't yet documented.

 await _helper.QueryCategorizedContentItemsAsync(query =>
            {
               return query.Where(x => x.TermContentItemId == contentItemId);
            });
@dodyg

This comment has been minimized.

Copy link
Author

commented May 3, 2019

This sample from the documentation isn't clear.

@foreach(var termId in Model.TermContentItemIds)
{
    @await OrchardCore.GetTaxonomyTermAsync(Model.TaxonomyContentItemId, termId);
}
@dodyg

This comment has been minimized.

Copy link
Author

commented May 3, 2019

It will be much user friendly if ContentItemId is visible in the UI.

It can be much smaller in contrast of the other UI element. Right now we have to fish it out of the url.

content-item-id

@LatinaAtanasova

This comment has been minimized.

Copy link

commented May 3, 2019

Hello, I'm working on similar website using decoupled CMS. I also came across with missing information on this part. There is a very nice video with Sebastien but it's just one :). Now I'm facing problems with contact form and sending e-mail. If i make it as widget, how will i insert it in my ready html. On the other side, when i start to write my logic in back-end, I should struggle with workflows and their usage. It will be nice if you share some experience :)

@dodyg

This comment has been minimized.

Copy link
Author

commented May 3, 2019

@LatinaAtanasova I have not started to touch the form and sending email although that's coming. I will let you know when I figure it out. As you say, it's not very obvious.

@dodyg

This comment has been minimized.

Copy link
Author

commented May 4, 2019

I tried precompilation but orchard core does not pick up the Shape templates that was located at Views folder. Now I have to copy the folder manually over.

@sebastienros

This comment has been minimized.

Copy link
Member

commented May 4, 2019

If you are using the .Web sdk then it's automatic. All our module work like this.

@LatinaAtanasova

This comment has been minimized.

Copy link

commented May 7, 2019

@dodyg so far i managed to use

functionality but i had to implement theme. I couldn't get without it. I used "The Theme" where i modified html according to my html just for that part. I kept all other html in Pages in Web Project. As to sending e-mails from contact form. I did it but with work around - wrote my own service and take smtp and recaptcha credentials from administration . I'm afraid that this is not the idea and i don't like it that way but so far...

@dodyg

This comment has been minimized.

Copy link
Author

commented May 7, 2019

@LatinaAtanasova yeah I don't know how to render the form yet on decoupled CMS.

@dodyg

This comment has been minimized.

Copy link
Author

commented May 7, 2019

@sebastienros I already use .Web SDK.

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.2</TargetFramework>
    <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
  </PropertyGroup>


  <ItemGroup>
    <PackageReference Include="Markdig" Version="0.16.0" />
    <PackageReference Include="Microsoft.AspNetCore.App" />
    <PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All" />
    <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.2.3" />
    <PackageReference Include="OrchardCore.Application.Cms.Targets" Version="1.0.0-beta3-71077" />
  </ItemGroup>


  <ItemGroup>
    <Content Update="wwwroot\css\site.css">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Content>
  </ItemGroup>

</Project>

@LatinaAtanasova

This comment has been minimized.

Copy link

commented May 7, 2019

me too :)

@jtkech

This comment has been minimized.

Copy link
Member

commented May 7, 2019

Yes, at the app level that's ok for pure mvc views but shape views need to be outputed on publishing.

This is because the shape discovering relies on the existing file providers, a shape view needs to be discovered at runtime even the related precompiled view will be used.

It works for modules because we also embed view contents in their assemblies, so shape views are found through our embedded file provider.

Right now, you need to output app level shape views (razor and liquid) on publishing.

@LatinaAtanasova

This comment has been minimized.

Copy link

commented May 8, 2019

Hello again, I need help on my project :) I have a few pages:

Web

and in order to use menu functionality i included "The Theme" with restyling the menu html.

Theme

I try to understand and implement one simple widget. I created an alert message as follows:

Layer Messages
Zones

My idea is to visualize that alert in zone name="Messages" which is in Contacts Page. (Next step will be to have it as success or warning, etc.)
I try it with

<zone name="Messages">
          @await DisplayAsync(Model.Content);
</zone>

but DisplayAsync is not reachable. I know that it can be used if page inherits OrchardCore.DisplayManagement.Razor.RazorPage but than i get the following error
"The name 'PageContext' does not exist in the current context" when trying to reach that page. If i remove "@page" at the start of "Contacts"page "await DisplayAsync" can be used, but page is than not reachable and i get "The page cannot be displayed".
Obviously I am missing something. I am really new to Orchard but I want to understand and use it.
My main question is what is the best approach to build a web side with ready html pages? I tried with replacing existing theme, but it was kind of mess :)
I need to have contact page, user registrations, some items and mapping them to users. I'll appreciate every advice :)

@dodyg

This comment has been minimized.

Copy link
Author

commented May 8, 2019

I need to have contact page, user registrations, some items and mapping them to users. I'll appreciate every advice :)

Right now my approach is to build the contact page form in razor as normal then submit the value to a workflow end point. I haven't completed the process yet. I will update when I am done.

Note: I just found out zone tag helper exists. I will research this further today and see if I can find a solution to the problem you encounter.

@LatinaAtanasova

This comment has been minimized.

Copy link

commented May 8, 2019

Thanks :) It will be great if you can share your experience with workflow. I also made one, but couldn't submit the value to it. I'm missing something with the transition between admin and back-end.

@dodyg

This comment has been minimized.

Copy link
Author

commented May 8, 2019

Make sure you enable HTTP Workflows Activities module

@dodyg

This comment has been minimized.

Copy link
Author

commented May 9, 2019

I screwed up a workflow and now my Content Item listing is broken.
#3593

@dodyg

This comment has been minimized.

Copy link
Author

commented May 9, 2019

@LatinaAtanasova,

This is a good guide for workflow except that it's a bit out of date in the liquid part.

This contains the latest up to date reference on what is available for Liquid.

My workflow is this.

workflow-1

You need to create:

  • A HTTP Request Event, set it to Post and mark it as a startup
  • Set a property Task. This one I call it "Contact". It just have this value JSON.parse(readBody())
  • I set a correlation Task just as a test. The value here is random.
  • I add a Create Content Task and set it to ContactUs content type (I've created it previously). You can see the script below.

workflow-2

In my ContactUs form, I just serialize a json payload and post it to the URL set by the HTTP Request event.

contact-us

So now when I submit my contact us form, a new content will be added in OrchardCore.

@LatinaAtanasova

This comment has been minimized.

Copy link

commented May 10, 2019

Thank you. I'll try it. By the way the following solution is nice and useful. I don't know if you have come across it.
https://github.com/psijkof/ModernBusiness.OC.RazorPages

@dodyg

This comment has been minimized.

Copy link
Author

commented May 10, 2019

Good find. Let me review that.

@dodyg

This comment has been minimized.

Copy link
Author

commented May 11, 2019

This is an important information regarding media resize #3474 (comment).

You cannot resize image outside these values.

To override the default values, do this in your startup.cs

    MediaFileProvider.DefaultSizes = new[] { 16, 32, 50, 100, 160, 240, 480, 576, 600, 1024, 2048 };

The default values are very limiting because it doesn't allow you to do the ratio of still photography https://en.wikipedia.org/wiki/Aspect_ratio_%28image%29#Still_photography

@dodyg

This comment has been minimized.

Copy link
Author

commented May 11, 2019

Lucene Queries seems to have problem with sorting #3278. Bummer.

@dodyg

This comment has been minimized.

Copy link
Author

commented May 15, 2019

Or use SupportedSizes configuration to provide custom supported resizing for the media module 8da9513

@sebastienros

This comment has been minimized.

Copy link
Member

commented May 16, 2019

We have an issue already and a design to support "any" size also.

@juniordev19

This comment has been minimized.

Copy link

commented May 17, 2019

All you need is love and

private readonly OrchardCore.IOrchardHelper _helper;
private readonly OrchardCore.Queries.IQueryManager _queryManager;

or @inject them at your views.

These two are pretty much all you need to implement decoupled CMS AFAIK.

Could you give an example of how to use these objects to execute a query in the controller and send the result of the query to the view?

@dodyg

This comment has been minimized.

Copy link
Author

commented May 20, 2019

This will get you going (Razor Page)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using OrchardCore.ContentManagement;

namespace Silverkey.Website.Pages
{
    public class GeneralPageModel : PageModel
    {
        private readonly OrchardCore.IOrchardHelper _helper;
        private readonly OrchardCore.Queries.IQueryManager _queryManager;

        public ContentItem PageContent { get; set; }

        public GeneralPageModel(OrchardCore.IOrchardHelper helper, OrchardCore.Queries.IQueryManager QueryManager)
        {
            _helper = helper;
            _queryManager = QueryManager;
        }

        public async Task<ActionResult> OnGetAsync(string alias)
        {
            if (string.IsNullOrEmpty(alias))
                return NotFound();

            var content = await _helper.GetContentItemByAliasAsync($"alias:{alias}");

            if (content is null)
                return NotFound();

            PageContent = content;

            return Page();
        }
    }
}

View

@page "/p/{*alias}"
@model Silverkey.Website.Pages.GeneralPageModel
@{
    ViewData[Fmt.Title] = Model.PageContent.DisplayText;
    bool sideSectionExists = Model.PageContent.ContentItem.Content.SideSections.ContentItems.Count > 0;

    var mainSectionSize = sideSectionExists ? "is-two-thirds" : Model.PageContent.ContentItem.Content.SinglePage.ColumnCssClass.Text;
}

@foreach (var pitch in Model.PageContent.ContentItem.Content.Pitch.ContentItems)
{
    <section class="hero is-fullheight-with-navbar">
        <div class="hero-body has-background-grey-lighter">
            <div class="container">
                <div class="columns is-centered">
                    <div class="column is-half" style="text-align:center;">
                        <h1 class="title is-1">@pitch.TitlePart.Title</h1>
                        <div class="content">
                            @Fmt.Markdown((string)pitch.MarkdownBodyPart.Markdown)
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </section>
}

<main class="section">
    <div class="container">
        <div class="columns">
            <div class="column @mainSectionSize">
                <main>
                    <h1 class="title is-1">@Model.PageContent.ContentItem.Content.TitlePart.Title</h1>
                    <div class="content">
                        @Fmt.Markdown((string)Model.PageContent.ContentItem.Content.MarkdownBodyPart.Markdown)
                    </div>

                    <div class="content">
                        @foreach (var part in Model.PageContent.ContentItem.Content.MainSections.ContentItems)
                        {
                            <h2>@part.DisplayText</h2>
                            if (part.ContentType == "Columns")
                            {
                                <div class="columns">
                                    @foreach (var column in part.BagPart.ContentItems)
                                    {
                                        <div class="column">
                                            <h3>@column.DisplayText</h3>
                                            @Fmt.Markdown((string)column.MarkdownBodyPart.Markdown)
                                        </div>
                                    }
                                </div>
                            }
                        }
                    </div>
                </main>
            </div>
            @if (sideSectionExists)
            {
                <div class="column">
                    @foreach (var part in Model.PageContent.ContentItem.Content.SideSections.ContentItems)
                    {
                        <div class="card">
                            <div class="card-header">
                                <p class="card-header-title is-centered">
                                    @part.TitlePart.Title
                                </p>
                            </div>
                            <div class="card-content">
                                <div class="content">
                                    @Fmt.Markdown((string)part.MarkdownBodyPart.Markdown)
                                </div>
                            </div>
                        </div>
                    }
                </div>
            }
        </div>
    </div>
</main>

@foreach (var part in Model.PageContent.ContentItem.Content.BottomSections.ContentItems)
{
    <section class="section">
        <div class="container">
            <div class="columns">
                <div class="column">
                    <div class="content">
                        @part.TitlePart.Title
                        @Fmt.Markdown((string)part.MarkdownBodyPart.Markdown)
                    </div>
                </div>
                <div class="column">
                </div>
            </div>
        </div>
    </section>
}

Note @Fmt.Markdown is my own method.

If you want to see what properties are available for you to access, do @Model.PageContent.Content and it will show you all the structure in Json that you can access dynamically.

@juniordev19

This comment has been minimized.

Copy link

commented May 20, 2019

Thanks for the detailed example.
Do you know how to create a table managed from the admin panel?

@dodyg

This comment has been minimized.

Copy link
Author

commented May 20, 2019

Use a text field with HTML Editor

@dodyg

This comment has been minimized.

Copy link
Author

commented Jun 1, 2019

When you are implementing a module, at="Foot" is your friend. It puts the scripts at the bottom of the page.

<script at="Foot" src="https://unpkg.com/gijgo@1.9.11/js/gijgo.min.js" type="text/javascript"></script>
<script at="Foot">
    document.write('<!-- some script -->');
</script>
@dodyg

This comment has been minimized.

Copy link
Author

commented Jun 3, 2019

Don't forget to put the Admin attribute for controller that handles your module admin pages

admin

@dodyg

This comment has been minimized.

Copy link
Author

commented Jun 5, 2019

Encountered a deployment problem #3755. The cause because the environment was set to Development.

@dodyg

This comment has been minimized.

Copy link
Author

commented Jun 6, 2019

This is important #3752

Check that each module using the razor sdk also references directly (not transitively) the Microsoft.AspNetCore.Mvc package, that modules using our custom script tag reference OrchardCore.ResourceManagement, and that their _ViewImports.cshtml import the related tag helpers @addTagHelper *, OrchardCore.ResourceManagement.
@Jetski5822

This comment has been minimized.

Copy link
Member

commented Jun 6, 2019

For a Admin Controller, Id suggest adding Admin to the controller name too... JobApplicationAdminController.cs -just my two cents ;)

@dodyg

This comment has been minimized.

Copy link
Author

commented Jun 24, 2019

Back from hiatus. This is important (re: content query/index/lucene) #3794

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.