<?xml version="1.0" encoding="utf-8"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><language>en</language><title>Blog posts by Mark Hall</title> <link>https://world.optimizely.com/blogs/Mark-Hall/</link><description></description><ttl>60</ttl><generator>Optimizely World</generator><item> <title>Working with docker and containers - .NET 5 Series, Part 3</title>            <link>https://world.optimizely.com/blogs/Mark-Hall/Dates/2021/9/working-with-docker-and-containers----net-5-series-part-3/</link>            <description>&lt;div class=&quot;alert&amp;#32;alert-secondary&quot;&gt;
&lt;h4&gt;Introduction to Optimizely .NET 5 Series&lt;/h4&gt;
&lt;p&gt;Welcome everyone to a series of posts about the upcoming releases of .Net 5 compatible commerce and content clouds.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/link/419ca46496a04c62b6587ba7c4a4b85f.aspx&quot;&gt;Getting started with upgrade-assistant&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/link/d392f3a2c61e43bf8da88345b50c8ddf.aspx&quot;&gt;Starting a new project&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/link/efb4bfd98fcf4437820933114fdd1f55.aspx&quot;&gt;Working with docker and containers&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Introduction&lt;/h3&gt;
&lt;p&gt;Welcome everyone to the third post in a series of posts about the upcoming releases of Optimizely .Net 5. In this post we will explore creating docker images of Optimizely applications and running them in containers.&lt;/p&gt;
&lt;h3&gt;Install Tools&lt;/h3&gt;
&lt;p&gt;When working with docker you will need to install the docker tools for you &lt;a href=&quot;https://www.docker.com/products/personal&quot;&gt;operating system environment&lt;/a&gt;. Please note if you work for a large organization you will need to license the software. If you are using Windows you will want to make sure you have the &lt;a href=&quot;https://docs.microsoft.com/en-us/windows/wsl/install-win10&quot;&gt;Windows subsystem for linux&lt;/a&gt; installed and enabled since we will be building linux images. Finally, we will be using &lt;a href=&quot;https://github.com/episerver/foundation-mvc-cms/tree/net5&quot;&gt;foundation-mvc-cms&lt;/a&gt; for the example so please clone or download zip file.&lt;/p&gt;
&lt;h3&gt;Creating the .env file&lt;/h3&gt;
&lt;p&gt;We need to create an .env file for the variables that will be used in the image creation. Please place the file src/Foundation.&lt;/p&gt;
&lt;pre class=&quot;language-markup&quot;&gt;&lt;code&gt;sa_password=Episerver123!
database_name=foundation.cms
site_port=44387
web_tag=foundation.mvc.cms
sql_tag=foundation.db
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Creating the Docker Compose File&lt;/h3&gt;
&lt;p&gt;The first thing we need to do is to create the docker-compose.yml file. This file is responsible for defining the images and creating the container instances of the images. This file should be placed on the root of the website in the src/Foundation folder.&lt;/p&gt;
&lt;pre class=&quot;language-markup&quot;&gt;&lt;code&gt;version: &quot;3&quot;
services:
  sql:
    build:
      dockerfile: ./docker/Sql.Dockerfile
      context: .
    # comment out below lines if you want to expose sql server port for using with other tool like sql server management studio
    ports:
      - &quot;1549:1433&quot;
    environment:
      SA_PASSWORD: ${sa_password}
      ACCEPT_EULA: &quot;Y&quot;
      DATABASE_NAME: ${database_name}
    image: foundation/db:${sql_tag}
  web:
    build:
      dockerfile: ./docker/Web.Dockerfile
      context: .
    ports:
      - &quot;${site_port}:80&quot;
    environment:
      ConnectionStrings__EPiServerDB: &quot;Data Source=sql;Database=${database_name};User Id=netcoreUser;Password=epi#Server7Local;MultipleActiveResultSets=True;&quot;
      site_port: ${site_port}
      ASPNETCORE_ENVIRONMENT: &quot;Development&quot;
    depends_on:
      - sql
    image: foundation/web:${web_tag}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This file is creating two images, sql and web. We need to create a database server for our application as well as a web server to handle our traffic to the Optimizely application. We our passing in environment variables and docker files which describe how to build the image for the container. We can also forward ports from external application to internal container.&lt;/p&gt;
&lt;h3&gt;Creating Sql.Dockerfile&lt;/h3&gt;
&lt;p&gt;Lets create a new file called Sql.Dockerfile in src/foundation/docker.&lt;/p&gt;
&lt;pre class=&quot;language-markup&quot;&gt;&lt;code&gt;FROM mcr.microsoft.com/mssql/server:2019-latest AS base

USER root

ENV ACCEPT_EULA=Y
ENV MSSQL_TCP_PORT=1433
EXPOSE 1433

WORKDIR /src
COPY ./docker/build-script/SetupDatabases.sh /docker/SetupDatabases.sh
RUN chmod -R 777 /docker/.

ENTRYPOINT /docker/SetupDatabases.sh &amp;amp; /opt/mssql/bin/sqlservr&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This file is configuring the sql server image. We are using a bash script to recreate a new database as part of the container initialization. Since we have a content data file we can import the necessary content as part of the build process.&lt;/p&gt;
&lt;h3&gt;Create SetupDatabases.sh&lt;/h3&gt;
&lt;p&gt;Create a new bash script file in src/Foundation/Docker/build-script&lt;/p&gt;
&lt;pre class=&quot;language-markup&quot;&gt;&lt;code&gt;#!/bin/bash

export cms_db=foundation.cms
export user=netcoreUser
export password=epi#Server7Local

export sql=&quot;/opt/mssql-tools/bin/sqlcmd -S . -U sa -P ${SA_PASSWORD}&quot;
echo @Wait MSSQL server to start
export STATUS=1
i=0

while [[ $STATUS -ne 0 ]] &amp;amp;&amp;amp; [[ $i -lt 60 ]]; do
    sleep 5s
	i=$i+1
	$sql -Q &quot;select 1&quot; &amp;gt;&amp;gt; /dev/null
	STATUS=$?
    echo &quot;***Starting MSSQL server...&quot;
done

if [ $STATUS -ne 0 ]; then 
	echo &quot;Error: MSSQL SERVER took more than 3 minute to start up.&quot;
	exit 1
fi
echo Dropping databases...
$sql -Q &quot;EXEC msdb.dbo.sp_delete_database_backuphistory N&#39;$cms_db&#39;&quot;
$sql -Q &quot;if db_id(&#39;$cms_db&#39;) is not null ALTER DATABASE [$cms_db] SET SINGLE_USER WITH ROLLBACK IMMEDIATE&quot;
$sql -Q &quot;if db_id(&#39;$cms_db&#39;) is not null DROP DATABASE [$cms_db]&quot;

echo Dropping user...
$sql -Q &quot;if exists (select loginname from master.dbo.syslogins where name = &#39;$user&#39;) EXEC sp_droplogin @loginame=&#39;$user&#39;&quot;

echo Creating databases...
$sql -Q &quot;CREATE DATABASE [$cms_db] COLLATE SQL_Latin1_General_CP1_CI_AS&quot;

echo Creating user...
$sql -Q &quot;EXEC sp_addlogin @loginame=&#39;$user&#39;, @passwd=&#39;$password&#39;, @defdb=&#39;$cms_db&#39;&quot;
$sql -d $cms_db -Q &quot;EXEC sp_adduser @loginame=&#39;$user&#39;&quot;
$sql -d $cms_db -Q &quot;EXEC sp_addrolemember N&#39;db_owner&#39;, N&#39;$user&#39;&quot;

echo Done creating databases.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This script waits for the sql server instance to be active, then tries to drop the database if it exists. Next, it will create the database and database user&lt;/p&gt;
&lt;h3&gt;Creating the Web.Dockerfile&lt;/h3&gt;
&lt;p&gt;Lets create a new file called Web.Dockerfile in src/foundation/docker.&lt;/p&gt;
&lt;pre class=&quot;language-markup&quot;&gt;&lt;code&gt;
FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim AS base
WORKDIR /app
EXPOSE 8000

FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim AS build
WORKDIR /src
COPY . .
RUN dotnet restore &quot;Foundation.csproj&quot; --configfile ./Nuget.config

RUN dotnet build &quot;Foundation.csproj&quot; -c Release -o /app/build

FROM build AS publish
RUN dotnet publish &quot;Foundation.csproj&quot; -c Release -o /app/publish
COPY ./docker/build-script/wait_sqlserver_start_and_attachdb.sh /app/publish/wait_sqlserver_start_and_attachdb.sh
COPY ./App_Data/foundation.episerverdata /app/publish/App_Data/foundation.episerverdata

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
#wait sql server container start and attach alloy database then start web
ENTRYPOINT ./wait_sqlserver_start_and_attachdb.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This file is configuring the web server image. We are using a bash script to wait two minutes for the database to initialize before running the Optimizely application.&lt;/p&gt;
&lt;h3&gt;Create wait_sqlserver_start_and_attachdb.sh&lt;/h3&gt;
&lt;p&gt;Create a new bash script file in src/Foundation/Docker/build-script&lt;/p&gt;
&lt;pre class=&quot;language-markup&quot;&gt;&lt;code&gt;sleep 120s
    echo &quot;Connection string:&quot; $ConnectionStrings__EPiServerDB
    echo &quot;Site port:&quot; $site_port
dotnet Foundation.dll
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Building and running the image&lt;/h3&gt;
&lt;p&gt;Run the following commands to build the images and run them in containers.&lt;/p&gt;
&lt;pre class=&quot;language-markup&quot;&gt;&lt;code&gt;
cd src/foundation
docker-compose up
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will take a couple minutes to the first time it is run. After building the image, it will run the containers and allow you to browse the site or connect to the database using SQL Management studio from the host machine.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost:44387/&quot;&gt;http://localhost:44387/&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;language-markup&quot;&gt;&lt;code&gt;
Server:127.0.0.1,1549
Username: sa
Password: Sa password from .env file
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Using DXP bacpac file&lt;/h3&gt;
&lt;p&gt;In DXP in the &lt;a href=&quot;/link/e857f1af908f40c0840a704ff8b640d8.aspx&quot;&gt;pass portal&lt;/a&gt;, you have the option of exporting the database to a bacpac file which you can use to import into the container. Lets update the Sql.Dockerfile and SetupDatabases.sh script to import the bacpac file instead of creating new database&lt;/p&gt;
&lt;pre class=&quot;language-markup&quot;&gt;&lt;code&gt;Sql.Dockerfile 
FROM mcr.microsoft.com/mssql/server:2019-latest AS base

USER root

ENV ACCEPT_EULA=Y
ENV MSSQL_TCP_PORT=1433
EXPOSE 1433
RUN apt-get update &amp;amp;&amp;amp; apt-get install unzip -y

RUN wget -O sqlpackage.zip https://go.microsoft.com/fwlink/?linkid=2165213 \
    &amp;amp;&amp;amp; unzip sqlpackage.zip -d /tmp/sqlpackage \
    &amp;amp;&amp;amp; chmod +x /tmp/sqlpackage/sqlpackage

COPY database.bacpac /tmp/db/database.bacpac 

WORKDIR /src
COPY ./docker/build-script/SetupDatabases.sh /docker/SetupDatabases.sh
RUN chmod -R 777 /docker/.

ENTRYPOINT /docker/SetupDatabases.sh &amp;amp; /opt/mssql/bin/sqlservr
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-markup&quot;&gt;&lt;code&gt;SetupDatabases.sh
#!/bin/bash

export cms_db=foundation.cms
export user=netcoreUser
export password=epi#Server7Local

export sql=&quot;/opt/mssql-tools/bin/sqlcmd -S . -U sa -P ${SA_PASSWORD}&quot;
export dac=&quot;/tmp/sqlpackage/sqlpackage /a:Import /tsn:localhost,1433 /tdn:${DATABASE_NAME} /tu:sa /tp:${SA_PASSWORD} /sf:/tmp/db/database.bacpac&quot;
echo @Wait MSSQL server to start
export STATUS=1
i=0

while [[ $STATUS -ne 0 ]] &amp;amp;&amp;amp; [[ $i -lt 60 ]]; do
    sleep 5s
	i=$i+1
	$sql -Q &quot;select 1&quot; &amp;gt;&amp;gt; /dev/null
	STATUS=$?
    echo &quot;***Starting MSSQL server...&quot;
done

if [ $STATUS -ne 0 ]; then 
	echo &quot;Error: MSSQL SERVER took more than 3 minute to start up.&quot;
	exit 1
fi
echo Dropping databases...
$sql -Q &quot;EXEC msdb.dbo.sp_delete_database_backuphistory N&#39;$cms_db&#39;&quot;
$sql -Q &quot;if db_id(&#39;$cms_db&#39;) is not null ALTER DATABASE [$cms_db] SET SINGLE_USER WITH ROLLBACK IMMEDIATE&quot;
$sql -Q &quot;if db_id(&#39;$cms_db&#39;) is not null DROP DATABASE [$cms_db]&quot;

echo Dropping user...
$sql -Q &quot;if exists (select loginname from master.dbo.syslogins where name = &#39;$user&#39;) EXEC sp_droplogin @loginame=&#39;$user&#39;&quot;

echo Creating databases...
$dac
rm /tmp/db/database.bacpac
echo Creating user...
$sql -Q &quot;EXEC sp_addlogin @loginame=&#39;$user&#39;, @passwd=&#39;$password&#39;, @defdb=&#39;$cms_db&#39;&quot;
$sql -d $cms_db -Q &quot;EXEC sp_adduser @loginame=&#39;$user&#39;&quot;
$sql -d $cms_db -Q &quot;EXEC sp_addrolemember N&#39;db_owner&#39;, N&#39;$user&#39;&quot;

echo Done creating databases.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next you will need to download the bacpac file and rename to src/Foundation/database.bac&lt;/p&gt;
&lt;p&gt;Run the following commands to build the images and run them in containers.&lt;/p&gt;
&lt;pre class=&quot;language-markup&quot;&gt;&lt;code&gt;
cd src/foundation
docker-compose up
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Today we demonstrated how easy it is to containerize and Optimizely application. We can use these techniques to allow for easy configuration of development environments or for production environments. We can also leverage DXP tools to be able to quickly get a solution running in a container for diagnosing issues.&lt;/p&gt;</description>            <guid>https://world.optimizely.com/blogs/Mark-Hall/Dates/2021/9/working-with-docker-and-containers----net-5-series-part-3/</guid>            <pubDate>Tue, 21 Sep 2021 03:46:39 GMT</pubDate>           <category>Blog post</category></item><item> <title>Starting a new project - .NET 5 Series, Part 2</title>            <link>https://world.optimizely.com/blogs/Mark-Hall/Dates/2021/8/starting-a-new-project----net-5-series-part-2/</link>            <description>&lt;div class=&quot;alert&amp;#32;alert-secondary&quot;&gt;
&lt;h4&gt;Introduction to Optimizely .NET 5 Series&lt;/h4&gt;
&lt;p&gt;Welcome everyone to a series of posts about the upcoming releases of .Net 5 compatible commerce and content clouds.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/link/419ca46496a04c62b6587ba7c4a4b85f.aspx&quot;&gt;Getting started with upgrade-assistant&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/link/d392f3a2c61e43bf8da88345b50c8ddf.aspx&quot;&gt;Starting a new project&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/link/efb4bfd98fcf4437820933114fdd1f55.aspx&quot;&gt;Working with docker and containers&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Introduction&lt;/h3&gt;
&lt;p&gt;Welcome everyone to the second post in a series of posts about the upcoming releases of Optimizely .Net 5. In this post we will explore starting a new project.&amp;nbsp; Optimizely Content and Commerce clouds have always been relatively easy to setup a new project, and .Net 5 is no different.&amp;nbsp; Lets take a look at whats changed compared to full framework.&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;Install Tools&lt;/h3&gt;
&lt;p&gt;When working with .Net 5, Microsoft provides tools to create and manage .Net 5 applications. These tools are actually .Net 5 applications themselves, which allow them to run on all operating systems.&amp;nbsp; In the full framework version, Optimizely relied on Visual Studio extensions and visual studio powershell to install new projects and update schema.&amp;nbsp; This of course will not work in .Net 5 because Visual Studio is not available on all the operating systems that can run .Net 5 applications.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;language-markup&quot;&gt;&lt;code&gt; dotnet new -i EPiServer.Net.Templates::1.0.0-pre-020039 --nuget-source https://pkgs.dev.azure.com/EpiserverEngineering/netCore/_packaging/beta-program/nuget/v3/index.json --force
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-markup&quot;&gt;&lt;code&gt; dotnet tool install EPiServer.Net.Cli --global --add-source https://pkgs.dev.azure.com/EpiserverEngineering/netCore/_packaging/beta-program/nuget/v3/index.json --version 1.0.0-pre-020034

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Note please use the beta feed for the preview packages until they are pushed to the normal Optimizely feed.)&lt;/p&gt;
&lt;h3&gt;Understanding the tools&lt;/h3&gt;
&lt;p&gt;The first tool we are installing are Optimizely project templates.&amp;nbsp; .Net 5 allows the developers the abiility to create a new project template using the &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-new&quot;&gt;dotnet new command&lt;/a&gt;.&amp;nbsp; There are many templates out there to help scaffold different types of .Net 5 applications.&amp;nbsp; For Optimizely, we provide empty cms and empty commerce projects.&lt;/p&gt;
&lt;p&gt;The second tool is the Optimizely CLI tool.&amp;nbsp; This is a tool for creating database and database users as well as updating connection strings in appsettings.json.&amp;nbsp; It is important to note that this tool does not install any schema, it just creates the database and the user.&amp;nbsp; The schema is auto installed for the connection string if it does not find the schema it needs to run the application.&lt;/p&gt;
&lt;h3&gt;Creating the project&lt;/h3&gt;
&lt;div class=&quot;alert&amp;#32;alert-secondary&quot;&gt;
&lt;p&gt;If the run command fails with missing assembly add beta feed package source to nuget.config on the root of the project folder and rerun.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Lets create a new cms project.&lt;/p&gt;
&lt;pre class=&quot;language-markup&quot;&gt;&lt;code&gt;dotnet new epicmsempty --name ProjectName
cd projectname
dotnet-episerver create-cms-database ProjectName.csproj -S . -E 
dotnet run&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now lets create a new commerce project.&lt;/p&gt;
&lt;pre class=&quot;language-markup&quot;&gt;&lt;code&gt;dotnet new epicommerceempty --name ProjectName
cd projectname
dotnet-episerver create-cms-database ProjectName.csproj -S . -E
dotnet-episerver create-commerce-database ProjectName.csproj -S . -E --reuse-cms-user
dotnet run&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Foundation&lt;/h3&gt;
&lt;p&gt;If you prefer to start with one of the foundation projects we have .Net 5 branches available.&amp;nbsp; These branches will move to develop once the software is General Availabiility.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/episerver/Foundation/tree/net5&quot;&gt;Foundation&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/episerver/foundation-mvc-cms/tree/net5&quot;&gt;foundation-mvc-cms&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Closing Thoughts&lt;/h3&gt;
&lt;p&gt;Getting started with a new Optimizely project is as easy as ever.&amp;nbsp; It is even easier for the non windows users who used to struggle with virtual machines to run the software locally.&amp;nbsp; With the cross plaform capabilities it is now possible to develop on your favored operating system.&amp;nbsp; In a future post we go a step further and explore how we leverage containers to run Optimizely.&lt;/p&gt;</description>            <guid>https://world.optimizely.com/blogs/Mark-Hall/Dates/2021/8/starting-a-new-project----net-5-series-part-2/</guid>            <pubDate>Wed, 04 Aug 2021 04:01:23 GMT</pubDate>           <category>Blog post</category></item><item> <title>Getting started with upgrade-assistant - .NET 5 Series, Part 1</title>            <link>https://world.optimizely.com/blogs/Mark-Hall/Dates/2021/7/getting-started-with-upgrade-assistant----net-5-series-part-1/</link>            <description>&lt;div class=&quot;alert&amp;#32;alert-secondary&quot;&gt;
&lt;h4&gt;Introduction to Optimizely .NET 5 Series&lt;/h4&gt;
&lt;p&gt;Welcome everyone to a series of posts about the upcoming releases of .Net 5 compatible commerce and content clouds.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/link/419ca46496a04c62b6587ba7c4a4b85f.aspx&quot;&gt;Getting started with upgrade-assistant&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/link/d392f3a2c61e43bf8da88345b50c8ddf.aspx&quot;&gt;Starting a new project&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/link/efb4bfd98fcf4437820933114fdd1f55.aspx&quot;&gt;Working with docker and containers&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Introduction&lt;/h3&gt;
&lt;p&gt;Welcome everyone to the first post in a series of posts about the upcoming releases of Optimizely .Net 5. In this post we will explore the &lt;a href=&quot;https://github.com/dotnet/upgrade-assistant&quot;&gt;upgrade-assistant&lt;/a&gt; tool from Microsoft.&amp;nbsp; This tool allows for developers to upgrade their .net full framework applications to .NET 5 and beyond.&amp;nbsp; It is also built to be extensible so ISV&#39;s can add extensions to make it easier to for their customers to upgrade their solutions.&amp;nbsp; Optimizely has created its own &lt;a href=&quot;https://github.com/episerver/upgrade-assistant-extensions&quot;&gt;extension library&lt;/a&gt; to automate common Optimizely specific fixes when moving to .NET 5 and beyond.&amp;nbsp; Lets dive into how to use the tool to ugrade your solution.&lt;/p&gt;
&lt;h3&gt;Install Tools&lt;/h3&gt;
&lt;p&gt;Install the latest vesion of the upgrade-assistant&lt;/p&gt;
&lt;pre class=&quot;language-markup&quot;&gt;&lt;code&gt; dotnet tool install -g upgrade-assistant&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or upgrade&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;language-markup&quot;&gt;&lt;code&gt;dotnet tool update -g upgrade-assistant&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Grab the latest release of &lt;a href=&quot;https://github.com/episerver/upgrade-assistant-extensions/releases&quot;&gt;Optimizely upgrade-assistant-extensions&lt;/a&gt; and unzip the file to a location of your computer (ex C:\temp\epi.source.updater). Technically you should be able to point the zip file instead of extracting, but there seems to be a bug in upgrade-assisant at the moment for that.&lt;/p&gt;
&lt;p&gt;Make sure to add the beta feed to you nuget config before running the tool.&amp;nbsp; Upgrade-asstant will use the defined package sources when looking for packages to update that are compatiable with .NET 5&lt;/p&gt;
&lt;pre class=&quot;language-markup&quot;&gt;&lt;code&gt;https://pkgs.dev.azure.com/EpiserverEngineering/netCore/_packaging/beta-program/nuget/v3/index.json&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Upgrade the solution&lt;/h3&gt;
&lt;pre class=&quot;language-markup&quot;&gt;&lt;code&gt;upgrade-assistant upgrade {projectName}.csproj --extension &quot;{extensionPath}&quot; --ignore-unsupported-features
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can point to solutiion file if you are upgrading more than project.&amp;nbsp; It is important to note that this tool is not meant to complete and handle all errors.&amp;nbsp; There are some fundamental differences between full framework and .NET 5 that make it impossible to automate everything.&amp;nbsp; It should however handle all the mundane repetitive tasks that can be automated, allowing the developer to worry about fixing the larger issues.&amp;nbsp; Also, after using a couple of times on solutions, I tend to use the non-interactive mode which speeds up the process.&amp;nbsp; I would not use this mode until you have used the tool a couple of times and understand what the tool is doing.&lt;/p&gt;
&lt;h3&gt;Closing Thoughts&lt;/h3&gt;
&lt;p&gt;I want to thank Microsoft for developing such a great tool.&amp;nbsp; We started with a couple ideas on how we could make the upgrade easier for our customers to .NET 5 and upgrade-assisant was the end result.&amp;nbsp; As I have been following the project, I have seen many nice additions in the journey to .NET 5 and beyond.&amp;nbsp; I am super exicted about the &lt;a href=&quot;https://github.com/dotnet/maui&quot;&gt;.NET Multi-platform App UI (MAUI)&lt;/a&gt; and see they are adding support for migrating WPF and Xarmin apps to the new maui framework through upgrade-assistant.&amp;nbsp;&amp;nbsp;&lt;/p&gt;</description>            <guid>https://world.optimizely.com/blogs/Mark-Hall/Dates/2021/7/getting-started-with-upgrade-assistant----net-5-series-part-1/</guid>            <pubDate>Wed, 28 Jul 2021 20:37:27 GMT</pubDate>           <category>Blog post</category></item><item> <title>Fallback languages for catalog content</title>            <link>https://world.optimizely.com/blogs/Mark-Hall/Dates/2020/4/fallback-languages-for-catalog-content/</link>            <description>&lt;p&gt;I recently had a partner ask me how they could get catalog content to work with fallback languages.&amp;nbsp; I figured this would be a tough task as been on the roadmap for awhile but tried to give this a shot.&amp;nbsp; I went looking into resuing the ContentLanguageSetting used in Cms but quickly found the it relies on content being stored in the content tables in the CMS.&amp;nbsp;&amp;nbsp;The partner use case only needed to fallback to a neutral version if the country specific locale did not exist.&amp;nbsp; This alllowed me to tackle using code.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Here is a&amp;nbsp;&lt;a href=&quot;https://gist.github.com/lunchin/0d8d8afdcce558aeb04e1a9dc8b2020e&quot;&gt;gist&lt;/a&gt;&amp;nbsp;of the code that was used.&lt;/p&gt;
&lt;p&gt;Please note the two Get overloads in&amp;nbsp;FallbackLanguageSettingsHandler as this is waht determines the fallback and replacement languages.&amp;nbsp; Here you could read from config or elsewhere if you need to have more business rules around which languages to choose.&amp;nbsp; Also note you need to set the&amp;nbsp;strictLanguageRouting&lt;span class=&quot;s1&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot; in episerver section.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Hopefully this helps someone else trying to do the same thing.&lt;/p&gt;</description>            <guid>https://world.optimizely.com/blogs/Mark-Hall/Dates/2020/4/fallback-languages-for-catalog-content/</guid>            <pubDate>Fri, 17 Apr 2020 18:54:55 GMT</pubDate>           <category>Blog post</category></item><item> <title>ServiceApi Customers and Organizations Extended</title>            <link>https://world.optimizely.com/blogs/Mark-Hall/Dates/2018/6/serviceapi-customers-and-organizations-extended/</link>            <description>&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;p&gt;Recently I helped a couple of partners who needed to be able to update custom properties on customers and organizations using the service api.&amp;nbsp; I sent them some code and thought I would share here for anyone else who might need.&amp;nbsp; Hopefully soon this will be in the product as it is on the backlog.&lt;/p&gt;
&lt;p&gt;Since there is seven files I created a sample project on my github where you can see the extensions&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/lunchin/EPiServer.ServiceApi.Extended.Sample&quot;&gt;https://github.com/lunchin/EPiServer.ServiceApi.Extended.Sample&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;To use the extensions, install the repo and refer to the API folder and specifically&amp;nbsp;CustomerExtendedController.&amp;nbsp; This api controller exposes the new endpoints. Next weill use postman to demonstarte how to use the new endpoints.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;First we need to get the token for our request&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;img src=&quot;/link/e1032090281444b7bb723841730c3898.aspx&quot; alt=&quot;Image Token.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;2. Next we will do a get on the contacts to make it easy to do an update the contact.&amp;nbsp; Make sure you add the authorization bearer token you received from the previous request&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/51ba6fe8a9b64df4961d4aeb627d09e7.aspx&quot; alt=&quot;Image GetContacts.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp; &amp;nbsp; 3.&amp;nbsp; Finally lets update the contact with a PUT.&amp;nbsp; First I show the body payload and then the request in postman.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;{
        &quot;FirstName&quot;: &quot;Order&quot;,
        &quot;LastName&quot;: &quot;Manager&quot;,
        &quot;Email&quot;: &quot;manager@unknowdomain.domain&quot;,
        &quot;RegistrationSource&quot;: &quot;john&quot;,
        &quot;PrimaryKeyId&quot;: &quot;3e6c1665-3d09-49f6-8c1a-fc91e7341dae&quot;,
        &quot;Addresses&quot;: [],
        &quot;MetaFields&quot;: [
            {
                &quot;Name&quot;: &quot;Created&quot;,
                &quot;Type&quot;: &quot;DateTime&quot;,
                &quot;Values&quot;: [
                    &quot;2018-06-06T11:28:28.2170000-07:00&quot;
                ]
            },
            {
                &quot;Name&quot;: &quot;Modified&quot;,
                &quot;Type&quot;: &quot;DateTime&quot;,
                &quot;Values&quot;: [
                    &quot;2018-06-06T11:28:28.2170000-07:00&quot;
                ]
            },
            {
                &quot;Name&quot;: &quot;CreatorId&quot;,
                &quot;Type&quot;: &quot;Guid&quot;,
                &quot;Values&quot;: [
                    &quot;d4af0b76-3ea8-4a3a-86ea-22d4b3ca2552&quot;
                ]
            },
            {
                &quot;Name&quot;: &quot;ModifierId&quot;,
                &quot;Type&quot;: &quot;Guid&quot;,
                &quot;Values&quot;: [
                    &quot;9c28ec95-fdec-41e9-aa1e-8acfcc58c074&quot;
                ]
            },
            {
                &quot;Name&quot;: &quot;FullName&quot;,
                &quot;Type&quot;: &quot;Text&quot;,
                &quot;Values&quot;: [
                    &quot;Order Test&quot;
                ]
            },
            {
                &quot;Name&quot;: &quot;UserId&quot;,
                &quot;Type&quot;: &quot;Text&quot;,
                &quot;Values&quot;: [
                    &quot;String:manager&quot;
                ]
            }
        ]
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/link/3192a36dc6af4e9d817f453b202a0fe2.aspx&quot; alt=&quot;Image PUT.png&quot; /&gt;&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;</description>            <guid>https://world.optimizely.com/blogs/Mark-Hall/Dates/2018/6/serviceapi-customers-and-organizations-extended/</guid>            <pubDate>Wed, 15 Aug 2018 21:03:08 GMT</pubDate>           <category>Blog post</category></item><item> <title>Simple Content Synchronization</title>            <link>https://world.optimizely.com/blogs/Mark-Hall/Dates/2018/6/simple-content-synchronization/</link>            <description>&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;p&gt;I recently received a question from a partner trying to implement a requirement for having no live edits on the production server.&amp;nbsp; I have received this question a few other times so I thought I would see if I could come up with a solution that would work.&amp;nbsp; This example is a really simple solution and should probably be extended for production use. For example a&amp;nbsp;service bus should be used for the change set&amp;nbsp;and maybe azure logic apps or scheduled job to read the queue and process it.&lt;/p&gt;
&lt;p&gt;The first thing I did was create an initialization module for listening to the publish events.&amp;nbsp; There is some code to check if these events should run.&amp;nbsp; This appsetting should only be set on the staging server that is to be used for content changes.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;using EPiServer.Core;
using EPiServer.Core.Transfer;
using EPiServer.Enterprise;
using EPiServer.Framework;
using EPiServer.Framework.Initialization;
using EPiServer.Logging;
using EPiServer.ServiceLocation;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;

namespace EPiServer.Reference.Commerce.Site.Infrastructure.Initialization
{
    [InitializableModule]
    [ModuleDependency(typeof(Web.InitializationModule))]
    public class PublishSync : IInitializableModule
    {
        private ServiceAccessor&amp;lt;IDataExporter&amp;gt; _dataExporterAccessor;
        private IContentLoader _contentLoader;
        private const string SettingPageFiles = &quot;ExportPageFiles&quot;;
        private const string SettingRecursively = &quot;ExportRecursively&quot;;
        private const string SettingPageLink = &quot;ExporterPageLink&quot;;
        private const string SettingIncludeContentTypeDependencies = &quot;ExportIncludeContentTypeDependencies&quot;;
        private string _username;
        private string _password;
        private string _stagingUrl;
        private ILogger _logger = LogManager.GetLogger(typeof(PublishSync));

        public void Initialize(InitializationEngine context)
        {
            if (!bool.TryParse(ConfigurationManager.AppSettings[&quot;contentStaging:Enabled&quot;], out var stagingEnabled))
            {
                return;
            }

            _stagingUrl = ConfigurationManager.AppSettings[&quot;contentStaging:ProductionUrlBase&quot;];
            _username = ConfigurationManager.AppSettings[&quot;contentStaging:Username&quot;];
            _password = ConfigurationManager.AppSettings[&quot;contentStaging:Password&quot;];
            _dataExporterAccessor = context.Locate.Advanced.GetInstance&amp;lt;ServiceAccessor&amp;lt;IDataExporter&amp;gt;&amp;gt;();
            _contentLoader = context.Locate.Advanced.GetInstance&amp;lt;IContentLoader&amp;gt;();

            var events = context.Locate.Advanced.GetInstance&amp;lt;IContentEvents&amp;gt;();
            events.PublishedContent += Events_PublishedContent;
        }

        public void Uninitialize(InitializationEngine context)
        {
            var events = context.Locate.Advanced.GetInstance&amp;lt;IContentEvents&amp;gt;();
            events.PublishedContent -= Events_PublishedContent;
        }

        private void Events_PublishedContent(object sender, ContentEventArgs e)
        {
            if (e.Content is PageData || e.Content is BlockData || e.Content is MediaData)
            {
                Task.Run(() =&amp;gt; ExportItem(e.Content, _dataExporterAccessor(), _contentLoader));
            }
        }

        private async Task ExportItem(IContent content, IDataExporter exporter, IContentLoader contentLoader)
        {
            var exportedFileLocation = Path.GetTempFileName();
            var stream = new FileStream(exportedFileLocation, FileMode.Create, FileAccess.ReadWrite, FileShare.None);
            var settings = new Dictionary&amp;lt;string, object&amp;gt;();
            settings[SettingPageLink] = content.ContentLink;
            settings[SettingRecursively] = false;
            settings[SettingPageFiles] = true;
            settings[SettingIncludeContentTypeDependencies] = true;

            var sourceRoots = new List&amp;lt;ExportSource&amp;gt;();
            sourceRoots.Add(new ExportSource(content.ContentLink, ExportSource.NonRecursive));

            var options = ExportOptions.DefaultOptions;
            options.ExcludeFiles = false;
            options.IncludeReferencedContentTypes = true;

            IContent parent;
            contentLoader.TryGet(content.ParentLink, out parent);

            var state = new ExportState
            {
                Stream = stream,
                Exporter = exporter,
                FileLocation = exportedFileLocation,
                Options = options,
                SourceRoots = sourceRoots,
                Settings = settings,
                Parent = parent?.ContentGuid ?? Guid.Empty
            };

            if (state.Parent == Guid.Empty)
            {
                return;
            }

            try
            {
                exporter.Export(state.Stream, state.SourceRoots, state.Options);
                exporter.Dispose();
                await SendContent(state.FileLocation, state.Parent);
            }
            catch (Exception ex)
            {
                exporter.Abort();
                exporter.Status.Log.Error(&quot;Can&#39;t export package because: {0}&quot;, ex, ex.Message);
            }
        }


        private async Task SendContent(string file, Guid parentId)
        {
            var token = await GetToken();
            if (string.IsNullOrEmpty(token))
            {
                return;
            }
            using (var client = new HttpClient())
            {
                client.BaseAddress = new Uri(_stagingUrl);
                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(&quot;Bearer&quot;, token);
                var content = new MultipartFormDataContent();
                var filestream = new FileStream(file, FileMode.Open);
                content.Add(new StreamContent(filestream), &quot;file&quot;, &quot;Import.episerverdata&quot;);
                var response = await client.PostAsync($&quot;/episerverapi/import/cms/content/{parentId}&quot;, content);
                if (response.StatusCode != HttpStatusCode.OK)
                {
                    _logger.Error(response.Content.ReadAsStringAsync().Result);
                }
            }
        }

        private async Task&amp;lt;string&amp;gt; GetToken()
        {
            using (var client = new HttpClient())
            {
                client.BaseAddress = new Uri(_stagingUrl);
                var fields = new Dictionary&amp;lt;string, string&amp;gt;
                {
                    { &quot;grant_type&quot;, &quot;password&quot; },
                    { &quot;username&quot;, _username },
                    { &quot;password&quot;, _password }
                };
                var response = await client.PostAsync(&quot;/episerverapi/token&quot;, new FormUrlEncodedContent(fields));
                if (response.StatusCode == HttpStatusCode.OK)
                {
                    var content = response.Content.ReadAsStringAsync().Result;
                    var token = JObject.Parse(content).GetValue(&quot;access_token&quot;);
                    return token.ToString();
                }
            }
            return null;
        }

        private class ExportState
        {
            public string FileLocation { get; set; }
            public IDataExporter Exporter { get; set; }
            public ExportOptions Options { get; set; }
            public Stream Stream { get; set; }
            public IList&amp;lt;ExportSource&amp;gt; SourceRoots { get; set; }
            public Dictionary&amp;lt;string, object&amp;gt; Settings { get; set; }
            public Guid Parent { get; set; }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next I added an endpoint to be able to update the content with the changeset.&amp;nbsp; This makes use of some service api classes so a reference to the package will be needed to create the endpoint.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;using EPiServer.Core;
using EPiServer.Enterprise;
using EPiServer.ServiceApi.Configuration;
using EPiServer.ServiceApi.Extensions;
using EPiServer.ServiceApi.Util;
using EPiServer.ServiceLocation;
using EPiServer.Web.Internal;
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Description;

namespace EPiServer.Reference.Commerce.Site.Features.Rest
{
    public class ImportController : ApiController
    {
        private static readonly ApiCallLogger _logger = new ApiCallLogger(typeof(ImportController));
        private readonly PermanentLinkMapper _permanentLinkMapper;
        private readonly  ServiceAccessor&amp;lt;IDataImporter&amp;gt; _dataImporterAccessor;

        private const string InvalidMediaTypeMessage = &quot;Wrong Media Type&quot;;

        public ImportController(PermanentLinkMapper permanentLinkMapper, ServiceAccessor&amp;lt;IDataImporter&amp;gt; dataImporterAccessor)
        {
            _permanentLinkMapper = permanentLinkMapper;
            _dataImporterAccessor = dataImporterAccessor;
        }

        [Route(&quot;episerverapi/import/cms/content/{id:guid}&quot;, Name = &quot;mh_UpdateContent&quot;)]
        [HttpPost]
        [ResponseType(typeof(Guid))]
        [AuthorizePermission(Permissions.GroupName, Permissions.Write)]
        public virtual async Task&amp;lt;IHttpActionResult&amp;gt; PostCmsImport(Guid id)
        {
            if (!Request.Content.IsMimeMultipartContent())
            {
                _logger.Error(InvalidMediaTypeMessage, Request.CreateResponseException(InvalidMediaTypeMessage, HttpStatusCode.UnsupportedMediaType));
                throw Request.CreateResponseException(InvalidMediaTypeMessage, HttpStatusCode.UnsupportedMediaType);
            }
            
            var file = await Request.GetUploadedFile(UploadPaths.IntegrationDataPath);
            var destinationRoot = _permanentLinkMapper.Find(id);
            if (destinationRoot == null)
            {
                return Ok(id);
            }

            var importerOptions = ImportOptions.DefaultOptions;
            importerOptions.KeepIdentity = true;
            importerOptions.ValidateDestination = true;
            importerOptions.EnsureContentNameUniqueness = true;
            importerOptions.IsTest = false;
            var importer = _dataImporterAccessor();
            Security.PrincipalInfo.RecreatePrincipalForThreading();
            var state = new ImporterState
            {
                Destination = destinationRoot.ContentReference,
                Importer = importer,
                Options = importerOptions,
                Stream = new FileStream(file.LocalFileName, FileMode.Open)
            };

            var message = await ImportFileThread(state);
            if (string.IsNullOrEmpty(message))
            {
                return Ok(id);
            }
            else
            {
                throw Request.CreateResponseException(message, HttpStatusCode.InternalServerError);
            }
        }

        private Task&amp;lt;string&amp;gt; ImportFileThread(ImporterState state)
        {
            return Task.Run(() =&amp;gt;
            {
                try
                {
                    state.Importer.Import(state.Stream, state.Destination, state.Options);
                    return &quot;&quot;;
                }
                catch (Exception ex)
                {
                    _logger.Error(&quot;Can&#39;t import data because, &quot;, ex);
                    return ex.StackTrace;
                }
            });
        }
    }

    public class ImporterState
    {
        public ContentReference Destination { get; set; }
        public IDataImporter Importer { get; set; }
        public Stream Stream { get; set; }
        public ImportOptions Options { get; set; }
    }
}&lt;/code&gt;&lt;/pre&gt;

&lt;/body&gt;
&lt;/html&gt;</description>            <guid>https://world.optimizely.com/blogs/Mark-Hall/Dates/2018/6/simple-content-synchronization/</guid>            <pubDate>Thu, 09 Aug 2018 01:28:34 GMT</pubDate>           <category>Blog post</category></item><item> <title>Hide Sites in Page Tree for editors without Create or Edit Access rights</title>            <link>https://world.optimizely.com/blogs/Mark-Hall/Dates/2018/5/hide-sites-in-page-tree-for-editors-without-create-or-edit-access-rights/</link>            <description>&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;p&gt;Recently I was asked by a customer how they could hide sites in the page tree for users without edit access.&amp;nbsp; I thought this was easily done using access rights, but I was wroing as the content still shows but cannot be edited since everyone has read access.&amp;nbsp; After some googling I found this&amp;nbsp;&lt;a href=&quot;/link/bb8b930dc1d0456796f0cffea2797496.aspx&quot;&gt;article&lt;/a&gt;&amp;nbsp;which was for CMS 6R2.&amp;nbsp; I tried removing the everyone role read access, but that led to the none of the sites showing in the page tree.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Next I tried the next&amp;nbsp;&lt;a href=&quot;/link/4b0bec6aaf7b454cb28e9e7c06d54432.aspx&quot;&gt;forum post&lt;/a&gt;&amp;nbsp;which led to this&amp;nbsp;&lt;a href=&quot;https://tedgustaf.com/blog/2013/hide-pages-in-the-page-tree-in-episerver-7/&quot;&gt;article&lt;/a&gt;.&amp;nbsp; The code below is a continuation of the code found on the blog post by Thomas Krantz.&amp;nbsp; Please note that this code only cares about pages so editors will still have access to commerce content, blocks, media, etc.&amp;nbsp; It is also required either Edit or Create access to show the tree.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;using EPiServer.Cms.Shell.UI.Rest.ContentQuery;
using EPiServer.Core;
using EPiServer.Data.Dynamic;
using EPiServer.Filters;
using EPiServer.Security;
using EPiServer.ServiceLocation;
using EPiServer.Shell.ContentQuery;
using System.Collections.Generic;
using System.Linq;

namespace EPiServer.Reference.Commerce.Site.Infrastructure
{
    [ServiceConfiguration(typeof(IContentQuery))]
    public class MyGetChildrenQuery : GetChildrenQuery
    {
        private readonly IContentRepository _contentRepository;
        private readonly IContentQueryHelper _queryHelper;
        private readonly LanguageSelectorFactory _languageSelectorFactory;

        public MyGetChildrenQuery(IContentQueryHelper queryHelper, IContentRepository contentRepository, LanguageSelectorFactory languageSelectorFactory) 
            : base(queryHelper, contentRepository, languageSelectorFactory)
        {
            _contentRepository = contentRepository;
            _queryHelper = queryHelper;
            _languageSelectorFactory = languageSelectorFactory;
        }

        public override int Rank =&amp;gt; 100; 

        protected override IEnumerable&amp;lt;IContent&amp;gt; GetContent(ContentQueryParameters parameters)
        {
            if (((parameters.References == null) || !parameters.References.Any()) &amp;amp;&amp;amp; ContentReference.IsNullOrEmpty(parameters.ReferenceId))
            {
                return Enumerable.Empty&amp;lt;IContent&amp;gt;();
            }

            var selector = _languageSelectorFactory.AutoDetect(true);
            IContent content = null;

            if (!ContentReference.IsNullOrEmpty(parameters.ReferenceId))
            {
                content = _contentRepository.Get&amp;lt;IContent&amp;gt;(parameters.ReferenceId) as PageData;
                if (content == null)
                {
                    return base.GetContent(parameters);
                }
                if (content.ContentLink.ID == 1 || (FilterAccess.QueryDistinctAccessEdit(content, AccessLevel.Edit) || FilterAccess.QueryDistinctAccessEdit(content, AccessLevel.Create)))
                {
                    return GetChildren(parameters, parameters.ReferenceId, selector);
                }
                return Enumerable.Empty&amp;lt;IContent&amp;gt;();
            }

            var filteredContent = new List&amp;lt;IContent&amp;gt;();
            foreach(var link in parameters.References.ToList())
            {
                content = _contentRepository.Get&amp;lt;IContent&amp;gt;(link) as PageData;
                if (content == null)
                {
                    filteredContent.AddRange(base.GetContent(parameters));
                    continue;
                }
                if (!FilterAccess.QueryDistinctAccessEdit(content, AccessLevel.Edit) || !FilterAccess.QueryDistinctAccessEdit(content, AccessLevel.Create))
                {
                    continue;
                }
                filteredContent.AddRange(GetChildren(parameters, parameters.ReferenceId, selector));
            }
            return filteredContent;
        }

        private IEnumerable&amp;lt;IContent&amp;gt; GetChildren(ContentQueryParameters parameters, ContentReference parentId, LanguageSelector selector)
        {
            IEnumerable&amp;lt;IContent&amp;gt; children = null;

            if (parameters.TypeIdentifiers != null &amp;amp;&amp;amp; parameters.TypeIdentifiers.Any())
            {
                // Get by type and concatenate into one list
                children =
                    parameters.TypeIdentifiers.Select(i =&amp;gt; TypeResolver.GetType(i, false))
                        .Where(type =&amp;gt; type != null)
                        .SelectMany(type =&amp;gt; GetChildrenByType(type, parentId, selector))
                        .Distinct()
                        .Where(x =&amp;gt; FilterAccess.QueryDistinctAccessEdit(x, AccessLevel.Edit) || FilterAccess.QueryDistinctAccessEdit(x, AccessLevel.Create));
            }
            else
            {
                children = _contentRepository.GetChildren&amp;lt;IContent&amp;gt;(parentId, selector)
                    .Where(x =&amp;gt; FilterAccess.QueryDistinctAccessEdit(x, AccessLevel.Edit) || FilterAccess.QueryDistinctAccessEdit(x, AccessLevel.Create)); ;
            }

            return (children ?? Enumerable.Empty&amp;lt;IContent&amp;gt;());
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is how the access rights or setup for the site in questions.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/707701c1148a4133887f7bd1b69ac9cc.aspx&quot; alt=&quot;Image AccessRights.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Here is how the user permissions has been setup.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/2cf86d37aa7c4895ab51c4dece566c82.aspx&quot; alt=&quot;Image UserPermissions.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Here is the site with administrator access.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/c16b7c42f98e4a4f9e6130aea4363f70.aspx&quot; alt=&quot;Image Adminsitator.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Here is the site with just site1 access.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/ad5ff86e21424b7fbb817afb9fd44e94.aspx&quot; alt=&quot;Image Site1.png&quot; /&gt;&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;</description>            <guid>https://world.optimizely.com/blogs/Mark-Hall/Dates/2018/5/hide-sites-in-page-tree-for-editors-without-create-or-edit-access-rights/</guid>            <pubDate>Wed, 16 May 2018 20:04:25 GMT</pubDate>           <category>Blog post</category></item><item> <title>Mixed Mode Authentication</title>            <link>https://world.optimizely.com/blogs/Mark-Hall/Dates/2017/10/mixed-mode-authentication/</link>            <description>&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;p&gt;Recently I have been asked to show a working mode authtenication as a couple partners coulld not get it working.&amp;nbsp; In this post I will go through the steps of setting up mixed mode with asp.net identity and WS-Federation with Azure AD.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Azure Application Setup&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;First we need to setup our application in the azure&amp;nbsp;&lt;a href=&quot;https://portal.azure.com&quot;&gt;portal&lt;/a&gt;.&amp;nbsp; Click on Azure Active Directory, and then&amp;nbsp;App registrations.&amp;nbsp; Now click on the new app registration button.&amp;nbsp; Fill out the name, application type, and sign-on url like below.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/99ad9a7ecee84e2faa63cc3121040caa.aspx&quot; alt=&quot;Image newapp.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After creating then new application click on the application name to edit the application.&amp;nbsp; Click on the manifest button so we can add application roles.&amp;nbsp;&amp;nbsp;&lt;span&gt;Locate the&amp;nbsp;&lt;/span&gt;&lt;strong&gt;appRoles&lt;/strong&gt;&lt;span&gt;&amp;nbsp;setting and insert the appRole definitions in the array. &amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;This is an example of approles that declare WebAdmins and WebEditors. You can modify it according to your application roles. Note that you need to generate new Guid for each role declaration.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;&quot;appRoles&quot;: [
    {
      &quot;allowedMemberTypes&quot;: [
        &quot;User&quot;
      ],
      &quot;description&quot;: &quot;Editor can edit the site.&quot;,
      &quot;displayName&quot;: &quot;WebEditors&quot;,
      &quot;id&quot;: &quot;XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX&quot;,
      &quot;isEnabled&quot;: true,
      &quot;value&quot;: &quot;WebEditors&quot;
    },
    {
      &quot;allowedMemberTypes&quot;: [
        &quot;User&quot;
      ],
      &quot;description&quot;: &quot;Admins can manage roles and perform all task actions.&quot;,
      &quot;displayName&quot;: &quot;WebAdmins&quot;,
      &quot;id&quot;: &quot;XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX&quot;,
      &quot;isEnabled&quot;: true,
      &quot;value&quot;: &quot;WebAdmins&quot;
    },
     {
      &quot;allowedMemberTypes&quot;: [
        &quot;User&quot;
      ],
      &quot;description&quot;: &quot;Admin the site.&quot;,
      &quot;displayName&quot;: &quot;Admininstrators&quot;,
      &quot;id&quot;: &quot;XXXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX&quot;,
      &quot;isEnabled&quot;: true,
      &quot;value&quot;: &quot;Admininstrators&quot;
    }
  ],&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After updating the mainfest, we will need to update some settings of the application.&amp;nbsp; Click on the settings button of the application.&amp;nbsp; Next click on permissions and edit the required permissions.&amp;nbsp; Read should be enough but you can set to write as well.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/2b529989dd0d4156881e208413ddc475.aspx&quot; alt=&quot;Image permissions.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Next we need to assign membership to the application roles.&amp;nbsp; In Azure Active Directory view, choose enterprise applications.&amp;nbsp; Choose all applications and select the application that was just created.&amp;nbsp; Select users and groups and click on the button add new user.&amp;nbsp; You will then select a user and map to a role either Administrators or WebAdmins.&amp;nbsp; Please note for standard Active Directory you will need to manually assign each user to a role.&amp;nbsp; If you have premium AD you can map a group to a role.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/8749ba1b9e0e4da3b771199bc016812c.aspx&quot; alt=&quot;Image roles.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Next we need to get the endpoints to use to connect to our application.&amp;nbsp; Click on App registrations in the Azure Ad view.&amp;nbsp; Next click the enpoints button.&amp;nbsp; Copy the the Federation MetaData Document.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/7aafe46889384682974fbb4cda7d312c.aspx&quot; alt=&quot;Image endpoints.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now go back to app registrations and choose the application you have setup.&amp;nbsp; Choose settings and the select properties.&amp;nbsp; Copy the App Id Uri.&amp;nbsp; Now we have the application setup and the connection values that will be needed in the setup of our WS-Fed.&lt;/p&gt;
&lt;h2&gt;Application Setup&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;h3&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Disable Role and Membership Providers&lt;/h3&gt;
&lt;p&gt;Disable the built-in Role and Membership providers in&amp;nbsp;&lt;em&gt;web.config&lt;strong&gt;&amp;nbsp;&lt;/strong&gt;&lt;/em&gt;because they do not support federated security:&lt;/p&gt;
&lt;pre class=&quot;language-xml&quot;&gt;&lt;code&gt;&amp;lt;authentication mode=&quot;None&quot; /&amp;gt;
    &amp;lt;membership&amp;gt;
      &amp;lt;providers&amp;gt;
        &amp;lt;clear/&amp;gt;
      &amp;lt;/providers&amp;gt;
    &amp;lt;/membership&amp;gt;
    &amp;lt;roleManager enabled=&quot;false&quot;&amp;gt;
      &amp;lt;providers&amp;gt;
        &amp;lt;clear/&amp;gt;
      &amp;lt;/providers&amp;gt;
    &amp;lt;/roleManager&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h3&gt;Configure Episerver to support federation&lt;/h3&gt;
&lt;p&gt;Enable claims on virtual roles by setting the&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;strong&gt;addClaims&lt;/strong&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;property. Also add the provider for security entities, which is used by the set access rights dialog and impersonating users. The&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;strong&gt;SynchronizingRolesSecurityEntityProvider&lt;/strong&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;configured in the following example is using the&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;strong&gt;SynchronizingUserService&lt;/strong&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;that is used in step 4.&amp;nbsp; Also if you are using the Administrators group you want to remove&amp;nbsp; or comment out the virtual role because it tried to use domain/username for authtenication.&lt;/p&gt;
&lt;br /&gt;
&lt;pre class=&quot;language-xml&quot;&gt;&lt;code&gt;&amp;lt;episerver.framework&amp;gt;
    &amp;lt;securityEntity&amp;gt;
      &amp;lt;providers&amp;gt;
        &amp;lt;add name=&quot;SynchronizingProvider&quot; type =&quot;EPiServer.Security.SynchronizingRolesSecurityEntityProvider, EPiServer&quot;/&amp;gt;
      &amp;lt;/providers&amp;gt;
    &amp;lt;/securityEntity&amp;gt;
    &amp;lt;virtualRoles addClaims=&quot;true&quot;&amp;gt;
       &amp;lt;!--&amp;lt;add name=&quot;Administrators&quot; type=&quot;EPiServer.Security.WindowsAdministratorsRole, EPiServer.Framework&quot; /&amp;gt;--&amp;gt;
    &amp;lt;/virtualRoles&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h3&gt;Install NuGet packages&lt;/h3&gt;
&lt;p&gt;Open Package Manager in Visual Studio and install the following packages:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;Install-Package Microsoft.Owin.Security.Cookies
Install-Package Microsoft.Owin.Security.WsFederation
Install-Package Microsoft.Owin.Host.SystemWeb
Update-Package Microsoft.IdentityModel.Protocol.Extensions -Safe&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h3&gt;Configure Identity and WSFederation&lt;/h3&gt;
&lt;p&gt;We configure the cms identity service and the ws-fed authtneication middleware.&amp;nbsp; We make the Ws-Fed the default so it will challenge us to login if we go /episerver for instance.&amp;nbsp; For normal site login the users will use the login and logout buttons of the web application.&amp;nbsp; We also set /logout to trigger a logout of the owin middleware.&amp;nbsp; Finally make sure there is no external cookie registered in the pipeline as it seems to mess up the ws-fed authenication and go into endless redirect loop.&amp;nbsp; Also note you might need to explictly set&amp;nbsp;TokenValidationParameters for the NameClaimType or RoleClaimType if they are not using the default ones setup for Ws-Fed.&amp;nbsp; You can put a preak point in the&amp;nbsp;SecurityTokenValidated callback to see what claims are being passed from Azure AD if you are not able to login correctly and update accordingly.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public class Startup
    {
        // For more information on configuring authentication,
        // please visit http://world.episerver.com/documentation/Items/Developers-Guide/Episerver-CMS/9/Security/episerver-aspnetidentity/

        private readonly IConnectionStringHandler _connectionStringHandler;

        public Startup() : this(ServiceLocator.Current.GetInstance&amp;lt;IConnectionStringHandler&amp;gt;())
        {
            // Parameterless constructor required by OWIN.
        }

        public Startup(IConnectionStringHandler connectionStringHandler)
        {
            _connectionStringHandler = connectionStringHandler;
        }

        public void Configuration(IAppBuilder app)
        {
            app.AddCmsAspNetIdentity&amp;lt;SiteUser&amp;gt;(new ApplicationOptions
            {
                ConnectionStringName = _connectionStringHandler.Commerce.Name
            });

            // Enable the application to use a cookie to store information for the signed in user
            // and to use a cookie to temporarily store information about a user logging in with a third party login provider.
            // Configure the sign in cookie.
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString(&quot;/Login&quot;),
                Provider = new CookieAuthenticationProvider
                {
                    // Enables the application to validate the security stamp when the user logs in.
                    // This is a security feature which is used when you change a password or add an external login to your account.  
                    OnValidateIdentity = SecurityStampValidator.OnValidateIdentity&amp;lt;ApplicationUserManager&amp;lt;SiteUser&amp;gt;, SiteUser&amp;gt;(
                        validateInterval: TimeSpan.FromMinutes(30),
                        regenerateIdentity: (manager, user) =&amp;gt; manager.GenerateUserIdentityAsync(user)),
                    OnApplyRedirect = (context =&amp;gt; context.Response.Redirect(context.RedirectUri)),
                    OnResponseSignOut = (context =&amp;gt; context.Response.Redirect(UrlResolver.Current.GetUrl(ContentReference.StartPage)))
                }
            });

            app.SetDefaultSignInAsAuthenticationType(WsFederationAuthenticationDefaults.AuthenticationType);
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType
            });
            //Enable federated authentication
            app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions()
            {
                //Trusted URL to federation server meta data
                MetadataAddress = &quot;Metadata document enpoint&quot;,
                //Value of Wtreal must *exactly* match what is configured in the federation server
                Wtrealm = &quot;App Id Uri&quot;,
         
                TokenValidationParameters = new TokenValidationParameters
                {
                   
                    NameClaimType = &quot;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress&quot;,
                },
                Notifications = new WsFederationAuthenticationNotifications()
                {
                    RedirectToIdentityProvider = (ctx) =&amp;gt;
                    {
                        //To avoid a redirect loop to the federation server send 403 when user is authenticated but does not have access
                        if (ctx.OwinContext.Response.StatusCode == 401 &amp;amp;&amp;amp; ctx.OwinContext.Authentication.User.Identity.IsAuthenticated)
                        {
                            ctx.OwinContext.Response.StatusCode = 403;
                            ctx.HandleResponse();
                        }
                        return Task.FromResult(0);
                    },
                    SecurityTokenValidated = (ctx) =&amp;gt;
                    {
                        //Ignore scheme/host name in redirect Uri to make sure a redirect to HTTPS does not redirect back to HTTP
                        var redirectUri = new Uri(ctx.AuthenticationTicket.Properties.RedirectUri, UriKind.RelativeOrAbsolute);
                        if (redirectUri.IsAbsoluteUri)
                        {
                            ctx.AuthenticationTicket.Properties.RedirectUri = redirectUri.PathAndQuery;
                        }

                        //if (ctx.AuthenticationTicket.Identity.Name)
                        //Sync user and the roles to EPiServer in the background
                        ServiceLocator.Current.GetInstance&amp;lt;ISynchronizingUserService&amp;gt;().SynchronizeAsync(ctx.AuthenticationTicket.Identity);
                        return Task.FromResult(0);
                    }
                }
            });
            

                        app.UseStageMarker(PipelineStage.Authenticate);
            app.Map(&quot;/Logout&quot;, map =&amp;gt;
            {
                map.Run(ctx =&amp;gt;
                {
                    ctx.Authentication.SignOut();
                    return Task.FromResult(0);
                });
            });
        }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Please refer to&amp;nbsp;&lt;a href=&quot;/link/31fe81f94c5b4d8ca67478b1ee44c6db.aspx&quot;&gt;federated-security&lt;/a&gt;&amp;nbsp;documentation for tips on troubleshooting issues.&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;</description>            <guid>https://world.optimizely.com/blogs/Mark-Hall/Dates/2017/10/mixed-mode-authentication/</guid>            <pubDate>Sun, 15 Oct 2017 18:50:05 GMT</pubDate>           <category>Blog post</category></item><item> <title>Anonymous Project Preview</title>            <link>https://world.optimizely.com/blogs/Mark-Hall/Dates/2016/11/anonymous-project-preview/</link>            <description>&lt;p&gt;A little while ago a colleague of mine wrote an excellent post on how to create a partial route to preview specific versions of content &lt;a href=&quot;/link/a6220bd76f404b9d9b47e8b86b480897.aspx&quot;&gt;here&lt;/a&gt;. &amp;nbsp;Some comments wanted to see this in the context of the projects as well as be able to allow anonymous users to be able to preview the page. &amp;nbsp;I created a small sample that addresses the issues in a different way then the partial route.&lt;/p&gt;
&lt;p&gt;First we will create an interface for dealing with getting project identifiers and content. &amp;nbsp;In the sample we are using the host header, but you could use cookies, session, or anything else you would like to enable. &amp;nbsp;Using the url seemed most easy and less places to change the code.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;using EPiServer.Core;
using System.Web;

namespace EPiServer.Reference.Commerce.Site.Infrastructure
{
    /// &amp;lt;summary&amp;gt;
    /// Helps identify project and version in the anonymous context
    /// &amp;lt;/summary&amp;gt;
    public interface IPreviewProjectIdentifier
    {
        /// &amp;lt;summary&amp;gt;
        /// Gets the project version of the content, otherwsie returns the published reference.
        /// &amp;lt;/summary&amp;gt;
        /// &amp;lt;param name=&quot;publishedContentReference&quot;&amp;gt;&amp;lt;/param&amp;gt;
        /// &amp;lt;param name=&quot;httpContextBase&quot;&amp;gt;&amp;lt;/param&amp;gt;
        /// &amp;lt;returns&amp;gt;A &amp;lt;see cref=&quot;ContentReference&quot;/&amp;gt;&amp;lt;/returns&amp;gt;
        ContentReference GetProjectVersion(ContentReference publishedContentReference, HttpContextBase httpContextBase);

        /// &amp;lt;summary&amp;gt;
        /// Gets the current project id based on custom logic.  If there is no current project returns 0/
        /// &amp;lt;/summary&amp;gt;
        /// &amp;lt;param name=&quot;httpContextBase&quot;&amp;gt;&amp;lt;/param&amp;gt;
        /// &amp;lt;returns&amp;gt;The primary key &amp;lt;see cref=&quot;int&quot;/&amp;gt; of the project&amp;lt;/returns&amp;gt;
        int GetProjectId(HttpContextBase httpContextBase);

    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we will create the implementation.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;using EPiServer.Core;
using EPiServer.DataAbstraction;
using EPiServer.ServiceLocation;
using EPiServer.Web.Hosting;
using System.Linq;
using System.Web;

namespace EPiServer.Reference.Commerce.Site.Infrastructure
{
    [ServiceConfiguration(Lifecycle = ServiceInstanceScope.Singleton, ServiceType = typeof(IPreviewProjectIdentifier))]
    public class DefaultPreviewProjectIdentifier : IPreviewProjectIdentifier
    {
        private readonly ProjectRepository _projectRepository;

        public DefaultPreviewProjectIdentifier(ProjectRepository projectRepository)
        {
            _projectRepository = projectRepository;
        }

        public ContentReference GetProjectVersion(ContentReference publishedReference, HttpContextBase httpContextBase)
        {
            var projectId = GetProjectId(httpContextBase);
            return projectId == 0 ? publishedReference : GetProjectReference(publishedReference, projectId);
        }

        public int GetProjectId(HttpContextBase httpContextBase)
        {
            var uri = GetUrl(httpContextBase);
            if (!uri.Host.StartsWith(&quot;project&quot;))
            {
                return 0;
            }

            var projectId = uri.Host.Substring(uri.Host.IndexOf(&quot;-&quot;) + 1, uri.Host.IndexOf(&quot;.&quot;) - (uri.Host.IndexOf(&quot;-&quot;) + 1));
            int id;
            return int.TryParse(projectId, out id) ? id : 0;
        }

        private ContentReference GetProjectReference(ContentReference publishedReference, int projectId)
        {
            var items = _projectRepository.ListItems(projectId);
            if (items == null)
            {
                return publishedReference;
            }

            var item = items.FirstOrDefault(x =&amp;gt; x.ContentLink.ToReferenceWithoutVersion() == publishedReference.ToReferenceWithoutVersion());
            return item == null ? publishedReference : items.FirstOrDefault(x =&amp;gt; x.ContentLink.ID == item.ContentLink.ID).ContentLink;
        }

        private static UrlBuilder GetUrl(HttpContextBase httpContext)
        {
            UrlBuilder hostUrl;
            if (httpContext != null &amp;amp;&amp;amp; httpContext.Request.Url != null)
            {
                hostUrl = new UrlBuilder(httpContext.Request.Url.AbsoluteUri)
                {
                    Path = VirtualPathUtility.AppendTrailingSlash(GenericHostingEnvironment.Instance.ApplicationVirtualPath)
                };
            }
            else
            {
                hostUrl = new UrlBuilder(VirtualPathUtility.AppendTrailingSlash(GenericHostingEnvironment.Instance.ApplicationVirtualPath));
            }
            return hostUrl;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next we need to create a custom IFilterProvider for MVC. &amp;nbsp;We need to do this because the base class ContentController&amp;lt;T&amp;gt; has the AuthorizeContentAttribute. &amp;nbsp;This is responsible checking the access rights of the content and will prevent an anonymous user from seeing the content.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;using EPiServer.ServiceLocation;
using EPiServer.Web.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;

namespace EPiServer.Reference.Commerce.Site.Infrastructure
{
    public class ProjectFilterProvider : IFilterProvider
    {
        private readonly FilterProviderCollection _filterProviders;
        private readonly Type _authorizeContent = typeof(AuthorizeContentAttribute);
        private readonly IPreviewProjectIdentifier _previewProjectIdentifier = ServiceLocator.Current.GetInstance&amp;lt;IPreviewProjectIdentifier&amp;gt;();

        public ProjectFilterProvider(IList&amp;lt;IFilterProvider&amp;gt; filters)
        {
            _filterProviders = new FilterProviderCollection(filters);
        }

        public IEnumerable&amp;lt;Filter&amp;gt; GetFilters(ControllerContext controllerContext,
            ActionDescriptor actionDescriptor)
        {
            var filters = _filterProviders.GetFilters(controllerContext, actionDescriptor);
            var projectId = _previewProjectIdentifier.GetProjectId(controllerContext.HttpContext);
            return projectId &amp;gt; 0 ? filters.Where(x =&amp;gt; x.Instance.GetType() != _authorizeContent) : filters;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally we will register the filter and add a event handler to&amp;nbsp;IContentRouteEvents.RoutedContent to update the content to the correct version in the context of the project.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;[ModuleDependency(typeof(ServiceApi.IntegrationInitialization), typeof(EPiServer.Commerce.Initialization.InitializationModule))]
    public class SiteInitialization : IConfigurableModule
    {
        public void Initialize(InitializationEngine context)
        { 
            var providers = FilterProviders.Providers.ToList();
            FilterProviders.Providers.Clear();
            FilterProviders.Providers.Add(new ProjectFilterProvider(providers));
            
            context.Locate.Advanced.GetInstance&amp;lt;IContentRouteEvents&amp;gt;().RoutedContent += ContentRoute_RoutedContent;
            
        }

        private void ContentRoute_RoutedContent(object sender, RoutingEventArgs e)
        {
            e.RoutingSegmentContext.RoutedContentLink = ServiceLocator.Current.GetInstance&amp;lt;IPreviewProjectIdentifier&amp;gt;().GetProjectVersion(e.RoutingSegmentContext.RoutedContentLink, HttpContext.Current.ContextBaseOrNull());
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we have a project preview for anonymous users where they can preview a live site in the context of a specific project.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.screencast.com/t/FMbCw188X&quot;&gt;http://www.screencast.com/t/FMbCw188X&lt;/a&gt;&lt;/p&gt;
</description>            <guid>https://world.optimizely.com/blogs/Mark-Hall/Dates/2016/11/anonymous-project-preview/</guid>            <pubDate>Mon, 07 Nov 2016 18:06:14 GMT</pubDate>           <category>Blog post</category></item><item> <title>New Order Events</title>            <link>https://world.optimizely.com/blogs/Mark-Hall/Dates/2015/5/new-order-events/</link>            <description>&lt;p&gt;Today we have released EPiServer.Commerce.Core 8.12.0 which includeds new Order Events. &amp;nbsp;These events were added to help support integrations. &amp;nbsp;One common use case might be sending information to your marketing automation when something is added to your basket. &amp;nbsp;These are synchronous events so it is important for the subscriber to finish quickly or start async task so execution can continue. &amp;nbsp;Please refer to &lt;a href=&quot;/link/3dcb80f07e9c44bd945f1c1f01d23461.aspx&quot;&gt;Documentation&lt;/a&gt;&amp;nbsp;for more information on how to use the events&lt;/p&gt;</description>            <guid>https://world.optimizely.com/blogs/Mark-Hall/Dates/2015/5/new-order-events/</guid>            <pubDate>Mon, 25 May 2015 15:57:43 GMT</pubDate>           <category>Blog post</category></item><item> <title>Important Updates to the ServiceApi</title>            <link>https://world.optimizely.com/blogs/Mark-Hall/Dates/2015/2/important-updates-to-the-serviceapi/</link>            <description>&lt;p&gt;We have released a new version of the EPiServer.ServiceApi and I wanted to highlight some important features over the last two releases. &amp;nbsp;As of version 1.2.0 the ServiceApi is now split out into two packages.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;EPiServer.ServiceApi - this is the base package that only has dependency to EPiServer.Cms.Core. &amp;nbsp;This package will allow you to import media as well as episerverdata files.&lt;/li&gt;
&lt;li&gt;EpiServer.ServiceApi.Commerce - this is the commerce package that has a dependency to EPiServer.Commerce.Core. &amp;nbsp;This package adds functionality to import catalog content and some restful operations for workng with catalog content.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As of version 1.3.0 all actions require permissions. &amp;nbsp;This means the user getting an auth token must have access to the permissions to use the functions. &amp;nbsp;The permissions are done with the new&amp;nbsp;&lt;a href=&quot;/link/618e394f45b34fe4bc451a58f51b580c.aspx?id=113299&quot;&gt;permissions to functions&lt;/a&gt;&amp;nbsp;introduced in EPiServer.Cms.Core 7.19.1. &amp;nbsp;The ServiceApi has two permissions read and write. &amp;nbsp;Any function that manipulates data will require write access while all other require read access. &amp;nbsp;By default when instaling version 1.3.0 the administrators role is granted read and write access.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;If you would like to use the ServiceApi read and write permissions in your own webapi controlllers, you can decorate your method&amp;nbsp;like below. &amp;nbsp;You also have the ability to create your own permissions and use with the AuthorizePermission attribute.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;[Route(&quot;myroute&quot;, Name = &quot;mymethod&quot;)]
[HttpGet]
[AcceptVerbs(&quot;GET&quot;)]
[ResponseType(typeof(IEnumerable&amp;lt;Models.MyModel&amp;gt;))]
[EPiServer.ServiceApi.Configuration.AuthorizePermission(EPiServer.ServiceApi.Configuration.Permissions.GroupName, EPiServer.ServiceApi.Configuration.Permissions.Read)]
public virtual IHttpActionResult MyMethod()
{
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }
        return Ok(ModelFactory.GetMyMethod());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ServiceAPi automatically registers controllers with attribute routing so this allows you to use the permissions in your own webapi controllers. &amp;nbsp;We needed to add a new AuthorizePermission for webapi controllers, the one introduced in EPiServer.Web.Mvc is only for mvc controllers.&lt;/p&gt;
&lt;p&gt;There were also some additional security updates which lead the removal of httpmodule&amp;nbsp;EPiServer.ServiceApi.IntegrationAuthorizationModule.&lt;/p&gt;</description>            <guid>https://world.optimizely.com/blogs/Mark-Hall/Dates/2015/2/important-updates-to-the-serviceapi/</guid>            <pubDate>Fri, 13 Feb 2015 17:30:13 GMT</pubDate>           <category>Blog post</category></item><item> <title>Commerce Expression Editor Released on Github</title>            <link>https://world.optimizely.com/blogs/Mark-Hall/Dates/2015/1/commerce-expression-editor-released-on-github/</link>            <description>&lt;p&gt;We have released the source code for the expression editor&amp;nbsp;&lt;a href=&quot;https://github.com/episerver/commerce-expression-editor&quot;&gt;here&lt;/a&gt;&amp;nbsp;on github. &amp;nbsp;The editor&amp;nbsp;is used to create expressions for custom promotions in EPiServer Commerce. &amp;nbsp;You can read the documenation&amp;nbsp;&lt;a href=&quot;/link/46d9b224b4e84289bdb70e787b5d54b3.aspx?id=111884&quot;&gt;here&lt;/a&gt;&amp;nbsp;to understand how to use.&lt;/p&gt;</description>            <guid>https://world.optimizely.com/blogs/Mark-Hall/Dates/2015/1/commerce-expression-editor-released-on-github/</guid>            <pubDate>Thu, 22 Jan 2015 10:31:49 GMT</pubDate>           <category>Blog post</category></item><item> <title>Syncing Active Directory Groups for use In EPiServer Commerce</title>            <link>http://thecommerceguy.wordpress.com/?p=3</link>            <description>I received a request the other day for the ability to use Active Directory Groups in Commerce Manager for permissions. &#160;While Commerce Manager uses granular permissions that will not allow this easily, there is a way to at least sync the groups and group memberships to be used in commerce manager. &#160;From there you can [&amp;#8230;]&lt;img alt=&quot;&quot; border=&quot;0&quot; src=&quot;http://pixel.wp.com/b.gif?host=thecommerceguy.wordpress.com&amp;#038;blog=73198446&amp;#038;post=3&amp;#038;subd=thecommerceguy&amp;#038;ref=&amp;#038;feed=1&quot; width=&quot;1&quot; height=&quot;1&quot; /&gt;</description>            <guid>http://thecommerceguy.wordpress.com/?p=3</guid>            <pubDate>Mon, 01 Sep 2014 12:59:58 GMT</pubDate>           <category>Blog post</category></item></channel>
</rss>