Template for a content type with a name that starts with an string

Topics: Core
Jan 25, 2015 at 9:26 AM
Edited Jan 25, 2015 at 9:34 AM
I was looking for an elegant way of defining a template for those content types which contain a ContentPart named ProductPart

Currently I override the default Content.cshtml with another version which includes an if like this:
@if (contentitem.Parts.Any(p => p.PartDefinition.Name == "ProductPart"))
However I would prefer to have a separate template for this case and using the default Content.cshtml for the sake of simplicity.

Today looking for a way to match a content type which includes that part through Placement I have found an interesting code in Orchard default ShapePlacementParsingStrategy. It allows to use an * with a ContentType match. So it will match all the ContentTypes with a Name that starts with the name before the * character.

This is the code:
public static Func<ShapePlacementContext, bool> BuildPredicate(Func<ShapePlacementContext, bool> predicate, KeyValuePair<string, string> term) {
            var expression = term.Value;
            switch (term.Key) {
                case "ContentType":
                    if (expression.EndsWith("*")) {
                        var prefix = expression.Substring(0, expression.Length - 1);
                        return ctx => ((ctx.ContentType ?? "").StartsWith(prefix) || (ctx.Stereotype ?? "").StartsWith(prefix)) && predicate(ctx);
                    return ctx => ((ctx.ContentType == expression) || (ctx.Stereotype == expression)) && predicate(ctx);
So in placement I can use something like this:
<Match ContentType="Product*">
This is very powerful and indeed I can make that all those content types that contain a ContentPart Product, should be named with "Product" prefix to address what I pursue.

However what I cannot find is a way of doing something similar with templates.

Obviously I cannot have files with those names:
But it would possible to replace the * per a plus character (I haven't managed to scape the plus character in codeplex):
Is it possible in a way I haven't noticed?

Jan 25, 2015 at 12:34 PM
Edited Jan 25, 2015 at 12:43 PM
I have managed to make it work changing Orchard.Core.Contents class. However two questions arise:

How can I override Orchard.CoreContents behavior from my own module instead of modifying Orchard.Core but maintaining the order of specifity I have used in my modified version of Shape class?
Will all the alternates I have included through loops have a big impact on performance?

Here is the modified class:
namespace Orchard.Core.Contents {
    public class Shapes : IShapeTableProvider {
        public void Discover(ShapeTableBuilder builder) {
                .OnCreated(created => {
                    var content = created.Shape;
                    content.Child.Add(created.New.PlaceChildContent(Source: content));
                .OnDisplaying(displaying => {
                    ContentItem contentItem = displaying.Shape.ContentItem;
                    if (contentItem != null) {
                        // Alternates in order of specificity. 
                        // Display type > content type > specific content > display type for a content type > display type for specific content
                        // BasicShapeTemplateHarvester.Adjust will then adjust the template name

                        // Content__[DisplayType] e.g. Content-Summary
                        displaying.ShapeMetadata.Alternates.Add("Content_" + EncodeAlternateElement(displaying.ShapeMetadata.DisplayType));

                        // Content__[ContentType] e.g. Content-BlogPost,
                        displaying.ShapeMetadata.Alternates.Add("Content__" + EncodeAlternateElement(contentItem.ContentType));

                        //CODE ADDED
                        var contentTypeNamePattern = EncodeAlternateElement(contentItem.ContentType);
                        while (contentTypeNamePattern.Length > 0)
                            displaying.ShapeMetadata.Alternates.Add("Content__" + contentTypeNamePattern + "+");
                            contentTypeNamePattern = contentTypeNamePattern.Remove(contentTypeNamePattern.Length-1);

                        // Content__[Id] e.g. Content-42,
                        displaying.ShapeMetadata.Alternates.Add("Content__" + contentItem.Id);

                        // Content_[DisplayType]__[ContentType] e.g. Content-BlogPost.Summary
                        displaying.ShapeMetadata.Alternates.Add("Content_" + displaying.ShapeMetadata.DisplayType + "__" + EncodeAlternateElement(contentItem.ContentType));

                        //CODE ADDED
                        contentTypeNamePattern = EncodeAlternateElement(contentItem.ContentType);
                        while (contentTypeNamePattern.Length > 0)
                            displaying.ShapeMetadata.Alternates.Add("Content_" + displaying.ShapeMetadata.DisplayType + "__" + contentTypeNamePattern + "+");
                            contentTypeNamePattern = contentTypeNamePattern.Remove(contentTypeNamePattern.Length - 1);

                        // Content_[DisplayType]__[Id] e.g. Content-42.Summary
                        displaying.ShapeMetadata.Alternates.Add("Content_" +  displaying.ShapeMetadata.DisplayType + "__" + contentItem.Id);

        /// <summary>
        /// Encodes dashed and dots so that they don't conflict in filenames 
        /// </summary>
        /// <param name="alternateElement"></param>
        /// <returns></returns>
        private string EncodeAlternateElement(string alternateElement) {
            return alternateElement.Replace("-", "__").Replace(".", "_");