Our blog contains the activity stream of Orchard Dojo: general news, new resources or tutorials are announced here.

Blazor guide for decoupled CMS, Workflow Trimming Task - This week in Orchard (06/09/2024)

Blazor guide for decoupled CMS, a new Workflow Trimming Task, and our renewed Orchard Dojo website are the topics for this week. You can still cast your votes for the Jean-Thierry Kéchichian Community Award! Only one week left until the Orchard Harvest conference! Let's see the news for this week!

Featured tags

IIS
API
All tags >

Enhance the Search module and UI, System Module - This week in Orchard (24/03/2023)

Ensure the Background tasks name is visible for old entries, enhance the Search module and UI, and a demo about the System Module! Check out our post for the details! Orchard Core updates Ensure the Background tasks name is visible for old entries In a recent change to the background tasks, a title was added to the background tasks. This works great for entries that have not been saved into the database. However, for entries that are already saved in the database, we do not have a title to show so it leads to no name being displayed on the UI. Since the title is not an editable field, we should always set the title to the default settings when showing tasks on the UI. Enhance the Search module and UI Currently, we have two different SearchController. Each one handles its own search provider, Lucence and Elasticsearch. We should be able to combine these two into one and place it on the Search module since that module's sole purpose is to export a front-end UI for the search. Also, we should add a setting to change the placeholder for the search bar. Additionally, we should have a Search Form widget that would allow the user to add a search widget. So, this update contains the following enhancements: Remove the SearchController from both Elasticsearch and the Lucene module. We now have one common SearchController in the Search module. The new controller now returns a shape instead of a view model which allows the user to override the default views. Adds settings to allow the user to define a title for the search page and also a placeholder text for the search bar. Add a Search widget that a user can add to their site. The default widget will direct the user to the /search route. Allow the user to optionally pass the index name in the route value. For example, /search/index-name the index-name is an optional value, and when it is not provided, the default index is used. Let's see some of the improvements mentioned in action! For this demonstration, we set up our site using the Blog recipe, and to be able to test the enhanced Search module, we have to enable the Search module itself and one search provider, which will be Lucene, for example. Now, we need to create a Lucene index under Search -> Indexing -> Lucene Indices. Here we added the name BlogPosts and included the Blog Post content type to the index. After that, we navigated to Search -> Settings -> Lucene and selected the BlogPosts search index as the default index to use for the search page and put a tick into the "Allow Lucene queries in search form" checkbox. Now we have a simple search set up, let's see the new Search Widget in action! We decided to place this widget on the home page. The predefined Blog content item is the home page in this recipe, so we edited the content definition of the Blog content type by adding a Flow Part to it. By having a Flow Part, we can add the new Search Form widget to the Blog content item. Here we can add some placeholder text if we want and can define the index name. Now it's time to utilize our widget! If we navigate to the home page of our site, we will see our Search Form widget. Let's type something and hit the Search button. As you can see, it lists our test posts which contain the "post" word. And you may notice the index name in the URL too (BlogPosts). If we had multiple indices, we can replace the BlogPosts one with any other existing index and that would mean that we search for something based on the content of that index. Remove .tiff from the supported images array Most modern web browsers, including Google Chrome, Microsoft Edge, Mozilla Firefox, and Opera, do not support the display of TIFF images natively on web pages. This is because TIFF files are typically very large in size and may take a long time to load. If you check out the MediaOrchardHelperExtensions class, you will see that the .tiff is now missing from the _imageExtensions. Demos System Module For this demo, we will clone the following GitHub repository which contains a set of modules for Orchard Core CMS that is driven by the community members who love Orchard Core. This will encourage all the passionate developers to build modules that aren't included in Orchard Core. Such modules may be necessary for the community or essential for any sort of CMS. This repository contains several modules, this time we will check out the one called System Module, which provides information about the currently running application; it can display the available system updates and put your site in maintenance mode while you upgrade. If you run this repository and enter the admin site using the admin username and admin@OCC123 password, you can navigate to Configuration -> Features and search for the "system" word. This will list the following features: System, System Maintenance, and System Updates. Enable them all! Now you will find a new option on the menu called System. Click on the Info under System! This page lists several useful information, like the ASP.NET Core Version, the OS Version, the Tenants, the enabled features, and so on. But you can find another option under System, called Updates. On this page, you can make sure that you are using the latest version of Orchard Core. If you are using the latest one (1.5 currently), you will see a "You're all up to date!" text, meaning there is no newer version of Orchard Core. And the third thing is the Maintenance mode. You can enable this mode by navigating to Configuration -> Settings -> System -> Maintenance and putting a tick into the "Allow maintenance mode" checkbox. If you do that and navigate to the front end of your site, you will see the page which shows you that the site is currently offline for maintenance. And as always, if you would like to learn more about this module, don't forget to check out this recording on YouTube! News from the community Orchard Dojo Newsletter Lombiq's Orchard Dojo Newsletter has 430 subscribers! We have started this newsletter to inform the community around Orchard of the latest news about the platform. By subscribing to this newsletter, you will get an e-mail whenever a new post is published to Orchard Dojo, including This week in Orchard of course. Do you know of other Orchard enthusiasts who would like to read our weekly articles? Tell them to subscribe here! If you are interested in more news about Orchard and the details of the topics above, don't forget to check out the recording of this Orchard meeting!

Media Theme for Orchard Core, Orchard Harvest survey results - This week in Orchard (13/01/2023)

Fixing that FlowPart's widgets are not indexed in Lucene, migrating the OpenID module to OpenIddict 4.0, demo about the new Media Theme, and announcing the results of the Orchard Harvest survey! Check out our post for the details! Orchard Core updates Fix FlowPart's widgets were not indexed in Lucene FlowPart's widgets were not indexed with Lucene (even with all the necessary settings on the Parts and Indexes). Marco Serralheiro added a FlowPartIndexHandler that is basically a clone of the BagPartIndexHandler to solve this issue. Migrate the OpenID module to OpenIddict 4.0 This is about updating the OpenID module to use OpenIddict 4.0 RTM, which shipped in December. The previous versions - including OpenIddict 3.x - are no longer supported. While OpenIddict 4.x itself has many internal improvements, the "public" API hasn't changed much so the changes here are mostly cosmetic. If you want to know more about the latest version of OpenIddict, check out this great post from Kévin Chalet! Demos Lombiq Hosting - Media Theme for Orchard Core The Lombiq Hosting - Media Theme for Orchard Core repository contains an open-source project which will allow developers to host their themes in the Orchard Core Media Library, including templates and assets. The inspiration came from our public Orchard (Core) SaaS called DotNest. If you don't know it, you can use this site to sign up and get an Orchard (Core) site with two clicks for everyone. We don't vet who is signing up, so it should be safe, and it should be limited. But still, we want people to be able to have their sites as flexible as possible. And of course, there are a lot of built-in features in Orchard for that, you can do a lot from the admin. Part of the things you can do from the admin as well in a limited fashion is theming. There is the Templates module, if the developer or the operator of the application allows it then you can also upload CSS files and do all kinds of tricks. In the end, you can adjust the styling of your website. But we wanted to have something closer to the usual developer experience. And that is pretty much creating a theme for your DotNest site with the help of Media Theme for a tenant in a Saas, and that's what media theming is about. We have documentation about that here which contains a link to the DotNest Core SDK. This is what we will use in this demo! We also have automatic deployments either from the command line or from a GitHub Actions action if you will. You can fork the SDK or can work with an Orchard Core-based solution and create a theme (it needs to use Liquid). When you are satisfied, you can either create an import package by using the .NET Command line tool or, you can use Orchard Core's remote deployment feature to import the package on your production site. Or you can also use our GitHub Actions to deploy your theme. If you would like to try it out in your own SaaS, feel free, and if you have questions, please let us know! If you would like to have a DotNest site and use this feature, just sign up here! And of course, don't forget to check out this recording on YouTube to see the usage of the Media Theme in a site runs in DotNest! News from the community New websites using Orchard Core: the site of Capri Exclusive Homeware and the site of BagPortr Capri's purpose is to provide high-quality homeware to everyone, and BagPortr collects your baggage from your doorstep and checks it onto your flight. Both sites were developed by Gert Smith. If you are interested in more websites using Orchard and Orchard Core, don't forget to visit Show Orchard. Show Orchard is a website for showing representative Orchard CMS (and now Orchard Core) websites all around the internet. It was started by Ryan Drew Burnett, but since he doesn't work with Orchard anymore, as announced earlier, it is now maintained by our team at Lombiq Technologies. Orchard Harvest 2023 survey results For those too young to remember, we had Orchard conferences, called Orchard Harvest. And the conference website was available under orchardharvest.org, but unfortunately, it's not anymore. The last one was in 2017 in New York. So, having another get-together is very much overdue. If you would like to see or get a feeling of how this looked like before, we have a couple of mood videos on the Orchard YouTube channel, like this one from the first conference. The point is that we should really think about organizing the next one, and we at Lombiq can take part in that or provide an organizing role with anybody who wants to take part. We created a survey, and now we have the results! Thank you for your feedback so far regarding Orchard Harvest! We have received a significant number of responses so far which has helped us to get a better idea of the right place and time. We have created a Discussion in GitHub where we described the possible dates and locations of the upcoming conference. Mike Alhayek also reached us saying he may have the right contact needed to facilitate the event in Las Vegas. Thanks, Mike! As we move forward, we will keep community members informed of the details, and you will also find every detail in this newsletter too! Orchard Dojo Newsletter Lombiq's Orchard Dojo Newsletter has 392 subscribers! We have started this newsletter to inform the community around Orchard of the latest news about the platform. By subscribing to this newsletter, you will get an e-mail whenever a new post is published to Orchard Dojo, including This week in Orchard of course. Do you know of other Orchard enthusiasts who would like to read our weekly articles? Tell them to subscribe here! If you are interested in more news about Orchard and the details of the topics above, don't forget to check out the recording of this Orchard meeting!

Orchard Harvest 2023, User Notifications - This week in Orchard (28/10/2022)

Adding default form templates for the Default Theme, adding wrapper element for all content fields, demo about the User Notifications module and don't forget to fill out a survey about the next Orchard Harvest conference! Orchard Core updates Adding default form templates for the Default Theme Currently, all of the form widgets are rendered using Widget.cshtml which renders too much HTML without any added value. The goal here is to create default Widget-Form, Widget-Input, Widget-Label, Widget-Select, Widget-TextArea, Widget-Validation, and Widget-ValidationSummary templates. It means that now, you will find these default form templates if you open up the Default Theme. Adding a wrapper element for all content fields There is no easy way to hide/show content fields "label and input as a group" using JavaScript event because there is no wrapper element to select. The goal here is to add a wrapper element to all of the content fields to allow the user to easily hide/show the entire field block using JavaScript. Here you can see the editor of the Text Field where the wrapper has now new classes with ones that you can easily use to distinguish the same kinds of Text Fields from each other. For example, the Text Field called Subtitle of the built-in Blog Post content type has the following HTML structure. Here you can see that the third class and the ID contain the technical name of the Text Field, which makes targeting the given field easier. Adding GetPageSize method to account for MaxPageSize throughout the code Now we have a new GetPageSize method in the PagerOptions class that returns the maximum number of the allowed page size based on the values of the default and maximum page size numbers. And we are using the value coming from this method for every occurrence, where we are constructing the Pager by passing it as the second parameter. Which is the default page size in this case. Demos User Notifications The goal of the User Notifications module is to push notifications to the user, meaning notify the user somehow. There are many ways you can notify a user, one of them being a web notification, you can do SMS, you can do push to a mobile application, etc. In Orchard Core right now, there is no way to send notifications, but there is a way to send emails. Let's navigate to Configuration -> Features and enable the Notifications module. This will provide a way to notify users. After you enable this module, you will see a new bell icon near the Dark mode icon at the top right. If you click on that icon, you will see a list that shows you all of the notifications that you have right now. You can click on them to mark them as read, or you can click on All Notifications at the bottom, which will redirect you to the page where you can see all of your notifications. Here you can see a list of all of the notifications available. You can use the search here, and filter them only to see the read or the unread notifications. You can sort by recently created, or by previously created. You can also mark the unread notifications as read, mark the read notifications as unread or you can delete the notifications as well. You can also hit the Mark All As Read button to mark everything as read. Now let's see how you can generate notifications! Notifications can be generated via code by injecting INotificationManager. But you can also generate notifications via Workflows. Here you can see we have a workflow called Notify Content Owner, which contains the custom activities provided by the Notifications module. Here we put two events, one for when the Blog Post content item is published and one for when the Article is deleted. And here, you can also see the two new Notify content's owner activities as well. Let's check out the content of the first Notify content's owner activity! Here you can see we have proper Liquid support to customize the summary, and the body of our notification. If we open up the editor of the second Notify content's owner activity, you can see here we have some HTML code in the body as well. To be able to render the HTML properly, don't forget to put a tick in the "The body contains HTML" checkbox. And that's not everything! If you would like to know don't forget to check out this recording on YouTube! And if you want to try out this feature for yourself, you can find the code in this PR! News from the community Orchard Harvest 2023 For those who are too young to remember, we had Orchard conferences, called Orchard Harvest. And the conference website was available under orchardharvest.org, but unfortunately, it's not anymore. The last one was in 2017 in New York. So, having another get-together is very much overdue. If you would like to see or get a feeling of how this looked like before, we have a couple of mood videos on the Orchard YouTube channel, like this one from the first conference. The point is that we should really think about organizing the next one, and we at Lombiq can take part in that or provide an organizing role with anybody who wants to take part. If you have any feedback or are you looking forward to having a Harvest again, please share your opinion with us by filling out this survey about the upcoming Orchard Harvest! Orchard Dojo Newsletter Lombiq's Orchard Dojo Newsletter has 381 subscribers! We have started this newsletter to inform the community around Orchard of the latest news about the platform. By subscribing to this newsletter, you will get an e-mail whenever a new post is 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 Orchard meeting!

Extend user permissions, add Contained Stereotypes Bag Part Settings - This week in Orchard (30/09/2022)

Add Contained Stereotypes Bag Part Settings to allow a user to include content types by stereotype, add Displayed Stereotypes property to Content Picker Field Settings, demo about extending the user permissions, and many more waiting for you in our current post! Orchard Core updates Add Contained Stereotypes Bag Part Settings to allow a user to include content types by stereotype When attaching Bag Part to a content type, the user must explicitly set Contained Content Types with an array of content types to be included in the Bag Part. This is good for most cases; however, it would be great to allow for setting the contained content types using Stereotype. The stereotype would be in addition to not in place of Contained Content Types. For example, we want to group all Contact Methods (phone number, address, email address, etc.) by a stereotype type called ContactMethod. All of these content types share a similar functionality which is a way to contact a person. Now we created a Person content type and attached a Bag Part to it. In this case, we had to explicitly specify each content type in the Contained Content Types. But if a new content type was added later from other feature/module, the user would have manually to edit the Bag Part settings every time/everywhere ContactMethods are used to add the new content type which isn't efficient. So now the Bag Part is more flexible. And as you can see here, now we have two radio buttons under the Contained Content Types option where the user can select Content Types or Stereotype. Add Displayed Stereotypes property to Content Picker Field Settings And this one is quite the same as the previous feature but for the Content Picker Field. For example, we want to group all to-do items (appointments, meetings) by a stereotype type called ToDoTask. All of these content types share similar functionality which is a to-do task. Now we want to use Content Picker Field in a different content type to allow the user to select a to-do-task of any kind. In this case, we have to explicitly specify each content type in the DisplayedContentTypes. But if a new content type was added later from another feature/module, the user would have to manually edit the Content Picker Field settings all time. Everywhere to-do tasks are used to add the new content type which isn't efficient. So, let's say we navigate to the editor of a Content Picker Field. And here you can say that you can select the Contained Content Types to: Display All Content Types Content Types Stereotype Add settings to form widgets There was a bug in OC. The option editor wasn't loaded when trying to add Select Input while creating a form. Also, the edit button did not open the modal that would allow you to populate the options using JSON. If the content is saved, and the page is loaded again then the modal and the options editor work just fine. Another form-related improvement is validation. When creating a form using the Form input widgets like (Input, Select, and Text Area) there is no easy way to add a label and validation elements to the input. The current approach is to add a label widget and then a validation widget which is not always ideal. We can make this process much simpler by adding settings to the Input, Select, and Text Area widgets with the following properties: LabelOption an enum value with the following values (None, Standard, ScreenReaders). By default, None is selected to keep it backward compatible. ValidationOptions an enum value with the following values (None, Standard). By default, None is selected to keep it backward compatible. Now, if the user selects an option other than None in the settings, we’ll create the label in the same widget block. The same applies to ValidationOptions. This will make things like dragging/dropping widgets during edit and controlling the size much easier. At the same time, we output less HTML code. And if we add the validation rules, we’ll have more validation logic which makes the widgets more useful. And now, the editor of the Select Input looks like the following with the additional options. And of course, the same applies to all of the built-in form inputs. Demos Extend user permissions A couple of months back there was a need reported to have some roles to be able to edit a user and some not. And there is a conclusion that the user interface is not very flexible at the moment with the permissions. So, it kind of makes it hard if you have a unique case where you need some specific users to be able to do stuff. And also, to set up who can see what users, when you are listing the users. To be able to test out this feature with us, you have to check out this PR on GitHub. The first thing that you will notice is the new permissions regarding Users. In this case, we navigated to Security -> Roles and edited the Moderator role. There are new permissions, for example, Assign any role, Delete any user or Delete users in role - Administrator, List all users, etc. So, you can say you can list all the users, but it doesn't mean you will be able to edit all the users. If you try to edit a given user, you will notice something new here. As you can see, the user name of the user is not editable, but you can edit the email address of the given user. This is controlled by settings that you can reach under Configuration -> Settings -> User Profile Settings. As you can see, here you can allow or deny changing the user names or email addresses of the users. But now back to the users' list. Let's say we have some predefined users and made some changes regarding the permissions of the Moderator role. Now, we logged in with a user who has the Moderator role. After that, the user with the Moderator role will see something like this. First of all, you can notice that there is a little badge under every user that shows the roles the user has. You can also see that this user has permission to see users in the Editor role, but they can't edit or delete the editor user. Now let's edit the author user! Here you can see that this user can edit the settings of this user but can't fully manage the roles of the author user. They have the option to add or remove the Author role but that's it, nothing more. And we are just showing you some simple scenarios about what you can achieve and how you can customize the user permissions. If you want to see more complex scenarios, head to YouTube for a recording! News from the community Helping Global Health build an Advanced Form Builder using Form.io When Global Health from Australia approached us with the request to build an advanced form builder using Form.io, it promised to be an interesting project. They were looking to integrate this new form builder deeply into MasterCare+, their Multi-Tenanted SaaS-based platform for Health Care which is built on Orchard CMS. It would allow creating custom forms for a wide range of scenarios in the health care domain, using the advanced editing capabilities of Form.io. The solution built by Lombiq was an important step in bridging the gap between paper forms and electronic health care management. Check out the full post here! Orchard Dojo Newsletter Lombiq's Orchard Dojo Newsletter has 354 subscribers! We have started this newsletter to inform the community around Orchard of the latest news about the platform. By subscribing to this newsletter, you will get an e-mail whenever a new post is 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 Orchard meeting!

GrahQL Queries queries, Negative role condition evaluator - This week in Orchard (07/06/2021)

We start this week by showing the ability to export templates as files, the negative role condition evaluator, and the brand new alternates for widget parts and for dynamic parts with great examples in the documentation. After, we will see a great demo about using templated GraphQL queries with Liquid. Check out our post for more! Orchard Core updates Add ability to export templates as files The idea here is in the exported ZIP file, the templates are now files instead of being embedded in the JSON recipe. Let's check it out quickly! Let's say you have the Templates module enabled (the Templates module provides a way to write custom shape templates from the admin) and have a defined template. If you set up your site using the Agency recipe, you will have one template, called Content__LandingPage, which is a template for the Landing Page content type, and the homepage of that installation is a Landing Page content item. Now, head to Configuration -> Import/Export and create a new Deployment Plan under the Deployment Plans option. Let's name the plan as Templates and add one Deployment Step to it, the All Templates one. Here you will see a new option: Export templates as files. Let's just put a tick in this checkbox to see what will happen. Now we have only one thing left to do, to execute the deployment plan. Let's just download the deployment plan locally and check the content of the Templates.zip file. Here you will see a Recipe.json file and a Templates folder. The Recipe.json file contains the Templates step with our only one Content__LandingPage template, and the content of that template can be found in the Content-LandingPage.liquid file in the Templates folder. Negative Role Condition Evaluator The way a role condition with a negative operator is evaluated right now doesn't take multiple roles into account. If we have 2 users: -- roleA roleB UserA X UserB X X The evaluator uses .Any() when evaluating the value against every role of the user. If we pick the operator "Equals" with the value "roleB", it will only be true for UserB, which has roleB. If we were now to just change the operator to "NotEquals" logically, it should be the opposite result of "Equals" but since the value gets compare against every role and roleA != roleB it will return true as well. .Any() (Current) Operator Value UserA UserB Equals roleB false true Not Equals roleB true true Contains roleB false true Not Contains roleB true true Using .All() with the negative operators which would give opposite results as intended. .All() Operator Value UserA UserB Equals roleB false true Not Equals roleB true false Contains roleB false true Not Contains roleB true false We have many negate operators, like StringNotContainsOperator, StringNotEndsWithOperator, StringNotStartsWithOperator, StringNotStartsWithOperator. Now, if you use them, the code execution will return with using the .All() instead of using the .Any(). If you don't know what are the rules and how to use them, check out the demo about the Rules module in this post! Validate content type and part names There was an unhandled exception during content item creation if you have a part named like property on ContentItem. The reason was that we have some reserved names that you cannot use when you are naming your content parts or content types. The _reservedNames HashSet here contains the names, that you cannot use. Use ZStringWriter ZString is a library that provides StringBuilder and StringWriter with zero allocation, meaning it's allocating an array on the stack for small buffers, instead of creating new StringBuilders and StringWriters every time. All ZString methods only allocate the final string. Also, ZString has enabled to access the inner buffer, so if the output target has stringless API, you can achieve completely zero allocation. Now we can use the new ZStringWriter instead of the existing StringWriter one. Alternates for Widget Parts This change contains several new alternates for widget parts and dynamic parts. And the documentation has also been updated with the new alternates. And when we are saying that the documentation has been updated, we mean that it's really got a huge update with many examples, like Display mode with Part Type and Shape without Display type Examples, Display mode with Part Type and Shape with Display type Examples, Display mode with Part Name and Custom Shape with Display type Examples and many more. Demos GraphQL Queries queries Let's meet with the new GraphQL Query type that basically allows you to use a GraphQL query in the admin interface and Liquid. It's using the same principle as the SQL query or the Lucene query. For example, let's say we have a GraphQL query created called ContactRequests, which is just about getting the Contact Request content items from the site. This query returns the contentItemID, the createdUtc, and the display text values of the Contact Request content items. But by using this experimental feature, you have the availability to use a templated GraphQL with Liquid. What do we mean? Well, first let's find the new Run GraphQL Query under Search -> Queries. Here let's add a template that contains Liquid code. By using Liquid, you can add output parameters to your query. In this case, we would like to get the email and the message fields too of the Content Request content items by using parameters. As you can see, we used the contactRequestEmail parameter to get the email and the contactRequestMessage parameter to get the message. If you open up the StatCan Orchard Core repository, you will find a collection of custom Orchard Core resources, modules, and themes that support various web applications and software-as-a-service (SaaS) products. One of the modules here is the VueForms module, which provides a form ContentType that simplifies using VueJs forms in the frontend. We had a demo about that one too, way back in 2020. If you don't remember about that, you can check out that in this post! And as always, if you want to know more about this feature, don't forget to check out the following recording on YouTube! News from the community Orchard Dojo Newsletter Lombiq's Orchard Dojo Newsletter has 203 subscribers! 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 is 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!

YesSql 3, Liquid Widget guide - This week in Orchard (17/05/2021)

YesSql 3 is here! Check out our current post to see what's new in YesSql, but first, read the new Liquid Widget guide and get to know why do the community needed to delete the Liquid Page and the Liquid Widget from the Blog and Agency recipes! Orchard Core updates Remove liquid type from recipes This is from a security report that if you have access to the admin, you can use and write in the LiquidPart. By using LiquidPart you can write HTML and JavaScript. It sounds obvious, but some sites don't expect users to be able to edit JavaScript on their pages, and it might have an issue. Because if you can write JavaScript, you can write XSS. The solution here is to remove the Liquid Page content type and Liquid Widget from the Blog and the Agency recipe. But the part is still there, and you can use it. And the Edit content types permission is marked as Security Critical. Meaning that, if you allow this permission to users, you let them be able to also use the LiquidPart and create some custom types that are about to render JavaScript in the frontend. Liquid widget guide As we mentioned previously, the Liquid Widgets from the default recipes were just removed. But that content type served as a very good example of how you can work with Liquid in Orchard Core. To have an example for working with Liquid, you will now find a new guide in the documentation about how to build a new Liquid Widget. Sometimes not having a feature but documenting how to use something is better than having a feature and no documentation about it. Did you know that our Helpful Extensions module for Orchard Core contains a Liquid Widget too, that adds Liquid code editing and rendering capabilities? Check out that repository for more goodies like the content definition code generation or the flows helpful extensions and many more! Use nameof for action name whenever it's possible Any controller action name that doesn't change can be called by using the nameof expression of C#. Now you won't break anything if you change the name of a method during some refactoring. Contents GetAsync: Recall published items Calls to IContentManager.GetAsync(string contentItemId, VersionOptions options) use IContentManagerSession.RecallPublishedItemId() to retrieve an already loaded content item if the request is to get a published item. The same could be used in GetAsync(IEnumerable<string> contentItemIds, bool latest = false) (i.e., the overload accepting multiple IDs). And the fix is here! When you do some loads with the content manager and if the content items are already have been loaded previously in the same request, there is no query that needs to be issued or just for the items that are missing. Demos YesSql 3 In the previous version of YesSql, every session created a new transaction automatically by default. Every session means even if your session is only doing reads. But when you are doing reads, you don't need a transaction because every read will be using the same transaction resolution. When you start a session in YesSql 3 and it's just about doing reads (like SELECTs) then it won't create a transaction. But the first time when you do a change on an object, the transaction will be created automatically (like when you would like to update a content item). Now it lazily creates a transaction if there are UPDATE, DELETE or INSERT statements. The second change is that if you really want to decide when the transaction should be created and not to wait for it to be automatically created. You can now call BeginTransaction and that will create a transaction or return the existing one if one is still open. And then there is another property (CurrentTransaction) that gives you either the existing transaction or NULL if there is no transaction. Everything has been renamed from CommitAsync to SaveChangesAsync. It's like in Entity Framework and now it's more obvious to know what it does (saves the changes). And what it means is that at that point if there is a transaction it will be committed and then released. If there is no transaction, it's just do nothing. And there is still the AutoFlush, meaning if you do some updates and then do a query to get some data, it will flush the changes from the database without committing a transaction, but your next request will be able to read the values that aren't in the database. Something that you can't do with YesSql before is let's say you start creating a session and there is an exception in the middle. If you didn't do a try-catch, and call CANCEL on the catch, it would commit the transaction because disposing of the session was committing the changes. In ASP.NET we don't have this issue because there is a rule that catches any exception that happens anywhere in the pipeline. But in some other apps, if you forget a try-catch, it would commit the changes even if it didn't go over the full list of commands that you want to execute. That was a big issue in YesSql. So, now that's actually changed. Meaning that if you don't call SaveChangesAsync now, it won't save anything. You have to call SaveChangesAsync at some point to commit the transaction. And if you disposing the session before calling SaveChangesAsync, it will cancel the transaction if it exists and closes the connection. So, SaveChangesAsync is now mandatory to mimic the EF behavior. So, if you have modules that use the session, you need now to call SaveChangesAsync, otherwise, nothing will be saved. But you should not have any module that calls SaveChangesAsync. And that's not all! If you would like to know every new feature and performance improvement included in YesSql 3, don't forget to check out the following recording on YouTube! News from the community Orchard Dojo Newsletter Lombiq's Orchard Dojo Newsletter has 199 subscribers! 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 is 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!

Reimplement batching in YesSql, Azure Application Insights module - This week in Orchard (26/02/2021)

Refactoring the Content zone, reimplement batching in YesSql, hiding Setup recipes, adding more indexes to index tables, Azure Application Insights module and a lot more is waiting just for you in this post! Orchard Core updates Do not display Setup recipes in the admin UI You can easily list all of the existing recipes of your site under Configuration -> Recipes. If you create a recipe, like myrecipe.recipe.json, and put it in a Recipes folder of one of your modules, that recipe will be listed on this page. Let's add a simple one and check out the content of that page. You can see that our recipe with the display name My Recipe is on the list but wait! What happened with the other recipes? We have several other built-in recipes like the Blog, the Agency, and so on. Where are they? From now the logic in the Index method of the AdminController in the OrchardCore.Recipes module is slightly changed. If the recipe is a setup recipe (defined in the issetuprecipe of the JSON file) or has the hidden tag, the recipe will not appear in that list. You can see that our myrecipe has the false value for the issetuprecipe but the Blog recipe has the true value. Admin Dashboard Widgets We mentioned the Admin Dashboard widgets two times in This week in Orchard that allows you to add cards to the homepage of the dashboard, which is about to represent a piece of functionality of a given feature or module. You can find the first post here and the second one here. And now this feature has been merged to the dev branch of Orchard Core and there is also a new page in the Orchard Core Documentation about this module. It hasn't got too much information yet but the embedded recording could help you to see this feature in action. If you haven't heard about this feature, don't forget to check out the previous blog posts and the demo video! Use DocumentId in indexes This fix is also about using the correct combination of fields for each index that we use. For example, the index for the UserIndex table now contains every field of the table. So, from now you will find indexes for every index table. Refactor ContentZone - Tabs, Card, Column containers With more and more dynamic shapes coming into Orchard Core (all the tab/card grouping shapes, and also shapes like the ContentCard) it would be potentially useful to have a ShapesViewModel that could be used as a hard type abstract class to create specific targetted ViewModels, i.e. a TabViewModel. The problem with the ShapeViewModel is it doesn't support a list of positioned Items, so right now we are using Shape because many of these tab/card/content card shapes require a list of positioned items as well. So we was not intending that a ShapesViewModel was actually an IShape, just an abstract that could be used to build shapes with, and the mixin would still turn it into an IShape, by mixing in the ShapeViewModel which has all the required IShape properties. The idea being you could then use the IShapeFactory to create a shape, then add items to it. var shape = ShapeFactory.CreateAsync<TabViewModel>("Tab", m =>{ m.TabId = tabId});foreach(var item in thetabs){ shape.Add(item);} The current usage builds 3 different shapes, even when displaying on the front end, and there are no groups. Here we do a quick check first, to see if there are any groups, and if there are none, just render the zone directly. No extra shapes. Also Removes uses a dynamic, and moves to hard typed models. Uses ToLookup instead of GroupBy (better usage). Uses fewer dynamics for injected properties, i.e. DisplayAsync can be resolved directly to its interface instead of being dynamic (this needs evaluation, as we might do it for the other C# Shape Attributes, or not). If you check out the modifications of the ZoneShapes class, you will find the implementation of the ContentZone shape and building the TabContainer shape by using the new GroupingsViewModel. And by having the GroupingsViewModel ViewModel we can use that instead of a dynamic one as you can see in the TabContainer.cshtml file. Demos YesSql: Reimplement batching When we do updates on the content items and when one deletes a document all the index tables corresponding to the document type get a DELETE query. And this DELETE query is actually a single DELETE query. If we see a benchmark we can realize that the batching isn't working. So, if you would like to delete multiple content items, the code is just about to send multiple DELETE requests instead of sending one. If you run the query locally using SQL Server and you have 1000 indexes, it takes one second. So, you might not notice that it's slow. It's slow but it could look like that's because we have several indexes. Extra Indexes: 1, Elapsed 00:00:00.0371806Extra Indexes: 100, Elapsed 00:00:00.1317780Extra Indexes: 200, Elapsed 00:00:00.2826214Extra Indexes: 500, Elapsed 00:00:00.6213360Extra Indexes: 1000, Elapsed 00:00:01.2628232 But when you have the SQL Server in Azure, the deletion of 1000 indexes could take 28 seconds. At this point, you will start noticing the performance just after 100 indexes. Apparently, we have around 20 indexes in Orchard Core but you can quickly arrive at 100. Extra Indexes: 1, Elapsed 00:00:00.0838498Extra Indexes: 100, Elapsed 00:00:02.5644737Extra Indexes: 200, Elapsed 00:00:05.0814065Extra Indexes: 500, Elapsed 00:00:12.6558409Extra Indexes: 1000, Elapsed 00:00:28.6622291 In YesSql, when you update a document, it needs to update all the indexes, which means it will rebuild them. And an index can return multiple records like if I'm indexing the name of someone, I might want to index the first name, the last name, the middle name. These are three records, one per name. So, what it does is, it builds the three records in memory and it will send a query to delete any record that was associated with the document and then sends three inserts for the new records. So you have one for the delete and three for the inserts. But it sends a delete even for the indexes that didn't return anything because the fact that you didn't return anything might mean that there is nothing anymore associated. So you need to delete and send nothing. This way we get a delete per index. That's not optimal but that's how YesSql works and that's optimal for reads and not writes. To goal is to make reads faster than writes. Still, we should not have to send one independent query to do the deletes per index. That's what this issue is about. And Sébastien Ros managed to fix it. And here are some numbers. These are all for dummy indexes that never perform any writes. Just cause deletes. What we can see in the profiler is that everything easy to batch is batched together. The big batch with mostly deletes, and a couple of inserts, is now long-running, instead of many many short runs. Probably from the look of it, the insert is still expensive, so which pushes up the time run. Before batchingLocal Indexes 1, Elapsed 00:00:00.3004236 Indexes 100, Elapsed 00:00:00.2375774 Indexes 200, Elapsed 00:00:00.3583902 Indexes 500, Elapsed 00:00:00.7695818 Indexes 1000, Elapsed 00:00:01.2836934Remote Indexes 1, Elapsed 00:00:00.7207663 Indexes 100, Elapsed 00:00:03.3552247 Indexes 200, Elapsed 00:00:05.5547927 Indexes 500, Elapsed 00:00:13.8364514 Indexes 1000, Elapsed 00:00:27.2306443After BatchingLocal Indexes 1, Elapsed 00:00:00.2207824 Indexes 100, Elapsed 00:00:00.0910920 Indexes 200, Elapsed 00:00:00.1632908 Indexes 500, Elapsed 00:00:00.4007200 Indexes 1000, Elapsed 00:00:00.4559752RemoteIndexes 1, Elapsed 00:00:03.6326000Indexes 100, Elapsed 00:00:04.9639312Indexes 200, Elapsed 00:00:09.8273422Indexes 500, Elapsed 00:00:16.1340951Indexes 1000, Elapsed 00:00:15.2008296 And how it looks like is the following. If you create a blog post in Orchard Core and after the change, there is a single communication to the database that contains everything that you can see here. This contains creating the Document, creating the ContentItemIndex, updating the ContentItemIndex with the DocumentId, and so on. These are three indexes to update (ContentItemIndex, ContainedPartIndex and AutoroutePartIndex). insert into [Document] ([Id], [Type], [Content], [Version]) values (19, 'OrchardCore.ContentManagement.ContentItem, OrchardCore.ContentManagement.Abstractions', '{"ContentItemId":"4cpw0fnmjb1kp07dmzxx8n8ecg","ContentItemVersionId":"4953p18bj3gyy5yy82f7mj7w4y","ContentType":"BlogPost","DisplayText":"The title","Latest":true,"Published":false,"ModifiedUtc":"2020-12-31T00:31:34.3346095Z","PublishedUtc":null,"CreatedUtc":"2020-12-31T00:31:34.3346095Z","Owner":"48v9vt5vxznr5z9m1df9zmvjm8","Author":"admin","TitlePart":{"Title":"The title"},"AutoroutePart":{"Path":"blog/the-title","SetHomepage":false,"Disabled":false,"RouteContainedItems":false,"Absolute":false},"BlogPost":{"Subtitle":{"Text":"Subtitle"},"Image":{"Anchors":[],"Paths":[],"MediaTexts":[]},"Tags":{"TagNames":["Space"],"TaxonomyContentItemId":"4ykev5wxfcny7tvsahz9y64mwe","TermContentItemIds":["4nv0z7r24r1vw3sfpq7t6xws59"]},"Category":{"TaxonomyContentItemId":"4tpy2wv97bkbf0zkx8tyd1bm4q","TermContentItemIds":["4bsstr09f29rp0sgy85n9f07wj"]}},"MarkdownBodyPart":{"Markdown":"Some text"},"ContainedPart":{"ListContentItemId":"491emynv0kavbzhy40xmqv1wds","Order":0}}', 1);insert into [ContentItemIndex] ([ContentItemId], [ContentItemVersionId], [Published], [Latest], [ContentType], [ModifiedUtc], [PublishedUtc], [CreatedUtc], [Owner], [Author], [DisplayText]) values ('4cpw0fnmjb1kp07dmzxx8n8ecg', '4953p18bj3gyy5yy82f7mj7w4y', 0, 1, 'BlogPost', '2020-12-31T00:31:34', '', '2020-12-31T00:31:34', '48v9vt5vxznr5z9m1df9zmvjm8', 'admin', 'The title') ; select last_insert_rowid() [Id];update [ContentItemIndex] set [DocumentId] = 19 where [Id] = (last_insert_rowid());insert into [ContainedPartIndex] ([ListContentItemId], [Order]) values ('491emynv0kavbzhy40xmqv1wds', 0) ; select last_insert_rowid() [Id];update [ContainedPartIndex] set [DocumentId] = 19 where [Id] = (last_insert_rowid());insert into [AutoroutePartIndex] ([ContentItemId], [Path], [Published], [Latest], [ContainedContentItemId], [JsonPath]) values ('4cpw0fnmjb1kp07dmzxx8n8ecg', 'blog/the-title', 0, 1, '', '') ; select last_insert_rowid() [Id];update [AutoroutePartIndex] set [DocumentId] = 19 where [Id] = (last_insert_rowid()); When we create a blog post there aren't any delete requests, because there is nothing before but when we update we need to delete all the indexes and these calls contains unnecessary deletes because for example there is no LayerMetaDataIndex on the blog post but the logic here is hey, we are dealing with a content item and there is an index for all of the content items called LayerMetadataIndex. So it will send the delete just in case which is dumb. But you can see here for each index we have a delete and it used to be a single query communication query with the database. Now let's divide by two the part where we insert and update the indexes. Instead of having to insert something then update something for each index now, we have just inserted it. We still have all the deletes and some of them are still useless but these are quick. So we have just this request now when we update a single blog post. insert into [Document] ([Id], [Type], [Content], [Version]) values (23, 'OrchardCore.ContentManagement.ContentItem, OrchardCore.ContentManagement.Abstractions', '{"ContentItemId":"4cpw0fnmjb1kp07dmzxx8n8ecg","ContentItemVersionId":"4ypdrxm7xbndr0dvcpwaraa95g","ContentType":"BlogPost","DisplayText":"The title","Latest":true,"Published":false,"ModifiedUtc":"2020-12-31T05:55:56.8113646Z","PublishedUtc":"2020-12-31T01:22:37.7926461Z","CreatedUtc":"2020-12-31T00:31:34.3346095Z","Owner":"48v9vt5vxznr5z9m1df9zmvjm8","Author":"admin","TitlePart":{"Title":"The title"},"AutoroutePart":{"Path":"blog/the-title","SetHomepage":false,"Disabled":false,"RouteContainedItems":false,"Absolute":false},"BlogPost":{"Subtitle":{"Text":"Subtitle"},"Image":{"Anchors":[],"Paths":[],"MediaTexts":[]},"Tags":{"TagNames":["Space"],"TaxonomyContentItemId":"4ykev5wxfcny7tvsahz9y64mwe","TermContentItemIds":["4nv0z7r24r1vw3sfpq7t6xws59"]},"Category":{"TaxonomyContentItemId":"4tpy2wv97bkbf0zkx8tyd1bm4q","TermContentItemIds":["4bsstr09f29rp0sgy85n9f07wj"]}},"MarkdownBodyPart":{"Markdown":"Some text"},"ContainedPart":{"ListContentItemId":"491emynv0kavbzhy40xmqv1wds","Order":0}}', 1);delete from [ContentItemIndex] where [DocumentId] = 22;delete from [AliasPartIndex] where [DocumentId] = 22;delete from [LayerMetadataIndex] where [DocumentId] = 22;delete from [ContainedPartIndex] where [DocumentId] = 22;delete from [AutoroutePartIndex] where [DocumentId] = 22;delete from [TaxonomyIndex] where [DocumentId] = 22;insert into [ContentItemIndex] ([ContentItemId], [ContentItemVersionId], [Published], [Latest], [ContentType], [ModifiedUtc], [PublishedUtc], [CreatedUtc], [Owner], [Author], [DisplayText], [DocumentId]) values ('4cpw0fnmjb1kp07dmzxx8n8ecg', '4q3271jp1705etwnf52c0nnbwz', 1, 0, 'BlogPost', '2020-12-31T01:22:37', '2020-12-31T01:22:37', '2020-12-31T00:31:34', '48v9vt5vxznr5z9m1df9zmvjm8', 'admin', 'The title', 22) ; select last_insert_rowid() [Id];insert into [ContainedPartIndex] ([ListContentItemId], [Order], [DocumentId]) values ('491emynv0kavbzhy40xmqv1wds', 0, 22) ; select last_insert_rowid() [Id];insert into [AutoroutePartIndex] ([ContentItemId], [Path], [Published], [Latest], [ContainedContentItemId], [JsonPath], [DocumentId]) values ('4cpw0fnmjb1kp07dmzxx8n8ecg', 'blog/the-title', 1, 0, '', '', 22) ; select last_insert_rowid() [Id];insert into [TaxonomyIndex] ([TaxonomyContentItemId], [ContentItemId], [ContentType], [ContentPart], [ContentField], [TermContentItemId], [DocumentId]) values ('4ykev5wxfcny7tvsahz9y64mwe', '4cpw0fnmjb1kp07dmzxx8n8ecg', 'BlogPost', 'BlogPost', 'Tags', '4nv0z7r24r1vw3sfpq7t6xws59', 22) ; select last_insert_rowid() [Id];insert into [TaxonomyIndex] ([TaxonomyContentItemId], [ContentItemId], [ContentType], [ContentPart], [ContentField], [TermContentItemId], [DocumentId]) values ('4tpy2wv97bkbf0zkx8tyd1bm4q', '4cpw0fnmjb1kp07dmzxx8n8ecg', 'BlogPost', 'BlogPost', 'Category', '4bsstr09f29rp0sgy85n9f07wj', 22) ; select last_insert_rowid() [Id];update [Document] set [Content] = '{"ContentItemId":"4cpw0fnmjb1kp07dmzxx8n8ecg","ContentItemVersionId":"4q3271jp1705etwnf52c0nnbwz","ContentType":"BlogPost","DisplayText":"The title","Latest":false,"Published":true,"ModifiedUtc":"2020-12-31T01:22:37.6390174Z","PublishedUtc":"2020-12-31T01:22:37.7926461Z","CreatedUtc":"2020-12-31T00:31:34.3346095Z","Owner":"48v9vt5vxznr5z9m1df9zmvjm8","Author":"admin","TitlePart":{"Title":"The title"},"AutoroutePart":{"Path":"blog/the-title","SetHomepage":false,"Disabled":false,"RouteContainedItems":false,"Absolute":false},"BlogPost":{"Subtitle":{"Text":"Subtitle"},"Image":{"Anchors":[],"Paths":[],"MediaTexts":[]},"Tags":{"TagNames":["Space"],"TaxonomyContentItemId":"4ykev5wxfcny7tvsahz9y64mwe","TermContentItemIds":["4nv0z7r24r1vw3sfpq7t6xws59"]},"Category":{"TaxonomyContentItemId":"4tpy2wv97bkbf0zkx8tyd1bm4q","TermContentItemIds":["4bsstr09f29rp0sgy85n9f07wj"]}},"MarkdownBodyPart":{"Markdown":"Some text"},"ContainedPart":{"ListContentItemId":"491emynv0kavbzhy40xmqv1wds","Order":0}}', [Version] = 1 where [Id] = 22;insert into [ContentItemIndex] ([ContentItemId], [ContentItemVersionId], [Published], [Latest], [ContentType], [ModifiedUtc], [PublishedUtc], [CreatedUtc], [Owner], [Author], [DisplayText], [DocumentId]) values ('4cpw0fnmjb1kp07dmzxx8n8ecg', '4ypdrxm7xbndr0dvcpwaraa95g', 0, 1, 'BlogPost', '2020-12-31T05:55:56', '2020-12-31T01:22:37', '2020-12-31T00:31:34', '48v9vt5vxznr5z9m1df9zmvjm8', 'admin', 'The title', 23) ; select last_insert_rowid() [Id];insert into [ContainedPartIndex] ([ListContentItemId], [Order], [DocumentId]) values ('491emynv0kavbzhy40xmqv1wds', 0, 23) ; select last_insert_rowid() [Id];insert into [AutoroutePartIndex] ([ContentItemId], [Path], [Published], [Latest], [ContainedContentItemId], [JsonPath], [DocumentId]) values ('4cpw0fnmjb1kp07dmzxx8n8ecg', 'blog/the-title', 0, 1, '', '', 23) ; select last_insert_rowid() [Id]; Using the other PR we are able to teach the index when not to send deletes as there will never be any index related to this Document, so don't send deletes. After that, you can see there is no more LayerMetaDataIndex and no more AliasPartIndex calls because the blog post doesn't have these. delete from [ContentItemIndex] where [DocumentId] = 23;delete from [ContainedPartIndex] where [DocumentId] = 23;delete from [AutoroutePartIndex] where [DocumentId] = 23;delete from [TaxonomyIndex] where [DocumentId] = 23; This is by having a new method on the IndexProvider to explain when to not use the IndexProvider. So it would not even go to the Map method. In this case, we say if you don't have the AliasPart, don't use the IndexProvider. context.For<AliasPartIndex>() .When(c => c.Has<AliasPart>()) .Map(contentItem => Check out the following recording on YouTube to know more about this YesSql improvement! News from the community Lombiq Hosting - Azure Application Insights This new Orchard Core module from Lombiq enables easy integration of Azure Application Insights telemetry into Orchard. Just install the module, configure the instrumentation key from a configuration source (like the appsettings.json file) as normally for AI, and collected data will start appearing in the Azure Portal. Would like to learn more about our new module? Then head to the repository now where you can find every detail about how to set up and use that module in your site! Orchard Dojo Newsletter Lombiq's Orchard Dojo Newsletter has 191 subscribers! 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!

Custom User Settings, Advanced Markdown Extensions - This week in Orchard (27/11/2020)

Now it's easier to extend the user setting than ever thanks to the new custom user settings feature! Create advanced Markdown with the new extensions, use the new isInRole layer rule, and many other great things in Orchard Core! Check out our current post for more! Orchard Core updates Use Advanced Markdown extensions Set up your site using the Blog recipe and navigate to the admin UI to edit the predefined blog post. You can use a Markdown editor to edit the body of your blog post. To convert the Markdown string to HTML, Orchard Core using Markdig, which is a fast, powerful, CommonMark compliant, extensible Markdown processor for .NET. This processor has a great extensible architecture with more than 20 built-in extensions to allow footnotes, support emojis, and smileys, adding figures, enable Bootstrap classes, and so on. You can check out the features section of the readme.md file to know more about these extensions. And the good news is now you can select which extension you want to enable by just setting different configuration values in your appsettings.json file. The default option is to turn on the nohtml extension (that disables HTML support) and the one called advanced, which enables advanced Markdown extensions. By turning on the advanced extension, Markdig will use every extension except the BootStrap, Emoji, SmartyPants and soft-line as hard-line breaks extensions. On the screen above you can see we also turn on the emoji extension. Let's try this out quickly! Here you can see we added some Emoji shortcodes and smileys to the blog post and they are converted to their respective Unicode characters. And we also added a task list at the end of the blog post that comes from the TaskLists extension that is enabled by default because the TaskList extension will be enabled if you say you want to use the Advanced Markdown extension. You can read more about Markdown configuration in the Orchard Core documentation too! Add isInRole layer rule Let's say we would like to create a layer that contains widgets that are only available for users who are in the Editor role. We cannot do that easily until now, so let's try out the newly added isInRole layer rule! Set up your site using the Blog recipe then head to Design -> Widgets in the admin UI. Here you can see the two predefined layers: Always and Homepage. Click on the Add button to create a new layer. The only thing we have to do here is to set the Rule to isInRole("editor"). Now put a widget to this layer and create a new user with the editor role. If you log in with that user you can see the widget in the Content zone of the theme. Add CloneContent Permission as Content Type dynamic permission Navigate to the admin UI of your site then head to Security -> Roles and hit the Edit button near an arbitrary role. Now type the clone word in the search box that will list the new clone content permissions. This means now you can set the clone content permission for every securable content type (a content type that can have custom permissions). Improves Document Options and Compression MessagePack for C# is an extremely fast MessagePack serializer for C#. It is 10x faster than MsgPack-Cli and outperforms other C# serializers. MessagePack for C# also ships with built-in support for LZ4 compression - an extremely fast compression algorithm. Performance is important, particularly in applications like games, distributed computing, microservices, or data caches. And now this package is part of Orchard Core. Orchard Core using MessagePack to GZIP content, because there is an option in MessagePack to also compress it. There is a new IDocumentSerialiser to say how to serialize into a sequence of bytes or deserialize from a byte array. JSON.NET can serialize anything, but if there is an issue with a custom document, we can use MessagePack to customize the way how to serialize content from the distributed cache. Demos Custom User Settings Set up your site using the Blog recipe. Now navigate to the admin UI and head to Configuration -> Features to enable the new Custom User Settings feature that allows content types to become custom user settings. After we can create a new content type that will be responsible to store the custom user settings. To do that, head to Content -> Content Definition -> Content Types and create a new content type. The most important thing to do here is to make the stereotype CustomUserSettings and remove the ticks from the Creatable, Listable, Draftable, etc, checkboxes. We named our content type as User Profile and attached three fields to it: a media field to store the avatar of the user and two text fields to store the first name and the last name of the user. We haven't added any content parts to this content type. Let's navigate to Security -> Users and edit one of the users in the system. Here you will notice a new tab near the Content one with the name User Profile. That's because we named our content type User Profile. Here we specified the first and the last name of the user and attached an image to it. If you hit Save, you can persist your changes for this given user. Now it's time to use our custom user settings from code. The Orchard Core documentation has a great detailed page about how you can get the custom user settings using Liquid, how to adjust the placement, and many more. Notice the brand new users_by_id Liquid Filter that you can use to get the User object from the database by a user ID. But how can you get your custom user settings in C#? Well, that's also quite simple! The User class in Orchard Core implements the Entity class, which has a JObject property called Properties. And inside the Properties, you can find the whole UserProfile content type with it's attached content parts and fields. { "Email": "[email protected]", "EmailConfirmed": true, "Id": 18, "IsEnabled": true, "LoginInfos": [], "NormalizedEmail": "[email protected]", "NormalizedUserName": "ADMIN", "PasswordHash": "AQAAAAEAACcQAAAAECfRTVdPZm1meRWrxja3vzbJlslet72QzrqSsPKxOeeGKQs7bcMVeTYRSNrhc2yjpw==", "Properties": { "UserProfile": { "Author": "admin", "ContentItemId": "4k7hmhyywz1ettg2hkv5xpcfxz", "ContentItemVersionId": null, "ContentType": "UserProfile", "CreatedUtc": null, "DisplayText": null, "Latest": false, "ModifiedUtc": "2020-11-24T14:36:36.8629171Z", "Owner": null, "Published": false, "PublishedUtc": null, "UserProfile": { "Firstname": { "Text": "Admin" }, "Image": { "MediaTexts": [ "" ], "Paths": [ "c827782e12851cd2cf4c5161c4f5445a.jpg" ] }, "Lastname": { "Text": "Orchard" } } } }, "ResetToken": null, "RoleNames": [ "Administrator" ], "SecurityStamp": "FAALQXTJCDTD5EOVTLCW6N75CW3BOVYU", "UserClaims": [], "UserId": "4gq8jagmrtxrwvg21csa0b6y4y", "UserName": "admin", "UserTokens": []} Navigate to the CustomUserSettingsDisplayDriver and check out the GetUserSettingsAsync private method. Here you can see the way about getting the custom user settings content item from the Properties array. And after you can easily work with the properties of your content item. And that's not all of it! Check out this recording on YouTube to know more about this great, useful feature! News from the community Introducing 40 days of Orchard Core: Dojo Course 3 is here! We've been hard at work to bring you the updated, Orchard Core version of our legendary Dojo Course tutorial series, Dojo Course 3! If you want to become an Orchard Core developer, Dojo Course 3 is for you! Starting with the 1st of December we'll publish a new Orchard Core tutorial every day for 40 days on our YouTube channel. Be sure to set a reminder for the premiere video and subscribe if you want to see them just as they come out! If you're looking for our previous Orchard 1.x tutorial series check out Dojo Course 2. Orchard Dojo Newsletter Lombiq's Orchard Dojo Newsletter has 169 subscribers! 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!

4 ways to display something from your module nested within a page in Orchard Core - Orchard Core Nuggets

A common question during Orchard Core development, something that came up again recently, is how to display something within the context of an Orchard page when that piece of data comes from your module? How can you "inject" something into the Orchard layout when you want to display e.g. a list of products retrieved from an external API? There are a couple of ways to do this depending on what exactly you need. All are fairly straightforward so let's see a quick rundown! Creating a whole page from you module If you want a whole page served by just your module then it's really simple: Create a module, add a controller as you'd do in standard ASP.NET Core MVC, make an action produce a view and that's it! The view will be wrapped into the Orchard layout so the theme you've selected will be visible around it: The basic styling will be there, the header and menu, any widgets you have put onto layers... Our Training Demo module has a simple sample exactly for this, just check it out and you'll see what we're talking about. The above screenshot comes from our Open-Source Orchard Core Extensions solution BTW. Creating a widget Widgets (see official docs) are basically little boxes of content or other functionality that you can put anywhere on the site. For example, an infobox about the site, a search box, a recent articles box, a footer can all be widgets. You can use them in two ways: Add a widget to a content item with Flow Part: Flow Part can be used to build flexible layouts out of various widgets, including nesting them (like putting widgets into a Container Widget). When you set up Orchard with the built-in Agency recipe and theme then you'll get a Page content type that has Flow Part out of the box: You can get the same content type from our Helpful Extensions Orchard Core module too. Another option to use widgets is to put them onto a layer, a sort of container of widgets. There you can place the widget into a global zone, an area of the Orchard layout, like the header, footer, or sidebars. The widgets on a layer will be displayed whenever the layer rule of that layer matches (you can think of it as a logic expression producing a boolean value), like on every page except the home page or on every page but only for authenticated visitors. For more info check out the docs. OK, but how can you create a widget? A widget is just a content item whose content type has its stereotype set as "Widget". You can change this value from the admin from under Content / Content Definitions and also from code. So, basically, the task is to create a content part of yours that'll display the data you want to show from its driver, then create a widget content type where that part is attached. Seems like a lot? It isn't, check out the relevant content part development tutorial again from the Training Demo, including creating a widget. Injecting a shape into the layout This can be a lot easier than developing a widget but also less flexible to use. You can also write your own code in a template (like a cshtml Razor file) and inject that into the Orchard layout directly. You can see an example of injecting a shape in the Training Demo module too. Displaying a shape from a Liquid Widget The Liquid Widget is a widget that can render a piece of Liquid markup. While there is such a widget in the Agency theme and you can throw it together from the admin quickly too the Helpful Libraries module has it built-in as well. With this widget and Orchard's pretty advanced Liquid support you can of course just write Liquid directly. However, for more complex apps maintaining templates editable from the admin quickly becomes an issue so we'd recommend keeping code in your modules and themes. The good news is that once you have a piece of Razor code in a cshtml template (or Liquid in a .liquid template) then you can just display it from a Liquid Widget! For example, if you have a WeatherData.cshtml template fetching some weather information from an external API then you can display it from a Liquid Widget, and thus use it just as any widget with this little piece of code: {% shape "WeatherData" %} There's more to it, check out the docs on the shape Liquid tag. Conclusion Pretty much that's it. There are other ways too but these are the most straightforward and most flexible ones. Do you have another technique you'd like others to know? Add below in the comments! Did you like this post? It's part of our Orchard Core Nuggets series where we answer common Orchard questions, be it about user-facing features or developer-level issues. Check out the other posts for more such bite-sized Orchard Core tips and let us know if you have another question!