<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="/assets/xslt/atom.xslt" ?>
<?xml-stylesheet type="text/css" href="/assets/css/atom.css" ?>
<feed xmlns="http://www.w3.org/2005/Atom">
	<id>https://blog.rsuter.com/</id>
	<title>Rico Suter's blog</title>
	<updated>2026-04-28T22:47:59+00:00</updated>

	<subtitle></subtitle>

	
		
		<author>
			
				<name>Rico Suter</name>
			
			
			
				<uri>https://rsuter.com</uri>
			
		</author>
	

	<link href="https://blog.rsuter.com/atom.xml" rel="self" type="application/rss+xml" />
	<link href="https://blog.rsuter.com/" rel="alternate" type="text/html" />

	<generator uri="http://jekyllrb.com" version="3.8.5">Jekyll</generator>

	
		<entry>
			<id>https://blog.rsuter.com/introducing-namotion-interceptor/</id>
			<title>Introducing Namotion.Interceptor for .NET</title>
			<link href="https://blog.rsuter.com/introducing-namotion-interceptor/" rel="alternate" type="text/html" title="Introducing Namotion.Interceptor for .NET" />
			<updated>2026-02-19T00:00:00+00:00</updated>

			
				
				<author>
					
						<name>Rico Suter</name>
					
					
					
						<uri>https://rsuter.com</uri>
					
				</author>
			
			<summary></summary>
			<content type="html" xml:base="https://blog.rsuter.com/introducing-namotion-interceptor/">&lt;p&gt;&lt;a href=&quot;https://github.com/RicoSuter/Namotion.Interceptor/&quot;&gt;&lt;strong&gt;Namotion.Interceptor&lt;/strong&gt;&lt;/a&gt; is a new .NET library for reactive object models. It is under active development and APIs may still change between versions.&lt;/p&gt;

&lt;p&gt;It is designed for any .NET application where a domain model needs to be reactive: industrial automation, IoT, real-time UIs, digital twins, MVVM desktop apps, or anything where local property changes should propagate automatically to derived state, observers, or external systems. The design hinges on &lt;strong&gt;C# 13 partial properties&lt;/strong&gt;, a recent language feature that lets a source generator emit real getters and setters for your properties without proxies, weaving, or base-class requirements. From the start, the project also optimizes hard for &lt;strong&gt;low allocations and high throughput&lt;/strong&gt;, since interceptor libraries tend to sit on the hot path of high-frequency systems.&lt;/p&gt;

&lt;p&gt;In practice, mark a class with &lt;code class=&quot;highlighter-rouge&quot;&gt;[InterceptorSubject]&lt;/code&gt;, declare your properties as &lt;code class=&quot;highlighter-rouge&quot;&gt;partial&lt;/code&gt;, and the source generator handles the rest. You get automatic change events, derived properties, lifecycle callbacks, and &lt;code class=&quot;highlighter-rouge&quot;&gt;INotifyPropertyChanged&lt;/code&gt; support, all generated at compile-time.&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;[InterceptorSubject]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;partial&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Person&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;partial&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FirstName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;partial&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LastName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Derived&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FullName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FirstName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LastName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;InterceptorSubjectContext&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WithFullPropertyTracking&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;person&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;GetPropertyChangeObservable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Subscribe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WriteLine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;$&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Property&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GetNewValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&amp;gt;()}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FirstName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;John&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// FirstName: John&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// FullName: John&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The library is structured as six packages, each building on the previous so you can adopt only what you need:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Core&lt;/strong&gt; provides &lt;code class=&quot;highlighter-rouge&quot;&gt;[InterceptorSubject]&lt;/code&gt;, the source generator, and the read/write interceptor pipeline that everything else plugs into.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Tracking&lt;/strong&gt; adds observable and queue-based change streams, automatic recalculation of &lt;code class=&quot;highlighter-rouge&quot;&gt;[Derived]&lt;/code&gt; properties, lifecycle attach/detach events, and atomic transactions.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Validation&lt;/strong&gt; rejects invalid writes using standard &lt;code class=&quot;highlighter-rouge&quot;&gt;[Required]&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;[MaxLength]&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;[Range]&lt;/code&gt; attributes or custom validators.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Registry&lt;/strong&gt; keeps a runtime catalog of every subject and property, enables strongly-typed metadata via property attributes, dynamic property creation, and stable subject IDs.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Connectors&lt;/strong&gt; synchronize the object graph with external systems. Bidirectional MQTT, OPC UA, and WebSocket integrations are included, along with infrastructure for custom connectors.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Integrations&lt;/strong&gt; expose subjects through .NET host frameworks: ASP.NET Core (REST + JSON), Blazor (auto re-rendering with &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;TrackingScope&amp;gt;&lt;/code&gt;), HotChocolate (GraphQL subscriptions), and MCP (so AI agents can browse and manipulate the object graph).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, wiring a subject to an MQTT broker is roughly two steps:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;[InterceptorSubject]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;partial&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Sensor&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;mqtt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;temperature&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;partial&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;decimal&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Temperature&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AddMqttSubjectClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Sensor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;brokerHost&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;mqtt.example.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;sourceName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;mqtt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;brokerPort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1883&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;topicPrefix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;sensors/room1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Local writes to &lt;code class=&quot;highlighter-rouge&quot;&gt;sensor.Temperature&lt;/code&gt; are now published to the broker, and inbound MQTT messages update the property and trigger change notifications. The same pattern works for OPC UA, WebSocket, or your own custom connector.&lt;/p&gt;

&lt;p&gt;A typical use case is an IoT or industrial scenario where the same C# domain model is the source of truth for a Blazor dashboard, the contract for an OPC UA client talking to a PLC, and the wire format for an MQTT bridge, without writing protocol glue code by hand.&lt;/p&gt;

&lt;p&gt;It also powers &lt;strong&gt;HomeBlaze v2&lt;/strong&gt;, a complete rewrite of my home automation platform for .NET and Blazor, now built on Namotion.Interceptor for all device modeling and synchronization. The v2 codebase currently lives alongside the library in the same repository while the two mature together, and will eventually be split out into its own repo (replacing the &lt;a href=&quot;https://github.com/RicoSuter/HomeBlaze&quot;&gt;HomeBlaze v1&lt;/a&gt; codebase there). HomeBlaze is also the primary driver behind many of the library’s design decisions.&lt;/p&gt;

&lt;p&gt;Feedback, issues, and contributions are very welcome. Source code and documentation can be &lt;a href=&quot;https://github.com/RicoSuter/Namotion.Interceptor/&quot;&gt;found on GitHub&lt;/a&gt;&lt;/p&gt;
</content>

			
				<category term="Software Engineering" />
			
			
				<category term=".NET" />
			
				<category term="C#" />
			
				<category term="Source Generators" />
			
				<category term="Reactive" />
			
				<category term="MQTT" />
			
				<category term="OPC UA" />
			
				<category term="WebSocket" />
			
				<category term="IoT" />
			
				<category term="Open Source" />
			

			<published>2026-02-19T00:00:00+00:00</published>
		</entry>
	
		<entry>
			<id>https://blog.rsuter.com/nginx-in-azure-web-app-for-containers/</id>
			<title>Hosting an Nginx Reverse Proxy in an Azure Web App for Containers (App Service)</title>
			<link href="https://blog.rsuter.com/nginx-in-azure-web-app-for-containers/" rel="alternate" type="text/html" title="Hosting an Nginx Reverse Proxy in an Azure Web App for Containers (App Service)" />
			<updated>2022-12-10T00:00:00+00:00</updated>

			
				
				<author>
					
						<name>Rico Suter</name>
					
					
					
						<uri>https://rsuter.com</uri>
					
				</author>
			
			<summary></summary>
			<content type="html" xml:base="https://blog.rsuter.com/nginx-in-azure-web-app-for-containers/">&lt;p&gt;The following steps describe how to set up an Azure Web App for Container to act as a Nginx reverse proxy to an other application. I used this to redirect all HTTP calls from a public domain into an internal Azure VM. The benefit is that this way you can still use App Service specific features like certificate handling or the AD-based authentication; and also SSL termination is handled by the proxy (the application runs only on HTTP).&lt;/p&gt;

&lt;h2 id=&quot;create-web-app-for-containers&quot;&gt;Create Web App for Containers&lt;/h2&gt;

&lt;p&gt;In the Azure Portal create a new Web App for Containers resource:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;./01_create_web_app_for_containers.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Choose “Docker Container” as Publish method and “Linux” as Operating System:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;./01_create_web_app_for_containers2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;setup-nginx-container&quot;&gt;Setup Nginx container&lt;/h2&gt;

&lt;p&gt;In the newly created Web App for Containers go to the Deployment Center and choose “nginx:latest” as Docker image:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;./02_deployment_center.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;map-storage-with-configuration&quot;&gt;Map storage with configuration&lt;/h2&gt;

&lt;p&gt;Next, create a new Azure Storage and a new storage container (e.g. &lt;code class=&quot;highlighter-rouge&quot;&gt;nginx&lt;/code&gt;). In this container create a new &lt;code class=&quot;highlighter-rouge&quot;&gt;nginx.config&lt;/code&gt; file:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;events {}
http {
    server {
        location / {
            proxy_pass http://yourtargethost/;

            proxy_set_header X-Forwarded-Host mynginxsample.azurewebsites.net;
            proxy_set_header X-Forwarded-Proto https;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

            proxy_pass_request_headers on;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection &quot;Upgrade&quot;;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;mynginxsample.azurewebsites.net&lt;/code&gt; is the public host address the external clients will use; &lt;code class=&quot;highlighter-rouge&quot;&gt;http://yourtargethost/&lt;/code&gt; is the host where the requests should be redirected to. The following headers are needed so that the target application knows the original host name:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;proxy_set_header X-Forwarded-Host mynginxsample.azurewebsites.net;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To support web socket connection pass through, use the following options (required for e.g. SignalR):&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;proxy_pass_request_headers on;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection &quot;Upgrade&quot;;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the App Service, go to Configuration and create a new Path Mapping with the Mount path &lt;code class=&quot;highlighter-rouge&quot;&gt;/etc/nginx&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;./03_create_path_mapping.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;optional-enable-forwarded-header-processing-in-aspnet-core&quot;&gt;Optional: Enable forwarded header processing in ASP.NET Core&lt;/h2&gt;

&lt;p&gt;In ASP.NET Core you need to enable the processing of forwarded headers (X-Forwarded-*), so that the application uses the correct public host names. To enable the feature, add the following configuration code:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ConfigureServices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IServiceCollection&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Configure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ForwardedHeadersOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ForwardedHeaders&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ForwardedHeaders&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;All&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// allow all (for security reasons, this should only be used when &lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// only the proxy can access the application directly)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;KnownNetworks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;IPNetwork&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IPAddress&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;0.0.0.0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

    &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then enable the configuration in the HTTP pipeline with:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Configure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IApplicationBuilder&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IHostEnvironment&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ILogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Startup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;UseForwardedHeaders&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The code above should only be executed when the application is actually running behind a proxy (i.e. consider putting it behind a configuration flag). For more information see &lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer&quot;&gt;Configure ASP.NET Core to work with proxy servers and load balancers&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;restart-the-app&quot;&gt;Restart the app&lt;/h2&gt;

&lt;p&gt;Now restart the app so that the configuration is picked up and you should have the Nginx proxy running.&lt;/p&gt;
</content>

			
				<category term="Software Engineering" />
			
			
				<category term="Azure" />
			
				<category term="Docker" />
			
				<category term="ASP.NET Core" />
			
				<category term=".NET" />
			
				<category term="Nginx" />
			

			<published>2022-12-10T00:00:00+00:00</published>
		</entry>
	
		<entry>
			<id>https://blog.rsuter.com/versioned-aspnetcore-apis-with-openapi-generation-and-azure-api-management/</id>
			<title>How to set up an ASP.NET Core project with multiple API versions, OpenAPI generation and Azure API Management</title>
			<link href="https://blog.rsuter.com/versioned-aspnetcore-apis-with-openapi-generation-and-azure-api-management/" rel="alternate" type="text/html" title="How to set up an ASP.NET Core project with multiple API versions, OpenAPI generation and Azure API Management" />
			<updated>2021-02-25T00:00:00+00:00</updated>

			
				
				<author>
					
						<name>Rico Suter</name>
					
					
					
						<uri>https://rsuter.com</uri>
					
				</author>
			
			<summary></summary>
			<content type="html" xml:base="https://blog.rsuter.com/versioned-aspnetcore-apis-with-openapi-generation-and-azure-api-management/">&lt;p&gt;In this article I’ll describe how to set up an ASP.NET Core web API project with multiple versions, automatic generation of multiple OpenAPI specifications with &lt;a href=&quot;https://github.com/RicoSuter/NSwag&quot;&gt;NSwag&lt;/a&gt; and uploading them to Azure API Management.&lt;/p&gt;

&lt;p&gt;Our sample service will expose two different API versions under the routes &lt;code class=&quot;highlighter-rouge&quot;&gt;/api/v1&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;/api/v2&lt;/code&gt;. So conceptually there is a service version (e.g. v1.6) and multiple API versions (e.g. v1.8 and v2.1). Only the major API version is exposed in the URL. The URL only contains and changes for major versions because minor versions are not breaking (see semver) and thus we don’t need to introduce and expose a new version.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two remarks:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;For simplicity you could use the latest API version (v2.1) as the service version.&lt;/li&gt;
  &lt;li&gt;To maintain backwards compatibility with older consumers we need to serve multiple versions simultaneously - at least two to be able to do gradual migrations without service interruptions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In our setup, each major version will produce an OpenAPI specification which is served in the Swagger UI for testing and also generated at build time as local OpenAPI JSON specification files. This file generation is very useful to review the public API changes in Pull Requests (when committed to the repository) and to upload to Azure API Management in the CD pipeline later when the service is deployed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There is one caveat though:&lt;/strong&gt; By default the paths in the specifications are relative to the root URL of the service, i.e. in both specs the operation paths start with &lt;code class=&quot;highlighter-rouge&quot;&gt;/api/v...&lt;/code&gt;. The problem is that Azure API Management does not support two APIs with the same external URL and then route a request to the correct API based on the operations in the specs, i.e it cannot choose the correct API based on the full URL. That’s why we need to remove the path prefixes in the specs (e.g. remove &lt;code class=&quot;highlighter-rouge&quot;&gt;/api/v1&lt;/code&gt;) and use this prefix as the base path of the Azure API Management API base URL.&lt;/p&gt;

&lt;p&gt;To clarify this:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The service is served under &lt;code class=&quot;highlighter-rouge&quot;&gt;https://myurl.com&lt;/code&gt; (here &lt;code class=&quot;highlighter-rouge&quot;&gt;myurl.com&lt;/code&gt; is served by Azure API Management)&lt;/li&gt;
  &lt;li&gt;The API v1 is served under &lt;code class=&quot;highlighter-rouge&quot;&gt;https://myurl.com/api/v1&lt;/code&gt; (using the route &lt;code class=&quot;highlighter-rouge&quot;&gt;/api/v1&lt;/code&gt; in the ASP.NET Core application internally)&lt;/li&gt;
  &lt;li&gt;The Azure API Management API backend URL must be &lt;code class=&quot;highlighter-rouge&quot;&gt;https://myurl.com/api/v1&lt;/code&gt; (and not &lt;code class=&quot;highlighter-rouge&quot;&gt;https://myurl.com&lt;/code&gt;) so that a request can clearly be correlated to a single APIM API and backend server URL&lt;/li&gt;
  &lt;li&gt;And thus the API v1 specification which is uploaded to APIM must not contain operations with &lt;code class=&quot;highlighter-rouge&quot;&gt;/api/v1&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;setup-the-aspnet-core-application&quot;&gt;Setup the ASP.NET Core application&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;All the sample code can be found in &lt;a href=&quot;https://github.com/Namotion/Apimundo.Demo/tree/master/ProductService/ProductService&quot;&gt;this repository&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First we will create a new project where we enable API versioning in the &lt;code class=&quot;highlighter-rouge&quot;&gt;Startup.cs&lt;/code&gt; with versioning in the path:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ConfigureServices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IServiceCollection&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AddApiVersioning&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ReportApiVersions&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AssumeDefaultVersionWhenUnspecified&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DefaultApiVersion&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ApiVersion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ApiVersionReader&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;UrlSegmentApiVersionReader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AddVersionedApiExplorer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DefaultApiVersion&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ApiVersion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GroupNameFormat&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;'v'VVV&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SubstituteApiVersionInUrl&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After versioning is set up we also register two OpenAPI documents - one for each major version (using &lt;a href=&quot;https://github.com/RicoSuter/NSwag&quot;&gt;NSwag&lt;/a&gt;):&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ConfigureServices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IServiceCollection&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;

    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;versions&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Here you can control the minor version of each supported major version&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;foreach&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;version&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;versions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AddOpenApiDocument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Title&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Product Service&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Description&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Manages products and their metadata.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

            &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DocumentName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;v&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Major&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ApiGroupNames&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;v&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Major&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Version&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Major&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;.&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Minor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

            &lt;span class=&quot;c1&quot;&gt;// Patch document for Azure API Management&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AllowReferencesWithProperties&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PostProcess&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;document&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/api/v&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Major&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;foreach&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pair&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Paths&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ToArray&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Paths&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Remove&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pair&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Paths&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pair&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Substring&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pair&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; To avoid problems with &lt;code class=&quot;highlighter-rouge&quot;&gt;$ref&lt;/code&gt; handling in Azure API Management we need to enable &lt;code class=&quot;highlighter-rouge&quot;&gt;allowReferencesWithProperties&lt;/code&gt; so that $refs are inlined and not described with &lt;code class=&quot;highlighter-rouge&quot;&gt;oneOf&lt;/code&gt; structures. If you do not need to import the specs into Azure API Management I recommend to not patch the operation paths and server URL and leave them as is (relative to the hosted URL).&lt;/p&gt;

&lt;p&gt;Because we patched the operation paths for Azure API Management, we need to add the removed prefix to the server URL so that the served Swagger UI still works:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Configure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IApplicationBuilder&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IWebHostEnvironment&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
    
    &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;UseSwaggerUi3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;UseOpenApi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PostProcess&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Patch server URL for Swagger UI&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/api/v&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;'.'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Servers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;First&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Url&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

    &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you now start the project and navigate to &lt;code class=&quot;highlighter-rouge&quot;&gt;/swagger&lt;/code&gt; you should be able to switch between the versions in the dropdown:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;SwaggerUI.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;As this is working we will now add the &lt;a href=&quot;https://www.nuget.org/packages/NSwag.MSBuild/&quot;&gt;NSwag.MSBuild&lt;/a&gt; package to the project to generate the specs at build time as files. For that we need to create two NSwag configuration files, &lt;code class=&quot;highlighter-rouge&quot;&gt;nswag_v1.json&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;nswag_v2.json&lt;/code&gt; (only v1 is shown):&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;documentGenerator&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;aspNetCoreToOpenApi&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;project&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ProductService.csproj&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;documentName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;v1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;output&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;openapi_v1.json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the &lt;code class=&quot;highlighter-rouge&quot;&gt;ProductService.csproj&lt;/code&gt; file we now need to run NSwag so that the OpenAPI JSON files are automatically generated on build:&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;Project&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Sdk=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Microsoft.NET.Sdk.Web&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  ...
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;Target&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;NSwag&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;AfterTargets=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Build&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;Exec&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Command=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;$(NSwagExe_Core31) run nswag_v1.json /variables:Configuration=$(Configuration)&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;Exec&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Command=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;$(NSwagExe_Core31) run nswag_v2.json /variables:Configuration=$(Configuration)&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/Target&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After building the solution you should see the two specs generated:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;SpecOutput.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In your CI build you can now add these two specs to the CI artifacts and then in your CD pipeline (release) you can pick them up and upload to Azure API Management.&lt;/p&gt;

&lt;h1 id=&quot;upload-the-specs-to-azure-api-management&quot;&gt;Upload the specs to Azure API Management&lt;/h1&gt;

&lt;p&gt;Assuming you have one Azure API Management instance per environment, we can now run the following script in the CD pipeline to create an API Version Set with the v1 and v2 APIs:&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ApiMgmtContext&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; New-AzApiManagementContext -ResourceGroupName &lt;span class=&quot;s2&quot;&gt;&quot;Namotion&quot;&lt;/span&gt; -ServiceName &lt;span class=&quot;s2&quot;&gt;&quot;NamotionApim&quot;&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;$VersionSet&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; New-AzApiManagementApiVersionSet &lt;span class=&quot;se&quot;&gt;`&lt;/span&gt;
    -Context &lt;span class=&quot;nv&quot;&gt;$ApiMgmtContext&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;`&lt;/span&gt;
    -Name &lt;span class=&quot;s2&quot;&gt;&quot;Product Service API&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;`&lt;/span&gt;
    -Description &lt;span class=&quot;s2&quot;&gt;&quot;The Product Service API.&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;`&lt;/span&gt;
    -Scheme Segment

Import-AzApiManagementApi &lt;span class=&quot;se&quot;&gt;`&lt;/span&gt;
    -Context &lt;span class=&quot;nv&quot;&gt;$ApiMgmtContext&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;`&lt;/span&gt;
    -ApiVersionSetId &lt;span class=&quot;nv&quot;&gt;$VersionSet&lt;/span&gt;.ApiVersionSetId &lt;span class=&quot;se&quot;&gt;`&lt;/span&gt;
    -ApiId &lt;span class=&quot;s2&quot;&gt;&quot;product-server-api-v1&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;`&lt;/span&gt;
    -Path &lt;span class=&quot;s2&quot;&gt;&quot;products&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;`&lt;/span&gt;
    -ServiceUrl &lt;span class=&quot;s2&quot;&gt;&quot;https://internal.products.myurl.com/api&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;`&lt;/span&gt;
    -ApiVersion &lt;span class=&quot;s2&quot;&gt;&quot;v1&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;`&lt;/span&gt;
    -SpecificationFormat &lt;span class=&quot;s2&quot;&gt;&quot;OpenApiJson&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;`&lt;/span&gt;
    -SpecificationPath &lt;span class=&quot;s2&quot;&gt;&quot;./openapi_v1.json&quot;&lt;/span&gt;

Import-AzApiManagementApi &lt;span class=&quot;se&quot;&gt;`&lt;/span&gt;
    -Context &lt;span class=&quot;nv&quot;&gt;$ApiMgmtContext&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;`&lt;/span&gt;
    -ApiVersionSetId &lt;span class=&quot;nv&quot;&gt;$VersionSet&lt;/span&gt;.ApiVersionSetId &lt;span class=&quot;se&quot;&gt;`&lt;/span&gt;
    -ApiId &lt;span class=&quot;s2&quot;&gt;&quot;product-server-api-v2&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;`&lt;/span&gt;
    -Path &lt;span class=&quot;s2&quot;&gt;&quot;products&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;`&lt;/span&gt;
    -ServiceUrl &lt;span class=&quot;s2&quot;&gt;&quot;https://internal.products.myurl.com/api&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;`&lt;/span&gt;
    -ApiVersion &lt;span class=&quot;s2&quot;&gt;&quot;v2&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;`&lt;/span&gt;
    -SpecificationFormat &lt;span class=&quot;s2&quot;&gt;&quot;OpenApiJson&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;`&lt;/span&gt;
    -SpecificationPath &lt;span class=&quot;s2&quot;&gt;&quot;./openapi_v2.json&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If you have a single Azure API Management instance for multiple environments, then you need to add the environment name to the version set’s &lt;code class=&quot;highlighter-rouge&quot;&gt;-Name&lt;/code&gt; (e.g. &lt;code class=&quot;highlighter-rouge&quot;&gt;Product Service API (Dev)&lt;/code&gt;) and API’s &lt;code class=&quot;highlighter-rouge&quot;&gt;-ApiId&lt;/code&gt; parameters (e.g. &lt;code class=&quot;highlighter-rouge&quot;&gt;product-server-api-v2-dev&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;As you can see in this sample, you should deploy your service to &lt;code class=&quot;highlighter-rouge&quot;&gt;https://internal.products.myurl.com&lt;/code&gt; so that the API v1 is hosted on &lt;code class=&quot;highlighter-rouge&quot;&gt;https://internal.products.myurl.com/api/v1&lt;/code&gt;. We add the &lt;code class=&quot;highlighter-rouge&quot;&gt;/api&lt;/code&gt; to the &lt;code class=&quot;highlighter-rouge&quot;&gt;ServiceUrl&lt;/code&gt; so that the API Version name only contains &lt;code class=&quot;highlighter-rouge&quot;&gt;v1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After running this script you should see the following API hierarchy in the Azure API Management instance:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;ApiManagement.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;track-the-api-versions-in-apimundo&quot;&gt;Track the API versions in Apimundo&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://apimundo.com&quot;&gt;Apimundo&lt;/a&gt; is an architecture and service API documentation system which can automatically track, document and compare web API interfaces with OpenAPI specs. After creating a new OpenAPI Endpoint, you can easily register multiple specification endpoints and add the prefix like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;ApimundoDialog.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After the system automatically indexes your specs (might take some minutes) you can see a single list of API versions where each major version can progress individually with new minor versions:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;ApimundoVersions.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The final result can be &lt;a href=&quot;https://apimundo.com/organizations/demo/projects/demo/services/10/endpoints/17/environments/15/version/latest/compare/none&quot;&gt;played with here (Demo)&lt;/a&gt;.&lt;/p&gt;
</content>

			
				<category term="Software Engineering" />
			
			
				<category term="ASP.NET Core" />
			
				<category term=".NET" />
			
				<category term=".NET Core" />
			
				<category term="OpenAPI" />
			
				<category term="NSwag" />
			
				<category term="Azure API Management" />
			
				<category term="Apimundo" />
			

			<published>2021-02-25T00:00:00+00:00</published>
		</entry>
	
		<entry>
			<id>https://blog.rsuter.com/azure-event-hubs-improve-scalability-with-batch-parallelization/</id>
			<title>Azure Event Hubs: Improve scalability with batch parallelization</title>
			<link href="https://blog.rsuter.com/azure-event-hubs-improve-scalability-with-batch-parallelization/" rel="alternate" type="text/html" title="Azure Event Hubs: Improve scalability with batch parallelization" />
			<updated>2020-05-05T00:00:00+00:00</updated>

			
				
				<author>
					
						<name>Rico Suter</name>
					
					
					
						<uri>https://rsuter.com</uri>
					
				</author>
			
			<summary></summary>
			<content type="html" xml:base="https://blog.rsuter.com/azure-event-hubs-improve-scalability-with-batch-parallelization/">&lt;p&gt;In one of my projects we are ingesting lots of device events with Azure Event Hubs. 
In the given scenario it is important to process the events from one device in sequence. 
In our initial implementation we just read a batch of events from the partition and process the 
events within the batch in sequence. However at some point we reached a limit and we couldn’t 
use all CPU resources of the machines we used - mainly because half the work of one event is I/O bound.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you process each partition stricly sequentially then your maximum parallelization is the number of partitions.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;./sequential.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;So what can we do? Of course the simplest solution is to increase the number of partitions 
in the Event Hub - but what if you already reached the maximum number of partitions (e.g. 32) or 
you cannot change the partition count because it is a running system?&lt;/p&gt;

&lt;p&gt;A general solution to this works as follows: 
&lt;strong&gt;The partition consumers reads a batch of events and then processes this batch as fast and efficient as possible, maybe not strictly sequentially.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;How can we improve this processing in our scenario? Instead of processing all events in sequence we 
process the events in the batch in parallel but in sequential groups by device. This way we can parallelize 
within the batch and at the same time ensure that events from one device are processed in sequence.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;./parallel.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The implementation of this is quite simple:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Group the events by partition key and put the groups into a queue&lt;/li&gt;
  &lt;li&gt;Spawn multiple threads and process the groups in parallel but sequentially within the groups&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This functionality is implemented in the open-source library &lt;a href=&quot;https://github.com/RicoSuter/Namotion.Messaging&quot;&gt;Namotion.Messaging&lt;/a&gt; (in the library events are the same as messages):&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ProcessByPartitionKeyAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TPartitionKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IEnumerable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;messages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TPartitionKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;partitionKeySelector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IEnumerable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CancellationToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;processPartitionMessages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;partitionParallelization&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;CancellationToken&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cancellationToken&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;deserializedMessages&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transform&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;messages&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Select&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AsParallel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AsOrdered&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ToArray&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;messages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Cast&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ToArray&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;batchPartitionsQueue&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ConcurrentQueue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;]&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;deserializedMessages&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;GroupBy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;partitionKeySelector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Select&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;g&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ToArray&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ToArray&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;

    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tasks&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Enumerable&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Min&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;deserializedMessages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;partitionParallelization&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Select&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;batchPartitionsQueue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;TryDequeue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;out&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;batchPartition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;processPartitionMessages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;batchPartition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cancellationToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cancellationToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WhenAll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/RicoSuter/Namotion.Messaging/blob/master/src/Namotion.Messaging/MessageEnumerableExtensions.cs&quot;&gt;MessageEnumerableExtensions.cs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These extension methods can now be easily used in a event receiver background service:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyBackgroundService&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BackgroundService&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PartitionParallelization&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;readonly&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IMessageReceiver&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;receiver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;MyBackgroundService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IMessageReceiver&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;receiver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;receiver&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;receiver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ExecuteAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CancellationToken&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cancellationToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;receiver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ListenWithRetryAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ProcessMessagesAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cancellationToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ProcessMessagesAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IEnumerable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;allMessages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CancellationToken&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cancellationToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;allMessages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ProcessByPartitionKeyAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;DeserializeMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DeviceId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;messages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;foreach&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;messages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// TODO: Process messages&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PartitionParallelization&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cancellationToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;receiver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ConfirmAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;allMessages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cancellationToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DeviceMessage&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;DeserializeMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Message&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// TODO: Deserialize message&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>

			
				<category term="Software Engineering" />
			
			
				<category term="Azure" />
			
				<category term="Event Hub" />
			
				<category term="Scalability" />
			
				<category term="Cloud" />
			
				<category term=".NET" />
			
				<category term="C#" />
			

			<published>2020-05-05T00:00:00+00:00</published>
		</entry>
	
		<entry>
			<id>https://blog.rsuter.com/azure-devops-my-versioning-flow-to-publish-dotnet-packages-from-github-repository/</id>
			<title>Azure DevOps: My versioning flow to publish .NET packages from a GitHub repository</title>
			<link href="https://blog.rsuter.com/azure-devops-my-versioning-flow-to-publish-dotnet-packages-from-github-repository/" rel="alternate" type="text/html" title="Azure DevOps: My versioning flow to publish .NET packages from a GitHub repository" />
			<updated>2019-08-15T00:00:00+00:00</updated>

			
				
				<author>
					
						<name>Rico Suter</name>
					
					
					
						<uri>https://rsuter.com</uri>
					
				</author>
			
			<summary></summary>
			<content type="html" xml:base="https://blog.rsuter.com/azure-devops-my-versioning-flow-to-publish-dotnet-packages-from-github-repository/">&lt;p&gt;The starting point of this guide is a GitHub repository with some SDK-style .NET projects for which you want to enable CI/CD builds to automatically run tests, build packages and push them to a preview or NuGet.org package feed.&lt;/p&gt;

&lt;p&gt;I created this setup and versioning flow for my new library &lt;a href=&quot;https://github.com/RicoSuter/Namotion.Reflection&quot;&gt;Namotion.Reflection&lt;/a&gt; and will describe it in this article. You can copy the pipeline YAML file and directly use it in your own repository.&lt;/p&gt;

&lt;h2 id=&quot;the-preview-and-release-flow&quot;&gt;The preview and release flow&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;The repository is using GitHub flow and thus the lastest releasable version is on the &lt;code class=&quot;highlighter-rouge&quot;&gt;master&lt;/code&gt; branch.&lt;/li&gt;
  &lt;li&gt;Package version numbers are managed in the in the repository in the &lt;code class=&quot;highlighter-rouge&quot;&gt;.csproj&lt;/code&gt; files.&lt;/li&gt;
  &lt;li&gt;Builds on &lt;code class=&quot;highlighter-rouge&quot;&gt;master&lt;/code&gt; create preview packages with the version &lt;code class=&quot;highlighter-rouge&quot;&gt;x.y.(z+1)-build.xxx&lt;/code&gt; so that the version is always greater than the latest published version (the current version &lt;code class=&quot;highlighter-rouge&quot;&gt;x.y.z&lt;/code&gt; in the repository) and always lower then the next published version (the next released version will be at least version &lt;code class=&quot;highlighter-rouge&quot;&gt;x.y.(z+1)&lt;/code&gt; but without preview tag and thus greater than &lt;code class=&quot;highlighter-rouge&quot;&gt;x.y.(z+1)-build.xxx&lt;/code&gt;).&lt;/li&gt;
  &lt;li&gt;When we release a new version, then we update the versions of the changed packages on &lt;code class=&quot;highlighter-rouge&quot;&gt;master&lt;/code&gt; (commit name &lt;code class=&quot;highlighter-rouge&quot;&gt;Release vx.y.z&lt;/code&gt;) and merge into the &lt;code class=&quot;highlighter-rouge&quot;&gt;release&lt;/code&gt; branch to trigger a non-preview build and push the packages to &lt;a href=&quot;https://nuget.org&quot;&gt;nuget.org&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;(Another option would be to not merge to the &lt;code class=&quot;highlighter-rouge&quot;&gt;release&lt;/code&gt; branch but tag the commit &lt;code class=&quot;highlighter-rouge&quot;&gt;Release vx.y.z&lt;/code&gt; with &lt;code class=&quot;highlighter-rouge&quot;&gt;vx.y.z&lt;/code&gt; and trigger a release this way.)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;1-enable-package-build-in-your-sdk-style-net-projects&quot;&gt;1. Enable package build in your SDK-style .NET projects&lt;/h2&gt;

&lt;p&gt;First we need to enable package builds for all projects which should produce packages.&lt;/p&gt;

&lt;p&gt;For this, open your solution in Visual Studio and enable “Generate NuGet package on build” on all projects you want to automatically deploy to the package feeds:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;enable-build-package-on-build.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Alternatively you can also enable this in the &lt;code class=&quot;highlighter-rouge&quot;&gt;.csproj&lt;/code&gt; file directly by adding a &lt;code class=&quot;highlighter-rouge&quot;&gt;GeneratePackageOnBuild&lt;/code&gt; tag:&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;Project&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Sdk=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Microsoft.NET.Sdk&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;TargetFrameworks&amp;gt;&lt;/span&gt;netstandard1.0;netstandard2.0;net40;net45&lt;span class=&quot;nt&quot;&gt;&amp;lt;/TargetFrameworks&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;Version&amp;gt;&lt;/span&gt;0.1.0&lt;span class=&quot;nt&quot;&gt;&amp;lt;/Version&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;GeneratePackageOnBuild&amp;gt;&lt;/span&gt;true&lt;span class=&quot;nt&quot;&gt;&amp;lt;/GeneratePackageOnBuild&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;GenerateDocumentationFile&amp;gt;&lt;/span&gt;true&lt;span class=&quot;nt&quot;&gt;&amp;lt;/GenerateDocumentationFile&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
  ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Also consider adding the &lt;code class=&quot;highlighter-rouge&quot;&gt;GenerateDocumentationFile&lt;/code&gt; attribute so that the XML documentation is included in the package.&lt;/p&gt;

&lt;h2 id=&quot;2-add-an-azure-pipelinesyml-file-to-your-repository&quot;&gt;2. Add an azure-pipelines.yml file to your repository&lt;/h2&gt;

&lt;p&gt;The next step is to add an &lt;code class=&quot;highlighter-rouge&quot;&gt;azure-pipelines.yml&lt;/code&gt; file which is later picked up by the Azure DevOps build pipeline to build, test and publish the NuGet packages - the complete YAML file can be found &lt;a href=&quot;https://github.com/RicoSuter/Namotion.Reflection/blob/master/azure-pipelines.yml&quot;&gt;here&lt;/a&gt;. The head of the build file looks like this:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;trigger&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;branches&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;master&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;release&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;pr&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;master&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;pool&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;vmImage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;windows-2019'&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;variables&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;BuildConfiguration&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Release&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;Projects&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;**/*.csproj'&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The build will trigger on the &lt;code class=&quot;highlighter-rouge&quot;&gt;master&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;release&lt;/code&gt; branches and on all PRs merging into &lt;code class=&quot;highlighter-rouge&quot;&gt;master&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The specified &lt;code class=&quot;highlighter-rouge&quot;&gt;vmImage&lt;/code&gt; is &lt;code class=&quot;highlighter-rouge&quot;&gt;windows-2019&lt;/code&gt; (with Visual Studio) because the &lt;code class=&quot;highlighter-rouge&quot;&gt;Namotion.Reflection&lt;/code&gt; projects &lt;a href=&quot;https://github.com/RicoSuter/Namotion.Reflection/blob/master/src/Namotion.Reflection/Namotion.Reflection.csproj#L3&quot;&gt;use multiple target frameworks&lt;/a&gt; and one of them is the full .NET Framework (netfx, Windows-only). If your projects only use .NET Core or .NET Standard target frameworks you should be able to build on Ubuntu with &lt;code class=&quot;highlighter-rouge&quot;&gt;ubuntu-latest&lt;/code&gt; image. You can find a list of &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/hosted?view=azure-devops#use-a-microsoft-hosted-agent&quot;&gt;all available vmImages here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now, let’s look at the required pipeline steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install required SDKs and tools&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the first steps we install the required SDKs and tools which are used later:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;.NET Core 2.2 is needed to build the solution and to use the DNT tool&lt;/li&gt;
  &lt;li&gt;The &lt;a href=&quot;https://github.com/RicoSuter/DNT&quot;&gt;DNT (DotNetTools)&lt;/a&gt; CLI tool is used to patch the project versions later&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;UseDotNet@2&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;displayName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Install&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;.NET&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Core&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;SDK'&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;inputs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;packageType&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;sdk'&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;2.2.203'&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;CmdLine@2&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;displayName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Install&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;DNT'&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;condition&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'))&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;inputs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;npm&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-g&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;dotnettools'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Patch project versions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the build is running on the &lt;code class=&quot;highlighter-rouge&quot;&gt;master&lt;/code&gt; branch, we increase the project patch version (&lt;code class=&quot;highlighter-rouge&quot;&gt;0.1.0&lt;/code&gt; =&amp;gt; &lt;code class=&quot;highlighter-rouge&quot;&gt;0.1.1&lt;/code&gt;) and add a a build suffix (&lt;code class=&quot;highlighter-rouge&quot;&gt;0.1.1&lt;/code&gt; =&amp;gt; &lt;code class=&quot;highlighter-rouge&quot;&gt;0.1.1-build.20190518.35&lt;/code&gt;). This is needed so that the package versions in the preview NuGet feed are unique even without changing the versions in the &lt;code class=&quot;highlighter-rouge&quot;&gt;.csproj&lt;/code&gt; files.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;UseDotNet@2&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;displayName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Install&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;.NET&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Core&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;SDK'&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;inputs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;packageType&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;sdk'&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;2.2.203'&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;CmdLine@2&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;displayName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Install&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;DNT'&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;condition&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'))&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;inputs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;npm&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-g&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;dotnettools'&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;CmdLine@2&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;displayName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Update&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;patch'&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;condition&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'))&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;inputs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;dnt&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;bump-versions&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;patch'&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;failOnStderr&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;CmdLine@2&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;displayName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Patch&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;preview'&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;condition&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'))&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;inputs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;dnt&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;bump-versions&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;preview&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;$(Build.BuildNumber)&quot;'&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;failOnStderr&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Build and test&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the following steps we build, test and create NuGet packages.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;DotNetCoreCLI@2&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;displayName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Build&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;solution'&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;inputs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;build'&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;projects&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;$(Projects)'&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--configuration&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;$(BuildConfiguration)'&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;feedsToUse&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;select'&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;versioningScheme&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;off'&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;DotNetCoreCLI@2&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;displayName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;tests'&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;inputs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;test'&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;projects&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;$(Projects)'&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--configuration&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;$(BuildConfiguration)&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--collect&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Code&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Coverage&quot;'&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;publishTestResults&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;feedsToUse&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;select'&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;versioningScheme&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;off'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The build step automatically creates packages because we enabled “Generate NuGet package on build” before. The &lt;code class=&quot;highlighter-rouge&quot;&gt;--collect &quot;Code Coverage&quot;&lt;/code&gt; parameter in the test step enables test code coverage publishing but this might only work if the build agent &lt;a href=&quot;https://github.com/Microsoft/vstest/issues/981&quot;&gt;has Visual Studio installed&lt;/a&gt; (i.e. a Windows build agent). To customize the code coverage execution, you can specify a &lt;code class=&quot;highlighter-rouge&quot;&gt;.runsettings&lt;/code&gt; file (&lt;a href=&quot;https://github.com/RicoSuter/Namotion.Storage/blob/master/azure-pipelines.yml&quot;&gt;sample&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pack and publish artifacts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The last steps copy the NuGet packages and publish them as build artifacts which are later picked up by a release pipeline and pushed to a package feed.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;CopyFiles@2&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;displayName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Copy&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;packages'&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;condition&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;inputs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;Contents&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;**/*.nupkg'&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;TargetFolder&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;$(Build.ArtifactStagingDirectory)'&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;flattenFolders&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;PublishBuildArtifacts@1&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;displayName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Publish&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;artifacts'&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;condition&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;inputs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;PathtoPublish&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;$(Build.ArtifactStagingDirectory)'&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ArtifactName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;drop'&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;publishLocation&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Container'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;3-setup-the-build-pipeline-in-azure-devops&quot;&gt;3. Setup the build pipeline in Azure DevOps&lt;/h2&gt;

&lt;p&gt;To actually use this build definition, we need to create an Azure DevOps project and then a new build pipeline. In your Azure DevOps dashboard, click on &lt;strong&gt;”+ Create project”&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;create-azure-devops-project.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After creating the project, go to &lt;strong&gt;Pipelines / Builds&lt;/strong&gt;, create a new build pipeline and select &lt;strong&gt;GitHub (yaml)&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;create-pipeline-step-1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In the next step, select your GitHub repository and authenticate it:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;create-pipeline-step-2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The “Review” step should automatically select your YAML build script from the repository and after clicking on “Run” the pipeline is created and the first build is started:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;create-pipeline-step-4.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;4-setup-the-release-pipelines-for-the-ci-and-release-branch&quot;&gt;4. Setup the release pipelines for the CI and release branch&lt;/h2&gt;

&lt;p&gt;Now that the build pipeline is set up, we will create two release pipelines:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Namotion.Reflection - Master:&lt;/strong&gt; Publish preview packages from the &lt;code class=&quot;highlighter-rouge&quot;&gt;master&lt;/code&gt; branch to a preview NuGet feed.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Namotion.Reflection - Release:&lt;/strong&gt; Publish release packages from the &lt;code class=&quot;highlighter-rouge&quot;&gt;release&lt;/code&gt; branch to the public NuGet.org feed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;create-release-pipeline-to-publish-to-preview-nuget-feed&quot;&gt;Create release pipeline to publish to preview NuGet feed&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;First go to &lt;strong&gt;Artifacts&lt;/strong&gt; and create a new NuGet feed where preview packages should be pushed to:&lt;/li&gt;
  &lt;li&gt;Then go to &lt;strong&gt;Pipelines / Releases&lt;/strong&gt;, create a new release pipeline and select &lt;strong&gt;Empty job&lt;/strong&gt; (no template).&lt;/li&gt;
  &lt;li&gt;Use &lt;strong&gt;Push to preview feed&lt;/strong&gt; as stage name.&lt;/li&gt;
  &lt;li&gt;Click on “+ Add an artifact” and select the build pipeline in the dialog.&lt;/li&gt;
  &lt;li&gt;Click on the artifact trigger symbol and enable a continuous deployment trigger on the &lt;code class=&quot;highlighter-rouge&quot;&gt;master&lt;/code&gt; branch:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;release-pipeline-continuous-trigger-preview.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Click on tasks and add a new &lt;strong&gt;.NET Core&lt;/strong&gt; task with the following properties:
    &lt;ul&gt;
      &lt;li&gt;Command: &lt;code class=&quot;highlighter-rouge&quot;&gt;nuget push&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;Path to NuGet package(s) to publish: &lt;code class=&quot;highlighter-rouge&quot;&gt;$(System.DefaultWorkingDirectory)/**/*.nupkg&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;Target feed location: Use “This organization/collection” and select your preview NuGet feed&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;create-a-release-pipeline-to-nugetorg-feed&quot;&gt;Create a release pipeline to NuGet.org feed&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;In order to push packages to the public NuGet.org feed, we need to setup a new service connection in Azure Devops:
    &lt;ul&gt;
      &lt;li&gt;Go to “Project settings” (bottom right)&lt;/li&gt;
      &lt;li&gt;Select “Service Connections”&lt;/li&gt;
      &lt;li&gt;Select “+ New service connection” and select “NuGet”&lt;/li&gt;
      &lt;li&gt;Use “ApiKey” and fill in the missing fields:
        &lt;ul&gt;
          &lt;li&gt;Connection name: &lt;code class=&quot;highlighter-rouge&quot;&gt;NuGet.org&lt;/code&gt; (must match the name in the build step)&lt;/li&gt;
          &lt;li&gt;Feed URL: &lt;code class=&quot;highlighter-rouge&quot;&gt;https://api.nuget.org/v3/index.json&lt;/code&gt;&lt;/li&gt;
          &lt;li&gt;ApiKey: &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;your NuGet.org push API key&amp;gt;&lt;/code&gt;&lt;/li&gt;
          &lt;li&gt;For more information see &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/pipelines/library/service-endpoints?view=azure-devops&amp;amp;tabs=yaml#create-new&quot;&gt;Create a service connection&lt;/a&gt;.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;nuget-service-connection.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Create a new release pipeline in the same way as before, but use  continuous deployment trigger on the &lt;code class=&quot;highlighter-rouge&quot;&gt;release&lt;/code&gt; branch:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;release-pipeline-continuous-trigger-release.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Click on tasks and add a new &lt;strong&gt;.NET Core&lt;/strong&gt; task with the following properties:
    &lt;ul&gt;
      &lt;li&gt;Command: &lt;code class=&quot;highlighter-rouge&quot;&gt;nuget push&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;Path to NuGet package(s) to publish: &lt;code class=&quot;highlighter-rouge&quot;&gt;$(System.DefaultWorkingDirectory)/**/*.nupkg&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;Target feed location
        &lt;ul&gt;
          &lt;li&gt;External NuGet server&lt;/li&gt;
          &lt;li&gt;NuGet server: Select “NuGet.org” which is service connection which has been created before&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;release-pipeline-tasks.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;4-test-the-pipelines&quot;&gt;4. Test the pipelines&lt;/h3&gt;

&lt;p&gt;Now you should be able to queue a new build in the build pipeline. This build is then picked up by the preview release pipeline which pushes the packages to the preview NuGet feed:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;preview-package-feed.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;5-publish-new-package-versions&quot;&gt;5. Publish new package versions&lt;/h3&gt;

&lt;p&gt;To publish new package versions, you first need to update package versions in the &lt;code class=&quot;highlighter-rouge&quot;&gt;.csproj&lt;/code&gt;. This can be easily accomplished with the DNT (DotNetTools) tool:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dnt bump-versions patch
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Commit these changes to the &lt;code class=&quot;highlighter-rouge&quot;&gt;master&lt;/code&gt; branch and merge &lt;code class=&quot;highlighter-rouge&quot;&gt;master&lt;/code&gt; into the &lt;code class=&quot;highlighter-rouge&quot;&gt;release&lt;/code&gt; branch to trigger the release pipeline which pushes the packages to NuGet.org.&lt;/p&gt;
</content>

			
				<category term="Software Engineering" />
			
			
				<category term=".NET" />
			
				<category term="C#" />
			
				<category term="GitHub" />
			
				<category term="Azure DevOps" />
			
				<category term="YAML" />
			
				<category term="Continuous Deployment" />
			
				<category term="Continuous Integration" />
			

			<published>2019-08-15T00:00:00+00:00</published>
		</entry>
	
		<entry>
			<id>https://blog.rsuter.com/dotnet-dependency-injection-way-to-work-around-missing-named-registrations/</id>
			<title>Dependency Injection in .NET: A way to work around missing named registrations</title>
			<link href="https://blog.rsuter.com/dotnet-dependency-injection-way-to-work-around-missing-named-registrations/" rel="alternate" type="text/html" title="Dependency Injection in .NET: A way to work around missing named registrations" />
			<updated>2019-08-02T00:00:00+00:00</updated>

			
				
				<author>
					
						<name>Rico Suter</name>
					
					
					
						<uri>https://rsuter.com</uri>
					
				</author>
			
			<summary></summary>
			<content type="html" xml:base="https://blog.rsuter.com/dotnet-dependency-injection-way-to-work-around-missing-named-registrations/">&lt;p&gt;The default .NET dependency injection container &lt;a href=&quot;https://www.nuget.org/packages/Microsoft.Extensions.DependencyInjection/&quot;&gt;Microsoft.Extensions.DependencyInjection&lt;/a&gt; does not provide a way to register named services. That means you cannot register an interface multiple times and then resolve a specific instance with a name or key. This article shows a technique to solve this by adding a generic marker parameter to the service interface so that it can be registered multiple times and resolved with the marker type.&lt;/p&gt;

&lt;p&gt;Let’s start with a sample: In the code below we have two service classes, &lt;code class=&quot;highlighter-rouge&quot;&gt;OrderService&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;ProductService&lt;/code&gt;, both of them want to publish a message to a message queue through the help of an &lt;code class=&quot;highlighter-rouge&quot;&gt;IMessagePublisher&lt;/code&gt; service interface. The first, &lt;strong&gt;non-working implementation&lt;/strong&gt; would look like this:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IMessagePublisher&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Publish&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Message&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OrderService&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Error: The DI container does not know what registration he needs to inject here&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;OrderService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IMessagePublisher&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orderPublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orderPublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Publish&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;OrderCreatedMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ProductService&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Error&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;OrderService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IMessagePublisher&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;productPublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;productPublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Publish&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ProductCreatedMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Register two publishers with the same interface (MyMessagePublisher is an actual implementation of IMessagePublisher)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AddSingleton&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IMessagePublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;MyMessagePublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;MyOrderCreatedQueue&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AddSingleton&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IMessagePublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;MyMessagePublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;MyProductCreatedQueue&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The previous code won’t work because the &lt;code class=&quot;highlighter-rouge&quot;&gt;IMessagePublisher&lt;/code&gt; interface has been registered multiple times and the resolver does not know what instance to resolve to. The problem we have here is that the default .NET dependency container does not support named service registrations. Something like &lt;strong&gt;this is not possible with the default container&lt;/strong&gt;:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OrderService&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;OrderService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;OrderPublisher&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IMessagePublisher&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orderPublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AddNamedSingleton&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IMessagePublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;OrderPublisher&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;MyMessagePublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;MyOrderCreatedQueue&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Most advanced dependency injection container libraries support a form of named registrations. However, the problem with them is that you will always have some sort of dependency to the injection library; in the previous sample a dependency to the &lt;code class=&quot;highlighter-rouge&quot;&gt;NameAttribute&lt;/code&gt; - this is a &lt;a href=&quot;http://software-pattern.org/leaky-abstraction&quot;&gt;leaky abstraction&lt;/a&gt; and should be avoided.&lt;/p&gt;

&lt;p&gt;One solution to this problem is to introduce the same interface but with an additional generic parameter. Then implement an interceptor class to wrap the original instance and “enrich” it with a generic type:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Define interface with a generic parameter&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IMessagePublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IMessagePublisher&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IMessagePublisher&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Publish&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Message&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Implement a publisher interceptor class which adds the generic parameter to an existing &lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// publisher instance and which just calls the methods of the intercepted instance.&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MessagePublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IMessagePublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IMessagePublisher&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_publisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;MessagePublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IMessagePublisher&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;publisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;_publisher&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;publisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;virtual&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Publish&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Message&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;_publisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Publish&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Now we can register specific publishers&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AddSingleton&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IMessagePublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;OrderCreatedMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&amp;gt;(&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MessagePublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;OrderCreatedMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;MyMessagePublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;MyOrderCreatedQueue&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)));&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AddSingleton&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IMessagePublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ProductCreatedMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&amp;gt;(&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MessagePublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ProductCreatedMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;MyMessagePublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;MyProductCreatedQueue&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)));&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// In the services, we can now inject specific publishers&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OrderService&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;OrderService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IMessagePublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;OrderCreatedMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orderPublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ProductService&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;OrderService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IMessagePublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ProductCreatedMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;productPublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As you can see, with the new interfaces we are able to register two publishers and inject the desired ones into the services. With the new &lt;code class=&quot;highlighter-rouge&quot;&gt;MessagePublisher&amp;lt;T&amp;gt;&lt;/code&gt; interceptor class you can also enhance existing publisher implementations with additional logic by implementing methods with new aspects:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LoggingMessagePublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MessagePublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;readonly&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ILogger&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;LoggingMessagePublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ILogger&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IMessagePublisher&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;publisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
        &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;publisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;_logger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;virtual&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Publish&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Message&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;_logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;LogInformation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Publishing a message.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Publish&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;IMessagePublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ProductCreatedMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;publisher&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; 
    &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LoggingMessagePublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ProductCreatedMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;MyMessagePublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;MyOrderCreatedQueue&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To simplify applying these interceptors you can also implement some extension methods:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MessagePublisherExtensions&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IMessagePublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WithMessageType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IMessagePublisher&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;publisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MessagePublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;publisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IMessagePublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WithLogging&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IMessagePublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;publisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LoggingMessagePublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;publisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IMessagePublisher&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;WithLogging&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IMessagePublisher&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;publisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Here we use object because we don't care about the type&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LoggingMessagePublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;publisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;IMessagePublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ProductCreatedMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;publisher&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; 
    &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;MyMessagePublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;MyOrderCreatedQueue&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WithLogging&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WithMessageType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ProductCreatedMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In simpler cases, you can also leave out the &lt;code class=&quot;highlighter-rouge&quot;&gt;IMessagePublisher&lt;/code&gt; interface and only use a &lt;code class=&quot;highlighter-rouge&quot;&gt;IMessagePublisher&amp;lt;T&amp;gt;&lt;/code&gt; interface.&lt;/p&gt;

&lt;p&gt;What do you think of this technique to circumvent the missing named registration support?&lt;/p&gt;

&lt;p&gt;This technique is heavily used in my abstraction projects &lt;a href=&quot;https://github.com/RicoSuter/Namotion.Messaging&quot;&gt;Namotion.Messaging&lt;/a&gt; and &lt;a href=&quot;https://github.com/RicoSuter/Namotion.Storage&quot;&gt;Namotion.Storage&lt;/a&gt;.&lt;/p&gt;
</content>

			
				<category term="Software Engineering" />
			
			
				<category term="Dependency Injection" />
			
				<category term=".NET" />
			
				<category term=".NET Core" />
			
				<category term="C#" />
			

			<published>2019-08-02T00:00:00+00:00</published>
		</entry>
	
		<entry>
			<id>https://blog.rsuter.com/logging-with-ilogger-recommendations-and-best-practices/</id>
			<title>Logging with ILogger in .NET: Recommendations and best practices</title>
			<link href="https://blog.rsuter.com/logging-with-ilogger-recommendations-and-best-practices/" rel="alternate" type="text/html" title="Logging with ILogger in .NET: Recommendations and best practices" />
			<updated>2019-05-06T00:00:00+00:00</updated>

			
				
				<author>
					
						<name>Rico Suter</name>
					
					
					
						<uri>https://rsuter.com</uri>
					
				</author>
			
			<summary></summary>
			<content type="html" xml:base="https://blog.rsuter.com/logging-with-ilogger-recommendations-and-best-practices/">&lt;p&gt;This article describes recommendations and best practices for using the &lt;code class=&quot;highlighter-rouge&quot;&gt;ILogger&lt;/code&gt; based logging system which has been introduced with .NET Core but is also available in all .NET Standard 2.0 supporting .NET frameworks.&lt;/p&gt;

&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;The logging interfaces provided by the &lt;a href=&quot;https://www.nuget.org/packages/Microsoft.Extensions.Logging.Abstractions&quot;&gt;Microsoft.Extensions.Logging.Abstractions&lt;/a&gt; NuGet package provide common logging abstractions with implementations for various logging backends and sinks.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;Logging backend&lt;/strong&gt; is the place where logs are written to, e.g. files, Application Insights (AI), &lt;a href=&quot;https://datalust.co/seq&quot;&gt;Seq&lt;/a&gt;, Kibana, etc. In the &lt;a href=&quot;https://serilog.net&quot;&gt;Serilog&lt;/a&gt; logging library they are called sinks.&lt;/p&gt;

&lt;h3 id=&quot;ilogger-vs-iloggerprovider-vs-iloggerfactory&quot;&gt;ILogger vs ILoggerProvider vs ILoggerFactory&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;ILogger&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The responsibility of the &lt;code class=&quot;highlighter-rouge&quot;&gt;ILogger&lt;/code&gt; interface is to write a log message of a given log level and create logging scopes. The interface itself only exposes some generic log methods which are then used by “external” extension methods like &lt;code class=&quot;highlighter-rouge&quot;&gt;LogInformation&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;LogError&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ILoggerProvider&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A logger provider is an actual logging sink implementation, e.g. console, Application Insights, files or Serilog (an adapter to the Serilog logging abstraction). The &lt;code class=&quot;highlighter-rouge&quot;&gt;ILoggerProvider&lt;/code&gt;’s only responsibility is to create &lt;code class=&quot;highlighter-rouge&quot;&gt;ILogger&lt;/code&gt; instances which log to an actual sink. A logger instance which is created by a logger provider will only log to the associated logger provider.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ILoggerFactory&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;ILoggerFactory&lt;/code&gt; logger factory instance is the boostrapper of the logging system: It is used to attach logger providers and create logger instances - either typed (&lt;code class=&quot;highlighter-rouge&quot;&gt;ILogger&amp;lt;T&amp;gt;&lt;/code&gt;) or untyped (&lt;code class=&quot;highlighter-rouge&quot;&gt;ILogger&lt;/code&gt;). These logger instances will log to all registered logger providers.&lt;/p&gt;

&lt;h2 id=&quot;log-statements&quot;&gt;Log statements&lt;/h2&gt;

&lt;h3 id=&quot;use-structured-logs&quot;&gt;Use structured logs&lt;/h3&gt;

&lt;p&gt;It is recommended to always use semantic/structured logs so that the logging backend receives the string with placeholders and its values separately. It is then able to just replace them in its UI on demand. This way each log statement preserves all associated properties and a template hash (in AI “MessageTemplate”) which allows the backend to apply advanced filtering or search for all logs of a given type.&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;LogWarning&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;The person {PersonId} could not be found.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;personId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The previous statement will log the following properties to Application Insights:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Message: &lt;code class=&quot;highlighter-rouge&quot;&gt;The person 5 could not be found.&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;MessageTemplate: &lt;code class=&quot;highlighter-rouge&quot;&gt;The person {PersonId} could not be found.&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;PersonId: &lt;code class=&quot;highlighter-rouge&quot;&gt;5&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Advantages of structured logs:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Properties are stored as seperate custom properties of the log entry and you can filter based on them in the logging backend&lt;/li&gt;
  &lt;li&gt;A &lt;a href=&quot;https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging#log-message-template&quot;&gt;message template&lt;/a&gt;/message hash is stored so that you can easily query for all logs of a given log statement type&lt;/li&gt;
  &lt;li&gt;Serialization of properties only happens if the log is actually written (not the case with string interpolation)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Disadvantages:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The order of the parameters in the C# log statement have to be correct and this cannot be statically checked by the compiler in the same way as it’s done in interpolated strings&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;always-pass-exception-as-first-parameter&quot;&gt;Always pass exception as first parameter&lt;/h3&gt;

&lt;p&gt;To log an exception, always pass the exception object as first argument:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;LogWarning&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;An exception occured&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Otherwise it is treated as custom property and if it has no placeholder in the message it will not end up in the log. Also the formatting and storage is exception specific if you use the correct method overload.&lt;/p&gt;

&lt;h3 id=&quot;always-use-placeholders-and-avoid-string-interpolation&quot;&gt;Always use placeholders and avoid string interpolation&lt;/h3&gt;

&lt;p&gt;To ensure that a message template is correctly logged and properties are transferred as custom properties, you always need to log properties with placeholders in the correct order, e.g.&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;LogWarning&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;The person {PersonId} could not be found.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;personId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Never use string interpolation because the replacement will be done in your application and the logging backend has no longer access to the message template and individual custom properties:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;LogWarning&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;$&quot;The person &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;personId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; could not be found.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Another downside of string interpolation is that objects are always serialized into text in your app even if the log is not actually written because there is a log level filter configured.&lt;/p&gt;

&lt;h3 id=&quot;do-not-use-dots-in-property-names&quot;&gt;Do not use dots in property names&lt;/h3&gt;

&lt;p&gt;Avoid using dots in placeholder property names (e.g &lt;code class=&quot;highlighter-rouge&quot;&gt;Device.ID&lt;/code&gt;) because some &lt;code class=&quot;highlighter-rouge&quot;&gt;ILogger&lt;/code&gt; implementations (e.g. Serilog) do not support this. The main reason for this is that some logging backends cannot handle these property names and thus Serilog has to implement the lowest common denominator.&lt;/p&gt;

&lt;h2 id=&quot;scopes&quot;&gt;Scopes&lt;/h2&gt;

&lt;h3 id=&quot;use-scopes-to-add-custom-properties-to-multiple-log-entries&quot;&gt;Use scopes to add custom properties to multiple log entries&lt;/h3&gt;

&lt;p&gt;Use scopes to transparently add custom properties to all logs in a given execution context.&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;BeginScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Dictionary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;PersonId&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;LogInformation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;LogInformation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;World&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now both log statements will be logged with an additional &lt;code class=&quot;highlighter-rouge&quot;&gt;PersonId&lt;/code&gt; property. Most scope implementations even work correctly in parallel code because they use async contexts.&lt;/p&gt;

&lt;p&gt;You should create scopes for logical contexts, regions or per unit of work; for example:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Per HTTP request&lt;/li&gt;
  &lt;li&gt;Per event queue message&lt;/li&gt;
  &lt;li&gt;Per database transaction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In each of these scopes you should set the following properties if available:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Correlation ID (use the &lt;a href=&quot;https://devcenter.heroic.com/articles/http-request-id&quot;&gt;X-Request-ID&lt;/a&gt; HTTP header to read it in HTTP request scopes)
    &lt;ul&gt;
      &lt;li&gt;Don’t forget to pass the correlation ID when calling other HTTP endpoints&lt;/li&gt;
      &lt;li&gt;In Application Insights the property should be called &lt;code class=&quot;highlighter-rouge&quot;&gt;operation_id&lt;/code&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Transaction ID&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;consider-adding-a-scope-identifier-to-filter-logs-by-scope&quot;&gt;Consider adding a scope identifier to filter logs by scope&lt;/h3&gt;

&lt;p&gt;In order to be able to show all logs of a given custom scope, I implemented a simple extension method which automatically adds a scope identifier and uses value tuples to specify properties for all log entries in the scope:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IDisposable&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;BeginNamedScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ILogger&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;params&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ValueTuple&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;properties&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dictionary&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;properties&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ToDictionary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Item1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Item2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dictionary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;.Scope&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Guid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;NewGuid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;BeginScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dictionary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This exention method can be used like this:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;foreach&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;messages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;BeginNamedScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Message&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Message.Id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Message.Length&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;LogInformation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;LogInformation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;World&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Both log entries will now have a &lt;code class=&quot;highlighter-rouge&quot;&gt;Message.Scope&lt;/code&gt; property with the same value: Using this property you are now able to show all logs of a given scope in your logging backend.&lt;/p&gt;

&lt;h3 id=&quot;use-scopes-to-add-additional-custom-properties-to-a-single-entry&quot;&gt;Use scopes to add additional custom properties to a single entry&lt;/h3&gt;

&lt;p&gt;If you want to add additional properties to a log statement which should not be part of the message template, use a “short lived” scope:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;BeginPropertyScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;UserId&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;CurrentUserId&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;LogTrace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;The message has been processed.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The used extension method to use value tuples looks like this:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IDisposable&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;BeginPropertyScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ILogger&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;params&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ValueTuple&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;properties&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dictionary&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;properties&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ToDictionary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Item1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Item2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;BeginScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dictionary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;organization&quot;&gt;Organization&lt;/h2&gt;

&lt;h3 id=&quot;use-a-list-of-conceptual-property-names&quot;&gt;Use a list of conceptual property names&lt;/h3&gt;

&lt;p&gt;You should build up a list of constant log entry property names (usually set with scopes) for your domain so that always the same name is used for them. For example always use &lt;code class=&quot;highlighter-rouge&quot;&gt;UserId&lt;/code&gt; instead of &lt;code class=&quot;highlighter-rouge&quot;&gt;Message.UserId&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;Request.UserId&lt;/code&gt; so that it’s easier to filter by user id over all log entries.&lt;/p&gt;

&lt;h2 id=&quot;log-levels&quot;&gt;Log levels&lt;/h2&gt;

&lt;p&gt;It is recommended to think about what &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel?view=aspnetcore-2.2&quot;&gt;available log level&lt;/a&gt; to use. With this differentiation you can automatically create alerts, reports and issues.&lt;/p&gt;

&lt;p&gt;For more information about when to use which log level, head over to the &lt;a href=&quot;https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-2.2#log-level&quot;&gt;official documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;use-correct-log-levels&quot;&gt;Use correct log levels&lt;/h3&gt;

&lt;p&gt;Here is a summary of the available .NET log levels and their meaning:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Trace/Verbose:&lt;/strong&gt; Logs that contain the most detailed messages. These messages may contain sensitive application data. These messages are disabled by default and should never be enabled in a production environment.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Debug:&lt;/strong&gt; Logs that are used for interactive investigation during development. These logs should primarily contain information useful for debugging and have no long-term value.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Information:&lt;/strong&gt; Logs that track the general flow of the application. These logs should have long-term value.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Warning:&lt;/strong&gt; Logs that highlight an abnormal or unexpected event in the application flow, but do not otherwise cause the application execution to stop.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Error:&lt;/strong&gt; Logs that highlight when the current flow of execution is stopped due to a failure. These should indicate a failure in the current activity, not an application-wide failure.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Critical:&lt;/strong&gt; Logs that describe an unrecoverable application or system crash, or a catastrophic failure that requires immediate attention.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;log-exceptions-with-full-details&quot;&gt;Log exceptions with full details&lt;/h3&gt;

&lt;p&gt;Exceptions should always be logged as exceptions (i.e. not only the exception message) so that the stacktrace and all other information is retained. Use the correct log level to distinguish crictial, erroneous and informational exceptions.&lt;/p&gt;

&lt;h2 id=&quot;logger-implementation&quot;&gt;Logger implementation&lt;/h2&gt;

&lt;h3 id=&quot;use-concrete-implementations-only-in-the-application-root&quot;&gt;Use concrete implementations only in the application root&lt;/h3&gt;

&lt;p&gt;Use concrete implementations (e.g. Serilog types, etc.) only in the root of the application (e.g. &lt;code class=&quot;highlighter-rouge&quot;&gt;Startup.cs&lt;/code&gt; of your ASP.NET Core app). All services and “logger consumers” should only use the &lt;code class=&quot;highlighter-rouge&quot;&gt;ILogger&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;ILogger&amp;lt;T&amp;gt;&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;ILoggerFactory&lt;/code&gt; interface via (constructor) dependency injection. This way everything except the application bootstrapper is independent of the actual logger implementation and changing the logging sink is a no-brainer.&lt;/p&gt;

&lt;h3 id=&quot;consider-using-serilog-for-a-stable-ilogger-implementation&quot;&gt;Consider using Serilog for a stable ILogger implementation&lt;/h3&gt;

&lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;ILogger&lt;/code&gt; (and &lt;code class=&quot;highlighter-rouge&quot;&gt;ILoggerFactory&lt;/code&gt;) are quite generic and open interfaces with a lot of room about how to actually implement them. To show the problem, let’s have a look at the &lt;code class=&quot;highlighter-rouge&quot;&gt;BeginScope&lt;/code&gt; method:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;IDisposable&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BeginScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TState&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As you can see, the state can be any object. The actual implementation has to deal with that somehow but each implementation might differ on how it handles them. If you pass a &lt;code class=&quot;highlighter-rouge&quot;&gt;Dictionary&amp;lt;string, object&amp;gt;&lt;/code&gt; to &lt;a href=&quot;https://serilog.net&quot;&gt;Serilog&lt;/a&gt;’s &lt;code class=&quot;highlighter-rouge&quot;&gt;ILogger&lt;/code&gt; implementation, then it adds all these key-value tuples as custom properties to all log statements in the scope.&lt;/p&gt;

&lt;p&gt;That is why I recommend to use Serilog as an intermedate layer/abstraction between &lt;code class=&quot;highlighter-rouge&quot;&gt;ILogger&lt;/code&gt; and the actual sink (e.g. Application Insights) and not directly use the Application Insights &lt;code class=&quot;highlighter-rouge&quot;&gt;ILogger&lt;/code&gt; implementation. The reason for that is that the AI implementation might not support all features (e.g. &lt;a href=&quot;https://github.com/Microsoft/ApplicationInsights-aspnetcore/issues/491&quot;&gt;scopes are not supported&lt;/a&gt;) and changing a sink will not change the behavior and feature set of the logger implementation.&lt;/p&gt;

&lt;p&gt;Sample code to use Serilog and &lt;code class=&quot;highlighter-rouge&quot;&gt;ILogger&lt;/code&gt; in an ASP.NET Core web app (&lt;code class=&quot;highlighter-rouge&quot;&gt;UseSerilog()&lt;/code&gt; can be found in &lt;a href=&quot;https://www.nuget.org/packages/Serilog.AspNetCore&quot;&gt;Serilog.AspNetCore&lt;/a&gt;):&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Program&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Logger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;LoggerConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WriteTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Seq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://localhost:5341&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WriteTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;nf&quot;&gt;CreateWebHostBuilder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;UseSerilog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IWebHostBuilder&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CreateWebHostBuilder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;WebHost&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateDefaultBuilder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UseStartup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Startup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Sample code to use Serilog and &lt;code class=&quot;highlighter-rouge&quot;&gt;ILogger&lt;/code&gt; in a console application (&lt;code class=&quot;highlighter-rouge&quot;&gt;AddSerilog()&lt;/code&gt; can be found in &lt;a href=&quot;https://www.nuget.org/packages/Serilog.Extensions.Logging&quot;&gt;Serilog.Extensions.Logging&lt;/a&gt;):&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Program&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Logger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;LoggerConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WriteTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Seq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://localhost:5341&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WriteTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;HostBuilder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ConfigureServices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hostContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AddHostedService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MyHostedService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;();&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ConfigureLogging&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hostContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AddSerilog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;setup-for-application-insights-without-dependency-injection&quot;&gt;Setup for Application Insights without dependency injection&lt;/h3&gt;

&lt;p&gt;Here you can see a simple setup code to initialize the Serilog logger with an Application Insights sink and then add this logger provider to the logger factory:&lt;/p&gt;

&lt;p&gt;Packages:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Microsoft.Extensions.Logging&lt;/li&gt;
  &lt;li&gt;Microsoft.Extensions.Logging.Abstractions&lt;/li&gt;
  &lt;li&gt;Serilog.Extensions.Logging&lt;/li&gt;
  &lt;li&gt;Serilog.Sinks.Seq&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;TelemetryConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Active&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;InstrumentationKey&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;instrumentationKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;serilogLogger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;LoggerConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MinimumLevel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Debug&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MinimumLevel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Override&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Microsoft&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LogEventLevel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Information&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Enrich&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;FromLogContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WriteTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ApplicationInsightsTraces&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TelemetryConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Active&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;loggerFactory&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ILoggerFactory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;LoggerFactory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;loggerFactory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AddSerilog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;serilogLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now you have a logger factory with which you can create new untyped loggers:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;loggerFactory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;… or typed loggers:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;loggerFactory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CreateLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MyService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In this case, the type is used to add the &lt;code class=&quot;highlighter-rouge&quot;&gt;SourceContext&lt;/code&gt; custom property in Application Insights for filtering.&lt;/p&gt;

&lt;h3 id=&quot;inject-ilogger-or-iloggert-avoid-injecting-iloggerfactory&quot;&gt;Inject ILogger or ILogger&amp;lt;T&amp;gt;, avoid injecting ILoggerFactory&lt;/h3&gt;

&lt;p&gt;Prefer injecting the &lt;code class=&quot;highlighter-rouge&quot;&gt;ILogger&lt;/code&gt; interface and if necessary the &lt;code class=&quot;highlighter-rouge&quot;&gt;ILogger&amp;lt;T&amp;gt;&lt;/code&gt; interface. Ideally your class’ constructor requests an &lt;code class=&quot;highlighter-rouge&quot;&gt;ILogger&lt;/code&gt; and the IoC framework creates an instance of an &lt;code class=&quot;highlighter-rouge&quot;&gt;ILogger&amp;lt;T&amp;gt;&lt;/code&gt; where &lt;code class=&quot;highlighter-rouge&quot;&gt;T&lt;/code&gt; is the requesting class.&lt;/p&gt;

&lt;p&gt;Avoid injecting &lt;code class=&quot;highlighter-rouge&quot;&gt;ILoggerFactory&lt;/code&gt; because it would violate the SRP (single responsibilty principle) if a service creates child instances (there are exceptions where you need to do this).&lt;/p&gt;

&lt;h2 id=&quot;libraries-and-logging&quot;&gt;Libraries and logging&lt;/h2&gt;

&lt;h3 id=&quot;try-to-avoid-logging-in-libraries&quot;&gt;Try to avoid logging in libraries&lt;/h3&gt;

&lt;p&gt;Most libraries should not use logging but use regular control flow like return values, callbacks, events or exceptions. Consider using &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.trace?view=netframework-4.8&quot;&gt;System.Diagnostics.Trace&lt;/a&gt; for debug logging.&lt;/p&gt;

&lt;h3 id=&quot;use-logging-via-dependency-injection-in-library-code&quot;&gt;Use logging via dependency injection in library code&lt;/h3&gt;

&lt;p&gt;.NET libraries which need to log, should only use the &lt;code class=&quot;highlighter-rouge&quot;&gt;ILogger&lt;/code&gt; interface to communicate with the logging infrastructure. They should use it via (constructor) injection. Avoid using a static property/singleton because it would force all callers to use the same logging infrastructure.&lt;/p&gt;

&lt;p&gt;Assume you have a library which has a &lt;code class=&quot;highlighter-rouge&quot;&gt;MyHttpClient&lt;/code&gt; class to communicate with a backend. The signature of its constructor should look like this:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyHttpClient&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ILogger&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;MyHttpClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ILogger&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;_logger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NullLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Instance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;only-use-logging-abstractions-in-libraries&quot;&gt;Only use logging abstractions in libraries&lt;/h3&gt;

&lt;p&gt;In libraries you should only use the described logging abstractions and no concrete types because they are set up in the application root. This all implies that a .NET library should only have a dependency to &lt;a href=&quot;https://www.nuget.org/packages/Microsoft.Extensions.Logging.Abstractions&quot;&gt;Microsoft.Extensions.Logging.Abstractions&lt;/a&gt; for logging.&lt;/p&gt;

&lt;p&gt;For more information about logging in libraries, I recommend reading &lt;a href=&quot;https://nblumhardt.com/2017/07/library-logging/&quot;&gt;Good citizenship - logging from .NET libraries&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;testing&quot;&gt;Testing&lt;/h2&gt;

&lt;h3 id=&quot;use-the-null-logger-to-ignore-logs-in-tests&quot;&gt;Use the null logger to ignore logs in tests&lt;/h3&gt;

&lt;p&gt;Use &lt;code class=&quot;highlighter-rouge&quot;&gt;NullLogger.Instance&lt;/code&gt; as &lt;a href=&quot;http://software-pattern.org/Null-Object&quot;&gt;null object&lt;/a&gt; in tests to avoid lots of null checks just for testing.&lt;/p&gt;

&lt;h3 id=&quot;use-seq-for-local-logging&quot;&gt;Use Seq for local logging&lt;/h3&gt;

&lt;p&gt;I recommend to use the &lt;a href=&quot;https://datalust.co/seq&quot;&gt;Seq&lt;/a&gt; as a local development logging backend. You can easily run it via Docker and write local development logs to it. This way you can better access and browse logs than with plain text files.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://datalust.co/img/screenshot-front-4.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Just start a local Seq image:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run -d --restart unless-stopped --name seq -e ACCEPT_EULA=Y -p 5341:80 datalust/seq:latest
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And add Seq as a Serilog logging sink:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;serilogLogger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;LoggerConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WriteTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Seq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://localhost:5341&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;loggerFactory&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ILoggerFactory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;LoggerFactory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;loggerFactory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AddSerilog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;serilogLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;loggerFactory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now you can browse to &lt;a href=&quot;http://localhost:5341&quot;&gt;http://localhost:5341&lt;/a&gt; to access the local logging UI.&lt;/p&gt;

&lt;h2 id=&quot;further-reading&quot;&gt;Further reading&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.stephencleary.com/2018/05/microsoft-extensions-logging-part-1-introduction.html&quot;&gt;Stephen Cleary - Microsoft.Extensions.Logging, Part 1: Introduction&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.stephencleary.com/2018/06/microsoft-extensions-logging-part-2-types.html&quot;&gt;Stephen Cleary - Microsoft.Extensions.Logging, Part 2: Types&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://andrewlock.net/exploring-dotnet-6-part-8-improving-logging-performance-with-source-generators/&quot;&gt;Improving logging performance with source generators&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>

			
				<category term="Software Engineering" />
			
			
				<category term="Best Practices" />
			
				<category term="Logging" />
			
				<category term=".NET" />
			
				<category term="Serilog" />
			
				<category term="Application Insights" />
			
				<category term="Seq" />
			
				<category term="C#" />
			

			<published>2019-05-06T00:00:00+00:00</published>
		</entry>
	
		<entry>
			<id>https://blog.rsuter.com/technology-summary-azure-blob-storage/</id>
			<title>Technology Summary: Azure Blob Storage</title>
			<link href="https://blog.rsuter.com/technology-summary-azure-blob-storage/" rel="alternate" type="text/html" title="Technology Summary: Azure Blob Storage" />
			<updated>2019-04-29T00:00:00+00:00</updated>

			
				
				<author>
					
						<name>Rico Suter</name>
					
					
					
						<uri>https://rsuter.com</uri>
					
				</author>
			
			<summary></summary>
			<content type="html" xml:base="https://blog.rsuter.com/technology-summary-azure-blob-storage/">&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Category:&lt;/strong&gt; Object storage (binary, unstructured)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Docker image:&lt;/strong&gt; Not available&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Local test replacement:&lt;/strong&gt; Local file system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&quot;https://azure.microsoft.com/services/storage/blobs/&quot;&gt;Microsoft Azure Blob Storage&lt;/a&gt; is used to store chunks of binary data to access from cloud services and applications. The technology is mainly used as replacement for a shared file system which is not available in the cloud.&lt;/p&gt;

&lt;p&gt;You can store static files that are frequently used by a website, such as images, CSS files, and PDF files or store uploaded files instead of putting them into a more expensive, structured storage.&lt;/p&gt;

&lt;h2 id=&quot;when-to-use&quot;&gt;When to use&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;When you need to store files but don’t have a shared file system (e.g. applications in the cloud)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;constraints&quot;&gt;Constraints&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Blob storage does not provide querying capability; a blob can only be accessed by its path/key/name (partial access is possible)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;concepts&quot;&gt;Concepts&lt;/h2&gt;

&lt;p&gt;The following list shows the fundamental concepts of the Blob Storage technology:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Storage account:&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;The Azure resource of a Blob Storage is called storage account, provides a namespace in Azure and is required to manage and access containers and blobs.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Container:&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;Each instance may have multiple blob containers. Containers are like root directories where Blobs can be stored.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Blob&lt;/strong&gt; (Binary Large Objects):
    &lt;ul&gt;
      &lt;li&gt;Binary data, e.g. file content&lt;/li&gt;
      &lt;li&gt;Each blob has a unique name within its container. This name may contain slashes so that the blobs can be displayed as if they were stored in an hierarchical directory structure.&lt;/li&gt;
      &lt;li&gt;There are &lt;a href=&quot;https://msdn.microsoft.com/library/azure/ee691964.aspx&quot;&gt;three Blob types&lt;/a&gt;:
        &lt;ul&gt;
          &lt;li&gt;&lt;strong&gt;Block Blobs:&lt;/strong&gt; Block blobs let you upload large blobs efficiently&lt;/li&gt;
          &lt;li&gt;&lt;strong&gt;Append Blobs:&lt;/strong&gt; An append blob is comprised of blocks and is optimized for append operations&lt;/li&gt;
          &lt;li&gt;&lt;strong&gt;Page Blobs:&lt;/strong&gt; Page blobs are a collection of 512-byte pages optimized for random read and write operations&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;security&quot;&gt;Security&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;authorization&lt;/strong&gt; level can be defined per container:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Private:&lt;/strong&gt; Only someone with the credentials for the storage account can access the container and the Blobs in it.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Blob:&lt;/strong&gt; The container is private but the Blobs are publicly accessible&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Container:&lt;/strong&gt; The container is public and the Blobs are public: If someone knows the container name, they can access all of the Blobs, and they can get a list and iterate through them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are three ways to access stored blobs:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Direct access:&lt;/strong&gt; If the blob is public, it can be accessed via its HTTP URL which is in the following form:
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  https://mystoragename.blob.core.windows.net/mycontainername/myblobname
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Pass-through to user&lt;/strong&gt; (front-end proxy service): A cloud application with full access to the blob storage loads the blob data and passes it through to an authorized client. In an ASP.NET MVC appliation, you create an authorized controller action which has the storage credentials needed to access the file, and have the application require authentication before letting the requestor download the file.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://azure.microsoft.com/en-us/documentation/articles/storage-dotnet-shared-access-signature-part-1/&quot;&gt;Access with an SAS token&lt;/a&gt;&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;A service authenticates a client as needed and then &lt;a href=&quot;https://azure.microsoft.com/en-us/documentation/articles/storage-dotnet-shared-access-signature-part-2/&quot;&gt;generates an SAS&lt;/a&gt; (Signed Access Signature) token in the backend and provides it to the client.&lt;/li&gt;
      &lt;li&gt;This token can have an expiry date and is added to the private blob URL so that it can be accessed directly.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;data-replication&quot;&gt;Data replication&lt;/h2&gt;

&lt;p&gt;To avoid data loss, you can choose between the following replication modes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Locally redundant storage (LRS):&lt;/strong&gt; This means three copies of your blobs are stored in a single facility in a single region&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Zone redundant storage (ZRS):&lt;/strong&gt; It replicates your data across 2 to 3 facilities, either within a single region or across two regions&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Geo-Redundant Storage (GRS):&lt;/strong&gt; This replicates your data three times in your chosen data center, and then replicates it three times in a secondary data center that is far away.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Read-Access Geo-Redundant Storage (RA-GRS):&lt;/strong&gt; This is geo-redundant storage plus the ability to read the data in the secondary data center.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;usage&quot;&gt;Usage&lt;/h2&gt;

&lt;p&gt;The common usage scenarios are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Access over an HTTP RESTful API&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Access using a client library&lt;/strong&gt;, for &lt;a href=&quot;https://azure.microsoft.com/en-us/documentation/articles/storage-dotnet-how-to-use-blobs/&quot;&gt;example in .NET&lt;/a&gt; (see sample below)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Access with an Azure storage management application&lt;/strong&gt; like &lt;a href=&quot;https://www.cerebrata.com/&quot;&gt;Cerulean&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The usage of the .NET client libary (&lt;a href=&quot;https://www.nuget.org/packages/Microsoft.Azure.Storage.Blob/&quot;&gt;Microsoft.Azure.Storage.Blob&lt;/a&gt;) is very simple:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;connectionString&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;myconnectionstring&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;containerName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;mycontainername&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blobName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;myblobname&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CloudStorageAccount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;TryParse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connectionString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;out&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;storageAccount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;CloudBlobClient&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blobClient&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;storageAccount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateCloudBlobClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;CloudBlobContainer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blobContainer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blobClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;GetContainerReference&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;containerName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Create the container if it does not exist yet&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blobContainer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ExistsAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blobContainer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Retrieve blob reference&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;CloudBlockBlob&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blob&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blobContainer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;GetBlockBlobReference&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;blobName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Read blob to a string&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;MemoryStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Here you can also directly stream into the &lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// HTTP response stream to avoid memory allocations: &lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// await blob.DownloadToStreamAsync(HttpContext.Response.Body);        &lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;DownloadToStreamAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; 
        
        &lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Position&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;streamReader&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;StreamReader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;streamReader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ReadToEndAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// TODO: Use text&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;pricing&quot;&gt;Pricing&lt;/h2&gt;

&lt;p&gt;The pricing is calculated from the following factors:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The amount of &lt;strong&gt;space&lt;/strong&gt; the blobs take up&lt;/li&gt;
  &lt;li&gt;The number of &lt;strong&gt;operations&lt;/strong&gt; performed&lt;/li&gt;
  &lt;li&gt;The amount of &lt;strong&gt;data transferred&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;The selected &lt;strong&gt;data redundancy&lt;/strong&gt; option&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&quot;https://azure.microsoft.com/pricing/details/storage/blobs/&quot;&gt;More information&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;alternatives&quot;&gt;Alternatives&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://aws.amazon.com/s3/&quot;&gt;Amazon S3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>

			
				<category term="Software Engineering" />
			
			
				<category term="Azure" />
			
				<category term="Blob Storage" />
			
				<category term="Technology Summary" />
			
				<category term="Cloud" />
			
				<category term="C#" />
			
				<category term=".NET" />
			

			<published>2019-04-29T00:00:00+00:00</published>
		</entry>
	
		<entry>
			<id>https://blog.rsuter.com/azure-devops-how-to-update-the-azure-function-default-host-key-in-a-powershell-task/</id>
			<title>Azure DevOps: How to update the Azure Function default Host Key in a PowerShell task</title>
			<link href="https://blog.rsuter.com/azure-devops-how-to-update-the-azure-function-default-host-key-in-a-powershell-task/" rel="alternate" type="text/html" title="Azure DevOps: How to update the Azure Function default Host Key in a PowerShell task" />
			<updated>2019-01-21T00:00:00+00:00</updated>

			
				
				<author>
					
						<name>Rico Suter</name>
					
					
					
						<uri>https://rsuter.com</uri>
					
				</author>
			
			<summary></summary>
			<content type="html" xml:base="https://blog.rsuter.com/azure-devops-how-to-update-the-azure-function-default-host-key-in-a-powershell-task/">&lt;p&gt;For a recent project I dynamically create new Azure Functions in an Azure DevOps release pipeline. One of the requirements was to automatically update each default Host Key to a given value so that it’s easier to access the newly created HTTP functions.&lt;/p&gt;

&lt;p&gt;Because there is no easy out-of-the-box API in Azure CLI or Azure PowerShell, I wanted to share the final solution here.&lt;/p&gt;

&lt;p&gt;To update the default Azure Function Host Key in an Azure PowerShell build/release task, just follow these steps:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Create a new “Azure PowerShell” task in your Azure DevOps build or release pipeline&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Choose an “Azure Subscription” which has privileges to access the resource&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Under “Azure PowerShell Version” use the “Latest installed version”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Use “Inline Script” and insert the following script:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$functionName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;my-azure-function&quot;&lt;/span&gt;;
&lt;span class=&quot;nv&quot;&gt;$resourceGroup&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;my-azure-function-resource-group&quot;&lt;/span&gt;;
&lt;span class=&quot;nv&quot;&gt;$functionHostKey&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;my-new-function-host-key&quot;&lt;/span&gt;;

&lt;span class=&quot;nv&quot;&gt;$publishingCredentials&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; Invoke-AzureRmResourceAction -ResourceGroupName &lt;span class=&quot;nv&quot;&gt;$resourceGroup&lt;/span&gt; -ResourceType &lt;span class=&quot;s2&quot;&gt;&quot;Microsoft.Web/sites/config&quot;&lt;/span&gt; -ResourceName &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$functionName&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/publishingcredentials&quot;&lt;/span&gt; -Action list -ApiVersion 2015-08-01 -Force
&lt;span class=&quot;nv&quot;&gt;$authorization&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Convert]::ToBase64String&lt;span class=&quot;o&quot;&gt;([&lt;/span&gt;Text.Encoding]::ASCII.GetBytes&lt;span class=&quot;o&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;{0}:{1}&quot;&lt;/span&gt; -f &lt;span class=&quot;nv&quot;&gt;$publishingCredentials&lt;/span&gt;.Properties.PublishingUserName, &lt;span class=&quot;nv&quot;&gt;$publishingCredentials&lt;/span&gt;.Properties.PublishingPassword&lt;span class=&quot;o&quot;&gt;)))&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$accessToken&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Invoke-RestMethod&lt;/span&gt; -Uri &lt;span class=&quot;s2&quot;&gt;&quot;https://&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$functionName&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.scm.azurewebsites.net/api/functions/admin/token&quot;&lt;/span&gt; -Headers @&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Authorization&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Basic {0}&quot;&lt;/span&gt; -f &lt;span class=&quot;nv&quot;&gt;$authorization&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)}&lt;/span&gt; -Method GET

&lt;span class=&quot;nv&quot;&gt;$data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; @&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;default&quot;&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$functionHostKey&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;ConvertTo-Json&lt;/span&gt;;

&lt;span class=&quot;nv&quot;&gt;$response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Invoke-RestMethod&lt;/span&gt; -Method PUT -Headers @&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;Authorization &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Bearer {0}&quot;&lt;/span&gt; -f &lt;span class=&quot;nv&quot;&gt;$accessToken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)}&lt;/span&gt; -ContentType &lt;span class=&quot;s2&quot;&gt;&quot;application/json&quot;&lt;/span&gt; -Uri &lt;span class=&quot;s2&quot;&gt;&quot;https://&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$functionName&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.azurewebsites.net/admin/host/keys/default&quot;&lt;/span&gt; -body &lt;span class=&quot;nv&quot;&gt;$data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Update the variables &lt;code class=&quot;highlighter-rouge&quot;&gt;$functionName&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;$resourceGroup&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;$functionHostKey&lt;/code&gt; to your liking - you can also use build variables, e.g. &lt;code class=&quot;highlighter-rouge&quot;&gt;$(Build.BuildNumber)&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;As you can see, this script retrieves the Kudu credentials from your App Service to retrieve an access token. With this access token it then calls the &lt;a href=&quot;https://github.com/Azure/azure-functions-host/wiki/Key-management-API&quot;&gt;Azure Function’s key management API&lt;/a&gt; to update the default host key.&lt;/p&gt;

</content>

			
				<category term="Software Engineering" />
			
			
				<category term="Azure" />
			
				<category term="Azure DevOps" />
			
				<category term="Azure Functions" />
			
				<category term="PowerShell" />
			

			<published>2019-01-21T00:00:00+00:00</published>
		</entry>
	
		<entry>
			<id>https://blog.rsuter.com/azure-devops-create-a-web-app-for-containers-ci-release-pipeline-for-an-asp-net-core-app/</id>
			<title>Azure DevOps: Create a Web App for Containers CI/Release pipeline for an ASP.NET Core app</title>
			<link href="https://blog.rsuter.com/azure-devops-create-a-web-app-for-containers-ci-release-pipeline-for-an-asp-net-core-app/" rel="alternate" type="text/html" title="Azure DevOps: Create a Web App for Containers CI/Release pipeline for an ASP.NET Core app" />
			<updated>2018-10-17T00:00:00+00:00</updated>

			
				
				<author>
					
						<name>Rico Suter</name>
					
					
					
						<uri>https://rsuter.com</uri>
					
				</author>
			
			<summary></summary>
			<content type="html" xml:base="https://blog.rsuter.com/azure-devops-create-a-web-app-for-containers-ci-release-pipeline-for-an-asp-net-core-app/">&lt;p&gt;In this tutorial we will:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Build an ASP.NET Core app and push the sources to a Git repository&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Setup the required Azure resources (Web App for Containers, etc.)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Setup an &lt;a href=&quot;https://azure.microsoft.com/services/devops/pipelines/&quot;&gt;Azure DevOps CI build&lt;/a&gt; which builds a Docker image and pushes it to a private &lt;a href=&quot;https://azure.microsoft.com/services/container-registry/&quot;&gt;Azure Container registry (ACR)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Step 4:&lt;/strong&gt; Create an Azure DevOps release pipeline which deploys the new Docker image to a Linux based &lt;a href=&quot;https://azure.microsoft.com/services/app-service/containers/&quot;&gt;Web App for Containers (App Service)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; The tutorial is using the name &lt;code class=&quot;highlighter-rouge&quot;&gt;DockerDemo&lt;/code&gt; for the demo project and Azure resources. It is recommended to use a new name for your sample app, because the name &lt;code class=&quot;highlighter-rouge&quot;&gt;DockerDemo&lt;/code&gt; is now in use and cannot be used for new Azure resources.&lt;/p&gt;

&lt;h2 id=&quot;step-1-create-an-aspnet-core-project&quot;&gt;Step 1: Create an ASP.NET Core project&lt;/h2&gt;

&lt;p&gt;In this step we will implement an ASP.NET Core sample app, add the Azure DevOps YAML build scripts and push everything to a Git repository. The sample app and build script can ge found on &lt;a href=&quot;https://github.com/RicoSuter/DockerDemo&quot;&gt;GitHub/RSuter/DockerDemo&lt;/a&gt;. You can skip this part of the tutorial and directly use this public repository.&lt;/p&gt;

&lt;p&gt;To manually create the app and build script, follow these steps:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Create a new directory for your new repository&lt;/li&gt;
  &lt;li&gt;Create a new ASP.NET Core project (with Linux Docker support) in the &lt;code class=&quot;highlighter-rouge&quot;&gt;src&lt;/code&gt; directory&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;In the root of the repository, add the &lt;a href=&quot;https://docs.microsoft.com/azure/devops/pipelines/yaml-schema&quot;&gt;YAML build script&lt;/a&gt; &lt;code class=&quot;highlighter-rouge&quot;&gt;azure-pipelines.yml&lt;/code&gt;:&lt;/p&gt;

    &lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;pool&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;vmImage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Ubuntu&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;16.04'&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;variables&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;imageName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;docker-demo:$(Build.BuildId)'&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;docker build -f DockerDemo/Dockerfile -t $(dockerId).azurecr.io/$(imageName) .&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;workingDirectory&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;src/&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;displayName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Docker&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Build'&lt;/span&gt;

&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;docker login -u $(dockerId) -p $pswd $(dockerId).azurecr.io&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;docker push $(dockerId).azurecr.io/$(imageName)&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;pswd&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$(dockerPassword)&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;workingDirectory&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;src/&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;displayName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Docker&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Push'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;This Azure DevOps YAML build script will build the Dockerfile in &lt;code class=&quot;highlighter-rouge&quot;&gt;src/DockerDemo&lt;/code&gt; and push it to the Container registry tagged with current build id. Because the CI build triggers a release with the same build id, the build pipeline is able to deploy this exact version. It is &lt;a href=&quot;https://blog.openshift.com/build-once-deploy-anywhere/&quot;&gt;common practice&lt;/a&gt; to build a docker image only once and deploy it to all the different environments.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Your file structure and solution should now look as follows:&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;WebAppOnContainers_03_Repository.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;WebAppOnContainers_04_Solution.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;Commit all your files and push the repository to your Azure DevOps project’s repository.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;step-2-create-the-azure-resources&quot;&gt;Step 2: Create the Azure Resources&lt;/h2&gt;

&lt;p&gt;In the second step, we will setup all required Azure resources: A Container registry, a Web App for Containers and its service plan:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Browse to the Azure Portal&lt;/li&gt;
  &lt;li&gt;Create a new resource group with the name &lt;code class=&quot;highlighter-rouge&quot;&gt;DockerDemo&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Create a new Azure Container Registry called &lt;code class=&quot;highlighter-rouge&quot;&gt;DockerDemoContainerRegistry&lt;/code&gt;
    &lt;ul&gt;
      &lt;li&gt;In the “Access keys” section, enable admin user so that a registry password is generated&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Create a new Web App for Containers (Linux) resource with the name &lt;code class=&quot;highlighter-rouge&quot;&gt;DockerDemoAppService&lt;/code&gt; and a new App Service Plan called &lt;code class=&quot;highlighter-rouge&quot;&gt;DockerDemoAppServicePlan&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After these steps, you should end up with a resource group with the following resources:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;WebAppOnContainers_01_ResourceGroup.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;step-3-create-the-build-definition&quot;&gt;Step 3: Create the build definition&lt;/h2&gt;

&lt;p&gt;Now, we will setup the build definition in Azure DevOps:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Browse to your Azure DevOps project, navigate to Pipelines/Build and create a new build pipeline&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Select your repository: The UI should automatically select your &lt;code class=&quot;highlighter-rouge&quot;&gt;azure-pipelines.yml&lt;/code&gt; file:&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;WebAppOnContainers_02_CreateCi.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;After creating the build definition, add the following build variables so that the script can successfully run:&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;dockerId&lt;/code&gt;: The name of the “Container registry” (without azurecr.io, e.g. &lt;code class=&quot;highlighter-rouge&quot;&gt;dockerdemocontainerregistry&lt;/code&gt;)&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;dockerPassword&lt;/code&gt;: The registry password which can be found in the Azure Portal by opening the “Container registry” resource and retrieving the password under “Access keys”&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Now, you can run a build against your repository and the Container registry should be populated with a new image called &lt;code class=&quot;highlighter-rouge&quot;&gt;docker-demo&lt;/code&gt; with the tag set to the current build id:&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;WebAppOnContainers_06_RegistryWithImage.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;step-4-create-the-release-pipeline&quot;&gt;Step 4: Create the release pipeline&lt;/h2&gt;

&lt;p&gt;In this step, we will create a new release pipeline which is triggered by a successful build.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limitation:&lt;/strong&gt; At the moment, the Azure App Service task only supports deploying a single Docker image to the App Service. I think more tasks to deploy “image groups” via Docker Compose or Kubernetes will be supported soon.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Create a new release pipeline&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Select the artifact from your build definition and enable the continuous deployment trigger:&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;WebAppOnContainers_07_DeploymentTrigger.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Create a new “Azure App Service deployment”&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;WebAppOnContainers_08_AddDeployment.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Click on the first stage and create a new “Azure App Service deployment”&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Prepare the newly created stage/environment:&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;Select a subscription and the app service name&lt;/li&gt;
      &lt;li&gt;Set the “App type” to “Linux app”&lt;/li&gt;
      &lt;li&gt;In “Image Source” select “Container Registry”&lt;/li&gt;
      &lt;li&gt;Enter the registry address and select the image &lt;code class=&quot;highlighter-rouge&quot;&gt;docker-demo&lt;/code&gt;&lt;/li&gt;
    &lt;/ul&gt;

    &lt;p&gt;&lt;img src=&quot;WebAppOnContainers_05_CreateRelease.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Edit the “Deploy Azure App Service” task:&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;Use &lt;code class=&quot;highlighter-rouge&quot;&gt;$(Build.BuildId)&lt;/code&gt; as “Tag” so that the correct Docker image version from the associated CI build is deployed&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, if you commit a change to your Git repository, a new CI build is triggered, a new image version is pushed and eventually a new release is created. This release will update the used image tag in the App Service which will trigger the actual image deployment.&lt;/p&gt;

</content>

			
				<category term="Software Engineering" />
			
			
				<category term="App Service" />
			
				<category term="ASP.NET Core" />
			
				<category term="Azure" />
			
				<category term="Container" />
			
				<category term="Azure DevOps" />
			
				<category term="Docker" />
			
				<category term="Microservices" />
			
				<category term="Web App" />
			

			<published>2018-10-17T00:00:00+00:00</published>
		</entry>
	
</feed>