This week in Orchard - 10/18/2019

Gábor Domonkos's avatar
Documentation, This week in Orchard

This week we are coming with a cool demo of using Statiq with custom GraphQL and Liquid modules to render static content for Orchard Core as a headless CMS! But before jumping to that don't forget to check out the other news around Orchard Core and another demo about adding the ability to generate DisplayText with a pattern!

On Orchard Core

Avoid slash in the role name

The slash will add another segment that prevents to use from editing or deleting the role. Now we are checking that if the name of the role is containing a slash or not. If yes, you will see an error message.

Decoupled CMS guide

When you navigate to the Orchard Core Documentation page and hit the Index under the Guides in the left, you will see that we have a new tutorial here, called Building a decoupled website with Razor Pages. By following this step-by-step tutorial you can easily develop your own decoupled CMS site from the scratch.

In this tutorial we learn how to:

  • Start a new Orchard Core CMS project
  • Create custom content types
  • Edit content items
  • Create Razor Pages with custom routes to render the content
  • Load content items with different identifiers
  • Render WYSIWYG preview screens while editing the content

Add Breadcrumbs zone

There is a new zone called Breadcrumbs in the layout of the admin. With that zone, we can inject breadcrumbs. Anywhere, where the breadcrumbs were used they are now using this zone, for example on the workflows page. There is a zone tag, so everything that is inside this zone tag will be injected inside the Breadcrumbs section.

And here you can see the RenderSectionAsync call in the Layout.cshtml of TheAdmin theme.

Updating themes

All the themes that are using Bootstrap now have been updated to use the latest version. The main change is that all the static assets now are included in the themes, which means they can now be served by the Orchard instance and not just the CDN. And now each theme has a ResourceManifest, that defines all the assets that each theme is using, like jQuery, Bootstrap, Bootstrap bundle and so on. The local version and the CDN is also defined here, that allows these themes to reuse the settings that we set in the admin to select which version of the resources we want to use either local or CDN or based on the environment (meaning development will use local, production will use CDN). You can see a vendor- prefix in the names, it is to prevent conflicts with the resource names that we have defined in the OrchardCore.Resources module.

Orchard Core Docs new structure

The structure of the Orchard Core docs will be reorganizing and come with a new MkDocs theme. Here you can see just the material and here you can see the new structure that coming soon.


Add ability to generate DisplayText with a Pattern

Let's set up a site using the Blog recipe. Now head to the admin site and edit one of the content types, for example, the Article. Add a new Text Field to it, let's use the Name for the Technical Name. Now edit the Title of the Article. Here you will see an Options drop-down list with the following options:

  • Title is editable: you can edit the title when editing the content item.
  • Title is generated and input is disabled: you can't edit the title and the textbox for the title is disabled.
  • Title is generated and input is hidden: you can't edit the title and the textbox for the title is hidden.

And under that, there is the pattern used to render the title of this content type. The pattern gets injected and this gets called when the content item is updated.

Now let's edit the existing Article, called About and fill the Name Text Field with some value, for example Name. And change the value of the Subtitle Text Field to Subtitle. After hit Publish. Now if you navigate to Manage Content page you will see that the title of this Article has been generated using the values of the Name and the Subtitle Text Fields.

Because at the content definition of the Title we select that the Title is generated and input is disabled, when you try to edit an existing article, you will see the disabled textbox for Title.

Generating static sites using Statiq(Wyam) and Orchard Core

If you haven't heard about Wyam(Statiq) yet, let's recap the underlying concepts first: Wyam is a simple to use, highly modular, and extremely configurable static content generator that can be used to generate web sites, produce documentation, create ebooks, and much more. Since everything is configured by chaining together flexible modules (that you can even write yourself), the only limits to what it can create are your imagination.

Common static sites are using tools like Jekyll/Hugo which are based on content in markdown files, stored in GitHub. Some arguments:

  • This might work for small and technical teams, but when users are more than a handful or don't have technical knowledge it's not possible anymore.
  • When assets management is required, with organizing images or adding metadata.
  • Rights management, such that sections of the site can only be modified by some users.
  • Editing workflow, when content needs to be reviewed and pass different levels of validation.
  • Allow more complex editing scenarios, like editing maps, choosing a color.
  • Managing more than a dozen content items, like e-commerce sites that could span thousands of products.
  • When online B2B communications are required, like export processes, tax data updates, or webhooks.

Now let's see how to generate static sites! First, let's set up a site using the Blog recipe. Now head to the Configuration and enable the following OpenID modules: OpenID Authorization Server and OpenID Token Validation. We also need the GraphQL, so enable that module too. The goal is to implement the authentication from a service to the Orchard Core by using Bearer tokens and OpenID. With that, you can use the APIs from another service.

After go to the Authorization server settings under OpenID Connect/Settings and allow the Client Credentials Flow. The OpenId Connect Client Credentials grant can be used for machine to machine authentication. In this grant a specific user is not authorized but rather the credentials are verified and a generic access_token is returned.

Now create a new application under OpenID Connect/Management. This will be our service that will connect to the Orchard Core instance. Don't forget to allow the Client Credentials Flow here and of course, provide a Client Id and a Client Secret. We will need these soon.

Now we have a configured Orchard Core instance to serve content to our application using GraphQL and the Client Credentials flow. For the sake of simplicity, we will create a Console Application. This application has to use the Client Credentials Grant Flow for authentication.

curl -XPOST "https://<region>" \
-H "Authorization: Basic <base64 encoded client_id:client_secret>" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials"

First, create a request on the /connect/token on the Orchard Core instance. Then create a Basic authentication header with the Client Id and the Client Secret. Remember you provided these data when setting up the OpenID application in Orchard Core. After we send the request and the response will be a JSON document with an access_token property. This property stores the value of the access token that we can use to send any request to the server as a Bearer token. Check this code snippet to receive this token.

using (var httpClientHandler = new HttpClientHandler())
httpClientHandler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
httpClientHandler.AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate;

using (var httpClient = new HttpClient(httpClientHandler))
string accessToken;

using (var request = new HttpRequestMessage(HttpMethod.Post, new Uri("https://localhost:44300/connect/token")))
request.Headers.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes("theclientid:theclientsecret")));
request.Content = new StringContent("grant_type=client_credentials");
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");

using (var response = await httpClient.SendAsync(request))
var data = await response.Content.ReadAsStringAsync();
var doc = JsonDocument.Parse(data);
accessToken = doc.RootElement.GetProperty("access_token").GetString();

And the value of the doc JsonDocument will be the following, where you can see the access token.


In the next step, we are creating a new Bearer token with the access token and we are pointing to the GraphQL endpoint with a query listing all the blog posts. With that, we can get all the blog posts.

using (var request = new HttpRequestMessage(HttpMethod.Get, new Uri("https://localhost:44300/api/graphql?query=query%20MyQuery%20%7B%20blogPost%20%7B%20displayText%20markdownBody%20%7B%20html%20%7D%20%7D%20%7D")))
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

using (var response = await httpClient.SendAsync(request))
var data = await response.Content.ReadAsStringAsync();

You can see that we pass the GraphQL query in the query string in an URL encoded format. Here is the query in a readable way:

MyQuery {
blogPost {
markdownBody {

We use Console.WriteLine to see the data coming from Orchard Core. Because we set up our site using the Blog recipe and haven't added any additional blog posts, our response will be the following:

So, this is how you do Client Credentials Flow to authenticate your application against Orchard Core. This is the first step needed to generate a static site. Now let's just refactor our code a little bit. Create a ClientCredentialsAuthenticate method to get the access token. With the ClientCredendialsAuthenticate method, we can do as many calls as we want. You can see the BlogPosts query that we will pass to a custom Statiq module called GraphQLQuery. This module will just take the GraphQL endpoint, the query, and the access token. We will also need to develop another custom module extension using Statiq, called Liquid module to be able to use Liquid.

const string BlogPosts = @"
MyQuery {
blogPost {
markdownBody {

static async Task Main(string[] args)
var token = await GraphQlQuery.ClientCredendialsAuthenticate(new Uri("https://localhost:44300/connect/token"), "theclientid", "theclientsecret");

await Bootstrapper
.BuildPipeline("Posts", builder => builder
.WithInputModules(new GraphQLQuery("https://localhost:5001/api/graphql", BlogPosts, token))
new SetDestination(Config.FromDocument(doc => new FilePath(doc.GetString("displayText") + ".html"))),
new Liquid("templates/blogpost.liquid"))

For every blog post, it will apply the transformation, by getting the displayText property of the JSON document and create an HTML file based on that. After apply the Liquid filter based on the blogpost.liquid template, that has the following content:

<p>{{ displayText }}</p>

{{ markdownBody.html | raw }}

If you run that application you will get one HTML file in the bin/Debug/netcoreapp3.0/output folder of your application called Man must explore, and this is exploration at its greatest.html. If you open the file you will see the following result.

With that, you can go further and generate all the static sites you want based on GraphQL and use Razor or Liquid.

On Lombiq

Orchard Dojo Newsletter

Now we have 98 subscribers of the Lombiq's Orchard Dojo Newsletter! We have started this newsletter to inform the community around Orchard with the latest news about the platform. By subscribing to this newsletter, you will get an e-mail whenever a new post published to Orchard Dojo, including This week in Orchard of course.

Do you know of other Orchard enthusiasts who you think would like to read our weekly articles? Tell them to subscribe here!

If you are interested in more news around Orchard and the details of the topics above, don't forget to check out the recording of this week's Orchard meeting!

No Comments

Add a Comment