<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Alexander Zeitler</title>
  <subtitle></subtitle>
  <link href="https://alexanderzeitler.com/feed.xml" rel="self"/>
  <link href="https://alexanderzeitler.com/"/>
  <updated>2026-04-03T18:00:00Z</updated>
  <id>https://alexanderzeitler.com/</id>
  <author>
    <name>Alexander Zeitler</name>
    <email>alexander.zeitler@pdmlab.com</email>
  </author>
  <icon>https://alexanderzeitler.com/favicon.svg</icon>
  
  <entry>
    <title>Farewell...</title>
    <link href="https://alexanderzeitler.com/articles/Farewell/"/>
    <updated>2015-01-17T22:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/Farewell/</id>
    <content type="html">&lt;p&gt;The title of this post might sound a bit scenic, but 2014 indeed has been a year of goodbys for me - at least regarding software development.
As almost every leave also is a fresh start, lots of things have changed.&lt;/p&gt;
&lt;p&gt;So lets say farewell to...&lt;/p&gt;
&lt;h3&gt;... .NET only software development&lt;/h3&gt;
&lt;p&gt;While contributing to &amp;quot;Pro ASP.NET Web API&amp;quot;, doing talks and projects on ASP.NET Web API in 2012 and 2013 by persuasion, 2014 has become the year where I decided to gain broader knowledge by practice in the non-Microsoft space.&lt;/p&gt;
&lt;p&gt;This means digging into Linux, Node.js and MongoDb and all that things like Gulp, Yeoman, Docker and ...you name it.&lt;/p&gt;
&lt;p&gt;I also nosed around Scala, Go and Erlang, but in the end, I stick with Node.js and it&#39;s friends.&lt;/p&gt;
&lt;p&gt;Things that felt bumpy (especcially because of Windows, e.g. CMD vs bash, Docker vs. ...?) in the .NET world, worked like a charm on the new stack and allowed me to purge my whole software development and deployment process.&lt;/p&gt;
&lt;p&gt;Also the huge open source momentum in the Node.js ecosystem deeply impressed me.&lt;/p&gt;
&lt;p&gt;On the other hand, in 2014, the .NET stack also has moved forward and Node.js not always is the promised land.
The biggest gain for ages has been open sourcing the .NET framework and making it a real cross platform development environment.&lt;/p&gt;
&lt;p&gt;As a .NET developer since the early days, I surely won&#39;t leave the .NET ecosystem in the near future, but there may be requirements where I or my customers will prefer the Node.js environment in the future.&lt;/p&gt;
&lt;h3&gt;... blog.alexonasp.net&lt;/h3&gt;
&lt;p&gt;When you&#39;re reading these lines, my old blog &amp;quot;Alex on ASP.NET&amp;quot; which has been around since 2003, is gone.
As aforementioned, I&#39;m no longer developing solely in the .NET space and thus my blog needs to be as open as I am.&lt;/p&gt;
&lt;h3&gt;... BlogEngine.NET&lt;/h3&gt;
&lt;p&gt;&amp;quot;Alex on ASP.NET&amp;quot; in it&#39;s early days has been running on &amp;quot;dasBlog&amp;quot; which has been replaced by SubText later on. Since 2006 it has been running on BlogEngine.NET.&lt;/p&gt;
&lt;p&gt;As blog posts after being published, almost never are modified, running a database backed blog engine doesn&#39;t make sence to me any longer.&lt;/p&gt;
&lt;p&gt;Because of this, I migrated my old blog to Wintersmith, a static site generator based on Node.js.
It is now hosted on the DigitalOcean cloud platform and deployment is done using git.&lt;/p&gt;
&lt;p&gt;I&#39;ll cover this in a upcoming post for everyone who&#39;s interested in that topic.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>MongoDB development environment journal size management using mongoctl</title>
    <link href="https://alexanderzeitler.com/articles/MongoDb-development-environment-and-journalsize-management-using-mongoctl/"/>
    <updated>2015-01-26T21:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/MongoDb-development-environment-and-journalsize-management-using-mongoctl/</id>
    <content type="html">&lt;p&gt;On a developer machine you might have to install multiple versions of MongoDB or you might want to run multiple MongoDB server instances.
Both can be done easily using &lt;a href=&quot;https://github.com/mongolab/mongoctl&quot;&gt;mongoctl&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To install mongoctl on Ubuntu, just run:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; pip install mongoctl&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In case you don&#39;t have installed pip, just run this before the above command:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;git &lt;span class=&quot;hljs-built_in&quot;&gt;clone&lt;/span&gt; https://github.com/pypa/pip.git&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;cd&lt;/span&gt; pip&lt;br /&gt;python setup.py install &lt;span class=&quot;hljs-comment&quot;&gt;#might need sudo / root&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To install the latest stable MongoDB release just run:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;mongoctl install-mongodb&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Beside installing MongoDB this also creates the config file for your first MongoDB server name &amp;quot;MyServer&amp;quot;.
You can simply start it using&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;mongoctl start MyServer&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Stopping is as easy as starting it:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;mongoctl stop MyServer&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By default, &lt;a href=&quot;http://docs.mongodb.org/manual/core/journaling/&quot;&gt;journaling&lt;/a&gt; is enabled for MongoDB and may eat your hard disk if you&#39;re running many MongoDB servers.
Having installed MongoDB using mongoctl, the journal of &amp;quot;MyServer&amp;quot; can be found here:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;~/mongodb-data/my-server/journal&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Listing the directory content shows us, that our instance uses 3GB of hard disk space.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;drwxrwxr-x  2 alexzeitler alexzeitler 4,0K Jan 25 00:09 .&lt;br /&gt;drwxrwxr-x 10 alexzeitler alexzeitler 4,0K Jan 24 23:15 ..&lt;br /&gt;-rw-------  1 alexzeitler alexzeitler 1,0G Jan 25 00:09 prealloc.0&lt;br /&gt;-rw-------  1 alexzeitler alexzeitler 1,0G Nov 25 13:04 prealloc.1&lt;br /&gt;-rw-------  1 alexzeitler alexzeitler 1,0G Nov 25 13:04 prealloc.2&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While (until the 2.0 release) disabling the journal is a bad idea for production, it is ok for a development environment.&lt;/p&gt;
&lt;p&gt;To disable the journaling for &amp;quot;MyServer&amp;quot;, head over to &lt;code&gt;~/.mongoctl&lt;/code&gt; and edit the &lt;code&gt;servers.config&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;No find the &amp;quot;MyServer&amp;quot; configuration, which should be the first entry and look like this:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;_id&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;MyServer&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;description&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;My server (single mongod)&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;cmdOptions&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;port&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;27017&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;dbpath&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;~/mongodb-data/my-server&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;directoryperdb&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;true&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In order to disable MongoDB journaling, add the &lt;code&gt;nojournal&lt;/code&gt; property to &lt;code&gt;cmdOptions&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;_id&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;MyServer&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;description&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;My server (single mongod)&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;cmdOptions&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;port&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;27017&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;dbpath&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;~/mongodb-data/my-server&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;directoryperdb&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;true&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;nojournal&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;true&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No, stop your &amp;quot;MyServer&amp;quot; instance and delete the files from the &lt;code&gt;~/mongodb-data/my-server/journal&lt;/code&gt; (you&#39;re doing this at your own risk, of course - and as said not in a production environment!) directory.&lt;/p&gt;
&lt;p&gt;After starting &amp;quot;MyServer&amp;quot; again, the journal folder should stay empty. That&#39;s it!&lt;/p&gt;
&lt;p&gt;You can also limit the journal file size to 128MB by setting the &lt;code&gt;smallfiles&lt;/code&gt; property to &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>mongoose: Referencing schema in properties or arrays</title>
    <link href="https://alexanderzeitler.com/articles/mongoose-referencing-schema-in-properties-and-arrays/"/>
    <updated>2015-02-01T01:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/mongoose-referencing-schema-in-properties-and-arrays/</id>
    <content type="html">&lt;p&gt;When using a NoSQL database like MongoDb, most of the time you&#39;ll have documents that contain all properties by itself.
But there are also scenarios where you might encounter the need for a more relational approach and need to reference other documents by the ObjectIds.&lt;/p&gt;
&lt;p&gt;This post will show you how to deal with these references using Node.js and the &lt;a href=&quot;http://mongoosejs.com/&quot;&gt;mongoose ODM&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Lets consider we&#39;ll have a users collection and a posts collection, thus we&#39;ll have a &lt;code&gt;UserSchema&lt;/code&gt; as well as a &lt;code&gt;PostSchema&lt;/code&gt;.
Posts can be written by users and the can by commented by users.&lt;/p&gt;
&lt;p&gt;In this example, well reference the users in posts and comments by their ObjectId reference.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;UserSchema&lt;/code&gt; is implemented straight forward and looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; mongoose = &lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;mongoose&amp;#x27;&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;UserSchema&lt;/span&gt; = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; mongoose.&lt;span class=&quot;hljs-title class_&quot;&gt;Schema&lt;/span&gt;({&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;name&lt;/span&gt;: &lt;span class=&quot;hljs-title class_&quot;&gt;String&lt;/span&gt;&lt;br /&gt;});&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-variable language_&quot;&gt;module&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;exports&lt;/span&gt; = mongoose.&lt;span class=&quot;hljs-title function_&quot;&gt;model&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;User&amp;quot;&lt;/span&gt;, &lt;span class=&quot;hljs-title class_&quot;&gt;UserSchema&lt;/span&gt;);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Beside the title property, the &lt;code&gt;PostSchema&lt;/code&gt; also defines the reference by ObjectId for the &lt;code&gt;postedBy&lt;/code&gt; property of the PostSchema as well as the &lt;code&gt;postedBy&lt;/code&gt; property of the comments inside the &lt;code&gt;comments&lt;/code&gt; array property:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; mongoose = &lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;mongoose&amp;#x27;&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;PostSchema&lt;/span&gt; = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; mongoose.&lt;span class=&quot;hljs-title class_&quot;&gt;Schema&lt;/span&gt;({&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;title&lt;/span&gt;: &lt;span class=&quot;hljs-title class_&quot;&gt;String&lt;/span&gt;,&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;postedBy&lt;/span&gt;: {&lt;br /&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;type&lt;/span&gt;: mongoose.&lt;span class=&quot;hljs-property&quot;&gt;Schema&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;Types&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;ObjectId&lt;/span&gt;,&lt;br /&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;ref&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;User&amp;#x27;&lt;/span&gt;&lt;br /&gt;    },&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;comments&lt;/span&gt;: [{&lt;br /&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;text&lt;/span&gt;: &lt;span class=&quot;hljs-title class_&quot;&gt;String&lt;/span&gt;,&lt;br /&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;postedBy&lt;/span&gt;: {&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;type&lt;/span&gt;: mongoose.&lt;span class=&quot;hljs-property&quot;&gt;Schema&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;Types&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;ObjectId&lt;/span&gt;,&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;ref&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;User&amp;#x27;&lt;/span&gt;&lt;br /&gt;        }&lt;br /&gt;    }]&lt;br /&gt;});&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-variable language_&quot;&gt;module&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;exports&lt;/span&gt; = mongoose.&lt;span class=&quot;hljs-title function_&quot;&gt;model&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Post&amp;quot;&lt;/span&gt;, &lt;span class=&quot;hljs-title class_&quot;&gt;PostSchema&lt;/span&gt;);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now lets create two users:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;./database&amp;quot;&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;User&lt;/span&gt; = &lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;./User&amp;#x27;&lt;/span&gt;),&lt;br /&gt;    &lt;span class=&quot;hljs-title class_&quot;&gt;Post&lt;/span&gt; = &lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;./Post&amp;#x27;&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; alex = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;User&lt;/span&gt;({&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;name&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Alex&amp;quot;&lt;/span&gt;&lt;br /&gt;});&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; joe = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;User&lt;/span&gt;({&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;name&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Joe&amp;quot;&lt;/span&gt;&lt;br /&gt;})&lt;br /&gt;&lt;br /&gt;alex.&lt;span class=&quot;hljs-title function_&quot;&gt;save&lt;/span&gt;();&lt;br /&gt;joe.&lt;span class=&quot;hljs-title function_&quot;&gt;save&lt;/span&gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The interesting part of course is the creation and even more the query for posts.
The post is created with the ObjectId references to the users.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; post = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Post&lt;/span&gt;({&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;title&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Hello World&amp;quot;&lt;/span&gt;,&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;postedBy&lt;/span&gt;: alex.&lt;span class=&quot;hljs-property&quot;&gt;_id&lt;/span&gt;,&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;comments&lt;/span&gt;: [{&lt;br /&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;text&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Nice post!&amp;quot;&lt;/span&gt;,&lt;br /&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;postedBy&lt;/span&gt;: joe.&lt;span class=&quot;hljs-property&quot;&gt;_id&lt;/span&gt;&lt;br /&gt;    }, {&lt;br /&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;text&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Thanks :)&amp;quot;&lt;/span&gt;,&lt;br /&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;postedBy&lt;/span&gt;: alex.&lt;span class=&quot;hljs-property&quot;&gt;_id&lt;/span&gt;&lt;br /&gt;    }]&lt;br /&gt;})&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now lets save the Post and after it got created, query for all existing Posts.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;post.&lt;span class=&quot;hljs-title function_&quot;&gt;save&lt;/span&gt;(&lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;error&lt;/span&gt;) {&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (!error) {&lt;br /&gt;        &lt;span class=&quot;hljs-title class_&quot;&gt;Post&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;find&lt;/span&gt;({})&lt;br /&gt;            .&lt;span class=&quot;hljs-title function_&quot;&gt;populate&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;postedBy&amp;#x27;&lt;/span&gt;)&lt;br /&gt;            .&lt;span class=&quot;hljs-title function_&quot;&gt;populate&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;comments.postedBy&amp;#x27;&lt;/span&gt;)&lt;br /&gt;            .&lt;span class=&quot;hljs-title function_&quot;&gt;exec&lt;/span&gt;(&lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;error, posts&lt;/span&gt;) {&lt;br /&gt;                &lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;hljs-title class_&quot;&gt;JSON&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;stringify&lt;/span&gt;(posts, &lt;span class=&quot;hljs-literal&quot;&gt;null&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&#92;t&amp;quot;&lt;/span&gt;))&lt;br /&gt;            })&lt;br /&gt;    }&lt;br /&gt;});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, the we&#39;re using the &lt;a href=&quot;http://mongoosejs.com/docs/populate.html&quot;&gt;&lt;code&gt;populate&lt;/code&gt;&lt;/a&gt; function of mongoose to join the documents when querying for Posts.
The first call to &lt;code&gt;populate&lt;/code&gt; joins the Users for the &lt;code&gt;postedBy&lt;/code&gt; property of the posts whereas the second one joins the Users for the comments.&lt;/p&gt;
&lt;p&gt;The Post document in the database looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;_id&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; ObjectId(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;54cd6669d3e0fb1b302e54e6&amp;quot;&lt;/span&gt;)&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;title&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Hello World&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;postedBy&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; ObjectId(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;54cd6669d3e0fb1b302e54e4&amp;quot;&lt;/span&gt;)&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;comments&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;text&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Nice post!&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;postedBy&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; ObjectId(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;54cd6669d3e0fb1b302e54e5&amp;quot;&lt;/span&gt;)&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;_id&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; ObjectId(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;54cd6669d3e0fb1b302e54e8&amp;quot;&lt;/span&gt;)&lt;br /&gt;        &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;text&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Thanks :)&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;postedBy&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; ObjectId(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;54cd6669d3e0fb1b302e54e4&amp;quot;&lt;/span&gt;)&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;_id&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; ObjectId(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;54cd6669d3e0fb1b302e54e7&amp;quot;&lt;/span&gt;)&lt;br /&gt;        &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;__v&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In contrast, the query result is a full document containing all User references for the Posts.&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;_id&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;54cd6669d3e0fb1b302e54e6&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Hello World&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;postedBy&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;_id&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;54cd6669d3e0fb1b302e54e4&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Alex&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;__v&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;__v&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;comments&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;				&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Nice post!&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;				&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;postedBy&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;					&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;_id&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;54cd6669d3e0fb1b302e54e5&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;					&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Joe&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;					&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;__v&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;&lt;br /&gt;				&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;				&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;_id&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;54cd6669d3e0fb1b302e54e8&amp;quot;&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;				&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Thanks :)&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;				&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;postedBy&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;					&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;_id&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;54cd6669d3e0fb1b302e54e4&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;					&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Alex&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;					&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;__v&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;&lt;br /&gt;				&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;				&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;_id&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;54cd6669d3e0fb1b302e54e7&amp;quot;&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can find the source code for this sample in &lt;a href=&quot;https://github.com/AlexZeitler/mongoose-schema-reference-sample&quot;&gt;this&lt;/a&gt; GitHub repository.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>#NodeJS / #ExpressJS: Adding routes dynamically at runtime</title>
    <link href="https://alexanderzeitler.com/articles/expressjs-dynamic-runtime-routing/"/>
    <updated>2015-02-02T01:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/expressjs-dynamic-runtime-routing/</id>
    <content type="html">&lt;p&gt;When running a ExpressJS based API or Website, you may want to add new routes without restarting your ExpressJS host.&lt;/p&gt;
&lt;p&gt;Actually, adding routes to ExpressJS at runtime is pretty simple.&lt;/p&gt;
&lt;p&gt;First, lets create some &amp;quot;design time&amp;quot; routes for an API endpoint as well as some static content to be served by our ExpressJS application:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; express = &lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;express&amp;#x27;&lt;/span&gt;);&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; app = &lt;span class=&quot;hljs-title function_&quot;&gt;express&lt;/span&gt;();&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// development time route&lt;/span&gt;&lt;br /&gt;app.&lt;span class=&quot;hljs-title function_&quot;&gt;get&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;/api/hello&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;request,response&lt;/span&gt;) {&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; response.&lt;span class=&quot;hljs-title function_&quot;&gt;status&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;200&lt;/span&gt;).&lt;span class=&quot;hljs-title function_&quot;&gt;send&lt;/span&gt;({&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;hello&amp;quot;&lt;/span&gt;:&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;world&amp;quot;&lt;/span&gt;})&lt;br /&gt;});&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// static file handling&lt;/span&gt;&lt;br /&gt;app.&lt;span class=&quot;hljs-title function_&quot;&gt;use&lt;/span&gt;(express.&lt;span class=&quot;hljs-title function_&quot;&gt;static&lt;/span&gt;(__dirname + &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;/client/app&amp;#x27;&lt;/span&gt;));&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;app.&lt;span class=&quot;hljs-title function_&quot;&gt;listen&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;3000&lt;/span&gt;);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A call to &lt;code&gt;http://localhost:3000/api/hello&lt;/code&gt; will return this:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;hello&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;world&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the call to &lt;code&gt;http://localhost:3000&lt;/code&gt; will render us some HTML:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;&amp;lt;!DOCTYPE &lt;span class=&quot;hljs-keyword&quot;&gt;html&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;html&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;head&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;lang&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;en&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;meta&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;charset&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;UTF-8&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;title&lt;/span&gt;&amp;gt;&lt;/span&gt;Hello World!&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;title&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;head&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;body&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;h1&lt;/span&gt;&amp;gt;&lt;/span&gt;Hello World&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;h1&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;body&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;html&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nothing special here, move along :)&lt;/p&gt;
&lt;p&gt;Now, lets assume we&#39;re dropping a new ExpressJS controller into our &lt;code&gt;./controllers&lt;/code&gt; folder at runtime:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;hljs-variable language_&quot;&gt;module&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;exports&lt;/span&gt;= {&lt;br /&gt;    init : init&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;hljs-title function_&quot;&gt;init&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;app&lt;/span&gt;){&lt;br /&gt;    app.&lt;span class=&quot;hljs-title function_&quot;&gt;get&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;/api/myruntimeroute&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;req,res&lt;/span&gt;) {&lt;br /&gt;        res.&lt;span class=&quot;hljs-title function_&quot;&gt;send&lt;/span&gt;({&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;runtime&amp;quot;&lt;/span&gt; : &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;route&amp;quot;&lt;/span&gt;});&lt;br /&gt;    })&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When calling it&#39;s route &lt;code&gt;http://localhost:3000/api/myruntimeroute&lt;/code&gt; we&#39;ll get a 404. Sad panda.&lt;/p&gt;
&lt;p&gt;Now lets drop this piece of code into our app.js before the &lt;code&gt;app.listen(3000)&lt;/code&gt; line (this needs to be added before the host is started, of course):&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// hook to initialize the dynamic route at runtime&lt;/span&gt;&lt;br /&gt;app.&lt;span class=&quot;hljs-title function_&quot;&gt;post&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;/api/dynamic&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;req,res&lt;/span&gt;) {&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; dynamicController = &lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;./controllers/RuntimeController&amp;#x27;&lt;/span&gt;);&lt;br /&gt;    dynamicController.&lt;span class=&quot;hljs-title function_&quot;&gt;init&lt;/span&gt;(app);&lt;br /&gt;    res.&lt;span class=&quot;hljs-title function_&quot;&gt;status&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;200&lt;/span&gt;).&lt;span class=&quot;hljs-title function_&quot;&gt;send&lt;/span&gt;();&lt;br /&gt;});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When sending a POST (&lt;code&gt;curl -X POST http://localhost:3000/api/dynamic&lt;/code&gt;) to that endpoint, our controller dropped in at runtime, now gets initalizied and can register it&#39;s routes.&lt;/p&gt;
&lt;p&gt;Calling  &lt;code&gt;http://localhost:3000/api/myruntimeroute&lt;/code&gt; now GETs us some fancy JSON:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;runtime&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;route&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looking back to the hookup code, you can see that the name of the controller is already known which of course is just for the sake of simplicity of this sample.&lt;/p&gt;
&lt;p&gt;In a real world implementation you might have some other hook up like a user action in your administration which looks for new controllers in a specific folder. Or you might monitor a specfic folder for new controller files. You get the point.&lt;/p&gt;
&lt;p&gt;Another approach for dynamic runtime routing could be to have catch some/all route definition which hooks in when all other routes fail.&lt;/p&gt;
&lt;p&gt;A small example:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;app.&lt;span class=&quot;hljs-title function_&quot;&gt;get&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;/api/:dynamicroute&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;req,res&lt;/span&gt;) {&lt;br /&gt;    res.&lt;span class=&quot;hljs-title function_&quot;&gt;send&lt;/span&gt;({&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;param&amp;quot;&lt;/span&gt; : req.&lt;span class=&quot;hljs-property&quot;&gt;params&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;dynamicroute&lt;/span&gt;});&lt;br /&gt;});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this snippet, the part behind the &lt;code&gt;/api/&lt;/code&gt; path is dynamic which means you can have some logic which distributes the requests based on decisions made at runtime.&lt;/p&gt;
&lt;p&gt;You can find the source code for this sample in &lt;a href=&quot;https://github.com/AlexZeitler/expressjs-dynamic-runtime-routing&quot;&gt;this&lt;/a&gt; GitHub repository.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Stop complaining or improve yourself</title>
    <link href="https://alexanderzeitler.com/articles/stop-complaining-or-improve-yourself/"/>
    <updated>2015-02-14T14:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/stop-complaining-or-improve-yourself/</id>
    <content type="html">&lt;p&gt;Working in the IT sector never has offered more choices than nowadays.
You can pick from a wide range of hardware platforms supporting an even broader range of operating systems. On top of it, you can pick from a myriad of development platforms and languages.&lt;/p&gt;
&lt;div class=&quot;more&quot;&gt;&lt;/div&gt;
&lt;p&gt;On the other hand, more and more people working in the IT sector (I&#39;ll focus on software development mostly in this rant) start complaining about so much things in their daily use with their hardware, OS or their development environment / programming language (yes, I also come through this hollow alley). If you start complaining publicly on twitter or Facebook et al. you can even get retweets and likes if you show some creativity when doing it.&lt;/p&gt;
&lt;p&gt;Let&#39;s face the sad truth: it won&#39;t change anything until you start acting.&lt;/p&gt;
&lt;p&gt;If you don&#39;t like&lt;sup&gt;&lt;a href=&quot;https://alexanderzeitler.com/articles/stop-complaining-or-improve-yourself/#dontlike&quot;&gt;(*)&lt;/a&gt;&lt;/sup&gt; JavaScript, stop complaining and try ES6. If you still don&#39;t like&lt;sup&gt;&lt;a href=&quot;https://alexanderzeitler.com/articles/stop-complaining-or-improve-yourself/#dontlike&quot;&gt;(*)&lt;/a&gt;&lt;/sup&gt; it: ditch it and try something different. Maybe you should focus on HTML5 and CSS3 only. If you don&#39;t like&lt;sup&gt;&lt;a href=&quot;https://alexanderzeitler.com/articles/stop-complaining-or-improve-yourself/#dontlike&quot;&gt;(*)&lt;/a&gt;&lt;/sup&gt; that either, stop doing web development as a whole. You&#39;re not forced to do web development at all.&lt;/p&gt;
&lt;p&gt;If you don&#39;t like&lt;sup&gt;&lt;a href=&quot;https://alexanderzeitler.com/articles/stop-complaining-or-improve-yourself/#dontlike&quot;&gt;(*)&lt;/a&gt;&lt;/sup&gt; WPF (like me), don&#39;t do it, maybe AngularJS might be your frontend framework of choice.
But if you&#39;re doing the switch, please don&#39;t start complaining about it again. If you don&#39;t like&lt;sup&gt;&lt;a href=&quot;https://alexanderzeitler.com/articles/stop-complaining-or-improve-yourself/#dontlike&quot;&gt;(*)&lt;/a&gt;&lt;/sup&gt; it&#39;s behavior, try ReactJS or start contributing to AngularJS to improve it.&lt;/p&gt;
&lt;p&gt;If you think running JavaScript on the server is plain wrong, simply don&#39;t use Node.js/io.js.&lt;/p&gt;
&lt;p&gt;If giving up referential integrity is blasphemy for you, don&#39;t use (most of the) NoSQL databases.&lt;/p&gt;
&lt;p&gt;If you don&#39;t like&lt;sup&gt;&lt;a href=&quot;https://alexanderzeitler.com/articles/stop-complaining-or-improve-yourself/#dontlike&quot;&gt;(*)&lt;/a&gt;&lt;/sup&gt; Windows (any longer - like me), try Linux, OS X or build your own operating system.
You may find it hard to change things, but it&#39;s up to you to understand and learn things like bash scripting, VIM and all that stuff. If you start experiencing doing better is hard, you might understand why existing things are as they are.&lt;/p&gt;
&lt;p&gt;It&#39;s your choice to improve yourself or stick with your habbits.
But if you stick with them, please do me a favour and stop complaining about them - they have been your own choice.&lt;/p&gt;
&lt;p&gt;&lt;a name=&quot;dontlike&quot;&gt;&lt;/a&gt;
* &lt;strong&gt;Update June 17th 2016:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As Daniel pointed in his Tweet, the usage of the term &amp;quot;Don&#39;t like...&amp;quot; might be misleading for some people who don&#39;t know me:&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;&lt;a href=&quot;https://twitter.com/alexzeitler_&quot;&gt;@alexzeitler_&lt;/a&gt; I think you wrote a good article in general. Though I disagree with the abuse of &amp;quot;I don&amp;#39;t like&amp;quot; // &lt;a href=&quot;https://twitter.com/StefanLieser&quot;&gt;@StefanLieser&lt;/a&gt;&lt;/p&gt;&amp;mdash; Daniel Marbach (@danielmarbach) &lt;a href=&quot;https://twitter.com/danielmarbach/status/743168006344347648&quot;&gt;June 15, 2016&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;p&gt;So here&#39;s a little explanation what &amp;quot;Don&#39;t like&amp;quot; means:
If I wrote &amp;quot;Don&#39;t like&amp;quot; in the text above, somebody might get the impression (as Daniel pointed out) &amp;quot;I&#39;m basing all my decisions on taste&amp;quot;.&lt;/p&gt;
&lt;p&gt;But when I wrote &amp;quot;Don&#39;t like&amp;quot; it means: &amp;quot;I tried to solve something with stack x but it didn&#39;t work for me in a elegant manner so I tried some other thing which did work much better for that particular problem&amp;quot;.&lt;/p&gt;
&lt;p&gt;So nothing is based on taste or disgust against a particular stack or company.
It&#39;s all based on evaluation without prejudices.&lt;/p&gt;
&lt;p&gt;Thanks Daniel, for reading and pointing me at this issue.&lt;/p&gt;
&lt;p&gt;And as a baseline for this post I would like to add the following:&lt;/p&gt;
&lt;p&gt;Everything we&#39;re complaining about everyday - especially on social media - is the work of other people.&lt;/p&gt;
&lt;p&gt;And even it doesn&#39;t look perfect or work in the expected way for us: Others might have worked hard to get it to this point and we should pay respect for their effort - how often did we build something that wasn&#39;t perfect at all? 😉&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Running ASP.NET 5 Beta 4 in Docker with DNX runtime #aspnet5 #docker</title>
    <link href="https://alexanderzeitler.com/articles/Running-ASP.NET-5-beta4-in-Docker-with-DNX-runtime/"/>
    <updated>2015-04-27T11:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/Running-ASP.NET-5-beta4-in-Docker-with-DNX-runtime/</id>
    <content type="html">&lt;h3&gt;Update [2015-05-02]&lt;/h3&gt;
&lt;p&gt;As Microsoft has updated it&#39;s &lt;a href=&quot;https://registry.hub.docker.com/u/microsoft/aspnet/&quot;&gt;Dockerfile&lt;/a&gt; to support DNX (beta 4 as of this Update), the introduction of this post has become obsolete and you can jump directly over &lt;a href=&quot;https://alexanderzeitler.com/articles/Running-ASP.NET-5-beta4-in-Docker-with-DNX-runtime/#dockerfile2&quot;&gt;here (in this post)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&amp;lt;/end of update&amp;gt; [2015-05-02]&lt;/p&gt;
&lt;p&gt;Microsoft recently changed the naming for the &lt;a href=&quot;https://github.com/aspnet/home&quot;&gt;ASP.NET 5&lt;/a&gt; runtime from &amp;quot;K&amp;quot; to &amp;quot;&lt;a href=&quot;https://github.com/aspnet/DNX&quot;&gt;DNX&lt;/a&gt;&amp;quot;.
With that, the following K utilities have been renamed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;kpm -&amp;gt; dnu&lt;/li&gt;
&lt;li&gt;kvm -&amp;gt; dnvm&lt;/li&gt;
&lt;li&gt;k -&amp;gt; dnx&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When you&#39;re trying to run DNX with the &lt;a href=&quot;https://registry.hub.docker.com/u/microsoft/aspnet/&quot;&gt;current ASP.NET 5 Docker image&lt;/a&gt; being provided by Microsoft, you&#39;ll fail.&lt;/p&gt;
&lt;p&gt;Based on the Dockerfile Microsoft provides &lt;a href=&quot;https://registry.hub.docker.com/u/microsoft/aspnet/dockerfile/&quot;&gt;here&lt;/a&gt;, I created a local Docker image to be able to run ASP.NET 5 Beta 4 with the new DNX runtime.&lt;/p&gt;
&lt;p&gt;The Dockerfile for the base ASP.NET 5 image goes here:&lt;/p&gt;
&lt;h3&gt;Update [2015-05-01]&lt;/h3&gt;
&lt;p&gt;You don&#39;t have to do the following step, there&#39;s also a working Microsoft Dockerfile, just jump to &lt;a href=&quot;https://alexanderzeitler.com/articles/Running-ASP.NET-5-beta4-in-Docker-with-DNX-runtime/#dockerfile1&quot;&gt;here (in this post)&lt;/a&gt;, to read the instructions how to use it.&lt;/p&gt;
&lt;pre class=&quot;language-dockerfile&quot;&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;FROM&lt;/span&gt; mono:&lt;span class=&quot;hljs-number&quot;&gt;3.12&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Get build dependencies, download/build/install mono 4.1.0&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;RUN&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; apt-get update -qq &#92;&lt;br /&gt;    &amp;amp;&amp;amp; apt-get install -qqy git autoconf libtool automake build-essential mono-devel gettext unzip &#92;&lt;br /&gt;    &amp;amp;&amp;amp; git &lt;span class=&quot;hljs-built_in&quot;&gt;clone&lt;/span&gt; https://github.com/mono/mono.git &#92;&lt;br /&gt;    &amp;amp;&amp;amp; &lt;span class=&quot;hljs-built_in&quot;&gt;cd&lt;/span&gt; mono &#92;&lt;br /&gt;    &amp;amp;&amp;amp; git reset --hard 53dc56ee39a8e3b013231957aca4671b202c6410 &#92;&lt;br /&gt;    &amp;amp;&amp;amp; ./autogen.sh --prefix=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;/usr/local&amp;quot;&lt;/span&gt; &#92;&lt;br /&gt;    &amp;amp;&amp;amp; make &#92;&lt;br /&gt;    &amp;amp;&amp;amp; make install &#92;&lt;br /&gt;    &amp;amp;&amp;amp; &lt;span class=&quot;hljs-built_in&quot;&gt;cd&lt;/span&gt; .. &#92;&lt;br /&gt;    &amp;amp;&amp;amp; &lt;span class=&quot;hljs-built_in&quot;&gt;rm&lt;/span&gt; mono -r&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Install aspnet 1.0.0-beta4&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;ENV&lt;/span&gt; DNX_FEED https://www.myget.org/F/aspnetmaster/api/v2&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;ENV&lt;/span&gt; DNX_USER_HOME /opt/dnx&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;RUN&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; curl -sSL https://raw.githubusercontent.com/aspnet/Home/7d6f78ed7a59594ce7cdb54a026f09cb0cbecb2a/dnvminstall.sh | DNX_BRANCH=master sh&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;RUN&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; bash -c &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;source &lt;span class=&quot;hljs-variable&quot;&gt;$DNX_USER_HOME&lt;/span&gt;/dnvm/dnvm.sh &#92;&lt;br /&gt;    &amp;amp;&amp;amp; dnvm install 1.0.0-beta4 -a default &#92;&lt;br /&gt;    &amp;amp;&amp;amp; dnvm alias default | xargs -i ln -s &lt;span class=&quot;hljs-variable&quot;&gt;$DNX_USER_HOME&lt;/span&gt;/runtimes/{} &lt;span class=&quot;hljs-variable&quot;&gt;$DNX_USER_HOME&lt;/span&gt;/runtimes/default&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Install libuv for Kestrel from source code (binary is not in wheezy and one in jessie is still too old)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;RUN&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; LIBUV_VERSION=1.4.2 &#92;&lt;br /&gt;    &amp;amp;&amp;amp; curl -sSL https://github.com/libuv/libuv/archive/v&lt;span class=&quot;hljs-variable&quot;&gt;${LIBUV_VERSION}&lt;/span&gt;.tar.gz | tar zxfv - -C /usr/local/src &#92;&lt;br /&gt;    &amp;amp;&amp;amp; &lt;span class=&quot;hljs-built_in&quot;&gt;cd&lt;/span&gt; /usr/local/src/libuv-&lt;span class=&quot;hljs-variable&quot;&gt;$LIBUV_VERSION&lt;/span&gt; &#92;&lt;br /&gt;    &amp;amp;&amp;amp; sh autogen.sh &amp;amp;&amp;amp; ./configure &amp;amp;&amp;amp; make &amp;amp;&amp;amp; make install &#92;&lt;br /&gt;    &amp;amp;&amp;amp; &lt;span class=&quot;hljs-built_in&quot;&gt;rm&lt;/span&gt; -rf /usr/local/src/libuv-&lt;span class=&quot;hljs-variable&quot;&gt;$LIBUV_VERSION&lt;/span&gt; &#92;&lt;br /&gt;    &amp;amp;&amp;amp; ldconfig&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Update NuGet feeds&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;RUN&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;mkdir&lt;/span&gt; -p ~/.config/NuGet/&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;RUN&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; curl -o ~/.config/NuGet/NuGet.Config -sSL https://gist.githubusercontent.com/AlexZeitler/a3412a4d4eeee60f8ce8/raw/45b0b5312845099cdf5da560829e75949d44d65f/NuGet.config&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;ENV&lt;/span&gt; PATH $PATH:$DNX_USER_HOME/runtimes/default/bin&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now lets build the base image:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo docker build -t pdmlab/aspnet:1.0.0 .&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Based on that, lets create a Dockerfile for our ASP.NET 5 Beta 4 DNX web application:&lt;/p&gt;
&lt;pre class=&quot;language-dockerfile&quot;&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;FROM&lt;/span&gt; pdmlab/aspnet:&lt;span class=&quot;hljs-number&quot;&gt;1.0&lt;/span&gt;.&lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;ADD&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; . /app&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;WORKDIR&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; /app&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;RUN&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; [&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;dnu&amp;quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;restore&amp;quot;&lt;/span&gt;]&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;EXPOSE&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;5004&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;ENTRYPOINT&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; [&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;dnx&amp;quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;./src/HelloMvc6&amp;quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;kestrel&amp;quot;&lt;/span&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a name=&quot;dockerfile1&quot;&gt;&lt;/a&gt;    Update [2015-05-01]&lt;/h3&gt;
&lt;p&gt;You can also use this Dockerfile using the Microsoft Beta 4 base Image:&lt;/p&gt;
&lt;pre class=&quot;language-dockerfile&quot;&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;FROM&lt;/span&gt; microsoft/aspnet:vs-&lt;span class=&quot;hljs-number&quot;&gt;1.0&lt;/span&gt;.&lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;-beta4&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;ADD&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; . /app&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;WORKDIR&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; /app&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;RUN&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; [&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;dnu&amp;quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;restore&amp;quot;&lt;/span&gt;]&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;EXPOSE&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;5004&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;ENTRYPOINT&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; [&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;dnx&amp;quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;./src/HelloMvc6&amp;quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;kestrel&amp;quot;&lt;/span&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/end of update&amp;gt; [2015-05-01]&lt;/p&gt;
&lt;h3&gt;&lt;a name=&quot;dockerfile2&quot;&gt;&lt;/a&gt;    Update [2015-05-02]&lt;/h3&gt;
&lt;p&gt;You can use this Dockerfile using the Microsoft DNX Beta 4 base Image:&lt;/p&gt;
&lt;pre class=&quot;language-dockerfile&quot;&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;FROM&lt;/span&gt; microsoft/aspnet:&lt;span class=&quot;hljs-number&quot;&gt;1.0&lt;/span&gt;.&lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;-beta4&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;ADD&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; . /app&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;WORKDIR&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; /app&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;RUN&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; [&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;dnu&amp;quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;restore&amp;quot;&lt;/span&gt;]&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;EXPOSE&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;5004&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;ENTRYPOINT&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; [&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;dnx&amp;quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;./src/HelloMvc6&amp;quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;kestrel&amp;quot;&lt;/span&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/end of update&amp;gt; [2015-05-02]&lt;/p&gt;
&lt;p&gt;After that, we need to build or application image:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo docker build -t aspnet5beta4dnx&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;With that done, we can run our container:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo docker run -t -d -p 80:5004 aspnet5beta4dnx&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;If erverything went well, you should be able to browse http://localhost/api/values on your host.&lt;/p&gt;
&lt;p&gt;The source for the HelloMvc6 application comes here:&lt;/p&gt;
&lt;p&gt;project.json:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;webroot&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;wwwroot&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;version&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;1.0.0-*&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;dependencies&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Kestrel&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;1.0.0-beta4&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Microsoft.AspNet.Mvc&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;6.0.0-beta4&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Microsoft.AspNet.Server.IIS&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;1.0.0-beta4&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Microsoft.AspNet.Server.WebListener&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;1.0.0-beta4&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Microsoft.AspNet.StaticFiles&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;1.0.0-beta4&amp;quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;commands&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;web&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Microsoft.AspNet.Hosting --server Microsoft.AspNet.Server.WebListener --server.urls http://localhost:5000&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;kestrel&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Microsoft.AspNet.Hosting --server Kestrel --server.urls http://localhost:5004&amp;quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;frameworks&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;dnx451&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;dnxcore50&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;exclude&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;wwwroot&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;node_modules&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;bower_components&amp;quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;publishExclude&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;node_modules&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;bower_components&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;**.xproj&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;**.user&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;**.vspscc&amp;quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Startup.cs:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; System;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; System.Collections.Generic;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; System.Linq;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; System.Threading.Tasks;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; Microsoft.AspNet.Builder;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; Microsoft.AspNet.Hosting;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; Microsoft.AspNet.Http;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; Microsoft.AspNet.Routing;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; Microsoft.Framework.DependencyInjection;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;HelloMvc6&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Startup&lt;/span&gt;&lt;br /&gt;    {&lt;br /&gt;        &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Startup&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;IHostingEnvironment env&lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;        {&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-comment&quot;&gt;// This method gets called by a runtime.&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-comment&quot;&gt;// Use this method to add services to the container&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;ConfigureServices&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;IServiceCollection services&lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;        {&lt;br /&gt;            services.AddMvc();&lt;br /&gt;            &lt;span class=&quot;hljs-comment&quot;&gt;// Uncomment the following line to add Web API services which makes it easier to port Web API 2 controllers.&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-comment&quot;&gt;// You will also need to add the Microsoft.AspNet.Mvc.WebApiCompatShim package to the &amp;#x27;dependencies&amp;#x27; section of project.json.&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-comment&quot;&gt;// services.AddWebApiConventions();&lt;/span&gt;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-comment&quot;&gt;// Configure is called after ConfigureServices is called.&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Configure&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;IApplicationBuilder app, IHostingEnvironment env&lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;        {&lt;br /&gt;            &lt;span class=&quot;hljs-comment&quot;&gt;// Configure the HTTP request pipeline.&lt;/span&gt;&lt;br /&gt;            app.UseStaticFiles();&lt;br /&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-comment&quot;&gt;// Add MVC to the request pipeline.&lt;/span&gt;&lt;br /&gt;            app.UseMvc();&lt;br /&gt;            &lt;span class=&quot;hljs-comment&quot;&gt;// Add the following route for porting Web API 2 controllers.&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-comment&quot;&gt;// routes.MapWebApiRoute(&amp;quot;DefaultApi&amp;quot;, &amp;quot;api/{controller}/{id?}&amp;quot;);&lt;/span&gt;&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ValuesController.cs:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; System;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; System.Collections.Generic;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; System.Linq;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; System.Threading.Tasks;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; Microsoft.AspNet.Mvc;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;HelloMvc6.Controllers&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;    [&lt;span class=&quot;hljs-meta&quot;&gt;Route(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;api/[controller]&amp;quot;&lt;/span&gt;)&lt;/span&gt;]&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;ValuesController&lt;/span&gt; : &lt;span class=&quot;hljs-title&quot;&gt;Controller&lt;/span&gt;&lt;br /&gt;    {&lt;br /&gt;        &lt;span class=&quot;hljs-comment&quot;&gt;// GET: api/values&lt;/span&gt;&lt;br /&gt;        [&lt;span class=&quot;hljs-meta&quot;&gt;HttpGet&lt;/span&gt;]&lt;br /&gt;        &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; IEnumerable&amp;lt;&lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt;&amp;gt; &lt;span class=&quot;hljs-title&quot;&gt;Get&lt;/span&gt;()&lt;/span&gt;&lt;br /&gt;        {&lt;br /&gt;            &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt;[] { &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;value1&amp;quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;value2&amp;quot;&lt;/span&gt; };&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-comment&quot;&gt;// GET api/values/5&lt;/span&gt;&lt;br /&gt;        [&lt;span class=&quot;hljs-meta&quot;&gt;HttpGet(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;{id}&amp;quot;&lt;/span&gt;)&lt;/span&gt;]&lt;br /&gt;        &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Get&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt; id&lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;        {&lt;br /&gt;            &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;value&amp;quot;&lt;/span&gt;;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-comment&quot;&gt;// POST api/values&lt;/span&gt;&lt;br /&gt;        [&lt;span class=&quot;hljs-meta&quot;&gt;HttpPost&lt;/span&gt;]&lt;br /&gt;        &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Post&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;[FromBody]&lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;value&lt;/span&gt;&lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;        {&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-comment&quot;&gt;// PUT api/values/5&lt;/span&gt;&lt;br /&gt;        [&lt;span class=&quot;hljs-meta&quot;&gt;HttpPut(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;{id}&amp;quot;&lt;/span&gt;)&lt;/span&gt;]&lt;br /&gt;        &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Put&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt; id, [FromBody]&lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;value&lt;/span&gt;&lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;        {&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-comment&quot;&gt;// DELETE api/values/5&lt;/span&gt;&lt;br /&gt;        [&lt;span class=&quot;hljs-meta&quot;&gt;HttpDelete(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;{id}&amp;quot;&lt;/span&gt;)&lt;/span&gt;]&lt;br /&gt;        &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Delete&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt; id&lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;        {&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  
  <entry>
    <title>lowerCamelCase JSON with ASP.NET MVC 6</title>
    <link href="https://alexanderzeitler.com/articles/lowerCamelCase-JSON-with-ASP-NET-MVC6-ASP-NET-5/"/>
    <updated>2015-05-01T17:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/lowerCamelCase-JSON-with-ASP-NET-MVC6-ASP-NET-5/</id>
    <content type="html">&lt;p&gt;With ASP.NET MVC 6 the default output for JSON is still UpperCamelCase.&lt;/p&gt;
&lt;p&gt;To change that to lowerCamelCase JSON, simply add this to the &lt;code&gt;ConfigureServices&lt;/code&gt; method:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Startup.cs&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;﻿&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;ConfigureServices&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;IServiceCollection services&lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;    services.AddMvc();&lt;br /&gt;   &lt;br /&gt;    services.ConfigureMvc(options =&amp;gt;&lt;br /&gt;    {&lt;br /&gt;        (options.OutputFormatters.First(f =&amp;gt; f.Instance &lt;span class=&quot;hljs-keyword&quot;&gt;is&lt;/span&gt; JsonOutputFormatter).Instance &lt;span class=&quot;hljs-keyword&quot;&gt;as&lt;/span&gt;&lt;br /&gt;            JsonOutputFormatter).SerializerSettings.ContractResolver = &lt;br /&gt;            &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; CamelCasePropertyNamesContractResolver();&lt;br /&gt;    });&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&#39;s it!&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Deploying a ASP.NET MVC 6 API as Azure API App in Azure App Services</title>
    <link href="https://alexanderzeitler.com/articles/Deploying-a-ASP-NET-MVC-6-API-as-Azure-API-App-in-Azure-App-Services/"/>
    <updated>2015-05-02T10:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/Deploying-a-ASP-NET-MVC-6-API-as-Azure-API-App-in-Azure-App-Services/</id>
    <content type="html">&lt;h3&gt;Setting some context...&lt;/h3&gt;
&lt;p&gt;Disclaimer: this post is based on Visual Studio 2015 RC, DNX beta 4 and Azure SDK 2.6 and might become obsolete (hopefully pretty fast).&lt;/p&gt;
&lt;p&gt;At Build 2015 &amp;quot;&lt;a href=&quot;https://channel9.msdn.com/Events/Build/2015/C9-16&quot;&gt;The Lesser Scotts&lt;/a&gt;&amp;quot; have been showing how to create a Azure API App based on ASP.NET Web API 2 using the Azure SDK Tools in Visual Studio 2013 which you should definitely &lt;a href=&quot;https://channel9.msdn.com/Events/Build/2015/2-628&quot;&gt;watch&lt;/a&gt; before reading this post.&lt;/p&gt;
&lt;p&gt;Well, deploying stable stuff is pretty neat.&lt;br /&gt;
But my first thought of course has been &amp;quot;What about &lt;a href=&quot;https://github.com/aspnet/home&quot;&gt;ASP.NET MVC 6 on DNX&lt;/a&gt; beta 4?&amp;quot;.&lt;/p&gt;
&lt;p&gt;So I asked the lesser Scotts at Twitter:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/Deploying-a-ASP-NET-MVC-6-API-as-Azure-API-App-in-Azure-App-Services/Tweetazeitlertolesserscotts.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Scotts answer was pretty promising:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/Deploying-a-ASP-NET-MVC-6-API-as-Azure-API-App-in-Azure-App-Services/Tweetshanselmantoalexzeitler.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;My next step of course was installing &lt;a href=&quot;http://azure.microsoft.com/en-us/downloads/archive-net-downloads/&quot;&gt;Azure SDK 2.6 for Visual Studio 2015 RC&lt;/a&gt; and trying to create a new Azure API App as shown in the video above.&lt;/p&gt;
&lt;p&gt;But there were no templates... so back to Twitter - this time Brady Gaster was my victim:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/Deploying-a-ASP-NET-MVC-6-API-as-Azure-API-App-in-Azure-App-Services/Tweetalexzeitlertobradygaster.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;No promising answer but at least some facts :)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/Deploying-a-ASP-NET-MVC-6-API-as-Azure-API-App-in-Azure-App-Services/Tweetbradygastertoalexzeitler.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Ok, lets do some reverse engineering using Visual Studio 2013 and Azure SDK 2.6 and find out what happens when you&#39;re using the SDK tooling - I&#39;ll skip this step here and show you, how to get stuff done in Visual Studio 2015 RC and &lt;a href=&quot;https://channel9.msdn.com/Events/Build/2015/2-687&quot;&gt;ASP.NET MVC 6 on DNX beta 4&lt;/a&gt; now.&lt;/p&gt;
&lt;h3&gt;First things first, the ASP.NET MVC 6 API&lt;/h3&gt;
&lt;p&gt;First, create a new ASP.NET Web Application in Visual Studio 2015 RC:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/Deploying-a-ASP-NET-MVC-6-API-as-Azure-API-App-in-Azure-App-Services/NewProject.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/Deploying-a-ASP-NET-MVC-6-API-as-Azure-API-App-in-Azure-App-Services/NewvNextWebApi.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After this, your solution should look like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/Deploying-a-ASP-NET-MVC-6-API-as-Azure-API-App-in-Azure-App-Services/NewProjectSolutionExplorer.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;As the default &lt;code&gt;ValuesController.cs&lt;/code&gt; in the &lt;code&gt;Controllers&lt;/code&gt; folder is pretty useless (and it will cause problems as you can read later on), we&#39;ll delete it and create a &lt;code&gt;SpeakersController.cs&lt;/code&gt; instead.&lt;/p&gt;
&lt;p&gt;SpeakersController.cs:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; System.Collections.Generic;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; HelloApiApps.Models;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; Microsoft.AspNet.Mvc;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;HelloApiApps.Controllers&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;    [&lt;span class=&quot;hljs-meta&quot;&gt;Route(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;api/[controller]&amp;quot;&lt;/span&gt;)&lt;/span&gt;]&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;SpeakersController&lt;/span&gt;&lt;br /&gt;    {&lt;br /&gt;        [&lt;span class=&quot;hljs-meta&quot;&gt;HttpGet&lt;/span&gt;]&lt;br /&gt;        &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; IEnumerable&amp;lt;Speaker&amp;gt; &lt;span class=&quot;hljs-title&quot;&gt;GetAll&lt;/span&gt;()&lt;/span&gt;&lt;br /&gt;        {&lt;br /&gt;            &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; List&amp;lt;Speaker&amp;gt;&lt;br /&gt;            {&lt;br /&gt;                &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; Speaker&lt;br /&gt;                {&lt;br /&gt;                    Id = &lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;,&lt;br /&gt;                    FirstName = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Scott&amp;quot;&lt;/span&gt;,&lt;br /&gt;                    LastName = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Hanselman&amp;quot;&lt;/span&gt;,&lt;br /&gt;                    Twitter = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;shanselman&amp;quot;&lt;/span&gt;&lt;br /&gt;                },&lt;br /&gt;                &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; Speaker&lt;br /&gt;                {&lt;br /&gt;                    Id = &lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;,&lt;br /&gt;                    FirstName = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Scott&amp;quot;&lt;/span&gt;,&lt;br /&gt;                    LastName = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Hunter&amp;quot;&lt;/span&gt;,&lt;br /&gt;                    Twitter = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;coolcsh&amp;quot;&lt;/span&gt;&lt;br /&gt;                },&lt;br /&gt;                &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; Speaker&lt;br /&gt;                {&lt;br /&gt;                    Id = &lt;span class=&quot;hljs-number&quot;&gt;3&lt;/span&gt;,&lt;br /&gt;                    FirstName = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Damian&amp;quot;&lt;/span&gt;,&lt;br /&gt;                    LastName = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Edwards&amp;quot;&lt;/span&gt;,&lt;br /&gt;                    Twitter = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;DamianEdwards&amp;quot;&lt;/span&gt;&lt;br /&gt;                }&lt;br /&gt;            };&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We also create a folder &lt;code&gt;Models&lt;/code&gt; and create a &lt;code&gt;Speaker.cs&lt;/code&gt; in it:&lt;/p&gt;
&lt;p&gt;Speaker.cs:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;HelloApiApps.Models&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Speaker&lt;/span&gt;&lt;br /&gt;    {&lt;br /&gt;        &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt; Id { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;set&lt;/span&gt;; }&lt;br /&gt;        &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; FirstName { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;set&lt;/span&gt;; }&lt;br /&gt;        &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; LastName { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;set&lt;/span&gt;; }&lt;br /&gt;        &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; Twitter { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;set&lt;/span&gt;; }&lt;br /&gt;    }&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this time, you should be able to test your API locally at &lt;code&gt;http://localhost:someport/api/speakers&lt;/code&gt; and you should get the JSON representation of our speaker list.&lt;/p&gt;
&lt;h3&gt;Creating the Swagger API definition file&lt;/h3&gt;
&lt;p&gt;According to the Scott&#39;s video we now would create the Swagger file, but &lt;a href=&quot;http://www.nuget.org/packages/Swashbuckle/&quot;&gt;Swashbuckle&lt;/a&gt; shown in the video doesn&#39;t work with ASP.NET MVC 6 right now.&lt;/p&gt;
&lt;p&gt;To the rescue, there&#39;s already a work in progress project on GitHub which is porting Swashbuckle to ASP.NET MVC 6: &lt;a href=&quot;https://github.com/domaindrivendev/Ahoy&quot;&gt;Ahoy&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;We create a local clone of Ahoy and for the sake of keeping this post simple, we add the &lt;code&gt;Swashbuckle.Swagger&lt;/code&gt; project inside our API Solution as existing project:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/Deploying-a-ASP-NET-MVC-6-API-as-Azure-API-App-in-Azure-App-Services/AddSwaggerProject.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Next, we have to register Swashbuckle / Swagger inside our &lt;code&gt;Startup.cs&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Startup.cs:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; System;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; System.Collections.Generic;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; System.Linq;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; System.Threading.Tasks;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; Microsoft.AspNet.Builder;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; Microsoft.AspNet.Hosting;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; Microsoft.AspNet.Http;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; Microsoft.AspNet.Routing;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; Microsoft.Framework.DependencyInjection;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; Swashbuckle.Application;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; Swashbuckle.Swagger;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;HelloApiApps&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;   &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Startup&lt;/span&gt;&lt;br /&gt;   {&lt;br /&gt;       &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Startup&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;IHostingEnvironment env&lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;       {&lt;br /&gt;       }&lt;br /&gt;&lt;br /&gt;       &lt;span class=&quot;hljs-comment&quot;&gt;// This method gets called by a runtime.&lt;/span&gt;&lt;br /&gt;       &lt;span class=&quot;hljs-comment&quot;&gt;// Use this method to add services to the container&lt;/span&gt;&lt;br /&gt;       &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;ConfigureServices&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;IServiceCollection services&lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;       {&lt;br /&gt;           services.AddMvc();&lt;br /&gt;           &lt;span class=&quot;hljs-comment&quot;&gt;// Uncomment the following line to add Web API services which makes it easier to port Web API 2 controllers.&lt;/span&gt;&lt;br /&gt;           &lt;span class=&quot;hljs-comment&quot;&gt;// You will also need to add the Microsoft.AspNet.Mvc.WebApiCompatShim package to the &amp;#x27;dependencies&amp;#x27; section of project.json.&lt;/span&gt;&lt;br /&gt;           &lt;span class=&quot;hljs-comment&quot;&gt;// services.AddWebApiConventions();&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;           services.AddSwagger(s =&amp;gt;&lt;br /&gt;           {&lt;br /&gt;               s.SwaggerGenerator(c =&amp;gt;&lt;br /&gt;               {&lt;br /&gt;                   c.Schemes = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt;[] { &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;http&amp;quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;https&amp;quot;&lt;/span&gt; };&lt;br /&gt;                   c.SingleApiVersion(&lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; Info&lt;br /&gt;                   {&lt;br /&gt;                       Version = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;v1&amp;quot;&lt;/span&gt;,&lt;br /&gt;                       Title = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Swashbuckle Sample API&amp;quot;&lt;/span&gt;,&lt;br /&gt;                       Description = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;A sample API for testing Swashbuckle&amp;quot;&lt;/span&gt;,&lt;br /&gt;                       TermsOfService = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Some terms ...&amp;quot;&lt;/span&gt;&lt;br /&gt;                   });&lt;br /&gt;               });&lt;br /&gt;&lt;br /&gt;               s.SchemaGenerator(opt =&amp;gt; opt.DescribeAllEnumsAsStrings = &lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;);&lt;br /&gt;           });&lt;br /&gt;       }&lt;br /&gt;&lt;br /&gt;       &lt;span class=&quot;hljs-comment&quot;&gt;// Configure is called after ConfigureServices is called.&lt;/span&gt;&lt;br /&gt;       &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Configure&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;IApplicationBuilder app, IHostingEnvironment env&lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;       {&lt;br /&gt;           &lt;span class=&quot;hljs-comment&quot;&gt;// Configure the HTTP request pipeline.&lt;/span&gt;&lt;br /&gt;           app.UseStaticFiles();&lt;br /&gt;&lt;br /&gt;           &lt;span class=&quot;hljs-comment&quot;&gt;// Add MVC to the request pipeline.&lt;/span&gt;&lt;br /&gt;           app.UseMvc();&lt;br /&gt;           &lt;span class=&quot;hljs-comment&quot;&gt;// Add the following route for porting Web API 2 controllers.&lt;/span&gt;&lt;br /&gt;           &lt;span class=&quot;hljs-comment&quot;&gt;// routes.MapWebApiRoute(&amp;quot;DefaultApi&amp;quot;, &amp;quot;api/{controller}/{id?}&amp;quot;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;           &lt;span class=&quot;hljs-comment&quot;&gt;// Add MVC to the request pipeline.&lt;/span&gt;&lt;br /&gt;           app.UseMvc(routes =&amp;gt;&lt;br /&gt;           {&lt;br /&gt;               routes.EnableSwagger(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;swagger/docs/{apiVersion}&amp;quot;&lt;/span&gt;);&lt;br /&gt;           });&lt;br /&gt;       }&lt;br /&gt;   }&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The default Swagger URL for Swashbuckle has been (as shown in the video): &lt;code&gt;http://somehost:someport/swagger/docs/v1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;By default, with Ahoy this has changed to &lt;code&gt;http://somehost:someport/swagger/v1/swagger.json&lt;/code&gt; whereas in both cases &lt;code&gt;v1&lt;/code&gt; depends on the &lt;code&gt;Version&lt;/code&gt; property being set during Swashbuckle registration in &lt;code&gt;Startup.cs&lt;/code&gt; inside the &lt;code&gt;ConfigureServices&lt;/code&gt; method in the code shown above.&lt;/p&gt;
&lt;p&gt;Thus, we have to change the route definition back to the old format as Azure API Apps expect it in that format.
You can see that inside the &lt;code&gt;Configure&lt;/code&gt; method of the &lt;code&gt;Startup.cs&lt;/code&gt; in the code shown above.&lt;/p&gt;
&lt;p&gt;Next, we can open up our App in a browser again and browse to &lt;code&gt;http://localhost:someport/swagger/docs/v1&lt;/code&gt; and we should get the JSON representation.&lt;/p&gt;
&lt;h3&gt;Adding Azure API Apps Metadata&lt;/h3&gt;
&lt;p&gt;Now we need to add the Metadata for the Azure API Apps manually as we don&#39;t have the tooling support right now...&lt;/p&gt;
&lt;p&gt;First, add a JSON file named &lt;code&gt;apiapp.json&lt;/code&gt; in the root of the API App project:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/Deploying-a-ASP-NET-MVC-6-API-as-Azure-API-App-in-Azure-App-Services/ApiAppsJSON.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Then paste this content into it:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt; &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;$schema&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;http://json-schema.org/schemas/2014-11-01/apiapp.json#&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt; &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;HelloApiApps&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt; &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;namespace&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;microsoft.com&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt; &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;gateway&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;2015-01-14&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt; &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;version&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;1.0.0&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt; &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;HelloApiApps&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt; &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;summary&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt; &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;author&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt; &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;endpoints&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;null&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, add the following folders and files to your &lt;code&gt;wwwroot&lt;/code&gt; folder in the API App solution:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/Deploying-a-ASP-NET-MVC-6-API-as-Azure-API-App-in-Azure-App-Services/MetadataFolderAndFiles.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The content of the &lt;code&gt;apiDefinition.swagger.json&lt;/code&gt; is the output from &lt;code&gt;http://localhost:someport/swagger/docs/v1&lt;/code&gt;.
Copy and paste it into the file.&lt;/p&gt;
&lt;p&gt;Important Note: Make sure your API Controllers don&#39;t contain ambiguous method names (e.g. &amp;quot;Get&amp;quot; twice), this will cause problems at the moment.
Thanks to &lt;a href=&quot;https://twitter.com/mohit&quot;&gt;@Mohit&lt;/a&gt; for &lt;a href=&quot;https://social.msdn.microsoft.com/Forums/en-US/621c2d06-51d7-48ff-8a02-d97fdda7b3e3/cannot-get-the-api-definition?forum=AzureAPIApps&quot;&gt;sorting that out&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;The last file required, is the &lt;code&gt;apiappconfig.azureresource.json&lt;/code&gt; and it&#39;s content is this:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt; &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;$schema&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;http://schemas.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json#&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt; &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;contentVersion&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;1.0.0.0&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt; &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;parameters&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;   &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;$system&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;     &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Object&amp;quot;&lt;/span&gt;&lt;br /&gt;   &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt; &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;resources&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;Update [2015/05/02]&amp;gt;:&lt;/strong&gt;
Instead of providing a static snapshot of your Swagger API definition, you can also provide an endpoint where the current swagger definition can be read from. This solves two problems: the route can be the new route template Ahoy introduces and your Swagger definition is always up to date when add new API endpoints (Controllers / Methods).&lt;/p&gt;
&lt;p&gt;Just update the &lt;code&gt;apiapp.json&lt;/code&gt; and add an fill the endpoint property (please note that I&#39;m use the new default Ahoy route here):&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;$schema&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;http://json-schema.org/schemas/2014-11-01/apiapp.json#&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;HelloApiApps&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;namespace&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;microsoft.com&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;gateway&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;2015-01-14&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;version&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;1.0.0&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;HelloApiApps&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;summary&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;author&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;endpoints&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;apiDefinition&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;/swagger/v1/swagger.json&amp;quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Kudos go to &lt;a href=&quot;https://twitter.com/pkefal&quot;&gt;@pkefal&lt;/a&gt; for the hint (By the way: if you want to create API Apps using Node.js you should definitely read his &lt;a href=&quot;http://azure.microsoft.com/en-us/documentation/articles/app-service-api-nodejs-api-app/&quot;&gt;article&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;/end of update [2015/05/02]&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Another step, the Azure SDK tooling does, is adding two NuGet packages and we have to do that also.
Update your &lt;code&gt;project.json&lt;/code&gt; and add &lt;code&gt;Microsoft.Azure.AppService.ApiApps.Service&lt;/code&gt; and &lt;code&gt;System.IdentityModel.Tokens.Jwt&lt;/code&gt;to it.&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt; &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;webroot&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;wwwroot&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt; &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;version&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;1.0.0-*&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;br /&gt; &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;dependencies&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;   &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Microsoft.AspNet.Mvc&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;6.0.0-beta4&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;   &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Microsoft.AspNet.Server.IIS&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;1.0.0-beta4&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;   &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Microsoft.AspNet.Server.WebListener&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;1.0.0-beta4&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;   &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Microsoft.Azure.AppService.ApiApps.Service&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;0.9.40&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;   &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;System.IdentityModel.Tokens.Jwt&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;4.0.2.202250711&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;   &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Microsoft.AspNet.StaticFiles&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;1.0.0-beta4&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;   &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Swashbuckle.Swagger&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;1.0.0-*&amp;quot;&lt;/span&gt;&lt;br /&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;br /&gt; &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;commands&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;   &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;web&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Microsoft.AspNet.Hosting --server Microsoft.AspNet.Server.WebListener --server.urls http://localhost:5000&amp;quot;&lt;/span&gt;&lt;br /&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;br /&gt; &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;frameworks&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;   &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;dnx451&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;   &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;dnxcore50&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;br /&gt; &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;exclude&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;   &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;wwwroot&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;   &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;node_modules&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;   &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;bower_components&amp;quot;&lt;/span&gt;&lt;br /&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt; &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;publishExclude&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;   &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;node_modules&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;   &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;bower_components&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;   &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;**.xproj&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;   &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;**.user&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;   &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;**.vspscc&amp;quot;&lt;/span&gt;&lt;br /&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Publishing the API App&lt;/h3&gt;
&lt;p&gt;If there were tooling support, you could create a new Azure API App using the tooling.
Yet: no tooling, thus we have to use the new Azure Portal.&lt;/p&gt;
&lt;p&gt;Login using your credentials and create a new Azure API App.&lt;/p&gt;
&lt;p&gt;Disclaimer: Depending on your plan and the settings, this will cost you money!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/Deploying-a-ASP-NET-MVC-6-API-as-Azure-API-App-in-Azure-App-Services/AzurePortalCreateApiApp.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After a few seconds, you&#39;ll get the success notification inside the Azure Portal and you should have a new tile on the Portal startpage and clicking on it should get you some details:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/Deploying-a-ASP-NET-MVC-6-API-as-Azure-API-App-in-Azure-App-Services/AzurePortalApiAppCreated.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now make sure to set the &amp;quot;Access Level&amp;quot; inside the &amp;quot;Settings&amp;quot; / &amp;quot;Application Settings&amp;quot; to &amp;quot;public (anonymous)&amp;quot; and click the &amp;quot;Save&amp;quot; button afterwards:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/Deploying-a-ASP-NET-MVC-6-API-as-Azure-API-App-in-Azure-App-Services/AzurePortalAccessLevel.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Back to Visual Studio 2015 RC, we can now publish our Azure API App by right clicking the project and selecting &amp;quot;Publish...&amp;quot;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/Deploying-a-ASP-NET-MVC-6-API-as-Azure-API-App-in-Azure-App-Services/VSPublish.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The next dialog allows you to select where to publish:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/Deploying-a-ASP-NET-MVC-6-API-as-Azure-API-App-in-Azure-App-Services/PublishWeb.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Select &amp;quot;Microsoft Azure Web Apps&amp;quot; and click &amp;quot;Next&amp;quot;.&lt;/p&gt;
&lt;p&gt;Now from &amp;quot;Existing Web Apps&amp;quot;, select the API App you just created inside the Azure Portal and click &amp;quot;OK&amp;quot;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/Deploying-a-ASP-NET-MVC-6-API-as-Azure-API-App-in-Azure-App-Services/PublishSelectexisting.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The next dialog sums up the connection details and can be confirmed with &amp;quot;Next&amp;quot;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/Deploying-a-ASP-NET-MVC-6-API-as-Azure-API-App-in-Azure-App-Services/PublishCreateAppConnection.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;During the next step, you can choose your DNX version to be used for the API App - the defaults are ok, click &amp;quot;Next&amp;quot;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/Deploying-a-ASP-NET-MVC-6-API-as-Azure-API-App-in-Azure-App-Services/PublishSettingsCLRVersion.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The last step is to hit the &amp;quot;Publish&amp;quot; button:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/Deploying-a-ASP-NET-MVC-6-API-as-Azure-API-App-in-Azure-App-Services/PublishFinalize.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The publishing process should take only a few seconds:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/Deploying-a-ASP-NET-MVC-6-API-as-Azure-API-App-in-Azure-App-Services/PublishActivtity.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;When it is finished, it should open up your default browser showing you this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/Deploying-a-ASP-NET-MVC-6-API-as-Azure-API-App-in-Azure-App-Services/Deployed.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Back to the Azure Portal you should now be able to click on the &amp;quot;API Definition&amp;quot; tile and see the API Operations and be able to download the Swagger using the button above the list of Operations.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/Deploying-a-ASP-NET-MVC-6-API-as-Azure-API-App-in-Azure-App-Services/AzurePortalApiDefinition.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;That&#39;s it!&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Integration-testing ASP.NET 5 / MVC 6 Controllers on DNX Beta 4</title>
    <link href="https://alexanderzeitler.com/articles/Integration-testing-ASP.NET-5-Controllers-with-DNX-Beta4/"/>
    <updated>2015-05-16T21:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/Integration-testing-ASP.NET-5-Controllers-with-DNX-Beta4/</id>
    <content type="html">&lt;p&gt;In ASP.NET 5 bootstrapping a self host test server has changed compared to ASP.NET 4 / Web API.&lt;/p&gt;
&lt;p&gt;This post will show you how you can run integration tests using ASP.NET 5 / MVC 6 API Controllers on Beta 4 of DNX.&lt;/p&gt;
&lt;p&gt;First, the Controller implementation:&lt;/p&gt;
&lt;p&gt;CustomersController.cs:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;[&lt;span class=&quot;hljs-meta&quot;&gt;Route(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;api/[controller]&amp;quot;&lt;/span&gt;)&lt;/span&gt;]&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;CustomersController&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;    [&lt;span class=&quot;hljs-meta&quot;&gt;HttpGet&lt;/span&gt;]&lt;br /&gt;    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; IActionResult &lt;span class=&quot;hljs-title&quot;&gt;GetAll&lt;/span&gt;()&lt;/span&gt;&lt;br /&gt;    {&lt;br /&gt;        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; ObjectResult(&lt;br /&gt;            &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; List&amp;lt;Customer&amp;gt;()&lt;br /&gt;            {&lt;br /&gt;                &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; Customer&lt;br /&gt;                {&lt;br /&gt;                    CompanyName = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;PDMLab&amp;quot;&lt;/span&gt;,&lt;br /&gt;                    AddressLine = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Ludwig-Erhard-Allee 10&amp;quot;&lt;/span&gt;,&lt;br /&gt;                    ZipCode = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;76131&amp;quot;&lt;/span&gt;,&lt;br /&gt;                    City = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Karlsruhe&amp;quot;&lt;/span&gt;,&lt;br /&gt;                    Website = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;https://pdmlab.com&amp;quot;&lt;/span&gt;&lt;br /&gt;                }&lt;br /&gt;            });&lt;br /&gt;    }&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our tests will use Xunit, so we&#39;ll add Xunit to our &lt;code&gt;project.json&lt;/code&gt; file:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;...&lt;br /&gt; &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;dependencies&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Microsoft.AspNet.Mvc&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;6.0.0-beta4&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Microsoft.AspNet.Server.IIS&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;1.0.0-beta4&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Microsoft.AspNet.Server.WebListener&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;1.0.0-beta4&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Microsoft.AspNet.StaticFiles&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;1.0.0-beta4&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;xunit&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;2.1.0-*&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;xunit.runner.dnx&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;2.1.0-beta2-build79&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;xunit.runner.visualstudio&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;2.1.0-beta1-build1051&amp;quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;...&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Furthermore, our &lt;code&gt;HttpClient&lt;/code&gt; we&#39;re using resides in &lt;code&gt;Microsoft.AspNet.WebApi.Client&lt;/code&gt; which we&#39;ll add also to our &lt;code&gt;project.json&lt;/code&gt; file:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;...&lt;br /&gt; &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;dependencies&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Microsoft.AspNet.Mvc&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;6.0.0-beta4&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Microsoft.AspNet.Server.IIS&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;1.0.0-beta4&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Microsoft.AspNet.Server.WebListener&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;1.0.0-beta4&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Microsoft.AspNet.StaticFiles&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;1.0.0-beta4&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Microsoft.AspNet.WebApi.Client&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;5.2.3&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;xunit&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;2.1.0-*&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;xunit.runner.dnx&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;2.1.0-beta2-build79&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;xunit.runner.visualstudio&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;2.1.0-beta1-build1051&amp;quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;...&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, we need &lt;code&gt;Kestrel&lt;/code&gt; to run our tests on *nix based systems as well as &lt;code&gt;Microsoft.AspNet.Hosting&lt;/code&gt; which contains the classes required for bootstrapping a self host server - :&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;...&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;dependencies&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Kestrel&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;1.0.0-beta4&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Microsoft.AspNet.Hosting&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;1.0.0-beta4&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Microsoft.AspNet.Mvc&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;6.0.0-beta4&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Microsoft.AspNet.Server.IIS&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;1.0.0-beta4&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Microsoft.AspNet.Server.WebListener&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;1.0.0-beta4&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Microsoft.AspNet.StaticFiles&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;1.0.0-beta4&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Microsoft.AspNet.WebApi.Client&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;5.2.3&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;xunit&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;2.1.0-*&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;xunit.runner.dnx&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;2.1.0-beta2-build79&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;xunit.runner.visualstudio&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;2.1.0-beta1-build1051&amp;quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;...&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The final part required is the test itself:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;CustomerControllerIntegrationTest&lt;/span&gt;&lt;br /&gt;    {&lt;br /&gt;        [&lt;span class=&quot;hljs-meta&quot;&gt;Fact&lt;/span&gt;]&lt;br /&gt;        &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;ShouldReturnCustomer&lt;/span&gt;()&lt;/span&gt;&lt;br /&gt;        {&lt;br /&gt;            &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; config = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; Configuration();&lt;br /&gt;            config.AddCommandLine(&lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt;[] { &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;--server.urls&amp;quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;http://localhost:5001&amp;quot;&lt;/span&gt; });&lt;br /&gt;            &lt;br /&gt;            &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; serverFactoryLocation = &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt;.Empty;&lt;br /&gt;            &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt;(!IsMono()) {&lt;br /&gt;                serverFactoryLocation = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Microsoft.AspNet.Server.WebListener&amp;quot;&lt;/span&gt;;&lt;br /&gt;            } &lt;span class=&quot;hljs-keyword&quot;&gt;else&lt;/span&gt; {&lt;br /&gt;                serverFactoryLocation = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Kestrel&amp;quot;&lt;/span&gt;;&lt;br /&gt;            }&lt;br /&gt;            &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; context = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; HostingContext()&lt;br /&gt;            {&lt;br /&gt;                Configuration = config,&lt;br /&gt;                ServerFactoryLocation = serverFactoryLocation,&lt;br /&gt;                ApplicationName = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;AspNet5IntegrationTesting&amp;quot;&lt;/span&gt;,&lt;br /&gt;                StartupMethods = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; StartupMethods(builder =&amp;gt; builder.UseMvc(), services =&amp;gt;&lt;br /&gt;                {&lt;br /&gt;                    services.AddMvc();&lt;br /&gt;                    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; services.BuildServiceProvider();&lt;br /&gt;                })&lt;br /&gt;            };&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; (&lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; HostingEngine().Start(context))&lt;br /&gt;            {&lt;br /&gt;                &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; client = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; HttpClient();&lt;br /&gt;                &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; customers = client.GetAsync(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;http://localhost:5001/api/customers&amp;quot;&lt;/span&gt;).Result&lt;br /&gt;                    .Content.ReadAsAsync&amp;lt;List&amp;lt;Customer&amp;gt;&amp;gt;().Result;&lt;br /&gt;&lt;br /&gt;                Assert.Equal(customers[&lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;].CompanyName,&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;PDMLab&amp;quot;&lt;/span&gt;);&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;        &lt;br /&gt;        &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;IsMono&lt;/span&gt;()&lt;/span&gt;&lt;br /&gt;        {&lt;br /&gt;            &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; Type.GetType(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Mono.Runtime&amp;quot;&lt;/span&gt;) != &lt;span class=&quot;hljs-literal&quot;&gt;null&lt;/span&gt;;&lt;br /&gt;        }&lt;br /&gt; &lt;br /&gt;    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The implementation is pretty straight forward:&lt;/p&gt;
&lt;p&gt;First we&#39;re adding the host and port our test server should listen on to our application configuration.&lt;/p&gt;
&lt;p&gt;Then we&#39;re checking if we&#39;re running on mono and depdending on the result, we&#39;re bootstrapping Kestrel oder WebListener.&lt;/p&gt;
&lt;p&gt;After that we&#39;re bootstrapping a &lt;code&gt;HostingContext&lt;/code&gt; instance which configures our StartupMethods (which contains the stuff that resides in your &lt;code&gt;Startup.cs&lt;/code&gt; normally), Configuration and the host to use.&lt;/p&gt;
&lt;p&gt;Using this context we&#39;re firing up a new &lt;code&gt;HostingEngine&lt;/code&gt; instance which gets disposedd after the assertions have finished.&lt;/p&gt;
&lt;p&gt;That&#39;s it... almost, because there&#39;s one caveat:&lt;/p&gt;
&lt;p&gt;The tests currently fail on Kestrel / *nix with this error:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;xUnit.net DNX &lt;span class=&quot;hljs-built_in&quot;&gt;test&lt;/span&gt; runner (64-bit DNX 4.5.1)&lt;br /&gt;Copyright (C) 2015 Outercurve Foundation.&lt;br /&gt;&lt;br /&gt;Discovering: AspNet5IntegrationTesting&lt;br /&gt;Discovered:  AspNet5IntegrationTesting&lt;br /&gt;Starting:    AspNet5IntegrationTesting&lt;br /&gt;   AspNet5IntegrationTesting.Tests.CustomerControllerIntegrationTest.ShouldReturnCustomer [FAIL]&lt;br /&gt;      System.ArgumentException : GCHandle value belongs to a different domain&lt;br /&gt;      Stack Trace:&lt;br /&gt;           at System.Runtime.InteropServices.GCHandle.op_Explicit (IntPtr value) [0x00000] &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; &amp;lt;filename unknown&amp;gt;:0 &lt;br /&gt;           at System.Runtime.InteropServices.GCHandle.FromIntPtr (IntPtr value) [0x00000] &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; &amp;lt;filename unknown&amp;gt;:0 &lt;br /&gt;           at Microsoft.AspNet.Server.Kestrel.Networking.UvMemory.DestroyMemory (IntPtr memory) [0x00000] &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; &amp;lt;filename unknown&amp;gt;:0 &lt;br /&gt;           at Microsoft.AspNet.Server.Kestrel.Networking.UvLoopHandle.ReleaseHandle () [0x00000] &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; &amp;lt;filename unknown&amp;gt;:0 &lt;br /&gt;           at System.Runtime.InteropServices.SafeHandle.RunRelease () [0x00000] &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; &amp;lt;filename unknown&amp;gt;:0 &lt;br /&gt;           at System.Runtime.InteropServices.SafeHandle.Dispose (Boolean disposing) [0x00000] &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; &amp;lt;filename unknown&amp;gt;:0 &lt;br /&gt;           at System.Runtime.InteropServices.SafeHandle.Dispose () [0x00000] &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; &amp;lt;filename unknown&amp;gt;:0 &lt;br /&gt;           at Microsoft.AspNet.Server.Kestrel.KestrelThread.ThreadStart (System.Object parameter) [0x00000] &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; &amp;lt;filename unknown&amp;gt;:0 &lt;br /&gt;         --- End of stack trace from previous location &lt;span class=&quot;hljs-built_in&quot;&gt;where&lt;/span&gt; exception was thrown ---&lt;br /&gt;           at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x00000] &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; &amp;lt;filename unknown&amp;gt;:0 &lt;br /&gt;           at Microsoft.AspNet.Server.Kestrel.KestrelThread.Stop (TimeSpan &lt;span class=&quot;hljs-built_in&quot;&gt;timeout&lt;/span&gt;) [0x00000] &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; &amp;lt;filename unknown&amp;gt;:0 &lt;br /&gt;           at Microsoft.AspNet.Server.Kestrel.KestrelEngine.Dispose () [0x00000] &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; &amp;lt;filename unknown&amp;gt;:0 &lt;br /&gt;           at Kestrel.ServerFactory+&amp;lt;&amp;gt;c__DisplayClass3_0.&amp;lt;Start&amp;gt;b__1 () [0x00000] &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; &amp;lt;filename unknown&amp;gt;:0 &lt;br /&gt;           at Microsoft.AspNet.Server.Kestrel.Disposable.Dispose (Boolean disposing) [0x00000] &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; &amp;lt;filename unknown&amp;gt;:0 &lt;br /&gt;           at Microsoft.AspNet.Server.Kestrel.Disposable.Dispose () [0x00000] &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; &amp;lt;filename unknown&amp;gt;:0 &lt;br /&gt;           at Microsoft.AspNet.Hosting.HostingEngine+&amp;lt;&amp;gt;c__DisplayClass9_0.&amp;lt;Start&amp;gt;b__1 () [0x00000] &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; &amp;lt;filename unknown&amp;gt;:0 &lt;br /&gt;           at Microsoft.AspNet.Hosting.HostingEngine+Disposable.Dispose () [0x00000] &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; &amp;lt;filename unknown&amp;gt;:0 &lt;br /&gt;           at AspNet5IntegrationTesting.Tests.CustomerControllerIntegrationTest.ShouldReturnCustomer () [0x00000] &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; &amp;lt;filename unknown&amp;gt;:0 &lt;br /&gt;           at (wrapper managed-to-native) System.Reflection.MonoMethod:InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&amp;amp;)&lt;br /&gt;           at System.Reflection.MonoMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00000] &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; &amp;lt;filename unknown&amp;gt;:0 &lt;br /&gt;Finished:    AspNet5IntegrationTesting&lt;br /&gt;&lt;br /&gt;=== TEST EXECUTION SUMMARY ===&lt;br /&gt;   AspNet5IntegrationTesting  Total: 1, Errors: 0, Failed: 1, Skipped: 0, Time: 0.697s&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If somebody has an idea about this, please send me a &lt;a href=&quot;https://github.com/AlexZeitler/AspNet5IntegrationTesting&quot;&gt;pull request at GitHub&lt;/a&gt; (yes, you can grab the source over there) ;-)&lt;/p&gt;
&lt;p&gt;Update [2015/05/17]:
This is a known Kestrel issue as described &lt;a href=&quot;https://github.com/aspnet/KestrelHttpServer/issues/9&quot;&gt;here&lt;/a&gt; (&lt;a href=&quot;https://github.com/aspnet/KestrelHttpServer/issues/9#issuecomment-67818250&quot;&gt;Description starts here&lt;/a&gt;) and a fix is on the way.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>A lap around AWS and docker-machine</title>
    <link href="https://alexanderzeitler.com/articles/a-lap-around-aws-and-docker-machine/"/>
    <updated>2015-08-16T21:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/a-lap-around-aws-and-docker-machine/</id>
    <content type="html">&lt;p&gt;This post will show you how can create a Docker Machine instance on AWS (EC2) starting from scratch. This means you&#39;re starting with your free AWS account with nothing configured after you have signed up for your free AWS trial account.&lt;/p&gt;
&lt;p&gt;To create a Docker machine instance in AWS, the &lt;code&gt;docker-machine&lt;/code&gt; command requires several &lt;a href=&quot;https://docs.docker.com/machine/drivers/aws/&quot;&gt;params for the AWS driver&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;At a minimum, we need to provide these params:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;amazonec2-access-key&lt;/li&gt;
&lt;li&gt;amazonec2-secret-key&lt;/li&gt;
&lt;li&gt;amazonec2-region&lt;/li&gt;
&lt;li&gt;amazonec2-zone&lt;/li&gt;
&lt;li&gt;amazonec2-vpc-id&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To get the &lt;code&gt;amazonec2-access-key&lt;/code&gt; and the &lt;code&gt;amazonec2-secret-key&lt;/code&gt; we need to have an Amazon EC2 Access Key and an Amazon EC2 Secret Key.&lt;/p&gt;
&lt;p&gt;Both of them can be obtained by &lt;a href=&quot;https://console.aws.amazon.com/iam/home?#users&quot;&gt;creating a user in IAM&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/a-lap-around-aws-and-docker-machine/iamempty.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Clicking on the &amp;quot;Create New Users&amp;quot; button will bring up this view where we create a new user named &amp;quot;awsdockeruser&amp;quot;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/a-lap-around-aws-and-docker-machine/awsdockeruser.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Make sure to check &amp;quot;Generate an access key for each user&amp;quot;.&lt;/p&gt;
&lt;p&gt;After creating the user, we&#39;ll get both keys:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/a-lap-around-aws-and-docker-machine/awsiamkeys.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Make sure to create a copy at safe place as this is the last time you&#39;ll see them in IAM.&lt;/p&gt;
&lt;p&gt;In order to manage AWS EC2 instances we need the appropriate permissions.
To assign the permission to manage EC2 instances our &lt;code&gt;awsdockeruser&lt;/code&gt; needs to be a member of a group which has that permission.&lt;/p&gt;
&lt;p&gt;So lets create that group in the IAM dashboard:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/a-lap-around-aws-and-docker-machine/awsiamgroups.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;We&#39;ll called it &amp;quot;awsdockergroup&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/a-lap-around-aws-and-docker-machine/awsdockergroup.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Next, assign the policy to manage EC2 instances (there might be lower privileges that are sufficient):&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/a-lap-around-aws-and-docker-machine/ec2fullaccess.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Next, lets add the &amp;quot;awsdockeruser&amp;quot; to our group:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/a-lap-around-aws-and-docker-machine/awsdockergroupdetails.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/a-lap-around-aws-and-docker-machine/awsdockeruserinawsdockergroup.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Next, we need to know region, zone and VPC id.
These can be obtained by using the AWS CLI.&lt;/p&gt;
&lt;p&gt;On Linux and OS X, it can be installed using &lt;a href=&quot;https://pip.pypa.io/en/stable/installing.html&quot;&gt;PIP&lt;/a&gt; package manager.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install awscli&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then AWS CLI needs to be configured:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;aws configure&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;AWS CLI configurations asks you for your &lt;code&gt;AWS Access Key ID&lt;/code&gt;, your &lt;code&gt;AWS Secret Access Key&lt;/code&gt; (remember them? 😀), &lt;code&gt;Default region name&lt;/code&gt; (&lt;a href=&quot;http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html&quot;&gt;codes can be found here&lt;/a&gt; - I&#39;ve choosen &lt;code&gt;eu-central-1&lt;/code&gt;), and &lt;code&gt;Default output format&lt;/code&gt; which I set to &lt;code&gt;json&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To get the vpc-id, just run:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;aws ec2 describe-subnets&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output will look like this:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;{&lt;br /&gt;    &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Subnets&amp;quot;&lt;/span&gt;: [&lt;br /&gt;        {&lt;br /&gt;            &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;VpcId&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&amp;lt;avpcid&amp;gt;&amp;quot;&lt;/span&gt;,&lt;br /&gt;            &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;CidrBlock&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;172.31.0.0/20&amp;quot;&lt;/span&gt;,&lt;br /&gt;            &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;MapPublicIpOnLaunch&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;,&lt;br /&gt;            &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;DefaultForAz&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;,&lt;br /&gt;            &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;State&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;available&amp;quot;&lt;/span&gt;,&lt;br /&gt;            &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;AvailabilityZone&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;eu-central-1a&amp;quot;&lt;/span&gt;,&lt;br /&gt;            &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;SubnetId&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&amp;lt;asubnetid&amp;gt;&amp;quot;&lt;/span&gt;,&lt;br /&gt;            &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;AvailableIpAddressCount&amp;quot;&lt;/span&gt;: 4091&lt;br /&gt;        },&lt;br /&gt;        {&lt;br /&gt;            &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;VpcId&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&amp;lt;anothervpcid&amp;gt;&amp;quot;&lt;/span&gt;,&lt;br /&gt;            &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;CidrBlock&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;172.31.16.0/20&amp;quot;&lt;/span&gt;,&lt;br /&gt;            &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;MapPublicIpOnLaunch&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;,&lt;br /&gt;            &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;DefaultForAz&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;,&lt;br /&gt;            &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;State&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;available&amp;quot;&lt;/span&gt;,&lt;br /&gt;            &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;AvailabilityZone&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;eu-central-1b&amp;quot;&lt;/span&gt;,&lt;br /&gt;            &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;SubnetId&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&amp;lt;anothersubnetid&amp;gt;&amp;quot;&lt;/span&gt;,&lt;br /&gt;            &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;AvailableIpAddressCount&amp;quot;&lt;/span&gt;: 4091&lt;br /&gt;        }&lt;br /&gt;    ]&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;amazonec2-zone&lt;/code&gt; param is the last character of the &lt;code&gt;AvailabilityZone&lt;/code&gt; of the subnet you choose to use, so &lt;code&gt;a&lt;/code&gt; or &lt;code&gt;b&lt;/code&gt; here.&lt;/p&gt;
&lt;p&gt;Ok, it&#39;s time to spin up our Docker machine instance...&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;docker-machine create &#92; &lt;br /&gt;				-d amazonec2 &#92;&lt;br /&gt;				--amazonec2-access-key &amp;lt;YOURACCESSKEY&amp;gt; &#92;&lt;br /&gt;				--amazonec2-secret-key &amp;lt;YOURSECRETKEY&amp;gt; &#92;&lt;br /&gt;				--amazonec2-zone a &#92;&lt;br /&gt;				--amazonec2-region eu-central-1 &#92;&lt;br /&gt;				--amazonec2-vpc-id &amp;lt;YOURVPCID&amp;gt; &lt;br /&gt;				awsdocker&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After about 60 seconds, your console should confirm your machine is ready to rock&#39;n roll&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Launching instance...&lt;br /&gt;To see how to connect Docker to this machine, run: docker-machine &lt;span class=&quot;hljs-built_in&quot;&gt;env&lt;/span&gt; awsdocker&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To connect to the machine, run this command:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;eval&lt;/span&gt; $(docker-machine &lt;span class=&quot;hljs-built_in&quot;&gt;env&lt;/span&gt; awsdocker)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To make sure everything works as expected, just run &lt;code&gt;docker ps&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you&#39;re working with various machines, you might want to know which is your current active machine:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cloud.githubusercontent.com/assets/287480/9203727/129f86ce-4059-11e5-8988-953b6f6fe231.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You can have this in your prompt if you use my bash prompt definiton from &lt;a href=&quot;https://gist.github.com/AlexZeitler/69140578a1bb13483242&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;docker-machine ip awsdocker&lt;/code&gt; you can get the public IP address of your machine.&lt;/p&gt;
&lt;p&gt;If you&#39;re deploying some containers, you might wonder, why you can`t access your containers exposed ports like http://&amp;lt;machine-ip&amp;gt;:&amp;lt;someport&amp;gt;: because firewall 😱.&lt;/p&gt;
&lt;p&gt;So head over to the &lt;a href=&quot;https://eu-central-1.console.aws.amazon.com/ec2/v2/home?region=eu-central-1#SecurityGroups:sort=groupId&quot;&gt;EC2 dashboard&lt;/a&gt; &amp;quot;Security Groups&amp;quot; section, select your &amp;quot;docker-machine&amp;quot; Security Group (which has been created when spinning up your machine) and make sure to allow some inbound traffic:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/a-lap-around-aws-and-docker-machine/vpcsecuritygroup.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Happy shipping! 😀&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Docker Machine: NS_ERROR_FAILURE Failed to create the host-only adapter</title>
    <link href="https://alexanderzeitler.com/articles/docker-machine-ns-error-failure-failed-to-create-the-host-only-adapter/"/>
    <updated>2016-01-16T17:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/docker-machine-ns-error-failure-failed-to-create-the-host-only-adapter/</id>
    <content type="html">&lt;p&gt;If you try to start a docker machine using &lt;code&gt;docker-machine start &amp;lt;machinename&amp;gt;&lt;/code&gt;, you might receive this error message:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Progress state: NS_ERROR_FAILURE&lt;br /&gt;VBoxManage: error: Failed to create the host-only adapter&lt;br /&gt;VBoxManage: error: VBoxNetAdpCtl: Error &lt;span class=&quot;hljs-keyword&quot;&gt;while&lt;/span&gt; adding new interface: failed to open /dev/vboxnetctl: No such file or directory&lt;br /&gt;VBoxManage: error: Details: code NS_ERROR_FAILURE (0x80004005), component HostNetworkInterface, interface IHostNetworkInterface&lt;br /&gt;VBoxManage: error: Context: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;int handleCreate(HandlerArg*, int, int*)&amp;quot;&lt;/span&gt; at line 66 of file VBoxManageHostonly.cpp&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To resolve this issue, you have to setup the &lt;code&gt;vboxdrv&lt;/code&gt; kernel module using&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; /etc/init.d/vboxdrv setup&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  
  <entry>
    <title>Rename Visual Studio project including files, folders and namespaces</title>
    <link href="https://alexanderzeitler.com/articles/rename-visual-studio-project-namespaces-and-folders-automate-everything/"/>
    <updated>2016-01-29T17:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/rename-visual-studio-project-namespaces-and-folders-automate-everything/</id>
    <content type="html">&lt;p&gt;TL;DR&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It&#39;s 2016 and Visual Studio doesn&#39;t support us in renaming Projects including files, folders and namespaces.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Considering you have a Visual Studio solution named &lt;code&gt;PDMLab.AwesomeProject.WithAnUglyTypo&lt;/code&gt; and this solution contains multiple projects all starting with &lt;code&gt;PDMLab.AwesomeProject.WithAnUglyTypo&lt;/code&gt;. The projects also may contain default namespace names starting using the solution name and the assemblies might be named accordingly.&lt;/p&gt;
&lt;p&gt;One solution is to use Notepad++ to replace every occurrence of &lt;code&gt;PDMLab.AwesomeProject.WithAnUglyTypo&lt;/code&gt; and rename the folders by hand or another tool.&lt;br /&gt;
Well, this works but I&#39;m more a console user...&lt;/p&gt;
&lt;p&gt;That said, the solution shown below asumes you have git bash installed.&lt;/p&gt;
&lt;p&gt;Replacing the contents like namespaces in the files:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;hljs-meta prompt_&quot;&gt;# &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;run this &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; the root folder of your project&lt;/span&gt;&lt;br /&gt;find . -name &amp;quot;*.*&amp;quot; -print0 | xargs -0 sed -i &amp;#x27;&amp;#x27; -e &amp;#x27;s/PDMLab&#92;.AwesomeProject&#92;.WithAnUglyTypo/PDMLab.AwesomeProject.WithoutAnUglyTypo/g&amp;#x27;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Renaming files and folders:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;hljs-meta prompt_&quot;&gt;# &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;run this &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; the root folder of your project&lt;/span&gt;&lt;br /&gt;for i in $(find * -maxdepth 1); do mv $i $(echo $i | sed &amp;#x27;s/PDMLab&#92;.AwesomeProject&#92;.WithAnUglyTypo/PDMLab.AwesomeProject.WithoutAnUglyTypo/&amp;#x27;); done&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The advantage of this solution: You can create an alias for both commands that expects the old and new name as an param and you&#39;re done.&lt;/p&gt;
&lt;p&gt;Make sure you have a backup of your files before you try that - I&#39;m not responsible if something goes wrong...&lt;/p&gt;
&lt;p&gt;Happy renaming :)&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Chrome crashes on Ubuntu in VMware Workstation</title>
    <link href="https://alexanderzeitler.com/articles/chrome-crashing-in-ubuntu-vmware-workstation/"/>
    <updated>2016-01-29T17:30:00Z</updated>
    <id>https://alexanderzeitler.com/articles/chrome-crashing-in-ubuntu-vmware-workstation/</id>
    <content type="html">&lt;p&gt;If you&#39;re using Chrome on Ubuntu in a VMware Workstation VM it is likely, that it has been crashing after being in the background for a while. Sometimes it also kills the VM entirely.&lt;/p&gt;
&lt;p&gt;For me the solution was to disable hardware accelartion completely - the performance isn&#39;t that bad after all.&lt;/p&gt;
&lt;p&gt;Go to Chrome settings, then show advanced settings and disable hardware acceleration:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/chrome-crashing-in-ubuntu-vmware-workstation/disablehardwareaccelerationinchrome.png&quot; alt=&quot;Disabling hardware acceleration in Chrome&quot; /&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Workflows: using Docker Machine and Docker Compose together in development</title>
    <link href="https://alexanderzeitler.com/articles/docker-machine-and-docker-compose-developer-workflows/"/>
    <updated>2016-02-01T01:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/docker-machine-and-docker-compose-developer-workflows/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@alexzeitler_&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@alexzeitler_&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Using Docker Machine and Docker Compose together in development&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Ever used Docker Machine and Compose together? Read about it here!&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/docker-machine-and-docker-compose-developer-workflows/docker-machine-and-compose-twitter.png&quot; /&gt;
&lt;p&gt;&lt;a href=&quot;https://pdmlab.com/&quot;&gt;We&lt;/a&gt;&#39;re using the Docker eco system now for a while in development and production.&lt;br /&gt;
As you may know, the Docker eco system not only consists of the Docker engine (client and server), but it also provides &lt;a href=&quot;https://docs.docker.com/machine/&quot;&gt;Docker Machine&lt;/a&gt; and &lt;a href=&quot;https://docs.docker.com/compose/&quot;&gt;Docker Compose&lt;/a&gt;.&lt;br /&gt;
While Docker Compose makes linking containers much easier, Docker Machine provides an abstraction for your environment where your Docker engine is being executed in.
Thus, Machine allows you to manage your Docker engine Host in a consistent way no matter whether you&#39;re on AWS, Azure or on premise - or even your local dev environment.&lt;/p&gt;
&lt;p&gt;Docker Machine and Docker Compose work together pretty well in production, but when using them in development, you might experience some issues - some of which I&#39;ll describe in this post and show solutions for it.&lt;/p&gt;
&lt;p&gt;This post is not about using Docker, Docker Machine or Compose in production!&lt;/p&gt;
&lt;h1&gt;Plain Docker development workflow with Node.js / Express&lt;/h1&gt;
&lt;p&gt;First, some basics. Let&#39;s consider we&#39;re composing an Docker application using a MongoDb database and we&#39;re implementing a Node.js application that uses MongoDb.&lt;/p&gt;
&lt;p&gt;A basic Docker workflow without Machine and Compose would look like this:&lt;/p&gt;
&lt;p&gt;Create a &lt;code&gt;Dockerfile&lt;/code&gt; for our Node.js application:&lt;/p&gt;
&lt;pre class=&quot;language-dockerfile&quot;&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;FROM&lt;/span&gt; node:&lt;span class=&quot;hljs-number&quot;&gt;4.2&lt;/span&gt;.&lt;span class=&quot;hljs-number&quot;&gt;3&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;EXPOSE&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;3000&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;EXPOSE&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;5858&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;COPY&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; . /app&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;WORKDIR&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; /app&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;RUN&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;cd&lt;/span&gt; /app; npm install&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;CMD&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; [&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;node&amp;quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;app.js&amp;quot;&lt;/span&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create our simplified Node.js appliation in &lt;code&gt;app.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; express = &lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;express&amp;#x27;&lt;/span&gt;);&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; app = &lt;span class=&quot;hljs-title function_&quot;&gt;express&lt;/span&gt;();&lt;br /&gt;&lt;br /&gt;app.&lt;span class=&quot;hljs-title function_&quot;&gt;use&lt;/span&gt;(express.&lt;span class=&quot;hljs-title function_&quot;&gt;static&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;public&amp;#x27;&lt;/span&gt;));&lt;br /&gt;app.&lt;span class=&quot;hljs-title function_&quot;&gt;listen&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;3000&lt;/span&gt;);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Place an &lt;code&gt;index.html&lt;/code&gt; inside the &lt;code&gt;public&lt;/code&gt; folder:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;&amp;lt;!DOCTYPE &lt;span class=&quot;hljs-keyword&quot;&gt;html&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;lang&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;en&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;head&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;meta&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;charset&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;UTF-8&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;title&lt;/span&gt;&amp;gt;&lt;/span&gt;Hello&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;title&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;head&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;body&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;Hello&lt;br /&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;body&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;html&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can build our image like this:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;docker build -t my-node-app .&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we can run our container based on this image:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;docker run -it --name=my-node-app-container -p 3000:3000 my-node-app&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pointing our browser to &lt;code&gt;http://localhost:3000/&lt;/code&gt; will return this result:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/docker-machine-and-docker-compose-developer-workflows/localhost3000static.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If we&#39;re changing something in our &lt;code&gt;index.html&lt;/code&gt;, we have to stop and remove our container, rebuild the image and start the container again:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;docker stop my-node-app-container&lt;br /&gt;docker &lt;span class=&quot;hljs-built_in&quot;&gt;rm&lt;/span&gt; my-node-app-container&lt;br /&gt;docker build -t my-node-app .&lt;br /&gt;docker run -it --name=my-node-app-container -p 3000:3000 my-node-app&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also this happens quite fast, we can get it even faster by mounting our local project folder as a volume for our container:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;docker build -t my-node-app .&lt;br /&gt;docker run -d -it --name=my-node-app-container -v $(&lt;span class=&quot;hljs-built_in&quot;&gt;pwd&lt;/span&gt;):/app -p 3000:3000 my-node-app&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By adding the &lt;code&gt;-v $(pwd):/app&lt;/code&gt; parameter to our &lt;code&gt;docker run&lt;/code&gt; command, we&#39;re mapping the local project folder into the &lt;code&gt;/app&lt;/code&gt; folder in our container.&lt;/p&gt;
&lt;p&gt;Now we can modify the content in below our &lt;code&gt;public&lt;/code&gt; folder and just hit F5 in our browser to get the latest changes.&lt;/p&gt;
&lt;p&gt;Let&#39;s get a bit further and add an API to our application and change the &lt;code&gt;app.js&lt;/code&gt; as follows:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; express = &lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;express&amp;#x27;&lt;/span&gt;);&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; app = &lt;span class=&quot;hljs-title function_&quot;&gt;express&lt;/span&gt;();&lt;br /&gt;&lt;br /&gt;app.&lt;span class=&quot;hljs-title function_&quot;&gt;use&lt;/span&gt;(express.&lt;span class=&quot;hljs-title function_&quot;&gt;static&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;public&amp;#x27;&lt;/span&gt;));&lt;br /&gt;&lt;br /&gt;app.&lt;span class=&quot;hljs-title function_&quot;&gt;get&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;/hello&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; (&lt;span class=&quot;hljs-params&quot;&gt;req, res&lt;/span&gt;) {&lt;br /&gt;    res.&lt;span class=&quot;hljs-title function_&quot;&gt;send&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;world&amp;#x27;&lt;/span&gt;);&lt;br /&gt;});&lt;br /&gt;&lt;br /&gt;app.&lt;span class=&quot;hljs-title function_&quot;&gt;listen&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;3000&lt;/span&gt;);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After this, we can run HTTPIe (or curl / wget) &lt;code&gt;http get localhost:3000/hello&lt;/code&gt; and receive an output like this:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;HTTP/1.1 200 OK&lt;br /&gt;Connection: keep-alive&lt;br /&gt;Content-Length: 5&lt;br /&gt;Content-Type: text/html; charset=utf-8&lt;br /&gt;Date: Sat, 23 Jan 2016 20:55:07 GMT&lt;br /&gt;ETag: W/&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;5-fXkwN6B2AYZXSwKC8vQ15w&amp;quot;&lt;/span&gt;&lt;br /&gt;X-Powered-By: Express&lt;br /&gt;&lt;br /&gt;world&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Changing something in the API and running HTTPIe won&#39;t result in updated output.&lt;br /&gt;
This is because express needs to be initialized again to run the app with the changes made.&lt;/p&gt;
&lt;p&gt;We have two options to re-initialize the container: restart the container or kill it and run it again.&lt;/p&gt;
&lt;p&gt;Restarting the container is a single command: &lt;code&gt;docker restart my-node-app-container&lt;/code&gt;.&lt;br /&gt;
Also this command is pretty simple, it may take a few seconds to gracefully stop and start the container.&lt;/p&gt;
&lt;p&gt;The second option is a bit faster and looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;docker &lt;span class=&quot;hljs-built_in&quot;&gt;kill&lt;/span&gt; my-node-app-container&lt;br /&gt;docker run -d -it --name=my-node-app-container -v $(&lt;span class=&quot;hljs-built_in&quot;&gt;pwd&lt;/span&gt;):/app -p 3000:3000 my-node-app&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Plain Docker workflow with Node.js and MongoDb&lt;/h1&gt;
&lt;p&gt;As said earlier in this post, our goal is to have an application that runs on Node.js and uses MongoDb.&lt;br /&gt;
Adding MongoDb to our game is as simple as this:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;docker run -d -it --name=some-mongo -p 27017:27017 mongo&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In order to connect our Node.js App container with the MongoDb container, we have to run it using:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;docker run -d -it --name=my-node-app-container -v $(&lt;span class=&quot;hljs-built_in&quot;&gt;pwd&lt;/span&gt;):/app -p 3000:3000 --&lt;span class=&quot;hljs-built_in&quot;&gt;link&lt;/span&gt; some-mongo:mongo my-node-app&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now let&#39;s change our Node.js application to use MongoDb and write the request body of a &lt;code&gt;POST&lt;/code&gt; to &lt;code&gt;/hello&lt;/code&gt; to the &lt;code&gt;documents&lt;/code&gt; colleciton in MongoDb:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; express = &lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;express&amp;#x27;&lt;/span&gt;);&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; app = &lt;span class=&quot;hljs-title function_&quot;&gt;express&lt;/span&gt;();&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; bodyparser = &lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;body-parser&amp;#x27;&lt;/span&gt;);&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;MongoClient&lt;/span&gt; = &lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;mongodb&amp;#x27;&lt;/span&gt;).&lt;span class=&quot;hljs-property&quot;&gt;MongoClient&lt;/span&gt;;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; url = &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;mongodb://&amp;#x27;&lt;/span&gt; + process.&lt;span class=&quot;hljs-property&quot;&gt;env&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;MONGO_PORT_27017_TCP_ADDR&lt;/span&gt; + &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;:27017/dockerdemo&amp;#x27;&lt;/span&gt;;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; db;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-title class_&quot;&gt;MongoClient&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;connect&lt;/span&gt;(url, &lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; (&lt;span class=&quot;hljs-params&quot;&gt;err, database&lt;/span&gt;) {&lt;br /&gt;    &lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Connected correctly to server&amp;quot;&lt;/span&gt;);&lt;br /&gt;    db = database;&lt;br /&gt;});&lt;br /&gt;&lt;br /&gt;app.&lt;span class=&quot;hljs-title function_&quot;&gt;use&lt;/span&gt;(bodyparser.&lt;span class=&quot;hljs-title function_&quot;&gt;json&lt;/span&gt;());&lt;br /&gt;app.&lt;span class=&quot;hljs-title function_&quot;&gt;use&lt;/span&gt;(express.&lt;span class=&quot;hljs-title function_&quot;&gt;static&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;public&amp;#x27;&lt;/span&gt;));&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; insertDocument = &lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; (&lt;span class=&quot;hljs-params&quot;&gt;db, &lt;span class=&quot;hljs-variable language_&quot;&gt;document&lt;/span&gt;, callback&lt;/span&gt;) {&lt;br /&gt;    &lt;span class=&quot;hljs-comment&quot;&gt;// Get the documents collection&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; collection = db.&lt;span class=&quot;hljs-title function_&quot;&gt;collection&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;documents&amp;#x27;&lt;/span&gt;);&lt;br /&gt;    &lt;span class=&quot;hljs-comment&quot;&gt;// Insert some documents&lt;/span&gt;&lt;br /&gt;    collection.&lt;span class=&quot;hljs-title function_&quot;&gt;insertOne&lt;/span&gt;(&lt;span class=&quot;hljs-variable language_&quot;&gt;document&lt;/span&gt;, &lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; (&lt;span class=&quot;hljs-params&quot;&gt;err, result&lt;/span&gt;) {&lt;br /&gt;        &lt;span class=&quot;hljs-title function_&quot;&gt;callback&lt;/span&gt;(err, &lt;span class=&quot;hljs-title class_&quot;&gt;JSON&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;stringify&lt;/span&gt;(result.&lt;span class=&quot;hljs-property&quot;&gt;ops&lt;/span&gt;[&lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;]));&lt;br /&gt;    });&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;app.&lt;span class=&quot;hljs-title function_&quot;&gt;post&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;/hello&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; (&lt;span class=&quot;hljs-params&quot;&gt;req, res&lt;/span&gt;) {&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; data = req.&lt;span class=&quot;hljs-property&quot;&gt;body&lt;/span&gt;;&lt;br /&gt;    &lt;span class=&quot;hljs-title function_&quot;&gt;insertDocument&lt;/span&gt;(db, data, &lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;err, result&lt;/span&gt;) {&lt;br /&gt;        res.&lt;span class=&quot;hljs-title function_&quot;&gt;status&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;201&lt;/span&gt;).&lt;span class=&quot;hljs-title function_&quot;&gt;send&lt;/span&gt;(result)&lt;br /&gt;    })&lt;br /&gt;});&lt;br /&gt;&lt;br /&gt;app.&lt;span class=&quot;hljs-title function_&quot;&gt;get&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;/hello&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; (&lt;span class=&quot;hljs-params&quot;&gt;req, res&lt;/span&gt;) {&lt;br /&gt;    res.&lt;span class=&quot;hljs-title function_&quot;&gt;send&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;world&amp;#x27;&lt;/span&gt;);&lt;br /&gt;});&lt;br /&gt;&lt;br /&gt;app.&lt;span class=&quot;hljs-title function_&quot;&gt;listen&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;3000&lt;/span&gt;);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sending a request using to our API like this &lt;code&gt;http post http://localhost:3000/hello name=PDMLab&lt;/code&gt; will result in a successful response:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;HTTP/1.1 201 Created&lt;br /&gt;Connection: keep-alive&lt;br /&gt;Content-Length: 50&lt;br /&gt;Content-Type: application/json; charset=utf-8&lt;br /&gt;Date: Wed, 27 Jan 2016 21:54:21 GMT&lt;br /&gt;ETag: W/&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;32-YLqScoop9RrYa0x7JH+FIg&amp;quot;&lt;/span&gt;&lt;br /&gt;X-Powered-By: Express&lt;br /&gt;&lt;br /&gt;{&lt;br /&gt;    &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;_id&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;56a93c8d51a0630100b28294&amp;quot;&lt;/span&gt;, &lt;br /&gt;    &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;name&amp;quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;PDMLab&amp;quot;&lt;/span&gt;&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using &lt;code&gt;nodemon&lt;/code&gt; we can &lt;code&gt;kill&lt;/code&gt; and &lt;code&gt;start&lt;/code&gt; the containers as shown before and we have a nice development workflow.&lt;/p&gt;
&lt;h1&gt;Simplifying container linking using Docker Compose&lt;/h1&gt;
&lt;p&gt;Now lets go one step further and add Docker Compose to handle the container linking.&lt;/p&gt;
&lt;p&gt;Using Compose, you don&#39;t link containers using the Docker CLI, but instead you create a file named &lt;code&gt;docker-compose.yml&lt;/code&gt; (you can name it as you like but the Compose CLI uses this by default).&lt;/p&gt;
&lt;p&gt;The Compose file for our application looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;mongo:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;image:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;mongo:2.6.11&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;ports:&lt;/span&gt;&lt;br /&gt;   &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;27017:27017&amp;quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;application:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;build:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;command:&lt;/span&gt;  &lt;span class=&quot;hljs-string&quot;&gt;node&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;--debug=5858&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;app.js&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;--color=always&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;ports:&lt;/span&gt;&lt;br /&gt;   &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;3000:3000&amp;quot;&lt;/span&gt;&lt;br /&gt;   &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;5858:5858&amp;quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;volumes :&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;./:/app&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;links:&lt;/span&gt;&lt;br /&gt;   &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;mongo&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When issuing the &lt;code&gt;http POST&lt;/code&gt; as shown above, we&#39;ll get a similar result.&lt;/p&gt;
&lt;p&gt;Refreshing our &lt;code&gt;index.html&lt;/code&gt; without restarting the containers still works. This happens because we&#39;re mapping the current folder to the &lt;code&gt;/app&lt;/code&gt; folder using inside the &lt;code&gt;volumes&lt;/code&gt; section of the &lt;code&gt;appliation&lt;/code&gt; container definition.&lt;/p&gt;
&lt;p&gt;Restarting the container can be automated using nodemon like this:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;nodemon -x docker-compose up&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will ensure that changes to the API part of our application are applied as shown before.&lt;/p&gt;
&lt;h1&gt;Adding Docker Machine to the scene&lt;/h1&gt;
&lt;p&gt;Now it&#39;s time to introduce Docker Machine and show how it plays together with our existing setup.&lt;/p&gt;
&lt;p&gt;We can simply create a new Docker Machine instance named &lt;code&gt;default&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;docker-machine create --driver=virtualbox default&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To make sure our Docker client talks to the Docker engine in our &lt;code&gt;default&lt;/code&gt; machine, we&#39;ll update the environment:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;eval&lt;/span&gt; $(docker-machine &lt;span class=&quot;hljs-built_in&quot;&gt;env&lt;/span&gt; default)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First, lets try to build and run our first sample from above with Docker Machine:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;docker run -d -it --name=my-node-app-container -p 3000:3000 my-node-app&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pointing the browser again to &lt;code&gt;http://localhost:3000&lt;/code&gt; will fail to access the site.&lt;/p&gt;
&lt;p&gt;This happens because port &lt;code&gt;3000&lt;/code&gt; is not open in VirtualBox where the VM (our machine named &lt;code&gt;default&lt;/code&gt;)  is running. To solve this, we can open the port using the VirtualBox port forwading Network setting for the machine or simply run &lt;code&gt;VBoxManage&lt;/code&gt; on the console:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;VBoxManage modifyvm &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;default&amp;quot;&lt;/span&gt; --natpf1 &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;default,tcp,,3000,,3000&amp;quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Trying to open the website again then will result in the expected &lt;code&gt;Hello World&lt;/code&gt; response.&lt;/p&gt;
&lt;p&gt;Another option (my preference) to access our application running in the machine, is to use the IP address of the machine. To retrieve it, we call&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;docker-machine ip default&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will result, for example, in &lt;code&gt;192.168.99.100&lt;/code&gt; and pointing our browser to &lt;code&gt;http://192.168.99.100:3000/&lt;/code&gt; will return the expected response as well.&lt;/p&gt;
&lt;h1&gt;Docker Machine and Volumes&lt;/h1&gt;
&lt;p&gt;The next thing we tried was the usage of Volumes and we&#39;ll now try this with Docker Machine as well.&lt;/p&gt;
&lt;p&gt;Our command was this, so lets use it again:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;docker run -d -it --name=my-node-app-container -v $(&lt;span class=&quot;hljs-built_in&quot;&gt;pwd&lt;/span&gt;):/app -p 3000:3000 my-node-app&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The console result will look fine at a first sight:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;098b5a3387df42092bec984105bf9f6725e11088d3f62d72a893f586cb33bc50&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But when we try to open the application in our browser, it fails again.&lt;/p&gt;
&lt;p&gt;Looking at our list of running containers using &lt;code&gt;docker ps&lt;/code&gt; doesn&#39;t show our &lt;code&gt;my-node-app-container&lt;/code&gt; running.&lt;br /&gt;
What happened?&lt;/p&gt;
&lt;p&gt;Let&#39;s dig a bit deeper using &lt;code&gt;docker logs my-node-app-container&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The result will look like this:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;module.js:339&lt;br /&gt;    throw err;&lt;br /&gt;    ^&lt;br /&gt;&lt;br /&gt;Error: Cannot find module &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;/app/app.js&amp;#x27;&lt;/span&gt;&lt;br /&gt;    at Function.Module._resolveFilename (module.js:337:15)&lt;br /&gt;    at Function.Module._load (module.js:287:25)&lt;br /&gt;    at Function.Module.runMain (module.js:467:10)&lt;br /&gt;    at startup (node.js:136:18)&lt;br /&gt;    at node.js:963:3&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The reason for this is that we&#39;re telling the Docker engine to map our local directory to the &lt;code&gt;/app&lt;/code&gt; folder inside the container. The problem here: our local folder doesn&#39;t exist in the Docker Machine &lt;code&gt;default&lt;/code&gt; thats running inside VirtualBox.&lt;/p&gt;
&lt;p&gt;Docker Machine provides a solution for this. We can use the &lt;code&gt;ssh&lt;/code&gt; command provided by Docker Machine to create the &amp;quot;local&amp;quot; folder inside the machine and then use the Docker Machine &lt;code&gt;scp&lt;/code&gt; command to copy the files from our host into that folder inside the machine:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;docker-machine ssh default &lt;span class=&quot;hljs-built_in&quot;&gt;mkdir&lt;/span&gt; -p $(&lt;span class=&quot;hljs-built_in&quot;&gt;pwd&lt;/span&gt;)&lt;br /&gt;docker-machine scp -r . default:$(&lt;span class=&quot;hljs-built_in&quot;&gt;pwd&lt;/span&gt;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Copying the files has to be done every time our source code changes. Because the scp command is a all or nothing operation, I was looking for another solution that copies only the file changed. One option would be Grunt or Gulp to get the particular file change but I&#39;m not a friend of these tools.&lt;/p&gt;
&lt;p&gt;A wide spread tool is &lt;code&gt;rsync&lt;/code&gt; which exactly does what we want: copy all changed files.&lt;/p&gt;
&lt;p&gt;The good part of the story: in the end it works. The bad part: There&#39;s &amp;quot;a little&amp;quot; work to do.&lt;/p&gt;
&lt;p&gt;First, to make sure all further &lt;code&gt;rsync&lt;/code&gt; commands work as expected, we need to get the SSH key to be used by rsync.&lt;/p&gt;
&lt;p&gt;This is done by the following commands:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;eval&lt;/span&gt; $(ssh-agent)&lt;br /&gt;ssh-add ~/.docker/machine/machines/default/id_rsa&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we need to know that the OS running inside Docker Machine does NOT provide an &lt;code&gt;rsync&lt;/code&gt; installation out of the box.
To the resuce, boot2docker (based on Tiny Core Linux), the OS running inside Docker Machine, comes with an package manager named &lt;code&gt;tce&lt;/code&gt; and using the following command, we can installed &lt;code&gt;rsync&lt;/code&gt; inside our Docker Machine:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;docker-machine ssh default -- tce-load -wi rsync&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we have to create the directory with the same path as our local path in our Docker Machine and then sync our local directory to the Docker Machine:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;docker-machine ssh default &lt;span class=&quot;hljs-built_in&quot;&gt;mkdir&lt;/span&gt; -p $(&lt;span class=&quot;hljs-built_in&quot;&gt;pwd&lt;/span&gt;)&lt;br /&gt;rsync -avzhe ssh --relative --omit-dir-times --progress ./ docker@$(docker-machine ip default):$(&lt;span class=&quot;hljs-built_in&quot;&gt;pwd&lt;/span&gt;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After this, we&#39;ll kill our &lt;code&gt;my-node-app-container&lt;/code&gt; and restart it:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;docker &lt;span class=&quot;hljs-built_in&quot;&gt;kill&lt;/span&gt; my-node-app-container&lt;br /&gt;docker start my-node-app-container&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the end, you&#39;ll put the &lt;code&gt;rsync&lt;/code&gt; command and restarting of the container into a &lt;code&gt;.sh&lt;/code&gt; file and call this from &lt;code&gt;nodemon&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Running this container together with the MongoDb container works similar to the sample shown in the section without MongoDb but of course with the &lt;code&gt;rsync&lt;/code&gt; stuff applied.&lt;/p&gt;
&lt;h1&gt;Docker Machine and Docker Compose together&lt;/h1&gt;
&lt;p&gt;The last part of this post will show how to run your containers in Docker Machine and get the composition done by Docker Compose.&lt;/p&gt;
&lt;p&gt;Our &lt;code&gt;docker-compose.yml&lt;/code&gt; file looks exactly the same as before:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;mongo:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;image:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;mongo:2.6.11&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;ports:&lt;/span&gt;&lt;br /&gt;   &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;27017:27017&amp;quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;application:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;build:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;command:&lt;/span&gt;  &lt;span class=&quot;hljs-string&quot;&gt;node&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;--debug=5858&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;app.js&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;--color=always&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;ports:&lt;/span&gt;&lt;br /&gt;   &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;3000:3000&amp;quot;&lt;/span&gt;&lt;br /&gt;   &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;5858:5858&amp;quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;volumes:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;./:/app&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;links:&lt;/span&gt;&lt;br /&gt;   &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;mongo&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In order to get Docker Machine and Compose work together and have a smooth development workflow, you&#39;ll have to add the &lt;code&gt;docker-compose up&lt;/code&gt; command to your aforementioned &lt;code&gt;.sh&lt;/code&gt; script and restart this by &lt;code&gt;nodemon&lt;/code&gt; like this:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;nodemon -x syncandrestartcontainers.sh&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, &lt;code&gt;syncandrestartcontainers.sh&lt;/code&gt; will look like this:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;eval&lt;/span&gt; $(ssh-agent)&lt;br /&gt;ssh-add ~/.docker/machine/machines/default/id_rsa&lt;br /&gt;rsync -avzhe ssh --relative --omit-dir-times --progress ./ docker@$(docker-machine ip default):$(&lt;span class=&quot;hljs-built_in&quot;&gt;pwd&lt;/span&gt;)&lt;br /&gt;docker-compose up&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Some tips for the praxis when using this workflow:&lt;/p&gt;
&lt;p&gt;Before starting this workflow&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;stop your Docker Machine and restart it using &lt;code&gt;docker-machine restart default&lt;/code&gt; (don&#39;t forget &lt;code&gt;eval $(docker-machine env default)&lt;/code&gt; after it)&lt;/li&gt;
&lt;li&gt;delete the &amp;quot;local&amp;quot; directory inside the Docker Machine using &lt;code&gt;docker-machine ssh default sudo -i &amp;quot;sudo rm -rf $(pwd)&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;create the &amp;quot;local&amp;quot; directory inside the Docker Machine using &lt;code&gt;docker-machine ssh default mkdir -p $(pwd)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Update 2016, 3rd February: One more thing... Docker networking&lt;/h3&gt;
&lt;p&gt;As suggested in the comments, this update of the post shows you how to use Docker networking which has been introduced in Docker 1.9.&lt;br /&gt;
You can read the basics about Docker network &lt;a href=&quot;https://docs.docker.com/engine/userguide/networking/&quot;&gt;here&lt;/a&gt; - please read them first and then continue here...&lt;/p&gt;
&lt;p&gt;Welcome back ;-)&lt;/p&gt;
&lt;p&gt;First, lets create a custom bridged network named &lt;code&gt;my-app-network&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;docker network create --driver=bridge my-app-network&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we&#39;ll start our MongoDb container connected to the network:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;docker run -d -it --name=some-mongo --net=my-app-network -p 27017:27017 mongo:2.6.11&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As Docker networking doesn&#39;t use environment variables to share connection settings but the container names instead, we need to change the MongoDb connection string in our &lt;code&gt;app.js&lt;/code&gt; accordingly:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; url = &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;mongodb://some-mongo:27017/dockerdemo&amp;#x27;&lt;/span&gt;;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, rebuild our image:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;docker build -t my-node-app .&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And finally, run our app container connected to the network as well:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;docker run -d -it --name=my-node-app-container -v $(&lt;span class=&quot;hljs-built_in&quot;&gt;pwd&lt;/span&gt;):/app -p 3000:3000 --net=my-app-network  my-node-app&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Issuing a &lt;code&gt;http post http://localhost:3000/hello name=PDMLab&lt;/code&gt; again works fine as before.&lt;/p&gt;
&lt;p&gt;Docker networking also works fine with Docker Machine, just make sure you follow the steps (rsync etc.) shown above.&lt;/p&gt;
&lt;p&gt;Docker networking in Docker Compose is experimental at the moment so I won&#39;t show that now.&lt;/p&gt;
&lt;p&gt;End of Update 2016, 3rd February&lt;/p&gt;
&lt;p&gt;Happy developing using Docker Machine and Compose ;-)&lt;/p&gt;
&lt;p&gt;P.S.: If you came up with other solutions, please let me know in the comments.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Resizing a VMware Workstation VM partition using GParted - get the swap partition out of my way!</title>
    <link href="https://alexanderzeitler.com/articles/resizing-a-vmware-workstation-partition-using-gparted/"/>
    <updated>2016-02-04T21:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/resizing-a-vmware-workstation-partition-using-gparted/</id>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;TL;DR Hard Disk space can only be replaced with more Hard Disk space&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Disclaimer: If you&#39;re doing things wrong described here you&#39;ll loose your data - create a backup first!&lt;/p&gt;
&lt;p&gt;Nowadays it&#39;s casual to use VMs for development environments. It&#39;s also casual that projects grow and you run out of disk space inside the VMs. Luckily, on the host there&#39;s always enough space available which can be assigned to an existing VM (isn&#39;t it?).&lt;/p&gt;
&lt;p&gt;Expanding the partition in the VMware Hardware options / hard disk details is pretty easy. Just stop the VM, click the expand button and increase as required:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/resizing-a-vmware-workstation-partition-using-gparted/vmwareexpand.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now we have some more space for partitions in our VM but we want to increase the size of an existing partition.
To manage the partitions, we&#39;re using &lt;a href=&quot;http://gparted.org/&quot;&gt;GParted&lt;/a&gt;, which is available as bootable ISO-Image.&lt;/p&gt;
&lt;p&gt;Assign the downloaded ISO in your VMware CD-ROM drive and boot from it.
You can select the boot device if you hit ESC right after powering on the VM.&lt;/p&gt;
&lt;p&gt;If the boot sequence is to fast, you can add this line to your VMs &lt;code&gt;.vmx&lt;/code&gt; file:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;bios.bootDelay = &amp;quot;2000&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will delay the boot sequence for 2000 ms and show the options.&lt;/p&gt;
&lt;p&gt;If you&#39;re running Ubuntu in your VM, it is likely that there might be a swap partition (&lt;code&gt;/dev/sda5&lt;/code&gt; here) between the main partition and the aforementioned created, unallocated new space and your partitions might look like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/resizing-a-vmware-workstation-partition-using-gparted/gparted1.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;What we want to achieve now, is to merge the 10 GB unallocated space at the end of the list to the &lt;code&gt;/dev/sda1&lt;/code&gt; partition.&lt;/p&gt;
&lt;p&gt;First, we&#39;ll select &lt;code&gt;/dev/sda2&lt;/code&gt; (NOT &lt;code&gt;/dev/sda5&lt;/code&gt;!):&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/resizing-a-vmware-workstation-partition-using-gparted/gparted2.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Then, we click &amp;quot;Resize/Move&amp;quot;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/resizing-a-vmware-workstation-partition-using-gparted/gparted3.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Next, we increase the size of &lt;code&gt;/dev/sda2&lt;/code&gt; to use all available space:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/resizing-a-vmware-workstation-partition-using-gparted/gparted4.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now we select &lt;code&gt;/dev/sda5&lt;/code&gt; and click &lt;code&gt;Resize/Move&lt;/code&gt; again:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/resizing-a-vmware-workstation-partition-using-gparted/gparted5.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In this step, we move &lt;code&gt;/dev/sda5&lt;/code&gt; inside &lt;code&gt;/dev/sda2&lt;/code&gt; to right end:
&lt;img src=&quot;https://alexanderzeitler.com/articles/resizing-a-vmware-workstation-partition-using-gparted/gparted6.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Then again, we select &lt;code&gt;/dev/sda2&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/resizing-a-vmware-workstation-partition-using-gparted/gparted7.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After clicking &lt;code&gt;Resize/Move&lt;/code&gt; now we shrink &lt;code&gt;/dev/sda2&lt;/code&gt; to the size of &lt;code&gt;/dev/sda5&lt;/code&gt; by moving the left arrow to the right until it is blocked:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/resizing-a-vmware-workstation-partition-using-gparted/gparted8.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/resizing-a-vmware-workstation-partition-using-gparted/gparted9.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now we select &lt;code&gt;/dev/sda1&lt;/code&gt; again:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/resizing-a-vmware-workstation-partition-using-gparted/gparted10.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After clicking &amp;quot;Resize/Move&amp;quot; again, we finally can expand &lt;code&gt;/dev/sda1&lt;/code&gt; to the right:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/resizing-a-vmware-workstation-partition-using-gparted/gparted11.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/resizing-a-vmware-workstation-partition-using-gparted/gparted12.PNG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The final step is hitting the &amp;quot;Apply&amp;quot; button, confirm everything, cross fingers and wait for the operations to succeed.&lt;/p&gt;
&lt;p&gt;As GParted already tells you in almost every step: Be careful with what you&#39;re doing and create a backup before you&#39;re trying to modify your partitions!&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Some thoughts on Open Source Software</title>
    <link href="https://alexanderzeitler.com/articles/some-thoughts-on-open-source-software/"/>
    <updated>2016-02-18T14:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/some-thoughts-on-open-source-software/</id>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;TL;DR There&#39;s no alternative to OSS but we need to improve how we&#39;re dealing with it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Hannes Preishuber recently wrote a &lt;a href=&quot;http://blog.ppedv.de/post/2016/02/10/Werte-schatzen-Werte-schopfen.aspx&quot;&gt;blog post&lt;/a&gt; (in german language) about appreciation of work as well as quality and documentation of software nowadays.&lt;/p&gt;
&lt;p&gt;While I agree with Hannes that someone&#39;s work has to be appreciated, I disagree with his assumptions that OSS is responsible for bad software quality and service / documentation in general.&lt;/p&gt;
&lt;p&gt;Today a huge amount of the cloud infrastructure and services run on OSS and companies like MongoDb have build a business around their OSS products.&lt;/p&gt;
&lt;p&gt;If quality and service wouldn&#39;t be warranted here, that business won&#39;t work.&lt;/p&gt;
&lt;p&gt;On the other hand, from a developer perspective, OSS can become frustrating anyway:&lt;br /&gt;
Independently from the platform, be it Node.js / JavaScript or .NET, new OSS projects are born, gain huge adoption - and die.&lt;/p&gt;
&lt;p&gt;The problem here is not that these projects die in general because there are several reasons for that.&lt;br /&gt;
One reason is, the community has decided to choose another OSS project as state of the art.&lt;br /&gt;
Another one is, the core maintainer has decided to no longer maintain the project and makes an official statement about that.&lt;br /&gt;
Some maintainers also look for a successor who maintains the project in the future so the project doesn&#39;t die at all.&lt;/p&gt;
&lt;p&gt;The real problem in OSS are those projects which start, grow over time and then die slowly - or from an adopters perspective: they become zombies.
Typical indications for a project dying slowly are unmerged pull requests, unanswered and unresolved issues and of course no further commits to at least update the projects own depedencies.&lt;/p&gt;
&lt;p&gt;As said in the introduction, there are no (or only few alternatives) to OSS. Even typical enterprise developers use OSS these days.&lt;br /&gt;
All of our customers use OSS in some way and frameworks like Angular will push this trend forward.&lt;/p&gt;
&lt;p&gt;When advising customers about using external dependencies (not only OSS) in their code base, we tell them to look for indicators of OSS zombie projects and have countermeasures on hand if a depedency is no longer maintained.&lt;/p&gt;
&lt;p&gt;On the other hand, I came to the conclusion that OSS projects, especially the many smaller ones need to be handled in another way opposed to how they&#39;re handled nowadays.&lt;/p&gt;
&lt;p&gt;If you look at Cassandra, Node.js or Microsoft .NET as OSS projects, they&#39;re all under the hood of a foundation which supports these projects. These foundations like Apache Foudation, Linux Foundation, or .NET Foundation provide guidance, mentoring as well as legal, technical and fincancial support.&lt;/p&gt;
&lt;p&gt;But I think we should have another form of support or foundation in order to avoid zombie projects and respect criticism from people like Hannes.&lt;/p&gt;
&lt;p&gt;After thinking about it for a while and talking to some fellows about it, here are my thoughts:&lt;/p&gt;
&lt;p&gt;In general we should distinguish between the &amp;quot;ownership&amp;quot; of the project and the copyright / license for the code.&lt;br /&gt;
If you decide to start a new OSS project on GitHub, GitLab, BitBucket or whereever, beside selecting a license for your project, you should be able to transfer the ownership of the project to sort of a &amp;quot;registry&amp;quot;.&lt;/p&gt;
&lt;p&gt;This registry should provide these features:&lt;/p&gt;
&lt;p&gt;The initiator of the project becomes the &amp;quot;general manager&amp;quot; of the project and can work on the project as we all used to work on OSS as before.&lt;br /&gt;
But in addition to that, the registry provides the following features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A general manager can resign itself if he doesn&#39;t want to maintain to project any longer. The project then is in a pool of projects &amp;quot;looking for new general manager&amp;quot;. Other people can apply for that role and take over projects in a governed and transparent way. Community could even vote the next general manager if more persons apply as general manager for a particular project.&lt;/li&gt;
&lt;li&gt;A general manager can be resigned by the registry which might sound frightening at a first sight. But I think there are several advantages. For example, if a core maintainer doesn&#39;t even respond to pull requests or issues in some manner in a reasonable time, he might get warnings from the registry. After a specific number of warnings he could be resigned by the registry and the project gets in the pool as described above.&lt;/li&gt;
&lt;li&gt;Projects can have proxies. If the general manager is temporarily not able to maintain the project, the proxy can maintain the project during that period.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some might argue that this is way to much overhead but I&#39;m sure I&#39;m not the first who ran into zombie projects and the bigger your codebase is or if you&#39;re maintaining a huge number of critical projects for your customers, you don&#39;t want to kill the living deads in your code every now and then. And your customers should not have to pay for this problem.&lt;/p&gt;
&lt;p&gt;Please let me know your thoughts about in the comments.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Debugging Node.js + TypeScript in a Docker Container with WebStorm</title>
    <link href="https://alexanderzeitler.com/articles/debugging-node-js-typescript-in-a-docker-container-with-webstorm/"/>
    <updated>2016-04-06T11:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/debugging-node-js-typescript-in-a-docker-container-with-webstorm/</id>
    <content type="html">&lt;p&gt;In order to be able to debug Node.js with TypeScript in a Docker container (or in general: debug remote) using WebStorm, &lt;code&gt;compilerOptions&lt;/code&gt; in your &lt;code&gt;tsconfig.json&lt;/code&gt; need to contain &lt;code&gt;inlineSources&lt;/code&gt; and &lt;code&gt;inlineSourceMap&lt;/code&gt; being set to &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So your &lt;code&gt;tsconfig.json&lt;/code&gt; might look like this:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;compilerOptions&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;outDir&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;build&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;inlineSources&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;true&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;inlineSourceMap&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;true&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;include&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;src/**/*&amp;quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;exclude&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;**/*.spec.ts&amp;quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  
  <entry>
    <title>Getting all registered routes in express.js</title>
    <link href="https://alexanderzeitler.com/articles/getting-all-registered-routes-in-express-js/"/>
    <updated>2016-04-06T21:45:00Z</updated>
    <id>https://alexanderzeitler.com/articles/getting-all-registered-routes-in-express-js/</id>
    <content type="html">&lt;p&gt;To get all routes registered in express.js, this snippet gets the work done:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; routes = [];&lt;br /&gt;&lt;br /&gt;app.&lt;span class=&quot;hljs-property&quot;&gt;_router&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;stack&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;forEach&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;middleware&lt;/span&gt;) =&amp;gt;&lt;/span&gt; {&lt;br /&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (middleware.&lt;span class=&quot;hljs-property&quot;&gt;route&lt;/span&gt;) {&lt;br /&gt;    routes.&lt;span class=&quot;hljs-title function_&quot;&gt;push&lt;/span&gt;(&lt;br /&gt;      &lt;span class=&quot;hljs-string&quot;&gt;`&lt;span class=&quot;hljs-subst&quot;&gt;${&lt;span class=&quot;hljs-built_in&quot;&gt;Object&lt;/span&gt;.keys(middleware.route.methods)}&lt;/span&gt; -&amp;gt; &lt;span class=&quot;hljs-subst&quot;&gt;${middleware.route.path}&lt;/span&gt;`&lt;/span&gt;&lt;br /&gt;    );&lt;br /&gt;  }&lt;br /&gt;});&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;hljs-title class_&quot;&gt;JSON&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;stringify&lt;/span&gt;(routes, &lt;span class=&quot;hljs-literal&quot;&gt;null&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;));&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Possible result:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;[&lt;br /&gt;  &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;post -&amp;gt; /customers&amp;quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;put -&amp;gt; /customers/:id&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;delete -&amp;gt; /customers/:id&amp;quot;&lt;/span&gt;&lt;br /&gt;]&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  
  <entry>
    <title>Deploying a private, secured Docker Registry within 15 minutes</title>
    <link href="https://alexanderzeitler.com/articles/deploying-a-private-secured-docker-registry-within-15-minutes/"/>
    <updated>2016-04-08T18:45:00Z</updated>
    <id>https://alexanderzeitler.com/articles/deploying-a-private-secured-docker-registry-within-15-minutes/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@alexzeitler_&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@alexzeitler_&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Deploying a private, secured Docker Registry within 15 minutes&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Need a private Docker registry with RBAC, GUI, AD/LDAP support and more?&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/deploying-a-private-secured-docker-registry-within-15-minutes/harborprojectrepository_twitter.png&quot; /&gt;
&lt;p&gt;After using Docker in &lt;a href=&quot;https://alexanderzeitler.com/articles/docker-machine-and-docker-compose-developer-workflows&quot;&gt;development&lt;/a&gt;, you might want to put your application into production. Depending on your need, you might not want to push your private Docker images for your application to the Docker Hub, but have a private registry instead.&lt;/p&gt;
&lt;p&gt;Docker has been providing the private registry on GitHub for a long time and it works pretty well.
The downside is that it doesn&#39;t provide security features like RBAC.&lt;/p&gt;
&lt;p&gt;That&#39;s where 3rd party solutions kick in.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://pdmlab.com/&quot;&gt;We&lt;/a&gt;&#39;ve been evaluating several solutions like &lt;a href=&quot;https://aws.amazon.com/ecr/&quot;&gt;AWS EC2 Container Registry (ECR)&lt;/a&gt; and others.&lt;br /&gt;
In the end, we&#39;re now running on &lt;a href=&quot;https://github.com/vmware/harbor&quot;&gt;Harbor&lt;/a&gt;, an Open Source registry server from VMware build around the aforementioned Docker registry.&lt;/p&gt;
&lt;p&gt;Harbor is being developed in Golang and provides these key features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Access Control: RBAC (Role Based Access Control) is provided. User management can be integrated with existing enterprise identity services like AD/LDAP.&lt;/li&gt;
&lt;li&gt;Audit: All access to the registry are logged and can be used for audit purpose.&lt;/li&gt;
&lt;li&gt;GUI: User friendly single-pane-of-glass management console.&lt;/li&gt;
&lt;li&gt;Project based approach to separate images and access to them&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;First, here are some screenshots from harbor, starting with the admin dashboard:
&lt;img src=&quot;https://alexanderzeitler.com/articles/deploying-a-private-secured-docker-registry-within-15-minutes/harboradmindashboard.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Beside user self registration (can be turned off), the Admin can invite users:
&lt;img src=&quot;https://alexanderzeitler.com/articles/deploying-a-private-secured-docker-registry-within-15-minutes/harboradminuserinvite.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Admins can create projects:
&lt;img src=&quot;https://alexanderzeitler.com/articles/deploying-a-private-secured-docker-registry-within-15-minutes/harboradminaddproject.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You can see all images for a particular project:
&lt;img src=&quot;https://alexanderzeitler.com/articles/deploying-a-private-secured-docker-registry-within-15-minutes/harborprojectrepository.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/vmware/harbor/blob/master/docs/user_guide.md&quot;&gt;Harbor User Guide&lt;/a&gt; shows some more screenshots.&lt;/p&gt;
&lt;p&gt;The goal of this post is to get a Harbor instance running on &lt;a href=&quot;https://www.digitalocean.com/&quot;&gt;DigitalOcean&lt;/a&gt; with HTTPS enabled using Docker Machine.&lt;/p&gt;
&lt;p&gt;I&#39;m assuming that you&#39;re familiar with &lt;a href=&quot;https://docs.docker.com/machine/&quot;&gt;Docker Machine&lt;/a&gt; basics and have it up and running.
Furthermore, you&#39;ll need to have the &lt;a href=&quot;https://docs.docker.com/engine/&quot;&gt;Docker engine&lt;/a&gt; and &lt;a href=&quot;https://docs.docker.com/compose/&quot;&gt;Docker Compose&lt;/a&gt; installed which are required by Harbor for deployment.&lt;br /&gt;
And because we want to enable HTTPS, you should have grabbed a certificate from &lt;a href=&quot;https://www.digicert.com/&quot;&gt;DigiCert&lt;/a&gt; or somewhere else.&lt;/p&gt;
&lt;p&gt;First, clone the latest Harbor version from GitHub:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ git &lt;span class=&quot;hljs-built_in&quot;&gt;clone&lt;/span&gt; git@github.com:vmware/harbor.git&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inside the &lt;code&gt;Deploy&lt;/code&gt; folder there are several configuration options within the &lt;code&gt;harbor.cfg&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;There are two options you have to configure at a minimum:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;hostname = harbor.yourdomain.com&lt;br /&gt;ui_url_protocol = https&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inside the &lt;code&gt;Deploy/config/nginx&lt;/code&gt; folder there are two nginx configuration files named &lt;code&gt;nginx.conf&lt;/code&gt; and &lt;code&gt;nginx.https.conf&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The file being used by Harbor is &lt;code&gt;nginx.conf&lt;/code&gt;. Because we want to use HTTPS, we need to get Harbor use &lt;code&gt;nginx.https.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;hljs-built_in&quot;&gt;mv&lt;/span&gt; nginx.conf nginx.conf.bak&lt;br /&gt;$ &lt;span class=&quot;hljs-built_in&quot;&gt;cp&lt;/span&gt; nginx.https.conf nginx.conf&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we have to modify the new &lt;code&gt;nginx.conf&lt;/code&gt; and replace the two occurrences of &lt;code&gt;harbordomain.com&lt;/code&gt; with our own Harbor URI, e.g. &lt;code&gt;harbor.yourdomain.com&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Regarding the certificate, there a two options: Use the cert filename from DigiCert (in my case) and the &lt;code&gt;server.key&lt;/code&gt; file and modify the &lt;code&gt;nginx.conf&lt;/code&gt; accordingly or rename the certificate to &lt;code&gt;harbordomain.crt&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Either case, you have to copy the cert file and the key file from your Certificate Signing Request to the &lt;code&gt;Deploy/config/nginx/cert&lt;/code&gt; folder.&lt;/p&gt;
&lt;p&gt;The SSL settings within the &lt;code&gt;nginx.conf&lt;/code&gt; should look like this:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;# SSL&lt;br /&gt;ssl_certificate /etc/nginx/cert/harbor_yourdomain_com.pem;&lt;br /&gt;ssl_certificate_key /etc/nginx/cert/harbor_yourdomain_com.key;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, from the bash, run the following command within the &lt;code&gt;Deploy&lt;/code&gt; folder:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ ./prepare&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will copy the settings to their appropriate destinations.&lt;/p&gt;
&lt;p&gt;With that done, lets create our Docker Machine instance for deployment.
To create a DigitalOcean Droplet using Docker Machine, we need an API access token which can be created at the DigitalOcean Website (Azure or AWS are similar):&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/deploying-a-private-secured-docker-registry-within-15-minutes/digitaloceantokens.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/deploying-a-private-secured-docker-registry-within-15-minutes/digitaloceangeneratetoken.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Copy your token and keep it safe, you won&#39;t see it again at the DigitalOcean website.&lt;/p&gt;
&lt;p&gt;Now, back to the bash, we can create a Droplet using Docker Machine - make sure it has at least 2GB of RAM:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ docker-machine create --driver digitalocean --digitalocean-access-token &amp;lt;yourtokenfromabove&amp;gt; --digitalocean-size 2GB harbor.yourdomain.com&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Docker Machine provides more options for the &lt;code&gt;--driver&lt;/code&gt; like the region. Just read the &lt;a href=&quot;https://docs.docker.com/machine/drivers/&quot;&gt;Docker Machine Driver Documentation&lt;/a&gt; for more.&lt;/p&gt;
&lt;p&gt;When the Droplet is up and running (this should take only a few seconds), activate that particular Docker Machine instance:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;hljs-built_in&quot;&gt;eval&lt;/span&gt; $(docker-machine &lt;span class=&quot;hljs-built_in&quot;&gt;env&lt;/span&gt; harbor.yourdomain.com)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because we want to spin up the containers required by Harbor using Docker Compose, we have to copy the settings from the &lt;code&gt;Deploy/config&lt;/code&gt; directory first to our Docker Machine instance.&lt;/p&gt;
&lt;p&gt;First, get the absolute path to the &lt;code&gt;Deploy&lt;/code&gt; directory (&lt;code&gt;cd&lt;/code&gt; first into it):&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;hljs-built_in&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;hljs-variable&quot;&gt;$PWD&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The result should be something like this:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;/home/&lt;yourusername&gt;/src/harbor/Deploy&lt;/yourusername&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we create the exact same absolute folder structure (including the &lt;code&gt;config&lt;/code&gt; subfolder) inside the Docker Machine instance and copy the contents of our local &lt;code&gt;Deploy/config&lt;/code&gt; folder into it:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ docker-machine ssh harbor.ỳourdomain.com &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;mkdir -p /home/&amp;lt;yourusername&amp;gt;/src/harbor/Deploy/config&lt;br /&gt;$ docker-machine scp -r ./config harbor.yourdomain.com:$PWD&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, build the container images using Docker Compose (inside the &lt;code&gt;Deploy&lt;/code&gt; folder):&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ docker-compose build&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And as a last step, we&#39;re spinning up our containers:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ docker-compose up -d&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you should be able to browse &lt;code&gt;https://harbor.yourdomain.com&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/deploying-a-private-secured-docker-registry-within-15-minutes/harborupandrunning.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;And of course, you can start pushing and pulling Docker images to and from your own registry now!&lt;/p&gt;
&lt;p&gt;After creating a new project (I&#39;m using &lt;code&gt;test&lt;/code&gt; here) and user as an Admin inside Harbor, you can push new images to Harbor like this:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ docker tag customerservice:1.0.0 harbor.yourdomain.com/test/customerservice:1.0.0&lt;br /&gt;$ docker login -u &amp;lt;username&amp;gt; -p &amp;lt;password&amp;gt; -e &amp;lt;email&amp;gt; harbor.yourdomain.com&lt;br /&gt;$ docker push harbor.yourdomain.com/test/customerservice:1.0.0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we&#39;re done!&lt;/p&gt;
&lt;p&gt;As a side note: If you want to disable user self registration, apply this setting in &lt;code&gt;harbor.cfg&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;self_registration = off&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;P.S.: This post mostly is a combination of the &lt;a href=&quot;https://github.com/vmware/harbor/blob/master/docs/installation_guide.md&quot;&gt;Harbor Installation Guide&lt;/a&gt; and the &lt;a href=&quot;https://github.com/vmware/harbor/blob/master/contrib/deploying_using_docker_machine.md&quot;&gt;Deploying Harbor using Docker Machine Guide&lt;/a&gt; (the latter I contributed to the Harbor project, so I didn&#39;t just copy existing stuff).&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Debugging a ES6 Node.js application in a Docker container using Visual Studio Code</title>
    <link href="https://alexanderzeitler.com/articles/debugging-a-nodejs-es6-application-in-a-docker-container-using-visual-studio-code/"/>
    <updated>2016-04-08T21:45:00Z</updated>
    <id>https://alexanderzeitler.com/articles/debugging-a-nodejs-es6-application-in-a-docker-container-using-visual-studio-code/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@alexzeitler_&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@alexzeitler_&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Debugging a ES6 Node.js application in a Docker container using Visual Studio Code&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Curious how to debug a Node.js application in a Docker Container? Look no further!&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/debugging-a-nodejs-es6-application-in-a-docker-container-using-visual-studio-code/runattachactivebreakpoint_twitter1.png&quot; /&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;p&gt;Yesterday I just tried to debug an ES6 Node.js application using Visual Studio Code and noticed that I couldn&#39;t remote debug.&lt;/p&gt;
&lt;p&gt;So I just asked on Twitter and got some replies from Erich Gamma and Andre Weinand today:&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;&lt;a href=&quot;https://twitter.com/alexzeitler_&quot;&gt;@alexzeitler_&lt;/a&gt; &lt;a href=&quot;https://twitter.com/code&quot;&gt;@code&lt;/a&gt; pls see the remote debugging section here &lt;a href=&quot;https://t.co/XoMlvRis5Z&quot;&gt;https://t.co/XoMlvRis5Z&lt;/a&gt; // &lt;a href=&quot;https://twitter.com/weinand&quot;&gt;@weinand&lt;/a&gt;&lt;/p&gt;&amp;mdash; ErichGamma (@ErichGamma) &lt;a href=&quot;https://twitter.com/ErichGamma/status/718426030424395776&quot;&gt;April 8, 2016&lt;/a&gt;&lt;/blockquote&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;&lt;a href=&quot;https://twitter.com/alexzeitler_&quot;&gt;@alexzeitler_&lt;/a&gt; &lt;a href=&quot;https://twitter.com/ErichGamma&quot;&gt;@ErichGamma&lt;/a&gt; &lt;a href=&quot;https://twitter.com/code&quot;&gt;@code&lt;/a&gt; Sorry, but some changes from the release notes have not yet found their way into the doc.&lt;/p&gt;&amp;mdash; Andre Weinand (@weinand) &lt;a href=&quot;https://twitter.com/weinand/status/718449866016497664&quot;&gt;April 8, 2016&lt;/a&gt;&lt;/blockquote&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;&lt;a href=&quot;https://twitter.com/ErichGamma&quot;&gt;@erichgamma&lt;/a&gt; &lt;a href=&quot;https://twitter.com/alexzeitler_&quot;&gt;@alexzeitler_&lt;/a&gt; &lt;a href=&quot;https://twitter.com/code&quot;&gt;@code&lt;/a&gt; and here the section about node.js remote debugging: &lt;a href=&quot;https://t.co/TbzNwjKX7y&quot;&gt;https://t.co/TbzNwjKX7y&lt;/a&gt;&lt;/p&gt;&amp;mdash; Andre Weinand (@weinand) &lt;a href=&quot;https://twitter.com/weinand/status/718464941418815490&quot;&gt;April 8, 2016&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;p&gt;As things didn&#39;t make it into official docs until now, I&#39;ll wrap it up here in a block post.&lt;/p&gt;
&lt;p&gt;To keep things simple, this is our Node.js application:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; express = &lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;express&amp;#x27;&lt;/span&gt;);&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; app = &lt;span class=&quot;hljs-title function_&quot;&gt;express&lt;/span&gt;();&lt;br /&gt;&lt;br /&gt;app.&lt;span class=&quot;hljs-title function_&quot;&gt;get&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;/&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;req, res&lt;/span&gt;) =&amp;gt;&lt;/span&gt; {&lt;br /&gt;    res.&lt;span class=&quot;hljs-title function_&quot;&gt;status&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;200&lt;/span&gt;).&lt;span class=&quot;hljs-title function_&quot;&gt;send&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;hello world&amp;#x27;&lt;/span&gt;);&lt;br /&gt;})&lt;br /&gt;&lt;br /&gt;app.&lt;span class=&quot;hljs-title function_&quot;&gt;listen&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;3000&lt;/span&gt;);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add a &lt;code&gt;jsconfig.json&lt;/code&gt; into the root of the project:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;compilerOptions&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;target&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;ES6&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;module&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;commonjs&amp;quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a &lt;code&gt;Dockerfile&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-dockerfile&quot;&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;FROM&lt;/span&gt; node:&lt;span class=&quot;hljs-number&quot;&gt;4.2&lt;/span&gt;.&lt;span class=&quot;hljs-number&quot;&gt;3&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;EXPOSE&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;3000&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;EXPOSE&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;5858&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;COPY&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; . /app&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;WORKDIR&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; /app&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;RUN&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;cd&lt;/span&gt; /app; npm install&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;CMD&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; [&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;node&amp;quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;--debug=5858&amp;quot;&lt;/span&gt;,&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;index.js&amp;quot;&lt;/span&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a &lt;code&gt;launch.json&lt;/code&gt; (or create it by clicking the &lt;code&gt;Debug&lt;/code&gt; button in VS Code):&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;version&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;0.2.0&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;configurations&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Launch&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;node&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;request&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;launch&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;program&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;${workspaceRoot}/index.js&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;stopOnEntry&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;false&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;args&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;cwd&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;${workspaceRoot}&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;preLaunchTask&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;null&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;runtimeExecutable&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;null&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;runtimeArgs&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;				&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;--nolazy&amp;quot;&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;env&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;				&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;NODE_ENV&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;development&amp;quot;&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;externalConsole&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;false&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;sourceMaps&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;false&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;outDir&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;null&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Attach&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;node&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;request&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;attach&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;port&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;5858&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;address&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;localhost&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;restart&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;false&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;sourceMaps&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;false&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;outDir&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;null&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;localRoot&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;${workspaceRoot}/&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;remoteRoot&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;/app/&amp;quot;&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important part in the &lt;code&gt;launch.json&lt;/code&gt; is the &lt;code&gt;Attach&lt;/code&gt; configuration, especially the &lt;code&gt;localRoot&lt;/code&gt; and &lt;code&gt;remoteRoot&lt;/code&gt; properties:&lt;/p&gt;
&lt;p&gt;As our application on the host is in &lt;code&gt;/index.js&lt;/code&gt;, &lt;code&gt;${workspaceRoot}/&lt;/code&gt; is the correct setting.&lt;/p&gt;
&lt;p&gt;As you can see in the &lt;code&gt;Dockerfile&lt;/code&gt;, inside the container our app runs in the &lt;code&gt;/app&lt;/code&gt; working directory.&lt;/p&gt;
&lt;p&gt;So I&#39;ve set the &lt;code&gt;remoteRoot&lt;/code&gt; to &lt;code&gt;/app/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Next, we can build our Docker image:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ docker build -t hellovscode .&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that finished, we can run a container based on the image with port mappings for &lt;code&gt;3000&lt;/code&gt; and &lt;code&gt;5858&lt;/code&gt; activated:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ docker run -d -p 3000:3000 -p 5858:5858 hellovscode&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next start the &lt;code&gt;Attach&lt;/code&gt; Debug configuration in VS Code:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/debugging-a-nodejs-es6-application-in-a-docker-container-using-visual-studio-code/runattach.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/debugging-a-nodejs-es6-application-in-a-docker-container-using-visual-studio-code/runattachactive.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;As you can see, I have already set a breakpoint.&lt;/p&gt;
&lt;p&gt;Now, issue a request to &lt;code&gt;http://localhost:3000/&lt;/code&gt; - and we&#39;re remote debugging Node.js / ES6 in Docker Container using Visual Studio Code!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/debugging-a-nodejs-es6-application-in-a-docker-container-using-visual-studio-code/runattachactivebreakpoint.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If you&#39;re using Docker-Machine, make sure you&#39;re using the IP address of the Docker Machine your container are running in.&lt;/p&gt;
&lt;p&gt;You can get the IP address of the Docker Machine using:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ docker-machine ip &amp;lt;machinename&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use this IP address in the &amp;quot;address&amp;quot; property of your VS Code Attach configuration in the &lt;code&gt;launch.json&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The source code including the Visual Studio Code Workspace Settings can be found &lt;a href=&quot;https://github.com/PDMLab/vscode-debug-es6-node-docker-sample&quot;&gt;here&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Debugging mocha tests with Visual Studio Code when Node.js is installed using nvm</title>
    <link href="https://alexanderzeitler.com/articles/debugging-mocha-tests-using-visual-studio-code-and-nvm/"/>
    <updated>2016-04-09T15:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/debugging-mocha-tests-using-visual-studio-code-and-nvm/</id>
    <content type="html">&lt;p&gt;In my development environments, I never install Node.j using &lt;code&gt;apt-get&lt;/code&gt; but use &lt;a href=&quot;https://github.com/creationix/nvm&quot;&gt;nvm&lt;/a&gt; instead to be able to use multiple Node.js versions in parallel.&lt;/p&gt;
&lt;p&gt;To be able to debug mocha tests using Visual Studio Code, I&#39;m using this &lt;code&gt;launch.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;version&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;0.2.0&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;configurations&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Debug Mocha tests&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;node&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;request&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;launch&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;program&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;${workspaceRoot}/node_modules/mocha/bin/_mocha&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;stopOnEntry&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;false&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;args&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;-t&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;10000&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;test/test.js&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;cwd&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;${workspaceRoot}&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;preLaunchTask&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;null&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;runtimeExecutable&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;/home/alex/.nvm/versions/node/v4.2.3/bin/node&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;runtimeArgs&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;				&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;--nolazy&amp;quot;&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;env&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;				&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;NODE_ENV&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;development&amp;quot;&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;externalConsole&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;false&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;sourceMaps&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;false&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;outDir&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;null&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important part is the &lt;code&gt;runtimeExecutable&lt;/code&gt; which points Visual Studio Code to the Node.js version I&#39;ve installed using &lt;code&gt;nvm&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you don&#39;t use this setting, you&#39;ll get &lt;code&gt;Cannot launch target (reason: spawn node ENOENT).&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/debugging-mocha-tests-using-visual-studio-code-and-nvm/error.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Also make sure to change the &lt;code&gt;args&lt;/code&gt; to change the mocha timeout in order to avoid timeout errors in the debug console.&lt;/p&gt;
&lt;p&gt;With that being done, hit F5 and debug your tests:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/debugging-mocha-tests-using-visual-studio-code-and-nvm/debuggingmochatests.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;As promised, no timeout errors:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/debugging-mocha-tests-using-visual-studio-code-and-nvm/debuggingmochatestsconsoleoutput.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Debugging mocha tests in a Docker container using Visual Studio Code</title>
    <link href="https://alexanderzeitler.com/articles/debugging-mocha-tests-in-a-docker-container-using-visual-studio-code/"/>
    <updated>2016-04-10T00:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/debugging-mocha-tests-in-a-docker-container-using-visual-studio-code/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@alexzeitler_&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@alexzeitler_&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Debugging a mocha tests in a Docker container using Visual Studio Code&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Your tests run in a Docker container - how to debug them?&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/debugging-mocha-tests-in-a-docker-container-using-visual-studio-code/debugmochaindocker_twitter.png&quot; /&gt;
&lt;p&gt;In a current Node.js / Docker customer project I had to debug some mocha tests because Docker containers use UTC and you know: that timezone stuff...&lt;/p&gt;
&lt;p&gt;In the end, I was debugging my mocha tests using WebStorm and node-inspector inside the containers and got stuff running.&lt;/p&gt;
&lt;p&gt;As I have been playing around with Visual Studio Code recently and &lt;a href=&quot;https://alexanderzeitler.com/articles/debugging-a-nodejs-es6-application-in-a-docker-container-using-visual-studio-code/&quot;&gt;debugging Node.js in Docker containers&lt;/a&gt; as well as &lt;a href=&quot;https://alexanderzeitler.com/articles/debugging-mocha-tests-using-visual-studio-code-and-nvm/&quot;&gt;debugging mocha tests&lt;/a&gt; (both using VS Code), this thought came up: &amp;quot;how would I be able to debug mocha tests using Visual Studio Code in Docker containers?&amp;quot;&lt;/p&gt;
&lt;p&gt;In the end, it worked out to be pretty staight forwarded.&lt;/p&gt;
&lt;p&gt;Make sure to have ES6 enabled in &lt;code&gt;jsconfig.json&lt;/code&gt; for the sample code:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;compilerOptions&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;target&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;ES6&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;module&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;commonjs&amp;quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First, I have a little express application inside &lt;code&gt;./index.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; express = &lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;express&amp;#x27;&lt;/span&gt;);&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; app = &lt;span class=&quot;hljs-title function_&quot;&gt;express&lt;/span&gt;();&lt;br /&gt;&lt;br /&gt;app.&lt;span class=&quot;hljs-title function_&quot;&gt;get&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;/&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;req, res&lt;/span&gt;) =&amp;gt;&lt;/span&gt; {&lt;br /&gt;    res.&lt;span class=&quot;hljs-title function_&quot;&gt;status&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;200&lt;/span&gt;).&lt;span class=&quot;hljs-title function_&quot;&gt;send&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;hello world&amp;#x27;&lt;/span&gt;);&lt;br /&gt;})&lt;br /&gt;&lt;br /&gt;app.&lt;span class=&quot;hljs-title function_&quot;&gt;listen&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;3000&lt;/span&gt;);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This application isn&#39;t really required for our tests but my aforementioned scenario was like it. Furthermore we need a process that prevents our container from exiting after he has been started, so we&#39;ll stick with that little app.&lt;/p&gt;
&lt;p&gt;Next, we have a &lt;code&gt;Dockerfile&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-dockerfile&quot;&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;FROM&lt;/span&gt; node:&lt;span class=&quot;hljs-number&quot;&gt;4.2&lt;/span&gt;.&lt;span class=&quot;hljs-number&quot;&gt;3&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;EXPOSE&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;3000&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;EXPOSE&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;5858&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;COPY&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; . /app&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;WORKDIR&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; /app&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;RUN&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;cd&lt;/span&gt; /app; npm install&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;CMD&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; [&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;node&amp;quot;&lt;/span&gt;,&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;index.js&amp;quot;&lt;/span&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As this post is about debugging mocha tests, here&#39;s our little test (&lt;code&gt;./test/test.js&lt;/code&gt;) we want to debug inside the container:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; assert = &lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;assert&amp;#x27;&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-title function_&quot;&gt;describe&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;Truth&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;hljs-function&quot;&gt;() =&amp;gt;&lt;/span&gt; {&lt;br /&gt;    &lt;span class=&quot;hljs-title function_&quot;&gt;it&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;should be told&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;done&lt;/span&gt;) =&amp;gt;&lt;/span&gt; {&lt;br /&gt;        assert.&lt;span class=&quot;hljs-title function_&quot;&gt;equal&lt;/span&gt;(&lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;, &lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;)&lt;br /&gt;        &lt;span class=&quot;hljs-title function_&quot;&gt;done&lt;/span&gt;()&lt;br /&gt;    })&lt;br /&gt;})&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The idea of how I want to debug this test inside a Docker container is as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start the container&lt;/li&gt;
&lt;li&gt;Run mocha inside the container with &lt;code&gt;--debug-brk&lt;/code&gt; at port &lt;code&gt;5858&lt;/code&gt; to wait for Visual Studio Code to attach to the debugger&lt;/li&gt;
&lt;li&gt;Attach Visual Studio code and debug the test&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There is another viable method:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Don&#39;t use our app in &lt;code&gt;index.js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Call mocha with &lt;code&gt;--debug-brk&lt;/code&gt; and port &lt;code&gt;5858&lt;/code&gt; in the &lt;code&gt;Dockerfile&lt;/code&gt; &lt;code&gt;CMD&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Start the container&lt;/li&gt;
&lt;li&gt;Attach Visual Studio code and debug the test&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As I don&#39;t want to have a container dedicated for debugging of mocha tests, I prefer the first version.&lt;/p&gt;
&lt;p&gt;Starting the container consists of building the image and running the container. This can be easily automated using &lt;code&gt;nodemon&lt;/code&gt;, &lt;code&gt;grunt-watch&lt;/code&gt; or something similar. You could even use a Docker Volume and not restart the container at all.&lt;/p&gt;
&lt;p&gt;Whatever automation you choose, here are the commands to build and run the container:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ docker build -t vscodemocha .&lt;br /&gt;$ docker run -d --name vscodemocha -p 3000:3000 -p 5858:5858 vscodemocha &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With our container being up and running, we can configure Visual Studio Code to run mocha inside the container.&lt;/p&gt;
&lt;p&gt;The Visual Studio Code &lt;code&gt;attach&lt;/code&gt; Configuration allows to provide a &lt;code&gt;preLaunchTask&lt;/code&gt; which is a task that can be defined inside the tasks configuration in &lt;code&gt;tasks.json&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So we&#39;ll simply create a &lt;code&gt;runmochaindocker&lt;/code&gt; task based on the &lt;code&gt;bash&lt;/code&gt; command in &lt;code&gt;tasks.json&lt;/code&gt; (inside the &lt;code&gt;.vscode&lt;/code&gt; folder):&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;version&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;0.1.0&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;isShellCommand&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;true&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;command&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;bash&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;args&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;-c&amp;quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;tasks&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;taskName&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;runmochaindocker&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;showOutput&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;always&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;suppressTaskName&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;true&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;args&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;docker exec -i vscodemocha bash -c &#92;&amp;quot;./node_modules/mocha/bin/mocha --debug-brk=5858 -t 10000 test/test.js&#92;&amp;quot;&amp;quot;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Having that done, we&#39;re now referencing that task in the &lt;code&gt;attach&lt;/code&gt; configuration in &lt;code&gt;launch.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;version&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;0.2.0&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;configurations&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Attach to Mocha in Docker&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;node&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;request&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;attach&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;port&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;5858&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;preLaunchTask&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;runmochaindocker&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;address&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;localhost&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;restart&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;false&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;sourceMaps&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;false&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;outDir&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;null&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;localRoot&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;${workspaceRoot}/&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;remoteRoot&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;/app/&amp;quot;&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As already said, &lt;code&gt;runmochaindocker&lt;/code&gt; now is defined as a &lt;code&gt;preLaunchTask&lt;/code&gt;. And as already described in &lt;a href=&quot;http://localhost:4000/articles/debugging-a-nodejs-es6-application-in-a-docker-container-using-visual-studio-code/&quot;&gt;my recent post on debugging a Node.js app in a Docker container&lt;/a&gt;, I set &lt;code&gt;localRoot&lt;/code&gt; and &lt;code&gt;remoteRoot&lt;/code&gt; accordingly.&lt;/p&gt;
&lt;p&gt;Guess what? We&#39;re done!&lt;/p&gt;
&lt;p&gt;Just hit F5 now, and you&#39;ll get this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/debugging-mocha-tests-in-a-docker-container-using-visual-studio-code/runmochaindocker.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Then, hit F5 again:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/debugging-mocha-tests-in-a-docker-container-using-visual-studio-code/debugmocha.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;(To be honest, I&#39;m not sure why the debugger breaks here - but we can ignore that).&lt;/p&gt;
&lt;p&gt;Hit F5 one last time, and we&#39;re there: debugging our mocha test in a Docker container:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/debugging-mocha-tests-in-a-docker-container-using-visual-studio-code/debugmochaindocker.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Happy debugging!&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Deploying Ubuntu Mate Desktop as a developer environment in AWS EC2</title>
    <link href="https://alexanderzeitler.com/articles/deploying-ubuntu-mate-desktop-as-developer-environment-on-aws-ec2/"/>
    <updated>2016-04-29T00:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/deploying-ubuntu-mate-desktop-as-developer-environment-on-aws-ec2/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@alexzeitler_&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@alexzeitler_&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Deploying Ubuntu Mate Desktop as a developer environment in AWS EC2&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Want to install a Ubuntu Desktop in the cloud without going nuts?&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/deploying-ubuntu-mate-desktop-as-developer-environment-on-aws-ec2/ubuntumateonawsec2_twitter.png&quot; /&gt;
&lt;blockquote&gt;
&lt;p&gt;TL;DR: If you don&#39;t want to find out the solution by running over 40 Ubuntu installations with several desktop environments on AWS, read on.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/deploying-ubuntu-mate-desktop-as-developer-environment-on-aws-ec2/ubuntumateonawsec2_twitter.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;On a current project the idea came up to put Ubuntu Desktop developer environments into AWS EC2 instead of local installations (VMs) and RDP into it.&lt;/p&gt;
&lt;p&gt;If you have a Windows background, you would expect to just spin up a server and have a UI...&lt;/p&gt;
&lt;p&gt;But with Ubuntu things get a little *cough* bit more complicated.&lt;/p&gt;
&lt;p&gt;In general, there are several options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Install an Ubuntu or Debian Server and install an Ubuntu Desktop environment onto it, then connect via VNC to it&lt;/li&gt;
&lt;li&gt;Install an Ubuntu or Debian Server and install an Ubuntu Desktop environment onto it, then connect via RDP to it&lt;/li&gt;
&lt;li&gt;Create an AMI based on an existing AMI&lt;/li&gt;
&lt;li&gt;Create an AMI from scratch&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Another option that sounds appealing &lt;em&gt;won&#39;t&lt;/em&gt; work: Create a Windows Server VM, activate Hyper-V, install VMware Workstation or VirtualBox and just run your Ubuntu Desktop inside it.&lt;br /&gt;
The first two options won&#39;t work because you can&#39;t have nested Hypervisors in AWS EC2 and VirtualBox would only allow 32bit OS installations because of this constraint.&lt;/p&gt;
&lt;p&gt;So, back to the viable options: First, I tried to install Ubuntu Server 14.04 from the official AWS AMI, install the XFCE or GNOME-Desktop on it as well as &lt;code&gt;vncserver&lt;/code&gt;.&lt;br /&gt;
While it works in general, the VNC performance and quality is not acceptable in 2016 (I expect true color and reasonable screen resolutions) - especially with GNOME or more complex desktop environments.&lt;br /&gt;
Besides that, XFCE feels like late 90th:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/deploying-ubuntu-mate-desktop-as-developer-environment-on-aws-ec2/xfce.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;So, lets head over to the next option - the one I ended up with:&lt;/p&gt;
&lt;p&gt;Install Ubuntu Server from the official AWS EC2 AMI, put Ubuntu Mate desktop on it and RDP into it.&lt;br /&gt;
What sounds like a straight forward plan, ended up as a *interesting* journey.&lt;/p&gt;
&lt;p&gt;I&#39;ve chosen (read: learned to choose) Mate, because it is one of the environments that looks pretty nice but doesn&#39;t require 3D acceleration enabled which you just don&#39;t have in cloud environments (at least not in the general compute instances).&lt;/p&gt;
&lt;p&gt;If you&#39;re ok with XCFE, the folks at AWS have created a &lt;a href=&quot;https://aws.amazon.com/premiumsupport/knowledge-center/connect-to-linux-desktop-from-windows/&quot;&gt;manual&lt;/a&gt; for that (on which this post partially is based on).&lt;/p&gt;
&lt;p&gt;First, create a AWS EC2 instance of your choice, enable inbound Port 3389 (RDP) in its security group settings and SSH into it.&lt;/p&gt;
&lt;p&gt;Enabling Port 3389:
&lt;img src=&quot;https://alexanderzeitler.com/articles/deploying-ubuntu-mate-desktop-as-developer-environment-on-aws-ec2/securitygroupindescription.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/deploying-ubuntu-mate-desktop-as-developer-environment-on-aws-ec2/securitydetails.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/deploying-ubuntu-mate-desktop-as-developer-environment-on-aws-ec2/securityrdp.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After that, install the latest updates:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt-get udpate&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt-get upgrade&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt-get dist-ugprade&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, enable password authentication for &lt;code&gt;sshd&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; vim /etc/ssh/sshd_config&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Find the line &lt;code&gt;PasswordAuthentication no&lt;/code&gt; and change it to &lt;code&gt;PasswordAuthentication yes&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Save the &lt;code&gt;sshd_config&lt;/code&gt; and restart &lt;code&gt;sshd&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; /etc/init.d/ssh restart&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, create a password for your default &lt;code&gt;ubuntu&lt;/code&gt; user:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; -i&lt;br /&gt;passwd ubuntu&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, create a user which you want to be your Desktop user, I&#39;ll use &lt;code&gt;awsgui&lt;/code&gt; here:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; useradd -m awsgui&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; passwd awsgui&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; usermod -aG admin awsgui&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; usermod -aG &lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; awsgui&lt;br /&gt;su - awsgui&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After that, install the Ubuntu Mate desktop and &lt;code&gt;xrdp&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;export&lt;/span&gt; DEBIAN_FRONTEND=noninteractive&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt-add-repository ppa:ubuntu-mate-dev/ppa&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt-add-repository ppa:ubuntu-mate-dev/trusty-mate&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt-get update &amp;amp;&amp;amp; &lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt-get upgrade&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt-get install --no-install-recommends ubuntu-mate-core ubuntu-mate-desktop&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt-get install mate-core mate-desktop-environment mate-notification-daemon&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt-get install xrdp&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, make Mate the default desktop environment for &lt;code&gt;xrdp&lt;/code&gt; sessions (also for new users being created afterwards):&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;echo&lt;/span&gt; mate-session&amp;gt; ~/.xsession&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;cp&lt;/span&gt; /home/awsgui/.xsession /etc/skel&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, we&#39;ll allow changing the host post port we can connect to (this allows reconnecting to an abandoned session):&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; vim /etc/xrdp/xrdp.ini&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Change &lt;code&gt;port=-1&lt;/code&gt; in the &lt;code&gt;[xrdp1]&lt;/code&gt; section to &lt;code&gt;port=ask-1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;With the changes done, restart &lt;code&gt;xrdp&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; service xrdp restart&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we&#39;re able to RDP into our AWS EC2 instance from Windows:&lt;/p&gt;
&lt;p&gt;First, enter the public DNS entry (later on you&#39;ll assign an Elastic IP, of course) into the Windows Remote Desktop Client:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/deploying-ubuntu-mate-desktop-as-developer-environment-on-aws-ec2/rdp-login.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Next you should get the &lt;code&gt;xrdp&lt;/code&gt; login screen were you enter your &lt;code&gt;awsgui&lt;/code&gt; credentials and make sure  &lt;code&gt;sesman-Xvnc&lt;/code&gt; module is selected:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/deploying-ubuntu-mate-desktop-as-developer-environment-on-aws-ec2/xrdp-login.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If everything went well, you should get this connection log:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/deploying-ubuntu-mate-desktop-as-developer-environment-on-aws-ec2/connectionlog.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After a second or two, you should also get the Ubuntu Mate desktop screen:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/deploying-ubuntu-mate-desktop-as-developer-environment-on-aws-ec2/matedesktop.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If you think we&#39;re done now: no.&lt;/p&gt;
&lt;p&gt;Lets consider, we installed an EC2 instance with an SSD, that volume is mount to &lt;code&gt;/mnt&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To make use of it, we need to &lt;code&gt;chown&lt;/code&gt; it for the &lt;code&gt;awsgui&lt;/code&gt; user - open a Terminal window by hitting &lt;code&gt;&amp;lt;Ctrl&amp;gt;&amp;lt;Alt&amp;gt;&amp;lt;T&amp;gt;&lt;/code&gt; in the Mate environment. Then run:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;chown&lt;/span&gt; awsgui /mnt&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, we can make use of &lt;code&gt;/mnt&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Running software in the Mate environment&lt;/h2&gt;
&lt;p&gt;Here are some tips for running software in that environment.&lt;/p&gt;
&lt;h3&gt;Ubuntu Software Center&lt;/h3&gt;
&lt;p&gt;First of all, Ubuntu Software Center doesn&#39;t work due to some configuration errors I didn&#39;t get behind.&lt;br /&gt;
So if you have some nice &lt;code&gt;.deb&lt;/code&gt; packages like Dropbox, just install them via Terminal:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; dpkg -i &amp;lt;package&amp;gt;.deb&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Changing the timezone&lt;/h3&gt;
&lt;p&gt;If you want to change the timezone of your instance, you can&#39;t unlock and edit the setting in the UI.&lt;/p&gt;
&lt;p&gt;Thus, you have to run &lt;code&gt;sudo dpkg-reconfigure tzdata&lt;/code&gt; in Terminal and select the appropriate setting.&lt;/p&gt;
&lt;h3&gt;Dropbox&lt;/h3&gt;
&lt;p&gt;If you plan to install Dropbox, just download the &lt;code&gt;.deb&lt;/code&gt; file and install it as described before.&lt;br /&gt;
You can start Dropbox using &lt;code&gt;start dropbox -i&lt;/code&gt; to install the daemon.&lt;/p&gt;
&lt;p&gt;After the installation you might see a weird or even no notification icon.&lt;br /&gt;
Just restart your EC2 instance and everything should be ok.&lt;/p&gt;
&lt;h3&gt;Restart / Shutdown via Mate&lt;/h3&gt;
&lt;p&gt;Speaking of restarting: You might notice that using the Mate logout / shutdown / restart feature won&#39;t end your RDP session.
Just use &lt;code&gt;sudo reboot&lt;/code&gt; or &lt;code&gt;sudo shutdown now -h&lt;/code&gt; in Terminal.&lt;/p&gt;
&lt;h3&gt;Docker on &lt;code&gt;/mnt&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;If you&#39;re running Docker and want to use the aforementioned volume &lt;code&gt;/mnt&lt;/code&gt; (because otherwise it might eat up your small boot volume) for the images and volumes, just change your Docker config:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; vim /etc/default/docker&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add this line or change / extend your &lt;code&gt;DOCKER_OPTS&lt;/code&gt; entry as follows:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;DOCKER_OPTS=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;-g /mnt/.docker&amp;quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Before you restart your Docker service, make sure to create the folder:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;mkdir&lt;/span&gt; /mnt/.docker&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now restart Docker by running &lt;code&gt;sudo service docker restart&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker pull node:4.2.3&lt;/code&gt; should pull Node 4.2.3 and put it to &lt;code&gt;/mnt/.docker&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/deploying-ubuntu-mate-desktop-as-developer-environment-on-aws-ec2/dockeronmnt.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Visual Studio Code&lt;/h3&gt;
&lt;p&gt;It just doesn&#39;t work in &lt;code&gt;xrdp&lt;/code&gt; environments as of now - even with the latest Insider build installed.&lt;/p&gt;
&lt;p&gt;I guess working with the environment will show some more caveats and if I get them solved, I&#39;ll keep the solutions posted.&lt;/p&gt;
&lt;p&gt;I also would be happy if you share further tips in the comments.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Deploying Ubuntu Mate Desktop as a developer environment in a Azure VM</title>
    <link href="https://alexanderzeitler.com/articles/deploying-ubuntu-mate-desktop-as-developer-environment-on-a-azure-vm/"/>
    <updated>2016-06-01T00:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/deploying-ubuntu-mate-desktop-as-developer-environment-on-a-azure-vm/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@alexzeitler_&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@alexzeitler_&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Deploying Ubuntu Mate Desktop as a developer environment in a Azure VM&#39;&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Want to install a Ubuntu Desktop in Azure without going nuts?&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/deploying-ubuntu-mate-desktop-as-developer-environment-on-a-azure-vm/ubuntumateazurevm.jpg&quot; /&gt;
&lt;blockquote&gt;
&lt;p&gt;TL;DR: While boring looking XFCE installation on Ubuntu Server is shown everywhere, this post shows you how to get a nice Mate Desktop on Ubuntu in Azure up and running and RDP into it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/deploying-ubuntu-mate-desktop-as-developer-environment-on-a-azure-vm/ubuntumateazurevm.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In Windows Server environments you just spin up a Server and have a GUI, install Visual Studio and start developing.&lt;/p&gt;
&lt;p&gt;But with Ubuntu Server things get a little bit more complicated.&lt;/p&gt;
&lt;p&gt;I&#39;ve chosen (read: learned to choose) Mate Desktop, because it is one of the environments that looks pretty nice but doesn&#39;t require 3D acceleration enabled which you just don&#39;t have in cloud environments (at least not in the general compute instances).&lt;/p&gt;
&lt;p&gt;First, create a Azure VM instance of your choice with a username and password, enable inbound Port 3389 (RDP) in its NIC security group settings and SSH into it.&lt;/p&gt;
&lt;p&gt;Enabling Port 3389 for RDP, first select &amp;quot;Network interfaces&amp;quot;:&lt;br /&gt;
&lt;img src=&quot;https://alexanderzeitler.com/articles/deploying-ubuntu-mate-desktop-as-developer-environment-on-a-azure-vm/vmdetails.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Next, select the &amp;quot;Network security group&amp;quot;:
&lt;img src=&quot;https://alexanderzeitler.com/articles/deploying-ubuntu-mate-desktop-as-developer-environment-on-a-azure-vm/vmnic.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Select &amp;quot;Inbound security rules&amp;quot;:
&lt;img src=&quot;https://alexanderzeitler.com/articles/deploying-ubuntu-mate-desktop-as-developer-environment-on-a-azure-vm/vmnicsecuritygroup.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You&#39;ll see the default inbound rules:
&lt;img src=&quot;https://alexanderzeitler.com/articles/deploying-ubuntu-mate-desktop-as-developer-environment-on-a-azure-vm/inboundrulesdefault.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Add a new rule for RDP via port 3389:&lt;br /&gt;
&lt;img src=&quot;https://alexanderzeitler.com/articles/deploying-ubuntu-mate-desktop-as-developer-environment-on-a-azure-vm/addrdprule.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Check the updated inbound rules:&lt;br /&gt;
&lt;img src=&quot;https://alexanderzeitler.com/articles/deploying-ubuntu-mate-desktop-as-developer-environment-on-a-azure-vm/inboundrulesupdated.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After that, install the latest updates:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt-get udpate&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt-get upgrade&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt-get dist-ugprade&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that done, install the Ubuntu Mate desktop and &lt;code&gt;xrdp&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;export&lt;/span&gt; DEBIAN_FRONTEND=noninteractive&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt-add-repository ppa:ubuntu-mate-dev/ppa&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt-add-repository ppa:ubuntu-mate-dev/trusty-mate&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt-get update &amp;amp;&amp;amp; &lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt-get upgrade&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt-get install --no-install-recommends ubuntu-mate-core ubuntu-mate-desktop&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt-get install mate-core mate-desktop-environment mate-notification-daemon&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt-get install xrdp&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, make Mate the default desktop environment for &lt;code&gt;xrdp&lt;/code&gt; sessions (also for new users being created afterwards):&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;echo&lt;/span&gt; mate-session&amp;gt; ~/.xsession&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;cp&lt;/span&gt; ~/.xsession /etc/skel&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, we&#39;ll allow changing the host post port we can connect to (this allows reconnecting to an abandoned session):&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; vim /etc/xrdp/xrdp.ini&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Change &lt;code&gt;port=-1&lt;/code&gt; in the &lt;code&gt;[xrdp1]&lt;/code&gt; section to &lt;code&gt;port=ask-1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;With the changes done, restart &lt;code&gt;xrdp&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; service xrdp restart&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we&#39;re able to RDP into our Azure VM from Windows:&lt;/p&gt;
&lt;p&gt;You should get the &lt;code&gt;xrdp&lt;/code&gt; login screen were you enter your users credentials and make sure  &lt;code&gt;sesman-Xvnc&lt;/code&gt; module is selected:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/deploying-ubuntu-mate-desktop-as-developer-environment-on-a-azure-vm/rdplogin.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If everything went well, you should get this connection log:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/deploying-ubuntu-mate-desktop-as-developer-environment-on-a-azure-vm/rdploginsuccess.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After a second or two, you should also get the Ubuntu Mate desktop screen:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/deploying-ubuntu-mate-desktop-as-developer-environment-on-a-azure-vm/ubuntumateazurevm.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If you think we&#39;re done now: no.&lt;/p&gt;
&lt;h2&gt;Running software in the Mate environment&lt;/h2&gt;
&lt;p&gt;Here are some tips for running software in that environment.&lt;/p&gt;
&lt;h3&gt;Ubuntu Software Center&lt;/h3&gt;
&lt;p&gt;First of all, Ubuntu Software Center doesn&#39;t work due to some configuration errors I didn&#39;t get behind.&lt;br /&gt;
So if you have some nice &lt;code&gt;.deb&lt;/code&gt; packages like Dropbox, just install them via Terminal:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; dpkg -i &amp;lt;package&amp;gt;.deb&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Changing the timezone&lt;/h3&gt;
&lt;p&gt;If you want to change the timezone of your instance, you can&#39;t unlock and edit the setting in the UI.&lt;/p&gt;
&lt;p&gt;Thus, you have to run &lt;code&gt;sudo dpkg-reconfigure tzdata&lt;/code&gt; in Terminal and select the appropriate setting.&lt;/p&gt;
&lt;h3&gt;Dropbox&lt;/h3&gt;
&lt;p&gt;If you plan to install Dropbox, just download the &lt;code&gt;.deb&lt;/code&gt; file and install it as described before.&lt;br /&gt;
You can start Dropbox using &lt;code&gt;start dropbox -i&lt;/code&gt; to install the daemon.&lt;/p&gt;
&lt;p&gt;After the installation you might see a weird or even no notification icon.&lt;br /&gt;
Just restart your Azure VM and everything should be ok.&lt;/p&gt;
&lt;p&gt;If you still experience issues, you can also install &lt;code&gt;caja-dropbox&lt;/code&gt; with is optimizied for the caja file manager of MATE.&lt;/p&gt;
&lt;h3&gt;Restart / Shutdown via Mate&lt;/h3&gt;
&lt;p&gt;Speaking of restarting: You might notice that using the Mate logout / shutdown / restart feature won&#39;t end your RDP session.
Just use &lt;code&gt;sudo reboot&lt;/code&gt; or &lt;code&gt;sudo shutdown now -h&lt;/code&gt; in Terminal.&lt;/p&gt;
&lt;h3&gt;Visual Studio Code / ATOM&lt;/h3&gt;
&lt;p&gt;It just doesn&#39;t work in &lt;code&gt;xrdp&lt;/code&gt; environments as of now - even with the latest Insider build installed.&lt;/p&gt;
&lt;p&gt;Today I published a &lt;a href=&quot;https://gist.github.com/AlexZeitler/ddd6bbf46f5a260b88565b953f5c1d3b&quot;&gt;gist&lt;/a&gt; which describes a fix to get VS Code / ATOM in Ubuntu via RDP working.&lt;/p&gt;
&lt;p&gt;I would be happy if you share further tips in the comments.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Mongoose toObject and toJSON transform behavior with sub-documents</title>
    <link href="https://alexanderzeitler.com/articles/mongoose-tojson-toobject-transform-with-subdocuments/"/>
    <updated>2016-06-08T00:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/mongoose-tojson-toobject-transform-with-subdocuments/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@alexzeitler_&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@alexzeitler_&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Mongoose toObject and toJSON transform behavior with sub-documents&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Mongoose toObject and toJSON transform behavior with sub-documents&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/mongoose-tojson-toobject-transform-with-subdocuments/mongoose.png&quot; /&gt;
&lt;p&gt;Mongoose supports two Schema options to transform Objects after querying MongoDb: &lt;code&gt;toObject&lt;/code&gt; and &lt;code&gt;toJSON&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In general you can access the returned object in the &lt;code&gt;transform&lt;/code&gt; method &lt;code&gt;toObject&lt;/code&gt; or &lt;code&gt;toJSON&lt;/code&gt; as described in &lt;a href=&quot;http://mongoosejs.com/docs/api.html#document_Document-toObject&quot;&gt;the docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Where things get interesting is when you&#39;re trying to use sub-documents and want them to be not touched by the transform method of the root or parent document.&lt;/p&gt;
&lt;p&gt;After playing around with &lt;code&gt;toObject&lt;/code&gt; and &lt;code&gt;toJSON&lt;/code&gt; transforms with sub-documents, I observed the behaviors described as follows.&lt;/p&gt;
&lt;p&gt;First, our two schemas for context:&lt;/p&gt;
&lt;p&gt;User.js:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;&amp;#x27;use strict&amp;#x27;&lt;/span&gt;;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; mongoose = &lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;mongoose&amp;#x27;&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;UserSchema&lt;/span&gt; = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; mongoose.&lt;span class=&quot;hljs-title class_&quot;&gt;Schema&lt;/span&gt;({&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;name&lt;/span&gt;: &lt;span class=&quot;hljs-title class_&quot;&gt;String&lt;/span&gt;&lt;br /&gt;});&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-variable language_&quot;&gt;module&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;exports&lt;/span&gt; = mongoose.&lt;span class=&quot;hljs-title function_&quot;&gt;model&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;User&amp;quot;&lt;/span&gt;, &lt;span class=&quot;hljs-title class_&quot;&gt;UserSchema&lt;/span&gt;);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Post.js:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;&amp;#x27;use strict&amp;#x27;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; mongoose = &lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;mongoose&amp;#x27;&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;PostSchema&lt;/span&gt; = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; mongoose.&lt;span class=&quot;hljs-title class_&quot;&gt;Schema&lt;/span&gt;({&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;title&lt;/span&gt;: &lt;span class=&quot;hljs-title class_&quot;&gt;String&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;postedBy&lt;/span&gt;: {&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;type&lt;/span&gt;: mongoose.&lt;span class=&quot;hljs-property&quot;&gt;Schema&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;Types&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;ObjectId&lt;/span&gt;,&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;ref&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;User&amp;#x27;&lt;/span&gt;&lt;br /&gt;  },&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;comments&lt;/span&gt;: [{&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;text&lt;/span&gt;: &lt;span class=&quot;hljs-title class_&quot;&gt;String&lt;/span&gt;,&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;postedBy&lt;/span&gt;: {&lt;br /&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;type&lt;/span&gt;: mongoose.&lt;span class=&quot;hljs-property&quot;&gt;Schema&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;Types&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;ObjectId&lt;/span&gt;,&lt;br /&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;ref&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;User&amp;#x27;&lt;/span&gt;&lt;br /&gt;    }&lt;br /&gt;  }]&lt;br /&gt;&lt;br /&gt;}, {&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;toObject&lt;/span&gt;: {&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;transform&lt;/span&gt;: &lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; (&lt;span class=&quot;hljs-params&quot;&gt;doc, ret&lt;/span&gt;) {&lt;br /&gt;      &lt;span class=&quot;hljs-keyword&quot;&gt;delete&lt;/span&gt; ret.&lt;span class=&quot;hljs-property&quot;&gt;_id&lt;/span&gt;;&lt;br /&gt;    }&lt;br /&gt;  },&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;toJSON&lt;/span&gt;: {&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;transform&lt;/span&gt;: &lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; (&lt;span class=&quot;hljs-params&quot;&gt;doc, ret&lt;/span&gt;) {&lt;br /&gt;      &lt;span class=&quot;hljs-keyword&quot;&gt;delete&lt;/span&gt; ret.&lt;span class=&quot;hljs-property&quot;&gt;_id&lt;/span&gt;;&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;});&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-variable language_&quot;&gt;module&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;exports&lt;/span&gt; = mongoose.&lt;span class=&quot;hljs-title function_&quot;&gt;model&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Post&amp;quot;&lt;/span&gt;, &lt;span class=&quot;hljs-title class_&quot;&gt;PostSchema&lt;/span&gt;);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, &lt;code&gt;mongoose.Schema&lt;/code&gt; accepts a second parameter which contains the definitions for &lt;code&gt;toObject&lt;/code&gt; and &lt;code&gt;toJSON&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next, let&#39;s use both Schemas in simple sample:&lt;/p&gt;
&lt;p&gt;app.js&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;&amp;#x27;use strict&amp;#x27;&lt;/span&gt;;&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;./database&amp;quot;&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;User&lt;/span&gt; = &lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;./User&amp;#x27;&lt;/span&gt;),&lt;br /&gt;  &lt;span class=&quot;hljs-title class_&quot;&gt;Post&lt;/span&gt; = &lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;./Post&amp;#x27;&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; alex = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;User&lt;/span&gt;({&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Alex&amp;quot;&lt;/span&gt;&lt;br /&gt;});&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; joe = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;User&lt;/span&gt;({&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Joe&amp;quot;&lt;/span&gt;&lt;br /&gt;});&lt;br /&gt;&lt;br /&gt;alex.&lt;span class=&quot;hljs-title function_&quot;&gt;save&lt;/span&gt;();&lt;br /&gt;joe.&lt;span class=&quot;hljs-title function_&quot;&gt;save&lt;/span&gt;();&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; post = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Post&lt;/span&gt;({&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;title&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Hello World&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;postedBy&lt;/span&gt;: alex.&lt;span class=&quot;hljs-property&quot;&gt;_id&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;comments&lt;/span&gt;: [{&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;text&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Nice post!&amp;quot;&lt;/span&gt;,&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;postedBy&lt;/span&gt;: joe.&lt;span class=&quot;hljs-property&quot;&gt;_id&lt;/span&gt;&lt;br /&gt;  }, {&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;text&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Thanks :)&amp;quot;&lt;/span&gt;,&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;postedBy&lt;/span&gt;: alex.&lt;span class=&quot;hljs-property&quot;&gt;_id&lt;/span&gt;&lt;br /&gt;  }]&lt;br /&gt;});&lt;br /&gt;&lt;br /&gt;post.&lt;span class=&quot;hljs-title function_&quot;&gt;save&lt;/span&gt;(&lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; (&lt;span class=&quot;hljs-params&quot;&gt;error&lt;/span&gt;) {&lt;br /&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (!error) {&lt;br /&gt;    &lt;span class=&quot;hljs-title class_&quot;&gt;Post&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;find&lt;/span&gt;({})&lt;br /&gt;      .&lt;span class=&quot;hljs-title function_&quot;&gt;populate&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;postedBy&amp;#x27;&lt;/span&gt;)&lt;br /&gt;      .&lt;span class=&quot;hljs-title function_&quot;&gt;populate&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;comments.postedBy&amp;#x27;&lt;/span&gt;)&lt;br /&gt;      .&lt;span class=&quot;hljs-title function_&quot;&gt;exec&lt;/span&gt;(&lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; (&lt;span class=&quot;hljs-params&quot;&gt;error, posts&lt;/span&gt;) {&lt;br /&gt;        &lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(posts)&lt;br /&gt;      })&lt;br /&gt;  }&lt;br /&gt;});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The result is as follows:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt; comments&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;Object&lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;Object&lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    __v&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    postedBy&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt; __v&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &amp;#x27;Alex&amp;#x27; &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    title&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &amp;#x27;Hello World&amp;#x27; &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, &lt;code&gt;_id&lt;/code&gt; from both &lt;code&gt;Post&lt;/code&gt; and &lt;code&gt;User&lt;/code&gt; get removed by the &lt;code&gt;toObject&lt;/code&gt; transformation.&lt;/p&gt;
&lt;p&gt;Next, we&#39;ll replace &lt;code&gt;console.log(posts)&lt;/code&gt; as follwos:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;hljs-title class_&quot;&gt;JSON&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;stringify&lt;/span&gt;(posts, &lt;span class=&quot;hljs-literal&quot;&gt;null&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;&#92;t&amp;#x27;&lt;/span&gt;));&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The result is the following JSON:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Hello World&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;postedBy&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;_id&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;57588aa352559c927c98c793&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Alex&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;__v&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;__v&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;comments&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;				&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Nice post!&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;				&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;postedBy&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;					&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;_id&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;57588aa352559c927c98c794&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;					&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Joe&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;					&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;__v&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;&lt;br /&gt;				&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;				&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;_id&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;57588aa352559c927c98c797&amp;quot;&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;				&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Thanks :)&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;				&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;postedBy&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;					&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;_id&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;57588aa352559c927c98c793&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;					&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Alex&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;					&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;__v&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;&lt;br /&gt;				&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;				&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;_id&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;57588aa352559c927c98c796&amp;quot;&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;_id&lt;/code&gt; from &lt;code&gt;Post&lt;/code&gt; gets removed while &lt;code&gt;_id&lt;/code&gt; from &lt;code&gt;User&lt;/code&gt; remains.&lt;/p&gt;
&lt;p&gt;The same behavior can be seen when we call &lt;code&gt;toJSON&lt;/code&gt; explicitly:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; json = posts.&lt;span class=&quot;hljs-title function_&quot;&gt;map&lt;/span&gt;(&lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt; (&lt;span class=&quot;hljs-params&quot;&gt;p&lt;/span&gt;) {&lt;br /&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; p.&lt;span class=&quot;hljs-title function_&quot;&gt;toJSON&lt;/span&gt;()&lt;br /&gt;});&lt;br /&gt;&lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(json)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Result:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt; title&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &amp;#x27;Hello World&amp;#x27;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    postedBy&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt; _id&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;57588&lt;/span&gt;c7ebf8340cc859660e1&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &amp;#x27;Alex&amp;#x27;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt; __v&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    __v&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    comments&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;Object&lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;Object&lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt; title&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &amp;#x27;Hello World&amp;#x27;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    postedBy&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt; _id&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;57588&lt;/span&gt;c97fc8232548659e6d4&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &amp;#x27;Alex&amp;#x27;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt; __v&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    __v&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    comments&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;Object&lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;Object&lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So it looks like &lt;code&gt;toObject&lt;/code&gt; is being applied to sub-documents while &lt;code&gt;toJSON&lt;/code&gt; is not. And this is different from what the  &lt;a href=&quot;http://mongoosejs.com/docs/api.html#document_Document-toObject&quot;&gt;documentation&lt;/a&gt; says:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Transforms are applied only to the document and are not applied to sub-documents.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The other option is that I&#39;m wrong but questions on stackoverflow show it really might be the documentation.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Non-Interactive Azure Resource Manager (ARM) login</title>
    <link href="https://alexanderzeitler.com/articles/Non-Interactive-Azure-Resource-Manager-Login/"/>
    <updated>2016-06-25T00:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/Non-Interactive-Azure-Resource-Manager-Login/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@alexzeitler_&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@alexzeitler_&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Non-Interactive Azure Resource Manager (ARM) login&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;How to log into Azure Resource Manager without entering credentials&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/Non-Interactive-Azure-Resource-Manager-Login/arm.png&quot; /&gt;
&lt;p&gt;&lt;a href=&quot;https://azure.microsoft.com/en-us/documentation/articles/resource-group-overview/&quot;&gt;Azure Resource Manager&lt;/a&gt; allows you to deploy, manage, and monitor all of the resources for your solution as a group, rather than handling these resources individually.&lt;/p&gt;
&lt;p&gt;TL;DR from the docs:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;With Resource Manager, you can create a simple template (in JSON format) that defines deployment and configuration of your application. This template is known as a Resource Manager template and provides a declarative way to define deployment. By using a template, you can repeatedly deploy your application throughout the app lifecycle and have confidence your resources are deployed in a consistent state.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Before you can deploy a resource group using PowerShell, you have to login using the &lt;code&gt;Login-AzureRmAccount&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;By default, this opens a dialog where you can enter your Azure credentials:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/Non-Interactive-Azure-Resource-Manager-Login/login.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This is ok if you are working with ARM on your local machine, but you don&#39;t want this behavior on your CI-Server for example.&lt;/p&gt;
&lt;p&gt;Luckily, the &lt;code&gt;Login-AzureRmAccount&lt;/code&gt; command also accepts an &lt;code&gt;-Credential&lt;/code&gt; parameter which is an object consisting of your username and password.&lt;/p&gt;
&lt;p&gt;The following PowerShell commands show how to create this object:&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$accountName&lt;/span&gt; =&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;your account name&amp;quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$password&lt;/span&gt; = &lt;span class=&quot;hljs-built_in&quot;&gt;ConvertTo-SecureString&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;your password&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;-AsPlainText&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;-Force&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$credential&lt;/span&gt; = &lt;span class=&quot;hljs-built_in&quot;&gt;New-Object&lt;/span&gt; System.Management.Automation.PSCredential(&lt;span class=&quot;hljs-variable&quot;&gt;$accountName&lt;/span&gt;, &lt;span class=&quot;hljs-variable&quot;&gt;$password&lt;/span&gt;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can login using the &lt;code&gt;Login-AzureRmAccount&lt;/code&gt; command like this:&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;Login&lt;span class=&quot;hljs-literal&quot;&gt;-AzureRmAccount&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;-Credential&lt;/span&gt; &lt;span class=&quot;hljs-variable&quot;&gt;$credential&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you&#39;re providing username and password using environment variables for example, you can set &lt;code&gt;$accountName&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt; using these commands:&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$accountName&lt;/span&gt; = (&lt;span class=&quot;hljs-built_in&quot;&gt;get-item&lt;/span&gt; env:&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;ACCOUNTNAME&amp;quot;&lt;/span&gt;).Value&lt;br /&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$password&lt;/span&gt; = (&lt;span class=&quot;hljs-built_in&quot;&gt;get-item&lt;/span&gt; env:&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;PASSWORD&amp;quot;&lt;/span&gt;).Value&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Happy deployment!&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Fixing Chrome 58+ [missing_subjectAltName] with openssl when using self signed certificates</title>
    <link href="https://alexanderzeitler.com/articles/Fixing-Chrome-missing_subjectAltName-selfsigned-cert-openssl/"/>
    <updated>2017-04-23T00:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/Fixing-Chrome-missing_subjectAltName-selfsigned-cert-openssl/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@alexzeitler_&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@alexzeitler_&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Fixing Chrome 58+ [missing_subjectAltName] with openssl when using self signed certificates&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Fixing Chrome 58+ [missing_subjectAltName] with openssl when using self signed certificates&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/Fixing-Chrome-missing_subjectAltName-selfsigned-cert-openssl/missing_subjectAltName.png&quot; /&gt;
&lt;p&gt;Since version 58, Chrome requires SSL certificates to use SAN (Subject Alternative Name)  instead of the popular Common Name (CN), thus &lt;a href=&quot;https://groups.google.com/a/chromium.org/forum/#!msg/security-dev/IGT2fLJrAeo/csf_1Rh1AwAJ&quot;&gt;CN support has been removed&lt;/a&gt;.&lt;br /&gt;
If you&#39;re using self signed certificates (but not only!) having only CN defined, you get an error like this when calling a website using the self signed certificate:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/Fixing-Chrome-missing_subjectAltName-selfsigned-cert-openssl/missing_subjectAltName.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Here&#39;s how to create a self signed certificate with SAN using &lt;code&gt;openssl&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;First, lets create a root CA cert using &lt;code&gt;createRootCA.sh&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;#!/usr/bin/env bash&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;mkdir&lt;/span&gt; ~/ssl/&lt;br /&gt;openssl genrsa -des3 -out ~/ssl/rootCA.key 2048&lt;br /&gt;openssl req -x509 -new -nodes -key ~/ssl/rootCA.key -sha256 -days 1024 -out ~/ssl/rootCA.pem&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, create a file &lt;code&gt;createselfsignedcertificate.sh&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;#!/usr/bin/env bash&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; openssl req -new -sha256 -nodes -out server.csr -newkey rsa:2048 -keyout server.key -config &amp;lt;( &lt;span class=&quot;hljs-built_in&quot;&gt;cat&lt;/span&gt; server.csr.cnf )&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; openssl x509 -req -&lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; server.csr -CA ~/ssl/rootCA.pem -CAkey ~/ssl/rootCA.key -CAcreateserial -out server.crt -days 500 -sha256 -extfile v3.ext&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, create the openssl configuration file &lt;code&gt;server.csr.cnf&lt;/code&gt; referenced in the openssl command above:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;[req]&lt;br /&gt;default_bits = 2048&lt;br /&gt;prompt = no&lt;br /&gt;default_md = sha256&lt;br /&gt;distinguished_name = dn&lt;br /&gt; &lt;br /&gt;[dn]&lt;br /&gt;C=US&lt;br /&gt;ST=New York&lt;br /&gt;L=Rochester&lt;br /&gt;O=End Point&lt;br /&gt;OU=Testing Domain&lt;br /&gt;emailAddress=your-administrative-address@your-awesome-existing-domain.com&lt;br /&gt;CN = localhost&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we need to create the &lt;code&gt;v3.ext&lt;/code&gt; file in order to create a X509 v3 certificate instead of a v1 which is the default when not specifying a extension file:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;authorityKeyIdentifier=keyid,issuer&lt;br /&gt;basicConstraints=CA:FALSE&lt;br /&gt;keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment&lt;br /&gt;subjectAltName = @alt_names&lt;br /&gt;&lt;br /&gt;[alt_names]&lt;br /&gt;DNS.1 = localhost&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In order to create your cert, first run &lt;code&gt;createRootCA.sh&lt;/code&gt; which we created first.
Next, run &lt;code&gt;createselfsignedcertificate.sh&lt;/code&gt; to create the self signed cert using &lt;code&gt;localhost&lt;/code&gt; as the SAN and CN.&lt;/p&gt;
&lt;p&gt;After adding the &lt;code&gt;rootCA.pem&lt;/code&gt; to the list of your trusted root CAs, you can use the &lt;code&gt;server.key&lt;/code&gt; and &lt;code&gt;server.crt&lt;/code&gt; in your web server and browse &lt;code&gt;https://localhost&lt;/code&gt; using Chrome 58 or later:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/Fixing-Chrome-missing_subjectAltName-selfsigned-cert-openssl/validsan.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You can also verify your certificate to contain the SAN by calling&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;openssl x509 -text -&lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; server.crt -noout&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This should look like this:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Certificate:&lt;br /&gt;    Data:&lt;br /&gt;        Version: 3 (0x2)&lt;br /&gt;        Serial Number: 17237690484651272016 (0xef38942aa5c52750)&lt;br /&gt;    Signature Algorithm: sha256WithRSAEncryption&lt;br /&gt;        Issuer: C=US, ST=New York, L=Rochester, O=End Point, CN=localhost/your-administrative-address@your-awesome-existing-domain.com&lt;br /&gt;        Validity&lt;br /&gt;            Not Before: Apr 23 16:07:38 2017 GMT&lt;br /&gt;            Not After : Sep  5 16:07:38 2018 GMT&lt;br /&gt;        Subject: C=US, ST=New York, L=Rochester, O=End Point, OU=Testing Domain/emailAddress=your-administrative-address@your-awesome-existing-domain.com, CN=localhost&lt;br /&gt;        Subject Public Key Info:&lt;br /&gt;            Public Key Algorithm: rsaEncryption&lt;br /&gt;                Public-Key: (2048 bit)&lt;br /&gt;                Modulus:&lt;br /&gt;                    00:b2:e3:bd:ed:28:04:85:ea:75:ee:d2:82:e1:eb:&lt;br /&gt;                    f5:5f:7f:cf:7e:cb:70:de:86:9f:75:7c:f3:71:e7:&lt;br /&gt;                    da:16:fb:bc:1f:89:bc:47:08:77:ca:33:20:f1:c1:&lt;br /&gt;                    9e:e3:20:8d:89:14:7e:c1:0a:12:d2:59:24:56:9b:&lt;br /&gt;                    77:90:5f:69:d1:a5:f1:00:38:93:1b:a7:75:f1:33:&lt;br /&gt;                    e2:da:dc:32:a9:0a:85:7d:9a:20:81:ca:20:ee:86:&lt;br /&gt;                    ce:e2:a0:52:d2:ab:11:34:e5:52:99:3a:81:c6:9f:&lt;br /&gt;                    6b:0f:6a:02:2b:38:a6:84:c9:ba:fa:9b:ef:0a:89:&lt;br /&gt;                    22:4b:79:86:3c:bd:44:a5:54:fb:cf:4d:8b:d1:44:&lt;br /&gt;                    03:35:22:de:69:77:c8:fa:4d:c6:01:25:08:9f:4d:&lt;br /&gt;                    a9:79:7a:aa:ca:03:b6:e4:51:57:22:27:5f:a7:12:&lt;br /&gt;                    11:f3:e6:00:29:f6:58:be:2c:aa:09:e4:06:45:d9:&lt;br /&gt;                    3f:75:a7:f0:75:bd:2b:a6:bb:6d:ad:93:bb:b9:1d:&lt;br /&gt;                    d7:75:39:4e:9b:1d:0e:39:cc:17:74:88:f7:e2:b7:&lt;br /&gt;                    85:12:96:e0:cb:42:56:d0:11:e0:84:86:e5:14:a5:&lt;br /&gt;                    f2:6d:43:5d:f9:59:ae:61:7f:01:ae:95:b8:92:27:&lt;br /&gt;                    1d:1c:02:d7:ad:fb:ee:f6:25:38:60:c8:41:20:17:&lt;br /&gt;                    80:69&lt;br /&gt;                Exponent: 65537 (0x10001)&lt;br /&gt;        X509v3 extensions:&lt;br /&gt;            X509v3 Authority Key Identifier: &lt;br /&gt;                keyid:5A:8D:89:64:BD:F2:3E:C2:D7:7B:BE:17:84:F4:29:E8:C5:32:35:34&lt;br /&gt;&lt;br /&gt;            X509v3 Basic Constraints: &lt;br /&gt;                CA:FALSE&lt;br /&gt;            X509v3 Key Usage: &lt;br /&gt;                Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment&lt;br /&gt;            X509v3 Subject Alternative Name: &lt;br /&gt;                DNS:localhost&lt;br /&gt;    Signature Algorithm: sha256WithRSAEncryption&lt;br /&gt;         27:1d:d6:84:50:33:d2:ff:b1:06:9b:fa:f1:40:7d:47:11:bc:&lt;br /&gt;         f7:80:fd:26:87:0e:91:9f:14:be:1f:1d:9b:32:d1:fb:d6:8d:&lt;br /&gt;         af:30:8a:88:38:8c:1c:bf:77:98:8e:cd:06:48:82:fa:09:b9:&lt;br /&gt;         3c:0d:38:c4:a0:da:b7:4d:f5:81:5f:5a:76:04:61:f8:c2:1a:&lt;br /&gt;         17:ad:56:7c:72:ba:f6:65:7f:7f:e7:5e:b2:34:ba:13:23:57:&lt;br /&gt;         84:f1:c5:ca:dd:5b:55:69:95:71:44:4a:30:53:61:5c:ad:47:&lt;br /&gt;         d8:9c:d5:a2:1b:18:2d:e1:19:35:3e:3f:b2:7e:fd:bf:f3:d0:&lt;br /&gt;         45:dc:f5:57:f0:1b:cd:70:1b:e0:34:de:27:98:89:b4:a5:25:&lt;br /&gt;         a5:6c:29:c3:89:a6:a5:c5:4d:f5:45:3b:47:8e:13:45:23:07:&lt;br /&gt;         5e:d6:59:0d:96:c6:a3:f0:c5:3d:ee:a8:ad:36:96:43:13:a1:&lt;br /&gt;         b8:55:f6:c7:10:7e:8f:5d:09:ef:61:17:2a:9c:3b:50:28:c8:&lt;br /&gt;         e3:8d:a6:34:06:50:d4:3e:d5:17:ea:7d:31:97:d3:ee:df:b5:&lt;br /&gt;         23:66:5e:22:b7:e4:fa:36:4f:9a:d5:f0:a3:f9:b4:2b:27:02:&lt;br /&gt;         0b:41:94:d1:a1:f7:1b:2c:7e:74:e6:14:c3:b5:67:15:d2:ca:&lt;br /&gt;         02:77:57:a6&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Watch for this line &lt;code&gt;Version: 3 (0x2)&lt;/code&gt; as well as &lt;code&gt;X509v3 Subject Alternative Name:&lt;/code&gt; (and below).&lt;/p&gt;
&lt;p&gt;Happy self signing!&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Accessing an HTTP API running on your MacBook in a Docker container from your iPhone/iPad using dnsmasq</title>
    <link href="https://alexanderzeitler.com/articles/accessing-an-api-in-docker-on-macbook-from-ios-iphone-ipad-using-dnsmasq/"/>
    <updated>2017-04-23T00:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/accessing-an-api-in-docker-on-macbook-from-ios-iphone-ipad-using-dnsmasq/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@alexzeitler_&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@alexzeitler_&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Accessing an HTTP API running on your MacBook in a Docker container from your iPhone/iPad using dnsmasq&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Accessing an HTTP API running on your MacBook in a Docker container from your iPhone/iPad using dnsmasq&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/accessing-an-api-in-docker-on-macbook-from-ios-iphone-ipad-using-dnsmasq/dnsmasqui.png&quot; /&gt;
&lt;p&gt;Please consider the following scenario:&lt;/p&gt;
&lt;p&gt;On my MacBook Pro I want to access a web application (SPA/PWA) and an API both running in Docker containers behind another nginx Docker container (reverse proxy managing SSL) from an iOS device.&lt;/p&gt;
&lt;p&gt;So I have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1 iOS device having IP &lt;code&gt;192.168.178.77&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;1 MacBook Pro having IP &lt;code&gt;192.168.178.64&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;1 Docker container running nginx having two &lt;code&gt;server_name&lt;/code&gt; settings: app.dev and api.dev, both using self-signed SSL certificates.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The challenge: the&#39;re no option in the local Router to point DNS entries to &lt;code&gt;192.168.178.64&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/etc/hosts&lt;/code&gt; has these entries:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;192.168.178.64 app.dev&lt;br /&gt;192.168.178.64 api.dev&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The easiest solution I&#39;ve found is to use &lt;code&gt;dnsmasq&lt;/code&gt; running in a Docker container.&lt;/p&gt;
&lt;p&gt;So what is &lt;code&gt;dnsmasq&lt;/code&gt;?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Dnsmasq is a lightweight, easy to configure, DNS forwarder and DHCP server. It is designed to provide DNS and optionally, DHCP, to a small network. It can serve the names of local machines which are not in the global DNS. The DHCP server integrates with the DNS server and allows machines with DHCP-allocated addresses to appear in the DNS with names configured either in each host or in a central configuration file. Dnsmasq supports static and dynamic DHCP leases and BOOTP/TFTP for network booting of diskless machines&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.debian.org/HowTo/dnsmasq&quot;&gt;source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In order to start &lt;code&gt;dnsmasq&lt;/code&gt; in Docker container, simply run this command:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;docker run &#92;&lt;br /&gt;	--name dnsmasq &#92;&lt;br /&gt;	-d &#92;&lt;br /&gt;	-p 53:53/udp &#92;&lt;br /&gt;	-p 5380:8080 &#92;&lt;br /&gt;	-v /opt/dnsmasq.conf:/etc/dnsmasq.conf &#92;&lt;br /&gt;	--log-opt &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;max-size=100m&amp;quot;&lt;/span&gt; &#92;&lt;br /&gt;	-e &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;USER=foo&amp;quot;&lt;/span&gt; &#92;&lt;br /&gt;	-e &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;PASS=bar&amp;quot;&lt;/span&gt; &#92;&lt;br /&gt;	jpillora/dnsmasq&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will run &lt;code&gt;dnsmasq&lt;/code&gt; mapping a configuration from &lt;code&gt;/opt/dnsmasq.conf&lt;/code&gt; into the container.&lt;/p&gt;
&lt;p&gt;Make sure &lt;code&gt;/opt/dnsmsq.conf&lt;/code&gt; at least contains this line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;address=/dev/192.168.178.64
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will route all traffic to &lt;code&gt;*.dev&lt;/code&gt; to &lt;code&gt;192.168.178.64&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dnsmasq&lt;/code&gt; will also inspect your local &lt;code&gt;/etc/hosts&lt;/code&gt; file and route traffic appropriately.&lt;/p&gt;
&lt;p&gt;It also provides a simple UI where you can verify your &lt;code&gt;hosts&lt;/code&gt; file got hooked up correctly:
Just browse to &lt;code&gt;http://localhost:5380&lt;/code&gt; and enter the credentials &lt;code&gt;foo&lt;/code&gt; and &lt;code&gt;bar&lt;/code&gt; from the &lt;code&gt;docker run&lt;/code&gt; command above.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/accessing-an-api-in-docker-on-macbook-from-ios-iphone-ipad-using-dnsmasq/dnsmasqui.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The UI output should look like this:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;[webproc] 2017/04/23 18:35:54 loaded config files changes from disk&lt;br /&gt;[webproc] 2017/04/23 18:35:54 agent listening on http://0.0.0.0:8080...&lt;br /&gt;dnsmasq: started, version 2.76 cachesize 150&lt;br /&gt;dnsmasq: compile time options: IPv6 GNU-getopt no-DBus no-i18n no-IDN DHCP DHCPv6 no-Lua TFTP no-conntrack ipset auth no-DNSSEC loop-detect inotify&lt;br /&gt;dnsmasq: reading /etc/resolv.conf&lt;br /&gt;dnsmasq: using nameserver 192.168.65.1#53&lt;br /&gt;dnsmasq: read /etc/hosts - 7 addresses&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The next step is to update the DNS settings on the iOS and macOS devices.&lt;br /&gt;
The iOS device has to point to the IP address of the macOS device:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/accessing-an-api-in-docker-on-macbook-from-ios-iphone-ipad-using-dnsmasq/ios.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The macOS device has to point to itself:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/accessing-an-api-in-docker-on-macbook-from-ios-iphone-ipad-using-dnsmasq/macosnetwork.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Click &amp;quot;Advanced...&amp;quot;, then select &amp;quot;DNS&amp;quot;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/accessing-an-api-in-docker-on-macbook-from-ios-iphone-ipad-using-dnsmasq/macosnetworkdns.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The last step is to call the &lt;code&gt;https://app.dev&lt;/code&gt; and &lt;code&gt;https://api.dev&lt;/code&gt; URIs on the iOS device and confirm the SSL warnings.&lt;/p&gt;
&lt;p&gt;Other solutions to this seem to be http://xip.io/ or http://nip.io/ but I didn&#39;t try them...&lt;/p&gt;
&lt;p&gt;Happy developing!&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Getting started with CQRS</title>
    <link href="https://alexanderzeitler.com/articles/getting-started-with-cqrs/"/>
    <updated>2018-04-11T21:45:00Z</updated>
    <id>https://alexanderzeitler.com/articles/getting-started-with-cqrs/</id>
    <content type="html">&lt;p&gt;CQRS and Event Sourcing have been there for a while now but especially in 2017 and the first months of 2018 we can see a rapid growth of inquires for CQRS based applications — why is this?&lt;/p&gt;
&lt;p&gt;Many cloud based applications are built following the pattern of Microservices and are publishing or consuming events for integration purposes.&lt;/p&gt;
&lt;p&gt;“The New Stack” recently released the article “&lt;a href=&quot;https://thenewstack.io/microservices-its-all-about-the-events/&quot;&gt;Microservices: It’s All About the Events&lt;/a&gt;” and I want to quote this paragraph from the post:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“One of the key things about distributed systems is that you have a whole bunch of independent control loops responsible for their own processing,” she said. One of the favored concepts in this space is Command Query Responsibility Segregation (CQRS), which separates the channels for inserting data into a data store from the channel of querying that data, so that the performance of one is not dependent on another.&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;p&gt;At &lt;a href=&quot;https://pdmlab.com/&quot;&gt;PDMLab&lt;/a&gt;, we’re building several SaaS solutions applying the CQRS pattern and recently I’ve been asked, how I got started with CQRS.&lt;/p&gt;
&lt;p&gt;Instead of repeating this answer over and over again, I decided to share my resources, that helped me getting into CQRS a few years ago, in this post.&lt;/p&gt;
&lt;p&gt;First of all - of course - some videos from Greg Young, who came up with the definition of CQRS.&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube-nocookie.com/embed/8JKjvY4etTY&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;  
&lt;br /&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube-nocookie.com/embed/LDW0QWie21s&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;br /&gt;
Greg Young also has a simple CQRS sample on GitHub: [https://github.com/gregoryyoung/m-r](https://github.com/gregoryyoung/m-r)
&lt;p&gt;Several blog posts to get started with CQRS can be found at &lt;a href=&quot;https://cqrs.wordpress.com/&quot;&gt;https://cqrs.wordpress.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A book, which helped me getting into CQRS was &amp;quot;CQRS&amp;quot; by Mark Nijhof: &lt;a href=&quot;https://leanpub.com/cqrs&quot;&gt;https://cqrs.wordpress.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Another video worth watching is &amp;quot;Eventually Consistent Distributed Systems with Node.js&amp;quot;:&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube-nocookie.com/embed/X_VHWQa1k0k&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;br /&gt;
&lt;p&gt;If you&#39;re looking for a more complete implementation of a sample application, you should have a look at the CQRS post series from Matthew Jones &lt;a href=&quot;https://exceptionnotfound.net/tag/cqrs/&quot;&gt;here&lt;/a&gt; (posts) and &lt;a href=&quot;https://github.com/exceptionnotfound/DotNetCqrsDemo&quot;&gt;here&lt;/a&gt; (code).&lt;/p&gt;
&lt;p&gt;The samples from Matthew are based on &lt;a href=&quot;https://github.com/gautema/CQRSlite&quot;&gt;CQRSLite&lt;/a&gt;, a CQRS framework for .NET Standard which we&#39;re using as well.&lt;/p&gt;
&lt;p&gt;If you&#39;re looking for a CQRS framework for Node.js, you should have a look at &lt;a href=&quot;https://www.wolkenkit.io/&quot;&gt;wolkenkit&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you&#39;re around in Vancouver these days, you also should attend &lt;a href=&quot;https://www.eventbrite.ca/e/lestercon-vancouver-2018-tickets-44856176030&quot;&gt;Lestercon&lt;/a&gt; where Adaptech will open source some of their Microservices / CQRS stacks and toolings.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Running ASP.NET Core on minikube</title>
    <link href="https://alexanderzeitler.com/articles/running-asp-net-core-on-minikube/"/>
    <updated>2018-11-13T11:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/running-asp-net-core-on-minikube/</id>
    <content type="html">&lt;p&gt;There are a ton of managed Kubernetes services available today (and for sure even more tomorrow) to run your ASP.NET Core application.&lt;/p&gt;
&lt;p&gt;If you want to run k8s locally you can &lt;a href=&quot;https://github.com/kelseyhightower/kubernetes-the-hard-way&quot;&gt;spin up a whole cluster manually&lt;/a&gt;. Another solution is to use &lt;code&gt;minikube&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So, what is &lt;code&gt;minikube&lt;/code&gt; exactly?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Minikube is a tool that makes it easy to run Kubernetes locally. Minikube runs a single-node Kubernetes cluster inside a VM on your laptop for users looking to try out Kubernetes or develop with it day-to-day.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://kubernetes.io/docs/setup/minikube/&quot;&gt;https://kubernetes.io/docs/setup/minikube/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Installing minikube&lt;/h3&gt;
&lt;p&gt;In order to be able to install &lt;code&gt;minikube&lt;/code&gt; you need to have a Hypervisor like VirtualBox (Linux) or VMWare Fusion (macOS) installed. (This post won’t cover Windows btw.).&lt;/p&gt;
&lt;p&gt;After installing your Hypervisor of choice, you have to install minikube&lt;/p&gt;
&lt;h3&gt;On macOS&lt;/h3&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.30.0/minikube-darwin-amd64 &amp;amp;&amp;amp; &lt;span class=&quot;hljs-built_in&quot;&gt;chmod&lt;/span&gt; +x minikube &amp;amp;&amp;amp; &lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;cp&lt;/span&gt; minikube /usr/local/bin/ &amp;amp;&amp;amp; &lt;span class=&quot;hljs-built_in&quot;&gt;rm&lt;/span&gt; minikube&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;On Linux (Ubuntu 18.04 here)&lt;/h3&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.30.0/minikube-linux-amd64 &amp;amp;&amp;amp; &lt;span class=&quot;hljs-built_in&quot;&gt;chmod&lt;/span&gt; +x minikube &amp;amp;&amp;amp; &lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;cp&lt;/span&gt; minikube /usr/local/bin/ &amp;amp;&amp;amp; &lt;span class=&quot;hljs-built_in&quot;&gt;rm&lt;/span&gt; minikube&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Installing kubectl&lt;/h3&gt;
&lt;p&gt;In order to manage your k8s cluster or &lt;code&gt;minikube&lt;/code&gt;, you need a command line tool named &lt;code&gt;kubectl&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;On macOS&lt;/h3&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;brew install kubernetes-cli&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;On Linux (Ubuntu 18.04 here)&lt;/h3&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt-get update &amp;amp;&amp;amp; &lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt-get install -y apt-transport-https&lt;br /&gt;curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | &lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt-key add -&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;deb http://apt.kubernetes.io/ kubernetes-xenial main&amp;quot;&lt;/span&gt; | &lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;tee&lt;/span&gt; -a /etc/apt/sources.list.d/kubernetes.list&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt-get update&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt-get install -y kubectl&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Running minikube&lt;/h3&gt;
&lt;p&gt;Its as simple as typing &lt;code&gt;minikube start&lt;/code&gt; into your terminal. And guess what? &lt;code&gt;minikube stop&lt;/code&gt; will stop it 😉.&lt;/p&gt;
&lt;h3&gt;Dockerizing ASP.NET Core&lt;/h3&gt;
&lt;p&gt;Next, we need some ASP.NET Core Code running in a Docker Container, so lets spin up &lt;a href=&quot;https://alexanderzeitler.com/articles/running-asp-net-core-on-minikube/&quot;&gt;JetBrains Rider&lt;/a&gt; and create a new Web API project for example:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/running-asp-net-core-on-minikube/rider-project.png&quot; alt=&quot;Rider rocks 😉&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The default code in &lt;code&gt;Startup.cs&lt;/code&gt; is fine here:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Startup&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Startup&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;IConfiguration configuration&lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;    {&lt;br /&gt;        Configuration = configuration;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; IConfiguration Configuration { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; }&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-comment&quot;&gt;// This method gets called by the runtime. Use this method to add services to the container.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;ConfigureServices&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;IServiceCollection services&lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;    {&lt;br /&gt;        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-comment&quot;&gt;// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Configure&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;IApplicationBuilder app, IHostingEnvironment env&lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;    {&lt;br /&gt;        app.UseMvc();&lt;br /&gt;    }&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The default &lt;code&gt;ValuesController&lt;/code&gt; is fine as well:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;[&lt;span class=&quot;hljs-meta&quot;&gt;Route(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;api/[controller]&amp;quot;&lt;/span&gt;)&lt;/span&gt;]&lt;br /&gt;[&lt;span class=&quot;hljs-meta&quot;&gt;ApiController&lt;/span&gt;]&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;ValuesController&lt;/span&gt; : &lt;span class=&quot;hljs-title&quot;&gt;ControllerBase&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;    &lt;span class=&quot;hljs-comment&quot;&gt;// GET api/values&lt;/span&gt;&lt;br /&gt;    [&lt;span class=&quot;hljs-meta&quot;&gt;HttpGet&lt;/span&gt;]&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; ActionResult&amp;lt;IEnumerable&amp;lt;&lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt;&amp;gt;&amp;gt; Get()&lt;br /&gt;    {&lt;br /&gt;        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt;[] {&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;value1&amp;quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;value2&amp;quot;&lt;/span&gt;};&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-comment&quot;&gt;// GET api/values/5&lt;/span&gt;&lt;br /&gt;    [&lt;span class=&quot;hljs-meta&quot;&gt;HttpGet(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;{id}&amp;quot;&lt;/span&gt;)&lt;/span&gt;]&lt;br /&gt;    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; ActionResult&amp;lt;&lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt;&amp;gt; &lt;span class=&quot;hljs-title&quot;&gt;Get&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt; id&lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;    {&lt;br /&gt;        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;value&amp;quot;&lt;/span&gt;;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-comment&quot;&gt;// POST api/values&lt;/span&gt;&lt;br /&gt;    [&lt;span class=&quot;hljs-meta&quot;&gt;HttpPost&lt;/span&gt;]&lt;br /&gt;    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Post&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;[FromBody] &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;value&lt;/span&gt;&lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;    {&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-comment&quot;&gt;// PUT api/values/5&lt;/span&gt;&lt;br /&gt;    [&lt;span class=&quot;hljs-meta&quot;&gt;HttpPut(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;{id}&amp;quot;&lt;/span&gt;)&lt;/span&gt;]&lt;br /&gt;    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Put&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt; id, [FromBody] &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;value&lt;/span&gt;&lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;    {&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-comment&quot;&gt;// DELETE api/values/5&lt;/span&gt;&lt;br /&gt;    [&lt;span class=&quot;hljs-meta&quot;&gt;HttpDelete(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;{id}&amp;quot;&lt;/span&gt;)&lt;/span&gt;]&lt;br /&gt;    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Delete&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt; id&lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;    {&lt;br /&gt;    }&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we need a &lt;code&gt;Dockerfile&lt;/code&gt; in our project:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/running-asp-net-core-on-minikube/dockerfiler-rider.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You can just grab the sample from the official &lt;a href=&quot;https://docs.docker.com/engine/examples/dotnetcore/&quot;&gt;Docker docs&lt;/a&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-dockerfile&quot;&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;FROM&lt;/span&gt; microsoft/dotnet:sdk AS build-&lt;span class=&quot;hljs-keyword&quot;&gt;env&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;WORKDIR&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; /app&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Copy csproj and restore as distinct layers&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;COPY&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; *.csproj ./&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;RUN&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; dotnet restore&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Copy everything else and build&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;COPY&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; . ./&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;RUN&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; dotnet publish -c Release -o out&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Build runtime image&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;FROM&lt;/span&gt; microsoft/dotnet:aspnetcore-runtime&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;WORKDIR&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; /app&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;COPY&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; --from=build-env /app/out .&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;ENTRYPOINT&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt; [&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;dotnet&amp;quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;HelloNetCoreOnK8s.dll&amp;quot;&lt;/span&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make sure to change the second param of the &lt;code&gt;ENTRYPOINT&lt;/code&gt; array to the name of your DLL.&lt;/p&gt;
&lt;p&gt;Next, build your image:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;docker build -t hello-netcore-k8s .&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should get a result similar to this:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Sending build context to Docker daemon  1.185MB&lt;br /&gt;Step 1/10 : FROM microsoft/dotnet:sdk AS build-env&lt;br /&gt; ---&amp;gt; 6baac5bd0ea2&lt;br /&gt;Step 2/10 : WORKDIR /app&lt;br /&gt; ---&amp;gt; Using cache&lt;br /&gt; ---&amp;gt; 8b1b0606dfc8&lt;br /&gt;Step 3/10 : COPY *.csproj ./&lt;br /&gt; ---&amp;gt; Using cache&lt;br /&gt; ---&amp;gt; 987d3ba17281&lt;br /&gt;Step 4/10 : RUN dotnet restore&lt;br /&gt; ---&amp;gt; Using cache&lt;br /&gt; ---&amp;gt; 829018788c05&lt;br /&gt;Step 5/10 : COPY . ./&lt;br /&gt; ---&amp;gt; 60d4d75c8825&lt;br /&gt;Step 6/10 : RUN dotnet publish -c Release -o out&lt;br /&gt; ---&amp;gt; Running &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; 3e13616e9438&lt;br /&gt;Microsoft (R) Build Engine version 15.8.169+g1ccb72aefa &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; .NET Core&lt;br /&gt;Copyright (C) Microsoft Corporation. All rights reserved.&lt;br /&gt;&lt;br /&gt;  Restoring packages &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; /app/HelloNetCoreOnK8s.csproj...&lt;br /&gt;  Generating MSBuild file /app/obj/HelloNetCoreOnK8s.csproj.nuget.g.props.&lt;br /&gt;  Generating MSBuild file /app/obj/HelloNetCoreOnK8s.csproj.nuget.g.targets.&lt;br /&gt;  Restore completed &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; 1.68 sec &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; /app/HelloNetCoreOnK8s.csproj.&lt;br /&gt;  HelloNetCoreOnK8s -&amp;gt; /app/bin/Release/netcoreapp2.1/HelloNetCoreOnK8s.dll&lt;br /&gt;  HelloNetCoreOnK8s -&amp;gt; /app/out/&lt;br /&gt;Removing intermediate container 3e13616e9438&lt;br /&gt; ---&amp;gt; e0edd75b100e&lt;br /&gt;Step 7/10 : FROM microsoft/dotnet:aspnetcore-runtime&lt;br /&gt; ---&amp;gt; 1fe6774e5e9e&lt;br /&gt;Step 8/10 : WORKDIR /app&lt;br /&gt; ---&amp;gt; Using cache&lt;br /&gt; ---&amp;gt; 6c95530f2566&lt;br /&gt;Step 9/10 : COPY --from=build-env /app/out .&lt;br /&gt; ---&amp;gt; 34932a25e3b6&lt;br /&gt;Step 10/10 : ENTRYPOINT [&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;dotnet&amp;quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;HelloNetCoreOnK8s.dll&amp;quot;&lt;/span&gt;]&lt;br /&gt; ---&amp;gt; Running &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; 599e41e2537f&lt;br /&gt;Removing intermediate container 599e41e2537f&lt;br /&gt; ---&amp;gt; e24c6efee7d4&lt;br /&gt;Successfully built e24c6efee7d4&lt;br /&gt;Successfully tagged hello-netcore-k8s:latest&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Hello k8s&lt;/h3&gt;
&lt;p&gt;In order to create a k8s deployment, we need a yaml file which specifies what should be deployed in &lt;code&gt;minikube&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Itshould be placed in our project as well:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/running-asp-net-core-on-minikube/rider-deployment-yaml.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This is the content of the &lt;code&gt;deployment.yaml&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;apiVersion:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;extensions/v1beta1&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;Deployment&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-netcore-k8s&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;spec:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;replicas:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;3&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;template:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;labels:&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;app:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-netcore-k8s&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;spec:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;containers:&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-netcore-k8s&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;hljs-attr&quot;&gt;image:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-netcore-k8s&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;hljs-attr&quot;&gt;ports:&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;containerPort:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;80&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important part for now is the &lt;code&gt;spec/template/spec/containers&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;name&lt;/code&gt; specifies the name of the containers and &lt;code&gt;image&lt;/code&gt; specifies the name of the image the containers should be created from — this obviously is the name of the image we created recently.&lt;/p&gt;
&lt;p&gt;So lets create that deployment now using &lt;code&gt;kubectl&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl create -f deployment.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now check if the deployment has suceeded:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl get deployments&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The result will look similar to this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/running-asp-net-core-on-minikube/bash-first-deployment.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Well... shouldn’t it be available? Lets dig a bit deeper and check the pods:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl get pods&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/running-asp-net-core-on-minikube/kubectl-pods-error.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Looks like we have a problem here. Seems &lt;code&gt;minikube&lt;/code&gt; doesn’t know about our recently created image 🤔.&lt;/p&gt;
&lt;p&gt;The reason is that the Docker daemon in &lt;code&gt;minikube&lt;/code&gt; doesn’t know about our Docker daemon on our Linux/macOS host.&lt;/p&gt;
&lt;p&gt;But there’s a simple solution for that - we can share the context using this command:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;eval&lt;/span&gt; $(minikube docker-env)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can stop sharing the context using eval &lt;code&gt;$(minikube docker-env -u)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Another solution would have been to push the image to the official Docker hub but we want to stay local for this sample.&lt;/p&gt;
&lt;p&gt;Now we have to build our image again because of the different context — remember how to do it?&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;docker build -t hello-netcore-k8s .&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also we have to add the &lt;code&gt;imagePullPolicy&lt;/code&gt;: Never to our &lt;code&gt;deployment.yaml&lt;/code&gt; file:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;apiVersion:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;extensions/v1beta1&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;Deployment&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-netcore-k8s&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;spec:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;replicas:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;3&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;template:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;labels:&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;app:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-netcore-k8s&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;spec:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;containers:&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-netcore-k8s&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;hljs-attr&quot;&gt;imagePullPolicy:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;Never&lt;/span&gt; &lt;span class=&quot;hljs-comment&quot;&gt;# &amp;lt;-- here we go!&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;hljs-attr&quot;&gt;image:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-netcore-k8s&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;hljs-attr&quot;&gt;ports:&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;containerPort:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;80&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This ensures that the Docker daemon doesn’ t try to pull unknown images from the Docker registry.&lt;/p&gt;
&lt;p&gt;So… lets fire up our deployment again:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl create -f deployment.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Validating the deployment using &lt;code&gt;kubectl get deployments&lt;/code&gt; looks much better now:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/running-asp-net-core-on-minikube/bash-second-deployment.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Also the result of &lt;code&gt;kubectl get pods&lt;/code&gt; is much better now:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/running-asp-net-core-on-minikube/kubectl-pods-ok.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Accessing the Application using a Service&lt;/h3&gt;
&lt;p&gt;So you might ask yourself: how do I access the application now?&lt;/p&gt;
&lt;p&gt;In k8s and &lt;code&gt;minikube&lt;/code&gt; as well, you have to create a service to expose the application to a public IP/Port. If you’ re running your application in a managed k8s environment at your cloud provider of choice, the type of the service to expose your application is likely to be a &lt;code&gt;LoadBalancer&lt;/code&gt;. When using &lt;code&gt;minikube&lt;/code&gt;, the only service type available is &lt;code&gt;nodePort&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The command to expose your application using a &lt;code&gt;nodePort&lt;/code&gt;, is this:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl expose deployment hello-netcore-k8s --&lt;span class=&quot;hljs-built_in&quot;&gt;type&lt;/span&gt;=NodePort&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The result should be this:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;service/hello-netcore-k8s exposed&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, at which IP and Port has it been exposed? When using &lt;code&gt;minikube&lt;/code&gt;, you have to use this command (this is different when using managed k8s):&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;minikube service hello-netcore-k8s --url&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You’ll get an output like this:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;http://192.168.99.100:31422&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now lets call our ASP.NET Core API via &lt;code&gt;http get 192.168.99.100:31422/api/values&lt;/code&gt; (I’m using &lt;a href=&quot;https://httpie.org/&quot;&gt;HTTPie&lt;/a&gt; here, you can choose your preferred tool here — maybe &lt;a href=&quot;https://www.getpostman.com/&quot;&gt;Postman&lt;/a&gt;):&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;HTTP/1.1 200 OK&lt;br /&gt;Content-Type: application/json; charset=utf-8&lt;br /&gt;Date: Tue, 13 Nov 2018 21:30:33 GMT&lt;br /&gt;Server: Kestrel&lt;br /&gt;Transfer-Encoding: chunked[&lt;br /&gt;    &quot;value1&quot;,&lt;br /&gt;    &quot;value2&quot;&lt;br /&gt;]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yay! We’re done!&lt;/p&gt;
&lt;p&gt;But wait — why are we running on port &lt;code&gt;31442&lt;/code&gt; and not port &lt;code&gt;80&lt;/code&gt;?&lt;/p&gt;
&lt;p&gt;The reason for that is the default configuration of &lt;code&gt;minikube&lt;/code&gt; which provides a Port range between &lt;code&gt;30000–32767&lt;/code&gt; for services and by creating a service using the command from above a random port is picked from that pool.&lt;/p&gt;
&lt;p&gt;We should fix this, right?&lt;/p&gt;
&lt;p&gt;First, delete all services and deployments in &lt;code&gt;minikube&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl delete service hello-netcore-k8s&lt;br /&gt;kubectl delete deployment hello-netcore-k8s&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then stop &lt;code&gt;minikube&lt;/code&gt; using &lt;code&gt;minikube stop&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now start minikube again using:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;minikube start --extra-config=apiserver.service-node-port-range=80-30000&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will start &lt;code&gt;minikube&lt;/code&gt; again, but with a Port range for services between &lt;code&gt;80-30000&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now that we have port &lt;code&gt;80&lt;/code&gt; available, how can we use it?&lt;/p&gt;
&lt;p&gt;Add a &lt;code&gt;services.yaml&lt;/code&gt; to your project with this content:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;apiVersion:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;v1&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;Service&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-netcore-k8s&lt;/span&gt; &lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;labels:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;app:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-netcore-k8s&lt;/span&gt; &lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;spec:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;type:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;NodePort&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;ports:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;port:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;80&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;targetPort:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;80&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;nodePort:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;80&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;protocol:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;TCP&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;selector:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;app:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-netcore-k8s&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will assign port &lt;code&gt;80&lt;/code&gt; to our service which is created using this command:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl create -f services.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, &lt;code&gt;kubectl get services&lt;/code&gt; will result in:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/running-asp-net-core-on-minikube/kubectl-services.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;As you can see, port &lt;code&gt;80&lt;/code&gt; is used inside the Pod and mapped to the host port &lt;code&gt;80&lt;/code&gt; as expected.&lt;/p&gt;
&lt;p&gt;With that, &lt;code&gt;http get 192.168.99.100/api/values&lt;/code&gt; again results in:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;HTTP/1.1 200 OK&lt;br /&gt;Content-Type: application/json; charset=utf-8&lt;br /&gt;Date: Tue, 13 Nov 2018 21:30:33 GMT&lt;br /&gt;Server: Kestrel&lt;br /&gt;Transfer-Encoding: chunked[&lt;br /&gt;    &quot;value1&quot;,&lt;br /&gt;    &quot;value2&quot;&lt;br /&gt;]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But this time it is clearly using port &lt;code&gt;80&lt;/code&gt; - and now we’re done! 🙌&lt;/p&gt;
&lt;p&gt;You can find the source code &lt;a href=&quot;https://github.com/PDMLab/aspnetcore-on-minikube&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Passing a ViewModel by Attribute to ASP.NET Core ViewComponents</title>
    <link href="https://alexanderzeitler.com/articles/passing-viewmodel-by-attribute-to-aspnet-core-viewcomponents/"/>
    <updated>2018-12-28T01:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/passing-viewmodel-by-attribute-to-aspnet-core-viewcomponents/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Passing a ViewModel by Attribute to ASP.NET Core ViewComponents&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Passing a ViewModel by Attribute to ASP.NET Core ViewComponents&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/passing-viewmodel-by-attribute-to-aspnet-core-viewcomponents/structure-twitter-card.png&quot; /&gt;
&lt;p&gt;ASP.NET Core provides the concept of View components and the intro from the &lt;a href=&quot;https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components?view=aspnetcore-2.2&quot;&gt;official docs&lt;/a&gt; reads quite straight forward:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;View components are similar to partial views, but they&#39;re much more powerful. View components don&#39;t use model binding, and only depend on the data provided when calling into it. This article was written using ASP.NET Core MVC, but view components also work with Razor Pages.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Yet, it was a bit of confusing when trying to use View components with Tag Helpers and passing fragments of the parent view model to the View Component by attributes.&lt;/p&gt;
&lt;p&gt;First, lets take a look at what we&#39;re building:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/passing-viewmodel-by-attribute-to-aspnet-core-viewcomponents/structure.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;As you can see, there are two &amp;quot;sections&amp;quot; named &amp;quot;Books&amp;quot; and &amp;quot;Services&amp;quot; and both have child elements.&lt;/p&gt;
&lt;p&gt;In order keep some structure and separate concerns, we don&#39;t want to render everything in the main view.
The structure, we want to establish, is this:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Index-View of HomeController&lt;br /&gt; - PageSection (&quot;Books&quot;)&lt;br /&gt;  - SectionItems (List&lt;SectionItem&gt;)&lt;br /&gt;    - SectionItem (Title &quot;Kubernetes&quot;)&lt;/SectionItem&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our complete ViewModel class structure is this - being loaded from a database or a headless CMS:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;IndexViewModel&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; List&amp;lt;PageSection&amp;gt; Sections { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;set&lt;/span&gt;; }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;PageSection&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; Title { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;set&lt;/span&gt;; }&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; List&amp;lt;SectionItem&amp;gt; SectionItems { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;set&lt;/span&gt;; }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;SectionItem&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; Text { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;set&lt;/span&gt;; }&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the sake of simplicity, I&#39;ll just create it inline in &lt;code&gt;HomeController.cs&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;HomeController&lt;/span&gt; : &lt;span class=&quot;hljs-title&quot;&gt;Controller&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; IActionResult &lt;span class=&quot;hljs-title&quot;&gt;Index&lt;/span&gt;()&lt;/span&gt;&lt;br /&gt;    {&lt;br /&gt;        &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; indexViewModel = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; IndexViewModel&lt;br /&gt;        {&lt;br /&gt;            Sections = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; List&amp;lt;PageSection&amp;gt;&lt;br /&gt;            {&lt;br /&gt;                &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; PageSection&lt;br /&gt;                {&lt;br /&gt;                    Title = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Books&amp;quot;&lt;/span&gt;,&lt;br /&gt;                    SectionItems = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; List&amp;lt;SectionItem&amp;gt;&lt;br /&gt;                    {&lt;br /&gt;                        &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; SectionItem { Text = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;ASP.NET Core in Action&amp;quot;&lt;/span&gt; },&lt;br /&gt;                        &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; SectionItem { Text = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Node.js in Action&amp;quot;&lt;/span&gt;}&lt;br /&gt;                    }&lt;br /&gt;                },&lt;br /&gt;                &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; PageSection&lt;br /&gt;                {&lt;br /&gt;                    Title = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Services&amp;quot;&lt;/span&gt;,&lt;br /&gt;                    SectionItems = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; List&amp;lt;SectionItem&amp;gt;&lt;br /&gt;                    {&lt;br /&gt;                        &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; SectionItem { Text = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;ASP.NET Core Development&amp;quot;&lt;/span&gt;},&lt;br /&gt;                        &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; SectionItem { Text = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Node.js Development&amp;quot;&lt;/span&gt;},&lt;br /&gt;                        &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; SectionItem { Text = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Kubernetes&amp;quot;&lt;/span&gt;}&lt;br /&gt;                    }&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;        };&lt;br /&gt;        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; View(indexViewModel);&lt;br /&gt;    }&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the end, our &lt;code&gt;Index.cshtml&lt;/code&gt; for the &lt;code&gt;HomeController&lt;/code&gt; will be this:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;@model IndexViewModel&lt;br /&gt;@addTagHelper *, ViewComponentsSample&lt;br /&gt;&lt;br /&gt;@foreach (var pageSection in Model.Sections)&lt;br /&gt;{&lt;br /&gt;    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;vc:page-section&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;section&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;@pageSection&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;vc:page-section&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Tag Helper to invoke a view component uses the &lt;code&gt;&amp;lt;vc&amp;gt;&amp;lt;/vc&amp;gt;&lt;/code&gt; element and it added by the &lt;code&gt;addTagHelper&lt;/code&gt; directive.&lt;/p&gt;
&lt;p&gt;Also, Pascal-cased class and method parameters for Tag Helpers are translated into their lower kebab case - this will be important very soon.&lt;/p&gt;
&lt;p&gt;The first View component we&#39;ll implement, obviously is the &lt;code&gt;PageSection&lt;/code&gt; View component (remember Tag Helpers and the kebab casing convention? 😉)&lt;/p&gt;
&lt;p&gt;The path of the Components HTML is &lt;code&gt;~/Views/Shared/Components/PageSection/Default.cshtml&lt;/code&gt; and this is the markup:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;@model PageSection&lt;br /&gt;@addTagHelper *, ViewComponentsSample&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;h3&lt;/span&gt;&amp;gt;&lt;/span&gt;@Model.Title&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;h3&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;vc:section-items&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;section-items&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;@Model.SectionItems&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;vc:section-items&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This component also uses the &lt;code&gt;@addTagHelper&lt;/code&gt; directive and it defines a ViewModel of type &lt;code&gt;PageSection&lt;/code&gt; which has been a fragment of the &lt;code&gt;IndexViewModel&lt;/code&gt; from above.&lt;/p&gt;
&lt;p&gt;Besides rendering the Section title it also calls another View component and passes it&#39;s own &lt;code&gt;SectionItems&lt;/code&gt; (kebab-casing again) to the child View component.&lt;/p&gt;
&lt;p&gt;The code behind for these two View components is inside the &lt;code&gt;~/ViewComponents&lt;/code&gt; directory and their code looks like this:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;PageSection.cs&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;PageSection&lt;/span&gt; : &lt;span class=&quot;hljs-title&quot;&gt;ViewComponent&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; IViewComponentResult &lt;span class=&quot;hljs-title&quot;&gt;Invoke&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;Models.PageSection section&lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;    {&lt;br /&gt;        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; View(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;~/Views/Shared/Components/PageSection/Default.cshtml&amp;quot;&lt;/span&gt;, section);&lt;br /&gt;    }&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;SectionItems.cs&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;SectionItems&lt;/span&gt; : &lt;span class=&quot;hljs-title&quot;&gt;ViewComponent&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; IViewComponentResult &lt;span class=&quot;hljs-title&quot;&gt;Invoke&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;List&amp;lt;Models.SectionItem&amp;gt; sectionItems&lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;    {&lt;br /&gt;        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; View(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;~/Views/Shared/Components/SectionItems/Default.cshtml&amp;quot;&lt;/span&gt;, sectionItems);&lt;br /&gt;    }&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are some similarities both classes share:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;They derive from the &lt;code&gt;ViewComponent&lt;/code&gt; base class&lt;/li&gt;
&lt;li&gt;They implement the &lt;code&gt;Invoke&lt;/code&gt; method which returns a &lt;code&gt;IViewComponentResult&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;They pass the model to the Views &lt;code&gt;cshtml&lt;/code&gt; Template&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And now we have some conventions (&amp;quot;magic&amp;quot; 🤙) to understand here to get things working:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;Invoke&lt;/code&gt; method is called by the&lt;code&gt;vc&lt;/code&gt; TagHelper&lt;/li&gt;
&lt;li&gt;The name (and of course the type) of the parameter(s) passed as Tag Helper attributes have to match the name and type of the &lt;code&gt;Invoke&lt;/code&gt; method - and kebab casing is your friend again.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can nest View components at arbitrary levels, so I added another one to finally list the &lt;code&gt;SectionItem&lt;/code&gt;s.&lt;/p&gt;
&lt;p&gt;This is it&#39;s template:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;@model List&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;ViewComponentsSample.Models.SectionItem&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;@addTagHelper *, ViewComponentsSample&lt;br /&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;ul&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;    @foreach (var item in Model)&lt;br /&gt;    {&lt;br /&gt;        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;vc:section-item&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;text&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;@item.Text&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;vc:section-item&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;    }&lt;br /&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;ul&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And this is the code behind:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;@model string&lt;br /&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;li&lt;/span&gt;&amp;gt;&lt;/span&gt;@Model&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;li&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once you get it, it&#39;s pretty cool 😎&lt;/p&gt;
&lt;p&gt;The sample can be found on &lt;a href=&quot;https://github.com/PDMLab/aspnet-core-viewcomponents-sample&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Enabling the Kubernetes Dashboard for DigitalOcean Kubernetes</title>
    <link href="https://alexanderzeitler.com/articles/enabling-the-kubernetes-dashboard-for-digitalocean-kubernetes/"/>
    <updated>2019-01-03T05:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/enabling-the-kubernetes-dashboard-for-digitalocean-kubernetes/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Enabling the Kubernetes Dashboard for DigitalOcean Kubernetes&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Enabling the Kubernetes Dashboard for DigitalOcean Kubernetes&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/enabling-the-kubernetes-dashboard-for-digitalocean-kubernetes/k8s-dashboard.png&quot; /&gt;
&lt;p&gt;DigitalOcean recently announced the public availability of it&#39;s &lt;a href=&quot;https://www.digitalocean.com/products/kubernetes/?refcode=b6ff141992c6&quot;&gt;managed Kubernetes offering&lt;/a&gt; and it&#39;s pretty awesome.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/enabling-the-kubernetes-dashboard-for-digitalocean-kubernetes/k8s-control.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Once you created your cluster following their &lt;a href=&quot;https://www.digitalocean.com/docs/kubernetes/quickstart/&quot;&gt;quickstart&lt;/a&gt;, you might want to open the Kubernetes dashboard using &lt;code&gt;kubectl proxy&lt;/code&gt;. But browsing &lt;code&gt;http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/&lt;/code&gt; will result in a &lt;code&gt;404&lt;/code&gt; error.&lt;/p&gt;
&lt;p&gt;That&#39;s because the Kubernetes dashboard is not deployed by default, so let&#39;s do this now using:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl create --kubeconfig=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;your-digitalocean-kubeconfig.yaml&amp;quot;&lt;/span&gt; -f https://raw.githubusercontent.com/kubernetes/dashboard/master/aio/deploy/recommended/kubernetes-dashboard.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By the way, if you don&#39;t want to always specify the &lt;code&gt;--kubeconfig&lt;/code&gt; parameter, you can merge your Cluster configuration into your local Kubernetes Config using this &lt;a href=&quot;https://github.com/digitalocean/doctl&quot;&gt;DigitalOcean CLI&lt;/a&gt; command (make sure to login first using your DO token) - and I&#39;m assuming this for the next &lt;code&gt;kubectl&lt;/code&gt; commands issued in this post:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;doctl k8s cluster kubeconfig save &amp;lt;your-do-cluster-name&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once the configuration is merged, you can list the Kubernetes contexts by issuing this &lt;code&gt;kubectl&lt;/code&gt; command&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl config get-contexts&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which will output a result similar to this:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;CURRENT   NAME                                         CLUSTER                                      AUTHINFO                                           NAMESPACE&lt;br /&gt;          do-fra1-k8s-1-12-1-do-2-fra1-xxxxxxxxxx   do-fra1-k8s-1-12-1-do-2-fra1-xxxxxxxxxx   do-fra1-k8s-1-12-1-do-2-fra1-xxxxxxxxxx-admin&lt;br /&gt;*         docker-for-desktop                           docker-for-desktop-cluster                   docker-for-desktop&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, the current active context is your local Kubernetes and if you want to issue commands against your DigitalOcean Cluster, you&#39;ll have to switch the context to this cluster by running&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl config use-context do-fra1-k8s-1-12-1-do-2-fra1-xxxxxxxxxx&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If everything went fine, you should be able to list the Nodes of your DigitalOcean cluster:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl get nodes&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fingers crossed 🤞, the output should be similar to this (except for the cluster name, of course):&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;NAME                   STATUS    ROLES     AGE       VERSION&lt;br /&gt;eloquent-hypatia-h12   Ready     &amp;lt;none&amp;gt;    63d       v1.12.1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you try to access your Kubernetes dashboard now by running &lt;code&gt;kubectl proxy&lt;/code&gt; and logging in using your Cluster configuration &lt;code&gt;yaml&lt;/code&gt; file, you&#39;ll get this error:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Not enough data to create auth info structure.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Read on, we&#39;re solving this now 💪!&lt;/p&gt;
&lt;p&gt;Next, we need to create a Service Account and a &lt;code&gt;ClusterRoleBinding&lt;/code&gt; using this &lt;code&gt;serviceaccount.yaml&lt;/code&gt; file:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;apiVersion:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;v1&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;ServiceAccount&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;lt;ServiceAccountName&amp;gt;&lt;/span&gt; &lt;span class=&quot;hljs-comment&quot;&gt;# replace this with the username you want to use&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;namespace:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kube-system&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-meta&quot;&gt;---&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;apiVersion:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;rbac.authorization.k8s.io/v1beta1&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;ClusterRoleBinding&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;lt;ServiceAccountName&amp;gt;&lt;/span&gt; &lt;span class=&quot;hljs-comment&quot;&gt;# replace this with the username you want to use&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;roleRef:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;apiGroup:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;rbac.authorization.k8s.io&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;ClusterRole&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;cluster-admin&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;subjects:&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;ServiceAccount&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;lt;ServiceAccountName&amp;gt;&lt;/span&gt; &lt;span class=&quot;hljs-comment&quot;&gt;# replace this with the username you want to use&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;namespace:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kube-system&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then apply the &lt;code&gt;serviceaccount.yaml&lt;/code&gt; using:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl apply -f serviceaccount.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After that, we need to get the access token for the Service Account:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl get secret -n kube-system&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will give you a list like this:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;NAME                               TYPE                                  DATA      AGE&lt;br /&gt;csi-do-controller-sa-token-hsgv9   kubernetes.io/service-account-token   3         62d&lt;br /&gt;csi-do-node-sa-token-vz7wk         kubernetes.io/service-account-token   3         62d&lt;br /&gt;default-token-tw59l                kubernetes.io/service-account-token   3         62d&lt;br /&gt;digitalocean                       Opaque                                1         62d&lt;br /&gt;kube-dns-token-7zvjw               kubernetes.io/service-account-token   3         62d&lt;br /&gt;kubernetes-dashboard-certs         Opaque                                0         4m&lt;br /&gt;kubernetes-dashboard-key-holder    Opaque                                2         4m&lt;br /&gt;kubernetes-dashboard-token-fxw8d   kubernetes.io/service-account-token   3         4m&lt;br /&gt;&amp;lt;ServiceAccountName-token-xxxxx&amp;gt;   kubernetes.io/service-account-token   3         17s&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally display your token:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl describe secret &amp;lt;ServiceAccountName-token-xxxxx&amp;gt; -n kube-system&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Name:         &amp;lt;ServiceAccountName-token-xxxxx&amp;gt;&lt;br /&gt;Namespace:    kube-system&lt;br /&gt;Labels:       &amp;lt;none&amp;gt;&lt;br /&gt;Annotations:  kubernetes.io/service-account.name=&amp;lt;ServiceAccountName-token-xxxxx&amp;gt;&lt;br /&gt;              kubernetes.io/service-account.uid=&amp;lt;some-uid&amp;gt;&lt;br /&gt;&lt;br /&gt;Type:  kubernetes.io/service-account-token&lt;br /&gt;&lt;br /&gt;Data&lt;br /&gt;====&lt;br /&gt;ca.crt:     1156 bytes&lt;br /&gt;namespace:  11 bytes&lt;br /&gt;token:      &amp;lt;here goes your token&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When running &lt;code&gt;kubectl proxy&lt;/code&gt; again, now you can enter your token in the login screen here:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/enabling-the-kubernetes-dashboard-for-digitalocean-kubernetes/k8s-dashboard-login.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If everything went well, you should be able to browse the Kubernetes dashboard now:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/enabling-the-kubernetes-dashboard-for-digitalocean-kubernetes/k8s-dashboard.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Push vs. Pull - regaining control over time</title>
    <link href="https://alexanderzeitler.com/articles/push-vs-pull-regaining-control-over-time/"/>
    <updated>2019-01-08T23:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/push-vs-pull-regaining-control-over-time/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Push vs. Pull - regaining control over time&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Make Twitter and Gadgets useful again&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/push-vs-pull-regaining-control-over-time/patryk-gradys-128898-unsplash.jpg&quot; /&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/push-vs-pull-regaining-control-over-time/patryk-gradys-128898-unsplash.jpg&quot; alt=&quot;&quot; /&gt;
Photo by &lt;a href=&quot;https://unsplash.com/photos/4pPzKfd6BEg?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Patryk Grądys&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/search/photos/control?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A few weeks ago I had my tenth Twitter anniversary. But I didn&#39;t feel happy about it - quite the opposite.&lt;/p&gt;
&lt;p&gt;At that time I was oscillating between leaving Twitter completely one day and following even more people because of their great Tweets the other day.&lt;/p&gt;
&lt;p&gt;I started thinking about what I was expecting from Twitter and what was holding me back to get this.
To me, the problem was: many people post several Tweets you&#39;re deeply interested in and you start following them. But then they&#39;re also posting a lot of stuff you&#39;re not interested in.
Some folks argue that you &amp;quot;buy&amp;quot; the whole person and not just the part you&#39;re interested in - but at some amount of people you&#39;re following, this won&#39;t scale.&lt;/p&gt;
&lt;p&gt;So I started an experiment: besides using Lists, I created search definitions using Hashtags and keywords in the &lt;a href=&quot;https://twitter.com/search-advanced?lang=en&quot;&gt;Twitter Advanced Search&lt;/a&gt; and bookmarked these searches. I also muted a lot of keywords I don&#39;t want to read about (this is just distracting noise, let&#39;s be honest).&lt;/p&gt;
&lt;p&gt;After that a - at that this point in time - crazy idea popped up in my mind: What if I would unfollow everybody? Shouldn&#39;t the tweets from the search lead me back to most of the people I followed anyway? Indeed, that&#39;s what happened. But now I also read Tweets from many people I had not seen before on Twitter. It&#39;s sort of that feeling when you find some awesome stuff on GitHub from people you never heard of.&lt;/p&gt;
&lt;p&gt;But I was still unsure.&lt;/p&gt;
&lt;p&gt;In the end I did it, because I read this Tweet by the end of 2018:&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;In 2018, I made a new year&amp;#39;s resolution to try and not follow anyone on Twitter for the year. It was the best thing I&amp;#39;ve done.  Make 2019 your year of 0 following, I promise you will not regret it. &lt;a href=&quot;https://t.co/NTPlQNEbrI&quot;&gt;https://t.co/NTPlQNEbrI&lt;/a&gt;&lt;/p&gt;&amp;mdash; Carmen Hernández Andoh (@carmatrocity) &lt;a href=&quot;https://twitter.com/carmatrocity/status/1073356921674051584?ref_src=twsrc%5Etfw&quot;&gt;December 13, 2018&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;p&gt;No I was in a rush. I also decided to invest the time saved into reading more Blogs again. So I cleaned up my blog roll and added blogs from people I found using the Twitter Search.&lt;/p&gt;
&lt;p&gt;After a while I discovered this pattern:
Find people by Search via Hashtags and keywords, subscribe to their blogs and dive deep into topics again - not just bite size fragments and endless discussions on Twitter.&lt;/p&gt;
&lt;p&gt;Now that I&#39;m deciding what to search for and when, Twitter has become much less time consuming as there&#39;s a plan what to do - and not browsing around just for fun.&lt;/p&gt;
&lt;p&gt;Having identified inefficient Twitter usage as a time hog I was thinking about where this was happening outside Twitter and I started questioning using my iPhone for anything else than taking Phone calls - weird.&lt;/p&gt;
&lt;p&gt;To cut a long story short - this is my iPhone home screen as of today (as you can see it works for more than ten days already):&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/push-vs-pull-regaining-control-over-time/iphone-screen-2019-1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;My second iPhone screen just contains rarely used stuff or Apps I can&#39;t delete anyway.
&lt;img src=&quot;https://alexanderzeitler.com/articles/push-vs-pull-regaining-control-over-time/iphone-screen-2019-2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Besides phone calls, text messages and meeting reminders, there&#39;s nothing installed which causes distraction - not even email. If it is really important, people will call anyway - anything else can wait until I&#39;m using a Notebook which is way more efficient for doing work than a small phone screen.&lt;/p&gt;
&lt;p&gt;And of course, less push, more pull. You&#39;re back in control - not others.&lt;/p&gt;
&lt;p&gt;P.S.: Norbert started unfollowing everyone on Twitter today as well, so read &lt;a href=&quot;https://www.norberteder.com/so-ist-auch-twitter-wieder-nutzbar/&quot;&gt;his post&lt;/a&gt; as well (in german language)&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Refresh ASP.NET Core MVC view without reloading the application</title>
    <link href="https://alexanderzeitler.com/articles/refresh-asp-net-core-website-without-reloading-application/"/>
    <updated>2019-12-11T17:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/refresh-asp-net-core-website-without-reloading-application/</id>
    <content type="html">&lt;p&gt;After doing no ASP.NET (Core) stuff for while, today I created an ASP.NET Core MVC 3.0 application using &lt;code&gt;dotnet new mvc&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I started the application using &lt;code&gt;dotnet watch run&lt;/code&gt; and I was surprised to see the whole application restarting even when I just changed a single character within a MVC View e.g. &lt;code&gt;./Views/Home/Index.cshtml&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The solution was to add this NuGet package to the project:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;Startup.cs&lt;/code&gt; I had to change this line:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;ConfigureServices&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;IServiceCollection services&lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;     services.AddControllersWithViews();&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to this&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;ConfigureServices&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;IServiceCollection services&lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;     services.AddControllersWithViews()&lt;br /&gt;         .AddRazorRuntimeCompilation();&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the application doesn&#39;t have to restart for simple HTML/Razor content changes.&lt;/p&gt;
&lt;p&gt;Happy coding 😉&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Parsing JSON parameters stored in AWS Parameter Store using jq</title>
    <link href="https://alexanderzeitler.com/articles/parsing-json-parameters-stored-in-aws-parameter-store-using-jq/"/>
    <updated>2020-01-07T17:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/parsing-json-parameters-stored-in-aws-parameter-store-using-jq/</id>
    <content type="html">&lt;p&gt;The other day I had the idea to store some JSON as a &lt;a href=&quot;https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html&quot;&gt;AWS Parameter Store&lt;/a&gt; value.&lt;/p&gt;
&lt;p&gt;If you read the parameter using AWS CLI:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;aws ssm get-parameters --name /prod/some-json-param&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;you get something like this:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Parameters&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Name&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;/prod/some-json-param&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Type&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;String&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Value&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;{&#92;n  &#92;&amp;quot;server&#92;&amp;quot;: &#92;&amp;quot;&#92;&amp;quot;,&#92;n  &#92;&amp;quot;token&#92;&amp;quot;: &#92;&amp;quot;&#92;&amp;quot;&#92;n}&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Version&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;LastModifiedDate&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;1578353153.788&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;ARN&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;arn:aws:ssm:eu-central-1:1234567890:parameter/prod/some-json-param&amp;quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;InvalidParameters&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What I wanted to get in a shell script, was the JSON representation of &lt;code&gt;Value&lt;/code&gt; like this:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;server&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;token&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thanks to the popular &lt;a href=&quot;https://stedolan.github.io/jq/&quot;&gt;jq&lt;/a&gt; command, this is quite easy:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;aws ssm get-parameters --name /prod/some-json-param | jq &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;.Parameters | .[] | .Value&amp;#x27;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That way, we get the value of &lt;code&gt;Value&lt;/code&gt; as a string:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;{&#92;n  &#92;&quot;server&#92;&quot;: &#92;&quot;&#92;&quot;,&#92;n  &#92;&quot;token&#92;&quot;: &#92;&quot;&#92;&quot;&#92;n}&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another call to &lt;code&gt;jq&lt;/code&gt; will give us the desired result:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;aws ssm get-parameters --name /prod/some-json-param | jq &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;.Parameters | .[] | .Value&amp;#x27;&lt;/span&gt; | jq &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;.|fromjson&amp;#x27;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;server&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;token&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Happy Coding!&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Hot reload tailwindcss changes with browser-sync</title>
    <link href="https://alexanderzeitler.com/articles/watch-tailwind-changes-update-browser-sync/"/>
    <updated>2020-09-08T21:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/watch-tailwind-changes-update-browser-sync/</id>
    <content type="html">&lt;p&gt;The other day I wanted &lt;code&gt;browser-sync&lt;/code&gt; to include my changes made to &lt;code&gt;tailwind.config.js&lt;/code&gt; or &lt;code&gt;tailwind.css&lt;/code&gt; without manually restarting &lt;code&gt;browser-sync&lt;/code&gt; after my &lt;code&gt;output.css&lt;/code&gt; has been generated.&lt;/p&gt;
&lt;h3&gt;Initial setup&lt;/h3&gt;
&lt;p&gt;My initial setup has been &lt;code&gt;tailwindcss&lt;/code&gt; and &lt;code&gt;browser-sync&lt;/code&gt; installed and configured like this:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;- tailwind.css&lt;br /&gt;- tailwind.config.js&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The contents of the &lt;code&gt;tailwind.css&lt;/code&gt; are based on the default setup:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;@tailwind&lt;/span&gt; base;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;@tailwind&lt;/span&gt; components;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;@tailwind&lt;/span&gt; utilities;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the default &lt;code&gt;tailwind.config.js&lt;/code&gt; file:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;hljs-variable language_&quot;&gt;module&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;exports&lt;/span&gt; = {&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;purge&lt;/span&gt;: [],&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;theme&lt;/span&gt;: {&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;extend&lt;/span&gt;: {},&lt;br /&gt;  },&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;variants&lt;/span&gt;: {},&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;plugins&lt;/span&gt;: [],&lt;br /&gt;};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;sync&lt;/code&gt; script in &lt;code&gt;package.json&lt;/code&gt; did look like this:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;browser-sync start --server --files &#92;&amp;quot;**/*&#92;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Having this setup, changes to files have been monitored by &lt;code&gt;browser-sync&lt;/code&gt; and updated in the browser.&lt;/p&gt;
&lt;p&gt;However, changes made to &lt;code&gt;tailwind.css&lt;/code&gt; or &lt;code&gt;tailwind.config.js&lt;/code&gt; have not been monitored and I had to restart &lt;code&gt;sync&lt;/code&gt; after running &lt;code&gt;npx tailwindcss build tailwind.css -o output.css&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Automate all the things&lt;/h3&gt;
&lt;p&gt;To get rid of this manual step, I installed these &lt;code&gt;devDevependencies&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;yarn add -D nodemon npm-run-all postcss-cli&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, I add the &lt;code&gt;dev&lt;/code&gt;, &lt;code&gt;watch-dev&lt;/code&gt; and &lt;code&gt;watch&lt;/code&gt; scripts, so my &lt;code&gt;scripts&lt;/code&gt; section in &lt;code&gt;package.json&lt;/code&gt; ends up like this:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;scripts&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;sync&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;browser-sync start --server --files &#92;&amp;quot;**/*&#92;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;dev&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;postcss tailwind.css --output output.css&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;watch:dev&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;nodemon -x npm run dev -w tailwind.config.js -w tailwind.css&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;watch&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;run-p watch:dev sync&amp;quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now I can just run &lt;code&gt;yarn watch&lt;/code&gt; and &lt;code&gt;browser-sync&lt;/code&gt; includes the changes made to &lt;code&gt;tailwind.css&lt;/code&gt; and &lt;code&gt;tailwind.config.js&lt;/code&gt; instantly.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;run-p&lt;/code&gt; is the CLI tool installed by &lt;code&gt;npm-run-all&lt;/code&gt; and first starts the &lt;code&gt;watch:dev&lt;/code&gt; followed by the &lt;code&gt;sync&lt;/code&gt;script.
&lt;code&gt;watch-dev&lt;/code&gt; runs &lt;code&gt;nodemon&lt;/code&gt; which watches the changes made to &lt;code&gt;tailwind.config.js&lt;/code&gt; and &lt;code&gt;tailwind.css&lt;/code&gt;. If changes are made, &lt;code&gt;postcss&lt;/code&gt; is run by the &lt;code&gt;dev&lt;/code&gt; script to compile tailwindcss output again.&lt;/p&gt;
&lt;p&gt;To make this work, I&#39;ve added a &lt;code&gt;postcss.config.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;hljs-variable language_&quot;&gt;module&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;exports&lt;/span&gt; = {&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;plugins&lt;/span&gt;: [&lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;tailwindcss&amp;quot;&lt;/span&gt;)(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;tailwind.config.js&amp;quot;&lt;/span&gt;)],&lt;br /&gt;};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This step does the work done by &lt;code&gt;tailwindcss build tailwind.css -o output.css&lt;/code&gt; before.&lt;/p&gt;
&lt;p&gt;That&#39;s it 🤷‍♂️&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Accessing local domains in local dev environment from Android emulator</title>
    <link href="https://alexanderzeitler.com/articles/accessing-local-domains-on-local-dev-environment-from-android-emulator-using-serverless-pihole-in-docker/"/>
    <updated>2020-09-18T21:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/accessing-local-domains-on-local-dev-environment-from-android-emulator-using-serverless-pihole-in-docker/</id>
    <content type="html">&lt;p&gt;Let&#39;s consider you&#39;re using local domains in development and want access them from Android virtual devices using the Android emulator? Here&#39;s how to solve this on macOS using &amp;quot;serverless&amp;quot; pihole.&lt;/p&gt;
&lt;p&gt;Given our local IP is &lt;code&gt;192.168.0.87&lt;/code&gt; and we have defined a domain &lt;code&gt;ui&lt;/code&gt; in &lt;code&gt;/etc/hosts&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;##&lt;br /&gt;# Host Database&lt;br /&gt;#&lt;br /&gt;# localhost is used to configure the loopback interface&lt;br /&gt;# when the system is booting.  Do not change this entry.&lt;br /&gt;##&lt;br /&gt;192.168.0.87    ui&lt;br /&gt;127.0.0.1       localhost&lt;br /&gt;::1             localhost&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you want to access &lt;code&gt;http://ui&lt;/code&gt; from an virtual Android device running in the Android emulator, you get this error in Chrome:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/accessing-local-domains-on-local-dev-environment-from-android-emulator-using-serverless-pihole-in-docker/chrome_ip_not_found.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;To make it work, we need a DNS server which knows how to resolve &lt;code&gt;http://ui&lt;/code&gt; to &lt;code&gt;192.168.0.87&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;An easy way is to run the popular &lt;a href=&quot;https://pi-hole.net/&quot;&gt;pihole&lt;/a&gt; in a Docker container (hence &amp;quot;serverless&amp;quot; in my post title 😉).&lt;/p&gt;
&lt;p&gt;Just grab the sample &lt;code&gt;docker-compose.yml&lt;/code&gt; from the &lt;a href=&quot;https://hub.docker.com/r/pihole/pihole/&quot;&gt;Docker registry&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;version:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;3&amp;quot;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# More info at https://github.com/pi-hole/docker-pi-hole/ and https://docs.pi-hole.net/&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;services:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;pihole:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;container_name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;pihole&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;image:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;pihole/pihole:latest&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;ports:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;53:53/tcp&amp;quot;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;53:53/udp&amp;quot;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;67:67/udp&amp;quot;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;80:80/tcp&amp;quot;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;443:443/tcp&amp;quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;environment:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;TZ:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;America/Chicago&amp;#x27;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-comment&quot;&gt;# WEBPASSWORD: &amp;#x27;set a secure password here or it will be random&amp;#x27;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-comment&quot;&gt;# Volumes store your data between container upgrades&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;volumes:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;./etc-pihole/:/etc/pihole/&amp;#x27;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;./etc-dnsmasq.d/:/etc/dnsmasq.d/&amp;#x27;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-comment&quot;&gt;# Recommended but not required (DHCP needs NET_ADMIN)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-comment&quot;&gt;#   https://github.com/pi-hole/docker-pi-hole#note-on-capabilities&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;cap_add:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;NET_ADMIN&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;restart:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;unless-stopped&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Given &lt;code&gt;http://ui&lt;/code&gt; runs on port &lt;code&gt;80&lt;/code&gt;, we need to change the port mapping for port &lt;code&gt;80&lt;/code&gt; in the &lt;code&gt;docker-compose.yml&lt;/code&gt; to another port, e.g. &lt;code&gt;8080&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-yml&quot;&gt;&lt;code class=&quot;language-yml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;ports:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;53:53/tcp&amp;quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;53:53/udp&amp;quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;67:67/udp&amp;quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;8080:80/tcp&amp;quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;443:443/tcp&amp;quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we run &lt;code&gt;docker-compose up -d&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then we can browse to &lt;code&gt;http://localhost:8080/admin&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The Browser should show something like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/accessing-local-domains-on-local-dev-environment-from-android-emulator-using-serverless-pihole-in-docker/pihole_anon.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After this, we can click &amp;quot;Login&amp;quot; from the sidebar and login using the password (how to obtain the password is described on the Docker registry page linked above).&lt;/p&gt;
&lt;p&gt;Next we can select &amp;quot;Local DNS records&amp;quot; from the sidebar:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/accessing-local-domains-on-local-dev-environment-from-android-emulator-using-serverless-pihole-in-docker/pihole_menu.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Then we add a new DNS entry for &lt;code&gt;ui&lt;/code&gt; with IP address &lt;code&gt;192.168.0.87&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/accessing-local-domains-on-local-dev-environment-from-android-emulator-using-serverless-pihole-in-docker/pihole_add_dns_entry.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now it&#39;s time to start our emulator again, this time from the command line, as this is the easiest way to pass the DNS server setting:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;cd&lt;/span&gt; ~/Library/Android/sdk/emulator&lt;br /&gt;emulator -list-avds&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your output will look like this:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Pixel_XL_API_29&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can start the emulator again and pass the pihole DNS server running on &lt;code&gt;192.168.0.87&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;./emulator -avd Pixel_XL_API_29 -dns-server 192.168.0.87&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pointing Chrome again to &lt;code&gt;http://ui&lt;/code&gt; inside the virtual device should result in something like this now:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/accessing-local-domains-on-local-dev-environment-from-android-emulator-using-serverless-pihole-in-docker/chrome_hello_world.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;To me, local DNS has never been that easy. How about you?&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Minimal IAM permissions for AWS CDK deployments</title>
    <link href="https://alexanderzeitler.com/articles/minimal-iam-permission-for-aws-cdk-deployment/"/>
    <updated>2020-10-01T02:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/minimal-iam-permission-for-aws-cdk-deployment/</id>
    <content type="html">&lt;p&gt;AWS CDK is leveraging AWS CloudFormation to deploy Stacks in AWS.&lt;/p&gt;
&lt;p&gt;In addition, AWS CDK may require some data which is being stored in a S3 Bucket named &lt;code&gt;cdktoolkit-stagingbucket-*&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is the IAM policy IAM assigning to a AWS IAM group which should be able to deploy resources via AWS CDK. Of course, depending on the resources you want to deploy, you need further IAM permissions.&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Version&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;2012-10-17&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Statement&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Action&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;cloudformation:*&amp;quot;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Resource&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;*&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Effect&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Allow&amp;quot;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Action&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;s3:*&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Resource&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;arn:aws:s3:::cdktoolkit-stagingbucket-*&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Effect&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Allow&amp;quot;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The policy gives full access to CloudFormation and all S3 Buckets named &lt;code&gt;cdktoolkit-stagingbucket-*&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Another option is to additionally grant full access for all resources and their actions if the action has been triggered by CloudFormation (or CDK):&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Version&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;2012-10-17&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Statement&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Action&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;cloudformation:*&amp;quot;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Resource&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;*&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Effect&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Allow&amp;quot;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Condition&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;ForAnyValue:StringEquals&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;aws:CalledVia&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;                        &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;cloudformation.amazonaws.com&amp;quot;&lt;/span&gt;&lt;br /&gt;                    &lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Action&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;*&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Resource&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;*&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Effect&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Allow&amp;quot;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Action&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;s3:*&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Resource&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;arn:aws:s3:::cdktoolkit-stagingbucket-*&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Effect&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Allow&amp;quot;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Handle with care...&lt;/p&gt;
&lt;h3&gt;Update for CDK version 2&lt;/h3&gt;
&lt;p&gt;Another permission is required for CDK 2:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Version&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;2012-10-17&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Statement&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Effect&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Allow&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Action&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;sts:AssumeRole&amp;quot;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;Resource&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;arn:aws:iam::*:role/cdk-*&amp;quot;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thanks to &lt;a href=&quot;https://linkedin.com/in/jamescrowley&quot;&gt;James Crowley&lt;/a&gt; for pointing this out.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Creating an AWS Cognito user pool with OAuth flows using AWS CDK</title>
    <link href="https://alexanderzeitler.com/articles/create-aws-cognito-userpool-with-oauth-flows-using-cdk/"/>
    <updated>2020-10-05T01:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/create-aws-cognito-userpool-with-oauth-flows-using-cdk/</id>
    <content type="html">&lt;p&gt;I tried to setup an &lt;a href=&quot;https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools.html&quot;&gt;AWS Cognito user pool&lt;/a&gt; supporting &lt;a href=&quot;https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-app-idp-settings.html&quot;&gt;OAuth 2.0 client credential flow&lt;/a&gt; using &lt;a href=&quot;https://aws.amazon.com/cdk/&quot;&gt;AWS CDK&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As of version 1.66.0. CDK allows you to create a Cognito User Pool very straight forward:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;mkdir&lt;/span&gt; idp-stack &amp;amp;&amp;amp; &lt;span class=&quot;hljs-built_in&quot;&gt;cd&lt;/span&gt; idp-stack&lt;br /&gt;cdk init idp-stack --language typescript&lt;br /&gt;npm install @aws-cdk/aws-cognito&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; { &lt;span class=&quot;hljs-title class_&quot;&gt;OAuthScope&lt;/span&gt;, &lt;span class=&quot;hljs-title class_&quot;&gt;UserPool&lt;/span&gt; } &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;@aws-cdk/aws-cognito&amp;quot;&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; pool = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;UserPool&lt;/span&gt;(&lt;span class=&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;dev-userpool&amp;quot;&lt;/span&gt;, {&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;userPoolName&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;dev-userpool&amp;quot;&lt;/span&gt;,&lt;br /&gt;});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next you would assume, you can add a client with client credential flow enabled (as explained in the links above). So here it is:&lt;/p&gt;
&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;pool.&lt;span class=&quot;hljs-title function_&quot;&gt;addClient&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;console-client&amp;quot;&lt;/span&gt;, {&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;generateSecret&lt;/span&gt;: &lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;oAuth&lt;/span&gt;: {&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;flows&lt;/span&gt;: {&lt;br /&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;clientCredentials&lt;/span&gt;: &lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;,&lt;br /&gt;    },&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;scopes&lt;/span&gt;: [&lt;span class=&quot;hljs-title class_&quot;&gt;OAuthScope&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;custom&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;https://resource-server//get-todos&amp;quot;&lt;/span&gt;)],&lt;br /&gt;  },&lt;br /&gt;});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In order to be able to add a custom scope like &lt;code&gt;https://resource-server//get-todos&lt;/code&gt;, first you need to create a resource server. But it is not connected to the user pool in terms of a function to call on the pool instance (which makes sense if you think about it for a while).&lt;/p&gt;
&lt;p&gt;So here we go:&lt;/p&gt;
&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;CfnUserPoolResourceServer&lt;/span&gt;(&lt;span class=&quot;hljs-variable language_&quot;&gt;this&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;dev-userpool-resource-server&amp;quot;&lt;/span&gt;, {&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;identifier&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;https://resource-server/&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;dev-userpool-resource-server&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;userPoolId&lt;/span&gt;: pool.&lt;span class=&quot;hljs-property&quot;&gt;userPoolId&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;scopes&lt;/span&gt;: [&lt;br /&gt;    {&lt;br /&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;scopeDescription&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Get todo items&amp;quot;&lt;/span&gt;,&lt;br /&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;scopeName&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;get-todos&amp;quot;&lt;/span&gt;,&lt;br /&gt;    },&lt;br /&gt;  ],&lt;br /&gt;});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can use the &lt;code&gt;get-todos&lt;/code&gt; scope in our client (take care of the correct convention to specify the scope here):&lt;/p&gt;
&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;pool.&lt;span class=&quot;hljs-title function_&quot;&gt;addClient&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;console-client&amp;quot;&lt;/span&gt;, {&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;generateSecret&lt;/span&gt;: &lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;oAuth&lt;/span&gt;: {&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;flows&lt;/span&gt;: {&lt;br /&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;clientCredentials&lt;/span&gt;: &lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;,&lt;br /&gt;    },&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;scopes&lt;/span&gt;: [&lt;span class=&quot;hljs-title class_&quot;&gt;OAuthScope&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;custom&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;https://resource-server//get-todos&amp;quot;&lt;/span&gt;)],&lt;br /&gt;  },&lt;br /&gt;});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make sure to take care of the convention for scopes: &lt;code&gt;&amp;lt;resourceserver-identifier&amp;gt;//&amp;lt;scope-name&amp;gt;&lt;/code&gt; (notice the double slash).&lt;/p&gt;
&lt;p&gt;Additionally we&#39;ll specify a domain for our user pool:&lt;/p&gt;
&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;pool.&lt;span class=&quot;hljs-title function_&quot;&gt;addDomain&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;CognitoDomain&amp;quot;&lt;/span&gt;, {&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;cognitoDomain&lt;/span&gt;: {&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;domainPrefix&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;dev-userpool&amp;quot;&lt;/span&gt;,&lt;br /&gt;  },&lt;br /&gt;});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lets try &lt;code&gt;cdk deploy&lt;/code&gt; and everything should be fine:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;npm run build &amp;amp;&amp;amp; npm run cdk deploy&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the result - and we&#39;re done ✅&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;br /&gt;The stack IdpStack already includes a CDKMetadata resource&lt;br /&gt;IdpStack: deploying...&lt;br /&gt;IdpStack: creating CloudFormation changeset...&lt;br /&gt;[██████████████████████████████████████████████████████████] (6/6)&lt;br /&gt;&lt;br /&gt; ✅  IdpStack&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The user pool:&lt;br /&gt;
&lt;img src=&quot;https://alexanderzeitler.com/articles/create-aws-cognito-userpool-with-oauth-flows-using-cdk/user-pool-overview.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The client:
&lt;img src=&quot;https://alexanderzeitler.com/articles/create-aws-cognito-userpool-with-oauth-flows-using-cdk/user-pool-client.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The resource server:
&lt;img src=&quot;https://alexanderzeitler.com/articles/create-aws-cognito-userpool-with-oauth-flows-using-cdk/user-pool-resource-server.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The full code example can be found &lt;a href=&quot;https://github.com/AlexZeitler/cdk-cognito-oauth-flow-example&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Refactoring-safe nested validation with express-validator and ts-simple-nameof</title>
    <link href="https://alexanderzeitler.com/articles/type-safe-nested-validation-with-express-validator-and-ts-simple-nameof/"/>
    <updated>2021-08-10T01:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/type-safe-nested-validation-with-express-validator-and-ts-simple-nameof/</id>
    <content type="html">&lt;p&gt;&lt;a href=&quot;https://www.npmjs.com/package/express-validator&quot;&gt;&lt;code&gt;express-validator&lt;/code&gt;&lt;/a&gt; is a powerful validation and sanitation library.&lt;/p&gt;
&lt;p&gt;However, it relies on string property names for the validation to work.&lt;/p&gt;
&lt;p&gt;This is not type and refactoring safe, hence error prone.&lt;/p&gt;
&lt;p&gt;Let&#39;s see if we can fix this...&lt;/p&gt;
&lt;p&gt;First, a litte example of how &lt;code&gt;express-validator&lt;/code&gt; works (from the official example):&lt;/p&gt;
&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; { body, validationResult } &lt;span class=&quot;hljs-keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;express-validator&amp;quot;&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; express = &lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;express&amp;quot;&lt;/span&gt;);&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; app = &lt;span class=&quot;hljs-title function_&quot;&gt;express&lt;/span&gt;();&lt;br /&gt;&lt;br /&gt;app.&lt;span class=&quot;hljs-title function_&quot;&gt;use&lt;/span&gt;(express.&lt;span class=&quot;hljs-title function_&quot;&gt;json&lt;/span&gt;());&lt;br /&gt;app.&lt;span class=&quot;hljs-title function_&quot;&gt;post&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;/user&amp;quot;&lt;/span&gt;, &lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;req, res&lt;/span&gt;) =&amp;gt;&lt;/span&gt; {&lt;br /&gt;  &lt;span class=&quot;hljs-title class_&quot;&gt;User&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;create&lt;/span&gt;({&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;username&lt;/span&gt;: req.&lt;span class=&quot;hljs-property&quot;&gt;body&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;username&lt;/span&gt;,&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;password&lt;/span&gt;: req.&lt;span class=&quot;hljs-property&quot;&gt;body&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;password&lt;/span&gt;,&lt;br /&gt;  }).&lt;span class=&quot;hljs-title function_&quot;&gt;then&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;user&lt;/span&gt;) =&amp;gt;&lt;/span&gt; res.&lt;span class=&quot;hljs-title function_&quot;&gt;json&lt;/span&gt;(user));&lt;br /&gt;});&lt;br /&gt;&lt;br /&gt;app.&lt;span class=&quot;hljs-title function_&quot;&gt;post&lt;/span&gt;(&lt;br /&gt;  &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;/user&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-comment&quot;&gt;// username must be an email&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-title function_&quot;&gt;body&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;username&amp;quot;&lt;/span&gt;).&lt;span class=&quot;hljs-title function_&quot;&gt;isEmail&lt;/span&gt;(),&lt;br /&gt;  &lt;span class=&quot;hljs-comment&quot;&gt;// password must be at least 5 chars long&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-title function_&quot;&gt;body&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;password&amp;quot;&lt;/span&gt;).&lt;span class=&quot;hljs-title function_&quot;&gt;isLength&lt;/span&gt;({ &lt;span class=&quot;hljs-attr&quot;&gt;min&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;5&lt;/span&gt; }),&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;req, res&lt;/span&gt;) =&amp;gt;&lt;/span&gt; {&lt;br /&gt;    &lt;span class=&quot;hljs-comment&quot;&gt;// Finds the validation errors in this request and wraps them in an object with handy functions&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;const&lt;/span&gt; errors = &lt;span class=&quot;hljs-title function_&quot;&gt;validationResult&lt;/span&gt;(req);&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (!errors.&lt;span class=&quot;hljs-title function_&quot;&gt;isEmpty&lt;/span&gt;()) {&lt;br /&gt;      &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; res.&lt;span class=&quot;hljs-title function_&quot;&gt;status&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;400&lt;/span&gt;).&lt;span class=&quot;hljs-title function_&quot;&gt;json&lt;/span&gt;({ &lt;span class=&quot;hljs-attr&quot;&gt;errors&lt;/span&gt;: errors.&lt;span class=&quot;hljs-title function_&quot;&gt;array&lt;/span&gt;() });&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-title class_&quot;&gt;User&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;create&lt;/span&gt;({&lt;br /&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;username&lt;/span&gt;: req.&lt;span class=&quot;hljs-property&quot;&gt;body&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;username&lt;/span&gt;,&lt;br /&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;password&lt;/span&gt;: req.&lt;span class=&quot;hljs-property&quot;&gt;body&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;password&lt;/span&gt;,&lt;br /&gt;    }).&lt;span class=&quot;hljs-title function_&quot;&gt;then&lt;/span&gt;(&lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;user&lt;/span&gt;) =&amp;gt;&lt;/span&gt; res.&lt;span class=&quot;hljs-title function_&quot;&gt;json&lt;/span&gt;(user));&lt;br /&gt;  }&lt;br /&gt;);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, &lt;code&gt;body(&#39;username&#39;).isEmail()&lt;/code&gt; is using the &lt;code&gt;&#39;username&#39;&lt;/code&gt; string to define a validation rule.&lt;/p&gt;
&lt;p&gt;This will become an issue if the &lt;code&gt;username&lt;/code&gt; property will be changed - if you don&#39;t have tests, your validation will be broken now.&lt;/p&gt;
&lt;p&gt;But of course, there&#39;s a package for that: &lt;a href=&quot;https://www.npmjs.com/package/ts-simple-nameof&quot;&gt;&lt;code&gt;ts-simple-nameof&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Parses a class name or a dot-separated property name from a lambda expression and provides some level of type safety using type parameters.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What does this mean exactly?&lt;/p&gt;
&lt;p&gt;Here&#39;s a simple example:&lt;/p&gt;
&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Comment&lt;/span&gt; = {&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;user&lt;/span&gt;: {&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;posts&lt;/span&gt;: &lt;span class=&quot;hljs-title class_&quot;&gt;Post&lt;/span&gt;[];&lt;br /&gt;  };&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// Returns &amp;quot;posts&amp;quot;.&lt;/span&gt;&lt;br /&gt;nameof&amp;lt;&lt;span class=&quot;hljs-title class_&quot;&gt;Comment&lt;/span&gt;&amp;gt;(&lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;c&lt;/span&gt;) =&amp;gt;&lt;/span&gt; c.&lt;span class=&quot;hljs-property&quot;&gt;user&lt;/span&gt;); &lt;span class=&quot;hljs-comment&quot;&gt;// returns &amp;quot;user&amp;quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-comment&quot;&gt;// Returns &amp;quot;user.posts&amp;quot;.&lt;/span&gt;&lt;br /&gt;nameof&amp;lt;&lt;span class=&quot;hljs-title class_&quot;&gt;Comment&lt;/span&gt;&amp;gt;(&lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;c&lt;/span&gt;) =&amp;gt;&lt;/span&gt; c.&lt;span class=&quot;hljs-property&quot;&gt;user&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;posts&lt;/span&gt;);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, &lt;code&gt;ts-simple-nameof&lt;/code&gt; returns the name of a property of a TypeScript &lt;code&gt;type&lt;/code&gt;, &lt;code&gt;class&lt;/code&gt; or &lt;code&gt;interface&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Having &lt;code&gt;ts-simple-nameof&lt;/code&gt; at hand, we now can solve our &lt;code&gt;express-validator&lt;/code&gt; refactoring issue like this:&lt;/p&gt;
&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;MyCommand&lt;/span&gt; = {&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;some&lt;/span&gt;: {&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;nested&lt;/span&gt;: {&lt;br /&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;property&lt;/span&gt;: &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt;;&lt;br /&gt;    };&lt;br /&gt;  };&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-title function_&quot;&gt;body&lt;/span&gt;(nameof&amp;lt;&lt;span class=&quot;hljs-title class_&quot;&gt;MyCommand&lt;/span&gt;&amp;gt;(&lt;span class=&quot;hljs-function&quot;&gt;(&lt;span class=&quot;hljs-params&quot;&gt;c&lt;/span&gt;) =&amp;gt;&lt;/span&gt; c.&lt;span class=&quot;hljs-property&quot;&gt;some&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;nested&lt;/span&gt;.&lt;span class=&quot;hljs-property&quot;&gt;property&lt;/span&gt;)).&lt;span class=&quot;hljs-title function_&quot;&gt;exists&lt;/span&gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will ensure, that a arbitrerily deeply nested property can be passed to the &lt;code&gt;body&lt;/code&gt; validation function in a refactoring safe way - neat!&lt;/p&gt;
&lt;p&gt;A sample repo can be found &lt;a href=&quot;https://github.com/AlexZeitler/spike-express-validatorjs-nested-nameof&quot;&gt;here&lt;/a&gt;. It also shows how you can test &lt;code&gt;express-validator&lt;/code&gt; validations.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Writing is thinking - and more</title>
    <link href="https://alexanderzeitler.com/articles/writing-is-thinking-and-more/"/>
    <updated>2022-05-30T02:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/writing-is-thinking-and-more/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Writing is thinking - and more&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Writing is thinking - and more&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/writing-is-thinking-and-more/yannick-pulver-hopX_jpVtRM-unsplash.jpg&quot; /&gt;
&lt;p&gt;&lt;small&gt;&lt;small&gt;Photo by &lt;a href=&quot;https://unsplash.com/@yanu?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Yannick Pulver&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/writing?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/small&gt;&lt;/small&gt;
&lt;img src=&quot;https://alexanderzeitler.com/articles/writing-is-thinking-and-more/yannick-pulver-hopX_jpVtRM-unsplash.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Recently I came across this tweet:&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Writing is not a result of thinking. &lt;br /&gt;&lt;br /&gt;Writing is thinking.&lt;/p&gt;&amp;mdash; Tiago Forte (@fortelabs) &lt;a href=&quot;https://twitter.com/fortelabs/status/1530901044200448000?ref_src=twsrc%5Etfw&quot;&gt;May 29, 2022&lt;/a&gt;&lt;/blockquote&gt; &lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;p&gt;This resonates very much with my experience and I want to share it here.&lt;/p&gt;
&lt;p&gt;This weekend I did nothing but read and write, with writing predominating.
I wrote concepts, ideas and strategies for &lt;a href=&quot;https://twitter.com/klientoapp&quot;&gt;@klientoapp&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Writing, in my opinion, is even more important when you are a founder on your own. It gives you the opportunity to talk to yourself and reflect on thoughts without having to keep them in your head. This relieves the brain and reduces the cognitive load and avoids multitasking with thoughts.&lt;/p&gt;
&lt;p&gt;This, in turn, creates capacity for more thoughts and gets you into flow faster because you can immediately &amp;quot;dump&amp;quot; the brain again with each new thought since you already have all the tools at your disposal (pen and paper or a note-taking app).&lt;/p&gt;
&lt;p&gt;When working and developing alone, it&#39;s easy to fall into the trap of immediately starting to write code. This leads neither to structured code nor to well thought-out features. You quickly produce a ball of mud and have no clear structure. Indicators are unclear commit messages, no releasable artifacts, and so on.&lt;/p&gt;
&lt;p&gt;You work away and never come to a defined end, because without a concept you often overlook and forget important things.&lt;/p&gt;
&lt;p&gt;Writing also helps with prioritization. If you write down the features, you know on the one hand the scope and on the other hand you recognize whether this is a USP, commodity or nice to have. You can prioritize accordingly.&lt;/p&gt;
&lt;p&gt;When you write, thoughts arise that are not only related to the features. You think more strategically, e.g. also in the direction of investors, hiring etc.&lt;/p&gt;
&lt;p&gt;Writing is also helpful when you take a break from a task, e.g. writing code, without losing the context, because you can always pick up the task again when reading what you have written.&lt;/p&gt;
&lt;p&gt;I&#39;ve also gotten into the habit of creating a &amp;quot;TIL&amp;quot; document for each day. This is where notes go in about things I&#39;ve learned. This can be personal, about the domain or code related on a project. If there are learnings that I can use in other contexts, I store them outside the project and only reference them in the TIL document.&lt;/p&gt;
&lt;p&gt;As you may have noticed, I&#39;m &lt;a href=&quot;https://www.zotero.org/&quot;&gt;Zotero&lt;/a&gt; and &lt;a href=&quot;https://www.zettlr.com/&quot;&gt;Zettlr&lt;/a&gt; for this.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;I am also in the process of using the Zettelkasten method.&lt;br /&gt;I use &lt;a href=&quot;https://twitter.com/zettlr?ref_src=twsrc%5Etfw&quot;&gt;@zettlr&lt;/a&gt; as it also provides a graphic of the &amp;quot;Zettel&amp;quot; (notes).&lt;br /&gt;The recommended book is a great read on this topic. &lt;a href=&quot;https://t.co/g2wd24x4JT&quot;&gt;https://t.co/g2wd24x4JT&lt;/a&gt;&lt;/p&gt;&amp;mdash; Alexander Zeitler (@lxztlr) &lt;a href=&quot;https://twitter.com/lxztlr/status/1524282453661212672?ref_src=twsrc%5Etfw&quot;&gt;May 11, 2022&lt;/a&gt;&lt;/blockquote&gt; &lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;p&gt;In Zotero I put everything I want to reference, while in Zettlr I write down information in my own words to understand the thought behind it later. I don&#39;t make copies of external information in Zettlr - such things are stored in Zotero.&lt;/p&gt;
&lt;p&gt;All notes and highlights I make in books and on my kindle I also put in Zotero. That way I avoid having to deal with different media and formats in the long run. Notes and highlights I made in a book or kindle I can delete afterwards, since everything is in Zotero. This also means I&#39;m not dependent on a platform like kindle and when I search I only have to search in one place: for things I&#39;ve read, listened to or watched it&#39;s Zotero, for my own thoughts it&#39;s Zettlr. So again, I avoid unnecessary strain on the brain.&lt;/p&gt;
&lt;p&gt;Another habit it got into is summarizing inquiries from prospects in my own words after the initial conversation (this can be quite detailed). At the end, I have a list of questions that I can discuss with the prospect and that they can distribute at their company as well. If I receive feedback on the questions, I expand my document. The questions remain, the answers are then also with the questions and so I can reference questions and answers again at the appropriate place. Thus, I have a living documentation of the inquiry process. Of course, this can also be maintained after I got the job...&lt;/p&gt;
&lt;p&gt;How do you organize your thoughts and learnings?&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Deconstructing a C# record with properties</title>
    <link href="https://alexanderzeitler.com/articles/deconstructing-a-csharp-record-with-properties/"/>
    <updated>2022-06-06T22:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/deconstructing-a-csharp-record-with-properties/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Deconstructing a C# record with properties&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Deconstructing a C# record with properties&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/deconstructing-a-csharp-record-with-properties/florian-klauer--K6JMRMj4x4-unsplash.jpg&quot; /&gt;
&lt;p&gt;&lt;small&gt;&lt;small&gt;Photo by &lt;a href=&quot;https://unsplash.com/@florianklauer?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Florian Klauer&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/deconstruct?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/small&gt;
&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/deconstructing-a-csharp-record-with-properties/florian-klauer--K6JMRMj4x4-unsplash.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Today I tried to deconstruct a C# record with properties and I failed.&lt;/p&gt;
&lt;p&gt;First, how do you deconstruct a record with positional parameters?&lt;/p&gt;
&lt;p&gt;It&#39;s as easy like this:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;record&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Person&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; FirstName,&lt;br /&gt;  &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; LastName&lt;br /&gt;&lt;/span&gt;)&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; janeDoe = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; Person(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Jane&amp;quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Doe&amp;quot;&lt;/span&gt;);&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; (lastName, firstName) = janeDoe;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&#39;s it.&lt;/p&gt;
&lt;p&gt;My naïve approach (the record is simplyfied for brevity) to deconstruct a record with properties was like this:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;record&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Person&lt;/span&gt;()&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; Firstname { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;init&lt;/span&gt;; }&lt;br /&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; Lastname { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;init&lt;/span&gt;; }&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, I created an instance:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; janeDoe = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; Person() { Firstname = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Jane&amp;quot;&lt;/span&gt;, Lastname = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Doe&amp;quot;&lt;/span&gt; };&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now I tried to deconstruct it like I would do with the first sample:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; (lastName, firstName) = janeDoe;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And I got this compiler error:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ No &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;Deconstruct&amp;#x27;&lt;/span&gt; method with 2 out parameters found &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;Person&amp;#x27;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After reading this error message several times, I got it: the compiler is expecting a method name &lt;code&gt;Deconstruct&lt;/code&gt; like this on my &lt;code&gt;Person&lt;/code&gt; record type:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;record&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Person&lt;/span&gt;()&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; Firstname { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;init&lt;/span&gt;; }&lt;br /&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; Lastname { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;init&lt;/span&gt;; }&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Deconstruct&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;out&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; firstName,&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;out&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; lastName&lt;br /&gt;  &lt;/span&gt;)&lt;/span&gt; =&amp;gt; (firstName, lastName) = (Firstname, Lastname);&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Deconstruction now works as expected:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; (lastName, firstName) = janeDoe;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The reason for this issue is quite simple: The C# compiler auto-generates the &lt;code&gt;Deconstruct&lt;/code&gt; method when you have a record type with positional parameters but not for record types with properties.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Resizung a Linux (Ubuntu/Xubuntu/Kubuntu) application window exactly</title>
    <link href="https://alexanderzeitler.com/articles/resizing-a-linux-ubuntu-application-window-exactly/"/>
    <updated>2022-06-20T14:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/resizing-a-linux-ubuntu-application-window-exactly/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Resizung a Linux (Ubuntu/Xubuntu/Kubuntu) application window exactly&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Resizung a Linux (Ubuntu/Xubuntu/Kubuntu) application window exactly&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/resizing-a-linux-ubuntu-application-window-exactly/adeolu-eletu-ohh8ROaQSJg-unsplash.jpg&quot; /&gt;
&lt;p&gt;&lt;small&gt;&lt;small&gt;Photo by &lt;a href=&quot;https://unsplash.com/@adeolueletu?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Adeolu Eletu&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/window?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/small&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/resizing-a-linux-ubuntu-application-window-exactly/adeolu-eletu-ohh8ROaQSJg-unsplash.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Sometimes you need a screenshot of an application window with specific dimensions.&lt;/p&gt;
&lt;p&gt;On Linux, you can use the &lt;code&gt;xdotool&lt;/code&gt; commandline tool, which is a fake keyboard/mouse input and window management tool for x11.&lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;xdotool&lt;/code&gt;, resizing a Firefox window to 1024 x 768 pixels is as easy as this command:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;xdotool search &amp;quot;Mozilla Firefox&amp;quot; windowsize 1024 768&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, knowing the right window name might fail you, there&#39;s a even better solution:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;xdotool selectwindow windowsize 1024 768&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can just run the command, click a window - and 💥 - the selected window is resized.&lt;/p&gt;
&lt;p&gt;⚠️ &lt;a href=&quot;https://github.com/jordansissel/xdotool#xdotool---x11-automation-tool&quot;&gt;Note&lt;/a&gt;: If you are using Wayland, please be aware this software will not work correctly. ⚠️&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Dynamic subdomain per tenant using nginx and Docker</title>
    <link href="https://alexanderzeitler.com/articles/dynamic-subdomains-per-tenant-using-nginx-and-docker/"/>
    <updated>2023-01-11T08:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/dynamic-subdomains-per-tenant-using-nginx-and-docker/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Dynamic subdomain per tenant using nginx and Docker&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Dynamic subdomain per tenant using nginx and Docker&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/dynamic-subdomains-per-tenant-using-nginx-and-docker/jose-martin-ramirez-carrasco-yhNVwsKTSaI-unsplash.jpg&quot; /&gt;
&lt;p&gt;&lt;small&gt;&lt;small&gt;Photo by &lt;a href=&quot;https://unsplash.com/@martinirc?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;José Martín Ramírez Carrasco&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/yhNVwsKTSaI?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/small&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/dynamic-subdomains-per-tenant-using-nginx-and-docker/jose-martin-ramirez-carrasco-yhNVwsKTSaI-unsplash.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Even if you don&#39;t use B2B SaaS, you might have seen urls like &lt;a href=&quot;https://theleaddeveloper.slack.com/&quot;&gt;https://theleaddeveloper.slack.com&lt;/a&gt;, where you can have your own subdomain on slack.com for your Slack channel.&lt;/p&gt;
&lt;p&gt;While playing around with some nginx settings, I noticed that this could actually be used for dynamic subdomains per tenant.&lt;/p&gt;
&lt;p&gt;Long story short, here&#39;s the solution (which can certainly be optimized):&lt;/p&gt;
&lt;pre class=&quot;language-nginx&quot;&gt;&lt;code class=&quot;language-nginx&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# sample shows only relevant part of the config, the rest is omitted for the sake of brevity&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-section&quot;&gt;server&lt;/span&gt; {&lt;br /&gt;    &lt;span class=&quot;hljs-attribute&quot;&gt;listen&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;443&lt;/span&gt; ssl;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attribute&quot;&gt;server_name&lt;/span&gt; &lt;span class=&quot;hljs-regexp&quot;&gt;*.tempuri.org&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;     &lt;span class=&quot;hljs-section&quot;&gt;location&lt;/span&gt; / { &lt;br /&gt;        &lt;span class=&quot;hljs-attribute&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;hljs-variable&quot;&gt;$tenant&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&amp;quot;&lt;/span&gt;; &lt;br /&gt;  &lt;br /&gt;        &lt;span class=&quot;hljs-attribute&quot;&gt;if&lt;/span&gt; (&lt;span class=&quot;hljs-variable&quot;&gt;$host&lt;/span&gt; &lt;span class=&quot;hljs-regexp&quot;&gt;~* &amp;quot;^(.+)&#92;.tempuri.org$&amp;quot;)&lt;/span&gt; {&lt;br /&gt;          &lt;span class=&quot;hljs-attribute&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;hljs-variable&quot;&gt;$tenant&lt;/span&gt; &lt;span class=&quot;hljs-variable&quot;&gt;$1&lt;/span&gt;; &lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-attribute&quot;&gt;resolver&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;127.0.0.11&lt;/span&gt;;&lt;br /&gt;        &lt;span class=&quot;hljs-attribute&quot;&gt;proxy_pass&lt;/span&gt; http://app/tenant?slug=&lt;span class=&quot;hljs-variable&quot;&gt;$tenant&lt;/span&gt;;&lt;br /&gt;      }   &lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So what&#39;s going on there?&lt;/p&gt;
&lt;p&gt;Given our domain is &lt;code&gt;tempuri.org&lt;/code&gt;, we want tenants to be available via &lt;code&gt;tenantname.tempuri.org&lt;/code&gt;, hence our nginx server is listening on &lt;code&gt;*.tempuri.org&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next we&#39;re using a little regular expression to pick up the name of the subdomain (which equals our tenant name/slug) the user entered.&lt;/p&gt;
&lt;p&gt;After that we set the DNS resolver to &lt;code&gt;127.0.0.11&lt;/code&gt; (yes: eleven) which is the internal Docker DNS resolver. That&#39;s required to make sure the host name &lt;code&gt;app&lt;/code&gt; used for the &lt;code&gt;proxy_pass&lt;/code&gt; directive can be resolved. &lt;code&gt;app&lt;/code&gt; is the name of the container hosting the actual application which e.g. shows a branded login screen or third party identity providers per tenant.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Serializing .NET objects for use with Alpine.js x-data attribute</title>
    <link href="https://alexanderzeitler.com/articles/serializing-dotnet-csharp-object-for-use-with-alpine-x-data-attribute/"/>
    <updated>2023-01-15T20:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/serializing-dotnet-csharp-object-for-use-with-alpine-x-data-attribute/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Serializing .NET objects for use with Alpine.js x-data attribute&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Serializing .NET objects for use with Alpine.js x-data attribute&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/serializing-dotnet-csharp-object-for-use-with-alpine-x-data-attribute/clint-patterson-CgIFBwOkApI-unsplash.jpg&quot; /&gt;
&lt;meta property=&quot;og:title&quot; content=&quot;Serializing .NET objects for use with Alpine.js x-data attribute&quot; /&gt;
&lt;meta property=&quot;og:url&quot; content=&quot;https://alexanderzeitler.com/articles/serializing-dotnet-csharp-object-for-use-with-alpine-x-data-attribute/&quot; /&gt;
&lt;meta property=&quot;og:image&quot; content=&quot;https://alexanderzeitler.com/articles/serializing-dotnet-csharp-object-for-use-with-alpine-x-data-attribute/clint-patterson-CgIFBwOkApI-unsplash.jpg&quot; /&gt;
&lt;p&gt;&lt;small&gt;&lt;small&gt;Photo by &lt;a href=&quot;https://unsplash.com/ja/@cbpsc1?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Clint Patterson&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/CgIFBwOkApI?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/small&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/serializing-dotnet-csharp-object-for-use-with-alpine-x-data-attribute/clint-patterson-CgIFBwOkApI-unsplash.jpg&quot; alt=&quot;Serialization in progress...&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Alpine.js allows you set javascript objects in the scope of a HTML tag using the &lt;code&gt;x-data&lt;/code&gt; attribute:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;x-data&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;{ open: false }&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;button&lt;/span&gt; @&lt;span class=&quot;hljs-attr&quot;&gt;click&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;open = ! open&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;Toggle Content&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;button&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;     &lt;br /&gt;    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;x-show&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;open&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;        Content...&lt;br /&gt;    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The data is a plain javascript object whose properties are available to all child elements of the outer &lt;code&gt;div&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Clicking the button will show/hide the content of the inner &lt;code&gt;div&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In a typical SPA like setup you might use &lt;code&gt;fetch&lt;/code&gt; together with Alpine&#39;s &lt;code&gt;json&lt;/code&gt; function to include server side data:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;x-data&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;{ posts: [] }&amp;quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;x-init&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;posts = await (await fetch(&amp;#x27;/posts&amp;#x27;)).json()&amp;quot;&lt;/span&gt;&lt;br /&gt;&amp;gt;&lt;/span&gt;...&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But what to do if you want to render the Razor view and let it include the server side object directly into Alpine&#39;s &lt;code&gt;x-data&lt;/code&gt; attribute? The closest you can get using out of the box serialization is this:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;x-data&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;@Html.Raw(Json.Serialize(new { Open = false}))&amp;#x27;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will render the expected output, and you can toggle the content using the button.&lt;/p&gt;
&lt;p&gt;But as you may have noticed, the &lt;code&gt;x-data&lt;/code&gt; attribute in the last sample is using single quotes. When using double quotes, the code will break at runtime. Your single quotes might get replaced by double quotes due to IDE settings on when saving the &lt;code&gt;cshtml&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;A better solution would be to use single quotes for the javascript object string and date properties.&lt;/p&gt;
&lt;p&gt;One approach could be to replace the instance of &lt;code&gt;IJsonHelper&lt;/code&gt; which is being called by &lt;code&gt;Json.Serialize&lt;/code&gt; inside the view.&lt;/p&gt;
&lt;p&gt;Yet, the implementation is using &lt;code&gt;System.Text.Json&lt;/code&gt; which neither allows to use non-quoted property names nor use a different quote char.&lt;/p&gt;
&lt;p&gt;So, back to &lt;code&gt;Newtonsoft.Json&lt;/code&gt; - here&#39;s a possible solution (we could also create a new implementation of &lt;code&gt;IJSonHelper&lt;/code&gt;, of course):&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;JavaScriptConverter&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;static&lt;/span&gt; IHtmlContent &lt;span class=&quot;hljs-title&quot;&gt;SerializeObject&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-built_in&quot;&gt;object&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;value&lt;/span&gt;&lt;br /&gt;  &lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; stringWriter = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; StringWriter();&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; jsonWriter = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; JsonTextWriter(stringWriter);&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; serializer = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; JsonSerializer&lt;br /&gt;    {&lt;br /&gt;      ContractResolver = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; CamelCasePropertyNamesContractResolver()&lt;br /&gt;    };&lt;br /&gt;&lt;br /&gt;    jsonWriter.QuoteName = &lt;span class=&quot;hljs-literal&quot;&gt;false&lt;/span&gt;;&lt;br /&gt;    jsonWriter.QuoteChar = &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;&#92;&amp;#x27;&amp;#x27;&lt;/span&gt;;&lt;br /&gt;    serializer.Serialize(jsonWriter, &lt;span class=&quot;hljs-keyword&quot;&gt;value&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; HtmlString(stringWriter.ToString());&lt;br /&gt;  }&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using the &lt;code&gt;SerializeObject&lt;/code&gt; method, we solve three tasks:&lt;/p&gt;
&lt;p&gt;.NET objects get serialized&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;using camelCase&lt;/li&gt;
&lt;li&gt;without quotes in property names, hence creating a Javascript object&lt;/li&gt;
&lt;li&gt;using single quotes for string and date properties&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Usage inside the view changed to following:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;x-data&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;@JavaScriptSerializer.SerializeObject(new { Open = false})&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is less error-prone but still feels a bit cumbersome.&lt;/p&gt;
&lt;p&gt;So let&#39;s wrap the &lt;code&gt;JavaScriptSerializer&lt;/code&gt; call using a Tag Helper:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;[&lt;span class=&quot;hljs-meta&quot;&gt;HtmlTargetElement(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;*&amp;quot;&lt;/span&gt;, Attributes = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;alpine-data&amp;quot;&lt;/span&gt;)&lt;/span&gt;]&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;AlpineTagHelper&lt;/span&gt; : &lt;span class=&quot;hljs-title&quot;&gt;TagHelper&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Process&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;    TagHelperContext context,&lt;br /&gt;    TagHelperOutput output&lt;br /&gt;  &lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;    output.Attributes.Add(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;x-data&amp;quot;&lt;/span&gt;, JavaScriptConverter.SerializeObject(Data));&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;base&lt;/span&gt;.Process(context, output);&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  [&lt;span class=&quot;hljs-meta&quot;&gt;HtmlAttributeName(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;alpine-data&amp;quot;&lt;/span&gt;)&lt;/span&gt;] &lt;br /&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;object&lt;/span&gt; Data { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;set&lt;/span&gt;; } = &lt;span class=&quot;hljs-literal&quot;&gt;null&lt;/span&gt;!;&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now lets see how we can use this one.&lt;/p&gt;
&lt;p&gt;First, register the Tag Helper in &lt;code&gt;_ViewImports.cshtml&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers&lt;br /&gt;@addTagHelper *, Alpine.TagHelpers&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And just use the Tag Helper:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;alpine-data&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;new { Open = false }&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;button&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;x-on:click&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;open = !open&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;Show&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;button&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;x-show&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;open&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;x-cloak&lt;/span&gt;&amp;gt;&lt;/span&gt;Some details...&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The rendered result:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;x-data&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;{open:false}&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;button&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;x-on:click&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;open = !open&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;Show&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;button&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;x-show&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;open&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;Some details...&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Tag Helper is available as a &lt;a href=&quot;https://www.nuget.org/packages/Alpine.TagHelpers&quot;&gt;NuGet package&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;dotnet add package Alpine.TagHelpers&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  
  <entry>
    <title>A ASP.NET Razor Fragments / Single View approach - not only for HTMX</title>
    <link href="https://alexanderzeitler.com/articles/htmx-razor-fragment-single-view-approach/"/>
    <updated>2023-01-18T10:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/htmx-razor-fragment-single-view-approach/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;A ASP.NET Core Razor Fragments / Single View approach for HTMX&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;A ASP.NET Core Razor Fragments / Single View approach for HTMX&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/htmx-razor-fragment-single-view-approach/clint-adair-BW0vK-FA3eg-unsplash.jpg&quot; /&gt;
&lt;meta property=&quot;og:title&quot; content=&quot;A ASP.NET Core Razor Fragments / Single View approach for HTMX&quot; /&gt;
&lt;meta property=&quot;og:url&quot; content=&quot;https://alexanderzeitler.com/articles/htmx-razor-fragment-single-view-approach/&quot; /&gt;
&lt;meta property=&quot;og:image&quot; content=&quot;https://alexanderzeitler.com/articles/htmx-razor-fragment-single-view-approach/clint-adair-BW0vK-FA3eg-unsplash.jpg&quot; /&gt;
&lt;p&gt;&lt;small&gt;&lt;small&gt;Photo by &lt;a href=&quot;https://unsplash.com/ja/@clintadair?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Clint Adair&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/BW0vK-FA3eg?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;
&lt;/small&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/htmx-razor-fragment-single-view-approach/clint-adair-BW0vK-FA3eg-unsplash.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;A few months ago, the HTMX team published &lt;a href=&quot;https://htmx.org/essays/template-fragments/&quot;&gt;an essay on so-called template fragments&lt;/a&gt; to adhere to the Locality of Behavior (and in my opinion also cohesion) principle in server side rendered views.&lt;/p&gt;
&lt;p&gt;If you&#39;re using a template engine it is likely you end up with several views like typical parent / child/detail views:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/htmx-razor-fragment-single-view-approach/parent-child.svg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Until now, it has been common that this results in multiple view/component files. However, this comes with a disadvantage:&lt;/p&gt;
&lt;p&gt;While separation of concern is improved now, in the same way cohesion and locality of behavior deteriorates:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Locality of Behavior&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The behaviour of a unit of code should be as obvious as possible by looking only at that unit of code&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Cohesion&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Cohesion refers to the degree to which the elements inside a module belong together.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The proposed solution is to have a single template file but being able to address fragments of it by a key. By providing the key by e.g. a controller method calling the view engine, it will just render the fragment.&lt;/p&gt;
&lt;p&gt;This is the proposed sample as an HTML file providing template fragment support:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;html&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;body&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;hx-target&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;this&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;          #fragment archive-ui&lt;br /&gt;            #if contact.archived&lt;br /&gt;            &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;button&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;hx-patch&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;/contacts/${contact.id}/unarchive&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;Unarchive&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;button&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;            #else&lt;br /&gt;            &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;button&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;hx-delete&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;/contacts/${contact.id}&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;Archive&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;button&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;            #end&lt;br /&gt;          #end&lt;br /&gt;        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;h3&lt;/span&gt;&amp;gt;&lt;/span&gt;Contact&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;h3&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;p&lt;/span&gt;&amp;gt;&lt;/span&gt;${contact.email}&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;p&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;body&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;html&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The fragment id here is &lt;code&gt;archive-ui&lt;/code&gt;. So you can either render the full view or just two buttons.&lt;/p&gt;
&lt;p&gt;When reading this essay after it has been published, it appealed to me a lot, but I didn&#39;t think it could be supported by ASP.NET Core Razor without extending the View engine.&lt;/p&gt;
&lt;p&gt;So I went over to GitHub and created an &lt;a href=&quot;https://github.com/dotnet/aspnetcore/issues/43713&quot;&gt;issue&lt;/a&gt;, requesting for that enhancement. Although it has been &lt;a href=&quot;https://github.com/dotnet/aspnetcore/blob/main/docs/TriageProcess.md&quot;&gt;triaged&lt;/a&gt; and moved to the Backlog this won&#39;t be available soon (read: at least not in .NET 8).&lt;/p&gt;
&lt;p&gt;After creating the issue, I was repeatedly annoyed by the lack of support, but did not pursue the issue further - until yesterday.&lt;/p&gt;
&lt;p&gt;The topic came up again in the &lt;a href=&quot;https://discord.com/channels/725789699527933952/963040198390861875&quot;&gt;dotnet-htmx&lt;/a&gt; channel on the HTMX discord server.&lt;/p&gt;
&lt;p&gt;Based on the discussion, a rough idea formed in my head how this could be solved without extending Razor at all, getting full IDE support and all the like.&lt;/p&gt;
&lt;p&gt;Here&#39;s what I came up with - let&#39;s just talk code:&lt;/p&gt;
&lt;p&gt;First, we define some models:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;FragmentModel&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;FragmentModel&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; fragmentId&lt;br /&gt;  &lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;    FragmentId = fragmentId;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; FragmentId { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;set&lt;/span&gt;; }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;ChildModel&lt;/span&gt; : &lt;span class=&quot;hljs-title&quot;&gt;FragmentModel&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt; Id { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; }&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;ChildModel&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt; id&lt;br /&gt;  &lt;/span&gt;) : &lt;span class=&quot;hljs-title&quot;&gt;base&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Detail&amp;quot;&lt;/span&gt;&lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;    Id = id;&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;ParentModel&lt;/span&gt; : &lt;span class=&quot;hljs-title&quot;&gt;FragmentModel&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; List&amp;lt;ChildModel&amp;gt; Childs { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; }&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;ParentModel&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;    List&amp;lt;ChildModel&amp;gt; childs&lt;br /&gt;  &lt;/span&gt;) : &lt;span class=&quot;hljs-title&quot;&gt;base&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Full&amp;quot;&lt;/span&gt;&lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;    Childs = childs;&lt;br /&gt;  }&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we define a &lt;strong&gt;single&lt;/strong&gt; View file:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;@model RazorFragments.Models.FragmentModel&lt;br /&gt;&lt;br /&gt;@{&lt;br /&gt;  Layout = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;_Layout&amp;quot;&lt;/span&gt;;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;@{&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;RenderDetail&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;    ChildModel child&lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;    &amp;lt;div &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;bg-gray-200&amp;quot;&lt;/span&gt;&amp;gt;&lt;br /&gt;      @Html.ActionLink(child.Id.ToString(), &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Detail&amp;quot;&lt;/span&gt;, &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt;&lt;br /&gt;      {&lt;br /&gt;        id = child.Id&lt;br /&gt;      })&lt;br /&gt;    &amp;lt;/div&amp;gt;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;RenderFull&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;    ParentModel parent&lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;    &amp;lt;div &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;bg-blue-500 p-4&amp;quot;&lt;/span&gt;&amp;gt;&lt;br /&gt;      @{&lt;br /&gt;        @foreach (&lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; parentChild &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; parent.Childs)&lt;br /&gt;        {&lt;br /&gt;          RenderDetail(parentChild);&lt;br /&gt;        }&lt;br /&gt;      }&lt;br /&gt;    &amp;lt;/div&amp;gt;&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;@{&lt;br /&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;switch&lt;/span&gt; (Model.FragmentId)&lt;br /&gt;  {&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Full&amp;quot;&lt;/span&gt;:&lt;br /&gt;      RenderFull(Model &lt;span class=&quot;hljs-keyword&quot;&gt;as&lt;/span&gt; ParentModel);&lt;br /&gt;      &lt;span class=&quot;hljs-keyword&quot;&gt;break&lt;/span&gt;;&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Detail&amp;quot;&lt;/span&gt;:&lt;br /&gt;      RenderDetail(Model &lt;span class=&quot;hljs-keyword&quot;&gt;as&lt;/span&gt; ChildModel);&lt;br /&gt;      &lt;span class=&quot;hljs-keyword&quot;&gt;break&lt;/span&gt;;&lt;br /&gt;  }&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And everything gets tied together in our controller:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;RazorFragmentController&lt;/span&gt; : &lt;span class=&quot;hljs-title&quot;&gt;Controller&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;  &lt;span class=&quot;hljs-comment&quot;&gt;// GET&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; IActionResult &lt;span class=&quot;hljs-title&quot;&gt;Index&lt;/span&gt;()&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; View(&lt;br /&gt;      &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; ParentModel(&lt;br /&gt;        &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; List&amp;lt;ChildModel&amp;gt;()&lt;br /&gt;        {&lt;br /&gt;          &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;),&lt;br /&gt;          &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;)&lt;br /&gt;        }&lt;br /&gt;      )&lt;br /&gt;    );&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  [&lt;span class=&quot;hljs-meta&quot;&gt;Route(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;/detail/{id}&amp;quot;&lt;/span&gt;)&lt;/span&gt;]&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; IActionResult &lt;span class=&quot;hljs-title&quot;&gt;Detail&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;    [FromRoute] &lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt; id&lt;br /&gt;  &lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; View(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Index&amp;quot;&lt;/span&gt;, &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; ChildModel(id));&lt;br /&gt;  }&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is just a first draft stitched together in the middle of the night so I&#39;m curious about your thoughts, feedback and suggestions for improvement.&lt;/p&gt;
&lt;p&gt;The full sample can be found &lt;a href=&quot;https://github.com/AlexZeitler/htmx-aspnet-razor-fragments&quot;&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Update (2023/01/26)&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;I created &lt;a href=&quot;https://github.com/AlexZeitler/razor-sections-layout-with-fragments&quot;&gt;another sample&lt;/a&gt; which is closer to the mockup in from the intro:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;List view&lt;/strong&gt;
&lt;img src=&quot;https://alexanderzeitler.com/articles/htmx-razor-fragment-single-view-approach/screenshot_list.png&quot; alt=&quot;List view&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Details view&lt;/strong&gt;
&lt;img src=&quot;https://alexanderzeitler.com/articles/htmx-razor-fragment-single-view-approach/screenshot_details.png&quot; alt=&quot;Details view&quot; /&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>ASP.NET Razor Pages Fragments / Single View approach</title>
    <link href="https://alexanderzeitler.com/articles/aspnet-razor-pages-fragment-single-view-approach/"/>
    <updated>2023-01-27T20:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/aspnet-razor-pages-fragment-single-view-approach/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;ASP.NET Razor Pages Fragments / Single View approach&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;ASP.NET Razor Pages Fragments / Single View approach&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/aspnet-razor-pages-fragment-single-view-approach/olga-deeva-0OMqNL2khKU-unsplash.jpg&quot; /&gt;
&lt;p&gt;&lt;small&gt;&lt;small&gt;Photo by &lt;a href=&quot;https://unsplash.com/@loniel?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Olga Deeva&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/0OMqNL2khKU?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;
&lt;/small&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/aspnet-razor-pages-fragment-single-view-approach/olga-deeva-0OMqNL2khKU-unsplash.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I got some feedback for my &lt;a href=&quot;https://alexanderzeitler.com/articles/htmx-razor-fragment-single-view-approach&quot;&gt;previous post&lt;/a&gt;: Will the single view approach work with ASP.NET Razor Pages instead of MVC Views and Controllers?&lt;/p&gt;
&lt;p&gt;Well, I&#39;m a total Razor Pages noob, but this didn&#39;t stop me to give it a try.&lt;/p&gt;
&lt;p&gt;Long story short: it works, but I might be doing it totally wrong.&lt;/p&gt;
&lt;p&gt;Here&#39;s the source:&lt;/p&gt;
&lt;p&gt;Biggest issue upfront: how do we get multiple GET handlers in our Page Model?&lt;/p&gt;
&lt;p&gt;Jerrie Pelser has &lt;a href=&quot;https://www.jerriepelser.com/blog/razor-pages-muliple-handlers/&quot;&gt;a post&lt;/a&gt; that solved it for me.&lt;/p&gt;
&lt;p&gt;Our Page now has this route template:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;@page &amp;quot;{handler?}&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This makes stuff dynamic, and we can end up having multiple GET requests within the same Code for the page.&lt;/p&gt;
&lt;p&gt;Thus, our page looks very similar to our MVC View except for the Model which is now a child property if the Page Model:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;@page &amp;quot;{handler?}&amp;quot;&lt;br /&gt;@model IndexModel&lt;br /&gt;@{&lt;br /&gt;ViewData[&amp;quot;Title&amp;quot;] = &amp;quot;Home page&amp;quot;;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;@{&lt;br /&gt;    void RenderDetail(ChildModel child)&lt;br /&gt;    {&lt;br /&gt;        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;class&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;bg-gray-200&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;href&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;@Url.PageLink(&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;Index&lt;/span&gt;&amp;quot;, &amp;quot;&lt;span class=&quot;hljs-attr&quot;&gt;Details&lt;/span&gt;&amp;quot;, &lt;span class=&quot;hljs-attr&quot;&gt;new&lt;/span&gt; {&lt;span class=&quot;hljs-attr&quot;&gt;Id&lt;/span&gt; = &lt;span class=&quot;hljs-string&quot;&gt;child.Id})&lt;/span&gt;&amp;quot;&amp;gt;&lt;/span&gt;@child.Id&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;a&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    void RenderFull(ParentModel parent)&lt;br /&gt;    {&lt;br /&gt;    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;class&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;bg-blue-500 p-4&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;        @{&lt;br /&gt;        @foreach (var parentChild in parent.Childs)&lt;br /&gt;        {&lt;br /&gt;        RenderDetail(parentChild);&lt;br /&gt;        }&lt;br /&gt;        }&lt;br /&gt;    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;@{&lt;br /&gt;    switch (Model.PageModel.FragmentId)&lt;br /&gt;    {&lt;br /&gt;        case &amp;quot;Full&amp;quot;:&lt;br /&gt;            RenderFull(Model.PageModel as ParentModel);&lt;br /&gt;            break;&lt;br /&gt;        case &amp;quot;Detail&amp;quot;:&lt;br /&gt;            RenderDetail(Model.PageModel as ChildModel);&lt;br /&gt;            break;&lt;br /&gt;        default:&lt;br /&gt;            throw new ArgumentOutOfRangeException();&lt;br /&gt;    }&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The page model looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; Microsoft.AspNetCore.Mvc;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; Microsoft.AspNetCore.Mvc.RazorPages;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;RazorPagesFragments.Pages&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;IndexModel&lt;/span&gt; : &lt;span class=&quot;hljs-title&quot;&gt;PageModel&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;readonly&lt;/span&gt; ILogger&amp;lt;IndexModel&amp;gt; _logger;&lt;br /&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; FragmentModel PageModel { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;set&lt;/span&gt;; } = &lt;span class=&quot;hljs-literal&quot;&gt;null&lt;/span&gt;!;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;IndexModel&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;    ILogger&amp;lt;IndexModel&amp;gt; logger&lt;br /&gt;  &lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;    _logger = logger;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;OnGet&lt;/span&gt;()&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;    PageModel = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; ParentModel(&lt;br /&gt;      &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; List&amp;lt;ChildModel&amp;gt;()&lt;br /&gt;      {&lt;br /&gt;        &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;),&lt;br /&gt;        &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt;(&lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;)&lt;br /&gt;      }&lt;br /&gt;    );&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;OnGetDetails&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;    [FromQuery] &lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt; id&lt;br /&gt;  &lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;    PageModel = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; ChildModel(id);&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;FragmentModel&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;FragmentModel&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; fragmentId&lt;br /&gt;  &lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;    FragmentId = fragmentId;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; FragmentId { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;set&lt;/span&gt;; }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;ChildModel&lt;/span&gt; : &lt;span class=&quot;hljs-title&quot;&gt;FragmentModel&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt; Id { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; }&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;ChildModel&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt; id&lt;br /&gt;  &lt;/span&gt;) : &lt;span class=&quot;hljs-title&quot;&gt;base&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Detail&amp;quot;&lt;/span&gt;&lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;    Id = id;&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;ParentModel&lt;/span&gt; : &lt;span class=&quot;hljs-title&quot;&gt;FragmentModel&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; List&amp;lt;ChildModel&amp;gt; Childs { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; }&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;ParentModel&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;    List&amp;lt;ChildModel&amp;gt; childs&lt;br /&gt;  &lt;/span&gt;) : &lt;span class=&quot;hljs-title&quot;&gt;base&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Full&amp;quot;&lt;/span&gt;&lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;    Childs = childs;&lt;br /&gt;  }&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that&#39;s it.&lt;/p&gt;
&lt;p&gt;If I have missed something due to my lack of Razor Pages knowledge, feel fry to drop me a line how to improve it.&lt;/p&gt;
&lt;p&gt;The full sample can be found &lt;a href=&quot;https://github.com/AlexZeitler/razor-pages-fragments-sample&quot;&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Fixing my .NET 6 SDK installation after installing .NET SDK 7.0 on Xubuntu</title>
    <link href="https://alexanderzeitler.com/articles/fixing-my-bricked-dotnet-sdk-6-installation-on-xubuntu-ubuntu-after-dotnet-7-sdk-installation/"/>
    <updated>2023-01-27T20:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/fixing-my-bricked-dotnet-sdk-6-installation-on-xubuntu-ubuntu-after-dotnet-7-sdk-installation/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Fixing my .NET 6 SDK installation after installing .NET SDK 7.0 on Xubuntu&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Fixing my .NET 6 SDK installation after installing .NET SDK 7.0 on Xubuntu&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/fixing-my-bricked-dotnet-sdk-6-installation-on-xubuntu-ubuntu-after-dotnet-7-sdk-installation/This_is_Fine_Gopher.jpg&quot; /&gt;
&lt;p&gt;&lt;small&gt;&lt;small&gt;Artwork by &lt;a href=&quot;https://twitter.com/ashleymcnamara&quot;&gt;Ashley Willis&lt;/a&gt; on &lt;a href=&quot;https://github.com/ashleymcnamara/gophers/&quot;&gt;GitHub&lt;/a&gt;
&lt;/small&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/fixing-my-bricked-dotnet-sdk-6-installation-on-xubuntu-ubuntu-after-dotnet-7-sdk-installation/This_is_Fine_Gopher.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I&#39;m running on Xubuntu 22.04 day in day out working with .NET most of the time, so downtime is not an option.&lt;/p&gt;
&lt;p&gt;.NET 6 SDK was installed using the &lt;code&gt;dotnet6&lt;/code&gt; &lt;a href=&quot;https://github.com/dotnet/core/issues/7699&quot;&gt;package from Ubuntu&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Shortly after Microsoft released .NET 7, I wanted to upgrade, but it did not work because of this error:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Package &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;dotnet-sdk-7.0&amp;#x27;&lt;/span&gt; has no installation candidate&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, no .NET 7 for me so far.&lt;/p&gt;
&lt;p&gt;Last weekend has been the time when I finally wanted to install .NET 7 SDK side-by-side with the existing .NET 7 SDK because I wanted to test out some stuff:&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;This 👇&lt;a href=&quot;https://t.co/WdanSAlhvW&quot;&gt;https://t.co/WdanSAlhvW&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;And side projects ofc &lt;a href=&quot;https://t.co/xrrLC8tV35&quot;&gt;https://t.co/xrrLC8tV35&lt;/a&gt; &lt;a href=&quot;https://t.co/rzMkeyIHYR&quot;&gt;pic.twitter.com/rzMkeyIHYR&lt;/a&gt;&lt;/p&gt;&amp;mdash; Alexander Zeitler (@lxztlr) &lt;a href=&quot;https://twitter.com/lxztlr/status/1619121970796457985?ref_src=twsrc%5Etfw&quot;&gt;January 27, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt; 
&lt;p&gt;As I knew there would be some trouble I pulled out the &lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/core/install/linux-ubuntu&quot;&gt;Microsoft installation Guide for .NET 7 on Ubuntu&lt;/a&gt;. I also had an eye on the &lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/core/install/linux-package-mixup&quot;&gt;troubleshooting guide&lt;/a&gt; for mixed installations like my scenario was.&lt;/p&gt;
&lt;p&gt;So my first attempt has been this:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;wget https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; dpkg -i packages-microsoft-prod.deb&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;rm&lt;/span&gt; packages-microsoft-prod.deb&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And it failed (obviously, because this would not be worth a blog post):&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Reading package lists... Done&lt;br /&gt;Building dependency tree... Done&lt;br /&gt;Reading state information... Done&lt;br /&gt;Package dotnet-sdk-7.0 is not available, but is referred to by another package.&lt;br /&gt;This may mean that the package is missing, has been obsoleted, or&lt;br /&gt;is only available from another &lt;span class=&quot;hljs-built_in&quot;&gt;source&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;E: Package &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;dotnet-sdk-7.0&amp;#x27;&lt;/span&gt; has no installation candidate&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Back to the troubleshooting guide, I prioritized the Microsoft package feed for the &lt;code&gt;dotnet-sdk-7.0&lt;/code&gt; package &lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/core/install/linux-package-mixup#solutions&quot;&gt;as described in the guide&lt;/a&gt; by editing / adding &lt;code&gt;/etc/apt/preferences&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Package: dotnet-sdk-7.0&lt;br /&gt;Pin: origin &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;packages.microsoft.com&amp;quot;&lt;/span&gt;&lt;br /&gt;Pin-Priority: 999&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next &lt;code&gt;sudo apt update &amp;amp;&amp;amp; sudo apt install dotnet-sdk-7.0&lt;/code&gt;: no errors.&lt;/p&gt;
&lt;p&gt;Let&#39;s try &lt;code&gt;dotnet --list-sdks&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;6.0.405 [/usr/share/dotnet/sdk]&lt;br /&gt;7.0.102 [/usr/share/dotnet/sdk]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Restrained joy. Should that have been it?&lt;/p&gt;
&lt;p&gt;Of course - not.&lt;/p&gt;
&lt;p&gt;Back to my major project, I noticed that .NET 6 SDK (via &lt;code&gt;dotnet run --framework net6.0&lt;/code&gt;) stopped working:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;dotnet run --framework net6.0   &lt;br /&gt;Building...&lt;br /&gt;You must install or update .NET to run this application.&lt;br /&gt;&lt;br /&gt;App: /home/alexzeitler/src/myproject/bin/Debug/net6.0/myproject&lt;br /&gt;Architecture: x64&lt;br /&gt;Framework: &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;Microsoft.NETCore.App&amp;#x27;&lt;/span&gt;, version &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;6.0.0&amp;#x27;&lt;/span&gt; (x64)&lt;br /&gt;.NET location: /usr/share/dotnet&lt;br /&gt;&lt;br /&gt;The following frameworks were found:&lt;br /&gt;  7.0.2 at [/usr/share/dotnet/shared/Microsoft.NETCore.App]&lt;br /&gt;&lt;br /&gt;Learn about framework resolution:&lt;br /&gt;https://aka.ms/dotnet/app-launch-failed&lt;br /&gt;&lt;br /&gt;To install missing framework, download:&lt;br /&gt;https://aka.ms/dotnet-core-applaunch?framework=Microsoft.NETCore.App&amp;amp;framework_version=6.0.0&amp;amp;&lt;span class=&quot;hljs-built_in&quot;&gt;arch&lt;/span&gt;=x64&amp;amp;rid=ubuntu.22.04-x64&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Maybe add a &lt;code&gt;global.json&lt;/code&gt; inside project folder?&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;sdk&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;version&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;6.0.405&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;rollForward&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;latestMinor&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;allowPrerelease&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;false&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nobody likes your &lt;code&gt;global.json&lt;/code&gt;, Alex.&lt;/p&gt;
&lt;p&gt;&amp;quot;Maybe I should just move on and work with .NET 7 locally...&amp;quot;.&lt;/p&gt;
&lt;p&gt;Didn&#39;t want that, but I didn&#39;t want to invest more time and I also wanted to keep .NET 7 SDK installed.&lt;/p&gt;
&lt;p&gt;So, I updated the &lt;code&gt;csproj&lt;/code&gt; file like this to target .NET 6 and .NET 7:&lt;/p&gt;
&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;TargetFrameworks&lt;/span&gt;&amp;gt;&lt;/span&gt;net6.0;net7.0&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;TargetFrameworks&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now I could use build and run my project locally using .NET 7 and tests did also work.&lt;/p&gt;
&lt;p&gt;That&#39;s all nice but how about some broken CI builds?&lt;/p&gt;
&lt;p&gt;Of course this didn&#39;t work as expected because the project has multiple targets now, and you need to specify a framework version in your Docker build.&lt;/p&gt;
&lt;p&gt;&amp;quot;Well, that&#39;s easy&amp;quot;.&lt;/p&gt;
&lt;p&gt;No, it&#39;s not.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dotnet restore&lt;/code&gt; doesn&#39;t understand &lt;code&gt;--framework&lt;/code&gt; flag.&lt;/p&gt;
&lt;p&gt;But: it does understand a MSBuild parameter called &lt;code&gt;TargetFramework&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env&lt;br /&gt;WORKDIR /app&lt;br /&gt;&lt;br /&gt;# Copy everything&lt;br /&gt;COPY . ./&lt;br /&gt;# Restore as distinct layers&lt;br /&gt;RUN dotnet restore -p:TargetFramework=net6.0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, this should also work for &lt;code&gt;dotnet publish&lt;/code&gt; one might guess...&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env&lt;br /&gt;WORKDIR /app&lt;br /&gt;&lt;br /&gt;# Copy everything&lt;br /&gt;COPY . ./&lt;br /&gt;# Restore as distinct layers&lt;br /&gt;RUN dotnet restore -p:TargetFramework=net6.0&lt;br /&gt;# Build and publish a release&lt;br /&gt;RUN dotnet publish --framework net6.0 -p:TargetFramework=net6.0 -c Release -o out &lt;br /&gt;&lt;br /&gt;# Build runtime image&lt;br /&gt;FROM mcr.microsoft.com/dotnet/aspnet:6.0&lt;br /&gt;WORKDIR /app&lt;br /&gt;COPY --from=build-env /app/out .&lt;br /&gt;ENTRYPOINT [&quot;dotnet&quot;, &quot;MyApp.dll&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hard &amp;quot;no&amp;quot;, again.&lt;/p&gt;
&lt;p&gt;And it&#39;s not interested in your &lt;code&gt;--framework&lt;/code&gt; flag also, Alex.&lt;/p&gt;
&lt;p&gt;For whatever reason, this command did work outside the Docker build.&lt;/p&gt;
&lt;p&gt;Meanwhile, I got a suggestion to try the .NET Core plugin for the &lt;code&gt;asdf&lt;/code&gt; version manager and do a manual install (did I mention we really should have something like &lt;code&gt;nvm&lt;/code&gt; for Node?).&lt;/p&gt;
&lt;p&gt;I was tempted to walk this path, but something dragged me to take a look at the issues for the plugin first.&lt;/p&gt;
&lt;p&gt;And there it was: &lt;a href=&quot;https://github.com/emersonsoares/asdf-dotnet-core/issues/21&quot;&gt;Rider can&#39;t detect dotnet sdk&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And this ongoing issue dug &lt;a href=&quot;https://github.com/emersonsoares/asdf-dotnet-core/issues/21&quot;&gt;another rabbit hole&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Ok, time to chop some wood.&lt;/p&gt;
&lt;p&gt;It&#39;s been late in the evening and I did not want to start the next day with a broken installation, so I decided to wipe .NET SDK completely and start with a fresh install.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt remove dotnet-sdk-6.0 dotnet-sdk-7.0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now &lt;code&gt;dotnet --version&lt;/code&gt; shouldn&#39;t work, but...&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;6.0.13&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Wait, what?&lt;/p&gt;
&lt;p&gt;Turns out, there&#39;s still something left from the Ubuntu .NET 6 SDK install.
So comment out the settings in &lt;code&gt;/etc/apt/preferences&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt remove dotnet6&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, gone.&lt;/p&gt;
&lt;p&gt;Next: reinstall .NET 6 SDK.&lt;/p&gt;
&lt;p&gt;Undo the comment out of the settings in &lt;code&gt;/etc/apt/preferences&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Package: dotnet-* aspnetcore-* netstandard-*&lt;br /&gt;Pin: origin &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;packages.microsoft.com&amp;quot;&lt;/span&gt;&lt;br /&gt;Pin-Priority: 999&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt clean&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt update&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt install dotnet-sdk-6.0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No errors.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;dotnet --version&lt;br /&gt;A fatal error occurred. The folder [/usr/share/dotnet/host/fxr] does not exist&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Great.&lt;/p&gt;
&lt;p&gt;But &lt;code&gt;apt install&lt;/code&gt; did tell me, the dependencies for &lt;code&gt;dotnet-sdk-6.0&lt;/code&gt; have been installed...&lt;/p&gt;
&lt;p&gt;Turns out, they have been installed from Ubuntu packages and &lt;code&gt;dotnet-sdk-6.0&lt;/code&gt; has been installed from Microsoft packages.&lt;/p&gt;
&lt;p&gt;So, remove everything again, but also the dependencies:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt remove dotnet-sdk-6.0&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt autoremove &lt;span class=&quot;hljs-comment&quot;&gt;#this will remove the unused dependencies &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Update the &lt;code&gt;/etc/apt/preferences&lt;/code&gt; again. This time include all dependencies:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Package: dotnet-* aspnetcore-* netstandard-*&lt;br /&gt;Pin: origin &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;packages.microsoft.com&amp;quot;&lt;/span&gt;&lt;br /&gt;Pin-Priority: 999&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next try:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt update&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt install dotnet-sdk-6.0&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;dotnet --version&lt;br /&gt;6.0.405&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Good. Next: Run the app:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;dotnet run&lt;br /&gt;&lt;br /&gt;[08:32:51 INF] Now listening on: https://0.0.0.0:5001&lt;br /&gt;[08:32:51 INF] Now listening on: http://0.0.0.0:5000&lt;br /&gt;[08:32:51 DBG] Loaded hosting startup assembly MyProject&lt;br /&gt;[08:32:51 DBG] Loaded hosting startup assembly Microsoft.AspNetCore.Watch.BrowserRefresh&lt;br /&gt;[08:32:51 INF] Application started. Press Ctrl+C to shut down.&lt;br /&gt;[08:32:51 INF] Hosting environment: Development&lt;br /&gt;[08:32:51 DBG] Hosting started&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ok, let&#39;s try &lt;code&gt;dotnet watch run&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;dotnet watch run&lt;br /&gt;You must install .NET to run this application.&lt;br /&gt;&lt;br /&gt;App: /home/alexzeitler/.dotnet/tools/dotnet-outdated&lt;br /&gt;Architecture: x64&lt;br /&gt;App host version: 6.0.13&lt;br /&gt;.NET location: Not found&lt;br /&gt;&lt;br /&gt;Learn about runtime installation:&lt;br /&gt;https://aka.ms/dotnet/app-launch-failed&lt;br /&gt;&lt;br /&gt;Download the .NET runtime:&lt;br /&gt;https://aka.ms/dotnet-core-applaunch?missing_runtime=&lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;&amp;amp;&lt;span class=&quot;hljs-built_in&quot;&gt;arch&lt;/span&gt;=x64&amp;amp;rid=ubuntu.22.04-x64&amp;amp;apphost_version=6.0.13&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Still something broken - but what?&lt;/p&gt;
&lt;p&gt;Looks like it&#39;s not just me having issues: &lt;a href=&quot;https://github.com/dotnet/runtime/issues/79237&quot;&gt;.NET runtime not found on Ubuntu&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Ok, let&#39;s follow &lt;a href=&quot;https://github.com/dotnet/runtime/issues/79237#issuecomment-1338168740&quot;&gt;the advice&lt;/a&gt; and set &lt;code&gt;/etc/dotnet/install_location&lt;/code&gt; to &lt;code&gt;/usr/share/dotnet&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then fix the missing &lt;code&gt;DOTNET_ROOT&lt;/code&gt; environment variable and add (re-add?) that path to the &lt;code&gt;PATH&lt;/code&gt; environment variable in &lt;code&gt;~/.zshrc&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;export&lt;/span&gt; DOTNET_ROOT=/usr/share/dotnet&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;export&lt;/span&gt; PATH=&lt;span class=&quot;hljs-variable&quot;&gt;$PATH&lt;/span&gt;:&lt;span class=&quot;hljs-variable&quot;&gt;$DOTNET_ROOT&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Source the updated &lt;code&gt;.zshrc&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;. ~/.zshrc&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Try a &lt;code&gt;dotnet&lt;/code&gt; tool:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;dotnet-outdated&lt;br /&gt;Discovering projects...The directory &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;/home/alex&amp;#x27;&lt;/span&gt; does not contain any solutions or projects.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fine. Try to &lt;code&gt;dotnet watch run&lt;/code&gt; the project again:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;[08:32:51 INF] Now listening on: https://0.0.0.0:5001&lt;br /&gt;[08:32:51 INF] Now listening on: http://0.0.0.0:5000&lt;br /&gt;[08:32:51 DBG] Loaded hosting startup assembly MyProject&lt;br /&gt;[08:32:51 DBG] Loaded hosting startup assembly Microsoft.AspNetCore.Watch.BrowserRefresh&lt;br /&gt;[08:32:51 INF] Application started. Press Ctrl+C to shut down.&lt;br /&gt;[08:32:51 INF] Hosting environment: Development&lt;br /&gt;[08:32:51 DBG] Hosting started&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ok. I can get my work done tomorrow.&lt;/p&gt;
&lt;p&gt;But what about .NET 7 SDK?&lt;/p&gt;
&lt;p&gt;&amp;quot;It&#39;s just 1:30am so why not give it a try again?&amp;quot;&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt update&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt install dotnet-sdk-7.0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No errors.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;dotnet --list-sdks&lt;br /&gt;6.0.405 [/usr/share/dotnet/sdk]&lt;br /&gt;7.0.102 [/usr/share/dotnet/sdk]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Open the .NET 6 Project in Rider with only .NET 6 targeted again:&lt;/p&gt;
&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;TargetFramework&lt;/span&gt;&amp;gt;&lt;/span&gt;net6.0&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;TargetFramework&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Loads.&lt;/p&gt;
&lt;p&gt;Try to build.&lt;/p&gt;
&lt;p&gt;Works.&lt;/p&gt;
&lt;p&gt;Try to run the tests.&lt;/p&gt;
&lt;p&gt;Works.&lt;/p&gt;
&lt;p&gt;Open a .NET 7 project.&lt;/p&gt;
&lt;p&gt;Try to build.&lt;/p&gt;
&lt;p&gt;Works.&lt;/p&gt;
&lt;p&gt;Try to run the tests.&lt;/p&gt;
&lt;p&gt;Works.&lt;/p&gt;
&lt;p&gt;YES!&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>ASP.NET: The default developer certificate could not be found</title>
    <link href="https://alexanderzeitler.com/articles/aspnet-the-default-developer-certifcate-could-not-be-found-or-is-out-of-date/"/>
    <updated>2023-03-04T14:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/aspnet-the-default-developer-certifcate-could-not-be-found-or-is-out-of-date/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;ASP.NET: The default developer certificate could not be found&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;ASP.NET: The default developer certificate could not be found&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/aspnet-developer-certificate-ssl-error-protocol-version-alert/georg-bommeli-ybtUqjybcjE-unsplash.jpg&quot; /&gt;
&lt;p&gt;&lt;small&gt;&lt;small&gt;
Photo by &lt;a href=&quot;https://unsplash.com/@calina?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Georg Bommeli&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/ybtUqjybcjE?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;
&lt;/small&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/aspnet-the-default-developer-certifcate-could-not-be-found-or-is-out-of-date/georg-bommeli-ybtUqjybcjE-unsplash.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After playing around with ASP.NET developer certificates on macOS I couldn&#39;t get it back to work. Whenever the app started, I got this exception on startup:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Unable to configure HTTPS endpoint. No server certificate was specified, and the default developer certificate could not be found or is out of date.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Whatever I tried (&lt;code&gt;dotnet dev-certs https&lt;/code&gt; [ &lt;code&gt;--clean&lt;/code&gt; / &lt;code&gt;--trust&lt;/code&gt; ]), I couldn&#39;t get it to work.&lt;/p&gt;
&lt;p&gt;I also removed the certs manually from macOS Keychain.&lt;/p&gt;
&lt;p&gt;After a while I noticed, that in &lt;code&gt;~/.aspnet/dev-certs/https&lt;/code&gt; even after a &lt;code&gt;dotnet dev-certs https --clean&lt;/code&gt; there has still been one &lt;code&gt;.pfx&lt;/code&gt; left, owned by &lt;code&gt;root&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;After deleting this one, ASP.NET picked up a fresh generated developer certifcate correctly again.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Quickly navigate between Finder folders using the keyboard on macOS</title>
    <link href="https://alexanderzeitler.com/articles/quick-navigate-to-folder-macos-finder-keyboard/"/>
    <updated>2023-03-05T08:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/quick-navigate-to-folder-macos-finder-keyboard/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Quickly navigate between Finder folders using the keyboard on macOS&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Quickly navigate between Finder folders using the keyboard on macOS&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/quick-navigate-to-folder-macos-finder-keyboard/glenn-carstens-peters-npxXWgQ33ZQ-unsplash.jpg)&quot; /&gt;
&lt;p&gt;&lt;small&gt;&lt;small&gt;
Photo by &lt;a href=&quot;https://unsplash.com/@glenncarstenspeters?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Glenn Carstens-Peters&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/npxXWgQ33ZQ?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;
&lt;/small&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/quick-navigate-to-folder-macos-finder-keyboard/glenn-carstens-peters-npxXWgQ33ZQ-unsplash.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Recently I learned &lt;a href=&quot;https://twitter.com/gittower/status/1626589872445325320&quot;&gt;how to navigate to a folder quickly&lt;/a&gt; when opening or saving files on macOS using &lt;code&gt;⇧ + ⌘  + G&lt;/code&gt; on the keyboard.&lt;/p&gt;
&lt;p&gt;This shortcut also works when having folders open in Finder:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/quick-navigate-to-folder-macos-finder-keyboard/quick-navigate-between-folders-shift-command-g-in-macos-finder-min.png&quot; alt=&quot;Quick navigation between folders in macOS Finder&quot; /&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>ASP.NET Core developer certificate: ERR_SSL_VERSION_OR_CIPHER_MISMATCH on macOS</title>
    <link href="https://alexanderzeitler.com/articles/asp-net-core-developer-certificate-err-ssl-version-or-cipher-mismatch-on-macos/"/>
    <updated>2023-03-08T21:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/asp-net-core-developer-certificate-err-ssl-version-or-cipher-mismatch-on-macos/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;ASP.NET Core developer certificate: ERR_SSL_VERSION_OR_CIPHER_MISMATCH on macOS&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;ASP.NET Core developer certificate: ERR_SSL_VERSION_OR_CIPHER_MISMATCH on macOS&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/the-asp-net-core-developer-certificate-is-in-an-invalid-state-macos/kenny-eliason--Cmz06-0btw-unsplash.jpg&quot; /&gt;
&lt;p&gt;&lt;small&gt;&lt;small&gt;
Photo by &lt;a href=&quot;https://unsplash.com/@neonbrand?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Kenny Eliason&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/wrong?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;
&lt;/small&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/asp-net-core-developer-certificate-err-ssl-version-or-cipher-mismatch-on-macos/kenny-eliason--Cmz06-0btw-unsplash.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I&#39;ve been facing an issue with the ASP.NET developer certificates for ASP.NET Core 7 apps using HTTPS on macOS:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ERR_SSL_VERSION_OR_CIPHER_MISMATCH&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Whatever I tried (&lt;code&gt;dotnet dev-certs https&lt;/code&gt; [ &lt;code&gt;--clean&lt;/code&gt; / &lt;code&gt;--trust&lt;/code&gt; ]), I couldn&#39;t get it to work.&lt;/p&gt;
&lt;p&gt;I also removed the certs manually from macOS Keychain.&lt;/p&gt;
&lt;p&gt;Then I found &lt;a href=&quot;https://github.com/dotnet/aspnetcore/issues/18236&quot;&gt;this issue&lt;/a&gt; on GitHub which suggested adding this &lt;code&gt;PropertyGroup&lt;/code&gt; to the &lt;code&gt;csproj&lt;/code&gt; file:&lt;/p&gt;
&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;PropertyGroup&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;UseAppHost&lt;/span&gt;&amp;gt;&lt;/span&gt;false&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;UseAppHost&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;PropertyGroup&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In fact it fixed the error.&lt;/p&gt;
&lt;p&gt;Now I could use some help from you: Why does this setting fix it?&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Can&#39;t delete a note in PDF using Preview on macOS</title>
    <link href="https://alexanderzeitler.com/articles/delete-note-in-pdf-preview-macos/"/>
    <updated>2023-03-11T19:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/delete-note-in-pdf-preview-macos/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Can&#39;t delete a note in PDF using Preview on macOS&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Can&#39;t delete a note in PDF using Preview on macOS&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/delete-note-in-pdf-preview-macos/cant-delete-note-pdf-macos-preview.png&quot; /&gt;
&lt;p&gt;While reviewing some PDF articles I took notes that I wanted to delete later on using macOS Preview app. But it didn&#39;t work as expected.&lt;/p&gt;
&lt;p&gt;Here&#39;s the crime scene:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/delete-note-in-pdf-preview-macos/cant-delete-note-pdf-macos-preview.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The most obvious things to try are hitting the backspace key (also together with ⇧ and/or ⌘). But this didn&#39;t work.&lt;/p&gt;
&lt;p&gt;Lucky me, there&#39;s a workaround.&lt;/p&gt;
&lt;p&gt;Open the &amp;quot;Inspector&amp;quot; using &lt;code&gt;⌘ + i&lt;/code&gt; shortcut:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/delete-note-in-pdf-preview-macos/inspector.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;There you can select a note and once it&#39;s highlighted, it can be removed using the backspace key:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/delete-note-in-pdf-preview-macos/note-deleted.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Late 2025 Update&lt;/h2&gt;
&lt;p&gt;Since this is one of the most read posts on my blog, I would like to point out that I completely stopped using macOS since shortly after the post in 2023.&lt;/p&gt;
&lt;p&gt;Linux has been my daily driver since 2014 and everything I&#39;ve still been using macOS for, I can do on Linux since then.&lt;/p&gt;
&lt;p&gt;Recently I&#39;ve been switching from Ubuntu to &lt;a href=&quot;https://omarchy.org/&quot;&gt;Omarchy&lt;/a&gt;, which is the most enjoyable OS I&#39;ve ever used.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>IdentityGeneratorTemplateMode2 does not contain a definition for &quot;UseSQLite&quot;</title>
    <link href="https://alexanderzeitler.com/articles/identitygeneratortemplatemode2-does-not-contain-a-definition-for-usesqlite/"/>
    <updated>2023-03-18T09:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/identitygeneratortemplatemode2-does-not-contain-a-definition-for-usesqlite/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;IdentityGeneratorTemplateMode2 does not contain a definition for &#39;UseSQLite&#39;&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;IdentityGeneratorTemplateMode2 does not contain a definition for &#39;UseSQLite&#39;&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/identitygeneratortemplatemode2-does-not-contain-a-definition-for-usesqlite/jackson-simmer-Vqg809B-SrE-unsplash.jpg&quot; /&gt;
&lt;p&gt;The other day I tried to scaffold ASP.NET Identity for Sqlite (actually I wanted to scaffold it for Postgres but that&#39;s another story) from the CLI:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;dotnet aspnet-codegenerator identity -dc IdentityDbContext -u AppUser -f -gl -dbProvider sqlite&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Straight forward, but I got this error:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;IdentityGeneratorTemplateMode2 does not contain a definition for &#39;UseSQLite&#39;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The project I was trying to add Identity to, has been a spike where I was trying out some stuff using &lt;a href=&quot;https://jasperfx.github.io/alba/&quot;&gt;&lt;code&gt;Alba&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One thing I had changed in the project compared to others where I was scaffolding Identity successfully, was that the project contained a &lt;code&gt;Startup.cs&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;As I was stuck I just tried guessing and deleted the &lt;code&gt;Startup.cs&lt;/code&gt; file and ran the scaffolding again.&lt;/p&gt;
&lt;p&gt;And boom 💥, it did work successfully again.&lt;/p&gt;
&lt;p&gt;Right now I don&#39;t know why the file caused this issue but I &lt;a href=&quot;https://github.com/dotnet/Scaffolding/issues/2257#issuecomment-1473977842&quot;&gt;provided my learning&lt;/a&gt; to an existing GitHub issue so Microsoft can figure it out.&lt;/p&gt;
&lt;h3&gt;Update&lt;/h3&gt;
&lt;p&gt;Make sure to read the &lt;a href=&quot;https://alexanderzeitler.com/articles/entity-framework-core-why-you-should-never-name-your-db-context-identitydbcontext/&quot;&gt;follow up&lt;/a&gt;.&lt;/p&gt;
&lt;small&gt;
&lt;small&gt;
Twitter preview Photo by &lt;a href=&quot;https://unsplash.com/@simmerdownjpg?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Jackson Simmer&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/Vqg809B-SrE?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;
&lt;/small&gt;
&lt;/small&gt;
</content>
  </entry>
  
  <entry>
    <title>Entity Framework Core: Why you should never name your DbContext &#39;IdentityDbContext&#39;</title>
    <link href="https://alexanderzeitler.com/articles/entity-framework-core-why-you-should-never-name-your-db-context-identitydbcontext/"/>
    <updated>2023-03-18T17:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/entity-framework-core-why-you-should-never-name-your-db-context-identitydbcontext/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Entity Framework Core: Why you should never name your DbContent &#39;IdentityDbContext&#39;&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Entity Framework Core: Why you should never name your DbContent &#39;IdentityDbContext&#39;&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/entity-framework-core-why-you-should-never-name-your-db-context-identitydbcontext/no.png&quot; /&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/entity-framework-core-why-you-should-never-name-your-db-context-identitydbcontext/no.png&quot; alt=&quot;Don&#39;t do this&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This is more or less a follow up to the &lt;a href=&quot;https://alexanderzeitler.com/articles/identitygeneratortemplatemode2-does-not-contain-a-definition-for-usesqlite/&quot;&gt;previous post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As said in the post already, I was playing around with &lt;code&gt;Startup.cs&lt;/code&gt; style of application start up together with Entity Framework Core / ASP.NET Identity scaffolding.&lt;/p&gt;
&lt;p&gt;So after I got Identity scaffolded, I re-added the &lt;code&gt;Startup.cs&lt;/code&gt; and wanted to move on with migrations.&lt;/p&gt;
&lt;p&gt;My &lt;code&gt;Program.cs&lt;/code&gt; looked like this:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Program&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Main&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt;[] &lt;span class=&quot;hljs-keyword&quot;&gt;args&lt;/span&gt;&lt;br /&gt;  &lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;    CreateHostBuilder(&lt;span class=&quot;hljs-keyword&quot;&gt;args&lt;/span&gt;)&lt;br /&gt;      .Build()&lt;br /&gt;      .Run();&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;static&lt;/span&gt; IHostBuilder &lt;span class=&quot;hljs-title&quot;&gt;CreateHostBuilder&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt;[] &lt;span class=&quot;hljs-keyword&quot;&gt;args&lt;/span&gt;&lt;br /&gt;  &lt;/span&gt;)&lt;/span&gt; =&amp;gt;&lt;br /&gt;    Host.CreateDefaultBuilder(&lt;span class=&quot;hljs-keyword&quot;&gt;args&lt;/span&gt;)&lt;br /&gt;      .ConfigureWebHostDefaults(webBuilder =&amp;gt; { webBuilder.UseStartup&amp;lt;Startup&amp;gt;(); });&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This was the &lt;code&gt;Startup.cs&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Startup&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; IConfiguration Configuration { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; }&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Startup&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;    IHostEnvironment env&lt;br /&gt;  &lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; builder = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; ConfigurationBuilder()&lt;br /&gt;      .SetBasePath(env.ContentRootPath)&lt;br /&gt;      .AddJsonFile(&lt;br /&gt;        &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;appsettings.json&amp;quot;&lt;/span&gt;,&lt;br /&gt;        optional: &lt;span class=&quot;hljs-literal&quot;&gt;false&lt;/span&gt;,&lt;br /&gt;        reloadOnChange: &lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;&lt;br /&gt;      )&lt;br /&gt;      .AddJsonFile(&lt;span class=&quot;hljs-string&quot;&gt;$&amp;quot;appsettings.&lt;span class=&quot;hljs-subst&quot;&gt;{env.EnvironmentName}&lt;/span&gt;.json&amp;quot;&lt;/span&gt;, optional: &lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;)&lt;br /&gt;      .AddEnvironmentVariables();&lt;br /&gt;    Configuration = builder.Build();&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-comment&quot;&gt;// This method gets called by the runtime. Use this method to add services to the container&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;ConfigureServices&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;    IServiceCollection services&lt;br /&gt;  &lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; connectionString = Configuration.GetConnectionString(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;IdentityDbContextConnection&amp;quot;&lt;/span&gt;) ??&lt;br /&gt;                           &lt;span class=&quot;hljs-keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; InvalidOperationException(&lt;br /&gt;                             &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Connection string &amp;#x27;IdentityDbContextConnection&amp;#x27; not found.&amp;quot;&lt;/span&gt;&lt;br /&gt;                           );&lt;br /&gt;&lt;br /&gt;    services.AddDbContext&amp;lt;IdentityDbContext&amp;gt;(options =&amp;gt; options.UseNpgsql(connectionString));&lt;br /&gt;&lt;br /&gt;    services.AddDefaultIdentity&amp;lt;AppUser&amp;gt;(options =&amp;gt; options.SignIn.RequireConfirmedAccount = &lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;)&lt;br /&gt;      .AddEntityFrameworkStores&amp;lt;IdentityDbContext&amp;gt;();&lt;br /&gt;&lt;br /&gt;    services.AddControllersWithViews();&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-comment&quot;&gt;// This method gets called by the runtime. Use this method to configure the HTTP request pipeline&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Configure&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;    IApplicationBuilder app,&lt;br /&gt;    IWebHostEnvironment env&lt;br /&gt;  &lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;    &lt;span class=&quot;hljs-comment&quot;&gt;// Configure the HTTP request pipeline.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (!env.IsDevelopment())&lt;br /&gt;    {&lt;br /&gt;      app.UseExceptionHandler(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;/Home/Error&amp;quot;&lt;/span&gt;);&lt;br /&gt;      &lt;span class=&quot;hljs-comment&quot;&gt;// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.&lt;/span&gt;&lt;br /&gt;      app.UseHsts();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    app.UseHttpsRedirection();&lt;br /&gt;    app.UseStaticFiles();&lt;br /&gt;&lt;br /&gt;    app.UseRouting();&lt;br /&gt;&lt;br /&gt;    app.UseAuthorization();&lt;br /&gt;&lt;br /&gt;    app.UseEndpoints(&lt;br /&gt;      endpoints =&amp;gt;&lt;br /&gt;      {&lt;br /&gt;        endpoints.MapControllerRoute(&lt;br /&gt;          name: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;default&amp;quot;&lt;/span&gt;,&lt;br /&gt;          pattern: &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;{controller=Home}/{action=Index}/{id?}&amp;quot;&lt;/span&gt;&lt;br /&gt;        );&lt;br /&gt;      }&lt;br /&gt;    );&lt;br /&gt;  }&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The next step would be to create the first migration and seed the database:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;dotnet ef migrations add CreateIdentitySchema&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Straight forward, one might think... but no:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;More than one DbContext was found. Specify which one to use. Use the &#39;-Context&#39; parameter for PowerShell commands and the &#39;--context&#39; parameter for dotnet commands.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, let&#39;s get a list of the contexts available using &lt;code&gt;dotnet ef dbcontext list&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;dotnet ef dbcontext list&lt;br /&gt;Build started...&lt;br /&gt;Build succeeded.&lt;br /&gt;Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityDbContext&lt;br /&gt;WebAppWithIdentity.Areas.Identity.Data.IdentityDbContext&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ok, let&#39;s try to use a full qualified name then:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;dotnet ef database update --context WebAppWithIdentity.Areas.Identity.Data.IdentityDbContext&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Build started...
Build succeeded.
Unable to create an object of type &#39;IdentityDbContext&#39;. For the different patterns supported at design time, see &lt;a href=&quot;https://go.microsoft.com/fwlink/?linkid=851728&quot;&gt;https://go.microsoft.com/fwlink/?linkid=851728&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That&#39;s not funny, tbh.&lt;/p&gt;
&lt;p&gt;Well, let&#39;s go back to scaffolding and try this one (still with &lt;code&gt;Startup.cs&lt;/code&gt; in use):&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;dotnet aspnet-codegenerator identity -dc SecurityDbContext -u AppUser -f -gl -dbProvider sqlite&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Result:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;Building project ...&lt;br /&gt;Finding the generator &amp;#x27;identity&amp;#x27;...&lt;br /&gt;Running the generator &amp;#x27;identity&amp;#x27;...&lt;br /&gt;RunTime 00:00:17.25&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Umm... no let&#39;s list the contexts again:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;dotnet ef dbcontext list&lt;br /&gt;Build started...&lt;br /&gt;Build succeeded.&lt;br /&gt;WebAppWithIdentity.Areas.Identity.Data.SecurityDbContext&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Well, that&#39;s interesting.&lt;/p&gt;
&lt;p&gt;Now, let&#39;s try to run the migrations:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;dotnet ef migrations add CreateIdentitySchema&lt;br /&gt;Build started...&lt;br /&gt;Build succeeded.&lt;br /&gt;Done. To undo this action, use &amp;#x27;ef migrations remove&amp;#x27;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Wow!&lt;/p&gt;
&lt;p&gt;And the final step: apply the changes to the actual database:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;dotnet ef database update&lt;br /&gt;&lt;br /&gt;Build started...&lt;br /&gt;Build succeeded.&lt;br /&gt;&lt;span class=&quot;hljs-meta prompt_&quot;&gt;&lt;br /&gt;# &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;shortened the output a bit here&lt;/span&gt;&lt;br /&gt;info: Microsoft.EntityFrameworkCore.Database.Command[20101]&lt;br /&gt;      Executed DbCommand (14ms) [Parameters=[], CommandType=&amp;#x27;Text&amp;#x27;, CommandTimeout=&amp;#x27;20&amp;#x27;]&lt;br /&gt;      CREATE UNIQUE INDEX &amp;quot;UserNameIndex&amp;quot; ON &amp;quot;AspNetUsers&amp;quot; (&amp;quot;NormalizedUserName&amp;quot;);&lt;br /&gt;info: Microsoft.EntityFrameworkCore.Database.Command[20101]&lt;br /&gt;      Executed DbCommand (14ms) [Parameters=[], CommandType=&amp;#x27;Text&amp;#x27;, CommandTimeout=&amp;#x27;20&amp;#x27;]&lt;br /&gt;      INSERT INTO &amp;quot;__EFMigrationsHistory&amp;quot; (&amp;quot;MigrationId&amp;quot;, &amp;quot;ProductVersion&amp;quot;)&lt;br /&gt;      VALUES (&amp;#x27;20230318144833_CreateIdentitySchema&amp;#x27;, &amp;#x27;7.0.4&amp;#x27;);&lt;br /&gt;Done.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ok, lessons learned:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Never name your DbContent &lt;code&gt;IdentityDbContext&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Integration testing an ASP.NET Core 7 app with ASP.NET Identity using Alba</title>
    <link href="https://alexanderzeitler.com/articles/integration-testing-aspnet-core-mvc-identity-app-using-alba/"/>
    <updated>2023-03-20T22:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/integration-testing-aspnet-core-mvc-identity-app-using-alba/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Integration testing an ASP.NET Core 7 app with ASP.NET Identity using Alba&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Integration testing an ASP.NET Core 7 app with ASP.NET Identity using Alba&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/integration-testing-aspnet-core-mvc-identity-app-using-alba/jeswin-thomas-dfRrpfYD8Iw-unsplash.jpg&quot; /&gt;
&lt;p&gt;&lt;small&gt;&lt;small&gt;
Photo by &lt;a href=&quot;https://unsplash.com/@jeswinthomas?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Jeswin Thomas&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/dfRrpfYD8Iw?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;
&lt;/small&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/integration-testing-aspnet-core-mvc-identity-app-using-alba/jeswin-thomas-dfRrpfYD8Iw-unsplash.jpg&quot; alt=&quot;Wiring things up&quot; /&gt;&lt;/p&gt;
&lt;p&gt;As you might have noticed on Twitter that I started to evaluate &lt;a href=&quot;http://wolverinefx.net/&quot;&gt;Wolverine&lt;/a&gt; (formerly known as &amp;quot;JasperFx&amp;quot;) to become the Message Bus for my SaaS apps (e.g. kliento).&lt;/p&gt;
&lt;p&gt;One of the benefits of Wolverine: it&#39;s part of the &amp;quot;Critter Stack for .NET&amp;quot;.&lt;/p&gt;
&lt;p&gt;If you want to understand what that means, I recommend &lt;a href=&quot;https://jeremydmiller.com/2023/01/05/automating-integration-tests-using-the-critter-stack/&quot;&gt;this blog post&lt;/a&gt; by Jeremy D. Miller.&lt;/p&gt;
&lt;p&gt;Another thing you might know about me (from &lt;a href=&quot;https://alexanderzeitler.com/articles/htmx-razor-fragment-single-view-approach/&quot;&gt;here&lt;/a&gt;, &lt;a href=&quot;https://alexanderzeitler.com/articles/aspnet-razor-pages-fragment-single-view-approach/&quot;&gt;here&lt;/a&gt; or &lt;a href=&quot;https://alexanderzeitler.com/articles/serializing-dotnet-csharp-object-for-use-with-alpine-x-data-attribute/&quot;&gt;here&lt;/a&gt;), is that I&#39;m using &lt;a href=&quot;https://htmx.org/&quot;&gt;HTMX&lt;/a&gt; instead of SPA libraries like React.&lt;/p&gt;
&lt;p&gt;This leads to the fact I&#39;m using ASP.NET Identity for user management.&lt;/p&gt;
&lt;p&gt;Now I wanted to refactor an ASP.NET Core 7 app with ASP.NET Identity to use Wolverine and being integration tested using Alba.&lt;/p&gt;
&lt;p&gt;In a first step, I started to refactor my tests to Alba, so I could integration test async operations as described in the post by Jeremy and be prepared for a second refactoring to Wolverine.&lt;/p&gt;
&lt;p&gt;And that&#39;s where things got interesting (read: went south).&lt;/p&gt;
&lt;p&gt;The easiest way to get a test set up using Alba is possibly &lt;a href=&quot;https://jasperfx.github.io/alba/guide/xunit.html&quot;&gt;this&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;[&lt;span class=&quot;hljs-meta&quot;&gt;Fact&lt;/span&gt;]&lt;br /&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; Task &lt;span class=&quot;hljs-title&quot;&gt;should_say_hello_world&lt;/span&gt;()&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;    &lt;span class=&quot;hljs-comment&quot;&gt;// Alba will automatically manage the lifetime of the underlying host&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; host = &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; AlbaHost.For&amp;lt;&lt;span class=&quot;hljs-keyword&quot;&gt;global&lt;/span&gt;::Program&amp;gt;();&lt;br /&gt;    &lt;br /&gt;    &lt;span class=&quot;hljs-comment&quot;&gt;// This runs an HTTP request and makes an assertion&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-comment&quot;&gt;// about the expected content of the response&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; host.Scenario(_ =&amp;gt;&lt;br /&gt;    {&lt;br /&gt;        _.Get.Url(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;/&amp;quot;&lt;/span&gt;);&lt;br /&gt;        _.ContentShouldBe(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Hello World!&amp;quot;&lt;/span&gt;);&lt;br /&gt;        _.StatusCodeShouldBeOk();&lt;br /&gt;    });&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That way the application under test will be bootstrapped in the exact same way like your production app.&lt;/p&gt;
&lt;p&gt;Now you might want to run some tests in parallel using different databases (as you might have guessed, I&#39;m using &lt;a href=&quot;https://martendb.io/&quot;&gt;Marten&lt;/a&gt;) with dynamic connection strings being generated during test setup (note to self: this could be another blog post).&lt;/p&gt;
&lt;p&gt;This means you need to have the same middleware pipeline etc. but with different parameters.&lt;/p&gt;
&lt;p&gt;This all works pretty nice using Marten and other stuff but when it came to modifying the configuration for ASP.NET Identity at runtime or to add it with different configuration settings errors like these happened:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;InvalidOperationException: Scheme already exists: Identity.Application Microsoft.AspNetCore.Authentication.AuthenticationOptions.AddScheme(string name, Action configureBuilder)...&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, I was in need of a different way to bootstrap my application under test - and in the end, the whole application (confirming my opinion that real world applications won&#39;t use the minimal APIs bootstrapping approach 🫢).&lt;/p&gt;
&lt;p&gt;Lucky me, the Alba &amp;quot;&lt;a href=&quot;https://jasperfx.github.io/alba/guide/gettingstarted.html&quot;&gt;Getting started&lt;/a&gt;&amp;quot; guide mentioned &lt;a href=&quot;https://andrewlock.net/exploring-dotnet-6-part-2-comparing-webapplicationbuilder-to-the-generic-host/&quot;&gt;this blog post&lt;/a&gt; by Andrew Lock, in which he describes the evolution of ASP.NET Core application bootstrapping since the beginning of ASP.NET Core (if you follow all linked posts - you better grab some coffee before you start reading).&lt;/p&gt;
&lt;p&gt;The solution I came up with is this - we&#39;ll start with a test asserting that a redirect to the login page happens when trying to access the application as an anonymous user:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;When_calling_app_as_anonymous_user&lt;/span&gt; : &lt;span class=&quot;hljs-title&quot;&gt;IAsyncLifetime&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;private&lt;/span&gt; AlbaHost? _host;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; Task &lt;span class=&quot;hljs-title&quot;&gt;InitializeAsync&lt;/span&gt;()&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; testServices = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; TestServices();&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; testConfiguration = &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; testServices.GetTestConfigurationRoot();&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; hostBuilder = ConfigureHost.GetHostBuilder(testConfiguration);&lt;br /&gt;    _host = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; AlbaHost(hostBuilder);&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; _host.MigrateIdentityDatabase();&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  [&lt;span class=&quot;hljs-meta&quot;&gt;Fact&lt;/span&gt;]&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; Task &lt;span class=&quot;hljs-title&quot;&gt;should_redirect_to_login&lt;/span&gt;()&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; _host?.Scenario(&lt;br /&gt;      _ =&amp;gt;&lt;br /&gt;      {&lt;br /&gt;        _.Get.Url(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;/&amp;quot;&lt;/span&gt;);&lt;br /&gt;        _.Header(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Location&amp;quot;&lt;/span&gt;)&lt;br /&gt;          .ShouldHaveValues(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;http://localhost/Identity/Account/Login?ReturnUrl=%2F&amp;quot;&lt;/span&gt;);&lt;br /&gt;        _.StatusCodeShouldBe(&lt;span class=&quot;hljs-number&quot;&gt;302&lt;/span&gt;);&lt;br /&gt;      }&lt;br /&gt;    );&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; Task &lt;span class=&quot;hljs-title&quot;&gt;DisposeAsync&lt;/span&gt;()&lt;/span&gt; =&amp;gt; &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; _host.DisposeAsync();&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;TestServices&lt;/code&gt; does a ton of stuff like handling the database bootstrapping/seeding mentioned above (yes, this really requires a blog post on it&#39;s own).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;GetTestConfigurationRoot()&lt;/code&gt;, as it&#39;s name suggests builds an in-memory configuration for things like databases, including the ASP.NET Identity database (each test has it&#39;s own test event store and identity database).&lt;/p&gt;
&lt;p&gt;The key thing for this post however is &lt;code&gt;ConfigureHost.GetHostBuilder(testConfiguration)&lt;/code&gt; which returns an &lt;code&gt;IHostBuilder&lt;/code&gt; instance which Alba is happily using to create an &lt;code&gt;IAlbaHost&lt;/code&gt; instance from:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;ConfigureHost&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;static&lt;/span&gt; IHostBuilder &lt;span class=&quot;hljs-title&quot;&gt;GetHostBuilder&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;    IConfigurationRoot configuration&lt;br /&gt;  &lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; hostBuilder = Host.CreateDefaultBuilder();&lt;br /&gt;&lt;br /&gt;    hostBuilder.AddLogging();&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; SerilogLoggerFactory(Log.Logger)&lt;br /&gt;      .CreateLogger&amp;lt;Program&amp;gt;();&lt;br /&gt;    &lt;br /&gt;    hostBuilder.ConfigureWebHostDefaults(&lt;br /&gt;      builder =&amp;gt;&lt;br /&gt;      {&lt;br /&gt;        builder.ConfigureServices(collection =&amp;gt; collection.ConfigureAppServices(configuration));&lt;br /&gt;        builder.UseConfiguration(configuration);&lt;br /&gt;        builder.Configure(&lt;br /&gt;          (&lt;br /&gt;            context,&lt;br /&gt;            app&lt;br /&gt;          ) =&amp;gt;&lt;br /&gt;          {&lt;br /&gt;            &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (!context.HostingEnvironment.IsDevelopment())&lt;br /&gt;            {&lt;br /&gt;              app.UseExceptionHandler(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;/Error&amp;quot;&lt;/span&gt;);&lt;br /&gt;              app.UseHsts();&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            app.UseStaticFiles();&lt;br /&gt;&lt;br /&gt;            app.UseRouting();&lt;br /&gt;&lt;br /&gt;            app.UseAuthentication();&lt;br /&gt;            app.UseAuthorization();&lt;br /&gt;&lt;br /&gt;            app.UseEndpoints(&lt;br /&gt;              endpoints =&amp;gt;&lt;br /&gt;              {&lt;br /&gt;                endpoints.MapControllers();&lt;br /&gt;                endpoints.MapRazorPages();&lt;br /&gt;                endpoints.MapDefaultControllerRoute()&lt;br /&gt;                  .RequireAuthorization();&lt;br /&gt;              }&lt;br /&gt;            );&lt;br /&gt;          }&lt;br /&gt;        );&lt;br /&gt;      }&lt;br /&gt;    );&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; hostBuilder;&lt;br /&gt;  }&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates an ASP.NET Host with MVC, Controllers and Identity set up.&lt;/p&gt;
&lt;p&gt;The magic happens in &lt;code&gt;collection.ConfigureAppServices(configuration))&lt;/code&gt; - I removed the event store and other configuration stuff for brevity:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;ConfigureServices&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;static&lt;/span&gt; IServiceCollection &lt;span class=&quot;hljs-title&quot;&gt;ConfigureAppServices&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;this&lt;/span&gt; IServiceCollection services,&lt;br /&gt;    IConfigurationRoot configuration&lt;br /&gt;  &lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; identityTestConnectionString = configuration.GetConnectionString(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Identity&amp;quot;&lt;/span&gt;);&lt;br /&gt;    services.AddDbContext&amp;lt;IdentityDbContext&amp;gt;(&lt;br /&gt;      options =&amp;gt;&lt;br /&gt;        options.UseNpgsql(identityTestConnectionString)&lt;br /&gt;    );&lt;br /&gt;&lt;br /&gt;    services.AddDefaultIdentity&amp;lt;AppUser&amp;gt;(options =&amp;gt; options.SignIn.RequireConfirmedAccount = &lt;span class=&quot;hljs-literal&quot;&gt;false&lt;/span&gt;)&lt;br /&gt;      .AddEntityFrameworkStores&amp;lt;IdentityDbContext&amp;gt;();&lt;br /&gt;&lt;br /&gt;    services.AddControllersWithViews()&lt;br /&gt;      .AddRazorRuntimeCompilation();&lt;br /&gt;    services.Configure&amp;lt;RazorViewEngineOptions&amp;gt;(&lt;br /&gt;      o =&amp;gt; o.ViewLocationExpanders.Add(&lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; FeatureFolderLocationExpander())&lt;br /&gt;    );&lt;br /&gt;    services.AddMvc();&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; services;&lt;br /&gt;  }&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That way I can spin up concurrent async integration tests (the async part has to be validated when Wolverine is added to the game).&lt;/p&gt;
&lt;p&gt;What&#39;s left is the application itself to be bootstrapped in the exact same way but with different configuration:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; configuration = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; ConfigurationBuilder()&lt;br /&gt;  .AddEnvironmentVariables()&lt;br /&gt;  .AddCommandLine(&lt;span class=&quot;hljs-keyword&quot;&gt;args&lt;/span&gt;)&lt;br /&gt;  .AddJsonFile(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;appsettings.json&amp;quot;&lt;/span&gt;)&lt;br /&gt;  .AddJsonFile(&lt;span class=&quot;hljs-string&quot;&gt;$&amp;quot;appsettings.&lt;span class=&quot;hljs-subst&quot;&gt;{Environment.GetEnvironmentVariable(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;ASPNETCORE_ENVIRONMENT&amp;quot;&lt;/span&gt;)}&lt;/span&gt;.json&amp;quot;&lt;/span&gt;, optional: &lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;)&lt;br /&gt;  .Build();&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; builder = ConfigureHost.GetHostBuilder(configuration);&lt;br /&gt;builder&lt;br /&gt;  .Build()&lt;br /&gt;  .Run();&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Well, that&#39;s it and works fine so far.&lt;/p&gt;
&lt;p&gt;Maybe there&#39;s an easier / better solution - if so, please let me know!&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>I&#39;m printing out websites - you might want to do it, too</title>
    <link href="https://alexanderzeitler.com/articles/printing-out-websites/"/>
    <updated>2023-04-01T20:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/printing-out-websites/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;I&#39;m printing out websites - you might want to do it, too&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;I&#39;m printing out websites - ou might want to do it, too&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/printing-out-websites/mufid-majnun-UdDCjj0rR7c-unsplash.jpg&quot; /&gt;
&lt;p&gt;&lt;small&gt;&lt;small&gt;
Photo by &lt;a href=&quot;https://unsplash.com/@mufidpwt?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Mufid Majnun&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/UdDCjj0rR7c?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;
&lt;/small&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/printing-out-websites/mufid-majnun-UdDCjj0rR7c-unsplash.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I&#39;m printing out websites on purpose.&lt;/p&gt;
&lt;p&gt;This may sound like an April Fool&#39;s joke, but I&#39;m serious.&lt;/p&gt;
&lt;p&gt;But I don&#39;t print them on paper, I print them as PDF files for the following reasons:&lt;/p&gt;
&lt;p&gt;I&#39;m using &lt;a href=&quot;https://logseq.com/&quot;&gt;Logseq&lt;/a&gt; as a &amp;quot;second brain&amp;quot;.&lt;/p&gt;
&lt;p&gt;Logseq comes with a ton of useful features and one of them are PDF annotations.&lt;/p&gt;
&lt;p&gt;Here&#39;s the &lt;a href=&quot;https://blog.logseq.com/newsletter-14-a-better-pdf-reader-sync-whiteboards-and-new-community-creations/&quot;&gt;official video demoing&lt;/a&gt; the feature:&lt;/p&gt;
&lt;div style=&quot;position: relative; padding-bottom: 62.5%; height: 0;&quot;&gt;&lt;iframe src=&quot;https://www.loom.com/embed/fa7ad4bb1dcd4d21b8f76e3e03c19d7f&quot; frameborder=&quot;0&quot; webkitallowfullscreen=&quot;&quot; mozallowfullscreen=&quot;&quot; allowfullscreen=&quot;&quot; style=&quot;position: absolute; top: 0; left: 0; width: 100%; height: 100%;&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;
&lt;p&gt;Logseq to me by far is the most comfortable way to annotate websites across multiple computers and integrate them with my existing knowledge graph - if you print them to PDF and import them to Logseq.&lt;/p&gt;
&lt;p&gt;Another win of this method: I won&#39;t loose the information if the website with the post goes dark at some point in time...&lt;/p&gt;
&lt;h3&gt;The workflow&lt;/h3&gt;
&lt;p&gt;Open an arbitrary blog post or article you want to annotate:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/printing-out-websites/website.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Next, I&#39;m activating Firefox Reader mode:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/printing-out-websites/website-reader-mode.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Then, I&#39;m calling the Print dialog and disable printing headers and footers:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/printing-out-websites/website-print.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;And finally, I can just drag and drop the PDF file into my Logseq page created for this post and start highlighting and annotating.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/printing-out-websites/logseq-pdf-annotation.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;How do you annotate and highlight websites?&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Exporting and importing Live Templates in JetBrains Rider</title>
    <link href="https://alexanderzeitler.com/articles/exporting-and-importing-jetbrains-rider-livetemplates/"/>
    <updated>2023-04-02T22:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/exporting-and-importing-jetbrains-rider-livetemplates/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Exporting and importing Live Templates in JetBrains Rider&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Exporting and importing Live Templates in JetBrains Rider&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/exporting-and-importing-jetbrains-rider-livetemplates/cmanage-layers.png&quot; /&gt;
&lt;p&gt;I love JetBrains Rider and I&#39;m using it on every Platform: Xubuntu, macOS and Windows. That&#39;s great a great experience but sharing Live Templates between the platforms is a bit tricky.&lt;/p&gt;
&lt;p&gt;Things might be easier if you&#39;re using &lt;a href=&quot;https://www.jetbrains.com/help/rider/Sharing_Your_IDE_Settings.html#IDE_settings_sync&quot;&gt;JetBrains sync&lt;/a&gt; but I&#39;m using the &lt;a href=&quot;https://www.jetbrains.com/help/rider/Sharing_Your_IDE_Settings.html#settings-repository&quot;&gt;repository sync for ages now...&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The obvious option might be to use the &amp;quot;Save&amp;quot; button in the &amp;quot;Live Template&amp;quot; settings. However, if this would work, I wouldn&#39;t write this post.&lt;/p&gt;
&lt;p&gt;The aforementioned option only export none .NET live templates.&lt;/p&gt;
&lt;p&gt;So how do we do it for C# (and F# I guess)?&lt;/p&gt;
&lt;h3&gt;Exporting&lt;/h3&gt;
&lt;p&gt;First, open the &amp;quot;Manage Layers&amp;quot; dialog - I&#39;m doing this via the Actions Panel (double shift keyboard shortcut). Then right click &amp;quot;This computer&amp;quot; (or where ever you want to export the live templates from) and select &amp;quot;Export to File...&amp;quot;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/exporting-and-importing-jetbrains-rider-livetemplates/manage-layers.png&quot; alt=&quot;Manage Layers dialog&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Then tick the checkbox &amp;quot;Live Templates&amp;quot;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/exporting-and-importing-jetbrains-rider-livetemplates/export-to-file.png&quot; alt=&quot;Exporting the Live Templates&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Next, assign a file name and you&#39;re done with exporting.&lt;/p&gt;
&lt;h3&gt;Importing&lt;/h3&gt;
&lt;p&gt;Bring up the &amp;quot;Manage Layers&amp;quot; dialog again and select &amp;quot;Import from&amp;quot; ➡️  &amp;quot;Import from File...&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/exporting-and-importing-jetbrains-rider-livetemplates/import-from-file.png&quot; alt=&quot;Select import from File&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Select a file containing exported Live Templates:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/exporting-and-importing-jetbrains-rider-livetemplates/select-import-file.png&quot; alt=&quot;Select file to import&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Tick the &amp;quot;LiveTemplates&amp;quot; checkbox&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/exporting-and-importing-jetbrains-rider-livetemplates/select-live-templates-to-import.png&quot; alt=&quot;Tick the Live Templates checkbox&quot; /&gt;&lt;/p&gt;
&lt;p&gt;And we&#39;re done 💥️&lt;/p&gt;
&lt;p&gt;Just double check the templates have been imported:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/exporting-and-importing-jetbrains-rider-livetemplates/double-check-live-templates.png&quot; alt=&quot;double check the templates have been imported&quot; /&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Inspecting ASP.NET integration testing logs made easy with JetBrains Rider</title>
    <link href="https://alexanderzeitler.com/articles/inspecting-aspnet-dotnet-integration-testing-logs-jetbrains-rider-grep-console/"/>
    <updated>2023-04-05T21:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/inspecting-aspnet-dotnet-integration-testing-logs-jetbrains-rider-grep-console/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Inspecting ASP.NET integration testing logs made easy with JetBrains Rider&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Inspecting ASP.NET integration testing logs made easy with JetBrains Rider&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/inspecting-aspnet-dotnet-integration-testing-logs-jetbrains-rider-grep-console/nathan-bingle-RB5ADoT-DZk-unsplash.jpg&quot; /&gt;
&lt;p&gt;&lt;small&gt;&lt;small&gt;
Photo by &lt;a href=&quot;https://unsplash.com/@nathangbingle?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Nathan Bingle&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/RB5ADoT-DZk?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;
&lt;/small&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/inspecting-aspnet-dotnet-integration-testing-logs-jetbrains-rider-grep-console/nathan-bingle-RB5ADoT-DZk-unsplash.jpg&quot; alt=&quot;Trying to spot something meaningful&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The other day I was integration testing a Saga in the kliento code base. The tests failed and I wanted to skim through the logs to find something meaningful.&lt;/p&gt;
&lt;p&gt;Scrolling through thousands of lines of log in a monochrome style is no fun... (to me this approach sometimes is easier than debugging anyway).&lt;/p&gt;
&lt;p&gt;First things first: how do I get this output?&lt;/p&gt;
&lt;p&gt;When spinning up my tests in &lt;a href=&quot;https://alexanderzeitler.com/articles/integration-testing-aspnet-core-mvc-identity-app-using-alba/&quot;&gt;Alba&lt;/a&gt;, I&#39;m registering a XUnit sink for Serilog, so I get my logs redirected to the test output window in Rider:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;When_recording_a_lead_inquiry_from_email&lt;/span&gt; : &lt;span class=&quot;hljs-title&quot;&gt;IAsyncLifetime&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;When_recording_a_lead_inquiry_from_email&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;    ITestOutputHelper testOutputHelper&lt;br /&gt;  &lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;    _testOutputHelper = testOutputHelper;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; Task &lt;span class=&quot;hljs-title&quot;&gt;InitializeAsync&lt;/span&gt;()&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; testServices = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; TestServices();&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; configuration = &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; testServices.GetTestConfigurationRoot();&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; serilogLogger = Log.Logger = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; LoggerConfiguration()&lt;br /&gt;      .MinimumLevel.Debug()&lt;br /&gt;      .WriteTo.Console()&lt;br /&gt;      .WriteTo.TestOutput(_testOutputHelper)&lt;br /&gt;      .CreateLogger();&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; dotnetILogger = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; SerilogLoggerFactory(serilogLogger)&lt;br /&gt;      .CreateLogger&amp;lt;Program&amp;gt;();&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; builder = ConfigureHost.GetHostBuilder(&lt;br /&gt;      configuration,&lt;br /&gt;      services =&amp;gt;&lt;br /&gt;      {&lt;br /&gt;        services.AddSingleton&amp;lt;ILogger&amp;gt;(dotnetILogger);&lt;br /&gt;      });&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-comment&quot;&gt;// arrange act assert...&lt;/span&gt;&lt;br /&gt;  }&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;My test output looks like this then:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/inspecting-aspnet-dotnet-integration-testing-logs-jetbrains-rider-grep-console/tail-black-white.png&quot; alt=&quot;The joy of black text on white background&quot; /&gt;&lt;/p&gt;
&lt;p&gt;While scrolling through the look I was thinking &amp;quot;would be nice if there would be some color coding for the log in Rider...&amp;quot;.&lt;/p&gt;
&lt;p&gt;And the next step was to spin up a search which eventually ended up on the landing page for &amp;quot;&lt;a href=&quot;https://plugins.jetbrains.com/plugin/7125-grep-console&quot;&gt;GrepConsole&lt;/a&gt;&amp;quot;, a plugin for IntelliJ based IDEs like Rider which does exactly what I was looking for: colorize my test output logs.&lt;/p&gt;
&lt;p&gt;After playing around with some settings, my logs now look like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/inspecting-aspnet-dotnet-integration-testing-logs-jetbrains-rider-grep-console/tail-grep-console.png&quot; alt=&quot;much better, isn&#39;t it?&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Much easier to spot exceptions, isn&#39;t it?&lt;/p&gt;
&lt;p&gt;What&#39;s even better: you can add arbitrary expressions to filter and colorize fragments or lines.&lt;/p&gt;
&lt;p&gt;So I set up a section for regular .NET log output colors and I added another one for &lt;a href=&quot;http://wolverinefx.net/&quot;&gt;Wolverine&lt;/a&gt; status messages. That way I can easily spot successful or failed messages.&lt;/p&gt;
&lt;p&gt;That&#39;s my GrepConsole config so far:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/inspecting-aspnet-dotnet-integration-testing-logs-jetbrains-rider-grep-console/grep-console-settings.png&quot; alt=&quot;GrepConsole settings&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Right now, there&#39;s only one downside: I didn&#39;t find a way in Rider to export/import these settings although this seems to work in other JetBrains IDEs.&lt;/p&gt;
&lt;h3&gt;Update&lt;/h3&gt;
&lt;p&gt;Looks like the settings get synced using repository sync 🎉️&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>How to stop JetBrains Rider asking to add files to Git version control</title>
    <link href="https://alexanderzeitler.com/articles/how-to-stop-jetbrains-rider-stop-asking-add-files-to-version-control-git/"/>
    <updated>2023-04-07T21:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/how-to-stop-jetbrains-rider-stop-asking-add-files-to-version-control-git/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;How to stop JetBrains Rider asking to add files to Git version control&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;How to stop JetBrains Rider asking to add files to Git version control&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/how-to-stop-jetbrains-rider-stop-asking-add-files-to-version-control-git/dim-hou-BjD3KhnTIkg-unsplash.jpg&quot; /&gt;
&lt;meta property=&quot;og:title&quot; content=&quot;How to stop JetBrains Rider asking to add files to Git version control&quot; /&gt;
&lt;meta property=&quot;og:url&quot; content=&quot;https://alexanderzeitler.com/articles/how-to-stop-jetbrains-rider-stop-asking-add-files-to-version-control-git/&quot; /&gt;
&lt;meta property=&quot;og:image&quot; content=&quot;https://alexanderzeitler.com/articles/how-to-stop-jetbrains-rider-stop-asking-add-files-to-version-control-git/dim-hou-BjD3KhnTIkg-unsplash.jpg&quot; /&gt;
&lt;p&gt;&lt;small&gt;&lt;small&gt;
Photo by &lt;a href=&quot;https://unsplash.com/@dimhou?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Dim Hou&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/BjD3KhnTIkg?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;
&lt;/small&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/how-to-stop-jetbrains-rider-stop-asking-add-files-to-version-control-git/dim-hou-BjD3KhnTIkg-unsplash.jpg&quot; alt=&quot;Stop adding my files to git, please&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Whenever I&#39;ve created a new project in Rider and added some files to the project, Rider was asking to add these file to Git / Version control:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/how-to-stop-jetbrains-rider-stop-asking-add-files-to-version-control-git/rider-asking-too-many-questions.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Ticking &amp;quot;Don&#39;t ask again&amp;quot; only helps for this project, but of course there must be a global setting.&lt;/p&gt;
&lt;p&gt;Indeed, a setting can be found in &lt;code&gt;File | Settings | Version Control | Confirmation&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/how-to-stop-jetbrains-rider-stop-asking-add-files-to-version-control-git/rider-dont-ask-again.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;So let&#39;s create a new project and add a file, to see if this works...&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/how-to-stop-jetbrains-rider-stop-asking-add-files-to-version-control-git/rider-asking-too-many-questions.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Well, that didn&#39;t work... because there&#39;s another setting in &lt;code&gt;File | New Projects Setup | Settings for New Projects...&lt;/code&gt; (you can also use actions modal) which looks absolutely the same but it is for &lt;em&gt;new&lt;/em&gt; projects:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/how-to-stop-jetbrains-rider-stop-asking-add-files-to-version-control-git/rider-settings-for-new-projects.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/how-to-stop-jetbrains-rider-stop-asking-add-files-to-version-control-git/rider-dont-ask-again-actually.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If you change &amp;quot;When files are created&amp;quot; to &amp;quot;Do not add&amp;quot;, you won&#39;t be asked again, I&#39;ll promise 🤞️.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Annotating and highlighting epub using Logseq</title>
    <link href="https://alexanderzeitler.com/articles/annotating-highlighting-epub-with-logseq-calibre/"/>
    <updated>2023-04-10T15:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/annotating-highlighting-epub-with-logseq-calibre/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Annotating and highlighting epub using Logseq&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Annotating and highlighting epub using Logseq&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/annotating-highlighting-epub-with-logseq-calibre/kelly-sikkema-YnRNdB-XTME-unsplash.jpg&quot; /&gt;
&lt;meta property=&quot;og:title&quot; content=&quot;Annotating and highlighting epub using Logseq&quot; /&gt;
&lt;meta property=&quot;og:url&quot; content=&quot;https://alexanderzeitler.com/articles/annotating-highlighting-epub-with-logseq-calibre/&quot; /&gt;
&lt;meta property=&quot;og:image&quot; content=&quot;https://alexanderzeitler.com/articles/annotating-highlighting-epub-with-logseq-calibre/kelly-sikkema-YnRNdB-XTME-unsplash.jpg&quot; /&gt;
&lt;p&gt;&lt;small&gt;&lt;small&gt;
Photo by &lt;a href=&quot;https://unsplash.com/@kellysikkema?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Kelly Sikkema&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/book-note?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;
&lt;/small&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/annotating-highlighting-epub-with-logseq-calibre/kelly-sikkema-YnRNdB-XTME-unsplash.jpg&quot; alt=&quot;Taking notes from a book&quot; /&gt;&lt;/p&gt;
&lt;p&gt;When possible, I&#39;m trying to read books in epub format or as printed books. Most of my notes and journals live in Logseq.&lt;/p&gt;
&lt;p&gt;So I was asking myself: &amp;quot;How could I bring my ebook highlights and notes to Logseq?&amp;quot;&lt;/p&gt;
&lt;p&gt;While it isn&#39;t possible right now to load epub files into Logseq like you can do with &lt;a href=&quot;https://alexanderzeitler.com/articles/printing-out-websites&quot;&gt;PDF files&lt;/a&gt;, it is possible to reference your &lt;a href=&quot;https://calibre-ebook.com/&quot;&gt;calibre&lt;/a&gt; notes and highlights - here&#39;s how:&lt;/p&gt;
&lt;p&gt;First, highlight a section in a book and maybe also jot down some notes in a book using calibre:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/annotating-highlighting-epub-with-logseq-calibre/calibre-book-highlight.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Next, click the &amp;quot;Export&amp;quot; button at the bottom right and select &amp;quot;Markdown&amp;quot;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/annotating-highlighting-epub-with-logseq-calibre/book-highlights-export-settings.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now, click the &amp;quot;Copy to clipboard&amp;quot; button and paste the clipboard into your Logseq page:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/annotating-highlighting-epub-with-logseq-calibre/logseq-book-highlight-notes.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;As you can see, the highlighted section and the notes taken are now in your Logseq page for the book.&lt;/p&gt;
&lt;p&gt;Did you spot the link &lt;code&gt;10.04.23 17:19&lt;/code&gt;? That&#39;s a deep link to your notes in calibre.&lt;/p&gt;
&lt;p&gt;If you click the link, the book will be opened in calibre at the highlighted section. Neat, isn&#39;t it?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/annotating-highlighting-epub-with-logseq-calibre/calibre-book-highlight-deeplink.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Of course, you could also &lt;a href=&quot;https://manual.calibre-ebook.com/en/conversion.html&quot;&gt;convert the whole book to PDF&lt;/a&gt; using calibre and use the PDF annotation workflow &lt;a href=&quot;https://alexanderzeitler.com/articles/printing-out-websites&quot;&gt;I described recently&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Another option could also be to just automate the whole stuff by reading the &lt;code&gt;metadata.opf&lt;/code&gt; calibre file and push it to your Logseq markdown files...&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Learning Elixir</title>
    <link href="https://alexanderzeitler.com/articles/learning-elixir/"/>
    <updated>2023-04-10T20:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/learning-elixir/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Learning Elixir&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Learning Elixir&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/learning-elixir/2157081.png&quot; /&gt;
&lt;meta property=&quot;og:title&quot; content=&quot;Learning Elixir&quot; /&gt;
&lt;meta property=&quot;og:url&quot; content=&quot;https://alexanderzeitler.com/articles/learning-elixir/&quot; /&gt;
&lt;meta property=&quot;og:image&quot; content=&quot;https://alexanderzeitler.com/articles/learning-elixir/2157081.png&quot; /&gt;
&lt;p&gt;&lt;small&gt;&lt;small&gt;
Photo by  Vlad Shagov on &lt;a href=&quot;https://gitu.net/en/details/elixir-logo-icon-freebie&quot;&gt;Gitu&lt;/a&gt;
&lt;/small&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/learning-elixir/2157081.png&quot; alt=&quot;Elixir artwork&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I&#39;ve decided to learn &lt;a href=&quot;https://elixir-lang.org/&quot;&gt;Elixir&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Not just poking a bit into the language but to be able to build full stack Elixir distributed applications and eventually SaaS.&lt;/p&gt;
&lt;p&gt;I don&#39;t want to talk much about the why for now but you can find a hint or two on my social media channels.&lt;/p&gt;
&lt;p&gt;This post aims to summarize the learning materials I&#39;ve used so far and the building blocks I intend to learn about and use and I would be more than happy about your feedback and preferences here.&lt;/p&gt;
&lt;p&gt;I&#39;ll categorize stuff a bit - here we go:&lt;/p&gt;
&lt;h3&gt;Language / General&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://elixir-lang.org/&quot;&gt;Elixir official website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=gRQIPvDFuts&quot;&gt;Elixir &amp;amp; Phoenix Fundamentals Full Course For Beginners&lt;/a&gt; on YouTube. Almost 6 hours non-stop Elixir and Phoenix learnings for free.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://elixircasts.io/&quot;&gt;ElixirCasts&lt;/a&gt;  [2023/04/17] - not everything is free but it&#39;s worth the money if you&#39;re serious abouit Elixir / Phoenix / OTP and GenServer&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://elixirschool.com/&quot;&gt;Elixir School&lt;/a&gt; - multilingual website packed with Elixir topics from basic to pro covering OTP, Testing, Data Processing and Storage&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://elixirforum.com/&quot;&gt;Elixir Forum&lt;/a&gt; - Discourse community for Elixir&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://discord.gg/elixir&quot;&gt;Elixir Discord&lt;/a&gt; - Elixir community on Discord&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://exercism.org/tracks/elixir/&quot;&gt;Elixir Track on Exercism&lt;/a&gt; [2023/04/15 via Fernando Canizo] - Free Elixir exercises&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/elixir-lang/ex_doc&quot;&gt;ExDoc&lt;/a&gt; - A tool to generate documentation for your Elixir projects&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/hexpm/hex&quot;&gt;hex&lt;/a&gt; - Package manager for the Erlang VM&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://livebook.dev/&quot;&gt;Livebook&lt;/a&gt; - Share knowledge, explore code, visualize data, run machine learning models, and much more&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Database&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/elixir-ecto/ecto&quot;&gt;ecto&lt;/a&gt; - A toolkit for data mapping and language integrated query&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/elixir-ecto/postgrex&quot;&gt;postgrex&lt;/a&gt; - PostgreSQL driver for Elixir&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Web Applications&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/elixir-plug/plug&quot;&gt;Plug&lt;/a&gt; - A specification for composing web applications with functions&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/mtrudel/bandit&quot;&gt;Bandit&lt;/a&gt; - A pure Elixir HTTP server for Plug &amp;amp; WebSock applications&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.phoenixframework.org/&quot;&gt;Phoenix Framework&lt;/a&gt; - Framework written in Elixir which implements the server-side Model View Controller (MVC) pattern&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Scheduling&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/sorentwo/oban&quot;&gt;Oban&lt;/a&gt; - Robust job processing in Elixir, backed by modern PostgreSQL or SQLite3&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Event Sourcing / CQRS&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/Nebo15/sage&quot;&gt;saga&lt;/a&gt; - A dependency-free tool to run distributed transactions in Elixir, inspired by Saga pattern&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/commanded&quot;&gt;commanded&lt;/a&gt; - Event Sourcing library for Postgres, EventStoreDB and In-memory&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Messaging&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://elixir-broadway.org/&quot;&gt;Broadway&lt;/a&gt; - Build concurrent and multi-stage data ingestion and data processing pipelines with Elixir&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Testing&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://hexdocs.pm/ex_unit&quot;&gt;ExUnit&lt;/a&gt; - Unit testing framework for Elixir.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Identity / Authentication&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://powauth.com/&quot;&gt;Pow&lt;/a&gt; - A robust, modular and extendable user management solution&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Cloud&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/aws-beam/aws-elixir&quot;&gt;aws-elixir&lt;/a&gt; - AWS clients for Elixir&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  
  <entry>
    <title>Elixir syntax highlighting for Logseq</title>
    <link href="https://alexanderzeitler.com/articles/logseq-code-syntax-highlighting-elixir/"/>
    <updated>2023-04-15T20:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/logseq-code-syntax-highlighting-elixir/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Elixir syntax highlighting for Logseq&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Elixir syntax highlighting for Logseq&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/logseq-code-syntax-highlighting-elixir/elixir-in-logseq.jpg&quot; /&gt;
&lt;meta property=&quot;og:title&quot; content=&quot;Elixir syntax highlighting for Logseq&quot; /&gt;
&lt;meta property=&quot;og:url&quot; content=&quot;https://alexanderzeitler.com/articles/logseq-code-syntax-highlighting-elixir/&quot; /&gt;
&lt;meta property=&quot;og:image&quot; content=&quot;https://alexanderzeitler.com/articles/logseq-code-syntax-highlighting-elixir/elixir-in-logseq.jpg&quot; /&gt;
&lt;p&gt;I&#39;m journaling my &lt;a href=&quot;https://alexanderzeitler.com/articles/learning-elixir&quot;&gt;Elixir learnings&lt;/a&gt; in Logseq.&lt;/p&gt;
&lt;p&gt;Until today I&#39;ve accepted the fact that LogSeq doesn&#39;t support Elixir syntax highlighting.&lt;/p&gt;
&lt;p&gt;But today I came across the &amp;quot;Extensions plus&amp;quot; plugin for Logseq, which - besides some other features - adds syntax highlighting for Elixir:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/logseq-code-syntax-highlighting-elixir/extensions-plus-plugin-logseq.png&quot; alt=&quot;Extensions Plus plugin&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/logseq-code-syntax-highlighting-elixir/elixir-in-logseq.jpg&quot; alt=&quot;Elixir syntax highlighting in Logseq&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Make sure to restart Logseq to make the plugin work.&lt;/p&gt;
&lt;p&gt;Also, make sure &amp;quot;elixir&amp;quot; is checked in the plugin settings:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/logseq-code-syntax-highlighting-elixir/plugin-settings.png&quot; alt=&quot;Plugin settings&quot; /&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Installing the latest Elixir and Erlang versions on Xubuntu 22.04</title>
    <link href="https://alexanderzeitler.com/articles/installing-latest-elixir-erlang-version-on-xubuntu-ubuntu-lts-22.04-asdf/"/>
    <updated>2023-04-16T20:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/installing-latest-elixir-erlang-version-on-xubuntu-ubuntu-lts-22.04-asdf/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Installing the latest Elixir and Erlang versions on Xubuntu 22.04&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Installing the latest Elixir and Erlang versions on Xubuntu 22.04&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/installing-latest-elixir-erlang-version-on-xubuntu-ubuntu-lts-22.04-asdf/mika-baumeister-9Qq_G14hNC8-unsplash.jpg&quot; /&gt;
&lt;meta property=&quot;og:title&quot; content=&quot;Installing the latest Elixir and Erlang versions on Xubuntu 22.04&quot; /&gt;
&lt;meta property=&quot;og:url&quot; content=&quot;https://alexanderzeitler.com/articles/installing-latest-elixir-erlang-version-on-xubuntu-ubuntu-lts-22.04-asdf/&quot; /&gt;
&lt;meta property=&quot;og:image&quot; content=&quot;https://alexanderzeitler.com/articles/installing-latest-elixir-erlang-version-on-xubuntu-ubuntu-lts-22.04-asdf/mika-baumeister-9Qq_G14hNC8-unsplash.jpg&quot; /&gt;
&lt;p&gt;&lt;small&gt;&lt;small&gt;
Photo by &lt;a href=&quot;https://unsplash.com/@mbaumi?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Mika Baumeister&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/9Qq_G14hNC8?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;
&lt;/small&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/installing-latest-elixir-erlang-version-on-xubuntu-ubuntu-lts-22.04-asdf/mika-baumeister-9Qq_G14hNC8-unsplash.jpg&quot; alt=&quot;Elixir artwork&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After &lt;a href=&quot;https://alexanderzeitler.com/articles/learning-elixir&quot;&gt;dabbling around with some Elixir courses&lt;/a&gt; I wanted to dig into the event sourcing sample named &amp;quot;&lt;a href=&quot;https://github.com/zorn/franklin&quot;&gt;franklin&lt;/a&gt;&amp;quot; by &lt;a href=&quot;https://mikezornek.com/&quot;&gt;Mike Zornek&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Mike provides a &lt;code&gt;README&lt;/code&gt;, so let&#39;s just run &lt;code&gt;mix setup&lt;/code&gt; and see what happens:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;** (Mix) You&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;re trying to run :franklin on Elixir v1.12.2 but it has declared in its mix.exs file it supports only Elixir ~&amp;gt; 1.14.0&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To be honest, I didn&#39;t care exactly about which versions of Elixir and Erlang I had installed using &lt;code&gt;apt-get install erlang elixir&lt;/code&gt; and &lt;code&gt;IEx&lt;/code&gt; experiments did work fine so far.&lt;/p&gt;
&lt;p&gt;Ok, the latest stable versions seem to be Erlang &lt;code&gt;25.3&lt;/code&gt; and Elixir &lt;code&gt;1.14.4&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Maybe &lt;code&gt;mix&lt;/code&gt; was right...&lt;/p&gt;
&lt;p&gt;After looking around at Elixir Forum, several people suggested using &lt;code&gt;asdf&lt;/code&gt; which I&#39;ve installed already (but I&#39;ll explain it from scratch anyway).&lt;/p&gt;
&lt;p&gt;So, here we go - first uninstalling the old versions:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;apt remove erlang elixir &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then install &lt;code&gt;asdf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;git &lt;span class=&quot;hljs-built_in&quot;&gt;clone&lt;/span&gt; https://github.com/asdf-vm/asdf.git ~/.asdf&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As I&#39;m using &lt;code&gt;oh-my-zsh&lt;/code&gt; / &lt;code&gt;zsh&lt;/code&gt;, I&#39;ve also enabled the &lt;code&gt;asdf&lt;/code&gt; plugin in &lt;code&gt;~/.zshrc&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;plugins=(&lt;br /&gt;  asdf&lt;br /&gt;  git&lt;br /&gt;  zsh-autosuggestions&lt;br /&gt;  zsh-syntax-highlighting&lt;br /&gt;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, I installed the &lt;code&gt;erlang&lt;/code&gt; and &lt;code&gt;elixir&lt;/code&gt; plugins for &lt;code&gt;asdf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;asdf plugin add erlang https://github.com/asdf-vm/asdf-erlang.git&lt;br /&gt;asdf plugin-add elixir https://github.com/asdf-vm/asdf-elixir.git&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, some build dependencies are required:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt install libssl-dev automake autoconf libncurses5-dev&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And finally, install Erlang:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;export&lt;/span&gt; KERL_CONFIGURE_OPTIONS=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;--disable-debug --without-javac&amp;quot;&lt;/span&gt;&lt;br /&gt;asdf install erlang 25.3&lt;br /&gt;&lt;br /&gt;asdf_25.3 is not a kerl-managed Erlang/OTP installation&lt;br /&gt;The asdf_25.3 build has been deleted&lt;br /&gt;Extracting &lt;span class=&quot;hljs-built_in&quot;&gt;source&lt;/span&gt; code&lt;br /&gt;Building Erlang/OTP 25.3 (asdf_25.3), please &lt;span class=&quot;hljs-built_in&quot;&gt;wait&lt;/span&gt;...&lt;br /&gt;APPLICATIONS DISABLED (See: /home/alexzeitler/.asdf/plugins/erlang/kerl-home/builds/asdf_25.3/otp_build_25.3.&lt;span class=&quot;hljs-built_in&quot;&gt;log&lt;/span&gt;)&lt;br /&gt;* jinterface     : Java compiler disabled by user&lt;br /&gt;* odbc           : ODBC library - &lt;span class=&quot;hljs-built_in&quot;&gt;link&lt;/span&gt; check failed&lt;br /&gt;&lt;br /&gt;APPLICATIONS INFORMATION (See: /home/alexzeitler/.asdf/plugins/erlang/kerl-home/builds/asdf_25.3/otp_build_25.3.&lt;span class=&quot;hljs-built_in&quot;&gt;log&lt;/span&gt;)&lt;br /&gt;* wx             : No OpenGL headers found, wx will NOT be usable&lt;br /&gt;* No GLU headers found, wx will NOT be usable&lt;br /&gt;* wxWidgets was not compiled with --enable-webview or wxWebView developer package is not installed, wxWebView will NOT be available&lt;br /&gt;*         wxWidgets must be installed on your system.&lt;br /&gt;*         Please check that wx-config is &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; path, the directory&lt;br /&gt;*         &lt;span class=&quot;hljs-built_in&quot;&gt;where&lt;/span&gt; wxWidgets libraries are installed (returned by&lt;br /&gt;*         &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;wx-config --libs&amp;#x27;&lt;/span&gt; or &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;wx-config --static --libs&amp;#x27;&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;command&lt;/span&gt;)&lt;br /&gt;*         is &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; LD_LIBRARY_PATH or equivalent variable and&lt;br /&gt;*         wxWidgets version is 3.0.2 or above.&lt;br /&gt;&lt;br /&gt;DOCUMENTATION INFORMATION (See: /home/alexzeitler/.asdf/plugins/erlang/kerl-home/builds/asdf_25.3/otp_build_25.3.&lt;span class=&quot;hljs-built_in&quot;&gt;log&lt;/span&gt;)&lt;br /&gt;* documentation  : &lt;br /&gt;*                  xsltproc is missing.&lt;br /&gt;*                  fop is missing.&lt;br /&gt;*                  xmllint is missing.&lt;br /&gt;*                  The documentation cannot be built.&lt;br /&gt;&lt;br /&gt;Erlang/OTP 25.3 (asdf_25.3) has been successfully built&lt;br /&gt;Cleaning up compilation products &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; &lt;br /&gt;Cleaned up compilation products &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt;  under /home/alexzeitler/.asdf/plugins/erlang/kerl-home/builds&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And Elixir:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;asdf install elixir 1.14.4-otp-25 &lt;br /&gt;==&amp;gt; Checking whether specified Elixir release exists...&lt;br /&gt;==&amp;gt; Downloading 1.14.4-otp-25 to /home/alexzeitler/.asdf/downloads/elixir/1.14.4-otp-25/elixir-precompiled-1.14.4-otp-25.zip&lt;br /&gt;% Total    % Received % Xferd  Average Speed   Time    Time     Time  Current&lt;br /&gt;Dload  Upload   Total   Spent    Left  Speed&lt;br /&gt;100 6516k  100 6516k    0     0  12.2M      0 --:--:-- --:--:-- --:--:-- 12.2M&lt;br /&gt;==&amp;gt; Copying release into place&lt;br /&gt;&lt;br /&gt;franklin on  main [?] is 📦 v0.1.0 via 💧 v1.14.4 (OTP 25) &lt;br /&gt;❯ elixir --version                                               &lt;br /&gt;Erlang/OTP 25 [erts-13.2] [&lt;span class=&quot;hljs-built_in&quot;&gt;source&lt;/span&gt;] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]&lt;br /&gt;&lt;br /&gt;Elixir 1.14.4 (compiled with Erlang/OTP 25)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Set the default versions:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;asdf global elixir 1.14.4-otp-25&lt;br /&gt;version 1.14.4 is not installed &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; elixir&lt;br /&gt;&lt;br /&gt;asdf global erlang 25.3&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Result:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Erlang/OTP 25 [erts-13.2] [&lt;span class=&quot;hljs-built_in&quot;&gt;source&lt;/span&gt;] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]&lt;br /&gt;&lt;br /&gt;Elixir 1.14.4 (compiled with Erlang/OTP 25)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looks good and seems to work - however I may have done it wrong. If so, please let me know.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Parsing nginx logs with GoAccess and creating a HTML report in a cron job</title>
    <link href="https://alexanderzeitler.com/articles/parsing-nginx-logs-with-goaccess-and-creating-a-html-report-using-a-cron-job/"/>
    <updated>2023-11-02T11:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/parsing-nginx-logs-with-goaccess-and-creating-a-html-report-using-a-cron-job/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Parsing nginx logs with GoAccess and creating a HTML report using a cron job&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Parsing nginx logs with GoAccess and creating a HTML report using a cron job&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/parsing-nginx-logs-with-goaccess-and-creating-a-html-report-using-a-cron-job/luke-chesser-JKUTrJ4vK00-unsplash.jpg&quot; /&gt;
&lt;meta property=&quot;og:title&quot; content=&quot;Parsing nginx logs with GoAccess and creating a HTML report using a cron job&quot; /&gt;
&lt;meta property=&quot;og:url&quot; content=&quot;https://alexanderzeitler.com/articles/parsing-nginx-logs-with-goaccess-and-creating-a-html-report-using-a-cron-job/&quot; /&gt;
&lt;meta property=&quot;og:image&quot; content=&quot;https://alexanderzeitler.com/articles/parsing-nginx-logs-with-goaccess-and-creating-a-html-report-using-a-cron-job/luke-chesser-JKUTrJ4vK00-unsplash.jpg&quot; /&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/parsing-nginx-logs-with-goaccess-and-creating-a-html-report-using-a-cron-job/luke-chesser-JKUTrJ4vK00-unsplash.jpg&quot; alt=&quot;Server logs&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;small&gt;&lt;small&gt;
Photo by &lt;a href=&quot;https://unsplash.com/@lukechesser?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;Luke Chesser&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/graphs-of-performance-analytics-on-a-laptop-screen-JKUTrJ4vK00?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;Unsplash&lt;/a&gt;
&lt;/small&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;Gyula recently asked for a decent cookieless analytics tool on Twitter:&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Okay. I don&amp;#39;t wanna use Google analytics, I don&amp;#39;t wanna anger my users with cookie popups, but wanna be GDPR compliant and I wanna have analytics on my website at the same time.&lt;br /&gt;&lt;br /&gt;What should I do? Any recommendations?&lt;/p&gt;&amp;mdash; GYN (@gyulanemeth85) &lt;a href=&quot;https://twitter.com/gyulanemeth85/status/1719802160702013925?ref_src=twsrc%5Etfw&quot;&gt;November 1, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;p&gt;I suggested to use &lt;a href=&quot;https://goaccess.io/&quot;&gt;GoAccess&lt;/a&gt; to parse the nginx logs and create a HTML report and Hannes asked for a quickstart - so here we go.&lt;/p&gt;
&lt;p&gt;Considering we&#39;re a running a Linux server with nginx and Docker, here&#39;s how to get started and our blog is static HTML generated with 11ty it is as easy as this to have a cron job running every day at 22:05 to create a HTML report of the nginx logs:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;5 22 * * * &lt;span class=&quot;hljs-built_in&quot;&gt;cat&lt;/span&gt; /var/opt/deploy/logs/nginx/access.log | /usr/bin/docker run --&lt;span class=&quot;hljs-built_in&quot;&gt;rm&lt;/span&gt; -i -e LANG=&lt;span class=&quot;hljs-variable&quot;&gt;$LANG&lt;/span&gt; allinurl/goaccess -a -o html --log-format COMBINED - &amp;gt; /var/opt/deploy/www/_site/report.html&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The command in detail:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;5 22 * * *&lt;/code&gt; - run the command every day at 22:05&lt;/p&gt;
&lt;p&gt;&lt;code&gt;cat /var/opt/deploy/logs/nginx/access.log&lt;/code&gt; - read the nginx access log&lt;/p&gt;
&lt;p&gt;&lt;code&gt;|&lt;/code&gt; - pipe the output to the next command&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/usr/bin/docker run --rm -i -e LANG=$LANG allinurl/goaccess -a -o html --log-format COMBINED -&lt;/code&gt; - run the docker container with the GoAccess image and parse the nginx access log and create a HTML report&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;gt; /var/opt/deploy/www/_site/report.html&lt;/code&gt; - write the HTML report to the file system&lt;/p&gt;
&lt;p&gt;The report:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/parsing-nginx-logs-with-goaccess-and-creating-a-html-report-using-a-cron-job/goaccesslog.png&quot; alt=&quot;the report&quot; /&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Using aliases in scripts with zsh</title>
    <link href="https://alexanderzeitler.com/articles/using-aliases-in-zsh-scripts/"/>
    <updated>2023-12-17T22:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/using-aliases-in-zsh-scripts/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Using aliases in subshells with zsh&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Using aliases in subshells with zsh&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/using-aliases-in-zsh-scripts/ahmed-zayan-yB_e0q_3kp0-unsplash.jpg&quot; /&gt;
&lt;meta property=&quot;og:title&quot; content=&quot;Using aliases in subshells with zsh&quot; /&gt;
&lt;meta property=&quot;og:url&quot; content=&quot;https://alexanderzeitler.com/articles/using-aliases-in-zsh-scripts/&quot; /&gt;
&lt;meta property=&quot;og:image&quot; content=&quot;https://alexanderzeitler.com/articles/using-aliases-in-zsh-scripts/ahmed-zayan-yB_e0q_3kp0-unsplash.jpg&quot; /&gt;
&lt;p&gt;&lt;small&gt;&lt;small&gt;Photo by &lt;a href=&quot;https://unsplash.com/@zayyerrn?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;Ahmed Zayan&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/man-in-black-jacket-and-guy-fawkes-mask-yB_e0q_3kp0?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;Unsplash&lt;/a&gt;
&lt;/small&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/using-aliases-in-zsh-scripts/ahmed-zayan-yB_e0q_3kp0-unsplash.jpg&quot; alt=&quot;Using aliases&quot; /&gt;&lt;/p&gt;
&lt;p&gt;We often use shell aliases to make our life easier. However, when we try to use them in shell scripts (hence, subshells) it seems to get harder.&lt;/p&gt;
&lt;p&gt;Let&#39;s say we have the following alias to run the AWS CLI in a docker container that is using the current directory as the working directory:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;alias&lt;/span&gt; aws=&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;docker run --rm -it -v ~/.aws:/root/.aws -v $(pwd):/aws amazon/aws-cli&amp;#x27;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we try to use this alias in a script, it will fail:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;#!/bin/zsh&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;aws --profile my-aws-profile s3 &lt;span class=&quot;hljs-built_in&quot;&gt;cp&lt;/span&gt; s3://my-bucket/path/to/file .&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will result in the following error:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;zsh: &lt;span class=&quot;hljs-built_in&quot;&gt;command&lt;/span&gt; not found: aws&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are a few things to know:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;aliases are &lt;a href=&quot;https://www.gnu.org/software/bash/manual/html_node/Aliases.html#Aliases&quot;&gt;not expanded in non-interactive shells&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;functions &lt;a href=&quot;https://www.gnu.org/software/bash/manual/html_node/Aliases.html&quot;&gt;are preferred over aliases&lt;/a&gt; in bash&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, let&#39;s try to use a function instead of an alias:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;#!/bin/zsh&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-title&quot;&gt;aws&lt;/span&gt;&lt;/span&gt;() {&lt;br /&gt;  docker run --&lt;span class=&quot;hljs-built_in&quot;&gt;rm&lt;/span&gt; -it -v ~/.aws:/root/.aws -v $(&lt;span class=&quot;hljs-built_in&quot;&gt;pwd&lt;/span&gt;):/aws amazon/aws-cli &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&lt;span class=&quot;hljs-variable&quot;&gt;$@&lt;/span&gt;&amp;quot;&lt;/span&gt;&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we could add the function to our &lt;code&gt;.zshrc&lt;/code&gt; and source it and try to use it in our script and get new errors.&lt;/p&gt;
&lt;p&gt;Long story short, there&#39;s a file named &lt;code&gt;~/.zshenv&lt;/code&gt; (if it doesn&#39;t exist, just create it) that is sourced on every invocation of &lt;code&gt;zsh&lt;/code&gt;. So, we can add the function to this file and it will be available in our scripts.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-title&quot;&gt;aws&lt;/span&gt;&lt;/span&gt;() {&lt;br /&gt;  docker run --&lt;span class=&quot;hljs-built_in&quot;&gt;rm&lt;/span&gt; -it -v ~/.aws:/root/.aws -v $(&lt;span class=&quot;hljs-built_in&quot;&gt;pwd&lt;/span&gt;):/aws amazon/aws-cli &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&lt;span class=&quot;hljs-variable&quot;&gt;$@&lt;/span&gt;&amp;quot;&lt;/span&gt;&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can use the function in our script:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;#!/bin/zsh&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;aws --profile my-aws-profile s3 &lt;span class=&quot;hljs-built_in&quot;&gt;cp&lt;/span&gt; s3://my-bucket/path/to/file .&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And it will work as expected 🎉.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Annotating Podcasts and MP3 files using Logseq</title>
    <link href="https://alexanderzeitler.com/articles/annotating-podcasts-or-mp3-in-logseq/"/>
    <updated>2023-12-23T20:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/annotating-podcasts-or-mp3-in-logseq/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Annotating Podcasts and MP3 files using Logseq&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Annotating Podcasts and MP3 files using Logseq&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/annotating-podcasts-or-mp3-in-logseq/soundtrap-h6PDEdr9IZo-unsplash.jpg&quot; /&gt;
&lt;meta property=&quot;og:title&quot; content=&quot;Annotating Podcasts and MP3 files using Logseq&quot; /&gt;
&lt;meta property=&quot;og:url&quot; content=&quot;https://alexanderzeitler.com/articles/annotating-podcasts-or-mp3-in-logseq/&quot; /&gt;
&lt;meta property=&quot;og:image&quot; content=&quot;https://alexanderzeitler.com/articles/annotating-podcasts-or-mp3-in-logseq/soundtrap-h6PDEdr9IZo-unsplash.jpg&quot; /&gt;
&lt;p&gt;&lt;small&gt;&lt;small&gt;
Photo by &lt;a href=&quot;https://unsplash.com/@soundtrap?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;Soundtrap&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/macbook-pro-on-brown-wooden-table-h6PDEdr9IZo?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;Unsplash&lt;/a&gt;
&lt;/small&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/annotating-podcasts-or-mp3-in-logseq/soundtrap-h6PDEdr9IZo-unsplash.jpg&quot; alt=&quot;Podcast Recording&quot; /&gt;&lt;/p&gt;
&lt;p&gt;A while ago I posted how to &lt;a href=&quot;https://alexanderzeitler.com/articles/annotating-highlighting-epub-with-logseq-calibre&quot;&gt;annotate epub using Logseq&lt;/a&gt;. This time I&#39;ll show you, how to annotate Podcasts or more generic, MP3 files - using Linux.&lt;/p&gt;
&lt;p&gt;First of all we need to create a &lt;code&gt;.mp3&lt;/code&gt; file from a Podcast.&lt;/p&gt;
&lt;p&gt;I&#39;m using the Podcasts browser extension (available for Firefox and Chrome) from &lt;a href=&quot;https://podcasts.bluepill.life/index.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It looks a bit like a lightweight version of Spotify. You can search for Podcasts and subscribe to them. You can also download episodes and play them.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/annotating-podcasts-or-mp3-in-logseq/podcasts-extension-firefox.png&quot; alt=&quot;Podcasts browser extension&quot; /&gt;&lt;/p&gt;
&lt;p&gt;So you can open an episode:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/annotating-podcasts-or-mp3-in-logseq/podcasts-extension-firefox-episode-playing.png&quot; alt=&quot;Playing an episode&quot; /&gt;&lt;/p&gt;
&lt;p&gt;And then you can download it by hovering over the episode and clicking the download button:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/annotating-podcasts-or-mp3-in-logseq/podcasts-extension-firefox-episode-download.png&quot; alt=&quot;Downloading an episode&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Open a terminal and change to the directory where you downloaded the episode. Then run:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ffmpeg -loop 1 -i ./cover.jpg &#92;&lt;br /&gt;       -i ./episode-12.mp3 &#92;&lt;br /&gt;       -vf &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:-1:-1:color=black,setsar=1,format=yuv420p&amp;quot;&lt;/span&gt; &#92;&lt;br /&gt;       -shortest -fflags +shortest &#92;&lt;br /&gt;       episode-12.mp4&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make sure to also have cover image named &lt;code&gt;cover.jpg&lt;/code&gt; in the same directory. This will create a &lt;code&gt;.mp4&lt;/code&gt; file from the &lt;code&gt;.mp3&lt;/code&gt; file and the cover image.&lt;/p&gt;
&lt;p&gt;Depending on the length of the episode and your machine, this can take a while, but you should get a result in under 10 minutes for a 20 minutes episode.&lt;/p&gt;
&lt;p&gt;Now we can upload this video to YouTube, mark it as unlisted (so nobody beside you can see it until they get the link) and use the YouTube video to annotate it using Logseq (just past the video link into Logseq).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/annotating-podcasts-or-mp3-in-logseq/logseq-podcast.png&quot; alt=&quot;Annotating the YouTube video&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You can add a timestamp by stopping the video at the desired position and then typing &lt;code&gt;/timestamp&lt;/code&gt; in the Logseq editor. This will show the menu where you can pick the &amp;quot;Embed YouTube timestamp&amp;quot; option with the number of seconds at which you paused the video.&lt;/p&gt;
&lt;p&gt;As said, make sure to have an background image added, otherwise the video will not be processed by YouTube.&lt;/p&gt;
&lt;p&gt;Of course, you can also create &lt;code&gt;.mp4&lt;/code&gt; files from &lt;code&gt;.mp3&lt;/code&gt; files on macOS using iMovie (and I guess there&#39;s something similar available on Windows, too).&lt;/p&gt;
&lt;p&gt;And of course, if the Podcast provides a YouTube version of the episode, you can use that instead of creating a video from the audio file and uploading it to YouTube.&lt;/p&gt;
&lt;p&gt;The Podcast extension also offers a paid service to transcribe the audio file. I haven&#39;t tried that yet because I want to annotate in my own words. I just need the timestamps feature.&lt;/p&gt;
&lt;p&gt;I also tried &lt;a href=&quot;https://www.snipd.com/&quot;&gt;Snipd&lt;/a&gt; but their transcription service is not available for German language yet.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Setting Displays to monochrome / grayscale (Saturation 0) on Xubuntu / XFCE</title>
    <link href="https://alexanderzeitler.com/articles/setting-display-monochrome-saturation-0-on-xubuntu-xfce/"/>
    <updated>2023-12-25T20:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/setting-display-monochrome-saturation-0-on-xubuntu-xfce/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Setting Displays to monochrome / grayscale (Saturation 0) on Xubuntu / XFCE&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Setting Displays to monochrome / grayscale (Saturation 0) on Xubuntu / XFCE&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/setting-display-monochrome-saturation-0-on-xubuntu-xfce/chris-ried-ieic5Tq8YMk-unsplash.jpg&quot; /&gt;
&lt;meta property=&quot;og:title&quot; content=&quot;Setting Displays to monochrome / grayscale (Saturation 0) on Xubuntu / XFCE&quot; /&gt;
&lt;meta property=&quot;og:url&quot; content=&quot;https://alexanderzeitler.com/articles/setting-display-monochrome-grayscale-saturation-0-on-xubuntu-xfce/&quot; /&gt;
&lt;meta property=&quot;og:image&quot; content=&quot;https://alexanderzeitler.com/articles/setting-display-monochrome-saturation-0-on-xubuntu-xfce/chris-ried-ieic5Tq8YMk-unsplash.jpg&quot; /&gt;
&lt;p&gt;&lt;small&gt;&lt;small&gt;
Photo by &lt;a href=&quot;https://unsplash.com/@cdr6934?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;Chris Ried&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/a-computer-screen-with-a-bunch-of-code-on-it-ieic5Tq8YMk?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;Unsplash&lt;/a&gt;
&lt;/small&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/setting-display-monochrome-saturation-0-on-xubuntu-xfce/chris-ried-ieic5Tq8YMk-unsplash.jpg&quot; alt=&quot;Grayscale...&quot; /&gt;&lt;/p&gt;
&lt;p&gt;For some tasks like writing I prefer to have my displays set to monochrome / grayscale. This is how I do it on Xubuntu / XFCE.&lt;/p&gt;
&lt;p&gt;There are several solutions out there that use &lt;code&gt;xrandr&lt;/code&gt; or &lt;code&gt;ddcutil&lt;/code&gt; but none of them worked for me. I&#39;m using Xubuntu 22.04.2 LTS on a late 2012 Mac mini for some tasks.&lt;/p&gt;
&lt;p&gt;So here is how you would do it using &lt;code&gt;xrandr&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;xrandr --output HDMI-3 --&lt;span class=&quot;hljs-built_in&quot;&gt;set&lt;/span&gt; Saturation 0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;HDMI-3&lt;/code&gt; is the name of my display.&lt;/p&gt;
&lt;p&gt;You can get the name of your display by running &lt;code&gt;xrandr&lt;/code&gt; without any arguments.&lt;/p&gt;
&lt;p&gt;Connected displays will be listed like this:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;HDMI-3 connected 1920x1200+1920+0 (normal left inverted right x axis y axis) 518mm x 324mm&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The solution that actually worked for me was to use &lt;code&gt;libvibrant&lt;/code&gt; and its source can be found on &lt;a href=&quot;https://github.com/libvibrant/libvibrant&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It&#39;s &lt;a href=&quot;https://github.com/libvibrant/libvibrant#basic-building&quot;&gt;said&lt;/a&gt; to be build and installed this way:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;mkdir&lt;/span&gt; build&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;cd&lt;/span&gt; build&lt;br /&gt;cmake ..&lt;br /&gt;make&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; make install&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, I had to do few more things to get it working:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;mkdir&lt;/span&gt; build&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;cd&lt;/span&gt; build&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# fixes: Could not find X11&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt install libx11-dev &lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# fixes: The RandR library and headers were not found&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt install xorg-dev libglu1-mesa-dev&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# fixes: fatal error: NVCtrl/NVCtrlLib.h: No such file or directory&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt install libxnvctrl-dev&lt;br /&gt;&lt;br /&gt;cmake ..&lt;br /&gt;make&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# fixes: libvibrant.so.1 cannot open shared object file&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; /sbin/ldconfig -v*&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; make install&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After that I was able to run &lt;code&gt;vibrant-cli&lt;/code&gt; and it worked like a charm:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;vibrant-cli HDMI-3 0&lt;br /&gt;vibrant-cli DP-1 0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To reset the saturation to 100%:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;vibrant-cli HDMI-3 1&lt;br /&gt;vibrant-cli DP-1 1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running this will make it look even better:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;xgamma -gamma 0.8&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you create a &lt;code&gt;.desktop&lt;/code&gt; entry for both commands, you easily run them using a launcher like &lt;a href=&quot;https://albertlauncher.github.io/&quot;&gt;Albert&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/setting-display-monochrome-saturation-0-on-xubuntu-xfce/monochrome-mode-on.png&quot; alt=&quot;Monochrome mode launcher&quot; /&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Rotating nginx logs using Docker Compose and logrotate</title>
    <link href="https://alexanderzeitler.com/articles/rotating-nginx-logs-with-docker-compose/"/>
    <updated>2023-12-30T15:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/rotating-nginx-logs-with-docker-compose/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Rotating nginx logs using Docker Compose and logrotate&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Rotating nginx logs using Docker Compose and logrotate&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/rotating-nginx-logs-with-docker-compose/sigmund-aI4RJ--Mw4I-unsplash.jpg&quot; /&gt;
&lt;meta property=&quot;og:title&quot; content=&quot;Rotating nginx logs using Docker Compose and logrotate&quot; /&gt;
&lt;meta property=&quot;og:url&quot; content=&quot;https://alexanderzeitler.com/articles/rotating-nginx-logs-with-docker-compose/&quot; /&gt;
&lt;meta property=&quot;og:image&quot; content=&quot;https://alexanderzeitler.com/articles/rotating-nginx-logs-with-docker-compose/sigmund-aI4RJ--Mw4I-unsplash.jpg&quot; /&gt;
&lt;p&gt;&lt;small&gt;&lt;small&gt;
Photo by &lt;a href=&quot;https://unsplash.com/@sigmund?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;Sigmund&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/text-aI4RJ--Mw4I?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;Unsplash&lt;/a&gt;
&lt;/small&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/rotating-nginx-logs-with-docker-compose/sigmund-aI4RJ--Mw4I-unsplash.jpg&quot; alt=&quot;Recycling&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If you&#39;re running nginx in a Docker container, you might want to rotate the logs. Here&#39;s how to do it using Docker Compose and &lt;code&gt;logrotate&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Consider the following &lt;code&gt;docker-compose.yml&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;version:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;3.7&amp;#x27;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;services:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;nginx:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;image:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;nginx:1.19.6-alpine&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;ports:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;hljs-string&quot;&gt;:80&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;volumes:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;./nginx.conf:/etc/nginx/nginx.conf&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;./logs:/var/log/nginx&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The default location for nginx logs is &lt;code&gt;/var/log/nginx&lt;/code&gt;. We&#39;re mounting the &lt;code&gt;logs&lt;/code&gt; directory from the host into the container. This way, we can access the logs from the host.&lt;/p&gt;
&lt;p&gt;We can now use &lt;code&gt;logrotate&lt;/code&gt; to rotate the logs. Create a &lt;a href=&quot;https://manpages.ubuntu.com/manpages/jammy/en/man8/logrotate.8.html&quot;&gt;logrotate configuration&lt;/a&gt; file called that&#39;s called like your service in &lt;code&gt;/etc/logrotate.d/&lt;/code&gt;, in this case &lt;code&gt;nginx&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;touch&lt;/span&gt; /etc/logrotate.d/nginx&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add the following content to the file:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;/var/opt/deploy/logs/*.&lt;span class=&quot;hljs-built_in&quot;&gt;log&lt;/span&gt; {&lt;br /&gt;  daily&lt;br /&gt;  missingok&lt;br /&gt;  rotate 31&lt;br /&gt;  dateext&lt;br /&gt;  compress&lt;br /&gt;  delaycompress&lt;br /&gt;  notifempty&lt;br /&gt;  sharedscripts&lt;br /&gt;  postrotate&lt;br /&gt;    &lt;span class=&quot;hljs-built_in&quot;&gt;cd&lt;/span&gt; /var/opt/deploy &#92;&lt;br /&gt;      &amp;amp;&amp;amp; /usr//bin/docker compose &lt;span class=&quot;hljs-built_in&quot;&gt;kill&lt;/span&gt; -s USR1 nginx&lt;br /&gt;  endscript&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will rotate the logs daily, keep 31 days of logs, compress the logs, and send a &lt;code&gt;USR1&lt;/code&gt; signal to the nginx process in the container. This will cause nginx to reopen the log files.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;dateext&lt;/code&gt; option will append the date to the rotated log files. For example, &lt;code&gt;access.log&lt;/code&gt; will become &lt;code&gt;access.log-20231230&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Make sure to match the path to your logs. In this case, the logs are located in &lt;code&gt;/var/opt/deploy/logs/&lt;/code&gt; on the host. The logs are mounted into the container at &lt;code&gt;/var/log/nginx/&lt;/code&gt;. The logs are located in &lt;code&gt;/var/log/nginx/&lt;/code&gt; in the container.&lt;/p&gt;
&lt;p&gt;Also make sure to match the path to your &lt;code&gt;docker-compose.yml&lt;/code&gt;. In this case, the &lt;code&gt;docker-compose.yml&lt;/code&gt; is located in &lt;code&gt;/var/opt/deploy/&lt;/code&gt; on the host.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;postrotate&lt;/code&gt; script will change into the directory where the &lt;code&gt;docker-compose.yml&lt;/code&gt; is located and send the &lt;code&gt;USR1&lt;/code&gt; signal to the nginx process in the container. This will cause nginx to reopen the log files.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;USR1&lt;/code&gt; signal is sent to the nginx process in the container using &lt;code&gt;docker compose kill&lt;/code&gt;. The &lt;code&gt;USR1&lt;/code&gt; signal is not sent to the container itself, but to the process running inside the container. The process running inside the container is nginx.&lt;/p&gt;
&lt;p&gt;You can test the log rotation by running &lt;code&gt;logrotate&lt;/code&gt; manually:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;logrotate -f /etc/logrotate.d/nginx&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that&#39;s it. Your nginx logs will now be rotated daily, compressed, and kept for 31 days.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Listening to HTMX HX-Trigger response header events from Alpine.js</title>
    <link href="https://alexanderzeitler.com/articles/listening-to-htmx-hx-trigger-response-header-events-from-alpine-js/"/>
    <updated>2024-02-11T00:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/listening-to-htmx-hx-trigger-response-header-events-from-alpine-js/</id>
    <content type="html">&lt;meta name=&quot;description&quot; content=&quot;HTMX and Alpine.js are a great combination. Here&#39;s how to listen to HTMX HX-Trigger response header events from Alpine.js.&quot; /&gt;
&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Listening to HTMX HX-Trigger response header events from Alpine.js&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Listening to HTMX HX-Trigger response header events from Alpine.js&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/listening-to-htmx-hx-trigger-response-header-events-from-alpine-js/vincent-van-zalinge-nq5bgxSg8gE-unsplash.jpg&quot; /&gt;
&lt;meta property=&quot;og:title&quot; content=&quot;Listening to HTMX HX-Trigger response header events from Alpine.js&quot; /&gt;
&lt;meta property=&quot;og:url&quot; content=&quot;https://alexanderzeitler.com/articles/listening-to-htmx-hx-trigger-response-header-events-from-alpine-js/&quot; /&gt;
&lt;meta property=&quot;og:image&quot; content=&quot;https://alexanderzeitler.com/articles/listening-to-htmx-hx-trigger-response-header-events-from-alpine-js/vincent-van-zalinge-nq5bgxSg8gE-unsplash.jpg&quot; /&gt;
&lt;p&gt;&lt;small&gt;&lt;small&gt;
Photo by &lt;a href=&quot;https://unsplash.com/@vincentvanzalinge?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;Vincent van Zalinge&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/brown-rabbit-standing-on-green-grass-field-nq5bgxSg8gE?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;Unsplash&lt;/a&gt;
&lt;/small&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/listening-to-htmx-hx-trigger-response-header-events-from-alpine-js/vincent-van-zalinge-nq5bgxSg8gE-unsplash.jpg&quot; alt=&quot;Recycling&quot; /&gt;&lt;/p&gt;
&lt;p&gt;HTMX and Alpine.js are a great combination.&lt;/p&gt;
&lt;p&gt;Here&#39;s how to listen to HTMX HX-Trigger response header events from Alpine.js.&lt;/p&gt;
&lt;p&gt;HTMX allows you to listen for Events from the HTTP Response using the &lt;code&gt;HX-Trigger&lt;/code&gt; response header (the last line matters) like this:&lt;/p&gt;
&lt;pre class=&quot;language-http&quot;&gt;&lt;code class=&quot;language-http&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;HTTP/1.1&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;200&lt;/span&gt; OK&lt;br /&gt;&lt;span class=&quot;hljs-attribute&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;: &lt;/span&gt;text/html; charset=utf-8&lt;br /&gt;&lt;span class=&quot;hljs-attribute&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;: &lt;/span&gt;Sat, 10 Feb 2024 22:46:58 GMT&lt;br /&gt;&lt;span class=&quot;hljs-attribute&quot;&gt;Server&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;: &lt;/span&gt;Kestrel&lt;br /&gt;&lt;span class=&quot;hljs-attribute&quot;&gt;Transfer-Encoding&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;: &lt;/span&gt;chunked&lt;br /&gt;&lt;span class=&quot;hljs-attribute&quot;&gt;HX-Trigger&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;: &lt;/span&gt;product-added-to-shopping-cart&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This indicates that a product has been added to the shopping cart.&lt;/p&gt;
&lt;p&gt;HTMX itself can respond to this type of &amp;quot;events&amp;quot; like this:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;hx-get&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;/shopping-cart-preview&amp;quot;&lt;/span&gt;&lt;br /&gt;     &lt;span class=&quot;hljs-attr&quot;&gt;hx-target&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;#shopping-cart-preview&amp;quot;&lt;/span&gt;&lt;br /&gt;     &lt;span class=&quot;hljs-attr&quot;&gt;hx-trigger&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;product-added-to-shopping-cart from:body&amp;quot;&lt;/span&gt;&lt;br /&gt;     &lt;span class=&quot;hljs-attr&quot;&gt;id&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;shopping-cart-preview&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will reload the shopping cart preview.&lt;/p&gt;
&lt;p&gt;No we might want to display a toast or the like using Alpine.js when the product has been added.&lt;/p&gt;
&lt;p&gt;HTMX is publishing the events it receives from the server as global events, so you can just listen to it from vanilla JavaScript:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&amp;lt;script type=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;text/javascript&amp;quot;&lt;/span&gt;&amp;gt;&lt;br /&gt;  &lt;span class=&quot;hljs-variable language_&quot;&gt;document&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;addEventListener&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;product-added-to-shopping-cart&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-params&quot;&gt;event&lt;/span&gt; =&amp;gt;&lt;/span&gt; {&lt;br /&gt;    &lt;span class=&quot;hljs-variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;hljs-title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;product-added-to-shopping-cart&amp;#x27;&lt;/span&gt;)&lt;br /&gt;  })&lt;br /&gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alpine.js allows you to &lt;a href=&quot;https://alpinejs.dev/essentials/events#listening-for-events-on-window&quot;&gt;listen for events on the global &lt;code&gt;window&lt;/code&gt; object&lt;/a&gt; like this:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt; @&lt;span class=&quot;hljs-attr&quot;&gt;some-event.window&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;console.log(&amp;#x27;some-event was dispatched&amp;#x27;)&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;...&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So we can listen to &lt;code&gt;product-added-to-shopping-cart&lt;/code&gt; using Alpine like this:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;br /&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;x-data&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;{showToast:false}&amp;quot;&lt;/span&gt;&lt;br /&gt;     @&lt;span class=&quot;hljs-attr&quot;&gt;product-added-to-shopping-cart.window&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;showToast = true&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;    ...&lt;br /&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Straight forward and simple, yet asked quite often.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Dealing with C# records in ASP.NET Core model binding</title>
    <link href="https://alexanderzeitler.com/articles/dealing-with-csharp-records-in-aspnet-core-model-binding/"/>
    <updated>2024-02-21T18:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/dealing-with-csharp-records-in-aspnet-core-model-binding/</id>
    <content type="html">&lt;meta name=&quot;description&quot; content=&quot;Dealing with C# records in ASP.NET Core model binding&quot; /&gt;
&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Dealing with C# records in ASP.NET Core model binding&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Dealing with C# records in ASP.NET Core model binding&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/dealing-with-csharp-records-in-aspnet-core-model-binding/viktor-forgacs--0rQ6AbnqeQ-unsplash.jpg&quot; /&gt;
&lt;meta property=&quot;og:title&quot; content=&quot;Dealing with C# records in ASP.NET Core model binding&quot; /&gt;
&lt;meta property=&quot;og:url&quot; content=&quot;https://alexanderzeitler.com/articles/dealing-with-csharp-records-in-aspnet-core-model-binding/&quot; /&gt;
&lt;meta property=&quot;og:image&quot; content=&quot;https://alexanderzeitler.com/articles/dealing-with-csharp-records-in-aspnet-core-model-binding/viktor-forgacs--0rQ6AbnqeQ-unsplash.jpg&quot; /&gt;
&lt;p&gt;&lt;small&gt;&lt;small&gt;
Photo by &lt;a href=&quot;https://unsplash.com/@sonance?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;
Viktor Forgacs&lt;/a&gt;
on &lt;a href=&quot;https://unsplash.com/photos/green-grass-field-with-trees-and-a-black-and-white-cross--0rQ6AbnqeQ?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;
Unsplash&lt;/a&gt;
&lt;/small&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/dealing-with-csharp-records-in-aspnet-core-model-binding/viktor-forgacs--0rQ6AbnqeQ-unsplash.jpg&quot; alt=&quot;Recycling&quot; /&gt;&lt;/p&gt;
&lt;p&gt;C# records are a great addition to the language. They are immutable, have value semantics, and are great for modeling
data. But when it comes to model binding in ASP.NET Core, things are getting a bit more complicated. Let&#39;s see how we
can deal with C# records in ASP.NET Core model binding.&lt;/p&gt;
&lt;h2&gt;What are C# records?&lt;/h2&gt;
&lt;p&gt;C# records are a new feature in C# 9. They are a reference type, but they have value semantics. This means that they are
immutable and can be compared by value. They are great for modeling data, and they are a great addition to the language.&lt;/p&gt;
&lt;p&gt;Here is an example of a C# record:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;record&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Person&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; FirstName, &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; LastName&lt;/span&gt;)&lt;/span&gt;;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, we have a &lt;code&gt;Person&lt;/code&gt; record with two properties: &lt;code&gt;FirstName&lt;/code&gt; and &lt;code&gt;LastName&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Model binding in ASP.NET Core&lt;/h2&gt;
&lt;p&gt;Model binding is the process of mapping data from an HTTP request to an object in your application. It is a fundamental
part of building web applications, and it is used to handle form submissions, query string parameters, and other types
of data.&lt;/p&gt;
&lt;p&gt;In ASP.NET Core, model binding is done automatically by the framework. When you create a controller action that takes a
parameter, the framework will try to bind data from the request to that parameter.&lt;/p&gt;
&lt;p&gt;Here is an example of a controller action that takes a &lt;code&gt;Person&lt;/code&gt; parameter:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;[&lt;span class=&quot;hljs-meta&quot;&gt;HttpPost&lt;/span&gt;]&lt;br /&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; IActionResult &lt;span class=&quot;hljs-title&quot;&gt;CreatePerson&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;Person person&lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;    &lt;span class=&quot;hljs-comment&quot;&gt;// ...&lt;/span&gt;&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, the framework will try to bind data from the request to a &lt;code&gt;Person&lt;/code&gt; object. This is done automatically
by the framework, and it is a very powerful feature.&lt;/p&gt;
&lt;h2&gt;Dealing with C# records in ASP.NET Core model binding&lt;/h2&gt;
&lt;p&gt;At a first glance, everything seems to be easy with C# records. But when it comes to model binding in ASP.NET Core,
things are getting a bit more complicated.&lt;/p&gt;
&lt;p&gt;Now there are two issues with C# records and model binding in ASP.NET Core:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The model binding needs a parameterless constructor&lt;/li&gt;
&lt;li&gt;If you want to translate property names or set display error messages, you need to use the &lt;code&gt;Display&lt;/code&gt; and &lt;code&gt;Required&lt;/code&gt;
attributes.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The shortest way (I know) to have a parameterless constructor while maintaining the positional constructor is this one -
I want to avoid embrace a &lt;code&gt;class&lt;/code&gt; like style:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;record&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Person&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt;? FirstName,&lt;br /&gt;  &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt;? LastName&lt;br /&gt;&lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Person&lt;/span&gt;() : &lt;span class=&quot;hljs-title&quot;&gt;this&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-literal&quot;&gt;null&lt;/span&gt;,&lt;br /&gt;    &lt;span class=&quot;hljs-literal&quot;&gt;null&lt;/span&gt;,&lt;br /&gt;  &lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;  }&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;Display&lt;/code&gt; and &lt;code&gt;Required&lt;/code&gt; attributes are used to set display error messages and translate property names. Here is an
naive approach to use them:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;record&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Person&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;  [Display(Name = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;First Name&amp;quot;&lt;/span&gt;&lt;/span&gt;)]&lt;br /&gt;  [&lt;span class=&quot;hljs-title&quot;&gt;Required&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;ErrorMessage = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;The first name is required&amp;quot;&lt;/span&gt;&lt;/span&gt;)]&lt;br /&gt;  &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt;? FirstName,&lt;br /&gt;  [&lt;span class=&quot;hljs-title&quot;&gt;Display&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;Name = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Last Name&amp;quot;&lt;/span&gt;&lt;/span&gt;)]&lt;br /&gt;  [&lt;span class=&quot;hljs-title&quot;&gt;Required&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;ErrorMessage = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;The last name is required&amp;quot;&lt;/span&gt;&lt;/span&gt;)]&lt;br /&gt;  &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt;? LastName&lt;br /&gt;)&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Person&lt;/span&gt;() : &lt;span class=&quot;hljs-title&quot;&gt;this&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-literal&quot;&gt;null&lt;/span&gt;,&lt;br /&gt;    &lt;span class=&quot;hljs-literal&quot;&gt;null&lt;/span&gt;&lt;br /&gt;  &lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;  }&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This won&#39;t work, because the &lt;code&gt;Display&lt;/code&gt; and &lt;code&gt;Required&lt;/code&gt; attributes are not recognized by the model binding. You need to do it that way:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;record&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Person&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;  [property:Display(Name = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;First Name&amp;quot;&lt;/span&gt;&lt;/span&gt;)]&lt;br /&gt;  [property:&lt;span class=&quot;hljs-title&quot;&gt;Required&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;ErrorMessage = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;The first name is required&amp;quot;&lt;/span&gt;&lt;/span&gt;)]&lt;br /&gt;  &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt;? FirstName,&lt;br /&gt;  [property:&lt;span class=&quot;hljs-title&quot;&gt;Display&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;Name = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Last Name&amp;quot;&lt;/span&gt;&lt;/span&gt;)]&lt;br /&gt;  [property:&lt;span class=&quot;hljs-title&quot;&gt;Required&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;ErrorMessage = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;The last name is required&amp;quot;&lt;/span&gt;&lt;/span&gt;)]&lt;br /&gt;  &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt;? LastName&lt;br /&gt;)&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Person&lt;/span&gt;() : &lt;span class=&quot;hljs-title&quot;&gt;this&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-literal&quot;&gt;null&lt;/span&gt;,&lt;br /&gt;    &lt;span class=&quot;hljs-literal&quot;&gt;null&lt;/span&gt;&lt;br /&gt;  &lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;  }&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The reason for this is, without the &lt;code&gt;property:&lt;/code&gt; prefix, the attributes are not recognized by the model binding because &lt;code&gt;FirstName&lt;/code&gt; and &lt;code&gt;LastName&lt;/code&gt; are not properties of the &lt;code&gt;Person&lt;/code&gt; but constructor parameters. By adding the &lt;code&gt;property:&lt;/code&gt; prefix, the attributes are emitted for generated properties instead and the model binding can recognize them.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>find_executable: checking for pg_config... no when installing pg Ruby gem on Ubuntu</title>
    <link href="https://alexanderzeitler.com/articles/find-executable-checking-for-pg-config-no-ruby-postgres-ubuntu-linux/"/>
    <updated>2024-06-26T14:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/find-executable-checking-for-pg-config-no-ruby-postgres-ubuntu-linux/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;find_executable: checking for pg_config... no when installing pg Ruby gem on Ubuntu&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;find_executable: checking for pg_config... no when installing pg Ruby gem on Ubuntu&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/find-executable-checking-for-pg-config-no-ruby-postgres-ubuntu-linux/dayne-topkin-_OBJpvBlkLg-unsplash.jpg&quot; /&gt;
&lt;meta property=&quot;og:title&quot; content=&quot;find_executable: checking for pg_config... no when installing pg Ruby gem on Ubuntu&quot; /&gt;
&lt;meta property=&quot;og:url&quot; content=&quot;https://alexanderzeitler.com/articles/find-executable-checking-for-pg-config-no-ruby-postgres-ubuntu-linux/&quot; /&gt;
&lt;meta property=&quot;og:image&quot; content=&quot;https://alexanderzeitler.com/articles/find-executable-checking-for-pg-config-no-ruby-postgres-ubuntu-linux/dayne-topkin-_OBJpvBlkLg-unsplash.jpg&quot; /&gt;
&lt;p&gt;&lt;small&gt;&lt;small&gt;
Photo by &lt;a href=&quot;https://unsplash.com/@dtopkin1?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;Dayne Topkin&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/person-holding-red-fruit-during-daytime-_OBJpvBlkLg?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;Unsplash&lt;/a&gt;
&lt;/small&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/find-executable-checking-for-pg-config-no-ruby-postgres-ubuntu-linux/dayne-topkin-_OBJpvBlkLg-unsplash.jpg&quot; alt=&quot;Picking some fruits&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Recently I&#39;ve been tipping my toes into Ruby/Rails water and I wanted to give the &lt;a href=&quot;https://railseventstore.org/&quot;&gt;Rails Event Store&lt;/a&gt; a try. When I tried to install the gems on Ubuntu, I got an error:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;find_executable: checking &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; pg_config... no&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;when installing the  &lt;code&gt;pg&lt;/code&gt; Ruby gem on Ubuntu. Here&#39;s how I handled it.&lt;/p&gt;
&lt;p&gt;The reason for that is quite obvious: the installer tries to refer a library named &lt;code&gt;pg_config&lt;/code&gt; which doesn&#39;t exist on my system.&lt;/p&gt;
&lt;p&gt;It&#39;s also quite obvious it will be included in the &lt;code&gt;postgresql&lt;/code&gt; package, but I&#39;m running Postgres in Docker for ages, so I don&#39;t want to install Postgres just for the sake of this.&lt;/p&gt;
&lt;p&gt;Long story short, just install the following packages using &lt;code&gt;apt&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt install postgresql-client libpq5 libpq-dev&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And with that installed you can install the Rails Event Store dependencies without installing Postgres.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Selecting and copying text without border in Zellij using the keyboard</title>
    <link href="https://alexanderzeitler.com/articles/select-and-copy-text-without-border-in-zellij-using-keyboard/"/>
    <updated>2024-09-28T07:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/select-and-copy-text-without-border-in-zellij-using-keyboard/</id>
    <content type="html">&lt;meta name=&quot;twitter:card&quot; content=&quot;summary_large_image&quot; /&gt;
&lt;meta name=&quot;twitter:site&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;twitter:creator&quot; content=&quot;@lxztlr&quot; /&gt;
&lt;meta name=&quot;description&quot; content=&quot;Zellij is a terminal workspace. It has the base functionality of a terminal multiplexer (similar to tmux or screen). Here&#39;s how to select and copy text without border in Zellij.&quot; /&gt;
&lt;meta name=&quot;twitter:title&quot; content=&quot;Selecting and copying text without border in Zellij using the keyboard&quot; /&gt;
&lt;meta name=&quot;twitter:description&quot; content=&quot;Selecting and copying text without border in Zellij using the keyboard&quot; /&gt;
&lt;meta name=&quot;twitter:image&quot; content=&quot;https://alexanderzeitler.com/articles/select-and-copy-text-without-border-in-zellij-using-keyboard/michelle-ding-50uD7HzOLW8-unsplash.jpg&quot; /&gt;
&lt;meta property=&quot;og:title&quot; content=&quot;Selecting and copying text without border in Zellij using the keyboard&quot; /&gt;
&lt;meta property=&quot;og:url&quot; content=&quot;https://alexanderzeitler.com/articles/select-and-copy-text-without-border-in-zellij-using-keyboard/&quot; /&gt;
&lt;meta property=&quot;og:image&quot; content=&quot;https://alexanderzeitler.com/articles/select-and-copy-text-without-border-in-zellij-using-keyboard/michelle-ding-50uD7HzOLW8-unsplash.jpg&quot; /&gt;
&lt;p&gt;&lt;small&gt;&lt;small&gt;Photo by &lt;a href=&quot;https://unsplash.com/@michelleding?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;Michelle Ding&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/black-and-orange-computer-keyboard-50uD7HzOLW8?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;Unsplash&lt;/a&gt;
&lt;/small&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/select-and-copy-text-without-border-in-zellij-using-keyboard/michelle-ding-50uD7HzOLW8-unsplash.jpg&quot; alt=&quot;Keyboard&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Thanks to &lt;a href=&quot;https://omakub.org/&quot;&gt;Omakub&lt;/a&gt; I got introduced to &lt;a href=&quot;https://zellij.dev/&quot;&gt;Zellij&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Zellij is a terminal workspace. It has the base functionality of a terminal multiplexer (similar to &lt;code&gt;tmux&lt;/code&gt; or `screen) but includes many built-in features that would allow users to extend it and create their own personalized environment.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is how a Zellij window could look like, showing the output from &lt;code&gt;/var/log/syslog&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/select-and-copy-text-without-border-in-zellij-using-keyboard/zellij-syslog.png&quot; alt=&quot;syslog output in Zellij&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now we want to copy this part (in the last line of the output) using the keyboard only:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;2024-09-28T06:56:13.403642+02:00 aetron kernel: [UFW BLOCK] IN=enp14s0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First press &lt;code&gt;CTRL&lt;/code&gt;+&lt;code&gt;SHIFT&lt;/code&gt;+&lt;code&gt;SPACE&lt;/code&gt; to enable the vim mode in Zellij.&lt;/p&gt;
&lt;p&gt;Then navigate to the first char you want to copy using the cursor key:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/select-and-copy-text-without-border-in-zellij-using-keyboard/zellij-cursor-at-first-selected-char.png&quot; alt=&quot;Cursor at first char to be selected&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now press &lt;code&gt;v&lt;/code&gt; to enable vim Visual Mode (nothing will change visually).&lt;/p&gt;
&lt;p&gt;Now move the cursor to the right until the char you want to be copied last.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/select-and-copy-text-without-border-in-zellij-using-keyboard/zellij-visual-mode-selection.png&quot; alt=&quot;Zellij Visual Mode selection&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Finally, press &lt;code&gt;y&lt;/code&gt; and this will yank you selection and it will be available in your clipboard for further usage.&lt;/p&gt;
&lt;p&gt;Great, but there&#39;s one issue: multiline selection:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/select-and-copy-text-without-border-in-zellij-using-keyboard/zellij-multiline-selection-with-border.png&quot; alt=&quot;Zellij multiline selection includes the border&quot; /&gt;&lt;/p&gt;
&lt;p&gt;As you can see, the green border is included in the selection and this will mess up you copied content.&lt;/p&gt;
&lt;p&gt;But there&#39;s a solution for that:&lt;/p&gt;
&lt;p&gt;Press &lt;code&gt;CTRL&lt;/code&gt;+&lt;code&gt;p&lt;/code&gt;, then &lt;code&gt;z&lt;/code&gt; to disable the border before you&#39;re entering vim Mode:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/select-and-copy-text-without-border-in-zellij-using-keyboard/zellij-without-border.png&quot; alt=&quot;Zellij without border&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now you can copy multiple lines without including the border.&lt;/p&gt;
&lt;p&gt;If you want to get the border back, press &lt;code&gt;CTRL&lt;/code&gt;+&lt;code&gt;p&lt;/code&gt;, then &lt;code&gt;z&lt;/code&gt; again.&lt;/p&gt;
&lt;p&gt;Instead of using Visual Mode, you can also enable Visual Line Mode by hitting &lt;code&gt;V&lt;/code&gt; instead of &lt;code&gt;v&lt;/code&gt; and that will allow you to just select full lines by moving the cursor up and down.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Multiple feeds with 11ty</title>
    <link href="https://alexanderzeitler.com/articles/eleventy-multiple-feeds/"/>
    <updated>2024-11-19T12:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/eleventy-multiple-feeds/</id>
    <content type="html">&lt;p&gt;You can create multiple feeds using 11ty.&lt;/p&gt;
&lt;p&gt;For example, if you want to split Posts and TILs (Today I Learned).&lt;/p&gt;
&lt;p&gt;Create a new collection &lt;code&gt;til&lt;/code&gt; in &lt;code&gt;.eleventy.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;eleventyConfig.&lt;span class=&quot;hljs-title function_&quot;&gt;addCollection&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;til&amp;quot;&lt;/span&gt;, &lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;collectionApi&lt;/span&gt;) {&lt;br /&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; collectionApi.&lt;span class=&quot;hljs-title function_&quot;&gt;getFilteredByTag&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;til&amp;quot;&lt;/span&gt;);&lt;br /&gt;});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a new &lt;code&gt;til.njk&lt;/code&gt; template for the feed.&lt;/p&gt;
&lt;pre class=&quot;language-twig&quot;&gt;&lt;code class=&quot;language-twig&quot;&gt;&lt;span class=&quot;language-xml&quot;&gt;---json&lt;br /&gt;{&lt;br /&gt;  &amp;quot;permalink&amp;quot;: &amp;quot;til.xml&amp;quot;,&lt;br /&gt;  &amp;quot;eleventyExcludeFromCollections&amp;quot;: true,&lt;br /&gt;  &amp;quot;metadata&amp;quot;: {&lt;br /&gt;    &amp;quot;title&amp;quot;: &amp;quot;Alexander Zeitler | TIL&amp;quot;,&lt;br /&gt;    &amp;quot;url&amp;quot;: &amp;quot;https://alexanderzeitler.com/&amp;quot;,&lt;br /&gt;    &amp;quot;feedUrl&amp;quot;: &amp;quot;https://alexanderzeitler.com/til.xml&amp;quot;,&lt;br /&gt;    &amp;quot;author&amp;quot;: {&lt;br /&gt;      &amp;quot;name&amp;quot;: &amp;quot;Alexander Zeitler&amp;quot;,&lt;br /&gt;      &amp;quot;email&amp;quot;: &amp;quot;alexander.zeitler@pdmlab.com&amp;quot;&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;---&lt;br /&gt;&lt;span class=&quot;hljs-meta&quot;&gt;&amp;lt;?xml version=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;1.0&amp;quot;&lt;/span&gt; encoding=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;utf-8&amp;quot;&lt;/span&gt;?&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;feed&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;xmlns&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;http://www.w3.org/2005/Atom&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;title&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;hljs-template-variable&quot;&gt;{{ metadata.title }}&lt;/span&gt;&lt;span class=&quot;language-xml&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;title&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;subtitle&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;hljs-template-variable&quot;&gt;{{ metadata.subtitle }}&lt;/span&gt;&lt;span class=&quot;language-xml&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;subtitle&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;link&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;href&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;hljs-template-variable&quot;&gt;{{ metadata.feedUrl }}&lt;/span&gt;&lt;span class=&quot;language-xml&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;rel&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;self&amp;quot;&lt;/span&gt;/&amp;gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;link&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;href&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;hljs-template-variable&quot;&gt;{{ metadata.url }}&lt;/span&gt;&lt;span class=&quot;language-xml&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&lt;/span&gt;/&amp;gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;updated&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;hljs-template-variable&quot;&gt;{{ collections.til | rssLastUpdatedDate }}&lt;/span&gt;&lt;span class=&quot;language-xml&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;updated&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;id&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;hljs-template-variable&quot;&gt;{{ metadata.url }}&lt;/span&gt;&lt;span class=&quot;language-xml&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;id&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;author&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;name&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;hljs-template-variable&quot;&gt;{{ metadata.author.name }}&lt;/span&gt;&lt;span class=&quot;language-xml&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;name&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;email&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;hljs-template-variable&quot;&gt;{{ metadata.author.email }}&lt;/span&gt;&lt;span class=&quot;language-xml&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;email&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;author&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;  {%- for post in collections.til %}&lt;br /&gt;  &lt;/span&gt;&lt;span class=&quot;hljs-template-tag&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;hljs-name&quot;&gt;set&lt;/span&gt; absolutePostUrl &lt;span class=&quot;hljs-template-tag&quot;&gt;%}&lt;/span&gt;&lt;span class=&quot;hljs-template-variable&quot;&gt;{{ post.url | &lt;span class=&quot;hljs-name&quot;&gt;url&lt;/span&gt; | absoluteUrl(metadata.url) }}&lt;/span&gt;&lt;span class=&quot;hljs-template-tag&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;hljs-name&quot;&gt;endset&lt;/span&gt; &lt;span class=&quot;hljs-template-tag&quot;&gt;%}&lt;/span&gt;&lt;span class=&quot;language-xml&quot;&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;entry&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;title&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;hljs-template-variable&quot;&gt;{{ post.data.title }}&lt;/span&gt;&lt;span class=&quot;language-xml&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;title&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;link&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;href&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;hljs-template-variable&quot;&gt;{{ absolutePostUrl }}&lt;/span&gt;&lt;span class=&quot;language-xml&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&lt;/span&gt;/&amp;gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;updated&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;hljs-template-variable&quot;&gt;{{ post.date | rssDate }}&lt;/span&gt;&lt;span class=&quot;language-xml&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;updated&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;id&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;hljs-template-variable&quot;&gt;{{ absolutePostUrl }}&lt;/span&gt;&lt;span class=&quot;language-xml&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;id&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;type&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;html&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;hljs-template-variable&quot;&gt;{{ post.templateContent | htmlToAbsoluteUrls(absolutePostUrl) }}&lt;/span&gt;&lt;span class=&quot;language-xml&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;content&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;entry&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;  {%- endfor %}&lt;br /&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;feed&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tag your posts with &lt;code&gt;til&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;---&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;title:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;Multiple feeds with 11ty&amp;#x27;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;date:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;2024-11-19T12:00:00&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;layout:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;default&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;tags:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;til&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-meta&quot;&gt;---&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  
  <entry>
    <title>How to handle PII using Marten</title>
    <link href="https://alexanderzeitler.com/articles/pii-marten/"/>
    <updated>2024-11-20T23:30:00Z</updated>
    <id>https://alexanderzeitler.com/articles/pii-marten/</id>
    <content type="html">&lt;p&gt;&lt;a href=&quot;https://mysticmind.dev/&quot;&gt;Babu&lt;/a&gt; has created a series of posts on how to handle PII using &lt;a href=&quot;https://martendb.io/&quot;&gt;Marten&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://mysticmind.dev/personally-identifiable-information-masking-in-marten-documents-using-partial-updatepatching&quot;&gt;Personally Identifiable Information masking in Marten documents using partial update/patching&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://mysticmind.dev/encrypting-personally-identifiable-information-at-rest-in-marten-documents&quot;&gt;Encrypting Personally Identifiable Information at rest in Marten documents&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://mysticmind.dev/encrypting-and-crypto-shredding-of-pii-data-in-marten-documents-using-hashicorp-vault&quot;&gt;Encrypting and crypto-shredding of PII data in Marten documents using HashiCorp Vault&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>ASP.NET Core TagHelpers: underrated feature of an underrated framework</title>
    <link href="https://alexanderzeitler.com/articles/asp-net-core-taghelpers-view-composition-vsa-vertical-slices-architecture-underrated-feature-underrated-framework/"/>
    <updated>2024-11-21T18:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/asp-net-core-taghelpers-view-composition-vsa-vertical-slices-architecture-underrated-feature-underrated-framework/</id>
    <content type="html">&lt;h2&gt;The State of ASP.NET Core MVC&lt;/h2&gt;
&lt;p&gt;ASP.NET Core TagHelpers have been around for quite some time but only few people seem to like them. Here&#39;s why I think they&#39;re awesome.&lt;/p&gt;
&lt;p&gt;ASP.NET Core - and  even more: MVC - is one of the most underrated Web Frameworks. This might be an unpopular opinion these days when Single Page Apps, JSX/TSX, Next.js and SSR are all the rage.&lt;/p&gt;
&lt;p&gt;Even Microsoft thinks they need to move away from the old stuff and build shiny new things like Blazor, which - surprise, surprise - suddenly was given a plain SSR mode.&lt;/p&gt;
&lt;h2&gt;ASP.NET Core Tag Helper Basics&lt;/h2&gt;
&lt;p&gt;Razor Syntax (the language used in MVC Views and Razor Pages) has been around for ages, and it&#39;s a powerful language.&lt;/p&gt;
&lt;p&gt;You can have iterators, C# code blocks, includes (partials) and you can inject everything from the IoC container.&lt;/p&gt;
&lt;p&gt;And - to get back to the beginning of this post: you can have tag helpers.&lt;/p&gt;
&lt;p&gt;TagHelpers can be used at the tag level (build a new HTML tag) or at the attribute level (add attributes to existing HTML tags).&lt;/p&gt;
&lt;p&gt;The ladder is heavily used by the great &lt;a href=&quot;https://www.nuget.org/packages/Htmx.TagHelpers&quot;&gt;HTMX TagHelpers library for .NET&lt;/a&gt; maintained by &lt;a href=&quot;https://khalidabuhakmeh.com/&quot;&gt;Khalid&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This also shows the great IDE support for Razor and TagHelpers in JetBrains Rider. &amp;quot;Go To Definition&amp;quot; and &amp;quot;Find Usages&amp;quot; works great as well as refactoring.&lt;/p&gt;
&lt;p&gt;A popular example for HTML Tag TagHelpers is the &lt;code&gt;partial&lt;/code&gt; Tag Helper provided by ASP.NET Core itself:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;partial&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;name&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;SomePartialRazorView&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;model&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;SomeModel&amp;quot;&lt;/span&gt; /&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using TagHelpers, we can either create HTML Tags or attributes that encapsulate functionality.&lt;/p&gt;
&lt;p&gt;A simple TagHelper that creates a custom element could look like this:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; Microsoft.AspNetCore.Razor.TagHelpers;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;MyApp.TagHelpers&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-title&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;MyTagHelper&lt;/span&gt; : &lt;span class=&quot;hljs-title&quot;&gt;TagHelper&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; Text { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;set&lt;/span&gt;; } &lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt; Number { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;set&lt;/span&gt;; } &lt;br /&gt; &lt;br /&gt;    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Process&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;TagHelperContext context, TagHelperOutput output&lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;    {&lt;br /&gt;        &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; (&lt;span class=&quot;hljs-built_in&quot;&gt;int&lt;/span&gt; i = &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;; i &amp;lt; Number; i++)&lt;br /&gt;        {&lt;br /&gt;            output.Content.AppendHtml(&lt;span class=&quot;hljs-string&quot;&gt;$&amp;quot;&lt;span class=&quot;hljs-subst&quot;&gt;{Text}&lt;/span&gt;&amp;lt;br/&amp;gt;&amp;quot;&lt;/span&gt;);&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The TagHelper gets registered in &lt;code&gt;ViewImports.cshtml&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;@addTagHelper MyApp.TagHelpers.MyTagHelper, MyAssembly&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or we can just register all TagHelpers defined within our app:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;@addTagHelper *, MyAssembly&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The TagHelper can be used in any Razor View like this:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;my-tag-helper&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;text&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Some text&amp;quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;number&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;5&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;my-tag-helper&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The generated HTML output will be this:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;Some text&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;br&lt;/span&gt;/&amp;gt;&lt;/span&gt;&lt;br /&gt;Some text&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;br&lt;/span&gt;/&amp;gt;&lt;/span&gt;&lt;br /&gt;Some text&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;br&lt;/span&gt;/&amp;gt;&lt;/span&gt;&lt;br /&gt;Some text&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;br&lt;/span&gt;/&amp;gt;&lt;/span&gt;&lt;br /&gt;Some text&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;br&lt;/span&gt;/&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Razor Views in Tag Helpers&lt;/h2&gt;
&lt;p&gt;What has been holding me back to make more use of Tag Helpers has been the need to build the HTML manually, sort of and the lack of ability to render Tag Helper in a nested style or having child content at all.&lt;/p&gt;
&lt;p&gt;After some research I found out that it&#39;s actually possible to have child content, nest Tag Helpers and even better: have Razor Views for Tag Helpers instead of creating the content from strings. Having Razor views at hand is especially useful when using Tailwind CSS which builds the CSS file on the fly.&lt;/p&gt;
&lt;p&gt;Long story short: this is what a base class for a Tag Helper, that loads its content from a Razor View, could look like:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;RazorTagHelperBase&lt;/span&gt;&amp;lt;&lt;span class=&quot;hljs-title&quot;&gt;TModel&lt;/span&gt;&amp;gt; : &lt;span class=&quot;hljs-title&quot;&gt;TagHelper&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;  [&lt;span class=&quot;hljs-meta&quot;&gt;HtmlAttributeNotBound&lt;/span&gt;] [ViewContext] &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; ViewContext ViewContext { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;set&lt;/span&gt;; }&lt;br /&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;private&lt;/span&gt; IHtmlHelper _htmlHelper;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;RazorTagHelperBase&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;    IHtmlHelper htmlHelper&lt;br /&gt;  &lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;    _htmlHelper = htmlHelper;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; _partialName;&lt;br /&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;private&lt;/span&gt; TModel? _model;&lt;br /&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;bool&lt;/span&gt; _allowChildContent;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Task&lt;/span&gt;&amp;lt;&lt;span class=&quot;hljs-title&quot;&gt;IHtmlContent&lt;/span&gt;&amp;gt; &lt;span class=&quot;hljs-title&quot;&gt;RenderPartial&lt;/span&gt;&amp;lt;&lt;span class=&quot;hljs-title&quot;&gt;T&lt;/span&gt;&amp;gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;    [AspMvcPartialView] &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; partialName,&lt;br /&gt;    TModel model&lt;br /&gt;  &lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;    (_htmlHelper &lt;span class=&quot;hljs-keyword&quot;&gt;as&lt;/span&gt; IViewContextAware).Contextualize(ViewContext);&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; _htmlHelper.PartialAsync(partialName, model);&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-comment&quot;&gt;&lt;span class=&quot;hljs-doctag&quot;&gt;///&lt;/span&gt; &lt;span class=&quot;hljs-doctag&quot;&gt;&amp;lt;summary&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-comment&quot;&gt;&lt;span class=&quot;hljs-doctag&quot;&gt;///&lt;/span&gt; &lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-comment&quot;&gt;&lt;span class=&quot;hljs-doctag&quot;&gt;///&lt;/span&gt; &lt;span class=&quot;hljs-doctag&quot;&gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-comment&quot;&gt;&lt;span class=&quot;hljs-doctag&quot;&gt;///&lt;/span&gt; &lt;span class=&quot;hljs-doctag&quot;&gt;&amp;lt;param name=&amp;quot;partialName&amp;quot;&amp;gt;&lt;/span&gt;&lt;span class=&quot;hljs-doctag&quot;&gt;&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-comment&quot;&gt;&lt;span class=&quot;hljs-doctag&quot;&gt;///&lt;/span&gt; &lt;span class=&quot;hljs-doctag&quot;&gt;&amp;lt;param name=&amp;quot;model&amp;quot;&amp;gt;&lt;/span&gt;&lt;span class=&quot;hljs-doctag&quot;&gt;&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-comment&quot;&gt;&lt;span class=&quot;hljs-doctag&quot;&gt;///&lt;/span&gt; &lt;span class=&quot;hljs-doctag&quot;&gt;&amp;lt;param name=&amp;quot;allowChildContent&amp;quot;&amp;gt;&lt;/span&gt;&lt;span class=&quot;hljs-doctag&quot;&gt;&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;SetPartialName&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;    [AspMvcView] [AspMvcPartialView] &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; partialName,&lt;br /&gt;    TModel? model,&lt;br /&gt;    &lt;span class=&quot;hljs-built_in&quot;&gt;bool&lt;/span&gt; allowChildContent = &lt;span class=&quot;hljs-literal&quot;&gt;false&lt;/span&gt;&lt;br /&gt;  &lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;    _partialName = partialName;&lt;br /&gt;    _model = model;&lt;br /&gt;    _allowChildContent = allowChildContent;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;async&lt;/span&gt; Task &lt;span class=&quot;hljs-title&quot;&gt;ProcessAsync&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;    TagHelperContext context,&lt;br /&gt;    TagHelperOutput output&lt;br /&gt;  &lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;try&lt;/span&gt;&lt;br /&gt;    {&lt;br /&gt;      (_htmlHelper &lt;span class=&quot;hljs-keyword&quot;&gt;as&lt;/span&gt; IViewContextAware)?.Contextualize(ViewContext);&lt;br /&gt;&lt;br /&gt;      IHtmlContent content;&lt;br /&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; error;&lt;br /&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (_allowChildContent)&lt;br /&gt;      {&lt;br /&gt;        &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; childContent = &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; output.GetChildContentAsync();&lt;br /&gt;        &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; children = childContent.GetContent();&lt;br /&gt;        &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (_model &lt;span class=&quot;hljs-keyword&quot;&gt;is&lt;/span&gt; IHasChildContent modelWithChildContent)&lt;br /&gt;          modelWithChildContent.ChildContent = children;&lt;br /&gt;        &lt;span class=&quot;hljs-keyword&quot;&gt;else&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;hljs-keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; InvalidOperationException(&lt;br /&gt;            &lt;span class=&quot;hljs-string&quot;&gt;$&amp;quot;Model of type &lt;span class=&quot;hljs-subst&quot;&gt;{&lt;span class=&quot;hljs-keyword&quot;&gt;typeof&lt;/span&gt;(TModel).Name}&lt;/span&gt; does not implement IHasChildContent&amp;quot;&lt;/span&gt;&lt;br /&gt;          );&lt;br /&gt;&lt;br /&gt;        content = &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; _htmlHelper.PartialAsync(_partialName, modelWithChildContent);&lt;br /&gt;      }&lt;br /&gt;      &lt;span class=&quot;hljs-keyword&quot;&gt;else&lt;/span&gt;&lt;br /&gt;      {&lt;br /&gt;        content = &lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; _htmlHelper.PartialAsync(_partialName, _model);&lt;br /&gt;      }&lt;br /&gt;&lt;br /&gt;      output.SuppressOutput();&lt;br /&gt;      output.TagMode = TagMode.StartTagAndEndTag;&lt;br /&gt;      output.PreContent.AppendHtml(content);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;catch&lt;/span&gt; (Exception exception)&lt;br /&gt;    {&lt;br /&gt;      output.PreContent.AppendHtml(&lt;span class=&quot;hljs-keyword&quot;&gt;await&lt;/span&gt; _htmlHelper.PartialAsync(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;RazorTagHelperBase&amp;quot;&lt;/span&gt;, exception));&lt;br /&gt;      Console.WriteLine(exception);&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All you have to do is to create a class that derives from this class, define a model class or record type.&lt;/p&gt;
&lt;p&gt;If you want to allow the Tag Helper to render child content, your model has to implement this interface:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;IHasChildContent&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;  &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt;? ChildContent { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;set&lt;/span&gt;; }&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&#39;s assume we&#39;re building a Tag Helper that renders a &amp;quot;thread&amp;quot; for discussions which we would use like this:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;thread&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;items&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;@Model.Items&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;   &lt;br /&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;thread&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The C# class:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Thread&lt;/span&gt;&lt;br /&gt;{&lt;br /&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; Title { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;set&lt;/span&gt;; }&lt;br /&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; List&amp;lt;ThreadItem&amp;gt; Items { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;set&lt;/span&gt;; } = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt;();&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;ThreadTagHelper&lt;/span&gt; : &lt;span class=&quot;hljs-title&quot;&gt;RazorTagHelperBase&lt;/span&gt;&amp;lt;&lt;span class=&quot;hljs-title&quot;&gt;Thread&lt;/span&gt;&amp;gt;&lt;br /&gt;{&lt;br /&gt;  [&lt;span class=&quot;hljs-meta&quot;&gt;HtmlAttributeName(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;title&amp;quot;&lt;/span&gt;)&lt;/span&gt;] &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-built_in&quot;&gt;string&lt;/span&gt; Title { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;set&lt;/span&gt;; }&lt;br /&gt;  [&lt;span class=&quot;hljs-meta&quot;&gt;HtmlAttributeName(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;thread-items&amp;quot;&lt;/span&gt;)&lt;/span&gt;] &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; List&amp;lt;ThreadItem&amp;gt; Items { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;set&lt;/span&gt;; } = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt;();&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;ThreadTagHelper&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;    IHtmlHelper htmlHelper&lt;br /&gt;  &lt;/span&gt;) : &lt;span class=&quot;hljs-title&quot;&gt;base&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;htmlHelper&lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;override&lt;/span&gt; Task &lt;span class=&quot;hljs-title&quot;&gt;ProcessAsync&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;br /&gt;    TagHelperContext context,&lt;br /&gt;    TagHelperOutput output&lt;br /&gt;  &lt;/span&gt;)&lt;/span&gt;&lt;br /&gt;  {&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; thread = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; Thread&lt;br /&gt;    {&lt;br /&gt;      Title = Title,&lt;br /&gt;      Items = Items&lt;br /&gt;    };&lt;br /&gt;    SetPartialName(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;ThreadTagHelper&amp;quot;&lt;/span&gt;, thread);&lt;br /&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;base&lt;/span&gt;.ProcessAsync(context, output);&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Razor view:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;@model ThreadTagHelper.Thread&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;section&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;aria-labelledby&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;notes-title&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;class&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;bg-white shadow sm:rounded-lg sm:overflow-hidden&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;class&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;divide-y divide-gray-200&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;class&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;px-4 py-5 sm:px-6&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;h2&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;class&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;text-lg font-medium text-gray-900&amp;quot;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;id&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;notes-title&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;          @Model.Title&lt;br /&gt;        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;h2&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;      @{ var index = 0; }&lt;br /&gt;      @foreach (var threadItem in Model.Items)&lt;br /&gt;      {&lt;br /&gt;        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;thread-item&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;thread-item&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;@threadItem&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;thread-item&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;        @if (index + 1 &amp;lt; Model.Items.Count)&lt;br /&gt;        {&lt;br /&gt;          &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;class&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;relative pb-4&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;span&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;aria-hidden&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;true&amp;quot;&lt;/span&gt;&lt;br /&gt;                  &lt;span class=&quot;hljs-attr&quot;&gt;class&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;absolute top-1 left-6 -ml-px h-full w-0.5 bg-gray-300&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;span&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;        }&lt;br /&gt;      }&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;div&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;section&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, the Razor view itself contains another Tag Helper &lt;code&gt;&amp;lt;thread-item&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Tag Helpers for View Composition in Distributed Systems / Vertical Slice Architecture&lt;/h2&gt;
&lt;p&gt;Two days ago, Khalid &lt;a href=&quot;https://khalidabuhakmeh.com/dynamic-htmx-islands-with-aspnet-core&quot;&gt;posted&lt;/a&gt; about creating an &amp;quot;Island&amp;quot; TagHelper, which loads a view fragment on demand.&lt;/p&gt;
&lt;p&gt;This is quite similar to what I&#39;m using in my Vertical Slice Architecture solutions for View Composition.&lt;/p&gt;
&lt;p&gt;Different contexts / features are providing self-contained components, which provide fragments of the UI:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/asp-net-core-taghelpers-view-composition-vsa-vertical-slices-architecture-underrated-feature-underrated-framework/view-composition.png&quot; alt=&quot;View Composition in an online store&quot; /&gt;&lt;/p&gt;
&lt;p&gt;While the parts highlighted yellow in the screenshot, are provided by a Catalog Context (Service), the parts highlighted blue are provided by a Pricing Context (Service).&lt;/p&gt;
&lt;p&gt;In my case, the components are implemented using TagHelpers as shown above.&lt;/p&gt;
&lt;p&gt;Depending on the level of Coupling/Decoupling your .NET Solutions/Projects have/allow, they can reside in a single project, or they can reside in shared projects and be imported in the &lt;code&gt;_ViewImports.cshtml&lt;/code&gt; as shown above as well.&lt;/p&gt;
&lt;p&gt;Also depending on your coupling/decoupling model, the Tag Helpers can either call services via HTTP (as Khalid has shown) to get their views, or they access their contexts in process and render the result using Razor views as shown above.&lt;/p&gt;
&lt;p&gt;That way you can have decoupled, self-contained components in your server side rendered UI (which is the place where the coupling should actually happen).&lt;/p&gt;
&lt;p&gt;And that&#39;s the major reason why I think that ASP.NET Core TagHelpers are one of the most underrated features of a highly underrated web framework.&lt;/p&gt;
&lt;p&gt;Even more, since JetBrains Rider - whose Razor support is top-notch - &lt;a href=&quot;https://blog.jetbrains.com/dotnet/2024/10/16/rider-reveal-livestream-big-news-for-dotnet-and-game-devs/&quot;&gt;can now be used for free for non-commercial projects&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>11ty redirects</title>
    <link href="https://alexanderzeitler.com/articles/eleventy-11ty-redirects/"/>
    <updated>2024-11-22T12:30:00Z</updated>
    <id>https://alexanderzeitler.com/articles/eleventy-11ty-redirects/</id>
    <content type="html">&lt;p&gt;Sometimes you want to change urls in 11ty without breaking them.&lt;/p&gt;
&lt;p&gt;There are several posts out there explaining how it can be done when your site is running using Netlify, but there&#39;s little content how to do it with plain 11ty.&lt;/p&gt;
&lt;p&gt;Lucky me, I found &lt;a href=&quot;https://brianm.me/posts/eleventy-redirect-from/&quot;&gt;this post&lt;/a&gt;, which shows a solution by introducing a new 11ty template named &lt;code&gt;eleventy-redirect.njk&lt;/code&gt; in the root folder.&lt;/p&gt;
&lt;p&gt;In your posts (the ones to which the redirect should point to), add this to your front matter (given your posts are in the &lt;code&gt;articles&lt;/code&gt; folder):&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;---&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;redirect_from:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;/articles/my-old-url&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-meta&quot;&gt;---&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make sure to delete the old directory/files from which the direct should happen.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>&quot;CRUD APIs are poor design&quot; - a follow up</title>
    <link href="https://alexanderzeitler.com/articles/crud-apis-are-poor-design/"/>
    <updated>2024-12-05T12:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/crud-apis-are-poor-design/</id>
    <content type="html">&lt;h2&gt;Derek&#39;s statement...&lt;/h2&gt;
&lt;p&gt;The gist of &lt;a href=&quot;https://www.youtube.com/watch?v=aRhfS_SLTUQ&quot;&gt;Derek&#39;s video&lt;/a&gt;, which he published yesterday, is this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Typical API Design consists of building around database records and Create Read Update Delete.  Making nothing more than a UI or HTTP API around a database.  But that database schema was driven by business processes.  But if those business processes don&#39;t live in your system, then trying to evolve your schema can be challenging.  Instead of modeling data structure, model business processes and the data behind them.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;... and my follow-up&lt;/h2&gt;
&lt;p&gt;As with most of Derek&#39;s videos, I completely agree with him.&lt;/p&gt;
&lt;p&gt;Yet, I want to add one or two thoughts.&lt;/p&gt;
&lt;p&gt;Back in the days, when I was young, we only had what&#39;s called &amp;quot;server side rendering&amp;quot; (SSR) these days.&lt;/p&gt;
&lt;p&gt;When HTTP JSON APIs and Single Page Applications took off almost 15 years ago, this seemed to be the promised land - even more so with the advent of Microservices.&lt;/p&gt;
&lt;p&gt;And yet here we are today, with failed projects, drowning in either technical debt or complexity - or both.&lt;/p&gt;
&lt;p&gt;While a lot of things we&#39;ve done as developers (and architects) in the past, have been doubted, CRUD still is accepted as the default storage pattern for most applications.&lt;/p&gt;
&lt;p&gt;At the other side of the applications, having an HTTP JSON API, also has become the default.&lt;/p&gt;
&lt;p&gt;Be it CRUD or HTTP JSON APIs: there has been built a ton of tooling around both of them, often to handle the complexity which these patterns are causing.&lt;/p&gt;
&lt;p&gt;But to be honest: I think this is a dead end.&lt;/p&gt;
&lt;p&gt;While it is compelling to sell tools, that deal with complexity and other issues caused by CRUD and HTTP JSON APIs, it will never solve the root cause.&lt;/p&gt;
&lt;p&gt;The root cause - in my opinion - is trying to solve business problems with the least amount of business understanding and the biggest amount of technology possible. It may sound like an exaggeration, but I&#39;ve seen it more than often in real world projects.&lt;/p&gt;
&lt;p&gt;&amp;quot;So, what&#39;s your solution?&amp;quot; one might ask.&lt;/p&gt;
&lt;p&gt;I wouldn&#39;t call it &amp;quot;solution&amp;quot; but I&#39;m explaining my approach.&lt;/p&gt;
&lt;p&gt;In short: less complexity and more explicit code.&lt;/p&gt;
&lt;p&gt;What does this mean in practice?&lt;/p&gt;
&lt;h2&gt;Less complexity&lt;/h2&gt;
&lt;p&gt;By &amp;quot;less complexity&amp;quot;, I mean choosing a boring stack - in my case that&#39;s ASP.NET Core MVC.&lt;/p&gt;
&lt;p&gt;It&#39;s production tested for ages. Especially when building something new - in terms of business model - the more code you create in the beginning, the more effort you have to put in maintenance and changes. And when exploring a new business model, you will have a lot of changes.&lt;/p&gt;
&lt;p&gt;You&#39;re creating legacy code with no need to do so.&lt;/p&gt;
&lt;p&gt;&amp;quot;Will it scale&amp;quot; is the wrong question if you have no customers.&lt;/p&gt;
&lt;p&gt;And if you do get more customers, how do you scale in terms of business and features, if nobody knows why this data even exists and who&#39;s changing it?&lt;/p&gt;
&lt;p&gt;&amp;quot;API first&amp;quot; has been all the rage for years, but from my experience a lot of APIs being built are used only by the product vendor in the SPA frontend itself.&lt;/p&gt;
&lt;p&gt;So why not start with a simple sever side rendered UI in the beginning?&lt;/p&gt;
&lt;p&gt;Due to lower ceremony (think AuthN/AuthZ, mapping data and whatnot), you can focus on the business processes (yes, that&#39;s what earns the money - not the code).&lt;/p&gt;
&lt;p&gt;To gather a good understanding of the processes, I would recommend using techniques like Event Storming or Event Modeling (my personal choice).&lt;/p&gt;
&lt;p&gt;That way you get an understanding of which data belongs together (coupling/cohesion), hence should exist in the same context or vertical slice.&lt;/p&gt;
&lt;p&gt;Building the UI for these slices with minimal effort as described above, you&#39;ll notice that data that is - and should be - coupled will be placed closely together in the UI as well.&lt;/p&gt;
&lt;p&gt;Which brings us back to APIs: your server side rendered web front end is the first implementation (&amp;quot;representation&amp;quot;) of your API - it&#39;s just serving HTML instead of JSON.&lt;/p&gt;
&lt;p&gt;Now you might tell me: &amp;quot;but its structure is completely different from  what my JSON API would be&amp;quot;.&lt;/p&gt;
&lt;p&gt;Yes, because people are building JSON APIs around entities instead of processes.&lt;/p&gt;
&lt;p&gt;Now instead, think about providing a JSON API that looks exactly what your HTML forms or tables (lists, grids) look like - just in JSON.&lt;/p&gt;
&lt;p&gt;Hint: You don&#39;t need to re-invent the wheel here: Hypermedia has got you covered - give HAL, HAL forms and real REST APIs a chance.&lt;/p&gt;
&lt;p&gt;And maybe, maybe out of sudden there&#39;s no need for GraphQL any longer, because data that belongs together isn&#39;t spread across services/contexts/slices.&lt;/p&gt;
&lt;h2&gt;Explicit code&lt;/h2&gt;
&lt;p&gt;Now you might ask another question: &amp;quot;but I have so many different views, how do I get all the entity data together?&amp;quot;&lt;/p&gt;
&lt;p&gt;The answer is: don&#39;t use one model to rule them all.&lt;/p&gt;
&lt;p&gt;Create dedicated models for specific views, because these are only representations of your data.&lt;/p&gt;
&lt;p&gt;Which brings us to the next question: &amp;quot;what is my data actually?&amp;quot;&lt;/p&gt;
&lt;p&gt;Data doesn&#39;t &amp;quot;just&amp;quot; exist - it comes to life by filling forms to meet the needs of business capabilities and processes.&lt;/p&gt;
&lt;p&gt;Calling it &amp;quot;filling forms&amp;quot; doesn&#39;t do it justice - you&#39;re completing a step in a process.&lt;/p&gt;
&lt;p&gt;In terms of business, this step has a meaning and most of the time a name already.&lt;/p&gt;
&lt;p&gt;Examples are &amp;quot;add item to cart&amp;quot;, &amp;quot;book a room&amp;quot;, &amp;quot;place order&amp;quot; - you name it.&lt;/p&gt;
&lt;p&gt;Now we&#39;re closing the loop with Derek&#39;s video:&lt;/p&gt;
&lt;p&gt;Model the events that have happened, after you completed the steps mentioned above successfully.&lt;/p&gt;
&lt;p&gt;You&#39;ll end up with &amp;quot;Item added/removed to/from cart&amp;quot;, &amp;quot;Room booked&amp;quot;, &amp;quot;Order placed&amp;quot;.&lt;/p&gt;
&lt;p&gt;Store these events instead of the state of a cart, room bookings or orders.&lt;/p&gt;
&lt;p&gt;Now you can start asking questions like the business wants it to do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;Which items have been removed from the cart before an order has been placed?&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;Which options (beamer, whiteboard etc., room size) have been chosen/unselected before the room booking happened?&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Because business wants to understand which products don&#39;t sell and why.&lt;/p&gt;
&lt;p&gt;The immutable truth is in your persisted events that will be there forever (if you want it to) and anything else are just different representations of the truth.&lt;/p&gt;
&lt;p&gt;Combined with Event Modeling you make it very explicit where your data comes from - properties for some state don&#39;t just exist - they&#39;re caused by a command and even more important: they&#39;re persisted in one or more events - now you are able to point exactly at a diagram or in your code and tell &lt;em&gt;when&lt;/em&gt; a property comes to life and when it gets changed - and on top you get the reason for it.&lt;/p&gt;
&lt;p&gt;Being able to evolve your code is the only way to keep your business up in the long term.
By knowing where your data is caused and modified, you&#39;re able to delete or change code (evolve) without being afraid of breaking things for your users when making required changes.&lt;/p&gt;
&lt;p&gt;So, in addition to Hypermedia/HAL, you might consider Event Sourcing instead of CRUD.&lt;/p&gt;
&lt;p&gt;And that&#39;s my thoughts around this topic.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>DBeaver supports projects</title>
    <link href="https://alexanderzeitler.com/articles/dbeaver-projects/"/>
    <updated>2024-12-19T02:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/dbeaver-projects/</id>
    <content type="html">&lt;p&gt;Just noticed DBeaver has projects, you can organize your connections.&lt;/p&gt;
&lt;p&gt;You can start a new project via File/New:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/dbeaver-projects/project-wizard.png&quot; alt=&quot;DBeaver Project wizard&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You pick a custom project location by unchecking &amp;quot;Use default location&amp;quot;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/dbeaver-projects/custom-project-location.png&quot; alt=&quot;Custom project location&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The active project can be selected here:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/dbeaver-projects/choose-active-dbeaver-project.png&quot; alt=&quot;Select the active DBeaver project&quot; /&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>How systems should be built</title>
    <link href="https://alexanderzeitler.com/articles/how-systems-should-be-built/"/>
    <updated>2025-01-03T12:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/how-systems-should-be-built/</id>
    <content type="html">&lt;p&gt;How systems should be built: easy to change when business changes.&lt;/p&gt;
&lt;p&gt;How most systems are built: easy to change when tech changes.&lt;/p&gt;
&lt;p&gt;Reality: business changes much more frequently than the technology.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Why I switched from ASP.NET Core to Ruby on Rails</title>
    <link href="https://alexanderzeitler.com/articles/why-i-switched-from-aspnet-core-to-ruby-on-rails/"/>
    <updated>2026-01-06T05:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/why-i-switched-from-aspnet-core-to-ruby-on-rails/</id>
    <content type="html">&lt;p&gt;If you have been following me on &lt;a href=&quot;https://x.com/lxztlr&quot;&gt;X&lt;/a&gt; for the past few months, you might have noticed that I&#39;ve moved on from ASP.NET Core to Ruby on Rails. Some people asked why...&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you are looking for a detailed technical - or even performance - comparison between Ruby on Rails and ASP.NET Core, this article is not for you - it only contains opinions, learnings and stories from everyday usage.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Setting the stage&lt;/h2&gt;
&lt;p&gt;Back in early 2015, I&#39;ve said &lt;a href=&quot;https://alexanderzeitler.com/articles/Farewell&quot;&gt;Farewell&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It was a farewell to Windows and .NET for technical reasons.&lt;/p&gt;
&lt;p&gt;Windows felt bumpy and .NET wasn&#39;t available on Linux (I know there&#39;s been Mono).&lt;/p&gt;
&lt;p&gt;So I went along with Linux and Node.js for several years.&lt;/p&gt;
&lt;p&gt;Time moved on and somewhere between 2020 and 2021 I gave .NET - now fully cross platform - another shot. This would not have happened without JetBrains Rider, btw.&lt;/p&gt;
&lt;p&gt;I&#39;ve started building some stuff using C# and F# and ASP.NET Core, of course.&lt;/p&gt;
&lt;p&gt;Stars really seemed to align again, when I discovered &lt;a href=&quot;https://martendb.io/&quot;&gt;Marten&lt;/a&gt; and &lt;a href=&quot;https://wolverinefx.net/&quot;&gt;Wolverine&lt;/a&gt; for Event Sourcing and Messaging.&lt;/p&gt;
&lt;p&gt;With ASP.NET Core MVC, HTMX, Marten, Wolverine, Vertical Slice Architecture and Rider I&#39;ve enjoyed software development like I didn&#39;t for ages.&lt;/p&gt;
&lt;p&gt;I&#39;ve written almost 30 articles for a major German .NET magazine during that time about these topics.&lt;/p&gt;
&lt;h2&gt;Bright stars and dark clouds&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;[Narrators voice: Scene 1]&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;However, there was an elephant in the room: Microsoft.&lt;/p&gt;
&lt;p&gt;Back around 2018, I had the questionable joy of dealing with Microsoft Germany in a customer project.&lt;/p&gt;
&lt;p&gt;After finishing it, I&#39;ve decided to never do anything related to Microsoft Azure again.&lt;/p&gt;
&lt;p&gt;&amp;quot;Why is this important?&amp;quot; you may ask.&lt;/p&gt;
&lt;p&gt;While .NET is open source software (OSS) at first sight, it is still tied to Microsoft. Back in the days when it has been Windows only, it has been a vehicle driving sales for Visual Studio and kept people on Windows.&lt;/p&gt;
&lt;p&gt;As cloud has become Microsoft&#39;s major business, .NET has aimed at getting developers to use Azure for their deployments.&lt;/p&gt;
&lt;p&gt;Not by making it the only choice, but by making it the default and easy one - and nobody gets fired for choosing Microsoft.&lt;/p&gt;
&lt;p&gt;As I&#39;ve been using Docker since it&#39;s early days, this hasn&#39;t been much of an issue for me - I could re-use my deployment scripts from Node.js with almost zero changes - give me a VPS and I&#39;m good to go.&lt;/p&gt;
&lt;p&gt;While I could ignore most of this stuff, it has always been around - the well known &amp;quot;.NET Drama&amp;quot; like not releasing the debugger as open source, constantly re-inventing successful community packages and stuff like that.&lt;/p&gt;
&lt;p&gt;Speaking of re-inventing stuff: Microsoft has a knack for building UI frameworks nobody likes (except the chosen few). Be it Silveright, MAUI or whatever year you pick.&lt;/p&gt;
&lt;p&gt;Back in 2018, Microsoft announced &amp;quot;Blazor&amp;quot;, their latest idea of how component based web development should be done. Driven by the pressure of client side frameworks like React and whatever Next.js is, they started building something similar based on .NET and Web Assembly.&lt;/p&gt;
&lt;p&gt;Over the years this has evolved into something with several &lt;a href=&quot;https://en.wikipedia.org/wiki/Blazor#Hosting_models&quot;&gt;hosting models&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I don&#39;t know what&#39;s its current state, but several people tried to make it work with libraries like HTMX and it seemed to be a PITA to me - do it the way Microsoft invented it or just stick with ASP.NET Core MVC.&lt;/p&gt;
&lt;p&gt;I did the latter because &amp;quot;what should possibly go wrong?&amp;quot;. I was wrong.
Over time, Microsoft started to make &amp;quot;optimiziations&amp;quot; in ASP.NET Core which started to more or less break stuff in MVC.&lt;/p&gt;
&lt;p&gt;Furthermore, it is virtually no longer actively maintained, as can be seen from the GitHub issues.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;[Narrators voice: Scene 2]&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;With short breaks, Linux has been my daily driver (speaking of Desktop OS) since mid 2014 - Ubuntu most of the time.&lt;/p&gt;
&lt;p&gt;Ubuntu has become more and more mainstream and at some point they&#39;ve decided to switch to Gnome Desktop as their default.&lt;/p&gt;
&lt;p&gt;While it became more polished, it also became less of a developer OS and it felt like the Desktop got in my way when I wanted to do things.&lt;/p&gt;
&lt;p&gt;Mid-2024 Linux got a new advocate: David Heinemeier Hannson (&lt;a href=&quot;https://dhh.dk/&quot;&gt;DHH&lt;/a&gt;) came up with &lt;a href=&quot;https://omakub.org/&quot;&gt;Omakub&lt;/a&gt;, &amp;quot;An Omakase Developer Setup for Ubuntu 24.04+&amp;quot;.&lt;/p&gt;
&lt;p&gt;It has been a nice match for my new TUXEDO Desktop. At least for while...&lt;/p&gt;
&lt;p&gt;While Alacritty and Tactile have been some nice improvements for Window management, it felt sort of unnatural with Ubuntu.&lt;/p&gt;
&lt;p&gt;As it turned out, DHH felt the same and Mid-2025 he came up with the real banger: &lt;a href=&quot;https://omarchy.org/&quot;&gt;Omarchy&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;A super polished Linux Desktop environment for developers based on Arch Linux and Hyprland, a tiling window manager.&lt;/p&gt;
&lt;p&gt;It has hit like a bombshell, and it is unlikely that you have not already heard about it.&lt;/p&gt;
&lt;p&gt;It&#39;s the daily driver on my Gen5 ThinkPad L14 since then and I&#39;ve installed it on several outdated Apple hardware like a Late 2012 Mac mini and a Mid-2012 MacBook Pro.&lt;/p&gt;
&lt;p&gt;It also got me back to vim as my primary editor by making &lt;a href=&quot;https://neovim.io/&quot;&gt;neovim&lt;/a&gt; the default for a lot of stuff in Omarchy.&lt;/p&gt;
&lt;p&gt;I even started using it for ASP.NET Core development and I already started to write a blog post on how to do full time ASP.NET Core development using neovim in summer 2025.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;[Narrators voice: Scene 3]&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I&#39;ve been a software developer most of my career but for some reasons I became a CEO in manufacturing by the end of 2024.&lt;/p&gt;
&lt;p&gt;At that time, the company has been running on a typical stack:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Windows 10&lt;/li&gt;
&lt;li&gt;Microsoft Office&lt;/li&gt;
&lt;li&gt;sage business software&lt;/li&gt;
&lt;li&gt;SolidWorks 3D CAD&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While I could accept things, I haven&#39;t been happy with it.&lt;/p&gt;
&lt;p&gt;The whole stack has been maintained by several companies via support contracts.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Side note: back in the late 90ties I&#39;ve been a sage Partner myself so I know a thing or two about the software in question.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Apart from the relationship with the system vendor, which is responsible for the network infrastructure, Windows and Office, user satisfaction with support is rather low.&lt;/p&gt;
&lt;p&gt;Given all the &amp;quot;you&#39;re not the customer, you&#39;re the product&amp;quot; features in Windows 11 and Office creeping in, I&#39;ve avoided the migration from Windows 10 for the company as long as I could.&lt;/p&gt;
&lt;p&gt;This paid off when Microsoft announced that Windows 10 support would be extended for another year in the EU.&lt;/p&gt;
&lt;p&gt;Speaking of SolidWorks: you get more support from the forums on the Internet than from official Partners you&#39;re paying for it.&lt;/p&gt;
&lt;p&gt;Regarding sage, things got worse when we tried to pull some data out of it and eventually getting rid of the software at all.&lt;/p&gt;
&lt;p&gt;Back in the days, this has been a no-brainer using pure SQL until 2008 and later DCOM.&lt;/p&gt;
&lt;p&gt;Trying this in 2025, DCOM has stopped working - as it turned out, libraries haven&#39;t been installed on intent (make the customer pay as often as you can).&lt;/p&gt;
&lt;p&gt;They tried to sell us a &amp;quot;project&amp;quot; where our &amp;quot;requirements would be evaluated&amp;quot; and then the typical dance would start - for a problem that could be purely solved using a single SQL SELECT statement until 2008 or roughly 10 lines of C# Code via DCOM.&lt;/p&gt;
&lt;p&gt;When I discovered this on a Saturday in mid-September 2025, the topic of proprietary ecosystems was finally dead to me.&lt;/p&gt;
&lt;p&gt;On that day, I decided that we would end our collaboration with all of the aforementioned software manufacturers by mid-2026 and also replace Windows entirely with Linux - remember: we&#39;re a manufacturing company, not a software business.&lt;/p&gt;
&lt;h2&gt;Omakase&lt;/h2&gt;
&lt;p&gt;As mentioned earlier, Omakub and Omarchy are based on the principle of &lt;a href=&quot;https://learn.omacom.io/3/omacom/76/omakase-computing&quot;&gt;Omacom&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Omacom stands for Omakase Computing. The word Omakase means &amp;quot;I&#39;ll leave it up to you&amp;quot; or &amp;quot;chef&#39;s choice&amp;quot; in Japanese.&lt;/p&gt;
&lt;p&gt;It&#39;s the idea that most people don&#39;t actually know what they want, at least not at first. That they&#39;re better off getting something beautifully curated and integrated from someone they trust to make competent, tasteful decisions rather than suffer from the paradox of choice.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;By watching DHHs videos on Omarchy, I found out that &lt;a href=&quot;https://rubyonrails.org/doctrine#omakase&quot;&gt; Ruby on Rails has been following this principle for a long time already&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As I&#39;ve been happy with the chef&#39;s choices in Omarchy, I&#39;ve decided to take a closer look at Rails.&lt;/p&gt;
&lt;p&gt;I managed to completely ignore it until this time - I didn&#39;t even look at it when I was looking for an cross platform alternative to ASP.NET back in 2014.&lt;/p&gt;
&lt;p&gt;Setting Rails up was a no-brainer thanks to mise which is the default in Omarchy to manage dev tools.&lt;/p&gt;
&lt;p&gt;After reading some Rails Guides on their website, I wanted to get results fast, so I opted for AI (&amp;quot;Vibe Coding&amp;quot;) to build some applications I would have built using ASP.NET Core anyway using Rails.&lt;/p&gt;
&lt;p&gt;Mid-September 2025 I&#39;ve been using ChatGPT most of the time and vibe coding a Rails app that would use Postgres and Tailwind started off quite well but at some point it got me bad vibes.&lt;/p&gt;
&lt;h2&gt;Good Vibes&lt;/h2&gt;
&lt;p&gt;As I&#39;ve posted some of my progress on X, people started to recommend Claude instead of ChatGPT and now things really took off.&lt;/p&gt;
&lt;p&gt;Within a few days I&#39;ve built an end-to-end app to track material requirements from the shop floor, incoming goods and invoice verification. This included stuff like stamping PDFs for approval.&lt;/p&gt;
&lt;p&gt;One thing I wanted to verify early has been deployments and this is where Rails and the &amp;quot;majestic monolith&amp;quot; you build really shines.&lt;/p&gt;
&lt;p&gt;When creating a new Rails app, it also initializes &lt;a href=&quot;https://kamal-deploy.org/&quot;&gt; Kamal&lt;/a&gt; - Omakase for deployments based on Docker.&lt;/p&gt;
&lt;p&gt;After providing a minimal set of credentials and settings I got the app deployed on our staging VM in our Proxmox setup.&lt;/p&gt;
&lt;p&gt;That has been the time when I was hooked already.&lt;/p&gt;
&lt;p&gt;But I wanted to build more - on the one hand, to continue testing Rails, and on the other, because I needed alternatives to the existing software for the reasons mentioned above.&lt;/p&gt;
&lt;p&gt;We&#39;ve been using Word for product documentation - customers don&#39;t want Markdown when they purchase industrial machinery or equipment.&lt;/p&gt;
&lt;p&gt;As I&#39;ve been looking for an alternative for this process, I came along AsciiDoc.
So I made a manual proof of concept for one of our machinery documentation first.&lt;/p&gt;
&lt;p&gt;Next, I wanted to have a platform to manage documentations (versions, text modules etc.).&lt;/p&gt;
&lt;p&gt;So I set up another Rails app using Claude and a few days later we&#39;ve been using this already to create a documentation for a just finished machinery project.&lt;/p&gt;
&lt;p&gt;The Rails app at this time already had HTML/PDF live preview and automated translation using the DeepL API. You could also manage the assets like images and pictograms (via Rails ActiveStorage).&lt;/p&gt;
&lt;p&gt;Sometimes the universe sends you a message.&lt;/p&gt;
&lt;p&gt;In October, the employee who prepares the time sheets for payroll accounting fell ill.&lt;/p&gt;
&lt;p&gt;I then did the payroll stuff and realized how little the current software supported us in this process.&lt;/p&gt;
&lt;p&gt;This made it clear what would happen next: software for time tracking and reporting using Rails.&lt;/p&gt;
&lt;p&gt;This time, however, using Event Sourcing.&lt;/p&gt;
&lt;p&gt;I picked &lt;a href=&quot;https://railseventstore.org/&quot;&gt;Rails Event Store&lt;/a&gt; and let Claude do it&#39;s job.&lt;/p&gt;
&lt;p&gt;Long story short: since December timesheets run on Rails.&lt;/p&gt;
&lt;p&gt;So as not to bore you further with apps, here is a short list of what else I have built with Rails and is already in use at our company:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Task management with completion tracking (uses SSE for notifications, for example)&lt;/li&gt;
&lt;li&gt;App for inventory recording, primarily used on smartphones (yes, this is possible with a monolith)&lt;/li&gt;
&lt;li&gt;An application that can be used to document decisions made during the development process of machines and systems and to perform risk assessments&lt;/li&gt;
&lt;li&gt;A customer portal for inquiries&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I am also working on a system for instant piping quotes that will be connected to Shopify.&lt;/p&gt;
&lt;p&gt;While I relied almost entirely on Claude at the beginning to quickly produce code (which I still reviewed), I naturally became more and more involved with Rails over time.&lt;/p&gt;
&lt;p&gt;In the process, I have also grown to appreciate and love Rails more and more.&lt;/p&gt;
&lt;p&gt;What excites me is the open approach, and that is exactly what I still found lacking in ASP.NET Core, even though it is open source.
What do I mean by an open approach?&lt;/p&gt;
&lt;p&gt;When reading the ASP.NET documentation, there are repeated references to how to perform certain tasks with Visual Studio.&lt;/p&gt;
&lt;p&gt;This distracts from the actual problem you want to solve and shows what Microsoft really wants to achieve: buy our tool.&lt;/p&gt;
&lt;p&gt;When it comes to deployment, the &amp;quot;easy way&amp;quot; always points to Azure.&lt;/p&gt;
&lt;p&gt;As already mentioned, Rails has no preference here: if you have a server running Docker, you can deploy with Kamal. End of story.&lt;/p&gt;
&lt;p&gt;But even beyond that, it&#39;s always exciting to see that the people who maintain and develop Rails also use it to develop applications.&lt;/p&gt;
&lt;p&gt;This is evident in many small details.&lt;/p&gt;
&lt;p&gt;If you use includes, for example, you can see where an include begins and ends in the HTML code in the browser during development. This makes visual debugging much easier.&lt;/p&gt;
&lt;p&gt;Another example is setting up a new project with Rails.&lt;/p&gt;
&lt;p&gt;You can configure so many options - and it&#39;s easy to do using the Rails CLI: which type of database, the CSS framework and which parts not to scaffold at an insane level of granularity.&lt;/p&gt;
&lt;p&gt;Another great feature is the Rails Console. You can easily interact with your application and database.&lt;/p&gt;
&lt;p&gt;With &lt;a href=&quot;https://hotwired.dev/&quot;&gt; Hotwire&lt;/a&gt; and &lt;a href=&quot;https://turbo.hotwired.dev/&quot;&gt; Turbo&lt;/a&gt;, Rails offers an approach to integrating JavaScript into Rails applications.&lt;/p&gt;
&lt;p&gt;With Turbo, it takes a similar approach to HTMX.&lt;/p&gt;
&lt;p&gt;Everything is opt-in and lightweight.&lt;/p&gt;
&lt;p&gt;Finally, I would like to circle back to the TL;DR at the beginning and briefly address the topic of performance comparison:&lt;/p&gt;
&lt;p&gt;Ruby - and therefore Rails - is not the fastest platform on the planet.&lt;/p&gt;
&lt;p&gt;But at the very least, a platform like Shopify has been running on it since the beginning, managing billions in sales for tens of thousands of companies.&lt;/p&gt;
&lt;p&gt;So if I ever need to scale Rails to this extent with our company, which has fewer than 20 employees, I will gladly take on the challenge and still have fun doing it.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>My tmux + Rails + AI TUIs development setup</title>
    <link href="https://alexanderzeitler.com/articles/my-tmux-tmuxinator-rails-ai-development-setup/"/>
    <updated>2026-02-11T22:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/my-tmux-tmuxinator-rails-ai-development-setup/</id>
    <content type="html">&lt;p&gt;I&#39;ve &lt;a href=&quot;https://alexanderzeitler.com/articles/why-i-switched-from-aspnet-core-to-ruby-on-rails/&quot;&gt;switched my tech stacks several times&lt;/a&gt; in the past 10+ years. Something that has been consistently there throughout these times has been &lt;code&gt;tmux&lt;/code&gt;. Here&#39;s how I&#39;m using it with Rails + AI TUIs.&lt;/p&gt;
&lt;h2&gt;So what&#39;s tmux?&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;tmux is a terminal multiplexer. It lets you switch easily between several programs in one terminal, detach them (they keep running in the background) and reattach them to a different terminal.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I became aware of &lt;code&gt;tmux&lt;/code&gt; around 2014 when I switched from Windows to Linux and I &lt;code&gt;ssh&lt;/code&gt;ed into my servers and connections sometimes have been interrupted.&lt;/p&gt;
&lt;p&gt;This is one thing &lt;code&gt;tmux&lt;/code&gt; gracefully handles for you: when an &lt;code&gt;shh&lt;/code&gt;  connection is interrupted, and gets re-established, the &lt;code&gt;tmux&lt;/code&gt;-session you started on the remote is still there so you can just attach to it and continue where you left of. Doing Linux updates via &lt;code&gt;ssh&lt;/code&gt; this way is also less risky.&lt;/p&gt;
&lt;p&gt;But there&#39;s more to it.&lt;/p&gt;
&lt;p&gt;When starting &lt;code&gt;tmux&lt;/code&gt; in your terminal, you notice it running by the sticky info bar at the bottom of your terminal:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/my-tmux-tmuxinator-rails-ai-development-setup/tmux-first-start.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If you didn&#39;t set up anything in your &lt;code&gt;~/tmux.conf&lt;/code&gt;, you can just use the terminal as is.&lt;/p&gt;
&lt;p&gt;Sometimes, you might want to have multiple terminals beneath each other and you can do that easily using &lt;code&gt;tmux&lt;/code&gt;: Hit &lt;code&gt;&amp;lt;leader&amp;gt;+&amp;lt;shift&amp;gt;+2&lt;/code&gt; and the &lt;code&gt;tmux&lt;/code&gt; window will split horizontally:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/my-tmux-tmuxinator-rails-ai-development-setup/tmux-horizontal-split.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If you need a vertical split, use &lt;code&gt;&amp;lt;leader&amp;gt;+&amp;lt;shift&amp;gt;+5&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;These splits can be nested arbitrarily:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/my-tmux-tmuxinator-rails-ai-development-setup/tmux-nested-splits.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;What&#39;s the &lt;code&gt;&amp;lt;leader&amp;gt;&lt;/code&gt;?&lt;/h2&gt;
&lt;p&gt;If you&#39;re familiar with tools like &lt;code&gt;nvim&lt;/code&gt;, you already know, what a  &lt;code&gt;&amp;lt;leader&amp;gt;&lt;/code&gt; is and the default for &lt;code&gt;tmux&lt;/code&gt; is &lt;code&gt;C-b&lt;/code&gt; which means &lt;code&gt;CTRL+b&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So &lt;code&gt;&amp;lt;leader&amp;gt;+&amp;lt;shift&amp;gt;+2&lt;/code&gt; means &amp;quot;Hit &lt;code&gt;CTRL+b&lt;/code&gt;, followed by &lt;code&gt;&amp;lt;SHIFT+2&amp;gt;&lt;/code&gt;&amp;quot;.&lt;/p&gt;
&lt;p&gt;I&#39;ve sticked with this default leader key for more than 10 years but after reading this great &lt;a href=&quot;https://lazyvim-ambitious-devs.phillips.codes/&quot;&gt;book about LazyVim&lt;/a&gt; (more specifically &lt;a href=&quot;https://lazyvim-ambitious-devs.phillips.codes/course/chapter-2/&quot;&gt;Chapter 2&lt;/a&gt;) last year and some pain in the wrist, I noticed that the root cause might be using the default &lt;code&gt;tmux&lt;/code&gt; leader key.&lt;/p&gt;
&lt;p&gt;Long story short: a few weeks ago, I changed my &lt;code&gt;leader&lt;/code&gt; leader key to &lt;code&gt;LEFT-ALT-M&lt;/code&gt; and this is much more comfortable to me. I can hit the &lt;code&gt;LEFT-ALT&lt;/code&gt; key with my left thumb and the &lt;code&gt;M&lt;/code&gt; key with my right index finger instead of stretching my left hand for &lt;code&gt;&amp;lt;CTRL+b&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Split panes are great but how do I navigate?&lt;/h2&gt;
&lt;p&gt;So with all these nested panes around, how do you select a particular one?&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;leader&amp;gt;-q&lt;/code&gt; is here for the rescue. This will bring up an overlay with the numbers of the panes. Hitting this number rigth after &lt;code&gt;&amp;lt;leader&amp;gt;-q&lt;/code&gt;, will focus that pane:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/my-tmux-tmuxinator-rails-ai-development-setup/tmux-pane-numbers.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If you want to get rid of a pane, just &lt;code&gt;exit&lt;/code&gt; that particular terminal.&lt;/p&gt;
&lt;h2&gt;You&#39;ve got more?&lt;/h2&gt;
&lt;p&gt;Let&#39;s consider you&#39;re building a web application and there&#39;s some development tooling required:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a web server&lt;/li&gt;
&lt;li&gt;one or more database(s) in Docker container(s)&lt;/li&gt;
&lt;li&gt;a editor, most likely &lt;code&gt;nvim&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;a shell to execute commands like &lt;code&gt;bin/rails&lt;/code&gt; or &lt;code&gt;bin/kamal&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;one ore more AI agent TUIs, e.g. &lt;code&gt;opencode&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;a database TUI like &lt;code&gt;sqlit&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;a TUI to manage Docker like &lt;code&gt;lazydocker&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That&#39;s much more than you could fit into a few nested panes.&lt;/p&gt;
&lt;p&gt;But &lt;code&gt;tmux&lt;/code&gt; has got you covered: we can create new Windows inside &lt;code&gt;tmux&lt;/code&gt; easily by hitting &lt;code&gt;&amp;lt;leader&amp;gt;-c&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You can navigate them by using &lt;code&gt;&amp;lt;leader&amp;gt;-&amp;lt;number-of-the-window&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Having windows and panes at hand, you can now create and environment where your complete tooling is running side by side:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/my-tmux-tmuxinator-rails-ai-development-setup/tmux-window-app.png&quot; alt=&quot;&quot; /&gt;
tmux app window&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/my-tmux-tmuxinator-rails-ai-development-setup/tmux-window-lazyvim.png&quot; alt=&quot;&quot; /&gt;
tmux layzvim window&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/my-tmux-tmuxinator-rails-ai-development-setup/tmux-window-shell.png&quot; alt=&quot;&quot; /&gt;
tmux shell window&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/my-tmux-tmuxinator-rails-ai-development-setup/tmux-window-agent.png&quot; alt=&quot;&quot; /&gt;
tmux opencode window&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/my-tmux-tmuxinator-rails-ai-development-setup/tmux-window-pg.png&quot; alt=&quot;&quot; /&gt;
tmux sqlit window&lt;/p&gt;
&lt;p&gt;The only downside: you have to re-create all of them after quitting the session.&lt;/p&gt;
&lt;p&gt;If you keep running the session like forever, you can just detach from it using &lt;code&gt;&amp;lt;leader&amp;gt;-d&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;tmux ls&lt;/code&gt; you get a list of all running &lt;code&gt;tmux&lt;/code&gt; sessions and you can attach to a running session using &lt;code&gt;tmux attach -t &amp;lt;session-number&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you have multiple sessions running (I&#39;ve almost 10 parallel &lt;code&gt;tmux&lt;/code&gt; sessions running while writing this - in a &lt;code&gt;tmux&lt;/code&gt; session, of course), identifying them by number, is not the easiest thing.&lt;/p&gt;
&lt;p&gt;Hit &lt;code&gt;&amp;lt;leader&amp;gt;-$&lt;/code&gt; and give your session a memorable name.&lt;/p&gt;
&lt;p&gt;Now you can also attach to it using &lt;code&gt;tmux attach -t &amp;lt;session-name&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Anyway, at some point you&#39;ll restart you machine and everything is gone...&lt;/p&gt;
&lt;h2&gt;Tmuxinator has entered the chat&lt;/h2&gt;
&lt;p&gt;That&#39;s where another tool comes handy: &lt;code&gt;tmuxinator&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Tmuxinator helps you manage complex terminal sessions easily. Create, manage, and automate your dev environment with simple YAML configuration files today.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;With &lt;code&gt;tmuxinator&lt;/code&gt; you can persist your complex layouts in YAML files.&lt;/p&gt;
&lt;p&gt;A YAML file for the session described above, could look like this:&lt;/p&gt;
&lt;pre class=&quot;language-YAML&quot;&gt;&lt;code class=&quot;language-YAML&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;name-of-session&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;root:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;path/to/the/project&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;windows:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;dev:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;layout:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;even-vertical&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;panes:&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;docker&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;compose&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;up&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;-d&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;bin/dev&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;editor:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;nvim&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;shell:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;bash&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;agent:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;opencode&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;sql:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;sqlit&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This &lt;code&gt;.tmuxinator.yml&lt;/code&gt; (notice the dot) file can be stored in multiple places: either in the root of your project or in the &lt;code&gt;~/.config/tmuxinator/&lt;/code&gt; folder.&lt;/p&gt;
&lt;p&gt;My workflow is to keep it inside the project and symlink it to the &lt;code&gt;~/.config/tmuxinator/&lt;/code&gt; folder.&lt;/p&gt;
&lt;p&gt;That way you can start a session from elsewhere globally using &lt;code&gt;tmuxinator start &amp;lt;name-of-session&amp;gt;&lt;/code&gt;. If the session is already running, it will just attach to it.&lt;/p&gt;
&lt;p&gt;If you enable bash completion for &lt;code&gt;tmuxinator&lt;/code&gt;, you can just type &lt;code&gt;tmuxinator start + &amp;lt;TAB&amp;gt;&lt;/code&gt; and it&#39;ll give you the list of defined sessions.&lt;/p&gt;
&lt;p&gt;As &lt;code&gt;tmuxinator&lt;/code&gt; is quite long to type, it is common, to &lt;code&gt;alias&lt;/code&gt; it to &lt;code&gt;mux&lt;/code&gt; (make sure to enable completion for this as well).&lt;/p&gt;
&lt;p&gt;Speaking of running &lt;code&gt;tmux&lt;/code&gt; session: you can easily hop between sessions using &lt;code&gt;&amp;lt;leader&amp;gt;-s&lt;/code&gt;. This gives you a list of &lt;code&gt;tmux&lt;/code&gt; sessions with a preview of it&#39;s windows and panes and you can just hit enter or the number of the session to switch to it:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/my-tmux-tmuxinator-rails-ai-development-setup/tmux-sessions.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;With this at hand, you literally need just one instance of your Terminal.&lt;/p&gt;
&lt;p&gt;And of course, you can always your mouse / trackpad if you enable it your tmux configuration.&lt;/p&gt;
&lt;p&gt;Mine can be found &lt;a href=&quot;https://gist.github.com/AlexZeitler/71705526091f41f459b97235e3534d58&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;One more thing&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;tmux&lt;/code&gt; is comming to the Omarchy menu soon:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/basecamp/omarchy/pull/4562&quot;&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/my-tmux-tmuxinator-rails-ai-development-setup/tmux-omarchy.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Introducing lazyqmd: a tui for QMD - and a little more...</title>
    <link href="https://alexanderzeitler.com/articles/introducing-lazyqmd-a-tui-for-qmd/"/>
    <updated>2026-02-13T22:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/introducing-lazyqmd-a-tui-for-qmd/</id>
    <content type="html">&lt;h2&gt;What&#39;s it all about?&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;QMD is a quite popular mini cli search engine for your docs, knowledge bases, meeting notes, whatever.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;qmd&lt;/code&gt; has been created by Tobi Luetke, the founder of Shopify and can be found &lt;a href=&quot;https://github.com/tobi/qmd&quot;&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It allows you to index Markdown files from several locations on your computer.&lt;/p&gt;
&lt;p&gt;You can search with keywords or natural language.&lt;/p&gt;
&lt;p&gt;QMD combines BM25 full-text search, vector semantic search, and LLM re-ranking—all running locally via node-llama-cpp with GGUF models.&lt;/p&gt;
&lt;p&gt;After installing it, you can add a new collection like this:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;qmd collection add ~/notes --name notes&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now all Markdown files under &lt;code&gt;~/notes&lt;/code&gt; will be indexed and be searched like this:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;qmd search -c notes &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;search term&amp;quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you have multiple collections, you can do a global search:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;qmd search &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;search term&amp;quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can query or do a vector search (see docs for more details).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;qmd&lt;/code&gt; also provides a mcp server, so you can integrate it as a memory server into your agentic workflows:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;qmd mcp --http&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will run start the mcp server on port &lt;code&gt;8181&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Of course you can specify another port and you can also run it in daemon mode:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;qmd mcp --daemon --http --port 9000&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So if I have a collection named &lt;code&gt;blog&lt;/code&gt; which points to the source repository of my blog on my computer, I can run a search like this:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;qmd search tmux&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will search all collections, hence also my collection named &lt;code&gt;blog&lt;/code&gt; and the results will look like this:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;qmd://blog/articles/my-tmux-tmuxinator-rails-ai-development-setup/index.md:2 &lt;span class=&quot;hljs-comment&quot;&gt;#b9e642&lt;/span&gt;&lt;br /&gt;Title: So what&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;s tmux?&lt;br /&gt;Score:  92%&lt;br /&gt;&lt;br /&gt;@@ -1,4 @@ (0 before, 156 after)&lt;br /&gt;---&lt;br /&gt;title: &amp;quot;My tmux + Rails + AI TUIs development setup&amp;quot;&lt;br /&gt;date: 2026-02-11T22:00:00&lt;br /&gt;layout: default&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Earlier today I was curious if I could display the whole Markdown file instead of result shown above and I came up with the idea of building a Temrinal UI (TUI) for for qmd - lazyqmd was born.&lt;/p&gt;
&lt;h2&gt;Introducing lazyqmd&lt;/h2&gt;
&lt;p&gt;As with &lt;code&gt;qmd&lt;/code&gt; itself, &lt;code&gt;lazyqmd&lt;/code&gt; can be installed using &lt;code&gt;bun&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;bun install -g lazyqmd&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To use all of the &lt;code&gt;lazyqmd&lt;/code&gt; features, make sure to start the &lt;code&gt;qmd&lt;/code&gt; mcp server in daemon mode as shown before. If you stick with the default port, you&#39;re good to go.&lt;/p&gt;
&lt;p&gt;If you set another port, you can configure &lt;code&gt;lazyqmd&lt;/code&gt; to use this one in the &lt;code&gt;~/.config/lazyqmd/options.json&lt;/code&gt; file (for more details, take a look at the &lt;code&gt;README&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Now we&#39;re ready to start &lt;code&gt;lazyqmd&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;lazyqmd&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you&#39;re starting from scratch without prior usage of &lt;code&gt;qmd&lt;/code&gt; itself, you&#39;ll have no collections at hand and &lt;code&gt;lazyqmd&lt;/code&gt; will look like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/introducing-lazyqmd-a-tui-for-qmd/lazyqmd-empty.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;As can bee seen from the bottom bar in the screenshot, there&#39;s a command &lt;code&gt;Add&lt;/code&gt; which can be invoked by pressing the &lt;code&gt;a&lt;/code&gt; key - this brings up a little dialog to add a new collection:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/introducing-lazyqmd-a-tui-for-qmd/lazyqmd-add-collection-blog.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Please notice, that you get tab completion for the path.&lt;/p&gt;
&lt;p&gt;Please wait until the &lt;code&gt;Indexing...&lt;/code&gt; message disappears. Once, the collection has been indexed, you can start using it. &lt;code&gt;lazyqmd&lt;/code&gt; should look similar to this now:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/introducing-lazyqmd-a-tui-for-qmd/lazyqmd-collection-blog.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You can navigte the collections in the left sidebar using up and down arrows and you can start a new search by either sticking with the &lt;code&gt;All&lt;/code&gt; selection for a global search or you can select a particular collection and search this one.&lt;/p&gt;
&lt;p&gt;Let&#39;s select &lt;code&gt;blog&lt;/code&gt; and hit &lt;code&gt;/&lt;/code&gt; to bring up the search dialog:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/introducing-lazyqmd-a-tui-for-qmd/lazyqmd-search-empty.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now lets type &amp;quot;tmux&amp;quot;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/introducing-lazyqmd-a-tui-for-qmd/lazyqmd-search-tmux.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Htting &amp;quot;Enter&amp;quot; will bring up the search results for this particular search term:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/introducing-lazyqmd-a-tui-for-qmd/lazyqmd-search-tmux-results.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The first result seems interesting as it has a relevance of 86%. So lets hit &lt;code&gt;&amp;lt;tab&amp;gt;&lt;/code&gt; to jump to the results list follow by &lt;code&gt;Enter&lt;/code&gt; to open that particular document:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/introducing-lazyqmd-a-tui-for-qmd/lazyqmd-tmux-result-view.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now you can scroll inside this document and as can be seen, there&#39;s some syntax highlighting for Markdown and YAML frontmatter.&lt;/p&gt;
&lt;p&gt;At this point I thought it would be nice to have a HTML preview at hand, so I&#39;ve added it. Within the Markdown preview just hit &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/introducing-lazyqmd-a-tui-for-qmd/lazyqmd-html-preview.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;As expected, this brings up a Chrome, Chromium or Brave instance in app mode. If you&#39;re living the dream and run &lt;a href=&quot;https://omarchy.org/&quot;&gt;Omarchy&lt;/a&gt;, everything will be auto aligned nicely thanks to &lt;code&gt;hyprland&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now I wanted to go a little further: What if I could just edit the file and get a sort of hot reload of the HTML?&lt;/p&gt;
&lt;p&gt;Lets focus the &lt;code&gt;lazyqmd&lt;/code&gt; window and hit &lt;code&gt;&amp;lt;e&amp;gt;&lt;/code&gt; for &amp;quot;Edit&amp;quot;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/introducing-lazyqmd-a-tui-for-qmd/lazyqmd-neovim.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If your &lt;code&gt;$EDITOR&lt;/code&gt; is &lt;code&gt;nvim&lt;/code&gt;, this will seamlessly open the Markdown file in &lt;code&gt;neovim&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now lets make a little change to our Markdown file:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/introducing-lazyqmd-a-tui-for-qmd/lazyqmd-neovim-edited.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;As you can see, when saving the change, the change is reflected in the HTML preview.&lt;/p&gt;
&lt;p&gt;Quitting &lt;code&gt;nvim&lt;/code&gt; will bring you back to &lt;code&gt;lazyqmd&lt;/code&gt; where you left off.&lt;/p&gt;
&lt;p&gt;Looking at the search screen again, you might have noticed, there&#39;s a &amp;quot;Mode&amp;quot; command show in the command bar at the bottom:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/introducing-lazyqmd-a-tui-for-qmd/lazyqmd-search-tmux.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;By hitting &lt;code&gt;&amp;lt;CTRL+T&amp;gt;&lt;/code&gt; you can switch between regular &lt;code&gt;search&lt;/code&gt;, &lt;code&gt;vsearch&lt;/code&gt; (QMD Vector search) and &lt;code&gt;query&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you want to use &lt;code&gt;vsearch&lt;/code&gt;, make sure you&#39;ve created the &lt;code&gt;Embeddings&lt;/code&gt;. If you didn&#39;t so far, you can just hit &lt;code&gt;&amp;lt;e&amp;gt;&lt;/code&gt; on the main screen with a collection selected. This will run &lt;code&gt;qmd ebmed&lt;/code&gt; for you and create the Embeddings.&lt;/p&gt;
&lt;p&gt;After finishing this, I noticed that this little TUI + QMD might replace LogSeq and Obsidian for me: file based, run locally and I can have my files where they belong to- lets see how this works in daily use.&lt;/p&gt;
&lt;p&gt;For more features, please have a look at the &lt;code&gt;README&lt;/code&gt; &lt;a href=&quot;https://github.com/AlexZeitler/lazyqmd&quot;&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As this project is pretty new, expect not everything to be perfect right now.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Introducing zugpferd: XRechnung and ZUGFeRD e-invoicing for Ruby</title>
    <link href="https://alexanderzeitler.com/articles/introducing-zugpferd-xrechnung-zugferd-e-invoicing-for-ruby/"/>
    <updated>2026-02-16T22:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/introducing-zugpferd-xrechnung-zugferd-e-invoicing-for-ruby/</id>
    <content type="html">&lt;h2&gt;What&#39;s it all about?&lt;/h2&gt;
&lt;p&gt;If you&#39;re dealing with invoicing in Germany or Europe, chances are you&#39;ve heard of &lt;strong&gt;XRechnung&lt;/strong&gt; and &lt;strong&gt;ZUGFeRD&lt;/strong&gt;. Starting January 2025, e-invoicing became mandatory for B2B transactions in Germany.&lt;/p&gt;
&lt;p&gt;The European standard &lt;strong&gt;EN 16931&lt;/strong&gt; defines a semantic data model for electronic invoices and supports two XML syntaxes: &lt;strong&gt;UBL 2.1&lt;/strong&gt; and &lt;strong&gt;UN/CEFACT CII&lt;/strong&gt;. &lt;strong&gt;XRechnung&lt;/strong&gt; is the German compliance profile on top of EN 16931, while &lt;strong&gt;ZUGFeRD&lt;/strong&gt; (also known as &lt;strong&gt;Factur-X&lt;/strong&gt; in France) adds the ability to embed the structured XML data into a PDF/A-3 document - creating a hybrid invoice that is both human-readable and machine-processable.&lt;/p&gt;
&lt;p&gt;Most existing libraries for this are written in Java or .NET. I wanted something that feels native to Ruby - plain Ruby objects, &lt;code&gt;BigDecimal&lt;/code&gt; for monetary values, &lt;code&gt;Date&lt;/code&gt; fields, and a clean API. That&#39;s why I built &lt;code&gt;zugpferd&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Introducing zugpferd&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/AlexZeitler/zugpferd&quot;&gt;zugpferd&lt;/a&gt; is a Ruby gem for reading, writing, and converting e-invoices according to EN 16931. It supports both UBL 2.1 and UN/CEFACT CII syntaxes, and optionally handles PDF/A-3 embedding and Schematron validation.&lt;/p&gt;
&lt;h3&gt;Features&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;UBL 2.1 &amp;amp; CII&lt;/strong&gt; - Full support for both EN 16931 syntaxes: read, write, and roundtrip for any XRechnung or ZUGFeRD document&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multiple document types&lt;/strong&gt; - Invoice, Credit Note, Corrected Invoice, Self-billed Invoice, Partial Invoice, and Prepayment Invoice&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PDF/A-3 embedding&lt;/strong&gt; - Create ZUGFeRD/Factur-X hybrid invoices by embedding XML into PDF/A-3 via Ghostscript&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Validation&lt;/strong&gt; - Validate invoices against EN 16931 and XRechnung business rules using Schematron (optional Java dependency)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Format conversion&lt;/strong&gt; - Read CII, write UBL (or vice versa) through a format-agnostic data model&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pure Ruby data model&lt;/strong&gt; - &lt;code&gt;BigDecimal&lt;/code&gt; amounts, &lt;code&gt;Date&lt;/code&gt; fields, simple Ruby objects mapped to EN 16931 Business Terms&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Installation&lt;/h3&gt;
&lt;p&gt;Add it to your &lt;code&gt;Gemfile&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;gem &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;zugpferd&amp;quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;bundle install&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For PDF/A-3 embedding, you&#39;ll also need Ghostscript installed:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Debian/Ubuntu&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; apt-get install ghostscript&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Arch Linux&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; pacman -S ghostscript&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# macOS&lt;/span&gt;&lt;br /&gt;brew install ghostscript&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Document types&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;zugpferd&lt;/code&gt; supports all common EN 16931 document types, each with a dedicated model class:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;Type Code&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Model::Invoice&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;380&lt;/td&gt;
&lt;td&gt;Commercial Invoice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Model::CreditNote&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;381&lt;/td&gt;
&lt;td&gt;Credit Note&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Model::CorrectedInvoice&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;384&lt;/td&gt;
&lt;td&gt;Corrected Invoice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Model::SelfBilledInvoice&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;389&lt;/td&gt;
&lt;td&gt;Self-billed Invoice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Model::PartialInvoice&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;326&lt;/td&gt;
&lt;td&gt;Partial Invoice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Model::PrepaymentInvoice&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;386&lt;/td&gt;
&lt;td&gt;Prepayment Invoice&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All document types share the same attributes and work identically with both UBL and CII readers and writers:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;credit_note = &lt;span class=&quot;hljs-title class_&quot;&gt;Zugpferd::Model::CreditNote&lt;/span&gt;.new(&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;number:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;CN-001&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;issue_date:&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Date&lt;/span&gt;.today,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;currency_code:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;EUR&amp;quot;&lt;/span&gt;&lt;br /&gt;)&lt;br /&gt;&lt;br /&gt;corrected = &lt;span class=&quot;hljs-title class_&quot;&gt;Zugpferd::Model::CorrectedInvoice&lt;/span&gt;.new(&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;number:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;C-001&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;issue_date:&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Date&lt;/span&gt;.today&lt;br /&gt;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In UBL, a &lt;code&gt;CreditNote&lt;/code&gt; produces a &lt;code&gt;&amp;lt;CreditNote&amp;gt;&lt;/code&gt; root element while all other types produce &lt;code&gt;&amp;lt;Invoice&amp;gt;&lt;/code&gt;. In CII, the structure is identical for all types - only the type code differs.&lt;/p&gt;
&lt;h2&gt;Building an invoice&lt;/h2&gt;
&lt;p&gt;Let&#39;s walk through creating an XRechnung-compliant invoice, validating it, and embedding it into a ZUGFeRD PDF.&lt;/p&gt;
&lt;p&gt;First, require the necessary modules:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;zugpferd&amp;quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;zugpferd/pdf&amp;quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;zugpferd/validation&amp;quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;bigdecimal&amp;quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Setting up the invoice header&lt;/h3&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;invoice = &lt;span class=&quot;hljs-title class_&quot;&gt;Zugpferd::Model::Invoice&lt;/span&gt;.new(&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;number:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;RE-2024-0042&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;issue_date:&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;Date&lt;/span&gt;.new(&lt;span class=&quot;hljs-number&quot;&gt;2024&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;6&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;15&lt;/span&gt;),&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;currency_code:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;EUR&amp;quot;&lt;/span&gt;&lt;br /&gt;)&lt;br /&gt;&lt;br /&gt;invoice.due_date = &lt;span class=&quot;hljs-title class_&quot;&gt;Date&lt;/span&gt;.new(&lt;span class=&quot;hljs-number&quot;&gt;2024&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;7&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;15&lt;/span&gt;)&lt;br /&gt;invoice.delivery_date = &lt;span class=&quot;hljs-title class_&quot;&gt;Date&lt;/span&gt;.new(&lt;span class=&quot;hljs-number&quot;&gt;2024&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;6&lt;/span&gt;, &lt;span class=&quot;hljs-number&quot;&gt;15&lt;/span&gt;)&lt;br /&gt;invoice.buyer_reference = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;LEITWEG-123-456&amp;quot;&lt;/span&gt;&lt;br /&gt;invoice.customization_id = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0&amp;quot;&lt;/span&gt;&lt;br /&gt;invoice.profile_id = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;urn:fdc:peppol.eu:2017:poacc:billing:01:1.0&amp;quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Adding seller and buyer&lt;/h3&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;invoice.seller = &lt;span class=&quot;hljs-title class_&quot;&gt;Zugpferd::Model::TradeParty&lt;/span&gt;.new(&lt;span class=&quot;hljs-symbol&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Zugpferd GmbH&amp;quot;&lt;/span&gt;)&lt;br /&gt;invoice.seller.vat_identifier = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;DE123456789&amp;quot;&lt;/span&gt;&lt;br /&gt;invoice.seller.electronic_address = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;zugpferd@example.com&amp;quot;&lt;/span&gt;&lt;br /&gt;invoice.seller.electronic_address_scheme = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;EM&amp;quot;&lt;/span&gt;&lt;br /&gt;invoice.seller.postal_address = &lt;span class=&quot;hljs-title class_&quot;&gt;Zugpferd::Model::PostalAddress&lt;/span&gt;.new(&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;country_code:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;DE&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;city_name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Frankfurt am Main&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;postal_zone:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;60311&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;street_name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Kaiserstr. 42&amp;quot;&lt;/span&gt;&lt;br /&gt;)&lt;br /&gt;invoice.seller.contact = &lt;span class=&quot;hljs-title class_&quot;&gt;Zugpferd::Model::Contact&lt;/span&gt;.new(&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Max Mustermann&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;telephone:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;+49 69 12345678&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;email:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;rechnung@zugpferd.example.com&amp;quot;&lt;/span&gt;&lt;br /&gt;)&lt;br /&gt;&lt;br /&gt;invoice.buyer = &lt;span class=&quot;hljs-title class_&quot;&gt;Zugpferd::Model::TradeParty&lt;/span&gt;.new(&lt;span class=&quot;hljs-symbol&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Muster AG&amp;quot;&lt;/span&gt;)&lt;br /&gt;invoice.buyer.electronic_address = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;muster@example.com&amp;quot;&lt;/span&gt;&lt;br /&gt;invoice.buyer.electronic_address_scheme = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;EM&amp;quot;&lt;/span&gt;&lt;br /&gt;invoice.buyer.postal_address = &lt;span class=&quot;hljs-title class_&quot;&gt;Zugpferd::Model::PostalAddress&lt;/span&gt;.new(&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;country_code:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;DE&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;city_name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Berlin&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;postal_zone:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;10115&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;street_name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Unter den Linden 1&amp;quot;&lt;/span&gt;&lt;br /&gt;)&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Adding line items&lt;/h3&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Position 1: Software licenses&lt;/span&gt;&lt;br /&gt;line1 = &lt;span class=&quot;hljs-title class_&quot;&gt;Zugpferd::Model::LineItem&lt;/span&gt;.new(&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;id:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;1&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;invoiced_quantity:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;5&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;unit_code:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;C62&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;line_extension_amount:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;2500.00&amp;quot;&lt;/span&gt;&lt;br /&gt;)&lt;br /&gt;line1.item = &lt;span class=&quot;hljs-title class_&quot;&gt;Zugpferd::Model::Item&lt;/span&gt;.new(&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Zugpferd Enterprise License&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;tax_category:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;S&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;tax_percent:&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;BigDecimal&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;19&amp;quot;&lt;/span&gt;)&lt;br /&gt;)&lt;br /&gt;line1.price = &lt;span class=&quot;hljs-title class_&quot;&gt;Zugpferd::Model::Price&lt;/span&gt;.new(&lt;span class=&quot;hljs-symbol&quot;&gt;amount:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;500.00&amp;quot;&lt;/span&gt;)&lt;br /&gt;invoice.line_items &amp;lt;&amp;lt; line1&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Position 2: Consulting&lt;/span&gt;&lt;br /&gt;line2 = &lt;span class=&quot;hljs-title class_&quot;&gt;Zugpferd::Model::LineItem&lt;/span&gt;.new(&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;id:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;2&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;invoiced_quantity:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;16&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;unit_code:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;HUR&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;line_extension_amount:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;2400.00&amp;quot;&lt;/span&gt;&lt;br /&gt;)&lt;br /&gt;line2.item = &lt;span class=&quot;hljs-title class_&quot;&gt;Zugpferd::Model::Item&lt;/span&gt;.new(&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Technische Beratung E-Invoicing&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;tax_category:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;S&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;tax_percent:&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;BigDecimal&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;19&amp;quot;&lt;/span&gt;)&lt;br /&gt;)&lt;br /&gt;line2.price = &lt;span class=&quot;hljs-title class_&quot;&gt;Zugpferd::Model::Price&lt;/span&gt;.new(&lt;span class=&quot;hljs-symbol&quot;&gt;amount:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;150.00&amp;quot;&lt;/span&gt;)&lt;br /&gt;invoice.line_items &amp;lt;&amp;lt; line2&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Position 3: Workshop&lt;/span&gt;&lt;br /&gt;line3 = &lt;span class=&quot;hljs-title class_&quot;&gt;Zugpferd::Model::LineItem&lt;/span&gt;.new(&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;id:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;3&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;invoiced_quantity:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;1&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;unit_code:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;C62&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;line_extension_amount:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;1800.00&amp;quot;&lt;/span&gt;&lt;br /&gt;)&lt;br /&gt;line3.item = &lt;span class=&quot;hljs-title class_&quot;&gt;Zugpferd::Model::Item&lt;/span&gt;.new(&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Workshop: XRechnung &amp;amp; ZUGFeRD in der Praxis (2 Tage)&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;tax_category:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;S&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;tax_percent:&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;BigDecimal&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;19&amp;quot;&lt;/span&gt;)&lt;br /&gt;)&lt;br /&gt;line3.price = &lt;span class=&quot;hljs-title class_&quot;&gt;Zugpferd::Model::Price&lt;/span&gt;.new(&lt;span class=&quot;hljs-symbol&quot;&gt;amount:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;1800.00&amp;quot;&lt;/span&gt;)&lt;br /&gt;invoice.line_items &amp;lt;&amp;lt; line3&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Tax, totals, and payment&lt;/h3&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;netto = &lt;span class=&quot;hljs-title class_&quot;&gt;BigDecimal&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;6700.00&amp;quot;&lt;/span&gt;)&lt;br /&gt;steuer = &lt;span class=&quot;hljs-title class_&quot;&gt;BigDecimal&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;1273.00&amp;quot;&lt;/span&gt;)&lt;br /&gt;brutto = &lt;span class=&quot;hljs-title class_&quot;&gt;BigDecimal&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;7973.00&amp;quot;&lt;/span&gt;)&lt;br /&gt;&lt;br /&gt;invoice.tax_breakdown = &lt;span class=&quot;hljs-title class_&quot;&gt;Zugpferd::Model::TaxBreakdown&lt;/span&gt;.new(&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;tax_amount:&lt;/span&gt; steuer.to_s(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;F&amp;quot;&lt;/span&gt;),&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;currency_code:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;EUR&amp;quot;&lt;/span&gt;&lt;br /&gt;)&lt;br /&gt;invoice.tax_breakdown.subtotals &amp;lt;&amp;lt; &lt;span class=&quot;hljs-title class_&quot;&gt;Zugpferd::Model::TaxSubtotal&lt;/span&gt;.new(&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;taxable_amount:&lt;/span&gt; netto.to_s(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;F&amp;quot;&lt;/span&gt;),&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;tax_amount:&lt;/span&gt; steuer.to_s(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;F&amp;quot;&lt;/span&gt;),&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;category_code:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;S&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;currency_code:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;EUR&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;percent:&lt;/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;BigDecimal&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;19&amp;quot;&lt;/span&gt;)&lt;br /&gt;)&lt;br /&gt;&lt;br /&gt;invoice.monetary_totals = &lt;span class=&quot;hljs-title class_&quot;&gt;Zugpferd::Model::MonetaryTotals&lt;/span&gt;.new(&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;line_extension_amount:&lt;/span&gt; netto.to_s(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;F&amp;quot;&lt;/span&gt;),&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;tax_exclusive_amount:&lt;/span&gt; netto.to_s(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;F&amp;quot;&lt;/span&gt;),&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;tax_inclusive_amount:&lt;/span&gt; brutto.to_s(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;F&amp;quot;&lt;/span&gt;),&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;payable_amount:&lt;/span&gt; brutto.to_s(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;F&amp;quot;&lt;/span&gt;)&lt;br /&gt;)&lt;br /&gt;&lt;br /&gt;invoice.payment_instructions = &lt;span class=&quot;hljs-title class_&quot;&gt;Zugpferd::Model::PaymentInstructions&lt;/span&gt;.new(&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;payment_means_code:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;58&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;account_id:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;DE89370400440532013000&amp;quot;&lt;/span&gt;&lt;br /&gt;)&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Writing XML&lt;/h2&gt;
&lt;p&gt;Now generate the CII XML:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;xml = &lt;span class=&quot;hljs-title class_&quot;&gt;Zugpferd::CII::Writer&lt;/span&gt;.new.write(invoice)&lt;br /&gt;&lt;span class=&quot;hljs-title class_&quot;&gt;File&lt;/span&gt;.write(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;invoice.xml&amp;quot;&lt;/span&gt;, xml)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Need UBL instead? Just swap the writer:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;xml = &lt;span class=&quot;hljs-title class_&quot;&gt;Zugpferd::UBL::Writer&lt;/span&gt;.new.write(invoice)&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Format conversion&lt;/h3&gt;
&lt;p&gt;Since the data model is format-agnostic, converting between UBL and CII is straightforward:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Read a CII invoice, write it as UBL&lt;/span&gt;&lt;br /&gt;cii_xml = &lt;span class=&quot;hljs-title class_&quot;&gt;File&lt;/span&gt;.read(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;invoice_cii.xml&amp;quot;&lt;/span&gt;)&lt;br /&gt;invoice = &lt;span class=&quot;hljs-title class_&quot;&gt;Zugpferd::CII::Reader&lt;/span&gt;.new.read(cii_xml)&lt;br /&gt;ubl_xml = &lt;span class=&quot;hljs-title class_&quot;&gt;Zugpferd::UBL::Writer&lt;/span&gt;.new.write(invoice)&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Validation&lt;/h2&gt;
&lt;p&gt;Before sending an invoice, you probably want to make sure it&#39;s valid. &lt;code&gt;zugpferd&lt;/code&gt; supports Schematron validation against EN 16931 and XRechnung business rules.&lt;/p&gt;
&lt;p&gt;First, download the required schemas:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;bin/setup-schemas&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then validate:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;validator = &lt;span class=&quot;hljs-title class_&quot;&gt;Zugpferd::Validation::SchematronValidator&lt;/span&gt;.new(&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;schemas_path:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;vendor/schemas&amp;quot;&lt;/span&gt;&lt;br /&gt;)&lt;br /&gt;errors = validator.validate_all(xml, &lt;span class=&quot;hljs-symbol&quot;&gt;rule_sets:&lt;/span&gt; [&lt;span class=&quot;hljs-symbol&quot;&gt;:cen_cii&lt;/span&gt;, &lt;span class=&quot;hljs-symbol&quot;&gt;:xrechnung_cii&lt;/span&gt;])&lt;br /&gt;fatals = errors.select { |&lt;span class=&quot;hljs-params&quot;&gt;e&lt;/span&gt;| e.flag == &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;fatal&amp;quot;&lt;/span&gt; }&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; fatals.any?&lt;br /&gt;  fatals.each { |&lt;span class=&quot;hljs-params&quot;&gt;e&lt;/span&gt;| puts &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;  [&lt;span class=&quot;hljs-subst&quot;&gt;#{e.id}&lt;/span&gt;] &lt;span class=&quot;hljs-subst&quot;&gt;#{e.text}&lt;/span&gt;&amp;quot;&lt;/span&gt; }&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;else&lt;/span&gt;&lt;br /&gt;  puts &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Invoice is valid.&amp;quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Schematron validation requires Java and Saxon HE. If you don&#39;t need it, just skip the &lt;code&gt;require &amp;quot;zugpferd/validation&amp;quot;&lt;/code&gt; - the core library works without Java.&lt;/p&gt;
&lt;h2&gt;PDF/A-3 embedding&lt;/h2&gt;
&lt;p&gt;This is where ZUGFeRD comes in: embedding the structured XML into a PDF, creating a hybrid document that works for both humans and machines.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;embedder = &lt;span class=&quot;hljs-title class_&quot;&gt;Zugpferd&lt;/span&gt;&lt;span class=&quot;hljs-symbol&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;hljs-symbol&quot;&gt;:PDF&lt;/span&gt;&lt;span class=&quot;hljs-symbol&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;hljs-symbol&quot;&gt;:Embedder&lt;/span&gt;.new&lt;br /&gt;embedder.embed(&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;pdf_path:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;invoice.pdf&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;xml:&lt;/span&gt; xml,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;output_path:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;invoice_zugferd.pdf&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;version:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;2p1&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-symbol&quot;&gt;conformance_level:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;XRECHNUNG&amp;quot;&lt;/span&gt;&lt;br /&gt;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The result is a PDF/A-3 compliant file with the XML attached as &lt;code&gt;factur-x.xml&lt;/code&gt;. This works with ZUGFeRD 1.0, 2.0, and 2.1, and supports all conformance levels from MINIMUM to XRECHNUNG.&lt;/p&gt;
&lt;h2&gt;Reading invoices&lt;/h2&gt;
&lt;p&gt;Of course, &lt;code&gt;zugpferd&lt;/code&gt; also reads existing invoices:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# UBL&lt;/span&gt;&lt;br /&gt;xml = &lt;span class=&quot;hljs-title class_&quot;&gt;File&lt;/span&gt;.read(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;invoice.xml&amp;quot;&lt;/span&gt;)&lt;br /&gt;invoice = &lt;span class=&quot;hljs-title class_&quot;&gt;Zugpferd::UBL::Reader&lt;/span&gt;.new.read(xml)&lt;br /&gt;puts invoice.number          &lt;span class=&quot;hljs-comment&quot;&gt;# =&amp;gt; &amp;quot;RE-2024-0042&amp;quot;&lt;/span&gt;&lt;br /&gt;puts invoice.seller.name     &lt;span class=&quot;hljs-comment&quot;&gt;# =&amp;gt; &amp;quot;Zugpferd GmbH&amp;quot;&lt;/span&gt;&lt;br /&gt;puts invoice.monetary_totals.payable_amount  &lt;span class=&quot;hljs-comment&quot;&gt;# =&amp;gt; 7973.0&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# CII&lt;/span&gt;&lt;br /&gt;invoice = &lt;span class=&quot;hljs-title class_&quot;&gt;Zugpferd::CII::Reader&lt;/span&gt;.new.read(cii_xml)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The reader auto-detects document types: Credit Notes, Corrected Invoices, and all other EN 16931 type codes are mapped to their respective model classes.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;zugpferd&lt;/code&gt; aims to make e-invoicing in Ruby feel natural - no XML wrangling, no Java dependencies for core functionality, just plain Ruby objects.&lt;/p&gt;
&lt;p&gt;For the full documentation, check out the &lt;a href=&quot;https://alexzeitler.github.io/zugpferd/&quot;&gt;docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The source code is available &lt;a href=&quot;https://github.com/AlexZeitler/zugpferd&quot;&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As this is a fairly new project, feedback and contributions are welcome.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Run Dropbox Headless as a Service Without User Session Login</title>
    <link href="https://alexanderzeitler.com/articles/run-dropbox-headless-as-a-service-without-user-session-login/"/>
    <updated>2026-03-08T12:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/run-dropbox-headless-as-a-service-without-user-session-login/</id>
    <content type="html">&lt;h2&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Dropbox stops syncing when no user is logged in. On a headless server or a machine that isn&#39;t always attended, this means your files go stale whenever the session ends.&lt;/p&gt;
&lt;h2&gt;The Solution&lt;/h2&gt;
&lt;p&gt;Run Dropbox as a &lt;strong&gt;systemd user service&lt;/strong&gt; with &lt;code&gt;loginctl enable-linger&lt;/code&gt;. This keeps Dropbox running at boot — no active session required.&lt;/p&gt;
&lt;h3&gt;1. Create the service file&lt;/h3&gt;
&lt;p&gt;Place this at &lt;code&gt;~/.config/systemd/user/dropbox.service&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-ini&quot;&gt;&lt;code class=&quot;language-ini&quot;&gt;&lt;span class=&quot;hljs-section&quot;&gt;[Unit]&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;Description&lt;/span&gt;=Dropbox Sync Service&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;After&lt;/span&gt;=network-&lt;span class=&quot;hljs-literal&quot;&gt;on&lt;/span&gt;line.target&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;Wants&lt;/span&gt;=network-&lt;span class=&quot;hljs-literal&quot;&gt;on&lt;/span&gt;line.target&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-section&quot;&gt;[Service]&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;Type&lt;/span&gt;=simple&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;ExecStart&lt;/span&gt;=/home/YOUR_USER/.dropbox-dist/dropboxd&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;Environment&lt;/span&gt;=HOME=/home/YOUR_USER&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;Environment&lt;/span&gt;=DISPLAY=&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;Restart&lt;/span&gt;=&lt;span class=&quot;hljs-literal&quot;&gt;on&lt;/span&gt;-failure&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;RestartSec&lt;/span&gt;=&lt;span class=&quot;hljs-number&quot;&gt;5&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;hljs-section&quot;&gt;[Install]&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-attr&quot;&gt;WantedBy&lt;/span&gt;=default.target&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace &lt;code&gt;YOUR_USER&lt;/code&gt; with your actual username.&lt;/p&gt;
&lt;h3&gt;2. Enable and start the service&lt;/h3&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;systemctl --user &lt;span class=&quot;hljs-built_in&quot;&gt;enable&lt;/span&gt; dropbox.service&lt;br /&gt;systemctl --user start dropbox.service&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. Enable linger&lt;/h3&gt;
&lt;p&gt;This is the key step — it tells systemd to start your user services at boot, even without an active login session:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;sudo&lt;/span&gt; loginctl enable-linger YOUR_USER&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. Verify&lt;/h3&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;systemctl --user status dropbox.service     &lt;span class=&quot;hljs-comment&quot;&gt;# should show active (running)&lt;/span&gt;&lt;br /&gt;loginctl show-user YOUR_USER | grep Linger  &lt;span class=&quot;hljs-comment&quot;&gt;# should show Linger=yes&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Important Note&lt;/h2&gt;
&lt;p&gt;Dropbox must be installed and linked to your account at least once in an interactive session before this setup works. Run &lt;code&gt;dropboxd&lt;/code&gt; manually first, follow the authentication link, and then switch to the systemd service.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Introducing LazyDbx: a TUI for Dropbox</title>
    <link href="https://alexanderzeitler.com/articles/introducing-lazydbx-a-tui-for-the-dropbox-desktop-client/"/>
    <updated>2026-03-12T12:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/introducing-lazydbx-a-tui-for-the-dropbox-desktop-client/</id>
    <content type="html">&lt;h2&gt;What&#39;s it all about?&lt;/h2&gt;
&lt;p&gt;In my &lt;a href=&quot;https://alexanderzeitler.com/articles/run-dropbox-headless-as-a-service-without-user-session-login/&quot;&gt;previous post&lt;/a&gt;, I showed how to run Dropbox as a systemd user service so it keeps syncing even when no user is logged in.&lt;/p&gt;
&lt;p&gt;That setup works well — but it introduces a few new challenges:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The Dropbox desktop app might not start anymore.&lt;/strong&gt; When Dropbox runs as a systemd service, the desktop app finds the already running daemon but fails to show a tray icon because the communication runs through the socket. This is a known issue with the headless service setup.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You might want to manage Dropbox from a terminal&lt;/strong&gt; in general — not everyone runs a desktop environment, and even on desktop machines, a TUI can be faster than the Dropbox UI.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Over SSH&lt;/strong&gt;, you need a way to check sync status, exclude folders, or add folders back — and the built-in &lt;code&gt;dropbox&lt;/code&gt; CLI is limited. It can only show you files that are already synced locally. Excluded folders and their contents are completely invisible.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That last point is the real problem: once you exclude a folder from sync, the &lt;code&gt;dropbox&lt;/code&gt; CLI has no way to show you what&#39;s on the server. You&#39;re flying blind.&lt;/p&gt;
&lt;h2&gt;Introducing LazyDbx&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/AlexZeitler/LazyDbx&quot;&gt;LazyDbx&lt;/a&gt; is a terminal UI (TUI) for Dropbox, built with &lt;a href=&quot;https://opentui.com/&quot;&gt;OpenTUI&lt;/a&gt; and &lt;a href=&quot;https://bun.sh/&quot;&gt;Bun&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It lets you browse your local and remote Dropbox files, manage sync exclusions, and create share links — all from the terminal.&lt;/p&gt;
&lt;h2&gt;Requirements&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://bun.sh/&quot;&gt;Bun&lt;/a&gt; v1+&lt;/li&gt;
&lt;li&gt;Dropbox installed and running (either via &lt;code&gt;dropbox start&lt;/code&gt; or as a &lt;a href=&quot;https://alexanderzeitler.com/articles/run-dropbox-headless-as-a-service-without-user-session-login/&quot;&gt;systemd user service&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;A &lt;a href=&quot;https://www.dropbox.com/developers/apps&quot;&gt;Dropbox App&lt;/a&gt; for API access (optional, enables the Server tab)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Install&lt;/h2&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;bun install -g github:alexzeitler/lazydbx&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or from source:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;git &lt;span class=&quot;hljs-built_in&quot;&gt;clone&lt;/span&gt; https://github.com/alexzeitler/lazydbx.git&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;cd&lt;/span&gt; lazydbx&lt;br /&gt;bun install&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Setup&lt;/h2&gt;
&lt;p&gt;Each user creates their own Dropbox App. Credentials and tokens stay on your machine (&lt;code&gt;~/.config/lazydbx/config.json&lt;/code&gt;) — all API requests go directly from your computer to Dropbox, no third-party servers involved.&lt;/p&gt;
&lt;h3&gt;1. Create a Dropbox App&lt;/h3&gt;
&lt;p&gt;Go to https://www.dropbox.com/developers/apps and create a new app:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Access type&lt;/strong&gt;: Scoped access, Full Dropbox&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Permissions tab&lt;/strong&gt;: Enable &lt;code&gt;account_info.read&lt;/code&gt;, &lt;code&gt;files.metadata.read&lt;/code&gt;, and &lt;code&gt;sharing.write&lt;/code&gt;. Then click &lt;strong&gt;Submit&lt;/strong&gt; in the black overlay bar at the bottom.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. Initialize config&lt;/h3&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;lazydbx init&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This prompts for your App Key and App Secret and saves them to &lt;code&gt;~/.config/lazydbx/config.json&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;3. Authorize&lt;/h3&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;lazydbx auth&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This opens the Dropbox authorization URL. Copy the code from the Dropbox page and paste it into the terminal. Tokens are saved automatically.&lt;/p&gt;
&lt;p&gt;To re-authorize (e.g. after changing scopes):&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;lazydbx auth --force&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. Run&lt;/h3&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;lazydbx&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Browsing your Dropbox&lt;/h2&gt;
&lt;h3&gt;Local&lt;/h3&gt;
&lt;p&gt;Browse files and folders synced locally in &lt;code&gt;~/Dropbox&lt;/code&gt;. You can exclude folders from sync, create share links, copy links, and check the sync status of individual files.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/introducing-lazydbx-a-tui-for-the-dropbox-desktop-client/screenshot-local.png&quot; alt=&quot;Local tab — browse synced files&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Server&lt;/h3&gt;
&lt;p&gt;Browse the &lt;strong&gt;complete&lt;/strong&gt; Dropbox folder structure via the API — including folders and files that have never been synced to this machine. This is the key difference to the built-in &lt;code&gt;dropbox&lt;/code&gt; CLI: you can see everything on the server, not just what&#39;s local.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/introducing-lazydbx-a-tui-for-the-dropbox-desktop-client/screenshot-server.png&quot; alt=&quot;Server tab — browse full Dropbox via API with sync status&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Without a configured Dropbox App, the Server tab falls back to the local filesystem combined with the exclude list.&lt;/p&gt;
&lt;h2&gt;Keybindings&lt;/h2&gt;
&lt;h3&gt;Global&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Tab&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Switch between Local and Server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;r&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Refresh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;q&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Quit&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Local tab&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;j&lt;/code&gt;/&lt;code&gt;k&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Navigate up/down&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Enter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Open folder or file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;u&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Go up one directory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;e&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Exclude folder from sync&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Get share link&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;y&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Copy last share link&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;f&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Show file sync status&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Server tab&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;j&lt;/code&gt;/&lt;code&gt;k&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Navigate up/down&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Enter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Open folder (or file if synced)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;u&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Go up one directory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Space&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Toggle sync (exclude/include)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;a&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Show auth status&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Early days&lt;/h2&gt;
&lt;p&gt;LazyDbx is a fresh project and might still have rough edges. If you run into issues or have ideas, feel free to &lt;a href=&quot;https://github.com/AlexZeitler/LazyDbx/issues&quot;&gt;open an issue&lt;/a&gt; or send a pull request on &lt;a href=&quot;https://github.com/AlexZeitler/LazyDbx&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Introducing sshimg.nvim: Paste Images into Remote Neovim over SSH</title>
    <link href="https://alexanderzeitler.com/articles/introducing-sshimg-nvim-paste-images-into-remote-neovim-over-ssh/"/>
    <updated>2026-03-16T12:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/introducing-sshimg-nvim-paste-images-into-remote-neovim-over-ssh/</id>
    <content type="html">&lt;h2&gt;The problem&lt;/h2&gt;
&lt;p&gt;When you write Markdown locally, pasting images is trivial - plenty of Neovim plugins handle it. But when you&#39;re editing files on a remote server over SSH, your clipboard lives on a different machine than your editor. The remote Neovim has no access to your local clipboard, so none of those plugins work.&lt;/p&gt;
&lt;p&gt;I ran into this while writing blog posts and documentation on remote machines. Taking a screenshot is easy, but getting that image from my local clipboard to the server and into the Markdown file required a manual workflow every time: save the screenshot locally, &lt;code&gt;scp&lt;/code&gt; it over, then type the Markdown image link by hand.&lt;/p&gt;
&lt;h2&gt;How sshimg.nvim solves it&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/AlexZeitler/sshimg.nvim&quot;&gt;sshimg.nvim&lt;/a&gt; connects your local clipboard to your remote Neovim through a small daemon and an SSH reverse tunnel.&lt;/p&gt;
&lt;p&gt;The flow looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Screenshot (local) → imgd daemon → SSH reverse tunnel → scp → Remote Neovim
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;You take a screenshot on your local machine (it lands in the clipboard).&lt;/li&gt;
&lt;li&gt;You press &lt;code&gt;&amp;lt;leader&amp;gt;pa&lt;/code&gt; or &lt;code&gt;&amp;lt;leader&amp;gt;pp&lt;/code&gt; in Neovim.&lt;/li&gt;
&lt;li&gt;The plugin contacts the local daemon through the SSH reverse tunnel.&lt;/li&gt;
&lt;li&gt;The daemon reads the image from the clipboard, sends it to the server via &lt;code&gt;scp&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Neovim inserts a Markdown image link at the cursor position.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The result:&lt;/p&gt;
&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;![](&lt;span class=&quot;hljs-link&quot;&gt;assets/2026-03-15-23-25-25.png&lt;/span&gt;)&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Setup&lt;/h2&gt;
&lt;h3&gt;Local machine&lt;/h3&gt;
&lt;h4&gt;1. Install the daemon&lt;/h4&gt;
&lt;p&gt;The daemon &lt;code&gt;imgd&lt;/code&gt; is a small Python script that reads images from &lt;code&gt;wl-paste&lt;/code&gt; (Wayland) and serves them over a local port.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;cp&lt;/span&gt; daemon/imgd.py ~/.local/bin/imgd&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;chmod&lt;/span&gt; +x ~/.local/bin/imgd&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2. Start the daemon&lt;/h4&gt;
&lt;p&gt;Start it as a systemd user service:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;cp&lt;/span&gt; daemon/imgd.service ~/.config/systemd/user/imgd.service&lt;br /&gt;systemctl --user &lt;span class=&quot;hljs-built_in&quot;&gt;enable&lt;/span&gt; --now imgd&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or run it manually:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;imgd&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3. Connect with a reverse tunnel&lt;/h4&gt;
&lt;p&gt;The reverse tunnel forwards port 9999 from the remote server back to your local machine, so the plugin can reach the daemon.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh -R 9999:localhost:9999 yourserver&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or add it to &lt;code&gt;~/.ssh/config&lt;/code&gt; so you don&#39;t have to type it every time:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Host yourserver
    RemoteForward 9999 localhost:9999
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Remote server&lt;/h3&gt;
&lt;p&gt;Install the plugin with your package manager. For &lt;a href=&quot;https://github.com/folke/lazy.nvim&quot;&gt;lazy.nvim&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-lua&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; {&lt;br /&gt;  &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;AlexZeitler/sshimg.nvim&amp;quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;hljs-built_in&quot;&gt;config&lt;/span&gt; = &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;hljs-params&quot;&gt;()&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;sshimg&amp;quot;&lt;/span&gt;).setup({&lt;br /&gt;      port = &lt;span class=&quot;hljs-number&quot;&gt;9999&lt;/span&gt;,&lt;br /&gt;      host = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;127.0.0.1&amp;quot;&lt;/span&gt;,&lt;br /&gt;      keymaps = {&lt;br /&gt;        assets   = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&amp;lt;leader&amp;gt;pa&amp;quot;&lt;/span&gt;,  &lt;span class=&quot;hljs-comment&quot;&gt;-- Save to ./assets/&lt;/span&gt;&lt;br /&gt;        parallel = &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;&amp;lt;leader&amp;gt;pp&amp;quot;&lt;/span&gt;,  &lt;span class=&quot;hljs-comment&quot;&gt;-- Save to same dir as current file&lt;/span&gt;&lt;br /&gt;      },&lt;br /&gt;    })&lt;br /&gt;  &lt;span class=&quot;hljs-keyword&quot;&gt;end&lt;/span&gt;,&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The values shown are the defaults - you only need to change them if your tunnel uses a different port.&lt;/p&gt;
&lt;h2&gt;Usage&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Take a screenshot (it lands in your local clipboard).&lt;/li&gt;
&lt;li&gt;Open a Markdown file in remote Neovim.&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;&amp;lt;leader&amp;gt;pa&lt;/code&gt; to save the image to &lt;code&gt;./assets/&lt;/code&gt; or &lt;code&gt;&amp;lt;leader&amp;gt;pp&lt;/code&gt; to save it next to the current file.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The plugin inserts the Markdown image link at your cursor position automatically.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/introducing-sshimg-nvim-paste-images-into-remote-neovim-over-ssh/screenshot-markdown-image-pasted-in-neovim-via-ssh.png&quot; alt=&quot;Neovim with a pasted Markdown image link over SSH&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Requirements&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Local machine:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;wl-paste&lt;/code&gt; (Wayland)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scp&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Python 3&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Remote server:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Neovim&lt;/li&gt;
&lt;li&gt;Python 3&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Early days&lt;/h2&gt;
&lt;p&gt;sshimg.nvim currently supports Linux with Wayland. Support for X11 and macOS is planned. If you run into issues or have ideas, feel free to &lt;a href=&quot;https://github.com/AlexZeitler/sshimg.nvim/issues&quot;&gt;open an issue&lt;/a&gt; or send a pull request on &lt;a href=&quot;https://github.com/AlexZeitler/sshimg.nvim&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Paste clipboard images into Claude Code over SSH</title>
    <link href="https://alexanderzeitler.com/articles/paste-clipboard-images-into-claude-code-over-ssh/"/>
    <updated>2026-03-27T12:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/paste-clipboard-images-into-claude-code-over-ssh/</id>
    <content type="html">&lt;h2&gt;The problem&lt;/h2&gt;
&lt;p&gt;Claude Code is multimodal. It can read and reason about images using the &lt;code&gt;Read&lt;/code&gt; tool. But when you run Claude Code on a remote server over SSH, there&#39;s no way to paste an image from your local clipboard into the session. The clipboard lives on your local machine, while Claude Code runs on the server.&lt;/p&gt;
&lt;p&gt;I ran into the same issue with Neovim and built &lt;a href=&quot;https://github.com/AlexZeitler/sshimg.nvim&quot;&gt;sshimg.nvim&lt;/a&gt; to solve it. Now I&#39;ve built the same thing for Claude Code: &lt;a href=&quot;https://github.com/AlexZeitler/claude-ssh-image-skill&quot;&gt;claude-ssh-image-skill&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;How it works&lt;/h2&gt;
&lt;p&gt;The solution has three parts:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;ccimgd&lt;/code&gt;&lt;/strong&gt;: a daemon running on your local machine that reads PNG images from the clipboard (&lt;code&gt;wl-paste&lt;/code&gt; on Wayland, &lt;code&gt;xclip&lt;/code&gt; on X11, &lt;code&gt;pngpaste&lt;/code&gt; on macOS) and serves them as base64-encoded JSON over TCP on port 9998&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;ccimg&lt;/code&gt;&lt;/strong&gt;: a client binary on the remote server that connects to the daemon, receives the image, and saves it as a temporary PNG file&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;/paste-image&lt;/code&gt;&lt;/strong&gt;: a Claude Code skill that runs &lt;code&gt;ccimg&lt;/code&gt; and then uses the &lt;code&gt;Read&lt;/code&gt; tool to display the image to Claude&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The connection between client and daemon runs through an SSH reverse tunnel:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Local Machine                            Remote Server (SSH)
┌──────────────────────┐                 ┌──────────────────────────┐
│  Clipboard (PNG)     │                 │  Claude Code             │
│        │             │                 │        │                 │
│        ▼             │                 │        ▼                 │
│  ccimgd (Port 9998)  │◄────────────────│  /paste-image Skill      │
│  - wl-paste          │  SSH Reverse    │  - TCP Request to ccimgd │
│  - Returns base64    │  Tunnel         │  - Receives base64 image │
│    image in response │  (Port 9998)    │  - Saves as temp file    │
│                      │                 │  - Read → Claude sees    │
└──────────────────────┘                 │    the image             │
                                         └──────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Building&lt;/h2&gt;
&lt;p&gt;Both binaries are written in Go and compile as fully static binaries with no runtime dependencies:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;git &lt;span class=&quot;hljs-built_in&quot;&gt;clone&lt;/span&gt; https://github.com/AlexZeitler/claude-ssh-image-skill.git&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;cd&lt;/span&gt; claude-ssh-image-skill&lt;br /&gt;./build.sh&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This builds statically linked binaries for all supported platforms:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;daemon/ccimgd-linux-amd64&lt;/code&gt;, &lt;code&gt;daemon/ccimgd-darwin-amd64&lt;/code&gt;, &lt;code&gt;daemon/ccimgd-darwin-arm64&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;client/ccimg-linux-amd64&lt;/code&gt;, &lt;code&gt;client/ccimg-darwin-amd64&lt;/code&gt;, &lt;code&gt;client/ccimg-darwin-arm64&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Setup&lt;/h2&gt;
&lt;h3&gt;Local machine (Linux)&lt;/h3&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;cp&lt;/span&gt; daemon/ccimgd-linux-amd64 ~/.local/bin/ccimgd&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;cp&lt;/span&gt; daemon/ccimgd.service ~/.config/systemd/user/&lt;br /&gt;systemctl --user daemon-reload&lt;br /&gt;systemctl --user &lt;span class=&quot;hljs-built_in&quot;&gt;enable&lt;/span&gt; --now ccimgd&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Local machine (macOS)&lt;/h3&gt;
&lt;p&gt;Requires &lt;code&gt;pngpaste&lt;/code&gt; for clipboard access:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;brew install pngpaste&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;cp&lt;/span&gt; daemon/ccimgd-darwin-arm64 /usr/local/bin/ccimgd   &lt;span class=&quot;hljs-comment&quot;&gt;# or ccimgd-darwin-amd64 for Intel&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;cp&lt;/span&gt; daemon/com.ccimgd.plist ~/Library/LaunchAgents/&lt;br /&gt;launchctl load ~/Library/LaunchAgents/com.ccimgd.plist&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Remote server&lt;/h3&gt;
&lt;p&gt;Copy the client binary and the Claude Code skill:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;scp client/ccimg-linux-amd64 your-server:~/.local/bin/ccimg&lt;br /&gt;scp skill/paste-image.md your-server:~/.claude/commands/paste-image.md&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To skip permission prompts, add the Bash permission to &lt;code&gt;~/.claude/settings.json&lt;/code&gt; on the server:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;permissions&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;allow&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;Bash(ccimg)&amp;quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;SSH connection&lt;/h3&gt;
&lt;p&gt;Connect with a reverse tunnel so the remote &lt;code&gt;ccimg&lt;/code&gt; can reach the local &lt;code&gt;ccimgd&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh -R 9998:localhost:9998 your-server&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or add it permanently to &lt;code&gt;~/.ssh/config&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Host your-server
    RemoteForward 9998 localhost:9998
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Usage&lt;/h2&gt;
&lt;p&gt;Copy an image to the clipboard on your local machine, then run this in Claude Code on the remote server:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/paste-image
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Claude receives the image and can reason about it: screenshots, diagrams, error messages, whatever you need.&lt;/p&gt;
&lt;h2&gt;The skill&lt;/h2&gt;
&lt;p&gt;The skill itself is minimal. It&#39;s a Markdown file that tells Claude what to do:&lt;/p&gt;
&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;Paste an image from the local clipboard into this session.&lt;br /&gt;&lt;br /&gt;Instructions:&lt;br /&gt;&lt;span class=&quot;hljs-bullet&quot;&gt;1.&lt;/span&gt; Run &lt;span class=&quot;hljs-code&quot;&gt;`ccimg`&lt;/span&gt; using the Bash tool.&lt;br /&gt;&lt;span class=&quot;hljs-bullet&quot;&gt;2.&lt;/span&gt; Use the Read tool to read the printed file path.&lt;br /&gt;   This will display the image to Claude.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Claude Code skills are just natural language instructions stored in &lt;code&gt;~/.claude/commands/&lt;/code&gt;. When you invoke &lt;code&gt;/paste-image&lt;/code&gt;, Claude follows the instructions: run the client, get the file path, read the image.&lt;/p&gt;
&lt;h2&gt;The daemon&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ccimgd&lt;/code&gt; listens on &lt;code&gt;127.0.0.1:9998&lt;/code&gt; and waits for a TCP connection. When a client connects and sends a newline-terminated message, the daemon reads the clipboard image and responds with a JSON object:&lt;/p&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;type&lt;/span&gt; response &lt;span class=&quot;hljs-keyword&quot;&gt;struct&lt;/span&gt; {&lt;br /&gt;    OK    &lt;span class=&quot;hljs-type&quot;&gt;bool&lt;/span&gt;   &lt;span class=&quot;hljs-string&quot;&gt;`json:&amp;quot;ok&amp;quot;`&lt;/span&gt;&lt;br /&gt;    Image &lt;span class=&quot;hljs-type&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;`json:&amp;quot;image,omitempty&amp;quot;`&lt;/span&gt;&lt;br /&gt;    Error &lt;span class=&quot;hljs-type&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;`json:&amp;quot;error,omitempty&amp;quot;`&lt;/span&gt;&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;Image&lt;/code&gt; field contains the base64-encoded PNG data. The daemon auto-detects the platform and uses &lt;code&gt;wl-paste&lt;/code&gt; on Wayland, &lt;code&gt;xclip&lt;/code&gt; on X11, or &lt;code&gt;pngpaste&lt;/code&gt; on macOS.&lt;/p&gt;
&lt;h2&gt;The client&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ccimg&lt;/code&gt; connects to &lt;code&gt;127.0.0.1:9998&lt;/code&gt; (which the SSH reverse tunnel forwards to the local machine), sends &lt;code&gt;{}&#92;n&lt;/code&gt;, receives the JSON response, decodes the base64 image, writes it to a temp file, and prints the path to stdout. That&#39;s all Claude Code needs to pick it up with &lt;code&gt;Read&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Coexistence with sshimg.nvim&lt;/h2&gt;
&lt;p&gt;If you also use &lt;a href=&quot;https://github.com/AlexZeitler/sshimg.nvim&quot;&gt;sshimg.nvim&lt;/a&gt;, both tools can run simultaneously. &lt;code&gt;imgd&lt;/code&gt; uses port 9999 while &lt;code&gt;ccimgd&lt;/code&gt; uses port 9998.&lt;/p&gt;
&lt;h2&gt;Source&lt;/h2&gt;
&lt;p&gt;The full source is on GitHub: &lt;a href=&quot;https://github.com/AlexZeitler/claude-ssh-image-skill&quot;&gt;AlexZeitler/claude-ssh-image-skill&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Introducing Aceto - a local dev server for building HTML mockups with AI</title>
    <link href="https://alexanderzeitler.com/articles/introducing-aceto-a-local-dev-server-for-building-html-mockups-with-ai/"/>
    <updated>2026-04-01T12:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/introducing-aceto-a-local-dev-server-for-building-html-mockups-with-ai/</id>
    <content type="html">&lt;h2&gt;The idea&lt;/h2&gt;
&lt;p&gt;Building HTML mockups with AI agents works surprisingly well - until you need to tell the agent which element to change. Describing &amp;quot;the second card in the pricing section&amp;quot; in plain text wastes tokens and is error-prone. Copying the full HTML back and forth is even worse.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/AlexZeitler/aceto&quot;&gt;Aceto&lt;/a&gt; takes a different approach: you point at an element in the browser, the agent gets a precise CSS selector. No pasting HTML, no guessing.&lt;/p&gt;
&lt;h2&gt;How it works&lt;/h2&gt;
&lt;p&gt;Aceto is not a drawing tool or visual editor. It is a feedback loop:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Tell the agent what you want&lt;/li&gt;
&lt;li&gt;The agent generates or modifies real HTML + Tailwind CSS&lt;/li&gt;
&lt;li&gt;See the result live in your browser&lt;/li&gt;
&lt;li&gt;Point at an element and say &amp;quot;change this&amp;quot;&lt;/li&gt;
&lt;li&gt;The agent understands which element you mean and modifies it&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The agent connects via MCP (Model Context Protocol), so it works with Claude Code out of the box. The browser overlay provides the visual layer - hover highlighting, element selection, and inline editing - while the agent handles all structural changes through standard HTML manipulation.&lt;/p&gt;
&lt;h2&gt;Browser overlay&lt;/h2&gt;
&lt;p&gt;When you move the mouse over any element, a blue outline appears with the element&#39;s tag and classes:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/introducing-aceto-a-local-dev-server-for-building-html-mockups-with-ai/06-hover-highlight.png&quot; alt=&quot;Hover highlighting shows tag and classes on mouseover&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Clicking an element selects it with a pink outline. The breadcrumb bar at the bottom shows the full DOM path. The scroll wheel navigates up and down the tree - parent and child elements:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/introducing-aceto-a-local-dev-server-for-building-html-mockups-with-ai/04-hero-selection.png&quot; alt=&quot;Element selection with breadcrumb bar&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The agent can also highlight elements in cyan to communicate visually - for example to ask &amp;quot;Do you mean this one?&amp;quot;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/introducing-aceto-a-local-dev-server-for-building-html-mockups-with-ai/02-agent-highlight.png&quot; alt=&quot;Agent highlighting an element in cyan with a label&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Both selections - pink for the user, cyan for the agent - can be visible at the same time.&lt;/p&gt;
&lt;h2&gt;Inline editing&lt;/h2&gt;
&lt;p&gt;Double-clicking any editable element lets you modify it directly in the browser. Text elements like headings and paragraphs open for text editing, inputs let you change their value, and checkboxes toggle on double-click:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/introducing-aceto-a-local-dev-server-for-building-html-mockups-with-ai/05-inline-editing.png&quot; alt=&quot;Inline editing of a text element&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Pressing &lt;code&gt;c&lt;/code&gt; on a selected element opens an inline editor for its Tailwind classes:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/introducing-aceto-a-local-dev-server-for-building-html-mockups-with-ai/03-class-editor.png&quot; alt=&quot;CSS class editor for Tailwind classes&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Tables get special treatment - selecting a table cell shows a floating toolbar with controls for adding and removing rows and columns:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/introducing-aceto-a-local-dev-server-for-building-html-mockups-with-ai/07-table-controls.png&quot; alt=&quot;Table controls with row and column operations&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Token efficiency&lt;/h2&gt;
&lt;p&gt;This is where Aceto differs from other AI-driven UI tools. The agent does not need to read and parse the full HTML to understand what you mean. You point at an element in the browser, the agent gets a precise selector. Write tools use the current selection by default - one tool call, no roundtrip.&lt;/p&gt;
&lt;h2&gt;Component libraries&lt;/h2&gt;
&lt;p&gt;Aceto supports adding component libraries via the CLI. DaisyUI works out of the box with Tailwind v4:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;aceto add daisyui&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/introducing-aceto-a-local-dev-server-for-building-html-mockups-with-ai/08-daisyui.png&quot; alt=&quot;DaisyUI components in Aceto&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;More features&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Yank and paste&lt;/strong&gt; - press &lt;code&gt;y&lt;/code&gt; to copy a selected element, &lt;code&gt;p&lt;/code&gt; to paste it after another selection&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Paste images&lt;/strong&gt; - Ctrl+V with a selection inserts an image instantly; without selection, it stages the image for agent-driven placement&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Asset picker&lt;/strong&gt; - press &lt;code&gt;a&lt;/code&gt; to browse and reuse previously pasted images&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Slash commands&lt;/strong&gt; - type &lt;code&gt;/list&lt;/code&gt; in an empty element to create lists&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Content shortcuts&lt;/strong&gt; - type &lt;code&gt;[]&lt;/code&gt; or &lt;code&gt;[x]&lt;/code&gt; in a cell to insert a checkbox&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Element defaults&lt;/strong&gt; - define default Tailwind classes for generated elements via &lt;code&gt;aceto.defaults.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Screenshots&lt;/strong&gt; - the agent captures full-page or element-level screenshots via &lt;code&gt;get_screenshot()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Live reload&lt;/strong&gt; - DOM morphing keeps scroll position and selection state without page reload flicker&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Getting started&lt;/h2&gt;
&lt;p&gt;Aceto requires &lt;a href=&quot;https://bun.sh/&quot;&gt;Bun&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;bun install -g github:alexzeitler/aceto&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a new project and start the dev server:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;mkdir&lt;/span&gt; my-mockup &amp;amp;&amp;amp; &lt;span class=&quot;hljs-built_in&quot;&gt;cd&lt;/span&gt; my-mockup&lt;br /&gt;aceto init&lt;br /&gt;aceto dev&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then add it as an MCP server to Claude Code:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;claude mcp add aceto -s user --transport http http://localhost:3000/mcp&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is all you need. Tell the agent what you want, point at elements to refine, and iterate until the mockup looks right.&lt;/p&gt;
&lt;h2&gt;What Aceto does not do&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;No build system - the output is a plain HTML file you can read with &lt;code&gt;cat&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;No framework - Tailwind v4 via CDN, optionally DaisyUI or Flowbite&lt;/li&gt;
&lt;li&gt;No abstraction layer - real HTML, no component model&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The source code is on &lt;a href=&quot;https://github.com/AlexZeitler/aceto&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Rails Diff MCP - an MCP server for Rails version diffs</title>
    <link href="https://alexanderzeitler.com/articles/rails-diff-mcp-server-for-rails-version-diffs/"/>
    <updated>2026-04-01T18:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/rails-diff-mcp-server-for-rails-version-diffs/</id>
    <content type="html">&lt;h2&gt;The problem&lt;/h2&gt;
&lt;p&gt;Upgrading a Rails app from one version to the next means figuring out what changed in the default &lt;code&gt;rails new&lt;/code&gt; output. You end up tabbing between &lt;a href=&quot;https://railsdiff.org/&quot;&gt;RailsDiff&lt;/a&gt; in the browser, copying file names, and pasting diffs back into your editor. If you are using an AI agent to help with the upgrade, it cannot access that information on its own - you become the middleman.&lt;/p&gt;
&lt;h2&gt;What Rails Diff MCP does&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/AlexZeitler/rails-diff-mcp&quot;&gt;Rails Diff MCP&lt;/a&gt; is an MCP server that gives your AI agent direct access to Rails version diffs. The data comes from the &lt;a href=&quot;https://github.com/railsdiff/rails-new-output&quot;&gt;railsdiff/rails-new-output&lt;/a&gt; repository, where each Rails version is tagged with the output of &lt;code&gt;rails new&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The server exposes three tools:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;list_rails_versions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List all available Rails versions (sorted)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;compare_rails_versions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Compare two versions - returns a list of changed files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_file_diff&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Get the patch for a specific file between two versions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Instead of looking up diffs yourself, you tell the agent &amp;quot;upgrade this app from 8.0.1 to 8.0.2&amp;quot; and it can inspect every changed file on its own.&lt;/p&gt;
&lt;h2&gt;Setup&lt;/h2&gt;
&lt;p&gt;The server runs via Docker Compose:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;docker compose up -d&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It listens on &lt;code&gt;http://localhost:8080/mcp&lt;/code&gt; with a health check at &lt;code&gt;http://localhost:8080/healthz&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Register it in Claude Code:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;claude mcp add --transport http rails-diff http://localhost:8080/mcp&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Optionally set a &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; environment variable to avoid GitHub API rate limits.&lt;/p&gt;
&lt;h2&gt;How an upgrade workflow looks&lt;/h2&gt;
&lt;p&gt;Once the MCP server is running and registered, a Rails upgrade conversation might go like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ask the agent to compare your current Rails version with the target version&lt;/li&gt;
&lt;li&gt;The agent calls &lt;code&gt;compare_rails_versions&lt;/code&gt; and gets the list of changed files&lt;/li&gt;
&lt;li&gt;For each relevant file, it calls &lt;code&gt;get_file_diff&lt;/code&gt; to see the exact patch&lt;/li&gt;
&lt;li&gt;It applies the changes to your project, adapting them to your existing configuration&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;No browser tabs, no copy-pasting diffs, no back-and-forth.&lt;/p&gt;
&lt;h2&gt;Credits&lt;/h2&gt;
&lt;p&gt;Built on top of &lt;a href=&quot;https://github.com/railsdiff/railsdiff&quot;&gt;RailsDiff&lt;/a&gt; by &lt;a href=&quot;https://github.com/airblade&quot;&gt;Andy Stewart&lt;/a&gt; and contributors.&lt;/p&gt;
&lt;p&gt;The source code is on &lt;a href=&quot;https://github.com/AlexZeitler/rails-diff-mcp&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Setting up Chrome DevTools MCP with Claude Code on Linux (Wayland)</title>
    <link href="https://alexanderzeitler.com/articles/chrome-devtools-mcp-with-claude-code-on-linux-wayland/"/>
    <updated>2026-04-01T19:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/chrome-devtools-mcp-with-claude-code-on-linux-wayland/</id>
    <content type="html">&lt;p&gt;If you are using &lt;a href=&quot;https://docs.anthropic.com/en/docs/claude-code&quot;&gt;Claude Code&lt;/a&gt; and want it to interact with a real browser - navigating pages, taking screenshots, clicking elements - the &lt;a href=&quot;https://github.com/anthropics/anthropic-cookbook/tree/main/misc/chrome-devtools-mcp&quot;&gt;Chrome DevTools MCP server&lt;/a&gt; is the way to go.&lt;/p&gt;
&lt;p&gt;On Linux with Wayland, getting this to work requires a few extra steps. Here is what I learned.&lt;/p&gt;
&lt;h2&gt;The problem&lt;/h2&gt;
&lt;p&gt;The Chrome DevTools MCP server uses Puppeteer under the hood. When you add it to your Claude Code config in &lt;code&gt;~/.claude.json&lt;/code&gt; like this:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;mcpServers&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;chrome-devtools-npx&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;stdio&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;command&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;npx&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;args&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;chrome-devtools-mcp@latest&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;env&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;...and try to use it, you will get an error like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Missing X server to start the headful browser.
Either set headless to true or use xvfb-run to run your Puppeteer script.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This happens because Puppeteer tries to launch Chrome with X11, but your system is running Wayland.&lt;/p&gt;
&lt;h2&gt;First attempt: connecting to a running Chrome instance&lt;/h2&gt;
&lt;p&gt;One approach is to start Chrome yourself with remote debugging enabled and then point the MCP server at it:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;google-chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug-profile --ozone-platform=wayland&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then configure the MCP server to connect to it:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;chrome-devtools-npx&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;stdio&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;command&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;npx&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;args&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;chrome-devtools-mcp@latest&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;env&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;CHROME_CDP_URL&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;http://localhost:9222&amp;quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works, but you have to manually start Chrome with the right flags every time before using the MCP tools. Not great.&lt;/p&gt;
&lt;h2&gt;The solution: let the MCP server launch Chrome with the right flags&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;chrome-devtools-mcp&lt;/code&gt; CLI supports &lt;code&gt;--chromeArg&lt;/code&gt; flags that get passed through to Chrome when the MCP server launches it. This means we can tell it to use Wayland - and suppress the annoying first-run dialogs at the same time.&lt;/p&gt;
&lt;p&gt;Here is the final config in &lt;code&gt;~/.claude.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;mcpServers&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;chrome-devtools-npx&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;stdio&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;command&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;npx&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;args&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;chrome-devtools-mcp@latest&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;--chromeArg=--ozone-platform=wayland&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;--chromeArg=--no-first-run&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;--chromeArg=--no-default-browser-check&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;--chromeArg=--disable-search-engine-choice-screen&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;--no-usage-statistics&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;--isolated&amp;quot;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;env&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let me break down the flags:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--chromeArg=--ozone-platform=wayland&lt;/code&gt; - tells Chrome to use the Wayland backend instead of X11&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--chromeArg=--no-first-run&lt;/code&gt; - skips the &amp;quot;Welcome to Chrome&amp;quot; dialog&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--chromeArg=--no-default-browser-check&lt;/code&gt; - skips the &amp;quot;Set as default browser?&amp;quot; prompt&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--chromeArg=--disable-search-engine-choice-screen&lt;/code&gt; - skips the search engine selection screen&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--no-usage-statistics&lt;/code&gt; - disables Google&#39;s usage statistics collection in the MCP server&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--isolated&lt;/code&gt; - uses a temporary profile directory that gets cleaned up when Chrome closes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;--isolated&lt;/code&gt; flag is important: without it, the MCP server stores its Chrome profile in &lt;code&gt;~/.cache/chrome-devtools-mcp/chrome-profile/&lt;/code&gt;. If a previous Chrome instance is still running (or was not shut down cleanly), launching a new one will fail with &amp;quot;The browser is already running&amp;quot; because two Chrome instances cannot share the same profile directory. With &lt;code&gt;--isolated&lt;/code&gt;, each session gets its own temporary profile, avoiding this conflict entirely.&lt;/p&gt;
&lt;p&gt;With this configuration, Claude Code will automatically launch a Chrome window when it needs browser access. No manual Chrome start required.&lt;/p&gt;
&lt;h2&gt;Using it&lt;/h2&gt;
&lt;p&gt;After restarting Claude Code, you can use the Chrome DevTools tools right away. Claude can navigate to URLs, take screenshots, click elements, fill forms, inspect network requests, and more - all through the MCP tools.&lt;/p&gt;
&lt;h2&gt;Available CLI flags&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;chrome-devtools-mcp&lt;/code&gt; CLI has quite a few options worth knowing about:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;npx chrome-devtools-mcp@latest --&lt;span class=&quot;hljs-built_in&quot;&gt;help&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Some useful ones:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--headless&lt;/code&gt; - run Chrome without a visible window&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--viewport 1280x720&lt;/code&gt; - set the initial viewport size&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--slim&lt;/code&gt; - only expose 3 tools (navigation, JavaScript execution, screenshot) instead of the full set&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--browserUrl http://127.0.0.1:9222&lt;/code&gt; - connect to an already running Chrome instance&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--isolated&lt;/code&gt; - use a temporary profile that gets cleaned up when Chrome closes&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--channel canary&lt;/code&gt; - use Chrome Canary instead of stable&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;The key insight is that the Chrome DevTools MCP server can launch Chrome on its own - you just need to pass the right platform flags via &lt;code&gt;--chromeArg&lt;/code&gt;. On Wayland, that means &lt;code&gt;--ozone-platform=wayland&lt;/code&gt;. Once configured, it just works.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>waybar-worldclock - a simple world clock for Waybar</title>
    <link href="https://alexanderzeitler.com/articles/waybar-worldclock-a-simple-world-clock-for-waybar/"/>
    <updated>2026-04-03T18:00:00Z</updated>
    <id>https://alexanderzeitler.com/articles/waybar-worldclock-a-simple-world-clock-for-waybar/</id>
    <content type="html">&lt;p&gt;If you use &lt;a href=&quot;https://github.com/Alexays/Waybar&quot;&gt;Waybar&lt;/a&gt; on your Linux desktop (for example with &lt;a href=&quot;https://omarchy.org/&quot;&gt;Omarchy&lt;/a&gt;), you might want to see the time in different timezones without opening a browser or running extra apps.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/AlexZeitler/waybar-worldclock&quot;&gt;waybar-worldclock&lt;/a&gt; is a small utility that shows your local time in the bar and displays world times in the tooltip when you hover over it.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://alexanderzeitler.com/articles/waybar-worldclock-a-simple-world-clock-for-waybar/screenshot.png&quot;&gt;&lt;img src=&quot;https://alexanderzeitler.com/articles/waybar-worldclock-a-simple-world-clock-for-waybar/screenshot.png&quot; alt=&quot;Tooltip showing world times in different timezones&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Installation&lt;/h2&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;git &lt;span class=&quot;hljs-built_in&quot;&gt;clone&lt;/span&gt; https://github.com/alexzeitler/waybar-worldclock.git&lt;br /&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;cd&lt;/span&gt; waybar-worldclock&lt;br /&gt;./install&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This copies the &lt;code&gt;worldclock&lt;/code&gt; script to &lt;code&gt;~/.local/bin/&lt;/code&gt; and creates a default config at &lt;code&gt;~/.config/worldclock/zones&lt;/code&gt; if none exists.&lt;/p&gt;
&lt;h2&gt;Configuring timezones&lt;/h2&gt;
&lt;p&gt;Edit &lt;code&gt;~/.config/worldclock/zones&lt;/code&gt; with one timezone per line, tab-separated:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;America/New_York    New York
Asia/Tokyo          Tokyo
Europe/London       London
Australia/Sydney    Sydney
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can find valid timezone names with &lt;code&gt;timedatectl list-timezones&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Waybar setup&lt;/h2&gt;
&lt;p&gt;Replace the built-in &lt;code&gt;clock&lt;/code&gt; module (or add alongside it) in your &lt;code&gt;~/.config/waybar/config.jsonc&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-jsonc&quot;&gt;&lt;code class=&quot;language-jsonc&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;modules-center&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;custom/worldclock&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then add the module definition:&lt;/p&gt;
&lt;pre class=&quot;language-jsonc&quot;&gt;&lt;code class=&quot;language-jsonc&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;custom/worldclock&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;exec&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;worldclock&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;return-type&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;json&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;&amp;quot;interval&amp;quot;&lt;/span&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;30&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;hljs-punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Restart Waybar (&lt;code&gt;killall waybar &amp;amp;&amp;amp; waybar &amp;amp;&lt;/code&gt;) and you are good to go.&lt;/p&gt;
&lt;h2&gt;Uninstall&lt;/h2&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;./uninstall&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The source code is on &lt;a href=&quot;https://github.com/AlexZeitler/waybar-worldclock&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
</feed>