<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
  xmlns:atom="http://www.w3.org/2005/Atom"
  xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Johan Driessen</title>
    <link>https://johan.driessen.se/</link>
    
    <atom:link href="https://johan.driessen.se/feed/rss.xml" rel="self" type="application/rss+xml"/>
    
    <description></description>
    <pubDate>Mon, 31 Mar 2025 15:20:01 GMT</pubDate>
    <generator>http://hexo.io/</generator>
    
    <item>
      <title>How to give Docker containers outbound network access on Ubuntu</title>
      <link>https://johan.driessen.se/posts/How-to-give-Docker-containers-outbound-network-access-on-Ubuntu/</link>
      <guid>https://johan.driessen.se/posts/How-to-give-Docker-containers-outbound-network-access-on-Ubuntu/</guid>
      <pubDate>Mon, 31 Mar 2025 14:51:39 GMT</pubDate>
      
        
        
      <description>&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;p&gt;The other week I was setting up &lt;a hre</description>
        
      
      
      
      <content:encoded><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>The other week I was setting up <a href="https://freshrss.org/">FreshRSS</a> as a replacement for <a href="https://www.inoreader.com/">Inoreader</a> that I have been using for years.<br>Inoreader works fine, but like many other companies they are trying to integrate lots of AI crap nowadays, and I just down want that in an RSS aggregator! So I thought it was<br>time to try something self-hosted, and FreshRSS seemed to tick every box.</p><p>Anyway, this post is not about FreshRSS<sup id="fnref:1"><a href="#fn:1" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="Although I will say that FreshRSS seems to work splendidly. I have been using it exclusively for about two weeks now, and it's really great! I strongly recommend it if you don't mind hosting your own RSS aggregator!">[1]</span></a></sup>, but rather how I solved a Docker problem that I’m sure I’ve run into several times before. I am writing these words in the<br>hope that next time, I’ll remember to read my own blog instead of searching on the Interwebs for at least two hours…</p><p>I was going to install FreshRSS on a <a href="https://www.hetzner.com/">Hetzner</a> virtual server running Ubuntu 22.04, and since that server runs some other stuff as well, I decided that it would be easiest<br>to run FreshRSS in a docker container. So I followed the instructions at <a href="https://github.com/FreshRSS/FreshRSS/tree/edge/Docker#readme">https://github.com/FreshRSS/FreshRSS/tree/edge/Docker#readme</a>.<br>Everything worked fine, I could even import all my feeds from Inoreader - except that my docker container could not download any RSS Feeds.</p><p>So yeah, it seems that by default Docker on Ubuntu<sup id="fnref:2"><a href="#fn:2" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="This at least seams to be true for Ubuntu 22.04 and 24.04, I haven't verified it on anything else.">[2]</span></a></sup> does not allow containers to access the outside network at all. After following a lot of false leads I finally determined that<br>all you have to do is to add <code>&quot;iptables&quot;: true</code> to <code>/etc/docker/daemon.json</code>. So after changing this, my daemon.json now looks like this:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line"><span class="attr">&quot;iptables&quot;</span>: <span class="literal">true</span>,</span><br><span class="line"><span class="attr">&quot;log-driver&quot;</span>: <span class="string">&quot;json-file&quot;</span>,</span><br><span class="line"><span class="attr">&quot;log-opts&quot;</span>: &#123;</span><br><span class="line"><span class="attr">&quot;max-size&quot;</span>: <span class="string">&quot;50m&quot;</span>,</span><br><span class="line"><span class="attr">&quot;max-file&quot;</span>: <span class="string">&quot;3&quot;</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>I’m no expert on Linux networking, even though I’ve been using Linux since 1998 or so, and even run publicly available servers since 2000, so I’m not going to explain exactly why this is needed.<br>But from my understanding, settings iptables to true in the config files makes Docker set up the required iptables rules on startup, and that fixes the outbound connectivity.</p><p>Now all I need to do is remember this for next time. Well, fingers crossed…</p><div id="footnotes"><hr><div id="footnotelist"><ol style="list-style: none; padding-left: 0; margin-left: 40px"><li id="fn:1"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">1.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">Although I will say that FreshRSS seems to work splendidly. I have been using it exclusively for about two weeks now, and it's really great! I strongly recommend it if you don't mind hosting your own RSS aggregator!<a href="#fnref:1" rev="footnote"> ↩</a></span></li><li id="fn:2"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">2.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">This at least seams to be true for Ubuntu 22.04 and 24.04, I haven't verified it on anything else.<a href="#fnref:2" rev="footnote"> ↩</a></span></li></ol></div></div>]]></content:encoded>
      
      
      
      <category domain="https://johan.driessen.se/tags/linux/">linux</category>
      
      <category domain="https://johan.driessen.se/tags/ubuntu/">ubuntu</category>
      
      <category domain="https://johan.driessen.se/tags/docker/">docker</category>
      
      
      <comments>https://johan.driessen.se/posts/How-to-give-Docker-containers-outbound-network-access-on-Ubuntu/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Multiple Dropbox accounts on Linux using Docker</title>
      <link>https://johan.driessen.se/posts/Multiple-Dropbox-accounts-on-Linux-using-Docker/</link>
      <guid>https://johan.driessen.se/posts/Multiple-Dropbox-accounts-on-Linux-using-Docker/</guid>
      <pubDate>Wed, 18 Sep 2024 14:13:42 GMT</pubDate>
      
        
        
      <description>&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;p&gt;I use Dropbox heavily, both for person</description>
        
      
      
      
      <content:encoded><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>I use Dropbox heavily, both for personal files, and for work files. So I have a personal account, and I also have a work account. I am also what you<br>might call an Operating System Agnostic, that is I use Windows, Mac OS and Linux pretty much equally.</p><p>Now, for some reason the <a href="https://www.dropbox.com/install-linux">Dropbox client for Linux</a> doesn’t have support for multiple accounts at all. In fact, the Linux Dropbox client seems all but<br>forgotten. This is obviously a problem for me. Now, this isn’t new to any one I suppose, but I think I figured out a pretty good way of handling<br>it, so that once it’s set up I can just forget about it.</p><p>My method is to use the standard client for my personal account and Docker for my work account (vice versa would of course work equally well).<br>I use the <a href="https://github.com/otherguy/docker-dropbox">Dropbox Docker Image</a> created by otherguy. There might very<br>well be other dropbox images that work better, I have no idea, but this one does the job for me.</p><p>I recently had to set this up for maybe the third time, so I figured it was time to document it, hence this post.</p><p>First, I created a docker compose file, <code>compose.yaml</code>:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">dropbox:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">otherguy/dropbox:latest</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">dropbox</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">/home/johan/.dropbox-approach:/opt/dropbox</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="attr">TZ:</span> <span class="string">&quot;Europe/Stockholm&quot;</span></span><br><span class="line">      <span class="attr">DROPBOX_UID:</span> <span class="number">1000</span></span><br><span class="line">      <span class="attr">DROPBOX_GID:</span> <span class="number">1000</span></span><br><span class="line">      <span class="attr">POLLING_INTERVAL:</span> <span class="number">20</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">unless-stopped</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>The <code>DROPBOX_UID</code>and <code>DROPBOX_GID</code> should match the userid and groupid of your user, but in most single-user linux systems it will be <strong>1000</strong>, and you should of course use the appropriate time zone for your location. Other than that, the only interesting thing is where to map the volume to. Since the <code>/opt/dropbox</code> volume of this container not only contains your actual dropbox, but a number of other files as well, I found it to be best to map it to a hidden folder, and the symlink my actual Dropbox folder later.</p><p>After this, start the container, and start following the logs:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&gt; docker compose up -d</span><br><span class="line">&gt; docker logs -f dropbox</span><br></pre></td></tr></table></figure><p>The reason why you have to follow the logs is that the container will print a URL that you have to use to connect it to your dropbox account. It will look something like this:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">This computer isn&#x27;t linked to any Dropbox account ...</span><br><span class="line">Please visit https://www.dropbox.com/cli_link_nonce?nonce=768d09d7-c6b9-4574-8aad-7a8d41fa9728</span><br></pre></td></tr></table></figure><p>Just paste this link into your <a href="https://www.mozilla.org/en-US/firefox/">favourite web browser</a>, log in to Dropbox and just like that, the syncing will start.</p><p>If you want to exclude some folders from syncing, you have to use the command line inside the container. For an overview of all the commands, use</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&gt; sudo docker <span class="built_in">exec</span> -it dropbox gosu dropbox dropbox exclude <span class="built_in">help</span></span><br></pre></td></tr></table></figure><p>To exclude a folder, use</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&gt; sudo docker <span class="built_in">exec</span> -it dropbox gosu dropbox dropbox exclude add <span class="string">&quot;Approach Dropbox/Top folder/Sub folder&quot;</span></span><br></pre></td></tr></table></figure><p>You have to start with the name of your dropbox, I have no idea why, but it’s the same as the folder name in the file system (directly below the hidden folder, in my case .dropbox-approach).</p><p>The only thing remaining is to symlink it in your home folder:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&gt; ln -s ~/.dropbox-approach/Approach\ Dropbox ~/Approach\ Dropbox</span><br></pre></td></tr></table></figure><p>Now maybe I won’t have to look allt this up a fourth time!</p>]]></content:encoded>
      
      
      
      <category domain="https://johan.driessen.se/tags/dropbox/">dropbox</category>
      
      <category domain="https://johan.driessen.se/tags/linux/">linux</category>
      
      <category domain="https://johan.driessen.se/tags/docker/">docker</category>
      
      
      <comments>https://johan.driessen.se/posts/Multiple-Dropbox-accounts-on-Linux-using-Docker/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>How to copy an Azure SQL database to your local development SQL Server using Azure Data Studio</title>
      <link>https://johan.driessen.se/posts/How-to-copy-an-Azure-SQL-database/</link>
      <guid>https://johan.driessen.se/posts/How-to-copy-an-Azure-SQL-database/</guid>
      <pubDate>Wed, 23 Aug 2023 12:29:20 GMT</pubDate>
      
        
        
      <description>&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;p&gt;&lt;em&gt;This is a guest post from my colle</description>
        
      
      
      
      <content:encoded><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p><em>This is a guest post from my colleague Staffan Rydergård.</em></p><h2 id="Export-your-Azure-SQL-database"><a href="#Export-your-Azure-SQL-database" class="headerlink" title="Export your Azure SQL database"></a>Export your Azure SQL database</h2><p>In Azure Portal, navigate to your SQL database and select Overview -&gt; Export</p><img src="/posts/How-to-copy-an-Azure-SQL-database/azure-export-db.png" class=""><p>Select an existing Storage account and Container as target location for your exported database.</p><p>Fill in your SQL Server credentials and then click on OK. Azure portal will start an export task in the background and notify when the export is done.</p><h2 id="Import-the-database-on-your-local-SQL-server"><a href="#Import-the-database-on-your-local-SQL-server" class="headerlink" title="Import the database on your local SQL server"></a>Import the database on your local SQL server</h2><p>Using e.g. Microsoft Azure Storage Explorer connect to your storage account download the newly created .bacpac file from the container you selected earlier.</p><p>Open Azure Data Studio and view Extensions. Search for “SQL Server Dacpac” from Microsoft and install the extension.</p><p>Connect to your local SQL server and right click on the Databases folder.</p><p>Select Data-tier Application Wizard.</p><p>In the wizard, select the option “Create a database from a .bacpac file [Import Bacpac]” and select the downloaded .bacpac file and name the target database you would like to create.</p><h2 id="Finishing-touches"><a href="#Finishing-touches" class="headerlink" title="Finishing touches"></a>Finishing touches</h2><p>If you are using a dedicated login to access your database from your code then you need to create a user in the imported database for that login. Right click on the imported database and select “New Query”. If your login user name is e.g. “devuser” run the following statements to add the user and assign the db_owner role to that user.</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">USER</span> devuser <span class="keyword">FROM</span> LOGIN devuser;</span><br><span class="line"><span class="keyword">EXEC</span> sp_addrolemember <span class="string">&#x27;db_owner&#x27;</span>, <span class="string">&#x27;devuser&#x27;</span>;</span><br></pre></td></tr></table></figure>]]></content:encoded>
      
      
      
      <category domain="https://johan.driessen.se/tags/sql/">sql</category>
      
      <category domain="https://johan.driessen.se/tags/azure/">azure</category>
      
      
      <comments>https://johan.driessen.se/posts/How-to-copy-an-Azure-SQL-database/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>I guess I&#39;m on Mastodon now</title>
      <link>https://johan.driessen.se/posts/I-guess-I-m-on-Mastodon-now/</link>
      <guid>https://johan.driessen.se/posts/I-guess-I-m-on-Mastodon-now/</guid>
      <pubDate>Mon, 07 Nov 2022 14:59:52 GMT</pubDate>
      
        
        
      <description>&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;p&gt;So, yeah, I also joined that elephant-</description>
        
      
      
      
      <content:encoded><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>So, yeah, I also joined that elephant-network, Mastodon. You can find me at <a href="https://mastodon.social/@nahojd">mastodon.social/@nahojd</a>. Beacause, you know, I want to be where it happens.</p><p>I’ve come to realise that not everyone has the same experience of Twitter that I have, mostly beacuse I’m using the Flamingo client for Android, which means I have a strictly chronological flow, consisting only of accounts that I follow, and no ads. So I’m keeping my Twitter account for the forseable future, or until they cut 3rd party API access completely!</p>]]></content:encoded>
      
      
      
      <category domain="https://johan.driessen.se/tags/meta/">meta</category>
      
      <category domain="https://johan.driessen.se/tags/social/">social</category>
      
      
      <comments>https://johan.driessen.se/posts/I-guess-I-m-on-Mastodon-now/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Goodbye, Google Analytics, hello Matomo!</title>
      <link>https://johan.driessen.se/posts/Goodbye-Google-Analytics-hello-Matomo/</link>
      <guid>https://johan.driessen.se/posts/Goodbye-Google-Analytics-hello-Matomo/</guid>
      <pubDate>Thu, 20 Jan 2022 18:58:23 GMT</pubDate>
      
        
        
      <description>&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;p&gt;This blog has been using &lt;a href=&quot;http</description>
        
      
      
      
      <content:encoded><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>This blog has been using <a href="https://analytics.google.com/">Google Analytics</a> (GA) to track visitors<sup id="fnref:1"><a href="#fn:1" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="Nothing nefarious, I swear! I just want to be able to see which posts are getting read, what browsers and operations systems you are using and that stuff.">[1]</span></a></sup> since pretty much the beginning. Mostly because it’s free, and I knew how to use it since I’ve been using it professionally for clients since forever. But I haven’t really felt good about it for some time, for two primary reasons:</p><ol><li>I see no reason whatsoever in providing Google with data about my visitors.</li><li>It’s really not that good anymore. GA is very focused on e-commerce, and simple not suited for this task anymore.</li></ol><p>So when I read about the recent <a href="https://www.jurist.org/news/2022/01/austria-data-authority-rules-websites-use-of-google-analytics-violates-gdpr/">Schrems II ruling in Austria</a>, where they find that Google Analytics is now probably<sup id="fnref:2"><a href="#fn:2" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="When it comes to law, EU and GDPR, it is always probably...">[2]</span></a></sup> illegal i the EU, I thought it was time for a change! The only question then, is what to use instead!</p><p>One obvious option was to simply remove the GA tracking script and not replace it at all, instead just relying in the server logs. To be honest, that would probably be enough for my purposes, even if I would lose some information about browser capabilities, screen sizes and such. But there are other sites where I also would like to replace GA, where that wouldn’t be enough, so I though I’d better find a better alternative.</p><p>There are, of course, numerous alternatives, at least if you’re willing to pay. If I was doing this for a client, I wouldn’t have a problem with that. But for this blog, where I write the occasional post and definitely don’t make any money, I’d prefer it if it was free. I also wouldn’t mind hosting the tracking service myself, as that would pretty much guarantee that the data doesn’t fall into the wrong hands.</p><p>So after a bit of research, I decided on <a href="https://matomo.org/">Matomo Analytics</a>, which can be installed on-premise, but also has a cloud hosting solution. It also has a lot of plugins, and even lets me import my old data from Google Analytics!</p><p>Matomo has pretty good documentation, with <a href="https://matomo.org/docs/installation/">nice guide on how to install it on a server</a>, but I decided to use a Docker container instead, since there is an <a href="https://hub.docker.com/_/matomo">official image from Matomo on docker hub</a>. Matomo also needs a MySQL or MariaDB database to store the data in, and to be honest I had some problems getting Matomo in the container to talk to the database.</p><p>In the end I installed MariaDB from <a href="https://hub.docker.com/_/mariadb/">the official docker image</a> as well, as it seemed easier to get the Matomo container talking to the database in another container, than to the db on the host system.</p><p>If it is of any help to someone else trying this, I installed the images like this, although you should probably create a persistent volume for mariadb as well, if you have to reinstall it:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ sudo docker run -d --name mariadb --env MARIADB_ROOT_PASSWORD=somesupersecretpassword mariadb:latest</span><br><span class="line">$ sudo docker run -d -p 8080:80 --link mariadb:db -v matomo:/var/www/html --name matomo matomo</span><br></pre></td></tr></table></figure><p>I then inspected the mariadb container to find out what IP address it was using (it was <code>172.17.0.3</code>!), and used that when connecting from Matomo. Oh yeah, I also attached to bash in the mariadb container and created and database and a database user for Matomo to use before setting up Matomo.</p><p>And that, I think, was it! Now it’s just a matter of seeing if it works properly!</p><div id="footnotes"><hr><div id="footnotelist"><ol style="list-style: none; padding-left: 0; margin-left: 40px"><li id="fn:1"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">1.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">Nothing nefarious, I swear! I just want to be able to see which posts are getting read, what browsers and operations systems you are using and that stuff.<a href="#fnref:1" rev="footnote"> ↩</a></span></li><li id="fn:2"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">2.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">When it comes to law, EU and GDPR, it is always probably...<a href="#fnref:2" rev="footnote"> ↩</a></span></li></ol></div></div>]]></content:encoded>
      
      
      
      <category domain="https://johan.driessen.se/tags/meta/">meta</category>
      
      <category domain="https://johan.driessen.se/tags/analytics/">analytics</category>
      
      
      <comments>https://johan.driessen.se/posts/Goodbye-Google-Analytics-hello-Matomo/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Loading private certificates from a Dotnet Core app in an Azure Linux App Service</title>
      <link>https://johan.driessen.se/posts/Loading-private-certificates-from-a-Dotnet-Core-app-in-an-Azure-Linux-App-Service/</link>
      <guid>https://johan.driessen.se/posts/Loading-private-certificates-from-a-Dotnet-Core-app-in-an-Azure-Linux-App-Service/</guid>
      <pubDate>Fri, 26 Feb 2021 19:09:42 GMT</pubDate>
      
        
        
      <description>&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;p&gt;This is somewhat a follow up to my pre</description>
        
      
      
      
      <content:encoded><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>This is somewhat a follow up to my previous post <a href="/posts/Calling-the-Swish-Payment-API-from-Azure-AppService/" title="Calling the Swish Payment API from Azure AppService">Calling the Swish Payment API from Azure AppService</a>, although it is not about Swish at all. It is, however, about certificates in Azure. It is also way too long, if you’re the impatient type, I’ve added a <a href="#TL-DR">TL;DR</a></p><p>Today, I was implementing authentication and signing with <a href="https://www.bankid.com/utvecklare/rp-info">BankID</a>, a popular electronic identification system in Sweden<sup id="fnref:1"><a href="#fn:1" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="In fact, it is so popular that it is the de facto standard. Many therefore assume that it is governement run, but in reality it is run by the largest banks in Sweden!">[1]</span></a></sup>, for a client. I was using a library called <a href="https://github.com/ActiveLogin/">Active Login</a><sup id="fnref:2"><a href="#fn:2" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="Active Login is created by Active Solution, which is actually a previous employer of mine!">[2]</span></a></sup>, which makes it a whole lot easier to implement. That is not really relevant to this post, though. What <em>is</em> relevant is that in order to call the BankID API, you need to use a certificate issued by your bank.</p><p>I had previously only worked with private certificates in Azure in Windows-based App Services. This time, however, we’re using Linux-based App Services. I had foolishly assumed that loading certificates in them would work the same way, since we’re still using a dotnet core application. Obviously, this turned out to be a false assumption<sup id="fnref:3"><a href="#fn:3" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="Otherwise, I would probably not have written this blog post.">[3]</span></a></sup>.</p><p>When you want to use private certificates in Azure, you probably don’t want to include them with your code<sup id="fnref:4"><a href="#fn:4" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="You also do not want to include api keys and passwords in your code, unless you want to be embarrassed on Twitter!">[4]</span></a></sup>. Instead you upload them to your App Service, either using the Azure Portal, or using the command line<sup id="fnref:5"><a href="#fn:5" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="Or so I am told. I have not tried it. Uploading certificates seems to be something I do quite rarely, so I have not really felt the need to automate it.">[5]</span></a></sup>.</p><img src="/posts/Loading-private-certificates-from-a-Dotnet-Core-app-in-an-Azure-Linux-App-Service/azure-app-service-private-certificates.png" class="" title="This is a picture showing the place where you handle your certificates in an Azure App Service. Really, really exciting stuff."><p>In a Windows-based App Service, when you upload private certificates to your app service, they end up in the Certificates Store, more specifically in the Personal Certificates store for the CurrentUser. They can then be loaded with these few lines of code:</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> certStore = <span class="keyword">new</span> X509Store(StoreName.My, StoreLocation.CurrentUser);</span><br><span class="line">certStore.Open(OpenFlags.ReadWrite);</span><br><span class="line"><span class="keyword">var</span> certs = certStore.Certificates.Find(X509FindType.FindByThumbprint, <span class="string">&quot;THUMBPRINT OF CERTIFICATE&quot;</span>, <span class="literal">false</span>);</span><br></pre></td></tr></table></figure><p>This, however does not work in Linux. Correction: it does not work in a Linux-based App Service<sup id="fnref:6"><a href="#fn:6" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="As it turns out, you can use the certificate store in dotnet core running in Linux, you just have to add the certificates in code. Also, the cert store is internal to dotnet core, and I assume that is basically just there for Windows compatibility. You can read a lot more about it in [the App Service documentation](https://docs.microsoft.com/en-us/dotnet/standard/security/cross-platform-cryptography#x509-certificates).">[6]</span></a></sup>, because there is no certificate store in Linux (not in the Windows way, at least). But wait, you say, you can still upload certificates through the Azure Portal in a Linux-based App Service! Where do they end up? Have no fear, child, that is what I am here to tell you!</p><p>If you, or rather I, had bothered to read the documentation, I, or perhaps you, would have found a page called <a href="https://docs.microsoft.com/en-us/azure/app-service/configure-ssl-certificate-in-code#load-certificate-in-linuxwindows-containers">Use SSL cert in code, and maybe even the heading &ldquo;Load certificate in Linux/Windows containers&rdquo;</a>. Here, it clearly states that private certificates in Linux containers<sup id="fnref:7"><a href="#fn:7" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="Which I guess is that Linux based App Services really are...">[7]</span></a></sup> are stored in <code>/var/ssl/private</code>, as long as you remember to set the environment variable <code>WEBSITE_LOAD_CERTIFICATES</code> to the thumbprint of your certificate (or <code>*</code> to load all of them).</p><img src="/posts/Loading-private-certificates-from-a-Dotnet-Core-app-in-an-Azure-Linux-App-Service/app-service-cert-locations.png" class="" title="Had I found this table earlier, I would not have been able to charge as many hours today. So, yeah."><p>Alright, so I opened a SSH session to my App Service, and typed in <code>ls /var/ssl/private</code>. Nothing. In fact, the directory did not even exist. Fortunately, my many hours of banging my head against the Swish implementation bare fruit, and I suddenly remembered that changes to the certificates <em>often does not apply unless you stop and start the App Service</em>. Restart is not enough, you have to Stop it! And then start it again. I checked again, and heureka! My certificate was there! In form of a name called <code>THUMBPRINTINCAPITALLETTERS.p12</code><sup id="fnref:8"><a href="#fn:8" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="It was not actually called _THUMBPRINTINCAPITALLETTERS.p12_, but I hope you get the point. I do, however, find it funny that you __MAY NOT__ upload a certificate with the .p12 extension, it __HAS TO BE__ .pfx, but after you uploaded it, it gets saved as .p12 anyway.">[8]</span></a></sup>.</p><p>So, to answer the question that I may have promised to answer by naming this post &ldquo;Loading private certificates from a Dotnet Core app in an Azure Linux App Service&rdquo;:</p><p>You just load the file. They are not even encrypted anymore, so you don’t need a password:</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> cert = <span class="keyword">new</span> X509Certificate2(<span class="string">&quot;/var/ssl/private/THUMBPRINTINCAPITALLETTERS.p12&quot;</span>);</span><br></pre></td></tr></table></figure><p>So there. Easy, peasy!</p><p>Wow, I did not expect this post to be this long. I’d better add a TLDR. I obviously find writing way to fun. Also, I may have broken my previous number-of-footnotes-record!</p><h2 id="TL-DR"><a href="#TL-DR" class="headerlink" title="TL;DR"></a>TL;DR</h2><ul><li>Private certificates in Linux-based App Services are stored in <code>/var/ssl/private</code> and named as their <code>thumbprint.p12</code>.</li><li>In order to load them from code, you can just pass the path to the constructor of <code>X509Certificate2</code>. It is not password protected.</li></ul><div id="footnotes"><hr><div id="footnotelist"><ol style="list-style: none; padding-left: 0; margin-left: 40px"><li id="fn:1"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">1.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">In fact, it is so popular that it is the de facto standard. Many therefore assume that it is governement run, but in reality it is run by the largest banks in Sweden!<a href="#fnref:1" rev="footnote"> ↩</a></span></li><li id="fn:2"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">2.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">Active Login is created by Active Solution, which is actually a previous employer of mine!<a href="#fnref:2" rev="footnote"> ↩</a></span></li><li id="fn:3"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">3.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">Otherwise, I would probably not have written this blog post.<a href="#fnref:3" rev="footnote"> ↩</a></span></li><li id="fn:4"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">4.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">You also do not want to include api keys and passwords in your code, unless you want to be embarrassed on Twitter!<a href="#fnref:4" rev="footnote"> ↩</a></span></li><li id="fn:5"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">5.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">Or so I am told. I have not tried it. Uploading certificates seems to be something I do quite rarely, so I have not really felt the need to automate it.<a href="#fnref:5" rev="footnote"> ↩</a></span></li><li id="fn:6"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">6.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">As it turns out, you can use the certificate store in dotnet core running in Linux, you just have to add the certificates in code. Also, the cert store is internal to dotnet core, and I assume that is basically just there for Windows compatibility. You can read a lot more about it in <a href="https://docs.microsoft.com/en-us/dotnet/standard/security/cross-platform-cryptography#x509-certificates">the App Service documentation</a>.<a href="#fnref:6" rev="footnote"> ↩</a></span></li><li id="fn:7"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">7.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">Which I guess is that Linux based App Services really are...<a href="#fnref:7" rev="footnote"> ↩</a></span></li><li id="fn:8"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">8.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">It was not actually called <em>THUMBPRINTINCAPITALLETTERS.p12</em>, but I hope you get the point. I do, however, find it funny that you <strong>MAY NOT</strong> upload a certificate with the .p12 extension, it <strong>HAS TO BE</strong> .pfx, but after you uploaded it, it gets saved as .p12 anyway.<a href="#fnref:8" rev="footnote"> ↩</a></span></li></ol></div></div>]]></content:encoded>
      
      
      
      <category domain="https://johan.driessen.se/tags/linux/">linux</category>
      
      <category domain="https://johan.driessen.se/tags/dotnet-core/">dotnet core</category>
      
      <category domain="https://johan.driessen.se/tags/azure/">azure</category>
      
      
      <comments>https://johan.driessen.se/posts/Loading-private-certificates-from-a-Dotnet-Core-app-in-an-Azure-Linux-App-Service/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Fixing the missing Azure Context in Azure Powershell</title>
      <link>https://johan.driessen.se/posts/Fixing-the-missing-Azure-Context-in-Azure-Powershell/</link>
      <guid>https://johan.driessen.se/posts/Fixing-the-missing-Azure-Context-in-Azure-Powershell/</guid>
      <pubDate>Fri, 08 May 2020 12:24:54 GMT</pubDate>
      
        
        
      <description>&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;p&gt;So, there I was trying to run some com</description>
        
      
      
      
      <content:encoded><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>So, there I was trying to run some command in <a href="https://docs.microsoft.com/en-us/powershell/azure/?view=azps-3.8.0" target="_blank" rel="noopener">Azure Powershell</a>, when suddenly I was greeted with the following error:</p><p><code>&gt; Some-Az-Action-That-Is-None-Of-Your-Business</code></p><pre><code>Your Azure credentials have not been set up or have expired, pleaserun Connect-AzAccount to set up your Azure credentials.</code></pre><p>Alright, I thought, maybe by credentials actually have expired, better do what it says!</p><p><code>&gt; Connect-AzAccount</code></p><pre><code>WARNING: To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code DVS4FGXQR to authenticate.</code></pre><p>Said, and done, surely it will work now!?</p><p><code>&gt; Some-Az-Action-That-Is-None-Of-Your-Business</code></p><pre><code>No tenant found in the context.  Please ensure that the credentials you providedare authorized to access an Azure subscription, then run Connect-AzAccount to login.</code></pre><p>Now, this is weird. I’m, pretty sure I just signed in with the correct Azure account. What just happened?</p><p><code>❯ Get-AzContex</code></p><pre><code>Name                                     Account    SubscriptionName Environment TenantId----                                     -------    ---------------- ----------- --------Default                                  jd@approa…                  AzureCloud</code></pre><p>Hey, that’s not correct! Where is my subscription and my tenant? This is getting curiouser and curiouser! I will spare you a couple of rounds of trying to sign in with different accounts, trying to manually set the tenant and duckduckgoing the shit out of this. Suffice to say that in the end it seems this is something that happens, your Azure Context becomes corrupted in some way. Fortunately, there is an easy way to fix it:</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&gt; Clear-AzContext</span><br><span class="line">&gt; Connect-AzAccount</span><br></pre></td></tr></table></figure><p>Simply clear the Azure context and re-connect, and it should all start working again. Now, if you run <code>Get-AzContext</code> again you will hopefully see something more like this:</p><pre><code>Name                                     Account    SubscriptionName Environment TenantId----                                     -------    ---------------- ----------- --------Betala per användning (0058c7f9-344f-4d… jd@approa… Betala pe…       AzureCloud  77eb242d…</code></pre><p>Weirdly enough, this happened to me on two different computers, just two days apart. It didn’t take nearly as long to fix it the second time, but at least it made me write this blog post!</p>]]></content:encoded>
      
      
      
      <category domain="https://johan.driessen.se/tags/Powershell/">Powershell</category>
      
      <category domain="https://johan.driessen.se/tags/Azure/">Azure</category>
      
      
      <comments>https://johan.driessen.se/posts/Fixing-the-missing-Azure-Context-in-Azure-Powershell/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Calling the Swish Payment API from Azure AppService</title>
      <link>https://johan.driessen.se/posts/Calling-the-Swish-Payment-API-from-Azure-AppService/</link>
      <guid>https://johan.driessen.se/posts/Calling-the-Swish-Payment-API-from-Azure-AppService/</guid>
      <pubDate>Mon, 16 Mar 2020 09:44:58 GMT</pubDate>
      
        
        
      <description>&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;p&gt;For the last couple of months, I’ve be</description>
        
      
      
      
      <content:encoded><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>For the last couple of months, I’ve been working on a new version of a site for a client that uses Swish for payments. This new version will be hosted as an Azure App Service. The <a href="https://www.swish.nu/developer#swish-for-merchants" target="_blank" rel="noopener">Swish API</a> is pretty nice and straight forward, but for some reason they have implemented authentication and security using client certificates instead of something like <a href="https://oauth.net/2/" target="_blank" rel="noopener">OAuth 2</a>. This makes it a bit more difficult, especially in Azure, since neither the server certificate for the API or the client certificates are signed by trusted authorities.</p><p>In fact, during my research<sup id="fnref:1"><a href="#fn:1" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="Yes, that is what we call googling now!">[1]</span></a></sup> I found many claims that it simply does not work, that you have to use a virtual machine in order to make the calls to the Swish API work. However, I also <a href="https://stackoverflow.com/a/60428617" target="_blank" rel="noopener">this answer on Stack Overflow</a> that claimed that the trick was simply to upload all certificates to Azure, and this turned out to be true. </p><p>So, in order to remember this for the next time, and hopefully help anyone else with the same problem, I decided to write a more comprehensive guide on how to get this working.</p><h2 id="1-Download-the-simulator-certificates"><a href="#1-Download-the-simulator-certificates" class="headerlink" title="1. Download the simulator certificates"></a>1. Download the simulator certificates</h2><p>All examples will be based on the Swish Test Merchant certificates, which can be downloaded from <a href="https://www.swish.nu/developer#swish-for-merchants" target="_blank" rel="noopener">the Swish developer page</a> (click View Guide in the Simulator Guide box<sup id="fnref:2"><a href="#fn:2" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="This will almost certainly be incorrect within minutes of publishing this, since they seem to change it between every time I look!">[2]</span></a></sup>). Extract the downloaded file and locate the file named <code>Swish_Merchant_TestCertificate_1234679304.p12</code> (or whatever they may have changed it to), and change the extension to <code>pfx</code>, since that is the extension that Azure will expect later<sup id="fnref:3"><a href="#fn:3" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="p12 and pfx are just two different extension for the same certificate format, PKCS12.">[3]</span></a></sup>.</p><h2 id="2-Extract-all-the-root-certificates-from-the-p12-pfx-file"><a href="#2-Extract-all-the-root-certificates-from-the-p12-pfx-file" class="headerlink" title="2. Extract all the root certificates from the .p12/.pfx file"></a>2. Extract all the root certificates from the .p12/.pfx file</h2><p>The <em>.pfx</em> file contains the whole certificate chain, and when working on a Windows machine, it will be enough to install that to your CurrentUser och LocalMachine store (depending on how you run your application), but in Azure you will need to upload all certificates separately. It is therefore necessary to extract the certificates from the file. This can be done in a number of different ways. If you <em>are</em> working on a Windows machine, you could just install the certificate, and then go into the certificate store and export the resulting certificates.</p><p>However, you can also do it from the command line with <a href="https://www.openssl.org/" target="_blank" rel="noopener">openssl</a>. Now, I’m no expert on openssl, and I’m sure there is a better way to do this, <a href="https://unix.stackexchange.com/a/393484" target="_blank" rel="noopener">this answer on the Unix &amp; Linux StackExchange</a> for example suggests piping the result through <code>sed</code>, but we’re only doing this once, so this works good enough.</p><p>First, list the certs in .pfx file and send the results to a text file<sup id="fnref:4"><a href="#fn:4" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="The password for the simulator certificates really is just _swish_. For production certs, you will of course need to use the real password.">[4]</span></a></sup>.</p><p><code>openssl pkcs12 -nokeys -info -in ./Swish_Merchant_TestCertificate_1234679304.pfx -passin pass:swish &gt; all_the_certs.txt</code></p><p>Then, open the text file in a text editor, and locate <em>the two last certificates</em> (the first one is your actual client certificate, you can ignore that for now). Now, copy everything from and including <code>-----BEGIN CERTIFICATE-----</code> to (and still including) <code>-----END CERTIFICATE-----</code> and paste it to a new file called <code>Swedbank_Customer_CA1_v1_for_Swish_Test.cer</code>, which should then look like this:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">-----BEGIN CERTIFICATE-----</span><br><span class="line">MIIFvzCCA6egAwIBAgIQbPUmsaJntOLs9L2jgZxlQzANBgkqhkiG9w0BAQ0FADBq</span><br><span class="line">MSswKQYDVQQDDCJTd2VkYmFuayBSb290IENBIHYyIGZvciBTd2lzaCBUZXN0MREw</span><br><span class="line">DwYDVQQFEwhTV0VEU0VTUzEbMBkGA1UECgwSU3dlZGJhbmsgQUIgKHB1YmwpMQsw</span><br><span class="line">CQYDVQQGEwJTRTAeFw0xODAzMjAxMjU0MjJaFw0zODAzMjAxMjIyMTJaMGoxKzAp</span><br><span class="line">BgNVBAMMIlN3ZWRiYW5rIEN1c3RvbWVyIENBMSB2MSBmb3IgU3dpc2gxETAPBgNV</span><br><span class="line">BAUTCFNXRURTRVNTMRswGQYDVQQKDBJTd2VkYmFuayBBQiAocHVibCkxCzAJBgNV</span><br><span class="line">BAYTAlNFMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAl/wQeoczfPad</span><br><span class="line">DPNIYJvhvwavDxIxGyGnQ2WDsXO7LQH7hz/TWNx0Ava6jRYT5pUubpBsALk/xEuA</span><br><span class="line">uKjUEUAY5uumPn/nMNVeMmpnqMHs7A3kfkr8F+o4AUM3mIgVe3inMF/mvlASfpGp</span><br><span class="line">4TS0ZCLZLE+OiG5REecVmgjn0i/JorXtXMGSWoqbAZCpqgS9uD3MQb9ua7TRTvkQ</span><br><span class="line">knUH30/GaX5i8KK/r45SRXBRLTxxk0ySk4AcUR21TLb2WOMV9BbBwdq336mSErgz</span><br><span class="line">wMy6G5EGlNhety3g+QRoc1ou0+oMw9tLdgsrhIx9opHF/M+E8bXXje5WZ8d9eyF/</span><br><span class="line">Eq0kIqvWm4c3TzLPS43DNhJCOBGO6GMV8neuXM0TKiBrub3/yli5BapCH62SENEF</span><br><span class="line">ZJG/ZB9fnRupDsn5vlt3MwtbipjnNhH4umcKacF/YtBdCUh1+CQ3U6lx2OolCYZG</span><br><span class="line">cn1YDD7DNzb5I71TTaWyC+FgbXFtGX/48UXBIrqc9A054o76A4eYKM+GQPNz3B27</span><br><span class="line">vZwzRC7Xjryg4uimmYzNRdJ3jz1q9PllUsOq/ElNr0ALj3MLKqOOtItb91SyE+NP</span><br><span class="line">gKbf0MiTYS2k4aTxpDuh972cz1UPgHFuixat3Zj42ZVRLyX4IcaPk7Vbh3GjkO66</span><br><span class="line">1qvgR6xInJoUts8J9HHbXgtjllmsf2UCAwEAAaNhMF8wDwYDVR0TAQH/BAUwAwEB</span><br><span class="line">/zARBgNVHQ4ECgQITvODy60zFEAwFAYDVR0gBA0wCzAJBgcqhXCBbQEBMBMGA1Ud</span><br><span class="line">IwQMMAqACEemPD8kK8dIMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQ0FAAOC</span><br><span class="line">AgEAdVwklqAdT3Ztn4RgLcg+hPKtu5wVYVqOnilVqQHSWx/ygcgUMspvZ+N6qYIQ</span><br><span class="line">NgBXTKaIUX6vFK4wvTgEaZnBUkuyYR9p/ZrAz7AjfdeUi2RRt5OLFAWwdddUZHC7</span><br><span class="line">qvRG6bzreUZvgrZ6lh0ercl07b9xSr8pc1lNy3ksbkW2lvTxBeRQDKJJQ53EYA9I</span><br><span class="line">/fFJ7H6vsUhWdn4x3GRLuDoOp0x6BdKGuSR06p5kjgytPCXxJt1QCnCvCsWKUZfe</span><br><span class="line">4XmASXG/Kku7dih9uDl2CWAfCWwu6eUSZxRLxWM0Htb1semJRo0AMsRW4Xb9uWtw</span><br><span class="line">i3XKT9oZbRDjaOn5NPPfWfYufjzh6AodH6aftSWns7MKSvhif+2mBBcF8mVySD2u</span><br><span class="line">SQZUNJ9YLBk26ii0jQ1k6ll5fCWutcEkvvdswq6R9cDm04DYKJionClg2isREy9m</span><br><span class="line">9PGWUntX3qL17fRTAAvTP3I918QTnMDEjIq5PaOxAgcuhoepweVbFHmyWLO04xpf</span><br><span class="line">DkHghRnId3UV2XELlKGcmuobuBsPWeSMEGDg96z49owzDBm2W6PFw0t7wwrbQN6J</span><br><span class="line">VCSlxq1hRaehRdYfqIcnvCbRZtsR1p6oQMKUK17NUyyr7OLLm+BUBpi0g8fPuV5h</span><br><span class="line">g7XJuK5Z3/D+uKRYfpw4SnSyk3s4RfDg4M2rgE80lplYnE8=</span><br><span class="line">-----END CERTIFICATE-----</span><br></pre></td></tr></table></figure><p>Repeat for the second certificate, but call the file <code>Swedbank_Root_CA_v2_for_Swish_Test.cer</code>.</p><h2 id="Upload-the-certificates-to-Azure-and-configure-your-App-Service"><a href="#Upload-the-certificates-to-Azure-and-configure-your-App-Service" class="headerlink" title="Upload the certificates to Azure and configure your App Service"></a>Upload the certificates to Azure and configure your App Service</h2><p>Now you need to upload the certificates to you App Service in Azure. Log in to your Azure Portal and navigate to <em>TLS/SSL settings</em> in you App Service. Click on the <em>Private Key Certificates (.pfx)</em> tab, and then on <em>Upload Certificate</em>.</p><img src="/posts/Calling-the-Swish-Payment-API-from-Azure-AppService/upload_private_cert_to_azure.png" class="" title="In case you really can" alt="t understand the instructions, this is where you should click!"><p>Select the <code>Swish_Merchant_TestCertificate_1234679304.pfx</code> file<sup id="fnref:5"><a href="#fn:5" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="You renamed it, remember?">[5]</span></a></sup> and enter the password (which is just <em>swish</em> for the simulator certificate). Click <em>Upload</em> and you should see the certificate in the <em>Private Key Certificates</em> list. If you do this right now, you will also get a warning that the certificate is about to expire, which is true, but there is no newer certs yet, so we’ll just have to repeat this process in a month or two.</p><blockquote><p><em>Update 2020-05-15</em></p><p>New certificates for the Swish sandbox finally arrived (mss_test_1.8.1)! I have verified that this exact same procedure works for them as well. However, they are now issued by Nordea rather than Swedbank, so it would make sense to call the certicates something with Nordea instead! :-)</p><p>Make sure to change the thumbprints as well!</p></blockquote><p>Next, you need to upload the public CA certs that you extracted earlier. Click on the <em>Public Key Certificates (.cer)</em> tab, and then on <em>Upload Public Key Certificate</em>. Select the <code>Swedbank_Customer_CA1_v1_for_Swish_Test.cer</code> file, and give it a name, something like <code>Swedbank Customer CA1 v1 for Swish Test</code> maybe? Click <em>Upload</em> and to the same for the other certificate.</p><img src="/posts/Calling-the-Swish-Payment-API-from-Azure-AppService/public_certs_uploaded_to_azure.png" class=""><p>In order for you App Service to be able to access the certificates, you need to set the environment variable <code>WEBSITE_LOAD_CERTIFICATES</code>. The value should be either <code>*</code> if you want to load all certificates, or a comma separated list of thumbprints if you want to limit which certificates that are available to your application. So go to <em>Configuration</em> -&gt; <em>Application settings</em> and click <em>New application setting</em> to add the setting.</p><p><em>NOTE: If you have multiple slots in your App Service, you have to do this for all slots!</em></p><p>In order to make sure that the certificates are uploaded correctly, and available to your application, you can use the debug console. Go to <em>Advanced Tools</em> (under the Development Tools heading), ang click <em>Go</em> to open it in a new window. Select <em>Debug Console</em> -&gt; <em>PowerShell</em> in the top menu.</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">Kudu Remote Execution Console</span><br><span class="line">Type <span class="string">'exit'</span> then hit <span class="string">'enter'</span> to get a new powershell process.</span><br><span class="line">Type <span class="string">'cls'</span> to clear the console</span><br><span class="line"></span><br><span class="line">PS D:\home&gt; dir cert:/CurrentUser/my</span><br><span class="line">dir cert:/CurrentUser/my</span><br><span class="line"></span><br><span class="line">   PSParentPath: Microsoft.PowerShell.Security\Certificate::CurrentUser\my</span><br><span class="line"></span><br><span class="line">Thumbprint                                Subject</span><br><span class="line">----------                                -------</span><br><span class="line">B71A8C2A7E0535AB50E4D9C715219DEEEB066FA4  C=SE, O=Swedbank AB (publ), SERIAL...</span><br><span class="line"><span class="number">88884499</span>BFFEF7FA80AB2CE2336568AE49C6D313  C=SE, O=Swedbank AB (publ), SERIAL...</span><br><span class="line"><span class="number">76</span>B6E2CB1BBA1BBC8A0C276AEF882B16AC48E7E0  CN=<span class="number">1233782570</span>, O=<span class="number">5564010055</span>, C=SE</span><br></pre></td></tr></table></figure><p>You should be able to see all the certificates you have uploaded here. Take note of the thumbprints, you will need them shortly!</p><h2 id="Use-the-client-certificates-when-connecting-to-the-Swish-REST-API"><a href="#Use-the-client-certificates-when-connecting-to-the-Swish-REST-API" class="headerlink" title="Use the client certificates when connecting to the Swish REST API"></a>Use the client certificates when connecting to the Swish REST API</h2><p>These examples will use the <a href="https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=netcore-3.1" target="_blank" rel="noopener">HttpClient</a> in dotnet core 3.1, because that is what I’ve been using. What we need to do is create our <code>HttpClient</code> with a custom <code>HttpClientHandler</code> that includes all the client certificates. This also lets us handle the validation failure that will occur for the server certificate, since it is not signed by a trusted authority.</p><p>The first step is to load all the certificates. You will need to pass all the thumbprints, both for your private client certificate and for the public certs you extracted.</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> X509Certificate2Collection <span class="title">GetCertificates</span>(<span class="params"><span class="keyword">string</span>[] thumbprints</span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">var</span> certStore = <span class="keyword">new</span> X509Store(StoreName.My, <span class="string">"CurrentUser"</span>);</span><br><span class="line">    certStore.Open(OpenFlags.ReadOnly);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">var</span> certificates = <span class="keyword">new</span> X509Certificate2Collection();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">foreach</span>(<span class="keyword">var</span> thumbprint <span class="keyword">in</span> thumbprints) &#123;</span><br><span class="line">        <span class="keyword">var</span> certs = certStore.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, <span class="literal">false</span>);</span><br><span class="line">        certificates.AddRange(certs);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> certificates;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The next step is to create our HttpClient with the custom HttpClientHandler:</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> HttpClient <span class="title">CreateClient</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">var</span> handler = <span class="keyword">new</span> HttpClientHandler()</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">//The server certificate is not signed by a trusted authority,</span></span><br><span class="line">        <span class="comment">//so we need to override the validation</span></span><br><span class="line">        ServerCertificateCustomValidationCallback = (sender, certificate, chain, sslPolicyErrors) =&gt;</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="comment">//If you want to, you can check that the server certificate</span></span><br><span class="line">            <span class="comment">//thumbprint matches the expected. However, just recently they</span></span><br><span class="line">            <span class="comment">//changed the certificate without notifying anyone, so this</span></span><br><span class="line">            <span class="comment">//may cause problems. You could also just return true and be done with it.</span></span><br><span class="line">            <span class="keyword">var</span> match = certificate?.Thumbprint == serverCertThumbprint;</span><br><span class="line">            <span class="keyword">return</span> match;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    handler.ClientCertificateOptions = ClientCertificateOption.Manual;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">var</span> clientCerts = GetCertficates(<span class="comment">/* Pass your array of thumbprints here */</span>);</span><br><span class="line">    <span class="keyword">foreach</span> (<span class="keyword">var</span> cert <span class="keyword">in</span> clientCerts)</span><br><span class="line">    &#123;</span><br><span class="line">        handler.ClientCertificates.Add(cert);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> HttpClient(handler);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>And that should be it! Unless I forgot something. If you follow this guide, and still have problems, please comment below, and I will try to update the guide. I do have it working now, so I should be able to figure out the problem!</p><div id="footnotes"><hr><div id="footnotelist"><ol style="list-style: none; padding-left: 0; margin-left: 40px"><li id="fn:1"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">1.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">Yes, that is what we call googling now!<a href="#fnref:1" rev="footnote"> ↩</a></span></li><li id="fn:2"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">2.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">This will almost certainly be incorrect within minutes of publishing this, since they seem to change it between every time I look!<a href="#fnref:2" rev="footnote"> ↩</a></span></li><li id="fn:3"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">3.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">p12 and pfx are just two different extension for the same certificate format, PKCS12.<a href="#fnref:3" rev="footnote"> ↩</a></span></li><li id="fn:4"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">4.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">The password for the simulator certificates really is just <em>swish</em>. For production certs, you will of course need to use the real password.<a href="#fnref:4" rev="footnote"> ↩</a></span></li><li id="fn:5"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">5.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">You renamed it, remember?<a href="#fnref:5" rev="footnote"> ↩</a></span></li></ol></div></div>]]></content:encoded>
      
      
      
      <category domain="https://johan.driessen.se/tags/dotnet-core/">dotnet core</category>
      
      <category domain="https://johan.driessen.se/tags/azure/">azure</category>
      
      <category domain="https://johan.driessen.se/tags/swish/">swish</category>
      
      
      <comments>https://johan.driessen.se/posts/Calling-the-Swish-Payment-API-from-Azure-AppService/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Setting up a Compact Flash card with Classic Workbench and WHDLoad for Amiga 600/1200</title>
      <link>https://johan.driessen.se/posts/Setting-up-a-Compact-Flash-card-with-Classic-Workbench-and-WHDLoad-for-Amiga-600-1200/</link>
      <guid>https://johan.driessen.se/posts/Setting-up-a-Compact-Flash-card-with-Classic-Workbench-and-WHDLoad-for-Amiga-600-1200/</guid>
      <pubDate>Thu, 05 Mar 2020 22:37:35 GMT</pubDate>
      
        
        
      <description>&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;p&gt;About a year ago, I bought an Amiga 60</description>
        
      
      
      
      <content:encoded><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>About a year ago, I bought an Amiga 600. It did not have a hard drive, but all Amiga 600 have an IDE port, and you can use a Compact Flash card with a CF-IDE adapter as a hard drive. That worked fine, and I could install Workbench on the CF card, and use it. But eventually I though I should try to use WHDLoad, so that I could run more games directly from the hard drive. I found a <a href="https://youtu.be/1k1328geyI8">video by Nostalgia Nerd on Youtube</a>, where he goes through the process of installing <a href="http://classicwb.abime.net/">Classic Workbench</a> and <a href="http://www.whdload.de/">WHDLoad</a> on a Compact Flash card, unfortunately this video is (currently) four years old, and also very… quick? With the help of the comments<sup id="fnref:1"><a href="#fn:1" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="Youtube comments are more useful than their reputation would have you believe!">[1]</span></a></sup> and some trial and error, I managed to get it working, though.</p><p>Now, recently I was generously given an Amiga 1200. This one actually had a hard drive, but who knows how long that will keep on working, so I though I’d replace it with a Compact Flash card as well. This time, however, for the benefits of my readers<sup id="fnref:2"><a href="#fn:2" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="That is, me, a few months from now.">[2]</span></a></sup> I thought I’d write down the process in a blog post. I thought it might be useful because a) I prefer written instructions, and b) it would give me a chance to update and correct the instructions so that they actually work.</p><p>The original video can be found <a href="https://youtu.be/1k1328geyI8">here</a>, along with <a href="http://www.nostalgianerd.com/whdload">instructions and download links</a>.</p><p>I won’t go into the hardware side of this, because there’s really nothing to it. But you will need a Compact Flash card with a capacity of at least 4GB, some kind of <a href="https://www.ebay.com/itm/Brand-New-CF-IDE-44-PIN-Adapter-2-5-IDE-Lead-for-Amiga-600-Amiga-1200/163092251498">CF-IDE adapter</a> (not neccessarily that one) and of course a Compact Flash reader for your computer. This tutorial also assumes that you are running Windows, although it should be possible to use pretty much the same procedure with <a href="https://fs-uae.net/">FS-UAE</a> on Linux or Mac.</p><h2 id="Step-1-Download-stuff"><a href="#Step-1-Download-stuff" class="headerlink" title="Step 1 - Download stuff"></a>Step 1 - Download stuff</h2><p>First of all, you will need to download a bunch of software.</p><h3 id="WinUAE-Amiga-emulator"><a href="#WinUAE-Amiga-emulator" class="headerlink" title="WinUAE - Amiga emulator"></a>WinUAE - Amiga emulator</h3><p>This tutorial uses the WinUAE amiga emulator, which can be found on <a href="http://www.winuae.net/">winuae.net</a>. I was using version 4.0.1, although I now see that version 4.3.0 is available. From what I can tell, the difference seems to be very minor, so it shouldn’t matter.</p><h3 id="Kickstart-Workbench"><a href="#Kickstart-Workbench" class="headerlink" title="Kickstart / Workbench"></a>Kickstart / Workbench</h3><p>You will need a copy of the Kickstart ROM and Workbench disk images, version 3.0 or 3.1. This is still under copyright, and at least the Workbench images can be bought from <a href="https://www.amigaforever.com/media/">amigaforever.com</a>. They can also be found on several places on the Internet, as usual <a href="https://duckduckgo.com/">DuckDuckGo</a> is your friend.</p><h3 id="PFS3-File-System-support"><a href="#PFS3-File-System-support" class="headerlink" title="PFS3 File System support"></a>PFS3 File System support</h3><p>You probably want to use the <a href="https://en.wikipedia.org/wiki/Professional_File_System">PFS3</a> file system, and you will need to download the handler for that from <a href="http://aminet.net/package/disk/misc/pfs3aio">http://aminet.net/package/disk/misc/pfs3aio</a>. This is not strictly necessary, but it’s faster than the standard AFFS and supports larger partitions.</p><h3 id="Classic-WB"><a href="#Classic-WB" class="headerlink" title="Classic WB"></a>Classic WB</h3><p>Hard disk images for Classic WB can be found at <a href="http://classicwb.abime.net/">classicwb.abime.net</a>. I used the <strong>LITE</strong> version for the Amiga 1200, but for an Amiga 600 you probably want the <strong>68K</strong> version.</p><p>You will also need kickstart files for WHDLoad. These can be found for example at (paradise.untergrund.net)[<a href="https://paradise.untergrund.net/tmp/PREMIUM/amiga_tools/]">https://paradise.untergrund.net/tmp/PREMIUM/amiga_tools/]</a>, it’s the <code>kickstarts.lha</code> file you’re looking for.</p><h3 id="Game-and-Demo-packs"><a href="#Game-and-Demo-packs" class="headerlink" title="Game and Demo packs"></a>Game and Demo packs</h3><p>The original tutorial suggested that game packs could be downloaded from <a href="http://kg.whdownload.com/kgwhd/">http://kg.whdownload.com/kgwhd/</a>, but that doesn’t seem to work anymore. I downloaded both games and demo packs from <a href="ftp://grandis.nu/Commodore_Amiga/Retroplay/">ftp://grandis.nu/Commodore_Amiga/Retroplay/</a>, but they are available from many more places, just search for <code>whdload games pack</code>.</p><p>Put everything in a folder somewhere on your PC.</p><h2 id="Step-2-Clean-the-CF-card"><a href="#Step-2-Clean-the-CF-card" class="headerlink" title="Step 2 - Clean the CF card"></a>Step 2 - Clean the CF card</h2><p>In order to use the Compact Flash card in an Amiga, you need to remove all previous file system information from it. In order to do this, run <code>diskpart</code> in an Administrative command prompt.</p><p>First, list your disk by entering <code>list disk</code>. This should give you a result something like this:</p><img src="/posts/Setting-up-a-Compact-Flash-card-with-Classic-Workbench-and-WHDLoad-for-Amiga-600-1200/diskpart-list-disk.png" class="" title="Yes, I have many drives in my computer."><p>Next, select your compact flash card, in my case it’s disk 6: <code>select disk 6</code>. <em>Be very, very sure that you select the correct disk. You will destroy everything on it.</em></p><img src="/posts/Setting-up-a-Compact-Flash-card-with-Classic-Workbench-and-WHDLoad-for-Amiga-600-1200/diskpart-select-disk.png" class="" title="Once, I did this on the wrong disk. Took me days to recover the data!"><p>If you want to be completely sure that you have selected the correct disk, you can run <code>detail disk</code> just to verify. When you are 100% sure, run <code>clean</code>. This completely wipes the file system information from the disk, making it ready to use in the Amiga. <code>exit</code> diskpart.</p><h2 id="Step-3-Configure-virtual-Amiga-in-WinUAE"><a href="#Step-3-Configure-virtual-Amiga-in-WinUAE" class="headerlink" title="Step 3 - Configure virtual Amiga in WinUAE"></a>Step 3 - Configure virtual Amiga in WinUAE</h2><p>Start WinUAE as Administrator. Now we need to configure the system, and make it a little faster than a real Amiga. Otherwise this process will take literally<sup id="fnref:3"><a href="#fn:3" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="figuratively">[3]</span></a></sup> forever.</p><h3 id="CPU-and-FPU"><a href="#CPU-and-FPU" class="headerlink" title="CPU and FPU"></a>CPU and FPU</h3><ul><li>CPU: 68040 (or maybe 68060) / JIT</li><li>FPU: CPU Internal / More Compatible</li><li>CPU Emulation speed: Fastest possible</li></ul><img src="/posts/Setting-up-a-Compact-Flash-card-with-Classic-Workbench-and-WHDLoad-for-Amiga-600-1200/winuae-cpu-and-fpu.png" class=""><h3 id="Chipset"><a href="#Chipset" class="headerlink" title="Chipset"></a>Chipset</h3><p>Uncheck cycle exact, otherwise leave the default values.</p><img src="/posts/Setting-up-a-Compact-Flash-card-with-Classic-Workbench-and-WHDLoad-for-Amiga-600-1200/winuae-chipset.png" class=""><h3 id="RAM"><a href="#RAM" class="headerlink" title="RAM"></a>RAM</h3><p>Add some Z3 Fast RAM, I used 64 MB, just as in the original video.</p><img src="/posts/Setting-up-a-Compact-Flash-card-with-Classic-Workbench-and-WHDLoad-for-Amiga-600-1200/winuae-ram.png" class=""><h3 id="ROM"><a href="#ROM" class="headerlink" title="ROM"></a>ROM</h3><p>Use the appropriate Kickstart ROM (probably the same as in your amiga). It needs to be version 3.0 or 3.1.</p><h3 id="Floppy-drives"><a href="#Floppy-drives" class="headerlink" title="Floppy drives"></a>Floppy drives</h3><p>Add the Workbench installer disk image to DF0:. It should <em>not</em> be write protected (use a copy if you don’t want to risk modifying your original image).</p><h3 id="Hard-drive-Compact-Flash-really"><a href="#Hard-drive-Compact-Flash-really" class="headerlink" title="Hard drive (Compact Flash, really)"></a>Hard drive (Compact Flash, really)</h3><p>Now you need to add your compact flash card as a file system, as well as the pfs3 handler. Enter the CD &amp; Hard drives section.</p><h4 id="Add-the-pfs3aio-archive-as-device-f"><a href="#Add-the-pfs3aio-archive-as-device-f" class="headerlink" title="Add the pfs3aio archive as device f"></a>Add the pfs3aio archive as device f</h4><p>If you want to use the PFS3 file system (which is recommended), you need to mount the archive with the handler as a file system as well:</p><ul><li>Click “Add Directory or Archive”</li><li>Select “Archive or plain file”</li><li>Select pfs3aio.lha</li><li>Device name: f</li></ul><img src="/posts/Setting-up-a-Compact-Flash-card-with-Classic-Workbench-and-WHDLoad-for-Amiga-600-1200/winuae-add-pfs3aio-device.png" class="" title="It does not really need to be bootable."><h4 id="Add-the-Compact-Flash-card"><a href="#Add-the-Compact-Flash-card" class="headerlink" title="Add the Compact Flash card"></a>Add the Compact Flash card</h4><p>The next step is to add your Compact Flash card, and this is why you need to run WinUAE as administrator, otherwise it won’t work.</p><ul><li>Click “Add Hard Drive”</li><li>Select the Compact Flash card as hard drive (it might be called something completely different on your machine)</li><li>Change from UEA to IDE (Auto)</li><li>Make sure Read/Write is checked</li><li>Click “Add hard drive”</li></ul><img src="/posts/Setting-up-a-Compact-Flash-card-with-Classic-Workbench-and-WHDLoad-for-Amiga-600-1200/winuae-add-cf-as-hdd.png" class=""><p>Start the virtual machine!</p><h2 id="Step-3-Partition-Compact-Flash-card"><a href="#Step-3-Partition-Compact-Flash-card" class="headerlink" title="Step 3 - Partition Compact Flash card"></a>Step 3 - Partition Compact Flash card</h2><p>Now we need to partition and format the Compact Flash card for use in an amiga.</p><img src="/posts/Setting-up-a-Compact-Flash-card-with-Classic-Workbench-and-WHDLoad-for-Amiga-600-1200/winuae-start-hdtoolbox.png" class="" title="This is what it should look like when you start your virtual amiga"><ul><li>Open the Install disk and the HDTools drawer.</li><li>Start HDToolbox, you should see Interface SCSI, Address 0, LUN 0, Status Unknown</li></ul><img src="/posts/Setting-up-a-Compact-Flash-card-with-Classic-Workbench-and-WHDLoad-for-Amiga-600-1200/hdtoolbox-1.png" class="" title="This should be how HDToolbox looks when you start it with your CF card mounted"><ul><li>Click “Change drive type” -&gt; “Define new” -&gt; “Read configuration” -&gt; “Continue” to configure the CF drive (ignore the values read, the Amiga does not really understand 4 GB Drive)</li><li>Click OK and go back to the list of hard drives in the system.</li><li>Click “Partition Drive”</li><li>Set up a small(ish) system partition, like 250 MB. Change the name to DH0.</li><li>Set up the rest of the CF Card as a partition, name it DH1.</li></ul><img src="/posts/Setting-up-a-Compact-Flash-card-with-Classic-Workbench-and-WHDLoad-for-Amiga-600-1200/hdtoolbox-2-partitions.png" class="" title="Example of partitioning of the CF card"><h3 id="Optional-Use-the-pfs3-file-system"><a href="#Optional-Use-the-pfs3-file-system" class="headerlink" title="Optional: Use the pfs3 file system"></a>Optional: Use the pfs3 file system</h3><ul><li>Check Advanced options and then click “Add/Update”</li><li>Click Add New File System</li><li>Enter filename <code>f:pfs3aio</code> (<strong>NOT</strong> pfs3_aio-handler as is claimed in the video, that is no longer correct) and click OK<img src="/posts/Setting-up-a-Compact-Flash-card-with-Classic-Workbench-and-WHDLoad-for-Amiga-600-1200/hdtoolbox-3-pfs3aio.png" class=""></li><li>Change DosType to <code>0x50465303</code> and remember to press Enter in the field<img src="/posts/Setting-up-a-Compact-Flash-card-with-Classic-Workbench-and-WHDLoad-for-Amiga-600-1200/hdtoolbox-4-pfs3aio-dostype.png" class=""></li><li>Click OK and OK to get back to your partitions</li><li>Select DH0, and click “Change” to change to the new file system</li><li>Select Custom File System or PFS/03 (depending on your Workbench version, I think)</li><li>Make sure Identifier says <code>0x50465303</code> (otherwise change it)</li><li>Change MaxTransfer to <code>0x1fe00</code> (and press enter)<img src="/posts/Setting-up-a-Compact-Flash-card-with-Classic-Workbench-and-WHDLoad-for-Amiga-600-1200/hdtoolbox-5-change-file-system.png" class=""></li><li>Click OK</li><li>Repeat for DH1 (you don’t have to add the PFS3 file system again)</li></ul><p>Now we’re done with the partitioning. Click OK, and then “Save changes to drive” (if you get an error here, you may want to try another Card Reader). Exit HDToolbox and reset the virtual Amiga.</p><h2 id="Step-3-Install-Classic-WB"><a href="#Step-3-Install-Classic-WB" class="headerlink" title="Step 3 - Install Classic WB"></a>Step 3 - Install Classic WB</h2><p>Alright, if you’re still with me, it’s finally time to install Classic Workbench!</p><p>First, format the partitions by right clicking on them and select Icons -&gt; Format disk from the menu. Name DH0 System and DH1 whatever you want (I just named mine <em>Stuff</em>). Make sure to use Quick Format. Confirm all warnings.</p><p>Then, press F12 to enter the WinUAE settings and go to CD &amp; Hard Drives. Now you need to add the <code>System.hdf</code> file that you extracted from the Classic WB archive you downloaded in Step 1. Click Add Hardfile and select the <code>System.hdf</code> file. Make sure that the HD Controller is UAE, and name the device <code>DH2</code>. <em>You should set boot prio to 1</em> (not 0).</p><img src="/posts/Setting-up-a-Compact-Flash-card-with-Classic-Workbench-and-WHDLoad-for-Amiga-600-1200/winuae-classicwb-hardfile-settings.png" class="" title="Gettings this to work correctly took me some time!"><p>You can remove the pfs3aio device, and then go to Floppy drives and eject all floppy drives. Restart the virtual machine.</p><p>It should now boot into the Classic WB installer. Follow the instructions (there are many, many options, and I have no good advice to give about them), and when prompted to insert a Workbench disk, press F12 to enter settings and do that. This is your change to choose between Workbench 3.0 and 3.1.</p><img src="/posts/Setting-up-a-Compact-Flash-card-with-Classic-Workbench-and-WHDLoad-for-Amiga-600-1200/classicwb.png" class="" title="This is how my workbench ended up, yours might differ."><p>After the installation is done, and you have restarted, you probably will not see you compact flash partitions. This is because the Amiga gets confused by the two <em>System</em> partions. Rename the Classic WB partition to <code>System2</code> (or something other than just System) and restart the virtual machine. You should now see all partitions.</p><p>Now you need to copy all the System/Workbench files from the System.hbf image to the System partition on the Compact Flash card. Start DOPUS by clicking RUN and selecting DOPUS. Select <code>DH2:</code> on the left (if DH2 does not appear in the list, you may have to type it in), and <code>DH0:</code> on the right. Select DH2 and click “All” to select all files, and then “Copy” to copy everything to the CF card. This will take a while.</p><img src="/posts/Setting-up-a-Compact-Flash-card-with-Classic-Workbench-and-WHDLoad-for-Amiga-600-1200/dopus-copy-system.png" class="" title="DOPUS really has a fantastic color scheme. Looks very professional!"><p>After the copying is done, press F12 again to go into settings, and remove the <code>System.hbf</code> image from the hard disks. You should now only have your Compact Flash card left. Reset the virtual machine, and you should hopefully boot back into Classic Workbench.</p><p>Congratulations, you now have a working Compact Flash card for use in your Amiga. At this point, you could install it in the Amiga, start it, and everything should work. However, the point of Amiga is playing games, so we have one step left!</p><h2 id="Step-4-Copy-Games-and-Demos-for-WHDLoad"><a href="#Step-4-Copy-Games-and-Demos-for-WHDLoad" class="headerlink" title="Step 4 - Copy Games and Demos for WHDLoad"></a>Step 4 - Copy Games and Demos for WHDLoad</h2><p>First, we need to mount the folder where you put your games, demos and kickstarts as a file system in the virtual amiga.</p><ul><li>Go into WinUAE settings -&gt; CD &amp; Hard Drives and click “Add Directory or Archive”.</li><li>Click “Select Directory” and point to where your Games and Demos are.</li><li>Put <code>PC</code> as both Device name and Volume label. Uncheck bootable. Click OK, and reset the machine.<img src="/posts/Setting-up-a-Compact-Flash-card-with-Classic-Workbench-and-WHDLoad-for-Amiga-600-1200/winuae-add-pc-device.png" class="" title="Fun fact: the path that I used in this image is fake, on my actual computer the path to where I put the files is completely different!"></li><li>You should now see a drive called PC on your workbench.</li></ul><p>Second, we need to copy all the kickstart files. WHDLoad uses these to emulate<sup id="fnref:4"><a href="#fn:4" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="It might not technically be emulation, but I have a very rudimentary idea of how WHDLoad works...">[4]</span></a></sup> the correct environment for the games and applications.</p><ul><li>If you haven’t done so already, unpack the kickstarts.lha archive into a folder.</li><li>Open DOPUS again, and select PC for the left side, and navigate into where you unpacked your kickstarts.</li><li>Copy all the kickstarts file to <code>DH0:Devs/Kickstarts</code>. Overwrite any files already there.</li></ul><img src="/posts/Setting-up-a-Compact-Flash-card-with-Classic-Workbench-and-WHDLoad-for-Amiga-600-1200/dopus-copy-kickstarts.png" class="" title="That sure is a lot of kickstarts..."><p>The Games and Demos need to be unpack into individual folders grouped by initial. For example <code>Games/A/AnotherWorld_v2.4_0425</code>. For games beginning with a number, the folder should be called <code>0_9</code>. This can be done on the PC, or you can unpack them using DOPUS (as long as you have grouped them by initial).</p><p>Depending on the size of your CF card, all games might not fit, or if you just don’t want that many, you can just select the ones you like. I think it’s fine to group them into fewer folders then, e.g. <code>A_E</code>, <code>F_K</code> et cetera. At least the demos I downloaded were grouped like that, and it seems to work fine.</p><p>Now, use DOPUS again to copy the files from PC to DH1. If you did not unpack the archives earlier you can use <code>Arc Ext</code> to extract all the archives, buy you will have to do it folder by folder. I copied them to <code>DH1:Games</code>and <code>DH1:Demos</code>, but you can organise your files however you want.</p><p>Go back into settings, and remove all file systems except for the Compact Flash card. Reset the system, and it should boot back into Classic WB on your Compact Flash card.</p><p>Time to configure the system so that WHDLoad can find your games and demos!</p><ul><li>Right click the top bar and select Workbench -&gt; Startup from the drop down menu. Click Assign.</li><li>Change the locations for Games (and Demos) to where you put them. In my case, change the line that reads <code>Assign &gt;NIL: A-Games: SYS:Games</code> to <code>Assign &gt;NIL: A-Games: DH1:Games</code> (and likewise for demos).</li><li>Click the close icon in the top left corner and then click Save. Reset the machine again.</li></ul><img src="/posts/Setting-up-a-Compact-Flash-card-with-Classic-Workbench-and-WHDLoad-for-Amiga-600-1200/classicwb-assign.png" class="" title="This is how my assignments look, of course it depends on how you organised your stuff}"><p>Finally, we need to add the games (and demos) to WHDLoad. Double click on the Files drawer in the bottom, and select AddGames. This may take some time. Do the same for AddDemos.</p><p>Now you can verify that the games are available. Right click on the desktop (of the Amiga!) and select RUN -&gt; Games. This should bring up the GamesMenu where you now should see a long list of games.</p><img src="/posts/Setting-up-a-Compact-Flash-card-with-Classic-Workbench-and-WHDLoad-for-Amiga-600-1200/whdload-gamesmenu.png" class="" title="This list of games goes on forever!"><h2 id="Step-5-Hardware-install"><a href="#Step-5-Hardware-install" class="headerlink" title="Step 5 - Hardware install"></a>Step 5 - Hardware install</h2><p>There is not really much to this, and the video explains it pretty good. Use a CF-IDE adapter of some kind, and connect it to the IDE port of the Amiga. That’s it.</p><p><em>UPDATE:</em> When I tried to put the CF card in my Amiga 1200, it didn’t recognize it, even though it had worked in my Amiga 600. I thought I had the same CF-IDE adapter, but on closer inspection it turned out they were not exactly the same. They both say <strong>CF-IDE44/2.0 ADAPTER</strong>, but the one that works has version <a href="https://www.amazon.com/ASHATA-2-5-inch-Compact-Adapter-Support/dp/B07NS9JD6G"><strong>V.H2</strong></a>, while the other one has version <a href="https://www.amazon.com/SinLoon-Adapter-Memory-2-5-inch-Laptop/dp/B07Y2N2SVD"><strong>V.B1</strong></a>. And it seems that other people have had the same issue with the V.B1. So if you use this kind of CF-IDE adapter, make sure it says V.H2 and <em>NOT</em> V.B1!</p><p>Start the Amiga (the real one), and it should boot to your Compact Flash card. Bring up the RUN -&gt; Games menu, and double click a game to start it!</p><div id="footnotes"><hr><div id="footnotelist"><ol style="list-style: none; padding-left: 0; margin-left: 40px"><li id="fn:1"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">1.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">Youtube comments are more useful than their reputation would have you believe!<a href="#fnref:1" rev="footnote"> ↩</a></span></li><li id="fn:2"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">2.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">That is, me, a few months from now.<a href="#fnref:2" rev="footnote"> ↩</a></span></li><li id="fn:3"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">3.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">figuratively<a href="#fnref:3" rev="footnote"> ↩</a></span></li><li id="fn:4"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">4.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">It might not technically be emulation, but I have a very rudimentary idea of how WHDLoad works...<a href="#fnref:4" rev="footnote"> ↩</a></span></li></ol></div></div>]]></content:encoded>
      
      
      <category domain="https://johan.driessen.se/categories/retro-computing/">retro computing</category>
      
      
      <category domain="https://johan.driessen.se/tags/retro-computing/">retro computing</category>
      
      
      <comments>https://johan.driessen.se/posts/Setting-up-a-Compact-Flash-card-with-Classic-Workbench-and-WHDLoad-for-Amiga-600-1200/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Hello darkness, my old friend</title>
      <link>https://johan.driessen.se/posts/Hello-darkness-my-old-friend/</link>
      <guid>https://johan.driessen.se/posts/Hello-darkness-my-old-friend/</guid>
      <pubDate>Sun, 29 Sep 2019 09:30:17 GMT</pubDate>
      
        
        
      <description>&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;p&gt;If you are reading this on a computer&lt;</description>
        
      
      
      
      <content:encoded><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>If you are reading this on a computer<sup id="fnref:1"><a href="#fn:1" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="A smartphone is a computer.">[1]</span></a></sup> with a dark system theme, you might notice that this blog now also has a dark theme. Although dark themes to be all the craze nowadays, I’ve been using dark themes for quite some time, and I’ve been wanting to implement a dark theme option for my blog since forever. But I could never decide on wether it was to be something that would change automatically according to the time of day, or if the sun was up or not, or something the visitor could toggle.</p><p>Well, as it turns out, while I have been procasticating, the browser vendors have solved the problem for me! Earlier this year a new CSS media query was introduced: <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme">prefers-color-scheme</a>.</p><p>This little gem equals <code>dark</code> if the system has a dark color scheme, and <code>light</code> otherwise. And it is supported by the latest versions of Firefox, Chrome, Safari and even Edge<sup id="fnref:2"><a href="#fn:2" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="According to [caniuse.com](https://caniuse.com/#feat=prefers-color-scheme)">[2]</span></a></sup>. It works something like this:</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* Default color scheme */</span></span><br><span class="line"><span class="selector-tag">body</span> &#123;</span><br><span class="line">    <span class="attribute">background-color</span>: <span class="number">#fff</span>;</span><br><span class="line">    <span class="attribute">color</span>: <span class="number">#000</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* Color scheme for dark mode */</span></span><br><span class="line"><span class="keyword">@media</span> (<span class="attribute">prefers-color-scheme:</span> dark) &#123;</span><br><span class="line">    <span class="selector-tag">body</span> &#123;</span><br><span class="line">        <span class="attribute">background-color</span>: <span class="number">#000</span>;</span><br><span class="line">        <span class="attribute">color</span>: <span class="number">#555</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>If the browser does not support prefers-color-scheme, or if it has a preferred color scheme other than “dark” (i.e. light), it will just ignore the overrides in the media query. So this is basically all I needed to do (well, I had to make a <a href="https://github.com/nahojd/supreme-invention/commit/c82cda61f2b9d3421a5f99977db02e6440e0cb49:wq">few more changes</a>) to make the theme of the site follow the system theme. Sweet!</p><div id="footnotes"><hr><div id="footnotelist"><ol style="list-style: none; padding-left: 0; margin-left: 40px"><li id="fn:1"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">1.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">A smartphone is a computer.<a href="#fnref:1" rev="footnote"> ↩</a></span></li><li id="fn:2"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">2.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">According to <a href="https://caniuse.com/#feat=prefers-color-scheme">caniuse.com</a><a href="#fnref:2" rev="footnote"> ↩</a></span></li></ol></div></div>]]></content:encoded>
      
      
      
      <category domain="https://johan.driessen.se/tags/css/">css</category>
      
      <category domain="https://johan.driessen.se/tags/meta/">meta</category>
      
      
      <comments>https://johan.driessen.se/posts/Hello-darkness-my-old-friend/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Some Problems and Solutions When Creating Xamaring Android Bindings</title>
      <link>https://johan.driessen.se/posts/Some-Problems-and-Solutions-When-Creating-Xamaring-Android-Bindings/</link>
      <guid>https://johan.driessen.se/posts/Some-Problems-and-Solutions-When-Creating-Xamaring-Android-Bindings/</guid>
      <pubDate>Thu, 05 Sep 2019 07:34:07 GMT</pubDate>
      
        
        
      <description>&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;p&gt;As announced in &lt;a href=&quot;/posts/Announ</description>
        
      
      
      
      <content:encoded><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>As announced in <a href="/posts/Announcing-Xamarin-Android-Bindings-for-Adyen-Checkout/" title="my last post">my last post</a>, we recently created Xamarin Bindings for the Adyen Android SDK. In this post, I thought I would share som experiences in creating those bindings, like what I kind of problems we ran into, and how we fixed them.</p><p>The process of creating Xamaring bindings can be a bit tricky. The process is documented at <a href="https://docs.microsoft.com/en-us/xamarin/android/platform/binding-java-library/" target="_blank" rel="noopener">docs.microsoft.com</a>, but I struggled quite a while in getting it to work.</p><p>First of all, you need the actual Android libraries that you want to create the bindings for. These are (at least in this case) available at <a href="https://jcenter.bintray.com/com/adyen/checkout" target="_blank" rel="noopener">jcenter</a>. Then you need to figure out exactly which libraries you need. In order to do this you can look in the <code>*.pom</code> file for a specific library to find out what other libraries it depends on.</p><p>Adyen advocates the use of their <a href="https://docs.adyen.com/checkout/android/drop-in" target="_blank" rel="noopener">Drop-in solution</a> which includes all supported payment types, but this also means that we would have to create bindings for all those libraries. This would amount to about <strong>25</strong> different libraries! However, many of the payment types supported were not interesting to us, at least not right now. So instead we opted to use only the Card Component and the Redirect Component, which would only require us to create bindings for 7 libraries<sup id="fnref:1"><a href="#fn:1" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="Actually, I finished the first version of these bindings in June. Unfortunately, just as I though I was done, I noticed that the Adyen developer documentation] had changed substantially. While I was working on this they had release an RC version of version 3.0, which was totally different from version 2.4.5 that I had been working on. So I basically had to start all over again and create new bindings for v3. The old bindings are available at github (tag: 2.4.5), and also at NuGet (Approach.Adyen.UI.Droid), should anyone be interested. But it's probably better to use the new ones.">[1]</span></a></sup>.</p><p>There are a couple of different ways to create bindings, but as Adyen provides <code>AAR</code> files, I basically followed the steps on the <a href="https://docs.microsoft.com/en-us/xamarin/android/platform/binding-java-library/binding-an-aar" target="_blank" rel="noopener">Binding an .AAR page</a>. This means creating a separate Xamarin Bindings Library for each AAR file, and the easiest way is to start at the “bottom”, and create a binding for the library that does not have any other java dependencies, in this case <a href="https://github.com/apprch/adyen-xamarin-android-binding/tree/master/AdyenCse" target="_blank" rel="noopener">adyen-cse</a> and work you way up, adding references to the other bindings as you go along. The Android dependencies in the POM files can simply be added as NuGet package references. Then you compile the project.</p><h2 id="It-won’t-compile"><a href="#It-won’t-compile" class="headerlink" title="It won’t compile!"></a>It won’t compile!</h2><p>Right. Most of the time, when you create a binding, add the AAR file and try to compile, it won’t work the first time. This could be due to a number of problems, but in this project, I’ve mainly had a handful of problems, which I’ll elaborate further below.</p><h3 id="Problem-1-Wrong-return-type"><a href="#Problem-1-Wrong-return-type" class="headerlink" title="Problem 1 - Wrong return type"></a>Problem 1 - Wrong return type</h3><p>Sometimes the generated code will have the wrong return type. This is often because of the difference between how interfaces and generics work in Java and C#.</p><p>For example, in the <a href="https://github.com/Adyen/adyen-android/blob/master/base-v3/src/main/java/com/adyen/checkout/base/api/LogoConnection.java" target="_blank" rel="noopener">original code for LogoConnection in base-v3</a>, the <code>call()</code> method returns a <code>BitmapDrawable</code>, which is ok, since the class implements the interface <code>java.util.concurrent.Callable&lt;T&gt;</code>, which is a generic interface, so you can have call() return a specific type.</p><p>In Xamarin, however, the interface <code>java.util.concurrent.Callable</code> is not generic (I don’t know why), and thus <code>LogoConnection.Call()</code> must have a return type of <code>Java.Lang.Object</code>. In the generated code, however, the return type is still BitmapDrawable. Fortunately, this is an easy fix!</p><p>Every generated method and class has method/class reference as a comment above it. This can be used to modify the generated code in the <code>Metadata.xml</code> file. On of the modifications that can be made is to change the return type. The following node changes the return type of the call method to <code>Java.Lang.Object</code>:</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">attr</span> <span class="attr">path</span>=<span class="string">"/api/package[@name='com.adyen.checkout.base.api']/class[@name='LogoConnection']/method[@name='call' and count(parameter)=0]"</span> <span class="attr">name</span>=<span class="string">"managedReturn"</span>&gt;</span>Java.Lang.Object<span class="tag">&lt;/<span class="name">attr</span>&gt;</span></span><br></pre></td></tr></table></figure><p>The path is just copied from the comment above the method in the generated code, but it is pretty straight forward anyway.</p><h3 id="Problem-2-Wrong-parameters"><a href="#Problem-2-Wrong-parameters" class="headerlink" title="Problem 2 - Wrong parameters"></a>Problem 2 - Wrong parameters</h3><p>Another problem that can occur, and that is related to the previous one is that sometimes generated methods have the wrong parameter types. This is not quite as easily fixed, as I have not found a way to modify the parameters of a method solely by a Metadata.xml node.</p><p>Example: In <a href="https://github.com/Adyen/adyen-android/blob/master/base-ui/src/main/java/com/adyen/checkout/base/ui/adapter/ClickableListRecyclerAdapter.java" target="_blank" rel="noopener">com.adyen.checkout.base.ui.adapter.ClickableListRecyclerAdapter</a>, the <code>onBindViewHolder</code> method takes a generic <code>ViewHolderT</code> as the first parameter. But in the generated code, <code>ClickableListRecyclerAdapter</code> is no longer generic, so <code>OnBindViewHolder</code> instead takes a <code>Java.Lang.Object</code>, as can be seen in the snippet below:</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Metadata.xml XPath method reference: path="/api/package[@name='com.adyen.checkout.base.ui.adapter']/class[@name='ClickableListRecyclerAdapter']/method[@name='onBindViewHolder' and count(parameter)=2 and parameter[1][@type='ViewHolderT'] and parameter[2][@type='int']]"</span></span><br><span class="line">[<span class="meta">Register (<span class="meta-string">"onBindViewHolder"</span>, <span class="meta-string">"(Landroid/support/v7/widget/RecyclerView$ViewHolder;I)V"</span>, <span class="meta-string">"GetOnBindViewHolder_Landroid_support_v7_widget_RecyclerView_ViewHolder_IHandler"</span>)</span>]</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">unsafe</span> <span class="keyword">void</span> <span class="title">OnBindViewHolder</span> (<span class="params"><span class="keyword">global</span>::Java.Lang.Object viewHolderT, <span class="keyword">int</span> position</span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">string</span> __id = <span class="string">"onBindViewHolder.(Landroid/support/v7/widget/RecyclerView$ViewHolder;I)V"</span>;</span><br><span class="line">    IntPtr native_viewHolderT = JNIEnv.ToLocalJniHandle (viewHolderT);</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        JniArgumentValue* __args = <span class="keyword">stackalloc</span> JniArgumentValue [<span class="number">2</span>];</span><br><span class="line">        __args [<span class="number">0</span>] = <span class="keyword">new</span> JniArgumentValue (native_viewHolderT);</span><br><span class="line">        __args [<span class="number">1</span>] = <span class="keyword">new</span> JniArgumentValue (position);</span><br><span class="line">        _members.InstanceMethods.InvokeVirtualVoidMethod (__id, <span class="keyword">this</span>, __args);</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        JNIEnv.DeleteLocalRef (native_viewHolderT);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>However, since ClickableRecyclerAdapter inherits from <code>Android.Support.V7.Widget.RecyclerView.Adapter</code>, <code>OnBindViewHolder</code> needs to take a <code>RecyclerView.ViewHolder</code> as its first argument. The solution to this problem - and many others - is to remove the generated method in the Metadata.xml, and add a modified version in the <code>Additions</code> folder:</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">remove-node</span> <span class="attr">path</span>=<span class="string">"/api/package[@name='com.adyen.checkout.base.ui.adapter']/class[@name='ClickableListRecyclerAdapter']/method[@name='onBindViewHolder' and count(parameter)=2 and parameter[1][@type='ViewHolderT'] and parameter[2][@type='int']]"</span> /&gt;</span></span><br></pre></td></tr></table></figure><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//Namespace should match that of the generated class</span></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Com.Adyen.Checkout.Base.UI.Adapter</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">//Note that this is a partial class</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">partial</span> <span class="keyword">class</span> <span class="title">ClickableListRecyclerAdapter</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">//This code is identical to the generated code above,</span></span><br><span class="line">        <span class="comment">//except for the type of the first parameter</span></span><br><span class="line">        [<span class="meta">Register(<span class="meta-string">"onBindViewHolder"</span>, <span class="meta-string">"(Landroid/support/v7/widget/RecyclerView$ViewHolder;I)V"</span>, <span class="meta-string">"GetOnBindViewHolder_Landroid_support_v7_widget_RecyclerView_ViewHolder_IHandler"</span>)</span>]</span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">unsafe</span> <span class="keyword">void</span> <span class="title">OnBindViewHolder</span>(<span class="params">RecyclerView.ViewHolder viewHolderT, <span class="keyword">int</span> position</span>)</span></span><br><span class="line"><span class="function"></span>        &#123;</span><br><span class="line">            <span class="keyword">const</span> <span class="keyword">string</span> __id = <span class="string">"onBindViewHolder.(Landroid/support/v7/widget/RecyclerView$ViewHolder;I)V"</span>;</span><br><span class="line">            IntPtr native_viewHolderT = JNIEnv.ToLocalJniHandle(viewHolderT);</span><br><span class="line">            <span class="keyword">try</span></span><br><span class="line">            &#123;</span><br><span class="line">                JniArgumentValue* __args = <span class="keyword">stackalloc</span> JniArgumentValue[<span class="number">2</span>];</span><br><span class="line">                __args[<span class="number">0</span>] = <span class="keyword">new</span> JniArgumentValue(native_viewHolderT);</span><br><span class="line">                __args[<span class="number">1</span>] = <span class="keyword">new</span> JniArgumentValue(position);</span><br><span class="line">                _members.InstanceMethods.InvokeVirtualVoidMethod(__id, <span class="keyword">this</span>, __args);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">finally</span></span><br><span class="line">            &#123;</span><br><span class="line">                JNIEnv.DeleteLocalRef(native_viewHolderT);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Problem-3-Missing-method"><a href="#Problem-3-Missing-method" class="headerlink" title="Problem 3 - Missing method"></a>Problem 3 - Missing method</h3><p>In at least one case, the generated code was simply missing a method that was required by the base class or interface. The method for fixing this is pretty much like described above, although you obviously don’t need to remove it in metadata.xml. You also have to figure out how the method should be implemented, but that is not as difficult as it sounds, as all implementations follow the same pattern.</p><p>In my case, the generated class <code>Com.Adyen.Checkout.Card.CardListAdapter</code> was missing the <code>OnBindViewHolder</code> method, which is required by the <code>RecyclerView.Adapter</code> base class, and is obviously present in the <a href="https://github.com/Adyen/adyen-android/blob/master/card-ui/src/main/java/com/adyen/checkout/card/CardListAdapter.java" target="_blank" rel="noopener">original code</a>.</p><p>This solution, then, is to add a partial CardListAdapter class in the Additions folder, and add the OnBindViewHolder implementation in it. In this case it was very easy, since I could just basically copy the OnBindViewHolder implementation from ClickableListRecyclerAdapter above (or any other class that has it).</p><h3 id="Problem-4-Other-unfixeable-problem-gt-Kill-it"><a href="#Problem-4-Other-unfixeable-problem-gt-Kill-it" class="headerlink" title="Problem 4 - Other unfixeable problem -&gt; Kill it!"></a>Problem 4 - Other unfixeable problem -&gt; Kill it!</h3><p>Sometimes you will get another problem, that is not as easy to fix, for whatever reason. In many cases, you can solve this problem just by <em>removing the offending method altogether</em>. If it is not a method that you need to call directly from the app, and not a method that is required for implementing an Interface or an abstract base class, you can probably remove it with a <code>remove-node</code> line in Metadata.xml and be done with it.</p><p>The reason for this is, of course, that once the call to a native method has been made, for example with <code>InvokeVirtualVoidMethod</code> as above, subsequent calls will be completely native, so it doesn’t matter if the methods have .NET wrappers or not. At least that is my understanding of it.</p><h2 id="Bug-in-the-AAR-file"><a href="#Bug-in-the-AAR-file" class="headerlink" title="Bug in the AAR file"></a>Bug in the AAR file</h2><p>When I tried to use the Card Component in the Demo App, I got the build error <code>Multiple substitutions specified in non-positional format; did you mean to add the formatted=&quot;false&quot; attribute?</code>. Turns out there is (at least at the time of writing) a bug in <a href="https://github.com/Adyen/adyen-android/blob/master/card-ui/src/main/res/values/strings.xml" target="_blank" rel="noopener">strings.xml in the card-ui library</a>.</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">resources</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- snip --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">string</span> <span class="attr">name</span>=<span class="string">"expires_in"</span>&gt;</span>%s/%s<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- snip --&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">resources</span>&gt;</span></span><br></pre></td></tr></table></figure><p>Turns out you can’t have multiple <code>%s</code> in a string resource <a href="https://stackoverflow.com/questions/4414389/android-xml-percent-symbol/4417333#4417333" target="_blank" rel="noopener">because of reasons</a>. If you do, you need to add <code>formatted=&quot;false&quot;</code> to the node. I fixed this by editing the AAR file (it’s just a zip file, really), and adding the attribute in <code>/res/values/values.xml</code> (which is a squashed version of all xml files in the res folder).</p><p>Unfortunately, this means I had to check in the modified AAR file. For the rest of the files, I have a <a href="https://cakebuild.net/" target="_blank" rel="noopener">Cake</a> build script that just downloads all the AAR files from jcenter. But hopefully it will be fixed in the next release of card-ui.</p><p>I hope someone who has to create Xamarin Bindings will find this rather long and unstructured post useful. If nothing else, it will help me remember the problems I had and how I solved them for the next time.</p><div id="footnotes"><hr><div id="footnotelist"><ol style="list-style: none; padding-left: 0; margin-left: 40px"><li id="fn:1"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">1.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">Actually, I finished the first version of these bindings in June. Unfortunately, just as I though I was done, I noticed that the Adyen developer documentation] had changed substantially. While I was working on this they had release an RC version of version 3.0, which was totally different from version 2.4.5 that I had been working on. So I basically had to start all over again and create new bindings for v3. The old bindings are available at github (tag: 2.4.5), and also at NuGet (Approach.Adyen.UI.Droid), should anyone be interested. But it's probably better to use the new ones.<a href="#fnref:1" rev="footnote"> ↩</a></span></li></ol></div></div>]]></content:encoded>
      
      
      
      <category domain="https://johan.driessen.se/tags/android/">android</category>
      
      <category domain="https://johan.driessen.se/tags/xamarin/">xamarin</category>
      
      <category domain="https://johan.driessen.se/tags/adyen/">adyen</category>
      
      
      <comments>https://johan.driessen.se/posts/Some-Problems-and-Solutions-When-Creating-Xamaring-Android-Bindings/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Announcing Xamarin Android Bindings for Adyen Checkout</title>
      <link>https://johan.driessen.se/posts/Announcing-Xamarin-Android-Bindings-for-Adyen-Checkout/</link>
      <guid>https://johan.driessen.se/posts/Announcing-Xamarin-Android-Bindings-for-Adyen-Checkout/</guid>
      <pubDate>Wed, 04 Sep 2019 09:11:33 GMT</pubDate>
      
        
        
      <description>&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;p&gt;I’ve been working on implementing &lt;a h</description>
        
      
      
      
      <content:encoded><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>I’ve been working on implementing <a href="https://www.adyen.com" target="_blank" rel="noopener">Adyen payments</a> for a customer lately. They have been using another PSP for many years, but are now switching to Adyen. This is super easy on the web site, but as it turns out, not so easy in the mobile app.</p><p>Adyen offers a lot of sdk’s, including an <a href="https://docs.adyen.com/checkout/android/components" target="_blank" rel="noopener">Android SDK</a>. The app I’m working on, however, is developed in Xamarin, and unfortunately, Adyen does not offer a Xamarin SDK. That means that in order to use the Android SDK, we have had to create Xamaring bindings for the java SDK.</p><p>We have created a set of Xamarin Android Bindings for the Adyen Checkout components. So far, we have only implemented the Card Component and the Redirect Component, because that was all we needed at the time. </p><p>The components are available as NuGet packages:</p><ul><li><a href="https://www.nuget.org/packages/Approach.Adyen.CardUI.Droid/" target="_blank" rel="noopener">Adyen Checkout Card UI</a></li><li><a href="https://www.nuget.org/packages/Approach.Adyen.Redirect.Droid/" target="_blank" rel="noopener">Adyen Checkout Redirect</a></li></ul><p>The source code is available at <a href="https://github.com/apprch/adyen-xamarin-android-binding" target="_blank" rel="noopener">our Github account</a>, should you want to build your own components, or maybe fix a bug or two. There is also a Demo app in the github repository, which should help you use the components. So yeah, that’s our first official public open source project!</p><p>I have published <a href="/posts/Some-Problems-and-Solutions-When-Creating-Xamaring-Android-Bindings/" title="a follow up post">a follow up post</a> where I dwelve a little deeper into the problems I ran into while creating Xamarin Bindings for Android, and how to fix some of them. So check that out as well, if you’re into that kind of stuff!</p>]]></content:encoded>
      
      
      
      <category domain="https://johan.driessen.se/tags/android/">android</category>
      
      <category domain="https://johan.driessen.se/tags/xamarin/">xamarin</category>
      
      <category domain="https://johan.driessen.se/tags/adyen/">adyen</category>
      
      
      <comments>https://johan.driessen.se/posts/Announcing-Xamarin-Android-Bindings-for-Adyen-Checkout/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Resolving ILogger with Nancy and TinyIoC</title>
      <link>https://johan.driessen.se/posts/Resolving-ILogger-with-Nancy-and-TinyIoC/</link>
      <guid>https://johan.driessen.se/posts/Resolving-ILogger-with-Nancy-and-TinyIoC/</guid>
      <pubDate>Fri, 14 Jun 2019 12:43:16 GMT</pubDate>
      
        
        
      <description>&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;p&gt;This is a shorter follow-up post to my</description>
        
      
      
      
      <content:encoded><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>This is a shorter follow-up post to my recent post about <a href="/posts/Properly-configuring-NLog-and-ILogger-in-ASP-NET-Core-2-2/" title="configuring NLog and ILogger in ASP.NET Core">configuring NLog and ILogger in ASP.NET Core</a>. As I mentioned there, since we’re using <a href="https://nancyfx.org" target="_blank" rel="noopener">Nancy</a> for our project, we can’t just use the built-in dependency resolver in ASP.NET Core, since Nancy uses it’s own dependency resolution.</p><p>In most cases, we use <a href="https://autofac.org" target="_blank" rel="noopener">Autofac</a> and the <a href="https://github.com/nancyfx/nancy.bootstrappers.autofac" target="_blank" rel="noopener">Nancy Autofac bootstrapper</a>, but in this case, we were using the default <a href="https://github.com/grumpydev/TinyIoC" target="_blank" rel="noopener">TinyIoC</a> implementation, so that’s what I’ll write about in this post. I might write another follow-up post when I implement this for Autofac.</p><p>First of all, we need to pass the <code>ILoggerFactory</code> that we configured in the previous post. Since this is available in <code>Startup.Configure</code> we can just pass it on to our Nancy bootstrapper.</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Startup</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Configure</span>(<span class="params">IApplicationBuilder app, IHostingEnvironment env,</span></span></span><br><span class="line"><span class="function"><span class="params">                          ILoggerFactory loggerFactory, IConfiguration configuration</span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">        app.UseOwin(x =&gt; x.UseNancy(<span class="keyword">new</span> NancyOptions</span><br><span class="line">        &#123;</span><br><span class="line">            Bootstrapper = <span class="keyword">new</span> CustomBootstrapper(env, configuration, loggerFactory)</span><br><span class="line">        &#125;));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Now, if we were content with just resolving then non-generic version of <code>ILogger</code> this wouldn’t be much of a problem, we could just create a default logger, and register that. But since we want to use the generic <code>ILogger&lt;T&gt;</code>, it’s a little more complicated.</p><p>So we can use this custom bootstrapper:</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">CustomBootstrapper</span> : <span class="title">DefaultNancyBootstrapper</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">//Of course we have a constructor that takes the arguments passed from Startup</span></span><br><span class="line">    <span class="comment">//and sets them as fields, but that seems obvious.</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> <span class="title">ApplicationStartup</span>(<span class="params">TinyIoCContainer container, IPipelines pipelines</span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">        <span class="keyword">base</span>.ApplicationStartup(container, pipelines);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//Fallback for non-generic logger</span></span><br><span class="line">        <span class="keyword">var</span> defaultLogger = loggerFactory.CreateLogger(<span class="string">"Default"</span>);</span><br><span class="line">        container.Register&lt;ILogger&gt;(defaultLogger);</span><br><span class="line">        <span class="comment">//The generic constructor for Logger needs ILoggerFactory</span></span><br><span class="line">        container.Register&lt;ILoggerFactory&gt;(loggerFactory);</span><br><span class="line">        <span class="comment">//Register generic logger as multi instance</span></span><br><span class="line">        container.Register(<span class="keyword">typeof</span>(ILogger&lt;&gt;), <span class="keyword">typeof</span>(Logger&lt;&gt;)).AsMultiInstance();</span><br><span class="line">        <span class="comment">//TinyIoC cannot resolve ILogger&lt;&gt; directly in modules for some reasons,</span></span><br><span class="line">        <span class="comment">//so we have to register this one manually.</span></span><br><span class="line">        container.Register&lt;ILogger&lt;API.Modules.FooBarModule&gt;&gt;(</span><br><span class="line">            (c, an) =&gt; loggerFactory.CreateLogger&lt;API.Modules.FooBarModule&gt;());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Now, there a couple of things that are important here:</p><ul><li>We need to register <code>ILoggerFactory</code> even though we aren’t going to use it, since the generic constructor to <code>ILogger</code> needs it.</li><li>The generic logger needs to be registered with <code>.AsMultiInstance()</code>, otherwise it will be resolved only the first time, and the same (and wrong) generic instance will be re-used after that.</li><li>For some reason it seems the resolution of <code>ILogger&lt;&gt;</code> doesn’t work in the modules themselves. This might have something to do with how Nancy auto discovers the modules, or it might have something to do with TinyIoC, I don’t know. But since generally we do very little logging in the modules themselves, we just manually register the loggers that we need for the modules. Other options would be to for example<ul><li>Use the non-generic <code>ILogger</code> in the modules</li><li>Use the <code>ILoggerFactory</code> instead in the module instead, and manually create a generic logger with <code>loggerFactory.CreateLogger&lt;FooBarModule&gt;</code></li></ul></li></ul><p>I’m sure there are other, and probably better ways to this, but this seems to work well enough.</p>]]></content:encoded>
      
      
      
      <category domain="https://johan.driessen.se/tags/asp-net/">asp.net</category>
      
      <category domain="https://johan.driessen.se/tags/logging/">logging</category>
      
      <category domain="https://johan.driessen.se/tags/ioc/">ioc</category>
      
      <category domain="https://johan.driessen.se/tags/dependency-injection/">dependency injection</category>
      
      <category domain="https://johan.driessen.se/tags/dotnet-core/">dotnet core</category>
      
      <category domain="https://johan.driessen.se/tags/nancy/">nancy</category>
      
      
      <comments>https://johan.driessen.se/posts/Resolving-ILogger-with-Nancy-and-TinyIoC/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Repairing a cracked PCB in a Commodore 1901 monitor</title>
      <link>https://johan.driessen.se/posts/Repairing-a-cracked-PCB-in-a-Commodore-1901-monitor/</link>
      <guid>https://johan.driessen.se/posts/Repairing-a-cracked-PCB-in-a-Commodore-1901-monitor/</guid>
      <pubDate>Fri, 07 Jun 2019 16:49:33 GMT</pubDate>
      
        
        
      <description>&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;p&gt;This is the second and final part in a</description>
        
      
      
      
      <content:encoded><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>This is the second and final part in a very short series where I improve and repair my Commodore 1901 monitor. In <a href="/posts/Adding-an-RGB-SCART-connector-to-a-Commodore-1901-monitor/" title="part 1">part 1</a> I added a SCART connector with analog RGB and audio support, but also discovered that the colours were a bit off – especially when using an RGBi input, such as CGA – and discovered a crack on the PCB. In this part, I will repair the PCB and hopefully fix the colours.</p><p>First I had to have a good look at the crack. It was in the lower left corner of the PCB, close to the potentiometers that adjust the color levels, as marked in the picture below.</p><img src="/posts/Repairing-a-cracked-PCB-in-a-Commodore-1901-monitor/Commodore_1901_monitor_full_backside_nudity.jpg" title="Why is there a space in the middle of the of??"><p>After pulling the board out a bit and turning the monitor upside down, I could get a closer look at the crack.</p><img src="/posts/Repairing-a-cracked-PCB-in-a-Commodore-1901-monitor/PCB_on_crack.jpg"><p>That doesn’t look too fucking good. No less than nine (9) traces are broken. Fortunately, since this is an old monitor, the PCB is single layer, so there are no traces on the back, and no traces inside. The easiest way to repair a broken trace on a PCB is to find a solder joint on each side of the crack and solder a wire over the crack. But I also wanted to try another way. So for the first three traces, where there was enough space, I just scraped a bit of the outer layer off, and soldered a very short piece of wire right over the crack.</p><img src="/posts/Repairing-a-cracked-PCB-in-a-Commodore-1901-monitor/PCB_partially_repaired.jpg"><p>For the rest of the broken traces, there just wasn’t enough room to use this method, at least not with the tools and skill at my disposal. So I had to solder wires over the rest of the cracks.</p><img src="/posts/Repairing-a-cracked-PCB-in-a-Commodore-1901-monitor/PCB_fully_repaired.jpg" title="Looks like a nest of snakes..."><p>After this I reassembled the monitor (well, actually, I finished <a href="/posts/Adding-an-RGB-SCART-connector-to-a-Commodore-1901-monitor/" title="the SCART mod">the SCART mod</a> as well) and connected my Bondwell Model 8 to the RGBi-input. To my great surprise everything worked perfectly! The lovely CGA palette of white, cyan and magenta was as vibrant as ever with no sign of the yellowish tint from before, and some careful banging on the side of the screen no longer causes the colors to change. So I have to label this a complete success!</p><p>I now have the perfect monitor for my small<sup id="fnref:1"><a href="#fn:1" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="I would consider my collection small. There are others in my family who would voice a different opinion...">[1]</span></a></sup> collection of retro computers. It takes RGBi, SCART with analog RGB and separate Chroma and Luma input (like S-VIDEO). And it even has a built-in speaker! The only input I so far haven’t had much success with is composite. If I connect composite to the Luma input (the yellow RCA jack), I get a monochrome picture (not a great surprise). If I connect it to the Chroma instead, I get no picture at all. If I split the composite cable and connect it to both, I still only get monochrome. If anyone has a working way to connect a composite signal to separate luma and chroma inputs, I would be very interested. A minor annoyance though, as I can connect composite to a TV instead. So, yay, working Commodore 1901 monitor!</p><p>Finally, here is a picture of my five year old son playing Krakout on the repaired monitor!</p><img src="/posts/Repairing-a-cracked-PCB-in-a-Commodore-1901-monitor/boy_playing_krakout_ii_on_c64_and_commodore_1901.jpg" title="Some games never get old! Unfortunately, the improved colours does not show at all in this image..."><div id="footnotes"><hr><div id="footnotelist"><ol style="list-style: none; padding-left: 0; margin-left: 40px"><li id="fn:1"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">1.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">I would consider my collection small. There are others in my family who would voice a different opinion...<a href="#fnref:1" rev="footnote"> ↩</a></span></li></ol></div></div>]]></content:encoded>
      
      
      <category domain="https://johan.driessen.se/categories/retro-computing/">retro computing</category>
      
      
      <category domain="https://johan.driessen.se/tags/retro-computing/">retro computing</category>
      
      
      <comments>https://johan.driessen.se/posts/Repairing-a-cracked-PCB-in-a-Commodore-1901-monitor/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Properly configuring NLog and ILogger in ASP.NET Core 2.2</title>
      <link>https://johan.driessen.se/posts/Properly-configuring-NLog-and-ILogger-in-ASP-NET-Core-2-2/</link>
      <guid>https://johan.driessen.se/posts/Properly-configuring-NLog-and-ILogger-in-ASP-NET-Core-2-2/</guid>
      <pubDate>Tue, 21 May 2019 13:32:37 GMT</pubDate>
      
        
        
      <description>&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;p&gt;Ever since we started using dotnet cor</description>
        
      
      
      
      <content:encoded><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>Ever since we started using dotnet core a couple of years ago, both for new projects and porting old projects, we’ve been struggling with configuration. Especially regarding logging. The official documentation has been – to put it mildly – confusing and inconsistent, and to make matters worse, we’ve been wanting to use <a href="https://nlog-project.org/" target="_blank" rel="noopener">NLog</a> as well. In the old days (e.g. when we used .NET Framework 4.x) using NLog was pretty easy, we just added a NLog configuration section to web.config (or a separate file if we were being fancy), and then just accessed the static instance of NLog with <code>LogManager.GetCurrentClassLogger()</code>. This, however, does not work particularly well in dotnet core, for the following reasons:</p><ul><li>Dotnet Core does not like static accessors</li><li>Dotnet Core really would prefer if we used the ILogger interface to log stuff</li><li>We don’t have a web.config anymore</li></ul><p>So, over the last years I’ve tried different approaches to this, without ever being fully happy with the result. But with recent versions of dotnet, and multiple more or less ugly attempts, I feel I finally have a pretty good grasp of how to set everything up properly, so I though I’d better write it down for future reference before it slips my mind again (my mind is very good at remembering release years for old movies, but not so great at remembering dotnet configuration syntax).</p><p>So, first things first. We have an asp.net core web app targeting netcoreapp2.2, and in order to use NLog for the logging, we need two additional package references:</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">PackageReference</span> <span class="attr">Include</span>=<span class="string">"NLog.Extensions.Logging"</span> <span class="attr">Version</span>=<span class="string">"1.5.0"</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">PackageReference</span> <span class="attr">Include</span>=<span class="string">"Nlog.Web.AspNetCore"</span> <span class="attr">Version</span>=<span class="string">"4.8.2"</span> /&gt;</span></span><br></pre></td></tr></table></figure><p>Then, we need to configure the app configuration in <code>Program.cs</code>. In older versions of dotnet core most of this setup was done in <code>Startup.cs</code>, but it has since mostly been moved to the Program class.. Besides setting up the logging, we also configure the rest of the app configuration here, e.g. setting up <code>appsettings.json</code>. For more fundamental information about the <code>Program.cs</code> and <code>Startup.cs</code> classes, see <a href="https://docs.microsoft.com/en.us/aspnet/core/fundamentals/startup?view=aspnetcore-2.2" target="_blank" rel="noopener">docs.microsoft.com</a>.</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//This method is called from Main</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> IWebHostBuilder <span class="title">CreateWebHostBuilder</span>(<span class="params"><span class="keyword">string</span>[] args</span>)</span> =&gt;</span><br><span class="line">    WebHost.CreateDefaultBuilder(args)</span><br><span class="line">        .UseStartup&lt;Startup&gt;()</span><br><span class="line">            .ConfigureAppConfiguration((hostingContext, config) =&gt;</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">var</span> env = hostingContext.HostingEnvironment;</span><br><span class="line"></span><br><span class="line">            <span class="comment">//Read configuration from appsettings.json</span></span><br><span class="line">            config</span><br><span class="line">                .AddJsonFile(<span class="string">"appsettings.json"</span>, optional: <span class="literal">true</span>, reloadOnChange: <span class="literal">true</span>)</span><br><span class="line">                .AddJsonFile(<span class="string">$"appsettings.<span class="subst">&#123;env.EnvironmentName&#125;</span>.json"</span>,</span><br><span class="line">                             optional: <span class="literal">true</span>, reloadOnChange: <span class="literal">true</span>);</span><br><span class="line">            <span class="comment">//Add environment variables to config</span></span><br><span class="line">            config.AddEnvironmentVariables();</span><br><span class="line"></span><br><span class="line">            <span class="comment">//Read NLog configuration from the nlog config file</span></span><br><span class="line">            env.ConfigureNLog(<span class="string">$"nlog.<span class="subst">&#123;env.EnvironmentName&#125;</span>.config"</span>);</span><br><span class="line">        &#125;)</span><br><span class="line">        .ConfigureLogging(logging =&gt;</span><br><span class="line">        &#123;</span><br><span class="line">            logging.ClearProviders();</span><br><span class="line">            logging.AddDebug();</span><br><span class="line">            logging.AddConsole();</span><br><span class="line">            logging.AddNLog();</span><br><span class="line">        &#125;);</span><br></pre></td></tr></table></figure><p>The key here is of course the <code>env.ConfigureNLog($&quot;nlog.{env.EnvironmentName}.config&quot;)</code> statement, which allows us to read the NLog configuration from a standard NLog configuration file, just as we did in the old .NET Framework. The ConfigureNLog extension method is provided by the <code>Nlog.Web.AspNetCore</code> package. In my example I have different nlog config files for different environments, just as I have different appsettings for different environments. The <code>nlog.*.config</code> files are automagically copied to the publish directory, just as the appsetting files. We also configure the different loggers, and add a Debug, a Console and an NLog logger, which all will the receive the same logging data.</p><p>This also has the additional benefit of getting rid of a very annoying warning that you get if you still use the old method of adding loggers in <code>Startup.cs</code>:</p><p><code>ConsoleLoggerExtensions.AddConsole(ILoggerFactory)&#39; is obsolete: &#39;This method is obsolete and will be removed in a future version. The recommended alternative is AddConsole(this ILoggingBuilder builder).</code></p><p>And with this, we’re pretty much finished. All setup regarding logging and app configuration can be removed from <code>Startup.cs</code> unless you need to do other fancy stuff there. Since <code>IConfiguration</code> and <code>ILoggerFactory</code> is already configured in <code>Program.cs</code>, you may have to inject them in Startup. This can be done in either the constructor or in the ConfigureServices or Configure methods. I really can’t say which is best.</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Startup</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">Startup</span>(<span class="params">IHostingEnvironment env, IConfiguration config</span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">        <span class="comment">//I guess you could store config as a field here and access it in the other methods</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Configure</span>(<span class="params">IApplicationBuilder app, IHostingEnvironment env,</span></span></span><br><span class="line"><span class="function"><span class="params">                          ILoggerFactory loggerFactory, IConfiguration configuration</span>)</span></span><br><span class="line"><span class="function"></span>    &#123;</span><br><span class="line">        <span class="comment">//You can inject both ILoggerFactory and IConfiguration directly</span></span><br><span class="line">        <span class="comment">//into the configuration methods as well</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>If you are using the standard asp.net core dependency resolution, this is it! You can inject <code>ILogger</code> or (preferably) the generic <code>ILogger&lt;FooBar&gt;</code> anywhere you want to log stuff, and just log away. In our case, we use <a href="https://github.com/NancyFx/Nancy" target="_blank" rel="noopener">Nancy</a> and <a href="https://github.com/grumpydev/TinyIoC" target="_blank" rel="noopener">TinyIoC</a> (or frequently <a href="https://autofac.org/" target="_blank" rel="noopener">Autofac</a>) for dependency injection, which makes things a little more complicated, but that will make for an excellent post of its own!</p>]]></content:encoded>
      
      
      
      <category domain="https://johan.driessen.se/tags/asp-net/">asp.net</category>
      
      <category domain="https://johan.driessen.se/tags/logging/">logging</category>
      
      <category domain="https://johan.driessen.se/tags/dotnet-core/">dotnet core</category>
      
      <category domain="https://johan.driessen.se/tags/nlog/">nlog</category>
      
      
      <comments>https://johan.driessen.se/posts/Properly-configuring-NLog-and-ILogger-in-ASP-NET-Core-2-2/#disqus_thread</comments>
      
    </item>
    
  </channel>
</rss>
