# Welcome

## Welcome to the owain.codes Wiki

This is my digital brain dump — a stash of notes, snippets, fixes, and “I swear I’ve solved this before” moments. If future‑me needs it, it’s probably in here. If you’ve stumbled in by accident… well, enjoy the chaos.

### What lives here

* C# things I don’t want to Google again
* Umbraco quirks and survival tips
* Git commands I always forget
* Handy CLI spells
* Random snippets that earned their keep

### Why this exists

Because remembering everything is overrated, and writing it down is faster than arguing with Stack Overflow at 2am.

####

#### `v5.0.0-alpha001` — Blazekuen

![Blazekuen (v5.0.0-alpha001)](/files/HOygeEZyGwAfyYBOUyaQ)

***

#### `v4.0.1-beta002` — Flambuon

![Flambuon (v4.0.1-beta002)](/files/GxRLkCgkVRjW2PbZ4vkp)

***


# Tips / How-to

Just some tips and how-to instructions on things I've picked up over time


# Detailed Error messages on Azure

To enable detailed errors, you can use the *ASPNETCORE\_DETAILEDERRORS* environment variable or configure the application to always show detailed error pages.

Example: Using ASPNETCORE\_DETAILEDERRORS

Add the following configuration to your *web.config* file when hosting on IIS:

```
<configuration>
 <system.webServer>
   <aspNetCore processPath="dotnet" arguments=".\YourApp.dll">
     <environmentVariables>
       <environmentVariable name="ASPNETCORE_DETAILEDERRORS" value="true" />
     </environmentVariables>
   </aspNetCore>
 </system.webServer>
</configuration>
```

Example: Developer Exception Page in Code

In your *Startup.cs* or *Program.cs*, enable the **Developer Exception Page** for the Development environment:

```
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
   app.UseDeveloperExceptionPage();
}
else
{
   app.UseExceptionHandler("/Error");
   app.UseHsts();
}
app.Run();
```

This ensures that detailed error pages are shown only during development.

Important Considerations

1. **Environment-Specific Configuration**: Always restrict detailed error messages to the Development environment. Exposing them in Production can lead to security risks.
2. **Custom Error Pages**: For Production, use *UseExceptionHandler* to redirect users to a custom error page.
3. **Testing**: Test your error-handling setup thoroughly to ensure no sensitive information is leaked.

By following these practices, you can debug effectively during development while maintaining security in production.


# Where is the hosts file on Windows?

C:\Windows\System32\drivers\etc


# Wrapped up a site

First thing is you need to check if there is pending work on Dev that isn't on the live environment.

If there is, (check by making a temp PR on DEV to UAT for example) this work really should get pushed up to LIVE through the usual steps e.g DEV->UAT, UAT-> LIVE

Assuming there isn't any changes, you'll want to pull down Develop, and then clean it up (remove connection strings, api keys etc).

Then zip it up without the .git hidden folder, (there is more you can clean up such as doing a VS clean on it to clear obj and bin folders, removing the packages folder etc other wise it will be 100's of MB big.

You'll also want to zip up the live Umbraco forms, and Media from blob storage, and then the live database.

<br>


# Snippets

Some code snippets, some non-code snippets

Snippets are literally just that, they are not full implementations of code, just useful little nuggets of info.


# Non-coding

Test

This is mostly just some interesting non-code specific snippets.&#x20;


# TinyMCE config generator for Umbraco

A great tool for quickly creating TinyMCE config for TinyMCE. Setup your styles and then copy the code in to AppSettings.json (Umbraco 13)

{% embed url="<https://www.iomi.net/tinymce-umbraco/>" %}


# Git Markdown

Useful snippets for adding to Git Pull Requests

**Adding notes to git comments**

```
> [!NOTE]  
> Highlights information that users should take into account, even when skimming.

> [!TIP]
> Optional information to help a user be more successful.

> [!IMPORTANT]  
> Crucial information necessary for users to succeed.

> [!WARNING]  
> Critical content demanding immediate user attention due to potential risks.

> [!CAUTION]
> Negative potential consequences of an action.
```


# Command Prompt / Terminal

Useful things to use in Command Prompt or Windows Terminal

From a windows terminal / cmd prompt. If you type `start .` it opens up a Windows Explorer window of that directory.


# Coding

The opposite of non-coding 😀


# Compile FrontEnd via NVM and Gulp

* > \>> nvm use 10.15.3
  >
  > \>> nvm on
  >
  > \>> npm -g install gulp-cli
  >
  > \>> gulp build


# Node-Sass

Create a watch on a folder

<img src="/files/j2BWlS58PbSbN6levpxq" alt="" data-size="line">

```
// node-sass -w ..\src\owaincodes\wwwroot\scss --output ..\src\owaincodes\wwwroot\css

```


# SQL Statements

useful snippets

```
use <database_name>
go
EXEC sp_change_users_login 'Update_One', 'sde', 'sde'
go
```

`ALTER USER [LOGIN_NAME] WITH PASSWORD = 'PASSWORD'`

```
use <database_name>
go
ALTER USER sde WITH login = sde
go
```

**Add a new user to a database:** <br>

```sql
// This is the new way of creating DB users
create user [user-login] with password = 'Password Here'
ALTER ROLE [db_owner] ADD MEMBER [user-login]
```

<br>


# Build up SQL statements

```
   var sql = scope.SqlContext.Sql().Select("*")
                        .From<DownloadsEntry>();
                    if (dateRange.From.HasValue)
                        sql.Where<DownloadsEntry>(d => d.DownloadDate >= dateRange.From.Value);
                    if(dateRange.To.HasValue)
                        sql.Where<DownloadsEntry>(d => d.DownloadDate < dateRange.To.Value);

```

Only works if you want AND clauses between the Wheres


# Get current logged in user from Controller

This is useful if you know you will have a logged in user and you get get the unique key for that account:&#x20;

```
IPublishedContent member = Members.GetCurrentMember();

// Pass member key to mode

model.MemberKey = member.key
```


# Dynamic BlockList Label

If you want to add a bit more user friendliness to your Blocklist here is a handy bit of code :

```
{{ imageCards.contentData.length == 1 ? imageCards.contentData.length + ' Image Copy card' : imageCards.contentData.length + 'Image Copy cards' }}
```

In this example, it counts how many imageCards I have on a blocklist and shows a different label depending on the number. imageCards is the alias on the element block : \
\
![](/files/bV5QtdhvvBJyWx6lr0RZ)

&#x20;I can add as many "image cards" to this property as I like.&#x20;

This is how it displays&#x20;

<figure><img src="/files/bycU9hJOJ8yI2K8McUIg" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/1u2mJHtOZw23M6QUeSWf" alt=""><figcaption></figcaption></figure>


# Alt Tag

Only show an `alt` tag if there is a value.

```csharp
var altText = Model.BackgroundImage.Value("altText")

<img src="a_source_for_the_image" @(altText.IsNullOrWhiteSpace() ? null : @Html.Raw($"alt=\"{altText}\"")) />
```


# SQL - Could not import package

<figure><img src="/files/oLBr2CbZQ0IlmuT6JNmQ" alt=""><figcaption></figcaption></figure>

Run this before importing

```
sp_configure 'show advanced options', 1;
RECONFIGURE;
GO
sp_configure 'contained database authentication', 1;
RECONFIGURE;
GO
```


# SQL Server Table Size Report (Used vs Allocated Space)

##

### **Overview**

This page documents a SQL Server query used to analyse **table‑level storage usage** within a database. It provides a breakdown of how much space each table consumes, both in terms of **used pages** and **allocated pages**, and orders the results from largest to smallest.

This is useful for:

* Capacity planning
* Identifying unusually large tables
* Performance tuning
* Understanding storage growth over time

***

### **Purpose of the Query**

SQL Server stores data in **8 KB pages**, grouped into allocation units. This query aggregates those pages for each table and converts them into megabytes, giving you a clear view of:

* **Used MB** – space actively used by the table
* **Allocated MB** – space reserved by SQL Server for future growth

The result is a concise report showing which tables consume the most space.

***

### **The SQL Query**

```sql
select schema_name(tab.schema_id) + '.' + tab.name as [table],
    cast(sum(spc.used_pages * 8)/1024.00 as numeric(36, 2)) as used_mb,
    cast(sum(spc.total_pages * 8)/1024.00 as numeric(36, 2)) as allocated_mb
from sys.tables tab
    inner join sys.indexes ind 
        on tab.object_id = ind.object_id
    inner join sys.partitions part 
        on ind.object_id = part.object_id and ind.index_id = part.index_id
    inner join sys.allocation_units spc
        on part.partition_id = spc.container_id
group by schema_name(tab.schema_id) + '.' + tab.name
order by sum(spc.used_pages) desc;
```

***

### **How It Works**

#### **1. Table Identification**

`sys.tables` provides the list of all user tables in the database.

#### **2. Joining System Views**

The query joins:

* `sys.indexes` – to include all index structures
* `sys.partitions` – to access partition-level metadata
* `sys.allocation_units` – to retrieve page counts

These views together expose how SQL Server physically stores table data.

#### **3. Calculating Space Usage**

SQL Server pages are 8 KB each.\
The query converts pages → KB → MB:

```
pages * 8 KB / 1024 = MB
```

Two metrics are calculated:

* **used\_pages** → actual data
* **total\_pages** → allocated space

#### **4. Grouping and Ordering**

Tables are grouped by schema and name, then sorted by **largest used space first**.

***

### **Example Output**

| table                | used\_mb | allocated\_mb |
| -------------------- | -------- | ------------- |
| dbo.Orders           | 512.00   | 768.00        |
| sales.TransactionLog | 430.25   | 512.00        |
| dbo.Customers        | 120.50   | 256.00        |

*(Values shown are illustrative.)*

***

### **When to Use This Query**

Use this script when you need to:

* Audit database size
* Identify large or growing tables
* Investigate performance issues related to storage
* Prepare for migrations or archiving
* Monitor space usage trends

***

### **Related Tools & Queries**

* `sp_spaceused` for quick table-level stats
* `sys.dm_db_partition_stats` for row counts
* Index fragmentation reports
* Filegroup and database size queries

***

If you'd like, I can also generate:

* A shorter “quick reference” version
* A version formatted for Confluence, GitHub Wiki, or Azure DevOps Wiki
* A companion page explaining how to automate this report

Just tell me the style you want.

Thanks Jack for sharing this.&#x20;


# Umbraco

How to do things with Umbraco CMS

[Umbraco,](https://www.umbraco.com) the friendly CMS.&#x20;


# Luke - Version helper

Ever wondered what version of Luke you need for the version of Umbraco you are using?

<http://www.getopt.org/luke/>\
\
<https://code.google.com/archive/p/luke/downloads>

| Umbraco Version | Luke Version | Download Link                                          |
| --------------- | ------------ | ------------------------------------------------------ |
| 7               |              |                                                        |
| 8               |              |                                                        |
| 9               |              |                                                        |
| 10              |              |                                                        |
| 11.1            | 4.8.0        | <https://github.com/DmitryKey/luke/releases/tag/4.8.0> |
| 12              |              |                                                        |


# Umbraco 10+

Some useful snippets for using Umbraco and .Net Core


# Find out Model of Current page

Need to find out the parent model type from a partial?

On the partial view use <br>

`if(Umbraco.AssignedContentItem is PropertyListing) { bannerClass += "__property"; }`\
\
This then checks to see if the current page that this partial is on and if it's of type PropertyListing then I add the \
`__property` css class


# error NU1301: Unable to load the service index for source

An error that appears when trying to setup a new site.

`error NU1301: Unable to load the service index for source` [`https://pkgs.dev.azure.com/internal/_packaging/Internal.Nuget/nuget/v3/index.json.`](https://pkgs.dev.azure.com/spindogs/_packaging/Spindogs.Nuget/nuget/v3/index.json.)<br>

How to fix - in your terminal enter this:&#x20;

```
dotnet restore --interactive
```


# Umbraco 9

Topics that are Umbraco 9 specific


# Get the current page content type alias

How to get the current rendered page document (content) type.

Imagine you have a masterpage which calls a searchbar partial view. The masterpage can be used on any page and the partial passes the SiteRoot model to the partial

```
@{
  var siteSettings = Model as SiteRoot;
}
  
<partial name="master/_header" model="siteSettings" />
```

The \_header will always have the model type of SiteRoot but if you are on a results page for example then you might want to do something specific e.g. hide the search bar because there is a search bar already on the search results page.&#x20;

You can check what page your partial is being rendered on like so :

```
@inherits UmbracoViewPage
@{
 var siteSettings = Model.Root() as SiteRoot;
var isResultsPage = UmbracoContext.PublishedRequest.PublishedContent.ContentType.Alias == SearchResults.ModelTypeAlias;
}
```


# Fluent SQL

```
 using Umbraco.Core.Persistence;
 
 using (var scope = _scopeProvider.CreateScope())
            {
                var sql = scope.SqlContext.Sql().Select("Keyword, Definition").From<GlossaryItem>()
                    .Where<GlossaryItem>(g => g.Language == culture);
                
                var glossaryDefinitions = scope.Database.Query<GlossaryItem>(sql);
                return glossaryDefinitions;
            }
```


# Unable to open ConfigSource file

![](/files/WRv63C1RawBugluKrXv7)

This is due to the Build Action being changed on the file -&#x20;

![](/files/HlqPpPJOPBJklWVsO5Js)

To fix, just check all the files that have had the Build Action changed and change back to Content.


# Delete items from backoffice by DocType ID

This deletes all content from the Root of the site that has a Document Type ID of 1097.

```
@{
    var contentService = Current.Services.ContentService;

    var RootNode = Model.Root().Children();

    foreach(var node in RootNode)
    {

        contentService.DeleteOfType(1097);

    }


}

```


# \[WIP] Setting up Examine

## Getting Setup with ExamineY

* Setup a view with a basic form

```
 @using (Html.BeginUmbracoForm("Search", "Search", Project.Core.Constants.Site, FormMethod.Get))
                                {
                                    @Html.AntiForgeryToken()

                                    <div class="field_wrap __text">
                                        <div class="label_wrap sronly"><label for="searchbar-input">Search</label></div>
                                        <div class="input_wrap"><input type="text" placeholder="Search..." id="searchbar-input" class="searchBarFocus" value="" name="q" tabindex="-1"></div>
                                    </div>
                                    <div class="submit_wrap">
                                        <button type="submit" tabindex="-1">
                                            <span>Search</span>
                                        </button>
                                    </div>

                                }
```

* Create a Controller with a suitable action method name
* Remember to create a Composer to register your Services!!

{% hint style="info" %}
&#x20;Super-powers are granted randomly so please submit an issue if you're not happy with yours.
{% endhint %}

Once you're strong enough, save the world:

{% code title="hello.sh" %}

```bash
# Ain't no code for that yet, sorry
echo 'You got to trust me on this, I saved the world'
```

{% endcode %}


# Umbraco.ModelsBuilder assembly error

![](/files/-MeUOHI6gUHgHCuklvCj)

Do a Package Restore on the solution

Restart Visual Studio

Rebuild project


# Working with IPublishedContent

Umbraco.TypedContent

```
public ActionResult GetDistinctContentTypesForAuthor(string authorId)
        {
            //Create an empty list of type String
            List<string> allResults = new List<string>();

            // Setup what Search Index to use
            var searcher = ExamineManager.Instance.SearchProviderCollection["ContentHubSearcher"];

            // Tell the search what will be searched, this is content that will be searched
            ISearchCriteria searchCriteria = searcher.CreateSearchCriteria(IndexTypes.Content);

            
            // Create the lucene query, this is looking for all authors with the a matching authorId
            // within lucene, author is stored as an Key e.g. author: 9e0dad13195243389215ce452031ffb5
            
            IBooleanOperation query = searchCriteria.GroupedOr(new string[] { "author" }, authorId);

            // Run the search within Examine
            var queryResults = searcher.Search(query.Compile());

            // from the results, select all the Ids and save them in IEnumerable
            IEnumerable<int> nodeIds = queryResults.Select(x => x.Id);
            
            // Create a List of IPublishedContent using Umbraco.TypedContent and the IEnumberable nodeIds List
            List<IPublishedContent> publishedContent = Umbraco.TypedContent(nodeIds).ToList();

            
            // With the List of IPublishedContent - do what you need to do.
            if (publishedContent != null)
            {
                foreach (var item in publishedContent)
                {

                    if (item is GenericContent genericContent && genericContent.ContentLabel is GenericPageLabel pageLabel)
                    {
                        allResults.Add(pageLabel.LabelTitle.IfNullOrWhiteSpace(pageLabel.Name));
                    }
                    else if (item is VideoItem videoItem && videoItem.ContentLabel is GenericPageLabel videoPageLabel)
                    {
                        allResults.Add(videoPageLabel.LabelTitle.IfNullOrWhiteSpace(videoPageLabel.Name));
                    }
                    else if (item is WebinarContent webinarItem && webinarItem.ContentLabel is GenericPageLabel webinarPageLabel)
                    {
                        allResults.Add(webinarPageLabel.LabelTitle.IfNullOrWhiteSpace(webinarPageLabel.Name));
                    }
                    else if (item is StoryContent storyItem && storyItem.ContentLabel is GenericPageLabel storyPageLabel)
                    {
                        allResults.Add(storyPageLabel.LabelTitle.IfNullOrWhiteSpace(storyPageLabel.Name));
                    }
                    else
                    {
                        allResults.Add(Global.FriendlyFromDocTypeAlias(item.DocumentTypeAlias));
                    }
                }
            }


            return PartialView("Authors/ContentTypesDropdown", allResults.Distinct().OrderBy(x => x));


        }
```


# How to Strongly Type to Models

OLD: <br>

```
case VideoItem.ModelTypeAlias:
                        if (article.HasValue("contentLabel"))
                        {
                            docType = article.GetPropertyValue<IPublishedContent>("contentLabel").Name;
                            break;
                        }
                        else
                        {
                            docType = "Video";
                            break;
                        }
```

New:&#x20;

```
case VideoItem.ModelTypeAlias:
    docType = "Video";
    if(article is VideoItem videoItem && videoItem.ContentLabel != null)
    {
       docType = videoItem.ContentLabel.Name;
    }
```


# Setting up a custom form

```
  @using (Html.BeginUmbracoForm("SearchAll", "CourseSearchAPI", Project.Core.Constants.Site, new { @class = "coursefinder-form-fields" }))
        {
        
        }
```


# Getting bounced away from /umbraco

working on a local site and every time you hit /umbraco to try and access the backoffice you get redirected back to localhost.

It's an SSL issue, just set :

```
<add key="umbracoUseSSL" value="false" />
```


# Examine

The search engine used within Umbraco

* <https://shazwazza.github.io/Examine/>&#x20;

{% tabs %}
{% tab title="Add custom field to the Index" %}

```
IndexerComponent : IComponent

Public void Initialize()
{
   externalIndex.FieldDefinitionCollection.AddOrUpdate(new FieldDefinition("FieldName", "FieldDataOrContent"));
}

```

{% endtab %}

{% tab title="Add content to custom field" %}
In the same file - `IndexerComponent.cs`

```
   private void IndexResourceSpecificProperties(IndexingItemEventArgs e, ResourceItem resourceItem)
        {
            try
            {
                var resourceDate = resourceItem.ResourceDate;
                e.ValueSet.Add(Constants.Resources.ResourceDateSortableExamineField, resourceDate.Ticks);
```

{% endtab %}
{% endtabs %}


# Explaining GroupedOr / GroupedAnd methods

A great explanation from Callum.

![](/files/-MMuz6J6BBsO8Q_w0QLG)


# Rosyln error

Every now and again you might see the error relating to csc.exe, this is how to fix it.

Within the Package Manager Console in Visual Studio type the following - **Remember the -r flag!**

`Update-Package Microsoft.CodeDom.Providers.DotNetCompilerPlatform -r`

![](/files/-MLwAXV6zEp_NGrxWZl4)


# Models Builder Settings

### Setting up Models Builder within web.config to save the models to a custom folder.

If using this, you need to leave out the `value` for line #4 and #5

```
  <add key="Umbraco.ModelsBuilder.Enable" value="true" />
  <add key="Umbraco.ModelsBuilder.ModelsMode" value="AppData" />

  <add key="Umbraco.ModelsBuilder.AcceptUnsafeModelsDirectory" value="true" />
  <add key="Umbraco.ModelsBuilder.ModelsDirectory" value="~/Models/Generated" />
```


# Adding content to the backoffice

Adding content programatically to the back office

Full blog : <https://owain.codes/blog/posts/2020/october/add-content-programmatically-to-umbraco-8/>

### Adding content with random Page titles.

Place this code on to a view of the parent node in the backoffice.&#x20;

Root \
-> Parent Node \
\--> Child.&#x20;

This example makes a mix of BlogArticles and NewsArticles under the parent node.&#x20;

```
@using Umbraco.Core.Composing;
@using System;
```

```
  
//Used to mass create content in the backoffice.
    var contentService = Current.Services.ContentService;
 
    // This is the id of the 'folder' you want to copy.
    // Place a child within the folder and it will be replicated
    var parentNodeId = Model.Id;
    var titleHeadings = new string[]
            {​​​​​​​​
               "rainstorm","preach","weary","gun","plain","zany","helpful","long","various","development","foamy","melted","narrow","freezing","reduce","burly","price","curl","bell",
                "distribution","glow","turkey","meddle","men","boundless","scratch","excuse","mature","post","file","unsuitable","writer","eatable","magical","mere","tray","bump","spotted",
            "volcano","squash","hushed","maddening","smooth","edge","tongue","scorch","gainful","please","decide","porter","jaded","ski","yoke","hospital","mask","barbarous","bubble","business",
            "normal","ashamed","underwear","superb","bore","tedious","beginner","pigs","disagree","earth","verse","perpetual","scattered","rhetorical","workable","cuddly","furry","seemly","puzzled","load",
            "sloppy","ludicrous","queen","ethereal","religion","psychotic","nifty","puzzling","truthful","connection","vulgar","lumpy","bake","bike","mixed","clover","flag","deafening","soak","flaky","statement","lucky"
                     }​​​​​​​​;
    var baseDate = DateTime.Today;
 
    // Keep this < n low, remember it replicates the folder so 1, 2, 4, 8, 16, 32 items are created for 5 loops.
    for (var i = 0; i < 300; i++)
    {​​​​​​​​
        Random rnd = new Random(DateTime.Now.Millisecond);
        var name = "Read about " + titleHeadings[rnd.Next(titleHeadings.Length)];
        var date = DateTime.Today.AddDays((rnd.NextDouble() * (28 - 1) + 1) * -1 ).AddMonths(rnd.Next(0, 12) * -1).AddYears(rnd.Next(0, 2) * -1);
        var contentType = rnd.Next(1, 2) == 1 ? BlogArticle.ModelTypeAlias : NewsArticle.ModelTypeAlias;
        var node = contentService.Create(name, parentNodeId, contentType);
        node.SetValue(Article.GetModelPropertyType(a => a.Title).Alias, name);
        node.SetValue(Article.GetModelPropertyType(a => a.PublishDate).Alias, date);
 
        contentService.SaveAndPublish(node);
    }​​​​​​​​
```

### Adding random postcodes to the content

Another example - this time adding random postcodes in to the content.&#x20;

```



    //Used to mass create content in the backoffice.
    var contentService = Current.Services.ContentService;

    // This is the id of the 'folder' you want to copy.
    // Place a child within the folder and it will be replicated
    var parentNodeId = Model.Id;
    var titleHeadings = new string[] { "rainstorm", "preach", "weary", "gun", "plain", "zany", "helpful", "long", "various", "development", "foamy", "melted", "narrow", "freezing", "reduce", "burly", "price", "curl", "bell", "distribution", "glow", "turkey", "meddle", "men", "boundless", "scratch", "excuse", "mature", "post", "file", "unsuitable", "writer", "eatable", "magical", "mere", "tray", "bump", "spotted", "volcano", "squash", "hushed", "maddening", "smooth", "edge", "tongue", "scorch", "gainful", "please", "decide", "porter", "jaded", "ski", "yoke", "hospital", "mask", "barbarous", "bubble", "business", "normal", "ashamed", "underwear", "superb", "bore", "tedious", "beginner", "pigs", "disagree", "earth", "verse", "perpetual", "scattered", "rhetorical", "workable", "cuddly", "furry", "seemly", "puzzled", "load", "sloppy", "ludicrous", "queen", "ethereal", "religion", "psychotic", "nifty", "puzzling", "truthful", "connection", "vulgar", "lumpy", "bake", "bike", "mixed", "clover", "flag", "deafening", "soak", "flaky", "statement", "lucky" };
    var postcodes = new string[]{"B15 1AZ","NE63 8JX","RH17 5EZ","BT32 9AY","SG7 6RG","SS8 7QN","DD2 4LU","LL18 4NG","B43 7EU","G64 2JZ","CF23 9JP","DN14 8JD","M24 1JP","OL9 0LW","SG5 1HX","PO31 7FL","GU24 9PP","B64 5RU","B31 1SE","SR8 5TB","WN3 6PF","PO12 3BP","PA12 4AD","M34 6HL","DG2 0RH","BT37 9SB","SY6 7EY","SA10 6DE","BS24 7FB","MK43 0SD","BB7 9NX","B62 9RJ","NR5 8HT","TN12 0AD","KY2 5XQ","LD1 5UW","L20 0BQ","TQ2 7GA"};

    var baseDate = DateTime.Today;

    // Keep this < n low, remember it replicates the folder so 1, 2, 4, 8, 16, 32 items are created for 5 loops.
    for (var i = 0; i < 300; i++)
    {
        Random rnd = new Random(DateTime.Now.Millisecond);
        var name = "Event " + titleHeadings[rnd.Next(titleHeadings.Length)];
        var date = DateTime.Today.AddDays((rnd.NextDouble() * (28 - 1) + 1) * -1).AddMonths(rnd.Next(0, 12) * -1).AddYears(rnd.Next(0, 2) * -1);
        var endDate = DateTime.Today.AddDays((rnd.NextDouble() * (28 - 1) + 1) * -1).AddMonths(rnd.Next(0, 12) * -1).AddYears(rnd.Next(0, 2) * -1);
        var postcode = postcodes[rnd.Next(postcodes.Length)];
        //var contentType = rnd.Next(1, 2) == 1 ? BlogArticle.ModelTypeAlias : NewsArticle.ModelTypeAlias;
        var node = contentService.Create(name, parentNodeId, EventItem.ModelTypeAlias);
        node.SetValue(EventItem.GetModelPropertyType(a => a.Title).Alias, name);
        node.SetValue(EventItem.GetModelPropertyType(a => a.EventStartDate).Alias, date);
        node.SetValue(EventItem.GetModelPropertyType(a => a.EventEndDate).Alias, endDate);
        node.SetValue(EventItem.GetModelPropertyType(a => a.AddressPostcode).Alias, postcode);

        contentService.SaveAndPublish(node);
```

Add content of a specific content type

```
  for (var i = 0; i < 2; i++)
    {


        var children = contentService.GetPagedChildren(parentNodeToCopy, 0, 904, out var totalRecords);
        foreach (var child in children)
        {

            String[] titleHeading = new string[]
            {
               "rainstorm","preach","weary","gun","plain","zany","helpful","long","various","development","foamy","melted","narrow","freezing","reduce","burly","price","curl","bell",
                "distribution","glow","turkey","meddle","men","boundless","scratch","excuse","mature","post","file","unsuitable","writer","eatable","magical","mere","tray","bump","spotted",
            "volcano","squash","hushed","maddening","smooth","edge","tongue","scorch","gainful","please","decide","porter","jaded","ski","yoke","hospital","mask","barbarous","bubble","business",
            "normal","ashamed","underwear","superb","bore","tedious","beginner","pigs","disagree","earth","verse","perpetual","scattered","rhetorical","workable","cuddly","furry","seemly","puzzled","load",
            "sloppy","ludicrous","queen","ethereal","religion","psychotic","nifty","puzzling","truthful","connection","vulgar","lumpy","bake","bike","mixed","clover","flag","deafening","soak","flaky","statement","lucky"
                                     };
            if (child.ContentType.Alias == "flexibleTextPage")
            {
                Random rnd = new Random();
                var copiedChild = contentService.Copy(child, parentNodeToCopy, false);

                copiedChild.Name = "WIGGING with " + titleHeading[rnd.Next(titleHeading.Length)];

                if (copiedChild.HasProperty("title"))
                {
                    copiedChild.SetValue("title", copiedChild.Name);
                }

                contentService.SaveAndPublish(copiedChild);
            }

        }
    }

```


# Pagination

How to do pagination for listing pages

TODO: Add code to demo Pagination

Thought process :&#x20;

Find all blogs and count the number of blog items which are published (Examine)\
Declare size of each page, 10 most likely\
Divide the total number of blogs found by 10, this is then the number of pages available<br>


# Creating YYYY/MM folders

TODO: Create this content for future reference


# Configuration Error - CodeDom

YSOD with reference to CodeDom

![](/files/RV29k6BetayLlTSlJRjQ)

This is usually due to your connectionstring being wonky!&#x20;


# C Sharp

Things that have peaked my interest and I've investigated futher


# Useful Links

A list of links that are handy for quick reference

* .Net Framework Source Code: <https://referencesource.microsoft.com/>
*


# Regex

Some useful Regex examples

Check if a value starts with a number:&#x20;

```
  @using System.Text.RegularExpressions
  
      string patternStartsWithNumber = @"^(?:[0-9])";
      
      
  @if (!vacancy.VacancySalary.IsNullOrWhiteSpace() && Regex.Match(vacancy.VacancySalary, patternStartsWithNumber).Success)
                        {
                        }
```


# Null check .any()

You can't just have&#x20;

```
if(content.Any())
{
    // Do something
}
```

The reason is if there is no content then there is nothing to count and do it doesn't exist which means content is equal to null

To combat that you need to null check it.&#x20;

```
 @if (content != null && content.Any())
 {
  // do something
 }
  
```


# internal static and internal const

`Classes` or `Methods` or things that need calculations done etc are generally `static`, `const` are fixed values that never change e.g. names for fields within indexer


# Dependency Injection

In the composer - register the Interface with the Service.&#x20;

```
 composition.Register<IEventsService, EventService>(Lifetime.Request);
```


# Dictionary\<Tkey, TValue>

Represents a collection of keys and values.

**Added 04/11/2020** \
Working with dictionaries for adding a filter value to the backoffice of Umbraco on CAS.

`TKey` - The type of the keys in the dictionary.

`TValue` - The type of the values in the dictionary.

```
// Create a new dictionary of strings, with string keys.
//
Dictionary<string, string> openWith =
    new Dictionary<string, string>();

// Add some elements to the dictionary. There are no
// duplicate keys, but some of the values are duplicates.
openWith.Add("txt", "notepad.exe");
openWith.Add("bmp", "paint.exe");
openWith.Add("dib", "paint.exe");
openWith.Add("rtf", "wordpad.exe");

```

&#x20;The [Dictionary\<TKey,TValue>](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.dictionary-2?view=netframework-4.8) generic class provides a mapping from a set of keys to a set of values. Each addition to the dictionary consists of a value and its associated key. Retrieving a value by using its key is very fast, close to O(1), because the [Dictionary\<TKey,TValue>](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.dictionary-2?view=netframework-4.8) class is implemented as a hash table.

```
using System;
using System.Collections.Generic;

public class Program
{
	public static void Main()
	{
		Dictionary<string, string> openWith = new Dictionary<string, string>();
		// Add some elements to the dictionary. There are no
		// duplicate keys, but some of the values are duplicates.
		openWith.Add("txt", "notepad.exe");
		openWith.Add("bmp", "paint.exe");
		openWith.Add("dib", "paint.exe");
		openWith.Add("rtf", "wordpad.exe");
		
		foreach (KeyValuePair<string, string> kvp in openWith)
		{
			Console.WriteLine("Key = {0}, Value = {1}", kvp.Key, kvp.Value);
		}
	}
}
```


# Linq / Lambda

<figure><img src="/files/XFsXafs9KpdcvWXB4ssH" alt=""><figcaption></figcaption></figure>

The ToModel method returns a IEnumberble of SitemapNodeModel, ToModel accepted a T of IEnumberable IPublishedcontent.&#x20;

I need to remove ToModel so I wrote the following :&#x20;

```


List<SitemapNodeModel> list = new List<SitemapNodeModel>();

foreach (var child in children)
{
				SitemapNodeModel sitemap = new SitemapNodeModel
				{
					Id = child.Id,
					Name = child.Name,
					DocumentTypeAlias = child.DocumentTypeAlias,
					Url = child.Url,
					UrlName = child.UrlName,
					UpdateDate = child.UpdateDate
				};
				
				list.Add(sitemap);
}
```

This works but it can also be done with Linq and Lambda&#x20;

```
var children = _umbracoWrapper.Descendants(root)
				.Where(x => _umbracoWrapper.GetPropertyValue<bool>(x, "metaSitemap"))
				.Select(child => new SitemapNodeModel
				{
					Id = child.Id,
					Name = child.Name,
					DocumentTypeAlias = child.DocumentTypeAlias,
					Url = child.Url,
					UrlName = child.UrlName,
					UpdateDate = child.UpdateDate
				}).ToList();



var umbracoNodes = new List<SitemapNodeModel>(children);
```


# Git Actions

These look pretty interesting for automation on repos


# Build your own Git Action

WIP: Something I fancy learning to do myself.

It looks like I could setup some actions with Powershell or Python which could be interesting.&#x20;

I want to try and get Github to automatically create a Changelog whenever I add something to this wiki. Not sure if it's possible with GitBooks but it could be interesting to try.&#x20;


# Create a readme file automatically

This job runs on the 0th, 6th, 12th, 18th hour of the day e.g. midnight, 6am, midday 6pm.\
To run it on the hour, every hour, just change the value to `/1`

```
name: Update README

on:
  push:
  schedule:
    - cron: "0 */6 * * *"

jobs:
  markscribe:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@master

      - uses: muesli/readme-scribe@master
        env:
          GITHUB_TOKEN: ${{ secrets.GITBOOK_TOKEN }}
        with:
          template: "templates/README.md.tpl"
          writeTo: "README.md"

      - uses: actions/upload-artifact@v1
        with:
          name: README.md
          path: README.md

      - uses: stefanzweifel/git-auto-commit-action@v4
        env:
          GITHUB_TOKEN: ${{ secrets.GITBOOK_TOKEN }}
        with:
          commit_message: Update generated README
          branch: master
          commit_user_name: readme-scribe 🤖
          commit_user_email: actions@github.com
          commit_author: readme-scribe 🤖 <actions@github.com>
```

Created a GITBOOK\_TOKEN which lives in my 'secrets' section of github and was generated from the developer settings under my profile. It must be prefixed with `secrets.` in the action. It doesn't work if you just try and use `GITBOOK_TOKEN` or whatever you decide to call your secret.&#x20;


# Command line

Some useful commands, mostly for my own reference


# Unstage a file

How to unstage a file that has been added by mistake

git restore --stage file


# Ignore web.config changes on commit

## Ignore web.config changes on commit

`git update-index --skip-worktree .\src\Site\web.config`

## Undo

`git update-index --no-skip-worktree .\src\Site\web.config`


# Allow for case-insensitivity in Windows

Turn on Windows Feature - Windows Subsystem for Linux\
This allows you the option of having one folder called `learning` and another called `Learning` in the same directory. Not that I wanted this but I needed it as I'd messed up my git repo!&#x20;

You need to restart Windows and it will reboot a couple of times while it installs this feature.&#x20;

Once it's done, open command prompt in Admin mode and run

`fsutil.exe file SetCaseSensitiveInfo C:\folder\path enable`

You can set CaseSensitiveInfo on a specific folder, which is what I needed to do.&#x20;

Once it's enabled, I was able to `git clone` my repo and fix the mess.&#x20;


# Making VSCode your Git editor and diff tool

To set Visual Studio Code as your default editor enter this command into command line:

`git config --global -e`

```
[user]
	email = your@email.com
	name = YourName
	signingKey = ""
[core]
	longpaths = true
	autocrlf = true
	safecrlf = warn
	editor = code --wait
[gpg]
	program = gpg
[commit]
	gpgSign = false
[tag]
	forceSignAnnotated = false

```

&#x20;By the way switch `--wait` holds shell until Visual Studio Code is closed. Make sure \[core] has the editor as code.

### Making VS Code your Diff Tool

To set Visual Studio Code as your `difftool`, you need to go into global git config file. Which you can access through previous mentioned command `git config --global -e`, then you need to add those entries (or replace existing ones).

```
[diff]
    tool = vscode
[difftool "vscode"]
    cmd = code --wait --diff $LOCAL $REMOTE
```

<https://blog.soltysiak.it/en/2017/01/set-visual-studio-code-as-default-git-editor-and-diff-tool/>


# Add, Push, Pull, Clone

The basic stuff that I use every day.

#### Git add

```
git add .
```

Add all changes to the index before using `commit`

#### Git pull

```
git pull
```

Pull down all updates from the working branch on Dev

#### Git commit

```
git commit
```

Commit all your local changes to the working branch, leaving out any message or description automatically opens VS Code to allow for a message and description to be added.

#### Git clone

```
git clone https://your.repo.address NewFolderName
```


# Make Terminal look nice

How to make your Windows Terminal look nice.

A blog article from Scott Hanselman on how to make Windows Terminal work well with Git

![](/files/-MLKGpFdObJfngt7DKjj)

<https://www.hanselman.com/blog/how-to-make-a-pretty-prompt-in-windows-terminal-with-powerline-nerd-fonts-cascadia-code-wsl-and-ohmyposh>


# Remove files from Git Index

```
git rm -r --cached ./bin
```

`git rm -r --cached` **removes files from the Git index (the staging area) but leaves them on your filesystem**.

Think of it as: **“Stop tracking these files, but don’t delete them from disk.”**

Breaking it down:

* `rm` → remove
* `-r` → recursively (folders too)
* `--cached` → remove *only* from the index, not from your working directory

So after running it, the files still exist locally, but Git treats them as untracked.


# Conventional Commit

The Conventional Commits specification is a lightweight convention on top of commit messages

This will be useful for Git Actions and triggering automation on this convention:&#x20;

The Conventional Commits specification is a lightweight convention on top of commit messages. It provides an easy set of rules for creating an explicit commit history; which makes it easier to write automated tools on top of. This convention dovetails with [SemVer](http://semver.org/), by describing the features, fixes, and breaking changes made in commit messages.

The commit message should be structured as follows:

```
<type>[optional scope]: <description>

[optional body]

[optional footer(s)]
```

{% embed url="<https://www.conventionalcommits.org/en/v1.0.0/>" %}

<https://dev.to/technolaaji/writing-proper-git-commits-4g06>


# OC.PowerSort

## Overview

**OC.PowerSort** is a powerful content sorting extension for Umbraco CMS that provides enhanced sorting capabilities for content nodes directly within the backoffice. It enables content editors to schedule custom sorting of content nodes without requiring developer intervention, making content management more flexible and efficient.


# INTRODUCTION

##

## What is OC.PowerSort?

OC.PowerSort extends Umbraco's native content tree functionality by providing advanced sorting features that go beyond simple alphabetical or creation date ordering. The package allows content editors to:

* **Sort content nodes** using various criteria and custom providers
* **Schedule sorting changes** to occur at specific dates and times
* **Create recurring sorting schedules** that automatically apply on a recurring basis
* **Set priorities** to control content order when multiple items have the same sort position
* **Define default sort orders** that automatically apply to new content

## Key Features

### 1. Enhanced Content Sorting

* Multiple sorting strategies available out-of-the-box
* Drag-and-drop interface for manual sorting
* Visual tree representation of content structure
* Apply sorting to any parent node's children

### 2. Schedule Management

* **One-time Schedules**: Set content to move to a specific position at a future date/time
* **Recurring Schedules**: Define patterns for content to automatically reorder on a recurring basis
  * Daily recurrence
  * Weekly recurrence (specific days of the week)
  * Monthly recurrence (specific day of month or day of week pattern)
  * Custom intervals
* **Schedule Duration**: Control how long a boosted position remains active
* **Schedule End Dates**: Set when recurring schedules should stop
* **Maximum Occurrences**: Limit the number of times a recurring schedule runs

### 3. Priority System

When multiple content items share the same sort order position, the priority system determines which item appears first. This is particularly useful when combined with schedules, allowing editors to boost important content temporarily.

### 4. Default Sort Orders

Define default sorting behavior for specific parent nodes, ensuring consistent content ordering without manual intervention each time new content is added.

### 5. Extensible Provider System

Developers can create custom sort providers to implement business-specific sorting logic:

* **ISortProvider Interface**: Clean abstraction for implementing custom sorting strategies
* **Built-in Examples**:
  * `DefaultScheduleSortProvider`: Standard schedule-based sorting
  * `AlphabeticalSortProvider`: Alphabetical ordering
  * `NewestFirstSortProvider`: Sort by creation date
  * `FeaturedContentBoostProvider`: Boost featured content
  * `PopularityBoostProvider`: Sort by popularity metrics
* **Provider Factory**: Automatic discovery and registration of custom providers

## Architecture

### Core Components

**Services:**

* `ScheduleService`: Manages schedule CRUD operations
* `ScheduleProcessingService`: Executes scheduled sorting operations
* `RecurrenceCalculatorService`: Calculates occurrence dates for recurring schedules
* `OccurrenceGenerationService`: Generates schedule occurrences from recurring patterns
* `SortProviderFactory`: Discovers and instantiates sort providers
* `SortingFlagService`: Manages sorting flags and default behaviors

**Controllers:**

* `ScheduleAPIController`: REST API for schedule management
* `RecurringScheduleApiController`: REST API for recurring schedule operations
* `ChildrenAndSortingAPIController`: API for retrieving and sorting content trees
* `MenuItemsApiController`: Provides menu items for the backoffice interface
* `EnumPriorityAPIController`: Manages priority enumeration values

**Models:**

* `SortScheduleModel`: Represents a one-time scheduled sort operation
* `RecurringScheduleDto`: Database model for recurring schedules
* `ScheduleOccurrence`: Individual occurrence generated from recurring schedules

### Database Structure

OC.PowerSort creates several database tables to persist sorting information:

* `ocPowerSortSchedule`: Stores one-time scheduled sort operations
* `ocPowerSortRecurringSchedule`: Stores recurring schedule patterns
* `ocPowerSortScheduleOccurrence`: Individual occurrences generated from recurring schedules
* `ocPowerSortDefaultSortOrder`: Default sorting behavior for parent nodes
* `ocPowerSortEnumPriority`: Priority enumeration values

## Technical Requirements

* **Umbraco Version**: 17+
* **.NET Version**: 10
* **Database**: SQL Server (compatible with Umbraco's database requirements)

## Use Cases

### Content Promotion

Automatically promote specific content items during campaigns or events. For example, boost a "Summer Sale" page every weekend during June and July.

### Time-Sensitive Content

Schedule content to appear at the top of listings when it becomes relevant. News articles can automatically move to prominent positions when published, then return to chronological order after a set duration.

### Event Management

Use recurring schedules to automatically promote event pages in the days leading up to events, ensuring users see upcoming events without manual intervention.

### Seasonal Content

Set up recurring schedules to automatically reorder content based on seasonal relevance, such as promoting holiday-related content during specific months.

### Editorial Workflows

Content editors can schedule sorting changes in advance, allowing for planned content strategies without requiring developer involvement or manual updates at specific times.

## Integration

### Backoffice Integration

The package adds a dedicated "PowerSort" section to the Umbraco backoffice, providing a dedicated workspace for managing sorting operations.

### Frontend Implementation

OC.PowerSort updates the underlying `sortOrder` property of Umbraco content nodes. Frontend implementation is handled by developers using standard Umbraco content queries:

```csharp
// Content will automatically be returned in the sort order defined by PowerSort
@foreach(var child in Model.Children())
{
    <div>@child.Name</div>
}
```

### API Integration

RESTful APIs are available for programmatic access to scheduling and sorting functionality, enabling integration with external systems or custom backoffice extensions.

## Extensibility

### Creating Custom Sort Providers

Developers can implement custom sorting logic by creating classes that implement the `ISortProvider` interface:

```csharp
public interface ISortProvider
{
    string ProviderKey { get; }          // Unique identifier
    string DisplayName { get; }          // UI display name
    string Description { get; }          // Provider description
    bool SupportsScheduling { get; }     // Whether scheduling is supported
    
    Task<SortResult> CalculateSortOrderAsync(SortContext context);
    Task<ProviderValidationResult> ValidateAsync();
}
```

Custom providers can:

* Integrate with external systems (CRM, analytics, weather APIs, etc.)
* Implement business-specific sorting rules
* Combine multiple data sources for sorting decisions
* Provide validation for configuration requirements

### Provider Registration

Custom providers are automatically discovered and registered through the composer system. Simply implement `ISortProvider` and register it in the DI container.

## Benefits

1. **Editor Empowerment**: Content editors can manage content presentation without developer assistance
2. **Time Efficiency**: Schedule sorting changes in advance, reducing manual workload
3. **Consistency**: Default sort orders ensure consistent content presentation
4. **Flexibility**: Extensible provider system allows custom sorting strategies
5. **Automation**: Recurring schedules eliminate repetitive manual tasks
6. **Predictability**: Scheduled changes occur exactly when planned, every time

## Getting Started

1. **Installation**: Install the NuGet package `OC.PowerSort`
2. **User Permissions**: Grant access to the "PowerSort" section for relevant user groups
3. **Configuration**: Define default sort orders for parent nodes (optional)
4. **Usage**: Navigate to the PowerSort section and start scheduling sorting operations

## Resources

* **GitHub Repository**: <https://github.com/OwainWilliams/OC.PowerSort>
* **NuGet Package**: <https://www.nuget.org/packages/OC.PowerSort/>
* **Video Tutorials**: Available in the README
* **License**: MIT

## Support and Community

For questions, issues, or contributions, please visit the GitHub repository or consult the contributing guidelines.

## Credits

**Created by:**

* [Owain Williams](https://github.com/OwainWilliams/)
* [Harrie Mayhew](https://github.com/mayhemcreates)

**License:** MIT License


