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

Featured tags

IIS
API
All tags >

Convert Themes views to shapes, Orchard Core Commerce 2.0 - This week in Orchard (26/04/2024)

Convert Themes views to shapes, improving notification filter performance, and announcing the 2.0 release of the Orchard Core Commerce project are the topics for this week! Without further ado, let's get started! Orchard Core updates Convert Themes views to shapes Before this change, there was no flexible way to contribute to the output of the Themes views. For example, there was no way to inject a "Customize" button next to the Enable/Disable button to allow customizing the appearance. The goal of this change is to convert the concrete view of the Themes view to shapes so we can inject shapes into the output. As you can see, the outcome is the same. The only difference is that people can inject shapes into variance zones now. Improve notification filter performance After the move to System.Text.Json some metrics went up. This fixed the lock contentions and large object heaps. Plus, some other GC stats are also better. The creation of JsonSerializerOptions uses a weak reference and locks internally so they shouldn't be created too often. The NotifyFilter was created then eagerly while not necessary. Now it's only on demand with some extra caching. The NotifyFilter class is a filter, meaning it will be invoked in every request. Orchard Core will invoke its OnHandlerExecuting method. And on every request, it was creating a new filter instance and it was then creating a new JsonSerializerOptions. It's creating a new one because it needs to set a dynamic converter, a converter that needs something that the DI resolves. And creating a JsonSerializerOptions object is expensive. Now you can just resolve the IOptions<NotifyJsonSerializerOptions> which class contains the JsonSerializerOptions to improve the performance. News from the community Orchard Core Commerce 2.0 Orchard Core Commerce is an Orchard Core port and partial rewrite of the open-source Nwazet Commerce module that was built for Orchard CMS 1.x. Nwazet Commerce was initially built in 2012 by Bertrand Le Roy, loosely based on a commerce sample by Sipke Shoorstra. The initial goal of Nwazet Commerce was to power the website of the hardware startup Nwazet. While Nwazet is no longer operating, the Nwazet Commerce project went on and was further developed by a group of passionate contributors who are using the platform for their own, and their customer's websites. Like Orchard, Nwazet Commerce was built with extensibility in mind, and as such, it has its own extensions (typical examples include local tax and shipping modules). It's also a pure, idiomatic Orchard. Orchard Core represents a major evolution of the Orchard design principles and is sufficiently different that running Nwazet Commerce on it will necessitate significant work. As such, the community decided that starting from a blank slate was the best way to go, so they will port Nwazet Commerce piece by piece, being careful to accurately apply Orchard Core's new design principles. The community also decided to adopt a new name that gets rid of the now obsolete origins and establishes our ambition for the module to become the go-to commerce module for Orchard Core. Back in 2022, we published a demo where you could see the current state of the module. A few months later, we reached another milestone when we finished with the basic implementation of Stripe Payment integration (without taxation, shipping, etc.). Stripe is a payment service provider that business owners can use to accept dozens of payment methods, from credit cards to buy now, pay later services. Stripe Payments handles the steps between a customer providing their card information and learning that their payment has been accepted. As you may know, Lombiq provides stewardship for the Orchard Core Commerce module. That means that we do code reviews and work on features and fixes. At the beginning of 2023, we released the 1.0 version of the Orchard Core Commerce module! You can read some of the key features of the release in this post. You can easily try it out by cloning this repo and building and running the OrchardCore.Commerce.Web project. Thanks to Auto Setup, the site will be set up with the OrchardCore Commerce - Development recipe. After you can go to the dashboard, using the credentials admin and Password1!, and go to Content -> Content Items to create your first Product. And one month ago, we released the 2.0 version of the Orchard Core Commerce module! This is a major version increment because there are some breaking changes, so watch out! We have upgraded to the latest Orchard Core 1.8 packages too. One of the breaking changes is that the built-in Stripe payment provider support has been separated into a standalone package. For now, it's still the only payment processor we support out-of-the-box but we plan to expand that in the near future. You can also implement your custom payment providers. We've also added product listing with filter, search and paging support, the ability to resume payment from the order page, more documentation including a guide, and many other improvements, bug fixes, and quality of life improvements. Check out the full list of changes here. Orchard Dojo Newsletter Lombiq's Orchard Dojo Newsletter has 470 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!

Tenant APIs improvements, MiniProfiler visibility based on permissions - This week in Orchard (10/03/2023)

The MiniProfiler visibility is now based on permissions, tenant APIs improvements, and using ListContent permission instead of ViewContent to see the content items list. Interested in the details? Well, then check out our post for the details! Orchard Core updates MiniProfiler visibility based on permissions MiniProfiler is a library and UI for profiling your application. By letting you see where your time is spent, which queries are run, and any other custom timings you want to add, MiniProfiler helps you debug issues and optimize performance. And MiniProfiler is a built-in feature in Orchard Core that has some updates. Let's say an admin needs to profile a production instance. Enabling MiniProfiler for everyone to see is not acceptable in most cases. Also, a user may want to profile an ajax request to analyze a query or other info. The solution here is to provide new permissions when you enable the Mini Profiler feature. If you navigate to Security -> Roles and hit Edit near any role, you can see the new permissions which enable you to view the Mini Profiler widget on the back end and the front-end pages. Tenant APIs improvements Orchard Core tenant APIs got several updates lately. First of all, there was no way to update some tenant settings from API calls. Meaning, the tenant's category was missing in the ApiController. We should be able to set a tenant's description via an API call, so this property is now included in the sent model. Another improvement is the new Edit endpoint because there was no way to update tenant settings from API calls. The new Edit endpoint in the Tenant's ApiController takes care of this and enables us to modify the tenant settings either if the tenant is uninitialized or running. The updateable properties are the same as what is displayed on the tenant editor on the Admin UI. Use ListContent permission instead of ViewContent Currently, we use View Content as a minimal permission to list the contents in the content items UI. We use the same permission to also show the Content -> Content Items admin menu. Here the List Content permission should be used, not View Content. What if someone wants to allow a user to view content using a direct link but does not want them to list the contents in the UI? Currently, this isn't possible. Additionally, ViewContent is granted to all Anonymous and Authenticated roles, which is fine. But, access to list contents should be granted by the ListContent permission to avoid allowing listing content without explicit permission grant. If you check out the change logs in the docs of the upcoming release, you can read about the updated permissions. News from the community Hastlayer is now fully open-source Hastlayer is being developed by Lombiq Technologies, a software, training, and services company focusing on web development with open Microsoft technologies. Hastlayer transforms .NET software into FPGA-implemented logic circuits. (FPGAs are chips that can mimic other chips.) The result is code that runs faster and uses less power than a code-only solution without sacrificing the ability of further developing your software. Using Hastlayer will optimize your performance and lower the power consumption of hardware, which will bring you more satisfied customers who want your solution and a boost in your company’s revenue. And we have just recently fully open-sourced Hastlayer! Orchard Dojo Newsletter Lombiq's Orchard Dojo Newsletter has 410 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 about Orchard and the details of the topics above, don't forget to check out the recording of this Orchard meeting!

Fluid Functions, Lombiq Training Demo updates - This week in Orchard (04/02/2022)

Fluid provides optional support for functions, optimizing SlugServie and MediaTokenService, and hiding a shape sample in the Lombiq Training Demo for Orchard Core are the topics of this week. Check out this post for the details! Orchard Core updates Optimize SlugService The goal here was to increase the performance of the SlugService as much as we can. Marko Lahma went through the thing, and the problem was that the ZString haven't been used, it was leaking all the time thus bad perf. BenchmarkDotNet=v0.13.1, OS=Windows 10.0.22000AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores.NET SDK=6.0.101 [Host] : .NET 6.0.1 (6.0.121.56705), X64 RyuJIT DefaultJob : .NET 6.0.1 (6.0.121.56705), X64 RyuJIT General easy rule (when implicit usings aren't being used): If there's using System.Linq; at the top of the performance-critical code, try to find a way to get rid of it. Reduce allocations in MediaTokenService and improve performance Since Orchard Core can use all .NET 6 goodness, Marko Lahma improved the performance of the MediaTokenService too. And what did he change? Don't ToString StringValues, keep them as-is as in dictionary once built. Parse commands and other commands with one pass using constructs that QueryHelpers uses. Allocate static command arrays to return from processors, some of them were always returning new ones. Change GetHash not to allocation anonymous closure each time by using Try/Set construct, using the same logic that extension method GetOrCreate uses. Change hash calculation to use stackalloc and spans for workloads < 1024 bytes (common case). Use the custom AddQueryString method that understands spans and concrete a dictionary type. Here you can see the numbers: Before: After: Fluid functions Now there is a new optional feature in Fluid called Functions. It's optional because the goal of Fluid is to be able to as close as possible to Liquid, but Liquid doesn't have Functions. If you open up the readme file of Fluid, you can see a new section there called Functions. There is a new option in the FluidParser that you can say AllowFunctions. By setting the AllowFunctions to true, you can call functions like this: {{ field('user') }}. The format of this one is {{ identifier('arguments')}} just like in JavaScript or in C#. Or you can say {{ field('pass', type='password') }} which is like {{ identifier('values', type='named value') }}. How it works is a field is an object, a FluidValue of type FunctionValue. In Fluid, anytime we access something like {% assign name = "Bill" %}, the name is a FluidValue, and in this case, the type is StringValue. When we find a field, the field is a value of type FunctionValue, and the FunctionValue implements the method name InvokeAsync. There is InvokeAsync, there is GetPropertyAsync, etc., everything that an object can do, and when this FunctionValue's Invoke is invoked it will execute something. And to invoke a FluidValue you use parenthesis. If we do parenthesis on something like name, which is a string, we can do name(), it will invoke the string, but it just returns NIL value. So you can invoke name if you want, it will not do anything. If it's a function, it will do something. To learn more about Functions, check out the documentation of Fluid! News from the community Hiding a shape sample in the Lombiq Training Demo for Orchard Core The Lombiq Training Demo for Orchard Core is a demo Orchard Core CMS module for training purposes guiding you to become an Orchard developer. And the module has a new sample, which is a shape table provider that hides shapes. Check out the code of the new ShapeHidigingShapeTableProvider here! Orchard Dojo Newsletter Lombiq's Orchard Dojo Newsletter has 240 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!

Extension method for site setting deployments, optimize AddTokenToPath - This week in Orchard (11/08/2021)

The development of Orchard Core of course has been continued after the release of 1.0. Are you curious about the newest improvements of Orchard Core? Well, let's check them out! Orchard Core updates Optimize AddTokenToPath The community tries to make Orchard Core as optimized and as fast as it could be. C# and ASP.NET Core with other third-party libraries offer several tricks and techniques to achieve this goal. For example, you can use ZString instead of StringBuilder. 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. Or another improvement could be to not use string.Split that causing array allocation, use sub-strings instead. Add extension method for site setting deployments Whenever you would like to add a deployment step that allows exporting your custom site settings, you need to register three services with the transient, scoped, and singleton lifetimes. It's not a huge issue though because you can easily open a Startup.cs file of a module that contains these kinds of registrations and see how you can do it in your module but having an extension method for site setting deployments could reduce the lines that you need to copy every time. The new AddSiteSettingsPropertyDeploymentStep allows you to register your custom site settings deployments just in one line! Check out the Startup.cs files of any module to see the new way of registering your site setting deployments. Not use a current culture when checking invalid Autoroute path If you enable the Localization module and set a default culture to a non en-US culture, e.g. th-TH, you will get a validation error when trying to save a content item with an Autoroute part. Here is the screenshot when trying to save a content item and a default culture is th-TH. This issue is from AutoroutePartExtensions.ValidatePathFieldValue method. Inside the method, there is a call of autoroute.Path?.IndexOf("//") which returns 0 instead of -1 when a current culture is th-TH. According to the MS document, IndexOf(String) searches a word by using culture and case sensitivity. Refer to Recommendations for string usage, we should use StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase for the non-linguistic string search. There is no case in a space and //, so the fix here was to pick StringComparison.Ordinal and to match IndexOf which is case-sensitive. Cypress 8.2.0 and Trumbowyg 2.25.1 If you are a huge Orchard Core enthusiast and like to check out the recent changes of the CMS from time to time, you may notice that the community upgrades the different .NET/Client-side/Test libraries from time to time. The Orchard Core documentation has a Libraries page under the Resources one where you can see the versions of these libraries that are used across the main branch of Orchard Core. Antoine Griffard recently updated Cypress to 8.2.0 and Trumbowyg to 2.25.1! With these updates, you can make sure that if you would like to use the built-in libraries of Orchard Core you will always have the latest versions! News from the community Lombiq Technologies: new developer colleagues and new group photo We had a great company trip last week where almost all of the colleagues of Lombiq gathered together to spend the whole day together offline. This was a great opportunity to update our group photo on our official website, so head to the About us page where you can see the most, but not all of our team as of the summer of 2021. And we also have two new colleagues to introduce: Ádám Jakab and Szabolcs Deme. Don't forget to check out their bio too! DotNest Core DotNest Core is a complete redevelopment of the DotNest platform, all on the latest version of Orchard Core. We've been running it with a couple of select few customers for a while now, and it's time to open it up a bit more. While you can't yet just simply create an Orchard Core-based DotNest site, you can sign up for our limited beta here. You'll soon be able to get a fully functional, reliably hosted Orchard Core site on DotNest where you can build your personal website or something to showcase your Orchard skills with. Orchard Dojo Newsletter Lombiq's Orchard Dojo Newsletter has 215 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!

Admin Layers, move admin branding to a distinct shape - This week in Orchard (31/05/2021)

A new demo about Admin Layers, several performance improvements, and a new AdminBranding shape is waiting for you in our upcoming post! Oh, and have we mentioned that Lombiq is now more than 8 years old? :) Orchard Core updates Move admin branding to a distinct shape Now you will find a brand new AdminBranding shape in the default admin theme of Orchard Core that is about containing the favicon, and the default, clickable Orchard Core logo that redirects the user to the home page of the admin UI (the admin dashboard). And now, because this whole stuff is a new shape, you can easily customize this one too! Check out the updated documentation to read some words about it! Fix drop widget to empty zone Someone reported an issue that it's not possible to drag and drop widgets to an empty zone. Now, this has been fixed. Remove some async-await It's not a recommendation anymore to remove async/await when it can be removed. In this case here the Task from _session.Query can be returned directly, it doesn't have to be awaited. There is a benefit in terms of allocations but at the same time if there is an exception you lose where it's coming from. And the impact in terms of perf is minimal. So, it's not a recommendation to do it every time. Adding spatial features to content (Lucene indexed) You could see a demo a few weeks ago about adding spatial features to content. This module provides a GeoPointField, which can be used to give a geographic position to content. And this has been merged to the dev branch of Orchard Core with nice detailed documentation also! Don't forget to check out the docs to read about Terms specifications, Geo Bounding Box, Geo Distance, and many more! Reduce string allocations in MediaTokenService and use pooled StringBuilder instances with StringWriter Now the code is using StringBuilder from the StringBuilderPool instead of using String.Concat(), which is about allocating one buffer. There are other improvements with the queryStringTokenKey, which is not recreating a new string every time, just using the existing one. StringWriter will create a StringBuilder by using the StringBuilderPool and passing that to the StringWriter. It's not creating a new one, it's just reusing the existing one. Demos Admin Layers People want to change shapes or styles for various things on the admin UI without having a custom admin theme. For example, currently, it is not possible to change the admin panel's logo and icon without a custom theme (set the base theme to Admin). The new Admin Layers feature is very similar to the Admin Templates one. This new one is about working with the layers of the admin theme, which means you can put different kinds of widgets to the zones of the admin theme using different kinds of layers rules. Sounds familiar? Yeah, that's the goal of this one! Let's say you set up your site using the Blog recipe. After go to the Configuration -> Settings -> Features and find the Admin Layers one which enables admin users to render widgets across pages of the admin based on conditions. Now, the first thing to do is to set up the available zones for widgets as you would do that in the case of the front-end widgets as well. You can do that under Design -> Settings -> Admin Zones. Let's say we would like to work with the HeadMeta, Footer, Header, and NavbarTop zones for now. And let's add some layers too! If you navigate to Design -> Admin Widgets, you could see a very-very familiar UI to manage zones and layers. Here we have just added a simple layer called Always that has one boolean condition rule, which is True. Meaning the widgets on this layer will always be rendered. Now, let's do some testing! Let's say we add a simple Raw Html widget to the NavbarTop zone that is just about containing some bold text. After we will create a new Liquid Widget and render it in the HeadMeta zone. The Liquid Widget is just about containing some styling for the theme: {% styleblock at:"Head"%} body { background-color: gray !important; }{% endstyleblock %} Now we have created two widgets that we have displayed on the Always layer of the admin theme. Let's see how our theme looks like! First, notice the Some text in the NavbarTop zone near the moon icon. This is the Raw HTML widget in the NavbarTop zone. And the new background color is coming from our Liquid Widget. If you would like to know more about this feature, don't forget to check out the following recording on YouTube! News from the community Lombiq is 8 years old! May 21, 2021, is the 8th anniversary of founding Lombiq! On this special occasion, we have gathered 8 important factors of our company's life. We are glad that we took these steps at that time otherwise we might not be able to celebrate with our strong community here today. Check out our post to see which important factors we gathered together! Orchard Dojo Newsletter Lombiq's Orchard Dojo Newsletter has 202 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!

Distributed cache, YesSql improvements - This week in Orchard (09/10/2020)

Get ready for the upcoming additions of Orchard Core! This week you can see demos about adding distributed cache to Orchard Core and the latest improvements of YesSql! Orchard Core updates Restrict Content Types Admin Node to only create the specified content type Let's say you have a site set up with the Blog recipe and your goal is to add a new admin menu that is about to list all the existing Article content items. To do that you have to navigate to Configuration -> Admin Menu and click on the Add Node button. Here you will need to select the Content Types Admin Node because you would like to add a link for each one of the selected content types from the list. And yes, you will only select the Article content type in the next screen. And that's it, you have created a new admin menu that lists all of the Article content items from the system. If you click on that you will see the list of the Article content items. Note that from now you will not see a filter here to filter by content types because that really doesn't make much sense. You created this Content Types Admin Node to list only the Article content items. If you would like to filter by content type, use the filter in the Content -> Content Items page as you would do it before. Remove ManageOwnMedia permission The ManageOwnMedia permission isn't handled today and we can replace it with the ManageMedia permission. So, now there is no separate permission to manage their own media or manage the media. It will be added back to the system again if the authorization logic is to be able to separate the user's media and the media uploaded by others. Right now, if you head to Security -> Roles and hit the Edit button near one of the Roles, you will see the missing Manage Own Media permission. Use a dropdown menu on the admin top navbar for the user menu The user menu on the admin theme is just about listing the user name of the currently logged-in user with a little icon near it. And there was a Log off link at the right side of the user name. It was just a piece of HTML code in the Layout.cshtml file of the default admin theme. From now the whole user menu gets its own shape, called UserMenu.cshtml. If you would like to override the user menu, you don't need to create a Layout.cshtml file in your admin theme, you just need to override the UserMenu.cshtml file. And if you check out this GIF, you will see that now the content of the user menu is placed in a dropdown instead of having it's content inline. Document release procedure Orchard Core is open-source that means you can check out and request changes to the source code, tell your opinion about it, and update the relevant documentation for that piece of code. Now there is a new page in the Orchard Core Documentation about how to publish a new Orchard Core release. These notes are primarily for Orchard's core contributors to guide them on how to prepare a new release, but if you just take a look at these you can now see what are the steps that need to be done to be able to ship a new release of Orchard Core. Add option to allow Lucene query syntax in the search There is a setting in the Lucene module that you can turn on and whatever you type in the search box won't be parsed as 'these are the words that I need to search on', but parsed as this is a Lucene query. You can use the Lucene query syntax in search forms, like the quotes, plus signs and things like this. But by default, it will still be like today, which is type words and it looks for these words. If you type stuff that has weird characters, they would be ignored. But how can I turn on that feature? Just navigate to the admin UI of Orchard Core, then find the settings in Search -> Settings -> Search (make sure you have enabled the Lucene module). Demos YesSql: Adding support for multi filters on the same table index There is a new method on .IQuery called .Or(), which starts a filter on a distinct table. This can be used for instance when the same index needs to be queried for multiple sets. It will actually add many filters on the same record of the index. With .Or() you can do now filters on different inner joins of the same index. Which means it will do unions of result with the filters. Let's check out the following unit test. [Fact]public async Task ShouldQueryMultipleIndexes(){ // We should be able to query documents on multiple rows in an index. // This mean the same Index table needs to be JOINed. _store.RegisterIndexes<PersonIdentitiesIndexProvider>(); using (var session = _store.CreateSession()) { var hanselman = new Person { Firstname = "Scott", Lastname = "Hanselman" }; var guthrie = new Person { Firstname = "Scott", Lastname = "Guthrie" }; session.Save(hanselman); session.Save(guthrie); } using (var session = _store.CreateSession()) { Assert.Equal(2, await session.Query<Person>() .With<PersonIdentity>(x => x.Identity == "Hanselman") .Or() .With<PersonIdentity>(x => x.Identity == "Guthrie")) .CountAsync()); }} This PersonIdentitiesIndexProvider will just map the first name and the last name of each person. We created two persons here that means the provider will be creating four PersonIdentity records. Two to store the first name and last name for Scott Hanselman and two to store the first name and the last name of Scott Guthrie. If without using the .Or() it will issue a query where the Identity == "Hanselman" AND Identity == "Guthrie", which will return nothing. With the .Or(), it will do an inner join on PersonIdentity with a filter on Identity == "Hanselman" and another inner join on PersonIdentity with a filter on Identity == "Guthrie". In this case, we will get both the two persons. It's like give me any person who's PersonIdentity has a record like Hanselman OR where the PersonIdentity equals Guthrie. Let's see another example! // Creating articles.using (var session = store.CreateSession()){ session.Save(new Article { Content = "This is a green fox" }); session.Save(new Article { Content = "This is a yellow cat" }); session.Save(new Article { Content = "This is a pink elephant" }); session.Save(new Article { Content = "This is a green tiger" });}using (var session = store.CreateSession()){ Console.WriteLine("Boolean query: 'pink or tiger'"); var boolQuery = await session.Query<Article>() .Where(x => x.Word.IsIn(new[] { "pink" })) .Or() .Where(x => x.Word.IsIn(new[] { "tiger" })) .ListAsync();} This is a full-text sample where in this case we can query documents by their content, by their words. If we have the documents as you could see in the code, we are creating an index for all the words pointing to the document, like green and fox points to the first Article. Now the query is about to give me all the documents that have pink in it OR all the documents that have tiger in it. The goal is to get the last two articles in this case. Let's check out this query: var boolQuery = await session.Query<Article, ArticleByWord>().Where(a => a.Word.IsIn(new[] { "white", "fox", "pink" })).ListAsync(); This query will return all the articles that have white AND fox AND pink in it and there is none in this case. You can find the recording of this improvement on YouTube! But wait a minute! Could this syntax be better? When you have a query, you can say .Any() and every predicate will be done in a different table. .Any() being an OR, and .All() being an AND. In this case, we're querying on the same index (ArticleByWord) but each of the calls on .With() will be a different projection, a different inner join in this case. Here we are looking for an article that has the word pink OR the words green AND fox. await session.Query<Article>() .Any( x => x.With<ArticleByWord>(a => a.Word == "pink"), x => x.All( x => x.With<ArticleByWord>(a => a.Word == "green"), x => x.With<ArticleByWord>(a => a.Word == "fox"))).ListAsync(); SELECT DISTINCT Document.* FROM DocumentINNER JOIN ArticleByWord_Document AS [ArticleByWord_Document_a1] ON ArticleByWord_Document_a1.DocumentId = Document.IdINNER JOIN ArticleByWord AS [ArticleByWord_a1] ON [ArticleByWord_a1].Id = ArticleByWord_Document_a1.ArticleByWordIdINNER JOIN ArticleByWord_Document AS [ArticleByWord_Document_a2] ON ArticleByWord_Document_a2.DocumentId = Document.IdINNER JOIN ArticleByWord AS [ArticleByWord_a2] ON [ArticleByWord_a2].Id = ArticleByWord_Document_a2.ArticleByWordIdINNER JOIN ArticleByWord_Document AS [ArticleByWord_Document_a3] ON ArticleByWord_Document_a3.DocumentId = Document.IdINNER JOIN ArticleByWord AS [ArticleByWord_a3] ON [ArticleByWord_a3].Id = ArticleByWord_Document_a3.ArticleByWordIdWHERE ArticleByWord_a1.Word = 'pink' OR ( ArticleByWord_a2.Word = 'green' AND ArticleByWord_a3.Word = 'fox') Here you can also see how does the query look like. That's the current syntax right now. And it's not breaking change because if you had .query.With().With(), it will still behave the same as before. It will be a single projection with two .With(). If you project on the same table it will be an AND on the same table, the same projection.If you would like to know more about the current syntax and the latest features, this is the YouTube video that you are looking for! Distributed Redis Cache There is a huge PR with a great addition to Orchard Core about having Distributed Cache in Orchard Core. In fact, this PR is related to all modules services using cached data that need to be in sync with a shared database, e.g. site settings and other admin settings, templates, queries, layers, and so on. Currently, in the dev branch, this is already the case for most of them but by using local memory cache entries that are invalidated through a distributed signal. This PR has changed this pattern by using a multi-level cache including a distributed cache. And Jean-Thierry Kéchichian made more services to be distributed aware, there are remaining things but could be done in other PRs. And recently he added the ability through a separate feature to also keep in sync tenant states, when you enable/disable/create/setup a tenant. To summarize: All services that were using the memory cache are now using a multi-level cache including a distributed cache, this cache is kept in sync with the database without using a message bus but document identifiers. We have concrete implementations of distributed services based on Redis, Redis cache, Redis data protection, Redis message bus, and Redis lock. In the current code, we never use a message bus/distributed lock. When enabling a concrete distributed cache in the default tenant, we automatically keep all tenants in sync through a hosted service, here also without using a message bus but identifiers. Here a stateless configuration is needed. There is a separate feature (DefaultTenantOnly) to keep tenants in sync. Don't forget to head to YouTube and watch the recording of this upcoming feature! News from the community Orchard Dojo Newsletter Lombiq's Orchard Dojo Newsletter has 160 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!

Content Picker Menu Item, Kast case study - This week in Orchard (02/05/2020)

Soon you will able to show content items in your menu easily! How? Check our newest This week in Orchard post and read about an amazing demo to see the new Content Picker Menu Item in action! We published a brand new case study this week on our website about the latest Orchard Core site we developed. By reading that study you can see the possibilities that you can easily achieve by using Orchard Core as your CMS! Don't forget to read our whole post for the most interesting news around the community! Orchard Core updates Added ability to restrict widgets within a flow part You can use the FlowPartSettings to give content managers the capability to restrict which widgets are available within the flow editor. If no widgets have been selected then all widgets will be available, as per the current implementation. Let's see it quickly! Set up a site with the Blog recipe and then edit the content type definition of the Page content type. To do that navigate to Content -> Content Definition -> Content Types and choose the Page. Then find the attached parts and hit Edit near the Flow one. Here you can select which content types this flow can contain. Just for the sake of demonstration, we say that the Flow editor of this page can only accept Liquid widgets. Let's see what will happen when we create a new Page! Hit New -> Page and try to add something to the Flow editor. You will see that only the Liquid one will be on the list because in the previous step we only allowed Liquid widgets. So, when you attach a FlowPart now you can decide what content types you want to be able to use in a FlowPart. It can be useful if you create a form page type with a FlowPart for it. You could then decide just to allow for form widgets. Remember: if you don't select anything you will be able to use any type of content type with the Widget stereotype in your editor. Added support for IN (SELECT) SQL statements You can use a custom SQL statement, that is about to parse for the queries module, the one that uses the generic SQL language and that will be translated to any dialect that the CMS supports (PostgreSQL, MySQL, SQLite, Microsoft SQL Server). If you use this language now you can use the select expression inside an in statement. It's also supporting the not in correctly and the like and not like was not working well, so these are also fixed. Check the new InlineData attributes added to the ShouldParseExpression test method to see some examples with the new expression. Fix shape table providers There was an issue with the way you would be able to override a template from your module, override a template for a dependent module. This change will look for shape templates in a module for any first-level dependencies and it's also improving performance because there would be fewer shape templates loaded in the memory. And if you have a feature depending on another feature then it won't be able to override the second level feature, you have to depend on that. It makes sense because you are creating a template for the second level feature, so you can depend on that because you expected that it would be there. Deployment plans search Let's navigate to Configuration -> Import/Export and create one or more deployment plans. Here you can filter the deployment plans and also do bulk actions. To do bulk actions select two or more deployment plans and after that, you will see the Delete option in the Actions dropdown. Content culture picker shape documentation If you navigate to the Content Localization section in the Orchard Core documentation, you may have noticed that there were no Razor example codes. From now the documentation has been improved with Razor examples! Demos Content Picker Menu Item Let's set up a site with the Blog recipe, create a new Page, and call it My brand new page. Then choose the Main Menu option in the admin UI and hit the Add Menu Item button. Here you could see the Available Menu Items modal window with two options: Link Menu Item and Content Picker Menu Item. Let's choose the second one! The Content Picker Menu Item is about having the ability to choose from the content items available in the CMS with a content picker. There is the Selected ContentItem dropdown, that can be used to select the content item that you would like to show on the menu. You can type to search or just simply select your item from the list. We will select our newly created page here. Publish the menu and navigate to the homepage of your site to see your menu. We placed the new menu item after the About, but of course, it's your choice to set the position of your menu item. If you are interested in the full demo don't forget to watch the recording on YouTube! Note that this feature is under development and can be found in this branch! News from the community Orchard Nuggets: How to add a culture URL segment for localization in Orchard Core So you're building a localized Orchard Core site and want all URLs to be in the form of /culture-name/rest/of/the/url, e.g. /hu-HU/my-page. What do you need to achieve this? In our newest Orchard Nuggets post, we give you the answers! Check out the other posts for more such bite-sized Orchard tips and let us know if you'd have another question! Helping Kast build a multi-tenant platform on Orchard Core Kast is an Australian company and one of their primary goals is to implement the Kast platform with the Kast Group Finder component. We worked together with Seth Cleaver (Co-founder and Director of Kast) on this tool to be able to create an intuitive self-service process that enables people within a church to easily find a suitable group to attend, simplify the administrative processes required for getting people into groups, and provide information to the group co-ordinators that might assist in planning and measuring effectiveness. Check out this case study about how we've developed this multi-tenant social group management platform for churches! 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 Core Training Demo module: combining ASP.NET Core Options with Orchard Core site settings Our Orchard Core Training Demo module has a new tutorial on combining ASP.NET Core Options with Orchard Core site settings. In the SiteSettingsController you could see how to use the Site Settings to access tenant-level settings and any other custom settings! Orchard Core Training Demo module is a demo Orchard Core module for training purposes guiding you to become an Orchard developer. You can use this module as part of a vanilla Orchard Core source that including the full source code - which is the recommended way. You can use it as part of a solution the uses Orchard Core NuGet packages, however, it's harder to look under the hood of Orchard Core features. The module assumes that you have a good understanding of basic Orchard concepts and that you can get around the Orchard admin area (the official documentation may help you with that). You should also be familiar with how to use Visual Studio and write C#, as well as the concepts of ASP.NET Core MVC. Bug reports, feature requests, and comments are warmly welcome, please do so via GitHub. Feel free to send pull requests too, no matter which source repository you choose for this purpose. Updated Lombiq Technologies logos You may have noticed that we rolled out our updated logo in the last few days. The spirit is the same: The lab flask with which we distill our IT solutions ("lombik" in Hungarian means lab flask :)). So, please welcome it! Orchard Core workshops The contributors of Orchard Core will hold some unique online workshops in the coming months, between May and September 2020. So even with Orchard Harvest postponed due to the coronavirus pandemic we'll get some new learning events. Lombiq's developers will also give two workshops, on using Orchard from the admin UI and on developing a module. Are you looking to get up to speed with Orchard? Check out the workshops' details on the Orchard Core homepage! Orchard Dojo Newsletter Now we have 140 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!

Scoped Liquid TemplateContext, Template Azure Blob with Liquid - This week in Orchard (10/01/2020)

Updated Trumbowyg plugin, configure your Azure Blob and Data Protection with Liquid and many more improvements are waiting for you in our upcoming post. Did we forget to mention that Orchard Core is now on the Area 51 site of Stack Exchange? Orchard Core updates Upgrade to ASP.NET 3.1.0 Orchard Core now using the v3.1.0 of the ASP.NET Core framework. This version is included in Visual Studio 16.4.0, so if you are using at least this version, you can build your own solution using Orchard Core. Here you can read a great article about the new features of this release. Validate Site Settings BaseUrl property You can set up the BaseUrl of your site under the Configuration -> Settings -> General on the dashboard. If you enter a URL, which is a not fully qualified URL, you will get a validation error. So, here you need to enter an absolute URL. Template Azure Blob and Data Protection blob configuration with Liquid You can customize the base path of the Blob Storage for media, for data protection and also for the container that you want to use. These can use a template in Liquid. If you have some custom rules to name your container or the base path, then you can define it this way. You can have access to the ShellSettings and the ContainerName properties. This will give you all the flexibility you need for instance to use the same container for all your tenants and then use a custom folder for each tenant. Or a custom container for every tenant and then the same path. You can also ask for the container to be created and it will be done when the tenant is started. You can read more about this feature in the documentation. Trumbowyg 2.21.0 and resizimg plugin Orchard Core using Trumbowyg, a lightweight WYSIWYG editor, that makes easier to edit your HTML content. Thanks to Antoine Griffard, Orchard Core now using version 2.2.10, including the resizimg plugin. For instance, when you set the Trumbowyg as the editor type of your HtmlBodyPart and insert a media with URL, you can just simply set the size of the image with a friendly user interface. Use GetLanguageDirection method everwhere When you check the content of the OrchardCore.Localization.Abstractions module, you will find a GetLanguageDirection extension method in the LanguageDirection static class. This method is used to get the language direction for a given culture. So, if we have an extension method like this, let's use it everywhere in the code. For example, check the code of the CultureDir extension method in the RazorHelperExtensions class, where you can see an example usage of the GetLanguageDirection method. Fix localization accessors names In ASP.NET Core we have many ways to use localization. The IStringLocalizer interface represents a service that provides localized strings and the IHtmlLocalizer interface provides localized HTML content. In Orchard Core, we used T, S, H, TS and TH, so it's time to unify these names and avoid the confusion. From now in the source code, the S will be used for the IStringLocalizer and the H will be used for IHtmlLocalizer. When localizing the views, the name T will be used like before. Scoped Liquid TemplateContext The goal was to improve perf on the Liquid rendering because we were creating a new TemplateContext and resolving all the services for each template. Here the idea is to create a shared Liquid TemplateContext only once per scope for perf, on which we do once a shared contextualization and then a specific contextualization before each rendering. Shared contextualization is done once per scope: add scoped services in ambient values, call Liquid handlers to add more ambient/scope values and access strategies, add all scoped Liquid filters. Specific contextualization before each rendering: e.g contextualize the localizer with the current view context, update the specific model value and its access strategy. And there are many places where we add a ContentItem value to the Liquid scope, we could remove them because now most of the time it is already accessible through Model.ContentItem. Here you can see that there is no need to create a new TemplateContext and add the ContentItem and the Model to it, it's enough to just simply pass the MarkdownBodyPartViewModel. News from the community Orchard Core on Stack Exchange Area 51 Area 51 is the Stack Exchange Network staging zone, where users come together to build new Q&A sites. New site ideas are proposed, discussed, and the best go on to beta. There is a request on Stack Exchange to create a custom community for Orchard Core. If you want to create a community you have to push for an idea and people need to approve and follow that idea. For this, it's needed more people to join and every person can vote for 5 questions and create 5 questions so, the community can continue on to the next stage. If you are interested in, check the FAQ of the Area 51 site and feel free to join the proposal! Orchard Nuggets: How to use Orchard Core without the sample themes? Try to reference the OrchardCore.Application.Cms.Core.Targets NuGet package instead of the OrchardCore.Application.Cms.Targets in your ASP.NET Core web application, that will only add the TheAdmin theme to your solution. In our third Orchard Nuggets post, we show you the differences between these two packages! Check out the other posts for more such bite-sized Orchard tips and let us know if you'd have another question! Orchard Dojo Newsletter Now we have 112 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!

This week in Orchard - 09/20/2019

Now we are in the finish line of releasing the RC version of Orchard Core! Read our post for the most important things you should now about the new release and for a quick demo about the Cypress e2e testing suite for Orchard Core! On Orchard Core Some performance tweaks Marko Lahma, the author of Quartz.NET made a lot of performance tweaks to make Orchard Core much faster. Let's see some of those! In the RunningShellTable.cs use host.IndexOf(':') != -1 instead of host.Contains(':'). And a same performance tweak here: instead of extensions.Contains(Path.GetExtensions(content.Name)) use Array.IndexOf(extensions, Path.GetExtension(content.Name)) != 1). Also, do not use dynamic typing when it's not necessary. Instead of context.AmbientValues.TryGetValue("LiquidPage", out dynamic page)) use context.AmbientValues.TryGetValue("LiquidPage", out var page));. He also created a project called OrchardCore.Benchmarks, where you can run some microbenchmark. Migrate all Content Type / Part / Field Settings to Settings<T> There were some places in the recipe where settings were not under the Settings property. Check the blog recipe for an example. Here you can see that everything is in its namespace. You can find a Settings property and inside that, there is a ContentTypeSettings property. By organizing all the settings in their correct path we get a cleaner recipe file. Add default paging to GraphQL queries We have three different new properties: DefaultNumberOfResults: if you don't say first or last in your queries, we will limit the results to 100. MaxNumberOfResults: if you define first of last (give me the last 2000 content items) we will block you and limit to 1000. If you didn't provide a first or last, and if you have so many results, it will slow down your system considerably. You should always page your queries, otherwise, you could load everything on the memory of the server and if you have multiple clients you will get out of memory or the performance will be slow. MaxNumberOfResultsValiadtionMode: specify the validation behavior if the max number of results is exceeded in a pager parameter.* Default: in production info will be logged and only the max number of results will be returned. In development, a GraphQL validation error will be raised.* Enabled: a GraphQL validation error will be raised.* Disabled: info will be logged and only the max number of results will be returned. Migration to .NET Core 3.0 RC Now Orchard Core using .NET Core 3.0 RC, which means you need to install the new SDK (v3.0.100-rc1) to work with Orchard from here. Sebastien pinned the 1.0.0-beta3-72452 version on MyGet, so it won't be deleted, and it is the last one targeting .NET Core 2.2. Don't forget: to work with Orchard Core, you will need Visual Studio 2019! Orchard Core RC release The plan is to ship Orchard Core RC next Monday, maybe on Tuesday, after .NET 3.0 is released, so we can update the dependencies and build the things. It will be on NuGet.org as well. This release will be followed with the 1.0 (RTM) version. The number of the new package will have the following convention: OrchardCore 1.0.0-rc1-10000. The build number will be reset to 10000 and will be increased by one with every build of the dev branch. We need to update the documentation and the ASP.NET package references. There will be blog announcements: they could be in the Asp.net site right after the .NET Conf. Try Orchard Core will be updated to use the new version too. Demos Cypress e2e testing suite for Orchard Core Cypress is a next-generation front end testing tool built for the modern web. Cypress is most often compared to Selenium; however, Cypress is both fundamentally and architecturally different. Cypress is not constrained by the same restrictions as Selenium. This enables you to write faster, easier and more reliable tests. To use Cypress in Orchard Core first you have to run npm install in the test/cypress folder. Then followed by npm run test to build and host Orchard and run Cypress test on it. This will execute the content of the test.js file. First builds the solution, starts the application and starts the different Cypress tests. The test cases are in the integration folder, where now you can find three different cases: Setup SaaS Blog Agency Let's check the content of the Blog one. You can find a file called 01-setup-blog-tenant.js. Here you can find different test methods, like cy.login(), or cy.gotoTenantSetup(blog). These functions are defined in the commands.js file within the support folder. Let's check quickly what are these methods for! As you can see, using some predefined command, you can easily navigate to a given URL using visit. You can target the different HTML elements using get and submit a form easily as you can see in the screen above. The Cypress testing suite for Orchard Core is in its early stage, but you can find the current state and follow the development in this branch. On Lombiq Orchard Dojo Newsletter Now we have 90 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!