src/docs/guides/decoupled-cms/README.md
This article goes through the process of creating a fully functional decoupled CMS website that lets you edit Blog Posts and render them.
Decoupled is a development model where the front-end and the back-end (administration) of the site are hosted in the same web application but only the back-end is driven by a CMS. Developers can then write their own ASP.NET Razor Pages or Controllers to have total control on what is generated by the website while still utilizing the CMS, in this case Orchard Core, to author content.
!!! note While this guide starts with a new project and uses Razor Pages you can use much of this guide to add Orchard Core as a content management back-end to any existing ASP.NET Core app too.
You should:
Follow this option if you want to use Visual Studio .NET.
From the folder where the project
dotnet new webapp -o OrchardSite where "OrchardSite" is the name of the project to create.This creates a Web application using Razor Pages.
The newly created website should be able to run, and look like this:
<PropertyGroup> section like this:<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
This will allow for the Razor Pages to be reloaded without the need to recompile them.
<ItemGroup> section like this:<ItemGroup>
<PackageReference Include="OrchardCore.Application.Cms.Core.Targets" Version="2.2.1" />
</ItemGroup>
This will add the packages from Orchard Core CMS
Program.cs file to configure OrchardCore CMS services like this:builder.Services.AddOrchardCms();
!!! warning "Razor Pages"
builder.Services.AddRazorPages() must not be called directly as builder.Services.AddOrchardCms() already invokes it internally.
Program.cs fileapp.UseOrchardCore();Program.cs file, remove them: builder.Services.AddRazorPages();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
}
Here is a sample of a bare minimum Program.cs file
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddOrchardCms();
var app = builder.Build();
app.UseStaticFiles();
app.UseOrchardCore();
app.Run();
}
}
Start the application, the Setup screen shows up:
The Setup screen expects some information in order to create a new database to store the content and user account.
admin as the user name.After a couple seconds the same site as the original template should be displayed, with a "Welcome" message.
If you chose Sqlite, all the state of the application is now stored in a folder named App_Data inside your project's root folder.
If something went wrong, try deleting the
App_Datafolder if it exists and go through this section again.
This part covers the basic content management concepts of Orchard Core CMS, like Content Types and Content Items.
In Orchard Core CMS most of the content that is managed is called a Content Item. A content item is a versioned document like a page, an article, a blog post, a news item, or anything you need to edit. Each of these documents are based on a Content Type that defines which properties it is made of. For instance any article will have a title and some text. A blog post might also have tags. Orchard Core CMS lets you model the content types the way you want, which is known as content modeling.
!!! hint "For developers" A Content Type is analogous to a class, where a Content Item can be seen as an instance of a Content Type.
Orchard comes pre-configured with a set of composable elements of data management called Content Parts that can be used to create custom types like a LEGO. A Title Part for instance will provide a nice editor to enter the title of a content item, and also set it to the text to display by default in the screens. Another important content part is the Markdown Body Part which provides a way to store and render Markdown as the main text of a content item. This is also useful for a Blog Post.
!!! hint "For developers" A Content Part is analogous to a partial class, where each Content Parts are then aggregated to define a Content Type. Content Fields are analogous to custom properties that are added to the Content Type.
Let's create a new content type named Blog Post and add some necessary content parts to it:
/admin.Blog Post. The Technical Name will be generated automatically with the value BlogPost, like this:You can notice an Edit button in front of each content part. This lets us define some settings that might be available for each of them, only for this type.
MarkdownBody part, click Edit.Wysiwyg editor as the type of editor to use, then click Save:The Blog Post content type is ready to use.
BlogPost content type.This is a new day and some Lorem Ipsum text.This shows that we now have a new blog post content item named This is a new day. As we create more content items these will appear on this page.
The next step is to create a custom Razor Page that will display any blog post with a custom url.
Pages folder, create a new file named BlogPost.cshtml with the following content:@page "/blogpost/{id}"
<h1>This is the blog post: @Id</h1>
@functions
{
[FromRoute]
public string Id { get; set; }
}
/blogpost/1 to display the previous page.!!! info "Accessing route values"
In the route, url segment named {id} is automatically assigned to the Id property that is rendered with the @Id syntax.
Each content item in Orchard Core has a unique and immutable Content Item Identifier. We can use it in our Razor Page to load a blog post.
BlogPost.cshtml Razor Page like this:@page "/blogpost/{id}"
@inject OrchardCore.IOrchardHelper Orchard
@{
var blogPost = await Orchard.GetContentItemByIdAsync(Id);
}
<h1>This is the blog post: @blogPost.DisplayText</h1>
@functions
{
[FromRoute]
public string Id { get; set; }
}
/ContentItems/, which is 4tavbc16br9mx2htvyggzvzmd3 in the following screenshot:/blogpost/[YOUR_ID] by replacing the [YOUR_ID] section with the values for your own blog post.In the previous section the DisplayText property is used to render the title of the blog post. This property is common to every content items, so is the ContentItemId or Author for instance. However each Content Type defines a unique set of dynamic properties, like the Markdown Part that we added in the Content Modeling section.
The dynamic properties of a content item are available in the Content property, as a Json document.
...
<h1>This is the blog post: @blogPost.DisplayText</h1>
@Orchard.ConsoleLog(blogPost)
...
All the properties of the current content item are displayed, including the Content property which contains all the dynamic parts we have configured for the Blog Post content type.
Expanding the MarkdownBodyPart node reveals the Markdown field with the content of the blog post.
...
<h1>@blogPost.DisplayText</h1>
<p>@blogPost.Content.MarkdownBodyPart.Markdown</p>
@Orchard.ConsoleLog(blogPost)
...
<p>@await Orchard.MarkdownToHtmlAsync((string) blogPost.Content.MarkdownBodyPart.Markdown)</p>
Even though we can load blog posts from their Content Item Id, this is not user friendly and a good SEO optimization is to reuse the title in the URL.
In Orchard Core CMS the Alias Part allows to provide a custom user friendly text to identify a content item.
new-dayWe can now update the Razor Page to use the alias instead of the content item id, in both the URL and in the way we load the content item.
@page "/blogpost/{slug}"
@inject OrchardCore.IOrchardHelper Orchard
@{
var blogPost = await Orchard.GetContentItemByHandleAsync($"alias:{Slug}");
}
...
@functions
{
[FromRoute]
public string Slug { get; set; }
}
The changes consist in using the slug name in both the route and the local property, and also use a new method to load a content item with an alias.
/blogpost/new-day which should display the exact same result, but using a more SEO and user friendly url.The Alias Part provides some custom settings in order to let it be generated automatically. In our case we want it to be generated from the Title, automatically. To provide such patterns the CMS uses a templating language named Liquid, together with some custom functions to manipulate content items' properties. Orchard provides a generally suitable default pattern.
This will dynamically extract the DisplayText property of a content item, in our case the Title, and call the slugify filter on this values, which will turn the title into a value that can be used in slugs.
The alias is now this-is-a-new-day:
/blogpost/this-is-a-new-day to confirm that the route still works with this auto-generated alias.!!! note "Assignment" Create a new Blog Post with a Title and verify that the alias is auto-generated, and that it can be displayed using its own custom url.
One very useful feature for the users who will have to edit the content is called Preview. If you try to edit a blog post and click on the Preview button, a new window will open with a live preview of the currently edited values.
The CMS doesn't know what Razor Page to use when rendering a content item, and will use a generic one instead. However, the same way we provided a pattern for generating an alias, we can provide a pattern to invoke a specific page for previewing a content item.
/blogpost/{{ ContentItem.Content.AliasPart.Alias }} which is the way to generate the same URL as the route which is configured in the Razor page.As you can see the preview is now using the specific route we set up for displaying a Blog Post, and editors have a full fidelity experience when editing the content.
!!! hint "Suggestion" A dedicated template can also be used for previews, which would provide hints for editors, or detect mistakes, and render them in the preview window. Users can also change the size of the window to test the rendering on different clients.
In this tutorial we have learned how to