<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title type="text">Blog posts by Johan Kronberg</title><link href="http://world.optimizely.com" /><updated>2025-06-02T23:00:00.0000000Z</updated><id>https://world.optimizely.com/blogs/Johan-Kronberg/</id> <generator uri="http://world.optimizely.com" version="2.0">Optimizely World</generator> <entry><title>Migrating from EPiFocalPoint to ImagePointEditor in a CMS 12 upgrade</title><link href="https://krompaco.nu/2025/06/migrating-from-epifocalpoint-to-imagepointeditor/" /><id>Upgraded a CMS 11 solution that was using EPiFocalPoint and didn&#39;t want to lose any set focal point coordinates.</id><updated>2025-06-02T23:00:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Routing to a page in SaaS CMS</title><link href="https://krompaco.nu/2025/04/saas-cms-routing-and-graph-queries-setup/" /><id>More early findings from using a SaaS CMS instance; setting up Graph queries that works for both visitor pageviews and editor previews.</id><updated>2025-04-14T17:30:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Another console app for calling the Optimizely CMS REST API</title><link href="https://krompaco.nu/2025/03/another-cli-cms-rest-api-dotnet-client/" /><id>Introducing a Spectre.Console.Cli app for exploring an Optimizely SaaS CMS instance and to source code control definitions.</id><updated>2025-03-11T08:45:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Poking around in the new Visual Builder and the SaaS CMS</title><link href="https://krompaco.nu/2025/02/poking-around-in-visual-builder-and-saas-cms/" /><id>Early findings from using a SaaS CMS instance and the new Visual Builder grids.</id><updated>2025-02-07T08:45:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Change the IP HTTP Header used for geo-lookup in Application Insights</title><link href="https://krompaco.nu/2024/06/changing-which-ip-http-header-to-use-in-application-insights/" /><id>How to change the configuration to get a more exact geo-lookup logged.</id><updated>2024-06-10T07:00:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Run imgproxy container on Azure App Service with Front Door CDN</title><link href="https://krompaco.nu/2024/05/imgproxy-container-on-azure-app-service-with-front-door-cdn/" /><id>A simple way to host a imgproxy Docker container and use it to get AVIF and WEBP images.</id><updated>2024-05-05T07:00:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Crawling Optimizely World using Chrome and C#</title><link href="https://krompaco.nu/2024/01/crawling-optimizely-world-using-chrome-and-csharp/" /><id>Crawling the current Optimizely MVP list for blog URLs.</id><updated>2024-01-03T13:37:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Serving static site content from inside a MediaData ZIP-file</title><link href="https://krompaco.nu/2023/11/serving-content-from-inside-a-media-zip-file/" /><id>An editor needs to regularly put static site output in their CMS 12 media system. Here&#39;s one idea I explored.</id><updated>2023-11-10T19:45:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Speaking at the April Optimizely Stockholm Developer Meetup</title><link href="https://krompaco.nu/2023/04/optimizely-stockholm-developer-meetup-april-2023/" /><id>Taking part in waking up the Optimizely Stockholm Developer Meetup group again.</id><updated>2023-04-20T08:30:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Migrating from Providers to CMS 12 ASP.NET Identity with cookie tweaks</title><link href="https://krompaco.nu/2023/01/from-providers-to-aspnetidentity-notes/" /><id>Notes on migrating a multi-site from Membership and Role Providers to ASP.NET Identity and changing cookie options dynamically.</id><updated>2023-01-30T06:30:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>A project structure for Razor Pages and View Components with CMS 12</title><link href="https://krompaco.nu/2022/11/file-and-folder-structure-with-razor-pages-in-cms-12/" /><id>Exploring some options and find a streamlined setup that works with VS tooling.</id><updated>2022-11-02T09:00:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Trying out imgproxy together with CMS 12 to resize images</title><link href="https://krompaco.nu/2022/10/trying-out-imgproxy-with-cms-12/" /><id>Optimize images on the fly from and behind an Optimizely CMS app using an imgproxy Docker container.</id><updated>2022-10-20T07:59:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Using SQL to identify embedded resources for your CMS site&#39;s CSP</title><link href="https://krompaco.nu/2022/10/identify-external-embedded-resources-in-cms-content/" /><id>SQL scripts that helps to get an overview of which external domains names you might have embedded in CMS content.</id><updated>2022-10-19T15:59:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Hosting this blog on Azure and switching to a static site setup</title><link href="https://krompaco.nu/2022/08/hosting-this-blog-on-azure-and-switching-setup/" /><id>Experiences from hosting an Optimizely CMS site on Azure and moving to a generated static site on Netlify.</id><updated>2022-08-15T12:00:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>SendGridForEpi fixed to work with CMS 12</title><link href="http://krompaco.nu/2022/06/sendgridforepi-fixed-to-work-with-cms-12/" /><id>&lt;p&gt;The versions published on Optimizely&#39;s NuGet now has a version 2.0.2 that is compatible with .NET 6.0 and CMS 12.4.2 or newer.&lt;/p&gt;&lt;p&gt;Once upon a time I created this package that handles SendGrid messages asynchronously. The typical use case is probably when you have a subscription feature and want to send e-mails to subscribers from an Optimizely Content Cloud app through SendGrid and use Handlebars with their template designer.&lt;/p&gt;
&lt;p&gt;It has a sort of local queue and scheduled job that processes it.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nuget.optimizely.com/package/?id=Krompaco.SendGridForEpi.SqlServer&quot;&gt;Krompaco.SendGridForEpi.SqlServer on the feed&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/krompaco/sendgrid-for-epi&quot;&gt;Package source code on Github&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some quick notes from upgrading:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Given the amount of code it seemed easiest to not upgrade anything and instead create empty .NET 6.0 class libraries and then move code over.&lt;/li&gt;
&lt;li&gt;Removed the .nuspec files and moved over to package settings within the .csproj file.&lt;/li&gt;
&lt;li&gt;Chose to take dependencies on &lt;a href=&quot;https://nuget.optimizely.com/package/?id=EPiServer.CMS.AspNetCore&quot;&gt;EPiServer.CMS.AspNetCore&lt;/a&gt; (&amp;ge; 12.4.2 &amp;amp;&amp;amp; &amp;lt; 13.0.0) and &lt;a href=&quot;https://nuget.optimizely.com/package/?id=EPiServer.Framework.AspNetCore&quot;&gt;EPiServer.Framework.AspNetCore&lt;/a&gt; (&amp;ge; 12.4.2 &amp;amp;&amp;amp; &amp;lt; 13.0.0) instead of the wrapping EPiServer.CMS package.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you have used the package with CMS 11 there are no changes in the database tables. The only thing you need to set up is the new way of configuring the connection string and the SendGrid API Key when using DI.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;Startup.cs&lt;/code&gt; and inside &lt;code&gt;ConfigureServices()&lt;/code&gt;, add these lines. Replace the connection string with the SQL Server database that you want to use.&lt;/p&gt;
&lt;p&gt;In most cases you would have these secret values in environment variables and fetched through &lt;code&gt;IConfiguration&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;// Set the SendGrid API Key
services.AddSendGrid(options =&amp;gt; { options.ApiKey = &quot;your-key-that-usually-starts-with-SG.&quot;; });

// Add a connection string for some SQL Server db
var mailService = new SqlServerMailService(&quot;a-sql-server-connection-string&quot;);

// Check that the tables are in database
mailService.CreateTablesIfNeeded();

// Register it
services.AddSingleton&amp;lt;IMailService&amp;gt;(mailService);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check the &lt;a href=&quot;https://github.com/krompaco/sendgrid-for-epi&quot;&gt;Github README&lt;/a&gt; on how to construct a message and work the personalization variables.&lt;/p&gt;</id><updated>2022-06-05T16:23:55.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Webperf ratings for blogs of Optimizely MVPs</title><link href="http://krompaco.nu/2022/05/webperf-ratings-for-blogs-of-optimizely-mvps/" /><id>&lt;p&gt;I&#39;ve mashed up my open source project Record Collector with the great Webperf Core test routine.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/Webperf-se/webperf_core/&quot;&gt;Webperf Core&lt;/a&gt; is a Python based project that has put together various test tools and services. The tests can run in GitHub Actions and the friendly people building it, who also run the popular &lt;a href=&quot;https://webperf.se/&quot;&gt;Webperf.se site&lt;/a&gt;, have put together a 1 to 5 rating calculation and common report format making it easy to benchmark sites.&lt;/p&gt;
&lt;p&gt;I previously created another leaderboard project for Webperf Core leaning on their SQLite database and that used Blazor Server but I&#39;ve had the idea for a while to instead generate the leaderboard as static pages...&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://record-collector.net/en/&quot;&gt;The static site generator I know best&lt;/a&gt; is my own and the recent addition of GitHub Action regression tests in Webperf Core helped me out a lot.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/krompaco/webperf-omvp-rc-leaderboard/blob/main/.github/workflows/run-tests.yml&quot;&gt;The YAML steps&lt;/a&gt; involved should help you see how it&#39;s set up.&lt;/p&gt;
&lt;p&gt;Take a look at &lt;a href=&quot;https://omvp-webperf.krompaco.nu/&quot;&gt;the generated OMVP blog leaderboard site&lt;/a&gt; and put some stars on our GitHub repositories if you start using it for your sites!&lt;/p&gt;</id><updated>2022-05-08T21:24:49.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Please do this with your Swagger-enabled Web APIs</title><link href="http://krompaco.nu/2021/02/please-do-this-with-your-swagger-enabled-web-apis/" /><id>&lt;p&gt;Quite often I encounter APIs that has a Swagger UI but lacking much of the rest (hehe) of Swagger tooling support.&lt;/p&gt;&lt;p&gt;This is a shame when you know what a quality OpenAPI specification with attention to detail can offer. My checklist contains the following steps.&lt;/p&gt;
&lt;h2&gt;Generate code in languages your consumers are using&lt;/h2&gt;
&lt;p&gt;Download and install &lt;a href=&quot;https://github.com/RicoSuter/NSwag/wiki/NSwagStudio&quot;&gt;NSwagStudio&lt;/a&gt; try generating API client code. If this works and you get good affordance you can take comfort in knowing you are somewhat following API conventions and good practices. Here it is also easy to spot junk data in your specification and route mistakes resulting in silly names after generating.&lt;/p&gt;
&lt;p&gt;When you have the settings you prefer you can use the included CLI and push a PowerShell script similar to this for re-generating C#.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;add-type @&quot;
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
    public class TrustAllCertsPolicy :ICertificatePolicy {
        public bool CheckValidationResult(
            ServicePoint srvPoint, X509Certificate certificate,
            WebRequest request, int certificateProblem) {
            return true;
        }
    }
&quot;@
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
$url = &quot;https://localhost:5001/swagger/v1/swagger.json&quot;
$output = &quot;$PSScriptRoot\swagger.json&quot;
$start_time = Get-Date

Invoke-WebRequest -Uri $url -OutFile $output
Write-Output &quot;Time taken:$((Get-Date).Subtract($start_time).Seconds) second(s)&quot;

nswag swagger2csclient /input:&quot;swagger.json&quot; /generateUpdateJsonSerializerSettingsMethod:true /generateClientClasses:true /generateClientInterfaces:true /injectHttpClient:&quot;true&quot; /disposeHttpClient:&quot;false&quot; /generateExceptionClasses:true /exceptionClass:&quot;ApiException&quot; /wrapDtoExceptions:true /useHttpClientCreationMethod:false /httpClientType:&quot;System.Net.Http.HttpClient&quot; /useHttpRequestMessageCreationMethod:false /useBaseUrl:false /generateBaseUrlProperty:false /generateSyncMethods:false /exposeJsonSerializerSettings:true /clientClassAccessModifier:&quot;public&quot; /typeAccessModifier:&quot;public&quot; /generateContractsOutput:false /parameterDateTimeFormat:&quot;s&quot; /generateUpdateJsonSerializerSettingsMethod:false /serializeTypeInformation:false /queryNullValue:&quot;&quot; /className:&quot;{controller}Client&quot; /operationGenerationMode:&quot;MultipleClientsFromPathSegments&quot; /generateOptionalParameters:false /generateJsonMethods:false /enforceFlagEnums:false /parameterArrayType:&quot;System.Collections.Generic.IEnumerable&quot; /parameterDictionaryType:&quot;System.Collections.Generic.IDictionary&quot; /responseArrayType:&quot;System.Collections.Generic.ICollection&quot; /responseDictionaryType:&quot;System.Collections.Generic.IDictionary&quot; /wrapResponses:false /generateResponseClasses:true /responseClass:&quot;SwaggerResponse&quot; /namespace:&quot;WebApp.Services.Generated&quot; /requiredPropertiesMustBeDefined:true /dateType:&quot;System.DateTimeOffset&quot; /anyType:&quot;object&quot; /dateTimeType:&quot;System.DateTimeOffset&quot; /timeType:&quot;System.TimeSpan&quot; /timeSpanType:&quot;System.TimeSpan&quot; /arrayType:&quot;System.Collections.Generic.ICollection&quot; /arrayInstanceType:&quot;System.Collections.ObjectModel.Collection&quot; /dictionaryType:&quot;System.Collections.Generic.IDictionary&quot; /dictionaryInstanceType:&quot;System.Collections.Generic.Dictionary&quot; /arrayBaseType:&quot;System.Collections.ObjectModel.Collection&quot; /dictionaryBaseType:&quot;System.Collections.Generic.Dictionary&quot; /classStyle:&quot;Poco&quot; /generateDefaultValues:true /generateDataAnnotations:true /handleReferences:false /generateImmutableArrayProperties:false /generateImmutableDictionaryProperties:false /inlineNamedArrays:false /inlineNamedDictionaries:false /inlineNamedTuples:true /inlineNamedAny:false /generateDtoTypes:true /generateOptionalPropertiesAsNullable:false /output:&quot;src\WebApp\Services\Generated\ApiClient.cs&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First part is to accept any dodgy self-signed certificate you might run on your local development site. Then you can see that it makes an HTTP request and writes the swagger.json to disk to finally run the the NSwag console application with all the options specified explicitly.&lt;/p&gt;
&lt;p&gt;For the recommended setup part below two key switches are /generateClientInterfaces:true and /injectHttpClient:true. Of course provide this script as part of your documentation.&lt;/p&gt;
&lt;h2&gt;Use the generated code in your integration tests&lt;/h2&gt;
&lt;p&gt;Lookup on how to use the WebApplicationFactory from the Microsoft.AspNetCore.Mvc.Testing package and use an HttpClient from there with the generated &lt;em&gt;ApiClient.cs&lt;/em&gt; in a (for example XUnit) Test Project. This means you will have all your (hopefully well documented) API models and async methods for each endpoint available to use when writing test methods and you will use the API in the same manor as the smart/experienced ones of your consumers. The test runner will start the web app internally and run impressively fast.&lt;/p&gt;
&lt;h2&gt;Validate and recommend this setup with ASP.NET Core/5.0&lt;/h2&gt;
&lt;p&gt;The generated code has put everything in place for you to do something like the following in your Startup &lt;em&gt;ConfigureServices()&lt;/em&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;services.AddHttpClient&amp;lt;IApiClient, ApiClient&amp;gt;(c =&amp;gt;
    {
        c.BaseAddress = new Uri(apiBaseUrlFromSomeSettingEtc);
        c.DefaultRequestHeaders.Accept.Clear();
        c.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(&quot;application/json&quot;));
    })
    .ConfigurePrimaryHttpMessageHandler(messageHandler =&amp;gt;
    {
        var handler = new HttpClientHandler();
    
        if (handler.SupportsAutomaticDecompression)
        {
            handler.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
        }
    
        return handler;
    });&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To be extra cool I&#39;m then using Blazor to validate and in a Razor Component I can now just do something like this.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@inject IApiClient ApiClient
..
@code {
    private async Task ToggleHistory()
    {
        this.Companies = await this.ApiClient.CompaniesAsync(segmentId);    
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hopefully Content and Commerce Cloud sites will be able to run on 5.0 in not too long making this the favored setup for calling from those as well. Meanwhile &lt;a href=&quot;/link/db6e74f37794414888c4e6d6a28c791c.aspx&quot;&gt;Daniel Ovaska&#39;s HttpClient blog post&lt;/a&gt; should be followed for HttpClient handling but in all other ways the &lt;em&gt;ApiClient.cs&lt;/em&gt; is fully compatible with &lt;em&gt;Newtonsoft.Json&lt;/em&gt; on old framework 4.8 for example.&lt;/p&gt;
&lt;p&gt;I&#39;m not on top of the whole background but there are &lt;a href=&quot;https://github.com/RicoSuter/NJsonSchema/issues/1014&quot;&gt;things missing for NSwag to be able to generate clients that use System.Text.Json&lt;/a&gt; for deserialization.&lt;/p&gt;
&lt;h2&gt;Provide documentation for authorization header values handling&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;This part is only if your API is protected by some OIDC flow.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Test and describe how to acquire tokens and a recommended cache approach with retry mechanism for when tokens have expired or don&#39;t work for other reasons.&lt;/p&gt;
&lt;p&gt;Make sure that you are re-using the HttpClient for the OIDC token issuer with a similar &lt;em&gt;services.AddHttpClient()&lt;/em&gt; approach as the API client use above.&lt;/p&gt;
&lt;p&gt;Setup your Swagger UI so that you can use the Authorize button and at least paste an access token there when wanting to execute actions in the UI.&lt;/p&gt;
&lt;p&gt;Thanks!&lt;/p&gt;</id><updated>2021-02-16T23:09:13.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Troubleshooting a large increase in Epi SQL Server calls</title><link href="http://krompaco.nu/2021/01/troubleshooting-a-large-increase-in-episerver-sql-server-calls/" /><id>&lt;p&gt;Recently spent some time investigating massive database dependency counts on an Epi CMS site that was using Geta Tags.&lt;/p&gt;&lt;p&gt;The &lt;a href=&quot;https://github.com/Geta/Tags/blob/master/src/TagsScheduledJob.cs&quot;&gt;Geta Tags maintenance job&lt;/a&gt; was running fine but possibly due to a bunch of recently removed content types was not able to remove now missing ContentGUIDs while cleaning up.&lt;/p&gt;
&lt;p&gt;Looks like this resulted in uncached &lt;code&gt;netFindContentCoreDataByContentGuid&lt;/code&gt; stored procedure calls with non-existing ContentGUIDs when &lt;code&gt;ITagEngine.GetContentReferencesByTags()&lt;/code&gt; was used.&lt;/p&gt;
&lt;p&gt;I put together an analysis query that returned rows showing that content GUIDs in Geta Tags DDS store had no content relation.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT t.*, c.* FROM (
	SELECT DISTINCT GuidValue FROM tblBigTableReference WHERE [Index] &amp;gt; -1 AND GuidValue IS NOT NULL AND PropertyName = &#39;PermanentLinks&#39;
	AND pkId IN (
		SELECT StoreId FROM [VW_Geta.Tags.Models.Tag]
	)
) AS t
LEFT JOIN tblContent c ON t.GuidValue = c.ContentGUID
WHERE c.ContentGuid IS NULL&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To get sorted (hopefully) I made it into this DELETE-statement.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DELETE FROM tblBigTableReference WHERE [Index] &amp;gt; -1 AND GuidValue IS NOT NULL AND PropertyName = &#39;PermanentLinks&#39; AND pkId IN
(
	SELECT StoreId FROM [VW_Geta.Tags.Models.Tag]
)
AND GuidValue IN
(
	SELECT GuidValue FROM
	(
		SELECT DISTINCT GuidValue FROM tblBigTableReference WHERE [Index] &amp;gt; -1 AND GuidValue IS NOT NULL AND PropertyName = &#39;PermanentLinks&#39; AND pkId IN
		(
			SELECT StoreId FROM [VW_Geta.Tags.Models.Tag]
		)
	) AS t
	LEFT JOIN tblContent c ON t.GuidValue = c.ContentGUID
	WHERE c.ContentGuid IS NULL
)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Things seem to be back in shape now.&lt;/p&gt;</id><updated>2021-01-27T04:42:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Notes on my part of the EWCW Cloud talk</title><link href="http://krompaco.nu/2020/10/notes-on-my-part-of-the-ewcw-cloud-talk/" /><id>&lt;p&gt;Maybe you listened in to the Cloud talk on Monday of the &lt;a href=&quot;/link/4c1af98a88fb48cc9997d18422ab954d.aspx&quot;&gt;Episerver World Community Week&lt;/a&gt; and wondered what I was talking about?&lt;/p&gt;&lt;p&gt;&lt;em&gt;Note that the page on World now has a video recording of the session.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;CD with the EpiCloud PowerShell Module&lt;/h2&gt;
&lt;p&gt;The&amp;nbsp;&lt;a href=&quot;/link/eb85a23d0c1843aaa36f121668e1059e.aspx&quot;&gt;DXP Deployment API documentation section on World&lt;/a&gt; is easy to follow so be sure to read up and start using it.&lt;/p&gt;
&lt;h2&gt;A KQL example&lt;/h2&gt;
&lt;p&gt;Run in Application Insights Logs section.&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;// Total requests to app per day&lt;br /&gt;let start=startofday(datetime(&quot;2020-09-01&quot;));&lt;br /&gt;let end=endofday(datetime(&quot;2020-09-23&quot;));&lt;br /&gt;let timeGrain=1d;&lt;br /&gt;requests&lt;br /&gt;| where timestamp &amp;gt; start and timestamp &amp;lt; end and client_Type != &quot;Browser&quot;&lt;br /&gt;| summarize count_=sum(itemCount) by bin(timestamp, timeGrain)&lt;br /&gt;| render timechart&lt;/pre&gt;
&lt;h2&gt;Trace with custom properties&lt;/h2&gt;
&lt;p&gt;This is a nice blog post on &lt;a href=&quot;https://camerondwyer.com/2020/05/26/how-to-use-application-insights-custom-properties-in-azure-monitor-log-kusto-queries/&quot;&gt;how to do custom tracing with Application Insights&lt;/a&gt;. Doing custom events is very similar.&lt;/p&gt;
&lt;h2&gt;W3C Trace Context for Telemetry Correlation&lt;/h2&gt;
&lt;p&gt;W3C Trace Context offers correlation between layers and applications with Application Insights and also between other APM services such as Elastic APM or New Relic.&lt;/p&gt;
&lt;p&gt;The specification: &lt;a href=&quot;https://www.w3.org/TR/trace-context/&quot;&gt;w3.org/TR/trace-context/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Application Insights is transitioning to W3C Trace Context and this will likely be more straight-forward later but for a typical Epi DXP app currently:&lt;/p&gt;
&lt;p&gt;Install &lt;em&gt;System.Diagnostics.DiagnosticSource&lt;/em&gt; 4.7 or later, and &lt;em&gt;Microsoft.ApplicationInsights&lt;/em&gt; 2.8 or later.&lt;/p&gt;
&lt;p&gt;Then set on app startup:&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;Activity.ForceDefaultIdFormat = true;&lt;br /&gt;Activity.DefaultIdFormat = ActivityIdFormat.W3C;&lt;/pre&gt;
&lt;p&gt;You can then check Activity.Current to see the ID of the ongoing request that your telemetry gets automatically.&lt;/p&gt;
&lt;p&gt;If you don&#39;t see IDs in the remote app you might need to add the headers to the outbound call yourself, check this &lt;a href=&quot;https://github.com/dotnet/runtime/blob/4f9ae42d861fcb4be2fcd5d3d55d5f227d30e723/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs#L254&quot;&gt;source code of the System.Net.Http.DiagnosticsHandler&lt;/a&gt; to see how it could be done.&lt;/p&gt;</id><updated>2020-10-22T08:13:09.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Putting custom data in Azure Table Storage from Episerver</title><link href="http://krompaco.nu/2020/09/putting-custom-data-in-azure-table-storage-from-episerver/" /><id>&lt;p&gt;It&#39;s completely normal to feel not so good when thinking about Entity Framework migrations or Episerver&#39;s Dynamic Data Store.&lt;/p&gt;&lt;p&gt;If your Episerver site is running on Azure and is using Blob Storage, either self-serviced or on the DXP, using Azure Table Storage instead can be a good alternative that&#39;s already available to use.&lt;/p&gt;
&lt;p&gt;To illustrate; here is my simple string service example.&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;public interface ISimpleStorageService
{
  string Get(string partitionKey, string rowKey);

  void Insert(string partitionKey, string rowKey, string value);
}&lt;/pre&gt;
&lt;p&gt;When implementing it with Azure Table Storage all the packages are already installed in your solution, since they are required by the EPiServer.Azure package, and the connection string name is fixed so this class should work out of the box as a simple key-value store.&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;using System;
using System.Configuration;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Table;

public class AzureSimpleStorageService : ISimpleStorageService
{
  public string Get(string partitionKey, string rowKey)
  {
    var entity = GetEntity(partitionKey, rowKey);
    return entity?.Value;
  }

  public void Insert(string partitionKey, string rowKey, string value)
  {
    if (string.IsNullOrWhiteSpace(partitionKey))
    {
      throw new Exception(&quot;PartitionKey must have value&quot;);
    }

    if (string.IsNullOrWhiteSpace(rowKey))
    {
      throw new Exception(&quot;RowKey must have value&quot;);
    }

    var table = GetSimpleStorageTable();
    var updateEntity = GetEntity(partitionKey, rowKey);

    if (updateEntity != null)
    {
      updateEntity.Value = value;
      var updateOperation = TableOperation.Replace(updateEntity);
      table.Execute(updateOperation);
    }
    else
    {
      var storageItem = new SimpleStorageItem { PartitionKey = partitionKey, RowKey = rowKey, Value = value };
      table.Execute(TableOperation.Insert(storageItem));
    }
  }

  private static CloudStorageAccount GetStorageAccount()
  {
    return CloudStorageAccount.Parse(ConfigurationManager.ConnectionStrings[&quot;EPiServerAzureBlobs&quot;].ConnectionString);
  }

  private static CloudTable GetSimpleStorageTable()
  {
    var storageAccount = GetStorageAccount();
    var tableClient = storageAccount.CreateCloudTableClient();

    // Create the table if it doesn&#39;t exist
    var table = tableClient.GetTableReference(&quot;SimpleStorageItems&quot;);
    table.CreateIfNotExists();

    return table;
  }

  private static SimpleStorageItem GetEntity(string partitionKey, string rowKey)
  {
    try
    {
      var table = GetSimpleStorageTable();
      var retrieveOperation = TableOperation.Retrieve(partitionKey, rowKey);
      var retrievedResult = table.Execute(retrieveOperation);
      var entity = (SimpleStorageItem)retrievedResult.Result;
      return entity;
    }
    catch (System.Data.Services.Client.DataServiceQueryException ex)
    {
      if (ex.Response.StatusCode == (int)System.Net.HttpStatusCode.NotFound)
      {
        return null;
      }

      throw;
    }
  }

  private class SimpleStorageItem : TableEntity
  {
    // The .NET Client supports more property types as well
    // but not very complex types...
    public string Value { get; set; }
  }
}&lt;/pre&gt;
&lt;p&gt;To see what&#39;s going on and which tables exist, download &lt;a href=&quot;https://azure.microsoft.com/en-us/features/storage-explorer/&quot;&gt;Azure Storage Explorer&lt;/a&gt; and then use the connection string to connect, in my experience that usually works better than listing through your Azure AD authentication. This accesss is only there for Integration environment, of course code works fine for Preproduction and Production but you need views or other own things to look at the data there.&lt;/p&gt;
&lt;p&gt;Note that even with the new Deployment API available I think you still need to create a support ticket if you want to copy table data between environments.&lt;/p&gt;
&lt;p&gt;This has been a slightly secretive option but it has been around since the start of the DXC Service and it has served us well for data we didn&#39;t want taking up space in our SQL database.&lt;/p&gt;</id><updated>2020-09-09T07:24:31.0000000Z</updated><summary type="html">Blog post</summary></entry></feed>