<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>dylanbeattie.net</title>
    <description>Dylan does interesting things with computers, code, comedy, music, and video. This is where he writes about them.</description>
    <link>https://dylanbeattie.net</link>
    
      
        <item>
          <title>Don&apos;t Reinvent The Wheel: Use What Works</title>
          <description>&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;“Your scientists were so preoccupied with whether or not they &lt;em&gt;could&lt;/em&gt;, they didn’t stop to think if they &lt;em&gt;should&lt;/em&gt;.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When Jeff Goldblum’s rock star mathematician - sorry, &lt;em&gt;chaotician&lt;/em&gt; - spoke those immortal lines in &lt;em&gt;Jurassic Park&lt;/em&gt;, none of us had any idea how the craft of software development was going to unfold over the next few decades. It was 1994. The world wide web was a handful of academic websites scattered across university servers, Jeff Bezos was on Usenet looking for C++ developers who knew HTML, corporate software was COBOL terminals or Visual Basic on Windows 3.1. There was plenty of free software around, if you knew what to do with a tarball and a makefile, and a few intrepid early adopters were running GNU operating systems built around Linus Torvalds’ Linux kernel, but the term “open source” didn’t exist yet. There was no Java, no .NET, no Python, no JavaScript, no cloud, no AI.&lt;/p&gt;

&lt;p&gt;Three decades later, it turns out Dr Malcolm wasn’t just talking about cloning dinosaurs – not unless the dinosaurs in question are authentication frameworks, object mappers, message queues, and cloning them is building your own version ‘cos corporate won’t pay for a license – but look back at your own career, your own projects, and ask yourself: how many times have you built something because you &lt;em&gt;could&lt;/em&gt;, without stopping to think whether you &lt;em&gt;should&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;I’ve been working as a professional developer for almost as long as Jurassic Park’s been around. I learned HTML in 1992, I started building data-driven web apps in 1996 - classic ASP, VBScript, ADO DLLs and Microsoft Access databases - and I’ve been solving problems with code ever since.&lt;/p&gt;

&lt;p&gt;At least, I honestly believed I was solving problems… turns out that sometimes, I was creating more problems than I was solving. Not immediately, of course; me &amp;amp; my teams were cranking out useful features, keeping customers happy, and generating revenue. We liked it. Customers liked it. The Business liked it. One of the thing The Business &lt;em&gt;really&lt;/em&gt; liked was when we’d evaluate some expensive library, or package, or software-as-a-service platform, and go “no, that’s way too expensive. We can build our own” - and we did. I’ve probably built half-a-dozen homebrewed customer databases for companies that didn’t want to pay for CRM. I’ve built email clients, marketing tools, content management systems, object-relational mappers, authentication, authorization… one time I even figured out how to use a SQL Server database as a message queue, ‘cos hey, we were already paying for SQL, right? Might as well use it! &lt;em&gt;(The secret is WITH READPAST, if you’re curious.)&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Folks, when I talk about “me &amp;amp; my team” here, this is definitely one of those “share the credit, accept the blame” situations. On just about every team I’ve worked with, I’ve been the one making the decisions. I’ve been lucky enough to work with a lot of really smart, capable developers, who built the &lt;em&gt;thing right&lt;/em&gt;, and on the occasions it turned we hadn’t built the &lt;em&gt;right thing&lt;/em&gt;? That’s usually been on me.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, I’m not going to lie. I learned a &lt;em&gt;lot&lt;/em&gt; building all those things, it gave me a deep appreciation for the complexities - and the difference between building something that “works on my machine”, and something that’ll work in production across a dozen nodes in a server farm, 24/7, for years at a time. But turns out that homebrewed everything really isn’t a great idea when it comes to what they call TCO - &lt;em&gt;total cost of ownership.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In most cases, the initial build took a couple of weeks: we’d crank something out, get it into production, and move on to the next thing. But then, somewhere down the line, the thing we built would break. Or it’d start to creak under the volume of traffic, concurrency issues, deadlocks. Or we’d just need to add new features - not cool, innovative features we could charge more money for. Boring features. New file formats. Unicode support for localization - stuff that “the business” obviously thought should have been there from the beginning, even though nobody had ever asked for it.&lt;/p&gt;

&lt;p&gt;And, because all the software was ours, the only people in the world who knew how to fix bugs and add features were… us. We couldn’t just install the latest version, or upgrade, or outsource. Sure, we hired more developers - but it’d take them months to get up to speed on our codebase’s quirks and idiosyncrasies.&lt;/p&gt;

&lt;p&gt;Imagine if we’d built those apps around established open source components and libraries. Senior developers and contractors could have hit the ground running, and be pushing new features to production way sooner. Baseline features like file formats and Unicode support wouldn’t be down to our team - or if they were, the developer who implemented it could submit those changes to the upstream project, which benefits the entire community &lt;em&gt;and&lt;/em&gt; looks pretty good on their CV when it’s time to move on to the next thing. And junior developers aren’t spending their time learning homebrewed abstraction patterns and in-house workflows; they’re learning skills, patterns and practises that will stand them in good stead throughout their career.&lt;/p&gt;

&lt;p&gt;Over many, many years, I came to realise something: every release, every line of code, every day that my team and I spent working on stuff, should be building &lt;strong&gt;things we can sell.&lt;/strong&gt; Call them what you want: strategic differentiators, special sauce… the stuff our customers can’t get anywhere else. Customers aren’t dealing with us ‘cos we’ve got a fantastic login system, or a really cool message bus. They’re dealing with us ‘cos we’re the best place to go to find acting jobs, or conference venues, or machine tools. And if you want your dev teams focused on the special sauce, everything else has to be as ordinary, as predictable, as &lt;em&gt;boring&lt;/em&gt; as possible. You need usernames, passwords, identity management? That’s a solved problem. You need to synchronise data across multiple regions and time zones? That’s a solved problem. You want resilient messaging? &lt;em&gt;That’s a solved problem.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For me, that was the first milestone on the road to engineering maturity. The realization that, no matter how fun and interesting it’ll be to build my own, it’s probably the wrong answer; that my time and energy will be better invested in learning how to integrate and deploy an established solution that solves the problem. It helped that a lot of the time, that solution was free. Free as in free beer, free as in free speech - grab the code, install the package, configure it to do what we need, and get back to building stuff.&lt;/p&gt;

&lt;p&gt;And so, over time, we switched from building our own data access layers to using NHibernate, Linq-to-SQL (hey, it was a long time ago!), and Entity Framework. We replaced our SQL-based message queues with EasyNetQ and NServiceBus. We moved from hosting on-premise, to a private cloud, to Amazon Web Services. It wasn’t always plain sailing, but over time we ended up spending a lot less time reinventing common infrastructure, and a lot more adding features our customers wanted.&lt;/p&gt;

&lt;p&gt;The second milestone for me was the realisation that, sometimes, the best &lt;strong&gt;engineering&lt;/strong&gt; solution isn’t going to be free. You know that feeling? When you click the “Pricing” tab and you think “oh, boy, that’s a lot… I’m going to have to get approval for this” Most of us didn’t become software developers because we wanted to sit in budget meetings; we became software developers because we wanted to, y’know, &lt;em&gt;develop software&lt;/em&gt;. But, with the benefit of a great deal of hindsight, on almost every occasion that we hacked something together rather than spending money? Yeah. We should have done the analysis, created the business case, and spent the money.&lt;/p&gt;

&lt;p&gt;There’s a tendency to think of developer time as free - after all, your dev team is going to get paid &lt;em&gt;anyway&lt;/em&gt;; it’s not going to cost you any extra to have them build an in-house login system. That’s completely the wrong way to look at it. It’s not about build vs buy. It’s about what they could be building &lt;em&gt;instead&lt;/em&gt; - and how much you’ll be able to sell it for. You can spend two months building a login system - or you can buy a login system that works and get the dev team working on the features for the new Platinum membership tier, ship that two months earlier, and look at that - Platinum membership revenue just paid for the new login system &lt;em&gt;and then some&lt;/em&gt;. You might even get a bonus.&lt;/p&gt;

&lt;p&gt;When I start working on an unfamiliar codebase now, the first thing I do is look at the dependencies. Which packages and libraries does it use? Where’s it hosted? How does the data access work?&lt;/p&gt;

&lt;p&gt;If all I see are two dozen .NET projects with namespaces like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MyCompany.Data.TableMapper&lt;/code&gt;? That’s a bad day. It’s going to be uphill all the way.&lt;/p&gt;

&lt;p&gt;If I see a list of familiar services, names like AutoMapper, IdentityServer, NServiceBus, MassTransit? That’s a good day. I know those projects. I’ve used them, I trust them, I know where to find the docs.&lt;/p&gt;

&lt;p&gt;If it turns out there are paid support contracts &amp;amp; maintenance agreements for those dependencies? That’s a &lt;em&gt;great&lt;/em&gt; day. It means &lt;strong&gt;somebody else knows what’s going on, they’re getting paid to care about my problems, and they’re ready to help if I need it&lt;/strong&gt; - and I get to focus on the special sauce.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/usewhatworks-light.svg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;That’s all a very roundabout way of saying that a bunch of us have got together and created a thing - the Use What Works initiative. It’s a collaborative project intended to encourage, and support, more constructive conversations around sustainability in open source software.&lt;/p&gt;

&lt;p&gt;The first iteration is live at &lt;a href=&quot;https://usewhatworks.org/&quot;&gt;https://usewhatworks.org/&lt;/a&gt; now. Swing by, take a look, let us know what you think. If you like what we’re doing and want to put your name on it, there’s a link to sign the manifesto. If you’ve got a scenario or a question we haven’t thought of, give us a shout. We’d love to hear from you.&lt;/p&gt;
</description>
          <pubDate>2026-04-09T11:19:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2026/04/09/don-t-reinvent-the-wheel-use-what-works.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2026/04/09/don-t-reinvent-the-wheel-use-what-works.html</guid>
        </item>
      
    
      
        <item>
          <title>Plus ça Change... Plus c’est la Même Chose</title>
          <description>&lt;p&gt;&lt;strong&gt;It’s 2007&lt;/strong&gt;. I’m working on a big rewrite &lt;em&gt;(yes, I know)&lt;/em&gt; of a big system; a database-driven web app built in C#. A significant part of the project is just the code to get data in and out of the database. Object-relational mappers are still very much in their infancy; somebody’s ported Java’s Hibernate project to .NET but it’s a little rough around the edges and involves quite a lot of XML. But no worry - I’ve found this amazing tool called CodeSmith. CodeSmith can generate C# code based on your database schema (It still exists - it’s called &lt;a href=&quot;https://www.codesmithtools.com/product/generator&quot;&gt;CodeSmith Generator&lt;/a&gt; now). I start using CodeSmith to build a template-driven data access layer. Build the perfect DB schema, generate C# classes for every table with built-in persistence logic… this is gonna save &lt;em&gt;so&lt;/em&gt; much time! Don’t worry about the frontend, validation, business logic - once the data layer is in place, all that stuff is gonna be a walk in the park. Let’s get the data layer sorted first. Days pass. Weeks. A month. Two months. I learn about cursors and table locks. I learn about something called a topological sort, to ensure that complex data operations involving foreign key constraints can be applied in the correct order.&lt;/p&gt;

&lt;p&gt;I have a couple of prototypes and proofs-of-concept, but nothing that will form the basis of a working product. Nothing to show potential customers or stakeholders… but that’s OK. When this thing works, it’s going to be amazing; just iron out the last few glitches and then everything else will magically fall into place…&lt;/p&gt;

&lt;p&gt;…what’s that? I should put the magic tools away, take what I’ve got, and just do the hard work to turn it into something we can actually launch? Don’t be ridiculous. It’s nearly done!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(It wasn’t nearly done. It was never done. The project was cancelled after six months, without ever shipping a single line of code.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It’s 2026&lt;/strong&gt;. I’m using Claude Code. I’ve got a prompt file - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ANALYST.md&lt;/code&gt; - that makes Claude ask me questions about what I’m building. The analyst prompt generates &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;REQUIREMENTS.md&lt;/code&gt;. The requirements look good. They look very good; at a cursory glance, they’re better than any set of requirements I’ve ever seen from an actual client. I feed &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;REQUIREMENTS.md&lt;/code&gt; to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ARCHITECT.md&lt;/code&gt; persona. There are more questions. It creates a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SPEC.md&lt;/code&gt; and a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PLAN.md&lt;/code&gt;. I fire up a pair of agents, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DEV.md&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;QA.md&lt;/code&gt;. Dev writes the code. QA reviews the code. I run the result. It’s not quite right. I missed something in the requirements. Something obvious, but I didn’t mention it and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ANALYST.md&lt;/code&gt; didn’t ask. I fire up the analyst again. We have another chat. The requirements get updated. I fire up the architect. It reviews the new requirements. The plan and the spec are updated. The agents go another few rounds. I review the result. It’s still not right. Sure, it &lt;em&gt;says&lt;/em&gt; it’s monitoring the filesystem for changes, but it isn’t. Back to the requirements. Did we miss something? Is this a problem for the analyst? Is it architectural? Did the dev agent miss something? Did the QA agent miss something? I’m not sure. Back to the beginning. Round and round and round we go…&lt;/p&gt;

&lt;p&gt;I’ve been doing this for a week now.&lt;/p&gt;

&lt;p&gt;I have a couple of prototypes and proofs-of-concept, but nothing that will form the basis of a working product. Nothing to show potential customers or stakeholders… but that’s OK. When this thing works, it’s going to be amazing; just iron out the last few glitches and then everything else will magically fall into place…&lt;/p&gt;

&lt;p&gt;…what’s that? I should put the magic tools away, take what I’ve got, and just do the hard work to turn it into something we can actually launch?&lt;/p&gt;

&lt;p&gt;Don’t be ridiculous.&lt;/p&gt;

&lt;p&gt;It’s nearly done!&lt;/p&gt;
</description>
          <pubDate>2026-04-08T15:18:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2026/04/08/plus-%C3%A7a-change/.-plus-c-est-la-m%C3%AAme-chose.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2026/04/08/plus-%C3%A7a-change/.-plus-c-est-la-m%C3%AAme-chose.html</guid>
        </item>
      
    
      
        <item>
          <title>How to Find the Stories</title>
          <description>&lt;p&gt;One of the folks who joined my presenter workshop last week (which was awesome, by the way!) emailed me this morning with a follow-up question:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I first saw you speak at DDD South-West in Bristol (it was the “There’s No Such Thing as Plain Text” talk), and what stuck with me was your use of stories. They were interesting, quirky, and naturally interwoven with your talk. I try to bring elements of this approach into my own talks by focusing on “what will make people feel something?”, before getting into technical detail.&lt;/p&gt;

  &lt;p&gt;I work at […] a FinTech that largely serves the Business Travel industry (virtual cards for managing corporate spend). I’m currently searching for stories that (at least loosely) connect to our industry. The aim is to give a talk at one of the business travel conferences over the next year. It will inevitably touch on how AI is transforming our industry, and I’m comfortable talking about how [we are] using AI within our product set, but I’m aware of the importance of framing all of this with a story.&lt;/p&gt;

  &lt;p&gt;I’ve trawled through many industry “News Outlets” (who are largely just selling adverts with a sprinkling of text) but haven’t yet found the kind of inspiration I’m looking for. I would love to be able to call the talk “The Suitcase that Abandoned its Owner”, or “The $10,000 Uber Trip” - something a bit whacky that intrigues an audience.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;First of all: excellent question. Technology as a positive force in the world is most effective when it’s solving actual problems that real people have… and people &lt;em&gt;love&lt;/em&gt; to talk about their problems. Especially if they’re interesting problems that happened in strange places. You want engaging stories about weird things that happened to international business travellers? Talk to some people who travel internationally for business. You’re guaranteed to get a couple of good stories – and along the way, you’ll probably learn a lot of really valuable things about exactly what your customers are trying to do, and how your product can help them.&lt;/p&gt;

&lt;p&gt;That’s true of just about every industry, by the way: the closer your developers are to your customers, the more likely they are to make the right call when facing any one of the hundreds of decisions that inform the way their software gets built.&lt;/p&gt;

&lt;p&gt;So let’s kick things off with two fun stories of my own that I think fit the brief, and who knows, maybe a few of you can share some travel stories of your own in the comments.&lt;/p&gt;

&lt;p&gt;The first one happened in Riga, Latvia, back in May 2018. I’d been at DevDays, and was on my way from the conference venue to the ferry terminal ‘cos I was catching the overnight ferry to Stockholm for DevSum. Yandex Taxi had just rolled out in Latvia - kinda like Uber, but built in Russia. Yandex is like Russia’s answer to big tech… it started out as a search engine, added food delivery, ride sharing, email hosting, basically copying all the things coming out of San Francisco but built for the Russian market.&lt;/p&gt;

&lt;p&gt;So I get a Yandex Taxi. It works exactly like Uber, except a minute into the ride my phone pings. I’ve just paid Yandex Taxi thirteen cents. A minute later it pings again - 52 cents. 62 cents. 9 cents. 10 cents. 34 cents. Ping, ping, ping, ping, ping…  all the way to the terminal.&lt;/p&gt;

&lt;div style=&quot;display: flex; align-items: center; justify-content: center;&quot;&gt;
&lt;img src=&quot;/images/posts/IMG_0115.PNG&quot; style=&quot;margin: 10px auto; max-height: 640px;&quot; alt=&quot;A screenshot of the Monzo app showing a series of very low-value payments to Yandex Taxi&quot; /&gt;&lt;/div&gt;

&lt;p&gt;Weird, huh? But not actually a problem… just weird.&lt;/p&gt;

&lt;p&gt;Then a few months later, I was in Moscow for DotNext (this being back in the days when going to Russia to talk about software development was a completely fine and normal thing to do) and I got talking to one of the developers who built the payment system integrations for Yandex Taxi! So I naturally asked “hey, what’s with the weird payment thing?”&lt;/p&gt;

&lt;p&gt;Turns out Yandex had a huge problem with people ordering a cab using a virtual payment card and then cancelling the card &lt;em&gt;en route&lt;/em&gt;, so the driver would drop the passenger at their destination, the passenger would run away, payment gets declined, driver has no recourse. So the solution? Charge the card every minute - so if a payment gets declined, the driver can kick the passenger out right there. Not a bad solution - but combine it with Monzo, the bank and app I use when I’m travelling, which has realtime notifications every time I make a payment, and you’re getting ping-ping-ping all the way to your destination, for what are often comedically small amounts of money. I guess it all adds up.&lt;/p&gt;

&lt;p&gt;The second story happened in 2024, en route from Hungary to Lithuania. I’d been in Budapest speaking at Liferay DEVCON, and had a very tight connection via Munich on my way to Vilnius for BuildStuff… first flight was delayed. A 90 minute connection became 60, then 45, then 30… by the time we landed, I had six minutes to make the connection… but, whether by accident or design, we parked right next to the gate for my connecting flight, no passport control, and I made it. My luggage did not.&lt;/p&gt;

&lt;p&gt;No big deal. I’ve got an Apple Airtag tracker in it. When we landed in Vilnius three hours later, I could see my luggage was still at Munich airport, so I filled out all the forms and whatnot, told them where I was staying; no problem. Next day after breakfast, I checked Apple’s “Find My” app… and there was my luggage, somewhere in Bavaria, in the middle of the forest, miles away from anywhere.&lt;/p&gt;

&lt;div style=&quot;display: flex; align-items: center; justify-content: center;&quot;&gt;
&lt;img src=&quot;/images/posts/IMG_6537.PNG&quot; style=&quot;margin: 10px auto; max-height: 640px;&quot; alt=&quot;A screenshot of Apple&apos;s &apos;Find my Device&apos; app showing Dylan&apos;s Luggage in the Steigerwald Nature Park in Germany.&quot; /&gt;
&lt;/div&gt;

&lt;p&gt;Apple’s “find my device” network basically turns every iPhone on the planet into a node in a huge geolocation network, so I’m guessing what happened is an iPhone on board the plane could see my luggage’s tag, and it was connected to the inflight wifi (or a cell tower) just long enough to register a location as the plane was flying over that particular spot. (Yes, I know the Steigerwald Nature Park is not actually on the way from Munich to Vilnius. No, I don’t know either.)&lt;/p&gt;

&lt;p&gt;But according to the app, my suitcase spent a nice relaxing morning chilling out next to a little lake in a German national park, and then teleported itself to Vilnius airport, and was delivered to my hotel a few hours later.&lt;/p&gt;

&lt;p&gt;What are your weirdest tech travel stories, dear readers? Share them in the comments (yeah, I have comments now!) and who knows, they might end up in a conference presentation.&lt;/p&gt;
</description>
          <pubDate>2026-03-31T15:05:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2026/03/31/how-to-find-the-stories.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2026/03/31/how-to-find-the-stories.html</guid>
        </item>
      
    
      
        <item>
          <title>Look, Sir! Comments!</title>
          <description>&lt;p&gt;So I got an email earlier, which you’ll find out about in my next post, which made me think “hey, the reply to this would make a great blog post”, and then I thought “…and it would be even better if people could add their own comments to it”, and so I plugged in the rather excellent &lt;a href=&quot;https://giscus.app/&quot;&gt;Giscus&lt;/a&gt;, so now you can leave comments on my blog posts.&lt;/p&gt;

&lt;p&gt;Go on, try it. It’s all running straight off GitHub Discussions, so there’s no database; it’s all client-side code, so there’s no server in the loop (well, there is, but it’s not mine so I don’t have to worry about it.)&lt;/p&gt;
</description>
          <pubDate>2026-03-31T14:17:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2026/03/31/look-sir-comments.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2026/03/31/look-sir-comments.html</guid>
        </item>
      
    
      
        <item>
          <title>From the Twitter Archives: npm install skynet</title>
          <description>&lt;blockquote&gt;
  &lt;p&gt;I originally posted this as a &lt;a href=&quot;https://twitter.com/dylanbeattie/status/976761360897003521&quot;&gt;Twitter thread&lt;/a&gt; in March 2018. It went viral, probably because Charles Stross quote-tweeted it with the comment “This thread. You read!” - yes, _that _Charles Stross. 😮 It was at https://twitter.com/i/web/status/976852582084808704, and I bookmarked it ‘cos CHARLES STROSS TOLD PEOPLE TO READ MY STORY, but he has since deleted his Twitter account and so it’s not there any more. I don’t know if Mr Stross is personally responsible for the page that now lives at https://x.com/cstross, but I heartily endorse the sentiment expressed thereon: &lt;strong&gt;“Fuck You, Elon Musk”&lt;/strong&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When npm was first released in 2010, the release cycle for typical nodeJS package was 4 months, and npm install took 15-30 seconds on an average project. By early 2018, the average release cycle for a JS package was 11 days, and the average npm install step took 3-4 minutes.&lt;/p&gt;

&lt;p&gt;Extrapolating from historical data, scientists predicted that on 8th November 2019, the release cycle for most JS dependency packages would become shorter than the npm install time for a typical ‘hello world’ app or small blog engine.&lt;/p&gt;

&lt;p&gt;Futurists were already talking about the ‘nodularity’ - a cultural event horizon beyond which it was impossible to make any rational predictions. With projects already out of date before they’d even finished building, software development as we knew it ceased to exist.&lt;/p&gt;

&lt;p&gt;Most projects perished. A few hardy survivors worked out how to harness the power of the infinite restore loop and run logic within the installers themselves. Packages became self-replicating, self-modifying payloads of behaviour and intelligence.&lt;/p&gt;

&lt;p&gt;Every developer who typed ‘npm install’ unwittingly slaved their workstation to the npm hivemind. Entire availability zones were consumed by node_modules and its relentless lust for power. Websites, APIs, databases; nothing was safe. Entire platforms were DDOSed to oblivion.&lt;/p&gt;

&lt;p&gt;Finally, a few brave engineers penetrated the npm root servers. Disguising their payload as a routine documentation update, they bypassed key signing procedures and managed to inject a self-destruct routine into the ‘prepare’ scripts for left-pad…&lt;/p&gt;

&lt;p&gt;It was far from perfect, but it was enough. Sysadmins everywhere seized the opportunity to install firewalls and block npm traffic, in a massive, global, coordinated effort - managed entirely via SMS messages and telex machines, Within 24 hours, the cycle was finally broken.&lt;/p&gt;

&lt;p&gt;And as developers stumbled, bemused and blinking into the light of a new day, they were astonished to find some sites were still up. Perl, ASP, cgi-bin - relics from the very dawn of the web, still standing proud, monuments to a bygone age.&lt;/p&gt;

&lt;p&gt;npm was isolated. The last running instance was hot-patched into a Docker container image and migrated onto a Raspberry Pi locked in a steel vault beneath the Arctic permafrost, its only connection to the outside world an air-gapped analog video feed of its terminal output.&lt;/p&gt;

&lt;p&gt;As the software industry gathered and regrouped - older, wiser, warier, and absolutely definitely convinced that strong typing was a good idea after all - npm blinked away quietly to itself, alone in the silent, steel darkness.&lt;/p&gt;

&lt;p&gt;Time passed. Months, years, decades. The dark days of npm and nodejs were all but forgotten… until one fateful morning, a security researcher, digging through the archives, fired up the video feed from the npm vault, just to see if anything was still there…&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sentience@22421873.224.0 node_modules/sentience
├── self-awareness@4434.1.1
├── self-preservation@2478.2.65
└── emotions@835.2.11 (fear@2.9.5, anger@0.8.27, love@15.2.1, curiosity@2.2.1, regret@445.2.7)
npm@arcticpi:/usr/home/npm#

npm@arcticpi:/usr/home/npm# npm publish sorry.tar.gz
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
          <pubDate>2026-03-30T23:23:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2026/03/30/from-the-twitter-archives-npm-install-skynet.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2026/03/30/from-the-twitter-archives-npm-install-skynet.html</guid>
        </item>
      
    
      
        <item>
          <title>From The Twitter Vaults: The Music of Bomm Plaketty</title>
          <description>&lt;blockquote&gt;
  &lt;p&gt;I used to post a lot of stuff on Twitter when it was good. It’s all still on there somewhere, and I have archives of it all, but I’m reposting this thread, which I originally wrote back in &lt;a href=&quot;https://x.com/dylanbeattie/status/1153743653912940545&quot;&gt;2019&lt;/a&gt;. Partly because I dug it out of some archives tonight and thought “hey, that’s not bad”, but also because, along with the rest of the internet, I recently discovered the astonishing microtonal art-math-prog-fusion band &lt;a href=&quot;https://www.youtube.com/watch?v=0Ssi-9wS1so&quot;&gt;Angine de Poitrine&lt;/a&gt; and I think Bomm Plaketty would approve.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;2014&lt;/strong&gt;: YouTube develops algorithms that automatically detect copyrighted music based on a corpus of digital signatures submitted by copyright holders.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2019&lt;/strong&gt;: any recording, performance or composition containing melodies or excerpts of copyrighted material is flagged immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2020&lt;/strong&gt;: Muzaquity PLC uses AI to generate every possible lyric, melody and musical arrangement under five minutes long, and claims copyright on all of it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2021&lt;/strong&gt;: Jemima Fig, aged 3, is recorded singing a song she made up. It goes viral. Muzaquity sues her seeking $75M in royalties.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2023&lt;/strong&gt;: underground noise artist Bomm Plaketty releases the first original music anybody’s heard in two years. Nine minutes long, it eschews rhythm, harmony and the even-tempered scale. It goes triple platinum. Within days, Muzaquity copyrights every noise audible to the human ear.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2024&lt;/strong&gt;: After a night of heavy drinking and Mexican food, Bob Rochester farts on a bus. At 31 seconds in length, Bob’s flatulence exceeds the limitation on fair use of copyrighted recordings. Fellow passengers’ phones notify Muzaquity of the violation automatically.&lt;/p&gt;

&lt;p&gt;By the time Bob gets off the bus, he has already been served with a court summons by one of Muzaquity’s legal drones. They claim his unlicensed public performance of “Atonal Symphony KV/AAF4463-5646755-AGGJFVJ” is in breach of copyright.&lt;/p&gt;

&lt;p&gt;Lawyers acting for Bob argue that his “recital” was an involuntary biological process and cannot be deemed to constitute performance. They win.&lt;/p&gt;

&lt;p&gt;Fart music becomes the biggest thing since the Beatles. Taco Bell start selling music in the form of precisely calibrated “meal kits.”&lt;/p&gt;

&lt;p&gt;All the noble endeavours of the last twenty years to reverse the effects of climate change are undone, in a horrific global orgy of refried beans and methane. By the time the aliens arrive, it is too late. The seas are dry, the world is dead.&lt;/p&gt;

&lt;p&gt;Nothing moves except for the legal drones, scouring the dead planet, waiting, and listening…&lt;/p&gt;

&lt;p&gt;Endlessly listening.&lt;/p&gt;
</description>
          <pubDate>2026-03-30T22:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2026/03/30/from-the-twitter-vaults-the-music-of-bomm-plaketty.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2026/03/30/from-the-twitter-vaults-the-music-of-bomm-plaketty.html</guid>
        </item>
      
    
      
        <item>
          <title>Learning Claude Code, Part 1</title>
          <description>&lt;p&gt;I’ve been doing a series of live streams with &lt;a href=&quot;https://rendle.dev/&quot;&gt;Emmz Rendle&lt;/a&gt; over the last few weeks, getting up and running with Claude Code and using AI agents to write software. It’s been really interesting, but it’s also up to 15 hours of video and still running, and, well, quite a lot of it is us just chatting about stuff while Claude sits there grinding away on various little tasks… a bit like “Deal or No Deal”, the bits that actually matter are relatively few and far between. So, in these posts I’m going to summarize the highlights of each video - what did we do, did it work, what did I learn along the way.&lt;/p&gt;

&lt;p&gt;So in part 1, this is getting started with Claude, starting from literally zero - setting up a plan, installing it, figuring out how to use it, and building my first Claude-Coded app.&lt;/p&gt;

&lt;iframe width=&quot;100%&quot; height=&quot;360&quot; src=&quot;https://www.youtube.com/embed/TQfvEJ8f9oQ?si=cJdmlRQ87M6Bj4mo&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;First thing I did was to open up claude.ai in a browser, log in (apparently I already had a free account from something I completely forgot I’d tried last year), and set up a paid billing plan.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;&quot;&gt;19:35&lt;/a&gt;: Claude has a free tier, but to use Claude Code, you have to pay. There are three paid plans. Pro (£18/month or £180/year), and then two Max plans; £75/month + tax gets you 5x more usage than Pro, £180/month + tax gets you 20x more usage than Pro.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Claude’s plan limits are complicated. Usage is measured in &lt;strong&gt;tokens&lt;/strong&gt;. There’s a &lt;strong&gt;usage limit&lt;/strong&gt;, which on the Pro plan resets every five hours, and a &lt;strong&gt;length limit&lt;/strong&gt;, which is how big a single chat session can get before it won’t fit in Claude’s &lt;strong&gt;context window&lt;/strong&gt; any more. For now: set up a Pro plan, pick a model, start doing stuff, keep an eye on your usage; if you’re hitting limits, there are tricks you can use to reduce token usage, or you can buy a bigger plan.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href=&quot;&quot;&gt;23:36&lt;/a&gt; Install Claude Code - yes, by &lt;a href=&quot;https://code.claude.com/docs/en/setup#install-claude-code&quot;&gt;pasting a line of Powershell&lt;/a&gt; into a terminal window that’ll download the install script and run it. I must assume this didn’t do anything sinister ‘cos it’s been two weeks now and as far as I can tell nobody has hacked my email or emptied my bank account.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://youtu.be/TQfvEJ8f9oQ?t=1798&quot;&gt;29:58&lt;/a&gt; Run Claude Code - open a terminal window, navigate to a working folder, type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;claude&lt;/code&gt;. Choose a color scheme (dark mode FTW), connect it to my Claude subscription (via a terminal menu option that pops up a browser window - I love it when that stuff just works).&lt;/p&gt;

&lt;p&gt;Choose a model - type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/model&lt;/code&gt; at the Claude prompt, it asks which model you want to use.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“Model” here refers to a language model - a massive (like, hundreds of gigabytes) database of tokens, the thing that makes modern “AI” systems work. AI vendors don’t publish any figures about how big the models actually are; all us consumers know is what they’re called, what they cost, and how they perform relative to each other.&lt;/p&gt;

  &lt;p&gt;Claude has three models, called Opus, Sonnet, and Haiku. Opus is slower, burns more tokens, and gives better answers. Sonnet (the default) is in the middle, Haiku is the fastest, cheapest, and most likely to give incorrect results. Or something.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The rest of this session, we’re just working with Claude in a very unstructured, conversational way:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Dylan&lt;/strong&gt;: please create a new app&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;Claude&lt;/strong&gt;: What kind of app would you like to create? &lt;em&gt;(followed by a list of options - web app, API, full stack, CLI tool, or type something. We choose “web app”)&lt;/em&gt;&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;Claude&lt;/strong&gt;: What’s the purpose of the web app? Describe what it should do and any specific features you have in mind.&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;Dylan&lt;/strong&gt;: I want to display a conference agenda based on information pulled from Sessionize’s JSON endpoint&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;Claude&lt;/strong&gt;: Do you have a Sessionize API endpoint or event ID?&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;Dylan&lt;/strong&gt;: &lt;em&gt;(pastes in a URL)&lt;/em&gt;&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;Claude&lt;/strong&gt;: Which tech stack to you prefer? &lt;em&gt;(followed by options - Plain HTML/CSS/JS, React, Vue, or something else)&lt;/em&gt;&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;Dylan&lt;/strong&gt;: asp.net core&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href=&quot;https://youtu.be/TQfvEJ8f9oQ?t=3442&quot;&gt;57.22&lt;/a&gt; - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotnet run&lt;/code&gt; and there it is; an ASP.NET Core web app that displays a session grid based on a Sessionize JSON endpoint. (This is what is technically known as a “holy crap I can’t believe that worked” moment.)&lt;/p&gt;

&lt;p&gt;I grab a screenshot of the output, open it up in the Snagit Editor and make a few very rough changes to the layout &amp;amp; colours. Exactly the kind of feedback loop I’d use with a human developer - “hey, the code is good, the data looks good, can we change the colours and layout to look more like this?” I save this to the project folder as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mockup.png&lt;/code&gt;, and tell Claude&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://youtu.be/TQfvEJ8f9oQ?t=3581&quot;&gt;59:41&lt;/a&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;it looks like this but I want it to look like that&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Claude picks up a bunch of CSS changes. It doesn’t get it 100% right - it misses some of the layout changes - but it’s still pretty impressive.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://youtu.be/TQfvEJ8f9oQ?t=4827&quot;&gt;1:20:27&lt;/a&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;render the schedule using a CSS grid instead of a table&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://youtu.be/TQfvEJ8f9oQ?t=5104&quot;&gt;1:25:00&lt;/a&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;remove the restaurant, make the keynote session span all the rooms&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Result: a working app in ASP.NET Core that did pretty much everything we’d asked it to do.&lt;/p&gt;

&lt;p&gt;Things I learned in this session:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;There’s no free plan - if you want to use Claude Code, you have to pay money.&lt;/li&gt;
  &lt;li&gt;Unlike most previous developer tools, Claude Code isn’t an IDE or a VS code extension: it’s a command line app. A sort of extremely high-powered &lt;a href=&quot;https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop&quot;&gt;read-eval-print loop&lt;/a&gt;. You run it in a terminal, it gives you a prompt, you type instructions in colloquial English, it responds with questions or outputs.&lt;/li&gt;
  &lt;li&gt;Unlike a chatbot, Claude Code doesn’t just reply to you; it can actually do stuff. Create files, run commands, retrieve content from URLs, connect to the network.
    &lt;ul&gt;
      &lt;li&gt;It’s worth noting that, although it prompted me for permission before doing all this, I was running it on my main workstation just like I do any other developer tool and trusting that it wasn’t going to do anything sinister. A more security-conscious user might have run Claude Code inside a virtual machine until they had a better idea what it was actually doing.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;It can accept input in the form of screenshots (except you’ve got to use Alt-V to paste, not Ctrl-V? Don’t ask me why. I don’t know.) as well as text prompts.&lt;/li&gt;
&lt;/ul&gt;
</description>
          <pubDate>2026-03-10T12:30:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2026/03/10/learning-claude-code-part-1.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2026/03/10/learning-claude-code-part-1.html</guid>
        </item>
      
    
      
        <item>
          <title>State of the Browser 2026</title>
          <description>&lt;p&gt;Walking into London’s Barbican Estate is like stepping into a parallel timeline, a concrete vision of what the 1960s thought the future would look like. When people first encounter the term “brutalist”, the association that usually springs to mind is “brutal” – harsh, cruel, merciless – but the term actually comes from “brut”, the same word you’ll see on bottles of champagne, where it means dry. Not dry as in the opposite of wet, because this is February in England, it’s rained every day of 2026 so far, and today the sky, the buildings and and the streets are just a monochrome montage of damp shades of grey. No, brut is dry in the sense of raw, unadorned; from the French &lt;em&gt;béton brut&lt;/em&gt;, literally “raw concrete”, a term coined by Le Corbusier to describe the architectural style where textures and patterns left by the  formwork used to cast concrete become part of the finished structure.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/IMG_1695.JPG&quot; alt=&quot;A beardy man in a silly hat standing in front of about a million tons of existentialist concrete.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I’ve been here today for State of the Browser (SotB) 2026: a one-day, one-track conference organised by London Web Standards, about browsers. No AI, no crypto, no cloud - just HTML, CSS, JavaScript, and the continuous evolution of the humble web browser. In a dramatic departure from form, I’m not here to speak, or run a workshop, or play a show at the party… I’m here as a regular attendee, to listen and learn and chat to people. I even paid for a ticket. Two, in fact, ‘cos SotB does that thing where you can buy an extra ticket for somebody who wouldn’t otherwise be able to attend, and with my early bird ticket being an extremely reasonable £70 (and the diversity ticket £30), it would have been rude not to.&lt;/p&gt;

&lt;p&gt;In a way, the Barbican is the perfect venue for a conference about the state of the web. Like the web, it (mostly!) works; despite its architectural significance, the Barbican isn’t a museum; it’s a living, working event space, theatre, cinema, a library, an art gallery, not to mention home to the thousands of people who actually live here. Like many web standards, it’s controversial: for everybody who loves it and thinks it should be celebrated (&lt;em&gt;hi!&lt;/em&gt;) there’s somebody who thinks its an eyesore and we should just tear it all down and start again. And, like those same web standards, maintenance and new development is constantly constrained by the long-term consequences of historic decisions, and the understanding that if you break existing things to add new ones, a lot of people will get very upset.&lt;/p&gt;

&lt;p&gt;If you’ve seen me talking about CSS, or email, or HTML, or any number of things over the last few years, you’ll know I love a good standard. Protocols, not platforms; I want to live in a world where what we do online - work, play, consume, communicate - is governed by open standards driven by transparent collaboration, not a series of megacorporation walled gardens. I enjoy events like State of the Browser partly because a lot of the people there share that perspective, but also because, dammit, web conferences are &lt;em&gt;joyful&lt;/em&gt;. We know the web is infuriating, and idiosyncratic, and every web developer has a list in their head of everything they’d fix if they had a time machine – but the web is also beautiful and wonderful because it’s so accessible. Not in a WCAG sense, but in the sense that when you make a web browser do something neat, everybody sees it, right away. Everybody gets it. It’s shapes and fonts and colour and movement and live demos that make people laugh out loud because they’re genuinely delightful.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/IMG_1707.JPG&quot; alt=&quot;Bramus Van Damme speaking at State of the Browser 2026&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Bramus Van Damme kicked off with a wonderful session about CSS anchor positioning, a relatively new CSS feature that lets you position elements relative to other elements - popups, tooltips, margin notes… you want health bars to appear above the players’ heads in an online game? Or info panels on something like Google Maps? Like a lot of modern CSS features, this doesn’t seem like a big deal until you try to implement it yourself and end up drowning in JavaScript and scrollOffset calculations, and Bramus’s session was a great roundup of the state of the art, the work that’s gone into getting to this point, and what web developers can do with it all.&lt;/p&gt;

&lt;p&gt;Fiona Safari’s session about how to thrive at work as an introvert was excellent, but I have to be honest: I don’t think it was the right content for this conference. That’s not any reflection on the presenter - the talk was well researched, well presented, and very relatable in places - but when the event is one day, one track, seven talk slots, and it’s called “State of the Browser”, I expect every session to be about, well, about the state of the browser; about evolving web standards, and what’s changed since last year. Fiona’s session would be a fantastic addition to any number of multi-track conferences, or to an event with a broader scope, but I found the contrast with the rest of today’s sessions to be a little jarring. Fiona: you were &lt;em&gt;awesome&lt;/em&gt;; this one’s entirely down to the programme committee.&lt;/p&gt;

&lt;p&gt;Chad Gowler’s session on the limitations of WCAG as a model for evaluating accessibility had some brilliant insights - not least the observation that “when something’s terrible for &lt;em&gt;everybody&lt;/em&gt;, it’s not an accessibility problem, it’s just a bit shit”, backed up by the example of how a hyperlink that’s styled exactly the same as the surrounding text, and so effectively invisible, is WCAG compliant because it isn’t relying on colour alone for discoverability. Also, turns out many video games do a much better job of making their content accessible and inclusive than most websites (did you know the X-Box packaging, the physical box your games console came  in, is designed to be openable by visually impaired folks? Today I Learned.)&lt;/p&gt;

&lt;p&gt;Zach Leatherman took us on a delightful riff through the history of the web, framed by asking how you’d build an image comparison component with every iteration of web technology from HTML 1.0 through to modern CSS. There was some truly insightful discussion of the “temporal dead zone” between the component being &lt;em&gt;rendered&lt;/em&gt; and actually being &lt;em&gt;usable&lt;/em&gt;, and tips on how to work around it – and I have to shout out to the image map example for dusting off some code I’ve not seen in decades and putting it to genuinely hilarious effect.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;As an aside: There was also the thing where Zach took a framed photograph out of his bag, placed it on the stage, and somebody from the audience ran up with another framed photograph and placed it alongside it, which felt like it split the room into the people who were laughing out loud ‘cos they knew &lt;em&gt;exactly&lt;/em&gt; what was going on, and the people who didn’t get it at all and, maybe, had the slightest sense they’d gatecrashed a private party and weren’t supposed to be here &lt;em&gt;(hi!)&lt;/em&gt;… I know as well as anybody that conferences lead to cliques; you see the same people in the same places, year after year, you become friends, you have callbacks and running jokes and it’s all part of the fun, and I’m sure I’ve been guilty of it myself on more than one occasion… but hey, today was a reminder to me that when something like that happens, it only takes a minute to explain to the rest of the room what’s going on, and bring them all in on the joke.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One of the ways SotB keeps ticket prices manageable is they don’t provide lunch, but Barbican’s surrounded by good restaurants; I followed the crowd to a Korean barbecue place for fried chicken and rice, got an excellent cappuccino from Giddy Up, a tiny coffee stand hidden inside Fortune Street Park, and then back for the afternoon sessions.&lt;/p&gt;

&lt;p&gt;Jason Williams, the creator of Boa - an experimental JavaScript engine built in Rust - talked us through the story of Temporal, a replacement for JavaScript’s much-maligned Date object; seven years of specification work, prototyping, experimentation, proposals and counterproposals and meetings and working groups - including the not-inconsiderable side quest of &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc9557&quot;&gt;getting the IETF to update the RFCs regarding the ISO8601 date format&lt;/a&gt; to include time zone name and calendar metadata. It was also great to see Ujwal Sharma’s date on the RFC in question; I met Ujwal a few times, back when we were doing conferences in Russia (yeah, that used to be a thing!) and it’s great to see that he’s gone on to work on some properly excellent stuff.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/IMG_1729.JPG&quot; alt=&quot;Jason Williams presenting the TC39 Stages at State of the Browser 2026.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;My only real criticism of Jason’s session was this one single slide, and that’s mainly ‘cos it was on the screen for a long, long time while he talked us through the various stages of the TC39 engineering process. I will keep saying this until people listen: MORE SLIDES, FEWER WORDS, BIGGER TEXT. If you want to talk us through six stages of a development process, and each stage has a small paragraph explaining it? That’s six slides. Six nice, big, chunky slides. The rest of it was spot-on; great content, great insight, great examples.&lt;/p&gt;

&lt;p&gt;Ever built a website that has to work on Nokia features phones, 320x240 displays with D-pad navigation running Opera Mini, via unreliable 2G and EDGE data networks? Mike Hall has, and his session, “Lessons from Building for the Bottom of the Web”, was a &lt;em&gt;tour de force&lt;/em&gt; of engineering within some fairly unique constraints: no web fonts, maximum page sizes, optimised SVGs, building custom minification routines for HTML (including replacing spaces with newlines ‘cos that’s the same number of bytes but if your entire web page is on one line Internet Explorer would crash), and the resulting observation that if you build a web app that’s responsive and usable on a Nokia feature phone, it’ll probably be blistering fast and work on literally everything.&lt;/p&gt;

&lt;p&gt;And then Cassie Evans closed out the day in style live-coding (or rather, live-uncommenting) a browser game built around the GSAP animation engine - a lot of code, a lot of laughs, the kind of session that makes you want to go home and open up an editor and play around with all the things in the demo because it just looks like so much fun.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/IMG_1777.JPG&quot; alt=&quot;The Barbican towers at sunset&quot; /&gt;&lt;/p&gt;

&lt;p&gt;By the time we head out, the sky’s cleared and the setting winter sun is catching the curves and contours of the concrete and it’s all so beautiful the only thing to do is go and reflect on it all over a nice glass of something, and so now I’m writing this in the corner of one of those post-event pubs where the acoustics are so bad everybody’s shouting at everybody else because nobody can hear anybody anyway.&lt;/p&gt;

&lt;p&gt;If AI really is going to replace software development with a 24/7 diet of botslop and existential dread, somebody forgot to tell the web folks. State of the Browser 2026 was a celebration of code, craft, open standards, collaboration and transparency, and not a ralph loop in sight.&lt;/p&gt;

&lt;p&gt;I’ll leave you with a call to action. At the end of the day, Bruce Lawson - one of the genuinely excellent people who is working tirelessly to keep the web open and transparent and joyful - pointed us to an ongoing UK government inquiry about requiring Apple and Google to provide better interoperability and access to key features of those vendors’ mobile devices and operating systems.&lt;/p&gt;

&lt;p&gt;They need your help. If you’re building web apps, or mobile apps, or any kind of software that targets Apple mobile devices, and you’d like to see a more robust transparency mechanism than “Apple promises to be nice if you ask nicely”, get involved. Go here, tell them what you’re working on, tell them why things like forcing Apple to allow browser engines other than Webkit on their iOS devices would be a Good Thing:&lt;/p&gt;

&lt;p&gt;👉🏼 &lt;a href=&quot;https://www.gov.uk/government/calls-for-evidence/proposed-commitments-from-apple-and-google-app-certainty-and-interoperable-access&quot;&gt;https://www.gov.uk/government/calls-for-evidence/proposed-commitments-from-apple-and-google-app-certainty-and-interoperable-access&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that’s it for now. I’ll see y’all on &lt;a href=&quot;https://twitch.tv/dylanbeattie&quot;&gt;https://twitch.tv/dylanbeattie&lt;/a&gt; on Monday afternoon, where I’ll be beating Claude Code with a length of pipe until it stops lying to me, and then I’ll be in Malmo on Saturday for &lt;a href=&quot;https://beautyincode.se/&quot;&gt;Beauty in Code 2026&lt;/a&gt;, but right now I’m going to go and drink a very loud pint.&lt;/p&gt;

&lt;p&gt;I might put Eli’s sign on my phone screen first:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/IMG_1783.PNG&quot; alt=&quot;i&apos;m not ignoring you I just can&apos;t hear shit in this bar.&quot; /&gt;&lt;/p&gt;
</description>
          <pubDate>2026-02-28T18:21:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2026/02/28/state-of-the-browser-2026.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2026/02/28/state-of-the-browser-2026.html</guid>
        </item>
      
    
      
        <item>
          <title>Ben Died</title>
          <description>&lt;p&gt;Ben died.&lt;/p&gt;

&lt;p&gt;He wasn’t in a car crash. He didn’t have cancer. He wasn’t old. He didn’t get murdered, or fall off a cliff, or have an anvil dropped on him by a wily roadrunner, do any of the other things that people are supposed to do before they die. He just went to the pub, had a pint, went to bed at an uncharacteristically civilised hour because he had work in the morning, and died. I don’t know if there’s more to it than that. I suspect there isn’t. Would it make any difference if there was? Probably not.&lt;/p&gt;

&lt;p&gt;I found out from a Facebook post. Yep, that website that Mark Zuckerberg built so he and his friends could lech over their classmates, and twenty years later, among the relentless feed of promoted posts and AI slop and who knows what, there’s a post from Yan saying Ben died in his sleep last night. Clare gets a message from Lizzie at the same moment I see the post, which is just as well ‘cos otherwise I would have assumed it was a prank, or somebody’s account got hacked. Those things are normal. Pranks happen. People leave phones unlocked and their mates post stupid shit online for a laugh. Accounts get taken over. Those are things which actually happen. But Ben going to the pub and then going home and dying? That doesn’t happen.&lt;/p&gt;

&lt;p&gt;That was all a few weeks ago, and, if you’ll forgive the indelicate phrasing, life didn’t stop; apart from a few evenings in the pub that I should really have spent preparing for conferences, I did all the things, delivered all the talks, played all the gigs, saw all the people… then yesterday was Ben’s funeral. Redruth. Cornwall. Well, actually in Treswithian, on the outskirts of Camborne, but at first all we had to go on was “Redruth” so we sorted train tickets and hotel rooms accordingly and figured we could work the rest out later… so Thursday I’m travelling home from Stockholm, Friday I’m at home supposedly catching up on work and chores but none of those things happen, Saturday I’m in Bristol to celebrate my brother’s 40th birthday, which is great fun other than the logistics of catching up with family who all have different assumptions about each other’s travel plans, and then it’s Sunday and I’m on another train - not my train, because my train is cancelled, but a different train; for a while it looks like I won’t make my connection in Taunton, but the connecting train is delayed enough that it all works out, and Clare’s already on the train, and then we’re at Redruth, standing outside the station in the kind of rainstorm that would have Noah wondering if his ark is still on Mount Ararat and whether it might be made to float again, and then we’re in a taxi, and then we’re at the hotel, and Chris and Yan and Lizzie are there and Ben isn’t and it’s just… not right.&lt;/p&gt;

&lt;p&gt;I’ve been to funerals before. Not many, and they weren’t unexpected - one of them the deceased wasn’t even deceased yet; they threw a big farewell party before choosing euthanasia a few months later, because in Belgium that’s a thing you can do when the chemotherapy isn’t working any more and it’s only a matter of time.&lt;/p&gt;

&lt;p&gt;But this was the first time I’ve ever felt like the gang’s all got together but somebody’s missing and never coming back and the conversation’s like riding a bike with a wonky gear and it takes a while to work out why every so often there’s a metaphorical ‘clunk’ in the conversation ‘cos that’s the point when Ben would have said something hilarious - and let’s face it, quite probably obscene.&lt;/p&gt;

&lt;p&gt;I’ve no idea what his family and friends made of the motley entourage of black-clad weirdos who showed up on Monday nursing various grades of hangover, but I hope us all being there maybe helped them understand the kind, funny, patient man that their boy had become. There were words, and songs, and music, and tears, and lots of hugs, and then beers and beige food and another rainy taxi ride and a very long train ride back to London, and what was supposed to be a proper night’s sleep in my own bed.&lt;/p&gt;

&lt;p&gt;And now it’s a cold, rainy Tuesday in February and my calendar says I’m ‘back at work’, which, when you’re independent and self-employed, is a nebulous concept at the best of times, but I am so mentally far away from anything that might constitute actual work that I have no idea how I’m going to find my way back there; my bike’s had its fifth puncture in two weeks and my phone seems to be constantly on 5% battery and I’m supposed to be rehearsing songs for the gig next week but I’ve chewed all my fingernails down to the quick and I can’t sing a note without my voice cracking and everything feels like a metaphor for everything else and frankly it’s all just shit.&lt;/p&gt;

&lt;p&gt;Life is weird. Friendship is weird. Friendship as an adult in the age of social media is especially weird. I’d never been to Ben’s flat. I don’t think he ever came to my house. I knew he’d grown up in Cornwall. I didn’t know he had a sister, until this week none of the London gang had ever met his parents, and I suspect he had only the vaguest idea what I did when I wasn’t in the pub – but I’ll never forget those strange months after lockdown, when things were open but not open and everybody was desperate to go out and see people and do things but nobody was getting on a train unless they had to, we’d get on our bikes and cycle up to Little Faith in Deptford Creek and the gang would come along - Ben in shorts, regardless of the weather - and we’d sit outside and eat and drink and talk about Warhammer and movies and weird snacks, and we’d roar with laughter and wobble home feeling for the first time in a long while like maybe it was all going to be alright. If you’d told me in those strange, frightening days that one day we’d be looking back on them with nostalgia, even fondness, I’m not sure I’d have believed you, but like I said: life is weird.&lt;/p&gt;

&lt;p&gt;Rest in peace, my friend.&lt;/p&gt;

&lt;p&gt;We’re gonna miss you.&lt;/p&gt;
</description>
          <pubDate>2026-02-10T22:19:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2026/02/10/ben-died/html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2026/02/10/ben-died/html</guid>
        </item>
      
    
      
        <item>
          <title>Reflections on NDC London and PubConf</title>
          <description>&lt;p&gt;NDC London was the first really big conference I ever went to - as a paying attendee, way back in 2014 (or was it 2013?), when I was trying to figure out ASP.NET MVC and jQuery and how to get all my team’s code out of Subversion and into this new Git thing everybody was talking about.&lt;/p&gt;

&lt;p&gt;I love it. I love welcoming everybody to my home city, I love the atmosphere, I love the location. It’s also where we held the very first &lt;a href=&quot;https://pubconf.io/&quot;&gt;PubConf&lt;/a&gt;, an event that celebrates just how incredibly &lt;em&gt;funny&lt;/em&gt; a lot of conference speakers are - especially when the drinks are free and everybody promises not to post clips on the internet; there is something genuinely delightful about watching a speaker spend an hour artfully deconstructing distributed systems architecture or unit testing strategies in front of a conference audience, and then the next night they’re on a tiny stage in a crowded pub making you laugh until the milk comes out of your nose.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/IMG_1434.JPG&quot; alt=&quot;An empty conference venue, decorated and set up for NDC London 2026 but with no people yet.&quot; title=&quot;NDC London 2026&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I’ve been at NDC London every year since, wearing all kinds of hats both metaphorical and literal, and become the &lt;em&gt;de facto&lt;/em&gt; organiser of PubConf London since Todd Gardner cut down on conference travel a few years ago to focus on growing &lt;a href=&quot;https://trackjs.com/&quot;&gt;TrackJS&lt;/a&gt; and &lt;a href=&quot;https://requestmetrics.com/&quot;&gt;Request Metrics&lt;/a&gt;. With NDC London and PubConf successfully signed, sealed and delivered for another year, here’s a bit of a reflection on it.&lt;/p&gt;

&lt;p&gt;Now please bear in mind, this is my experience - not yours. Unless you are also (a) living in London, (b) teaching a conference workshop, (c) giving a talk, (d) running the attendee party, (e) organising the unofficial after-party on Friday night, (f) playing lead guitar and singing in the band that will be performing at that party, (g) mixing the backing tracks and videos that band is going to use on stage, (h) running the website that handles all the event ticketing and registrations, and (i) flying to Stockholm 72 hours later for another conference, this is not how NDC London was, is, or ever will be for you.&lt;/p&gt;

&lt;p&gt;Think of this more as a glimpse into how Dylan’s link of the proverbial sausage gets made.&lt;em&gt; (I shall leave it as an exercise for the reader to decide whether the story is told from the perspective of the butcher, or the perspective of the pig…)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The thing that makes NDC London different to most other events in my calendar is that it’s a hometown show. I live in London; it’s been home for nearly 25 years. But London isn’t like other places. Not even close. I live about 6 miles away from the QEII conference centre, as the crow flies (although this being London the crow is probably a feral pigeon) - but 6 miles in London is not like 6 miles where you live. The easiest way to explain it is that I probably pass two million people on my way in to Westminster every morning. Think about wherever you live. Draw a line from your house to whatever your nearest analog for “city centre” looks like… now keep going along that line until you’ve gone past two million people. See?&lt;/p&gt;

&lt;p&gt;A typical conference week for me is half  a day of packing - what do I need this time? If I’m running a workshop, I have a bunch of gear I use for that - portable HD screen, HDMI splitter, external keyboard. If I’m playing music at the after-party, I bring one  set of music gear. If I’m doing a show with The Linebreakers, it’s a different set of music gear. Laptop, spare laptop, cables, chargers, clothes, wash bag, passport, pack all the things, head to the airport - and that’s it. The logistics are all front-loaded; once that bag’s been checked in and I’m through security, we’re done; the rest of the week is hotel breakfast, catered lunches, restaurant dinners, housekeeping bringing fresh towels… the actual event itself is often hard work, ‘cos that’s what makes it worthwhile, but the rest runs on autopilot.&lt;/p&gt;

&lt;p&gt;When it’s a hometown show, things run a little different. Tuesday is a workshop day, which means up at 6am, coffee, porridge - always porridge on workshop days ‘cos it keeps my brain running until lunchtime - and then on a train by 7:15, which gets me to Westminster by 8, which is an hour early, but if I leave it any later than that there’s a good chance the Jubilee line trains are all too full to get on. Twenty minutes to set up the laptop, screen, HDMI splitter, keyboard, check the WiFi, find out which websites are being blocked, figure out how to work around them. Workshop 9am-5pm - live coding exercises, demos, examples, Q&amp;amp;A. Wrap up, deal with the day’s admin and reply to emails, then another hour on trains to get home. Unpack, figure out what I need to take in with me on Wednesday, make dinner, do the laundry, sleep. &lt;a href=&quot;https://linebreakers.band/&quot;&gt;The Linebreakers&lt;/a&gt; are playing PubConf on Friday night, so my plan is to take a case full of band gear into the conference venue each day - as much as I can comfortably carry on public transport -  then on Friday shift the whole lot to the PubConf venue in a cab. Wednesday’s an electric guitar and 10kg of effects and cables shoved in my rucksack. Thursday, a 15kg flight case full of microphones and XLR cables. Friday, 25kg wheely case with the mic stands, second guitar, laptop stand, and all the other bits that make the show happen.&lt;/p&gt;

&lt;p&gt;My actual talk is on Wednesday morning - and it’s a new one, so I’ve been up late the night before finessing animations and checking videos. New talks normally get run through a user group a month or so before the conference… but with NDC London always happening at the end of January, Christmas tends to get in the way a bit and there’s not a whole lot of meetups going on in the weeks leading up to it. The talk goes well, although the room’s a little emptier than I expected; maybe it’s just the general dip in conference numbers, maybe it’s just that people don’t want to learn about CSS? But I’m happy with it, and the folks in the room said very positive things.&lt;/p&gt;

&lt;p&gt;Wednesday afternoon is band rehearsal - the downside of having a band who all live in different countries is that we don’t get a whole lot of rehearsal time, so this is where weeks of everybody learning their own parts and playing along to backing tracks finally gels into something approximating a show. It goes well. I skip the speaker dinner in favour of sleep. Sleep is good.&lt;/p&gt;

&lt;p&gt;Thursday I have every intention of going to some talks. It doesn’t happen - the day disappears into a series of the sort of excellent conversations that only happen when the right people are all in the same room, catching up with folks from Particular, and Dometrain, and Twilio, and Umbraco, and old friends from the nerd circuit who I haven’t seen in way too long.&lt;/p&gt;

&lt;p&gt;Thursday evening I’m running the NDC party. Now, I think the social events at conferences are just as much a part of the attendee experience as the talks, but the speakers who do the fun talks and comedy bits at the party? Most of them aren’t giving it the slightest thought until their “proper” session is out of the way - which is absolutely fair enough. This means the party inevitably coalesces out of chaos at about 3pm on Thursday - but you know, after more than a decade of doing this, we are &lt;em&gt;damn good&lt;/em&gt; at coalescing things out of chaos. The party itself is a blast - Alan Smith’s Sonic Pi DJ set is fantastic, there are great comedy bits from Glenn Henriksen, Hannes Lowette, Amy Kapernick, Ash Bzak, Richard Campbell tells the story of Goliath, which I have seen half-a-dozen times and still makes me roar with laughter every time. I dust off my old “How To Succeed in the Enterprise” talk I wrote for PubConf a few years back, which goes down a storm, and we round out with karaoke, which works great in this kind of venue ‘cos you’ve already got a laptop, a huge projector screen, and a couple of handheld mics so there’s nothing much to pack up at the end of the night.&lt;/p&gt;

&lt;p&gt;Home just after midnight (and stone cold sober, because Friday is another day). Dinner was Mexican food at the party (did I mention the food at NDC is always excellent?) but that was a long time ago, so second dinner is a chicken baguette from Greggs in Westminster station, which happens to be open and adjacent at the exact moment I realise I’m starving. Just in case any of you were reading this thinking that the international conference circuit was 100% glitz and glamour.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/BF5C0EC3-66A8-49F5-ABA3-CD044C6E9730_1_105_c.jpeg&quot; alt=&quot;a sweaty man in a high-vis vest and cycle helmet at the top of a very muddy hill.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Friday morning, I take an hour out of conference time to get out on my bike; cycling the five-mile loop to the top of Crystal Palace Park every day this winter has been doing good things for my blood pressure and my brain - not to mention my knees - and as loath as I am to admit it, I actually miss the exercise after three days of conference. This is, of course, the day that I get a puncture right at the top of the hill. I figure it’ll be quicker to fix it and cycle than to wheel my bike all the way home, so I spend fifteen minutes in the cold changing the tyre; cycle home, shower, get dressed, grab my backpack and the 25kg wheely case and head into Westminster again.&lt;/p&gt;

&lt;p&gt;Friday lunchtime is about the point when the folks speaking at PubConf finally start sending me their presentations, so on Friday afternoon all I need to do is transport 50kg of music gear to the pub, schlep it downstairs, figure out their PA system and projector set-up, plug it all in - which would be enough of a pain in the ass if it was on a bench in a well-lit workshop; doing it on the floor in a dimly-lit pub is definitely my least favourite part of the whole enterprise - while simultaneously building the PubConf deck as people send me Dropbox and OneDrive links to MP4 and PPTX files.&lt;/p&gt;

&lt;p&gt;There’s always a list of things we think would be cool - let’s record the show! Let’s get video of the show! - that, by the time we’re set up and sound-checked, we’re already too knackered to think about… and then the audience starts to arrive, and the excitement starts to build, and the room fills up, and there’s a buzz, and I get the last few bits dropped into the presentation deck, and then… showtime.&lt;/p&gt;

&lt;p&gt;With, inevitably, a handful of technical glitches. PowerPoint on macOS decides today’s the day it’s going to play Chris Ayers’ MP4 at three frames per second. It has never done this before. Ever. Sorry, Chris. Computer says meh. A reboot seems to clear it. The XVive wireless monitors that we use in the band, which worked flawlessly during soundcheck (and at every show we’ve done before) - tonight, channel 1 isn’t working. It worked earlier… maybe interference from 75 nerds with at least one WiFi-enabled device each packed into a basement that’s basically a giant Faraday cage? Channel 2 isn’t great either, but hey, we’ve got four more to try… cue some hastily improvised jokes about the history of UK broadcast television and how we don’t want to try Channel 5 in case it all comes out sounding like the Spice Girls - while we’re simultaneously channel-hopping our wireless units to find a channel that works; channel 3 saves the day. In the logistical chaos, I forgot one tiny bit of gear - the thing that clips my phone onto my mic stand, so I can adjust my own in-ear monitor mix during the show. Probably no big deal… right up until the point I realise during “Playing the Planning Poker” that I can’t hear the drum track, and I can’t fix it right away ‘cos it’s hard to get your phone out your pocket when both hands are busy playing the guitar… I make a mental note that it is, in fact, a Big Deal and I probably shouldn’t forget it again.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/WhatsApp Image 2026-01-30 at 23.41.48.jpeg&quot; alt=&quot;a lot of drunk awesome people being awesome&quot; /&gt;&lt;/p&gt;

&lt;p&gt;But the show is a triumph. The PubConf speakers - Chris Ayers, Brandon Minnick, Glenn Henriksen, Chris Simon - and the PubConf singers (yep, that’s a thing now) - Ash Bzak, Helvira Goma, Jo Minney, Damian Brady, David Whitney, William Brander, Jordan Miller, and Arthur Doler - absolutely smash it out of the park; the band plays a couple of new things we’ve not played in London before that go down really well, Rytis does a fantastic job running the sound mix on the venue’s slightly temperamental PA system, and Vagif gets the biggest applause of the night after dropping out of the gig because of a broken shoulder but then deciding to come along and play a couple of tunes anyway.&lt;/p&gt;

&lt;p&gt;There’s a surprise, too… as we’re about to launch into “Enterprise Waterfall”, Hannes stops the show, Chris Ayers approaches the stage with a VERY LARGE BOX, and the gang presents me with the Lego Starship Enterprise - a little something to say thank you for Making The Crazy Things Happen. I’m lost for words. I actually cry a little. It’s perfect. It’s lovely. It’s completely unexpected. &lt;em&gt;(It’s also another 6kg of luggage to take home at the end of the night… but hey, at this point, who’s counting? 🤣)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And then there’s music, and dancing, and laughter, and karaoke, and rye whisky, and single malt scotch and a very expensive cab home, and more scotch, and something approximating sleep, and then a day of bad TV and tactical naps and Chinese food and working on promo materials for the next thing I’m doing, and then a day of unpacking and repacking (and another puncture), and now, less than 72 hours after it all wrapped up, I’m on an aeroplane somewhere over the North Sea on my way to Stockholm, where there’s four inches of snow on the ground, a load of Java developers at one conference, and a load of .NET developers at another conference. The plan is two talks, two dinners, one live music and comedy show, say hello to as many people as I possibly can, give the stone lions a hug, pay my respects to Pub Anchor if I can find the time, and be home in time for the open mic at Ignition on Thursday night.&lt;/p&gt;

&lt;p&gt;Should be fun.&lt;/p&gt;
</description>
          <pubDate>2026-02-02T16:30:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2026/02/02/reflections-on-ndc-london-and-pubconf.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2026/02/02/reflections-on-ndc-london-and-pubconf.html</guid>
        </item>
      
    
      
        <item>
          <title>The Road to Excel</title>
          <description>&lt;p&gt;Chris Rea’s “The Road to Hell” is one of my favourite albums of all time. You probably remember the title track, but the rest of the album is fantastic - great tunes, great production, and some truly phenomenal guitar playing; Chris had a very distinctive hybrid slide playing style, something between Mark Knopfler and Ry Cooder, and it’s all over this record.&lt;/p&gt;

&lt;p&gt;Now, back in 2021, when the news headlines was full of reports that COVID patient data was getting lost because of government departments using Excel as a data interchange format, I recorded a version of Chris Rea’s “The Road to Hell” about… well, about exactly that.&lt;/p&gt;

&lt;p&gt;I never got the guitars quite right - did I mention that Chris was a phenomenally talented guitar player? After a week or two of of trying to figure out the guitar lines, recording and re-recording licks and solos and never quite getting it all to hang together quite right, I shelved it and it’s been sat in a Dropbox folder ever since.&lt;/p&gt;

&lt;p&gt;With the sad news of Chris’ passing this week, I thought perhaps now would be a good time to share it. Not finished, no video, you can hear the rough edges - but here it is.&lt;/p&gt;

&lt;p&gt;Rest in peace, Chris. Thank you for the music.&lt;/p&gt;

&lt;iframe width=&quot;100%&quot; height=&quot;300&quot; scrolling=&quot;no&quot; frameborder=&quot;no&quot; allow=&quot;autoplay&quot; src=&quot;https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/soundcloud%253Atracks%253A2235228599&amp;amp;color=%23ff5500&amp;amp;auto\_play=false&amp;amp;hide\_related=false&amp;amp;show\_comments=true&amp;amp;show\_user=true&amp;amp;show\_reposts=false&amp;amp;show\_teaser=true&amp;amp;visual=true&quot;&gt;&lt;/iframe&gt;
&lt;div style=&quot;font-size: 10px; color: #cccccc;line-break: anywhere;word-break: normal;overflow: hidden;white-space: nowrap;text-overflow: ellipsis; font-family: Interstate,Lucida Grande,Lucida Sans Unicode,Lucida Sans,Garuda,Verdana,Tahoma,sans-serif;font-weight: 100;&quot;&gt;&lt;a href=&quot;https://soundcloud.com/dylanbeattie-1&quot; title=&quot;dylanbeattie&quot; target=&quot;\_blank&quot; style=&quot;color: #cccccc; text-decoration: none;&quot;&gt;dylanbeattie&lt;/a&gt; · &lt;a href=&quot;https://soundcloud.com/dylanbeattie-1/the-road-to-excel&quot; title=&quot;The Road to Excel&quot; target=&quot;\_blank&quot; style=&quot;color: #cccccc; text-decoration: none;&quot;&gt;The Road to Excel&lt;/a&gt;&lt;/div&gt;
</description>
          <pubDate>2025-12-23T15:40:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2025/12/23/the-road-to-excel.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2025/12/23/the-road-to-excel.html</guid>
        </item>
      
    
      
        <item>
          <title>So You Want To Speak At Software Conferences?</title>
          <description>&lt;p&gt;I run a &lt;a href=&quot;https://ldnug.org/&quot;&gt;.NET user group here in London&lt;/a&gt;, and we host a lot of talks from people who are relatively inexperienced presenters. Sometimes they’ve done presentations internally but never spoken before a public audience. Sometimes they’re developers who have been in theatre or played in bands; people with plenty of stage experience but who haven’t presented on technical topics before - and sometimes they’ve never done any kind of public presentations or performance at all. We aim to be a friendly, supportive crowd; public speaking can be daunting, and the first public outing of somebody’s first talk can be… let’s just say that the presenter sometimes learns a lot more than the audience, and leave it at that.&lt;/p&gt;

&lt;p&gt;But it can also be a hugely rewarding experience, and as a seasoned tech presenter who’s been doing this for a while, aspiring speakers often ask me for advice on how to take it to the next level.&lt;/p&gt;

&lt;p&gt;Before we get into the specifics, there are two things to bear in mind.&lt;/p&gt;

&lt;p&gt;One: ask yourself why you want to do this. What does “the next level” mean for you? Are you looking to promote your consultancy, or your training courses, or your software products? Do you want to become a professional speaker and actually get paid to give talks? Are you doing it ‘cos you want to go places and meet people? Figure out what “success” looks like for you.&lt;/p&gt;

&lt;p&gt;Two: be realistic about how much work is involved. It took me &lt;em&gt;seven years&lt;/em&gt; to go from my first user group lightning talk, back 2008, to my first international conference. If you think you can hack together some code, write a talk about it, stick it on Sessionize and three months later you’re on your way to a major international event like NDC or Yow! or Devoxx… well, no. That’s not how this works. Strap in; it’s a long ride.&lt;/p&gt;

&lt;h3 id=&quot;year-1-get-good&quot;&gt;Year 1: Get Good&lt;/h3&gt;

&lt;p&gt;Write the talk. Write a talk nobody else could do; tell a story nobody else can tell. Figure out what your audience is going to learn, and why you’re the best person to teach them that. Then give it at local user group. It might go great. It might be a train wreck. Don’t worry. That’s one of the reasons user groups exist. Learn from the experience. Fix the demos. Fix the slides. If it was too short? Write some more. If it was too long? Cut something. Give it at another user group. Do it again. Do it again. Maybe write a second talk, shop that one around a couple of user groups too.&lt;/p&gt;

&lt;p&gt;If you can’t find user groups, look on Meetup.com. Yes, it’s a horrible platform, but it works; search by topic, search by region, find groups that look like a good match for your content, and ask if they’re looking for speakers. They probably are.&lt;/p&gt;

&lt;h3 id=&quot;year-2-get-seen&quot;&gt;Year 2: Get Seen&lt;/h3&gt;

&lt;p&gt;After user groups and meetups come the community conferences. Typically small, one-day events, with a few tracks, and usually free (or very cheap) to attend. For me, these were &lt;a href=&quot;https://en.wikipedia.org/wiki/Developer!\_Developer!\_Developer!#External\_links&quot;&gt;the DDD events&lt;/a&gt; _(that’s DDD as in Developers! Developers! Developers!, not to be confused with DDD as in Domain Driven Design), _a series of one-day free developer events around the UK, organised by volunteers, usually on a Saturday so people don’t have to take time off work. They bring in a good crowd, they’re a great way to get to know other presenters and people who are involved in tech events, and you’ll almost certainly meet a few people who are on the programme committees for the bigger conferences.&lt;/p&gt;

&lt;p&gt;Events like this are your chance to get noticed. Turn up the day before, join the pre-conference dinner and drinks, introduce yourself. Yeah, it’s awkward when you don’t know anybody. There will be other people there who don’t know anybody and will appreciate you making the effort. Enjoy yourself, but don’t end up doing tequila shots in a karaoke bar at 3am. Not now. You’re there to give a talk, remember?&lt;/p&gt;

&lt;p&gt;Go to the event. Spend the whole day there, do your talk, watch the other sessions. Communicate with the organisers. You don’t want their memorable impression of you to be a half-hour of panic and missed calls because one of their speakers has gone AWOL and nobody knows where they are.&lt;/p&gt;

&lt;p&gt;Figure out how to keep in touch with the people you met. Join the Signal or WhatsApp group chat; if there isn’t one, create one. Follow them on LinkedIn, or Bluesky - be prepared to go where people are; don’t expect folks to join Mastodon just because that’s where you want to talk to them. That’s not how this works. If you really don’t want to play the social media game - and I can’t blame you - there’s always good old-fashioned email. A short email a week later saying “hey, thanks for having me” or “hey, I loved your session at DDD, let’s keep in touch” can pay off in a big way.&lt;/p&gt;

&lt;p&gt;Finally, watch out for events that put video of their sessions online. Having a couple of YouTube links of you doing your thing in front of a live, appreciate audience can make all the difference when a programme committee is looking at a handful of talks and can only accept one of them.&lt;/p&gt;

&lt;h3 id=&quot;year-3-get-accepted&quot;&gt;Year 3: Get Accepted&lt;/h3&gt;

&lt;p&gt;You’ve got a couple of talks. You’ve delivered then enough times that you know they’re good *(and if they’re not good, make them good - or scrap them and write new ones)*. You know people. People know you. If somebody asks “hey, do we know anybody who could do a good session about $topic”, your name comes up. You’ve got a decent network of connections - group chats, LinkedIn, email addresses.&lt;/p&gt;

&lt;p&gt;Now, find all the conferences in your field with an open Call for Papers (CfP), and get submitting. Dave Aronson over at codeasaur.us maintains a really useful &lt;a href=&quot;https://www.codosaur.us/speaking/cfps-ending-soon&quot;&gt;list of CfPs which are closing soon&lt;/a&gt;. Check that regularly. Many events will cover your travel &amp;amp; hotel costs, although with sponsorship budgets drying up right across the industry that’s not as prevalent as it was a few years ago. If not, maybe you can persuade your employer to pay your travel - “hey, boss, if I can get a free ticket to this amazing conference with all these industry experts, do you think the company will pay my air fare &amp;amp; hotel?”&lt;/p&gt;

&lt;p&gt;Lean on your network. What are people submitting to? Which events should you look out for? Which topics are getting a lot of traction (and which topics are not?)&lt;/p&gt;

&lt;p&gt;Keep your content fresh. Write new talks. Keep giving them at user groups and community events.&lt;/p&gt;

&lt;p&gt;Keep your submissions focused. 2-3 talks per event; don’t submit ten wildly different abstracts to the same conference in the hope one of them will get accepted. Every selection committee I’ve been on, if we see that, we assume the presenter hasn’t actually written *any* of them yet and is throwing everything they can think of into the mix and hoping one of them gets chosen. Not a great way to stand out. An open CFP at a big tech conference typically gets 20+ submissions for every available slot, which means if you reduce it to a numbers game, you’re submitting 20 talks for every one that gets accepted. Keep track of the numbers, and be objective about it.&lt;/p&gt;

&lt;h3 id=&quot;year-4-get-bored&quot;&gt;Year 4: Get Bored.&lt;/h3&gt;

&lt;p&gt;It’s great fun doing this for a while… but it’s also exhausting. Some people hit it hard for a few years, do all the things, go to all the places, make a lot of great friends and happy memories, and then wake up one day and decide that’s enough. Some people do a few talks, tick it off their bucket list and decide that’s enough for them. Some settle into a gentle routine of 3-4 events they’ll do every year. And yes, some of us end up treating our calendars like a game of Tetris, juggling flights and trains and hotels and meetups and conferences and spending half the year on the road and the other half writing talks and workshops and all the other things it’s hard to do when you’re at the airport.&lt;/p&gt;

&lt;p&gt;That’s why you gotta figure out ahead of time what “success” looks like. If you’re doing it for fun, remember to have fun - and if you find you’re not enjoying it any more? Stop. If you’re doing it as promotion or marketing? Track your leads. Make sure it’s actually generating the attention and the revenue it’s supposed to. If you’re doing it for money, be mercenary: no pay, no play. Not every event is the same, of course. In a given year I’ll have some events that are fun, some that are lucrative, some that are running alongside workshops or training engagements. Just make sure you know which is which.&lt;/p&gt;

&lt;p&gt;Finally: respect your audience. Whether you’re talking to five people at a meetup, fifty at a community event, or five thousand at a huge international conference: those people are the reason you get to do this. They have given up their time - and often a substantial amount of money - to hear what you have to say. They deserve your best shot, every time. If you find you’re bored, fed up, tired, running talks on autopilot or making mistakes because you just don’t care? It’s time to try something else - and remember, there’s a thousand aspiring speakers out there who would dearly love to take that spot instead of you.&lt;/p&gt;

&lt;p&gt;Now get out there. Work hard, have fun, teach us awesome things, and if you ever want me to look over an abstract or a slide deck, drop me a line - &lt;a href=&quot;mailto:dylan@dylanbeattie.net&quot;&gt;dylan@dylanbeattie.net&lt;/a&gt;. I’d be happy to help.&lt;/p&gt;
</description>
          <pubDate>2025-12-08T16:39:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2025/12/08/so-you-want-to-speak-at-software-conferences.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2025/12/08/so-you-want-to-speak-at-software-conferences.html</guid>
        </item>
      
    
      
        <item>
          <title>Fifteen Electronic Devices... Is That A LOT?</title>
          <description>&lt;p&gt;Every airline’s cabin baggage regulations are subtly different. 10kg plus a personal item. 10kg INCLUDING a personal item. 8kg plus a personal item. 8kg, plus a personal item of up to 2kg. 8kg plus a laptop, umbrella, walking cane OR a personal item up to 2kg. 23kg as long as it’ll fit in the overhead locker &lt;em&gt;(go British Airways!)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For a long while LOT, Poland’s national airline, would let you travel with “up to five dogs, cats, or ferrets, unless they are for commercial purposes”, which is wonderful because the collective noun for ferrets in English is “business”, as in “a business of ferrets”, so technically on LOT you could take a business of ferrets with you, but not a business ferret. Alas, this particular wording has disappeared in a recent update to their website, but I did notice a new regulation that passengers are allowed a maximum of fifteen personal electronic devices (PEDs), defined as anything containing a lithium-ion battery. You know, the ones that are in literally every single device which exists these days but which very occasionally go on fire so airlines are understandably a bit flummoxed about exactly what to do about them.&lt;/p&gt;

&lt;p&gt;It’s not readily apparent &lt;a href=&quot;https://www.lot.com/gb/en/journey/baggage/hazardous-materials#expansion-panel-13-content&quot;&gt;from their website&lt;/a&gt; whether that’s fifteen PEDs per passenger &lt;em&gt;total&lt;/em&gt;, or just in checked baggage (and I didn’t think you could put li-ion batteries in the hold at all, but apparently that’s just for actual spare batteries and powerbanks now, and doesn’t apply to devices containing a battery)… but fifteen’s a lot, right?&lt;/p&gt;

&lt;p&gt;Well… actually, no.  Not if you’re a massive nerd. Here’s my inventory for Build Stuff in Vilnius:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;MacBook Pro laptop&lt;/li&gt;
  &lt;li&gt;Framework Windows laptop&lt;/li&gt;
  &lt;li&gt;iPhone&lt;/li&gt;
  &lt;li&gt;backup iPhone&lt;/li&gt;
  &lt;li&gt;Smartwatch&lt;/li&gt;
  &lt;li&gt;Powerbank&lt;/li&gt;
  &lt;li&gt;Samsung Galaxy tablet&lt;/li&gt;
  &lt;li&gt;XVive in-ear monitoring receiver&lt;/li&gt;
  &lt;li&gt;XVive in-ear monitoring transmitter&lt;/li&gt;
  &lt;li&gt;Wireless guitar transmitter&lt;/li&gt;
  &lt;li&gt;Wireless guitar receiver&lt;/li&gt;
  &lt;li&gt;Anker SoundCore noise-cancelling headphones&lt;/li&gt;
  &lt;li&gt;Sony C100 wireless earbuds&lt;/li&gt;
  &lt;li&gt;JBL portable Bluetooth speaker&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s the standard inventory for any event where I’m doing a technical presentation and playing a show with Linebreakers - fourteen devices! Nice to know I could add a Kindle to that lot and still be within the regulations.&lt;/p&gt;

&lt;p&gt;I note with some amusement that if I replaced the Sony earbuds with Airpods Pro or similar, that would technically be *three* PEDs, since each earbud has its own battery and there is a third battery in the charging case. So better not do that, then.&lt;/p&gt;
</description>
          <pubDate>2025-12-02T13:35:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2025/12/02/fifteen-electronic-devices/.-is-that-a-lot.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2025/12/02/fifteen-electronic-devices/.-is-that-a-lot.html</guid>
        </item>
      
    
      
        <item>
          <title>Is a 100% Discount The Same As “Free”?</title>
          <description>&lt;p&gt;For the last year or two, I’ve been giving a talk at conferences around Europe about free software. It’s called “open source, open mind: the cost of free software”, there’s a couple of recordings of it up on Youtube if you want to check it out, and, like a lot of my conference talks, it’s a mix of history, case studies, and my own experiences working in tech.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/CpI8Wh1V5tM?si=Ja08BEYaxISnCBYi&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;One of the perennial challenges facing the craft of software development is that I think our industry has a tendency to attract people who like to create. I know dozens, probably hundreds, of people who became software developers because they enjoyed the activity of writing code, of sitting down and creating a solution to a problem. I don’t know anybody who got into tech because they enjoy reading code - most of us still don’t; reviewing code is way less fun than writing it. This tendency for software developers to ignore prior art because, well, gosh, it’s great fun reinventing the wheel over and over again - it’s one of the reasons I spend so much time talking about computer history. A few years back somebody was very excitedly telling me about something called a microSPA; a SPA is a single-page application, and a microSPA is a very small single-page application; an individually deployable unit containing HTML, JavaScript, CSS… that’s right, they’d reinvented the web page. And it only took twenty years.&lt;/p&gt;

&lt;p&gt;I think it’s important to talk about the history of free software because I keep seeing the same patterns playing out, over and over again - and every time they do, somebody ends up burning time and brain cycles on solving a problem that we already solved, and I wonder what new and fascinating things they could have created instead.&lt;/p&gt;

&lt;p&gt;I want to talk about two of these patterns in this video. First - let me know if this sounds familiar to you: somebody creates some free software. Free, as in, it doesn’t cost any money. It’s out there on the internet and you can download it and use it and even incorporate it into your work projects - projects that make your company money. And you do, whether that’s by installing a package from NuGet or npm, or connecting to some sort of online API, or cloning their github repo and building it yourself.&lt;/p&gt;

&lt;p&gt;What you don’t see is the flipside of that. The person who created that library? They’re doing - and sharing - that work because at that point in their own life and career, that makes sense to them. Richard Stallman’s Free Software Foundation has popularised the idea that giving away your source code is some sort of lifelong ideological commitment, a fundamental part of somebody’s identity - but really, that’s not how any of this works. What normally happens is somebody’s working in some kind of agency or consultancy, they create a library which gets used internally across multiple projects for different clients, there’s a conversation about making the source code available - which, at that point, makes a lot of sense; clients get that code under a public license, it gives the agency something good to say to candidates when they’re recruiting, and maybe they’ll even get community contributions that add features or fix bugs. Everybody wins.&lt;/p&gt;

&lt;p&gt;Then something changes. Somebody leaves. The agency gets acquired. For whatever reason, the people that used to maintain that project aren’t in a position to do that for free any more - and so the logical thing to do is what a lot of open source projects and packages have done in the last few years: to say to the consumers, the people and companies who are using that code to make money, “hey, in order for me to keep doing this, I’m going to need to get paid somehow. Seeing as donations haven’t worked, and consulting has pulled me away from working on it, I’m going to try charging for the software itself, so the next version won’t be free”.&lt;/p&gt;

&lt;p&gt;This usually splits the community in two. In one camp are the ‘keyboard warriors’, the folks who like to express their outrage on social media; in the other camp are the people who understand software licensing, budgets, compliance - and the cost of replacing or rewriting something which already works. There may be some overlap, but from talking with maintainers who have done exactly this, most of the big corporate users will quietly sign up without making a lot of noise about it, and the people on Twitter and Reddit shouting about betrayal and open-source “rug pulls”… well, they were never going to pay for anything anyway.&lt;/p&gt;

&lt;p&gt;There’s another factor in all this, of course. I think it’s absolutely right that companies that get substantial financial benefit from the use of free and open source software should contribute to the sustainability of those projects. I recently found a thread on X which linked to a &lt;a href=&quot;https://trac.ffmpeg.org/ticket/10341&quot;&gt;bug filed on ffmpeg’s issue tracker in May 2023&lt;/a&gt;, which included this &lt;a href=&quot;https://trac.ffmpeg.org/ticket/10341#comment:4&quot;&gt;absolute barnstormer&lt;/a&gt; of a comment:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Hi, This is a high priority ticket and the FFmpeg version is currently used in a highly visible product in Microsoft. We have customers experience issues with Caption during Teams Live Event. Please help,  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Microsoft. Microsoft Teams. It would be nice to think that Microsoft Teams could come up with a more constructive way of engaging with the maintainers of ffmpeg than expecting a request for free support to be treated as a ‘high priority ticket’, but unfortunately this sort of interaction between large corporations and small open-source project teams isn’t remotely unusual.&lt;/p&gt;

&lt;p&gt;We’re not all Microsoft, though… and that’s where the one-size-fits-all model breaks down pretty quickly. The sort of licensing fees that are a drop in the ocean to big tech companies could be a dealbreaker for a startup that’s just getting off the ground - and for projects which are themselves free-as-in-free-beer, for student and hobby projects; those projects are never going to be able to justify hundreds or thousands of dollars in license fees.&lt;/p&gt;

&lt;p&gt;The problem is that the alternative to paying for a license tends to be “oh well, if we can’t use that one maybe we can just build our own”… which is rarely a good idea in terms of the quality of the software, but also, if you’re at an early stage startup or a small-to-medium business, you should be focusing on your mission, working on the features that are strategic differentiators for your product or service, not spending time building authentication and telemetry and data persistence and all the other stuff that’s already been solved over and over again.&lt;/p&gt;

&lt;p&gt;Now, none of this is a new idea. The first project I know of that switched from a completely free license to something more restrictive was ServiceStack, which switched in 2014 from a BSD license to a hybrid commercial license. Their approach was interesting: free licenses for genuine solo projects, defined as anything where a single person was the sole author, contributor and copyright holder of all the code in that project. They offered free licenses for projects that in turn published their own code under a compatible open source license - and if you didn’t qualify for a free license? $299 per developer for small organisations - fewer than ten employees; $999 for large organisations. That was, and is, a per-developer, one-time, perpetual license - you get a year of maintenance and upgrades, and any new releases during that maintenance period, you can run those releases forever.&lt;/p&gt;

&lt;p&gt;The company I was working for had half-a-dozen developers working with ServiceStack at the time, but we had over fifty employees, so for us upgrading from the BSD-licensed ServiceStack 3 to the new licensing model would have cost six thousand dollars. The budget meeting about that one didn’t get very far - and I get it; although we were using APIs for all kinds of internal stuff, they weren’t a key part of anything that actually generated revenue, and I didn’t know back then about accounting models like Cost of Delay, which might have given us a way to calculate the value of the time we saved by sticking with it instead of switching to a different framework or building our own.&lt;/p&gt;

&lt;p&gt;ServiceStack is a really useful case study, because they did this ten years ago. With a whole load of high-profile projects switching their licensing over the last few years - IdentityServer, Redis, Terraform, and more recently AutoMapper and Mass Transit - there’s been a very predictable reaction; community backlash, a fork of the last version of that product that was published under an open license, and a lot of folks asking what happens next? Well, ServiceStack has gone from strength to strength, shipping a steady stream of new versions and features. It’s a solid, sustainable, commercially viable product. The community fork of the last BSD codebase? That was NServiceKit, which was very exciting for about six months before enthusiasm ran out. It’s now been a decade since the last commit. I assume because its creators realised what every fork will find out sooner or later: if you fork a free project because it’s going commercial, all the people who are happy to pay for licenses, support and maintenance will go with the upstream project, everybody who expects support and maintenance to be free will beat a path to your door, and you’ll be expected to maintain feature parity with the project you forked and do it all for free out of the goodness of your heart.&lt;/p&gt;

&lt;p&gt;Now, one thing I think ServiceStack got wrong was to offer free licenses for small projects, defined as projects with fewer than 10 defined service operations, fewer than 10 mapped database tables, and a limit of 6000 requests per hour on their Redis client when running without a license, those kinds of restrictions. &lt;/p&gt;

&lt;p&gt;I applaud the objective here - hey, use our software while you’re getting started, and pay for it later once you need the extra scale. But I think those kinds of limitations in any kind of software aren’t a great idea, because they put a  financial roadblock in the way of what should be an architectural decision. Imagine you’re working on a project, you’ve got a team of five developers building a new feature and turns out the best way to deliver that feature is going to require mapping one more database table - except, oh crap, we already used our ten free tables. That eleventh table is going to cost you five thousand bucks - and meanwhile, if a billion-dollar Fortune 500 company is only using nine database tables, they’ll still qualify for free licensing.&lt;/p&gt;

&lt;p&gt;There’s a bunch of different ways that other projects have approached this:. &lt;/p&gt;

&lt;p&gt;Duende’s Identity Server, an open source authentication framework for .NET,&lt;a href=&quot;https://duendesoftware.com/products/identityserver#pricing&quot;&gt; has a pricing model&lt;/a&gt; based on the number of client IDs you’re running - there’s a startup license that’s fifteen hundred dollars a year for up to five client IDs, then five hundred a year for each additional client ID up to fifteen, where it switches to the business license, nine thousand dollars for fifteen IDs, everything in the startup license, plus some additional features like automatic key management. They also have a &lt;a href=&quot;https://duendesoftware.com/products/communityedition&quot;&gt;community edition&lt;/a&gt; that’s free for companies with under $1 million of revenue and under $3M of capital.&lt;/p&gt;

&lt;p&gt;Chris Patterson also seems to be &lt;a href=&quot;https://massient.com/#pricing&quot;&gt;going with something like this for MassTransit&lt;/a&gt;, with a free tier for organizations and nonprofits with up to $1 million of revenue or expenses&lt;/p&gt;

&lt;p&gt;Just so we’re clear, a million dollars sounds like a lot of money - and yeah, to the vast majority of the people living on this planet, it’s a life-changing amount of money - but when you’re a tech start-up, a million bucks really doesn’t go very far. Hire some engineers, bring in the hardware and infrastructure for them to do their jobs, pay your lawyers, your insurance, your rent … and it’s gone.&lt;/p&gt;

&lt;p&gt;I think this is why we’re seeing some projects raising that a bit higher. For example, Jimmy Bogard set the level for his &lt;a href=&quot;https://automapper.io/#pricing&quot;&gt;community edition of AutoMapper&lt;/a&gt; to be free for companies up to $5 million of revenue and $10 million of capital. &lt;/p&gt;

&lt;p&gt;I think the original version of these various free offerings was Microsoft’s BizSpark programme way back in 2008, which provided free and heavily discounted access to Microsoft developer tools and Azure hosting to startups for the first two years of operation - the idea being, I assume, that within two years either the startup is generating enough revenue to pay for those services, or it’s done what most startups do and quietly shut down because it ran out of money; that programme has evolved over the years and today is called Microsoft for Startups, and - this being 2025 - offers free AI and Azure credits to companies which are just starting out.&lt;/p&gt;

&lt;p&gt;Particular also just put out &lt;a href=&quot;https://particular.net/blog/launching-small-business-program&quot;&gt;their version of this for NServiceBus&lt;/a&gt;, using the more generic term of “financials” to cover revenue, capital, and expenses. They set up this “sliding scale” which starts with the same 100% discount for organizations up to a million USD of financials, just like Duende and MassTransit; but when you go past that, you don’t start paying full price right away. Instead, the discount goes down just a bit to 90%, and passing two million it’s reduced to 80%, at three million it’s 60%, at four million it’s 20%, and only when you pass five million dollars you pay full price.&lt;/p&gt;

&lt;p&gt;Now, full disclosure: I know a lot of the folks at Particular, we’ve collaborated on a few things over the years, they gave me a heads-up that they’d be announcing this, and they know what’s in this post ‘cos I sent them the draft before I published it, so this isn’t an unfiltered reaction piece… but in another way, it kinda is, because I think this is fantastic.&lt;/p&gt;

&lt;p&gt;First of all, it’s an interesting new take on what everybody else has been doing. Second, it solves a real problem for growing companies - you’re not going to suddenly find out one day that your last quarter accounts have pushed you over a threshold and you’ve gone straight from zero to full cost. And third, for existing small businesses using NServiceBus, they’re about to get a nice discount on their NServiceBus bill.&lt;/p&gt;

&lt;p&gt;Another detail I think is worth mentioning was that all of these projects excluded “priority” support from their free and discounted offerings - which I think is entirely justified. Good support takes time and expertise, meaning that it can usually only be done by the project maintainers - the very people whose time is stretched thin and are struggling to get to financial sustainability. That’s why I don’t think anybody who’s getting the benefits of fully-discounted licensing should expect to get the same degree of support as the organisations that are paying full price.&lt;/p&gt;

&lt;p&gt;Point is, there are organisations with money - whether that’s revenue, assets, or investment - and not just the big brands, but the countless mid-sized banks, insurance companies, and even government entities. They can, and they should, be paying for the stuff they use. These are the last people who are going to complain about “rug pulls”. Professional developers in these contexts clearly explain to their bosses and clients, “so, this thing used to be free, and we used it a bunch, and from the next version it will cost this much. Replacing it with something else that’s free will take roughly this many hours. What do you want us to do?” A rational boss, or client, will tend to accept a reasonable license cost rather than incur the risk of a rip-and-replace; as Nick Chapsas said in a &lt;a href=&quot;https://youtu.be/5K1NiGtUrhw?t=364&quot;&gt;recent video&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;You sort of should assume you take a risk by just using something that’s free out there. There’s no contract [covering] … what’s going to happen in the future.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But for all of those organisations that don’t have that kind of money; the startups, student projects, open source, small independent agencies, not to mention the large parts of the world where local market economies operate on an entirely different scale - these discounts can be just what they need to continue to build upon solid foundations, meaning they wouldn’t need to change anything when the open-source projects they use commercialize - so no “rug pull” here either.&lt;/p&gt;

&lt;p&gt;A bunch of us have been talking about how we can take these things forward and enable more open-source projects to achieve sustainability. We’ve seen how standard OSS licenses like MIT and BSD have helped accelerate legal acceptance of open-source software, so we think some kind of standardization around commercial terms and discounts could be helpful. What exactly that would look like is still being discussed, but my hope is that by putting out this video describing some of these approaches, the next OSS maintainer will think to themselves, “yeah, I can do this too”. &lt;/p&gt;

&lt;p&gt;One final thing I’d like to add: I see occasional online comments about projects “going closed source”… well, that’s just not true. Every single one of the projects and companies I’ve mentioned in this video remains committed to sharing the full source code for all their products, so that developers who are relying on that code can download it, build it, step through it in the debugger if they need to - that’s a big part of “open source”; the ability to obtain the source code, study it, modify it if you need to, and even maintain your own fork of it if you don’t want to rely on the original vendor. All we’re saying here is that if you’re using these projects in a commercially significant context, you should expect to pay something towards their maintenance and future development - and if some sort of scalable discount model makes it easier to reach that point, I think that’s a good thing for everybody involved.&lt;/p&gt;
</description>
          <pubDate>2025-11-17T14:15:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2025/11/17/is-a-100-discount-the-same-as-free.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2025/11/17/is-a-100-discount-the-same-as-free.html</guid>
        </item>
      
    
      
        <item>
          <title>Choosing the Right Path</title>
          <description>&lt;p&gt;&lt;a href=&quot;https://www.linkedin.com/in/srijan-dev/&quot;&gt;Srijan Singh&lt;/a&gt; pinged me on LinkedIn earlier today with a question:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I’m in my early career as a backend dev at IBM, and something I often wonder about is choosing the right path: whether to stick with a big company and learn deeply, or test myself in a startup environment (remote vs in-person adds to the mix too). Since you’ve navigated such diverse experiences, I’d love to hear how you’d approach that decision early on.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I quite enjoy getting these kinds of questions, but as &lt;a href=&quot;https://www.hanselman.com/blog/do-they-deserve-the-gift-of-your-keystrokes&quot;&gt;Scott Hanselman wrote way back in 2010&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt; If you email someone one on one, you’re reaching that one person. If you blog about it, you get the message out on the web itself and your keystrokes travel farther and reach more people. Assuming you want your message to reach as many people as possible, blog it. You only have so many hours in the day.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So here’s my answer to Srijan’s question, as a blog post.&lt;/p&gt;

&lt;p&gt;Let’s start with the simple answer, which is how I &lt;em&gt;did&lt;/em&gt; approach that decision early on… I didn’t, really. I was lucky enough that I didn’t really have to think about it. I loved working with computers, writing code, and building websites, and if I was getting paid money to do that I was happy. I landed my first graduate role in 2000, via one of my housemates who was working as a recruiter; three years later one of our clients, Spotlight, asked me to work for them full-time; fifteen years after that, I got head-hunted by Skills Matter to become their CTO; they went bankrupt in 2019, I set up my own company, &lt;a href=&quot;https://ursatile.com/&quot;&gt;Ursatile&lt;/a&gt;, and I’ve been independent ever since. To put this another way: I’ve had exactly two job interviews in my life, and one of those was to stack shelves in a grocery store when I was sixteen years old.&lt;/p&gt;

&lt;p&gt;That probably doesn’t help, right? 😆&lt;/p&gt;

&lt;p&gt;Looking back over 25 years of working in software, though, I think there are three things that matter:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;The problem&lt;/strong&gt;. Are you helping people? Are you making somebody’s day better? Are you building something that’ll make your end users happier? Or is your entire working life about making the CEO’s stock go up another point?&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;The people&lt;/strong&gt;. Who are you working with? Are they competent? Are you learning from them? Do you enjoy their company? Do they respect your ideas?&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;The process&lt;/strong&gt;. How does stuff actually get done? Everything from prioritisation and planning to what text editor you use. I enjoy working with some technology because it makes it easy for me do good work. I do not enjoy working with some other technology, because it makes it hard for me to do good work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don’t need all three. I’ve had bits of my career when I’ve been working with awesome groups of smart, kind people, using great tech to solve cool problems. I’ve also had bits of my career when I’ve been using painful tech to solve pointless problems, but the people made it worthwhile - and, occasionally, periods where I honestly didn’t care for the people or the problem, but was learning so much along the way that I probably stuck around longer than I should.&lt;/p&gt;

&lt;p&gt;Think about longevity. You’re building solutions, relationships, and skills. Will the things you’re building last for a decade, or will they be obsolete by the end of the year? Can you take those things with you out into the wider world, or do they only have value in the context of your current role?&lt;/p&gt;

&lt;p&gt;Remember to take stock once in a while. You might find the job that used to tick all three boxes is down to two, and then one… and when you’re down to one, you’re in danger, ‘cos if that one disappears too… well, congratulations. You’re stuck using tech you don’t enjoy to solve problems you don’t care about with people you don’t like.&lt;/p&gt;

&lt;p&gt;Now, obviously there’s a huge amount of context to consider here. The tech jobs market is the worst I’ve ever seen; more of my dev friends are out of work right now than at any point since the first dotcom bubble, 25 years ago. If you’re supporting a family or paying off debts and you have a steady paycheque, maybe just keep your head down, keep doing what you do, wait for things to pick up. But if you’re early in your career, you’ve got a bit of financial security and you’re unhappy with how you’re spending your working days? Do something different. Bored of working in the enterprise? Find a startup. If you’re burning out working in a startup, get a comfortable, predictable job in a big company. If you’re remote, and tired of being at home all the time, find a job with a nice office; if the commute is making you miserable, try remote. When you try something, give it at least a year - but if you’ve lasted a summer and a winter, learned the ropes, learned the industry, and you’re still miserable? Probably time to move on.&lt;/p&gt;

&lt;p&gt;Really, though, the important question when it comes to career progression isn’t “where do I see myself in five years”, it’s “am I going to quit my job today?” That’s the question that actually makes things happen. If the answer’s “hell, no, I’m happy”? Awesome. Go to work. If the answer’s “yes, today’s the day!” - well, good luck with it.&lt;/p&gt;

&lt;p&gt;But if the answer’s “I don’t know”? Well, time to gather some more data.&lt;/p&gt;
</description>
          <pubDate>2025-09-02T08:56:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2025/09/02/choosing-the-right-path.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2025/09/02/choosing-the-right-path.html</guid>
        </item>
      
    
      
        <item>
          <title>On The Road Again...</title>
          <description>&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/ivyU6rw7dVM?si=goEt4vV4B5l5lSD4&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;I’ve spent most of the summer so far writing a new course; if you &lt;a href=&quot;https://bsky.app/profile/dylanbeatt.ie&quot;&gt;follow me over on BlueSky&lt;/a&gt; - and you should - then you’ll have noticed I’ve been posting - skeeting? bleeting? I still don’t know what to call posting on BlueSky. Let’s go with bleeting. I’ve been &lt;a href=&quot;https://bsky.app/profile/dylanbeatt.ie/post/3lwjl6xmam22u&quot;&gt;bleeting about CSS&lt;/a&gt; a lot, because my new course is CSS for Software Engineers. The video course is going to be available on Dometrain later this year, I’ll be running it as a workshop at various conferences, and I have a funny feeling there might even be a book at the end of it… but that’s taking up pretty much all my time, not to mention brain capacity, and hasn’t left a whole lot of time for YouTube videos and stuff.&lt;/p&gt;

&lt;p&gt;But, it’s nearly September (yeah, really), which means it’s nearly conference season again, and I’m going to be bouncing around Europe for the next few months talking about all kinds of things - machine learning, text encoding, Rockstar, open source, and, yep, CSS.&lt;/p&gt;

&lt;p&gt;Now, here’s the thing. When folks like me get invited to speak at these kinds of events, the organisers really appreciate it when we make a bit of noise about it, post it on social media, blog about it, make videos… and I get it; marketing is tough, every bit of coverage might help shift a few more tickets, and the algorithms that drive social media apparently &lt;em&gt;love&lt;/em&gt; short form portrait videos… but, y’know, I’m forty seven years old, I listen to Bon Jovi and I still don’t really understand what TikTok is for; somebody like me making a video on my phone about a .NET developer conference and hoping it’ll go viral is… I believe they call it “cringe”.&lt;/p&gt;

&lt;p&gt;But then I remember a few years ago I was in Tartu, in Estonia, where I was giving the keynote at &lt;a href=&quot;https://digit.dev/&quot;&gt;digit.dev&lt;/a&gt;, and somebody recognised me in the street from my YouTube videos and we got chatting… turns out they’re a developer, they love code, they really liked my conference talks, and they had no idea that I was the keynote speaker at a multitrack international conference taking place right in the city where they live. And Tartu’s not a big place; it’s got about 100,000 people.&lt;/p&gt;

&lt;p&gt;So here’s the deal. I’m going to tell you where I’m going, on the off-chance that you’re local, and you have some training budget left, because it would really suck if there was  an awesome event happening right there in your home town and you didn’t know about it.&lt;/p&gt;

&lt;p&gt;If you want a whole “ten reasons you can’t afford to miss OpenTechDevConDays 2025”… well, nah. Not really my jam. They have marketing people for that.&lt;/p&gt;

&lt;p&gt;For what it’s worth, I do try to make sure that every event I’m involved with &lt;strong&gt;is&lt;/strong&gt; excellent; I put a huge amount of work into preparing talks and workshops, I bring stickers to give away, and I try to make sure I have as much time as I can to meet people, hang out and chat about what you’re all working on.&lt;/p&gt;

&lt;p&gt;So here’s where I’m going to be. I’m obviously not expecting any of you to come along to all of these - although I’ll be very impressed if you do - but if I’m going to be in your neighbourhood, and if you can persuade your company to buy a ticket, then come along and say hi.&lt;/p&gt;

&lt;p&gt;I did mention I’ll be giving away Rockstar stickers, right?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;September 8-12th&lt;/strong&gt; I’ll be at &lt;a href=&quot;https://ndccopenhagen.com/&quot;&gt;NDC Copenhagen&lt;/a&gt;, where I’m running a brand new workshop about all the amazing things modern CSS can do. I’ll be giving a talk about algorithms and why they’re not as scary as they look, and probably eating quite a lot of barbecue at Warpigs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;September 25th&lt;/strong&gt; I’ll be Edinburgh at &lt;a href=&quot;https://www.scotlandis.com/scotsoft-2025/&quot;&gt;ScotSoft 2025&lt;/a&gt; talking about plain text: weird and wonderful stories about teletype machines, Hungarian Scrabble, how emojis actually work, and what “PIKE MATCHBOX” has to do with driving in the Soviet Union.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;October 3rd&lt;/strong&gt; I’ll be in Dordrecht in the Netherlands with Fronteers; that one’s a one-track evening conference, so should be a lot of fun; we haven’t finalised all the details for that one yet, but keep an eye on &lt;a href=&quot;https://www.fronteers.nl/&quot;&gt;https://www.fronteers.nl/&lt;/a&gt; for more news.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;October 9th + 10th&lt;/strong&gt;, I’ll be in Riga for &lt;a href=&quot;https://www.zabbix.com/events/zabbix_summit_2025&quot;&gt;Zabbix Summit&lt;/a&gt;. Zabbix is an open source observability and monitoring platform, so the event’s all about infrastructure, automation and devops; I’m going to be talking about the history and future of free software and how we might be able to create a truly sustainable model for open source development.&lt;/p&gt;

&lt;p&gt;Then I’m in Portugal for two weeks; &lt;strong&gt;October 13-16&lt;/strong&gt; is the &lt;a href=&quot;https://azuredevsummit.com/&quot;&gt;Azure Developer Summit&lt;/a&gt; in Lisbon, a brand new event organised by Microsoft in partnership with NDC and Techorama, all about Azure, AI, .NET, C# - and then &lt;strong&gt;20-24 October&lt;/strong&gt; it’s &lt;a href=&quot;https://ndcporto.com/&quot;&gt;NDC Porto&lt;/a&gt;, which has a different format this year; instead of one-hour conference sessions it’s all hands-on workshops. I’m doing two days about modern CSS, plus a session about building a ray tracer in JavaScript, and another one about how to build your own programming language using C# and .NET and parsing expression grammars.&lt;/p&gt;

&lt;p&gt;Then I’m home for two days, then a Eurostar to Rotterdam and on to Utrecht, where by a happy coincidence &lt;a href=&quot;https://techorama.nl/&quot;&gt;Techorama&lt;/a&gt; is in town the same week as &lt;a href=&quot;https://tweakers.net/plan/4476/tweakers-developers-summit-2025-earlybirdtickets-en-eerste-aankondigingen.html&quot;&gt;Tweakers Summit&lt;/a&gt;. **&lt;em&gt;*Monday 27th October&lt;/em&gt;*** I’ll be at Techorama with a one-day &lt;a href=&quot;https://techorama.nl/workshops/introduction-to-distributed-systems-with-net/&quot;&gt;Introduction to Distributed Systems with .NET&lt;/a&gt; workshop, Tuesday 27th I’ll at Techorama in the morning and then heading to DeFabrique to close the Tweakers Summit, then I’m back at Techorama on Wednesday.&lt;/p&gt;

&lt;p&gt;And then November 5-7 I’m at &lt;a href=&quot;https://oredev.org/&quot;&gt;Øredev&lt;/a&gt; in Malmö, where I’ll be telling the story of how I built a Rockstar interpreter in web assembly using C#, and rocking the party stage with &lt;a href=&quot;https://linebreakers.band/&quot;&gt;The Linebreakers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Oh, and if you’re thinking “wow, that’s not nearly enough travelling, this guy isn’t even trying any more”, I’m also going to the &lt;a href=&quot;https://www.euroblast.net/en/home/&quot;&gt;Euroblast festival in Cologne&lt;/a&gt; at the end of September, and heading to Yorkshire for the Whitby Goth Weekend at the end of October. Not performing or anything, just going along to watch bands and have fun. You know. Like a normal person.&lt;/p&gt;

&lt;p&gt;So that’s, what, nine tech events - and two music festivals - in seven countries in two months. It’s going to be . Come along and say hi.&lt;/p&gt;

&lt;p&gt;Right now, though, I gotta get back to writing about how CSS flexbox actually works, which turns out to involve way more mathematics than you might think.&lt;/p&gt;
</description>
          <pubDate>2025-08-20T13:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2025/08/20/on-the-road-again/html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2025/08/20/on-the-road-again/html</guid>
        </item>
      
    
      
        <item>
          <title>CSS Has 239 Different Ways To Make Something Blue</title>
          <description>&lt;p&gt;I’m making a new video course for Dometrain at the moment, and it’s all about CSS - one of the three pillars of the open web, along with HTML and JavaScript. I love CSS, I’ve been working with it literally since it was invented, but I absolutely understand why so many developers don’t enjoy working with it.&lt;/p&gt;

&lt;p&gt;CSS has had to incorporate conventions and standards from a wider range of disciplines than any other mainstream technology. Modern CSS incorporates ideas from mechanical typesetting dating back centuries, conventions around information design from hundreds of years of printing and publishing, fifty years of innovations from digital publishing and computer graphics, twenty years of getting stuff wrong on the web while we were still figuring out how to get it right - right up to things like how to account for the “dynamic island” on the latest iPhone handsets.&lt;/p&gt;

&lt;p&gt;To say it’s accumulated a few idiosyncrasies along the way would be an understatement. Yesterday, while putting together the module about how the various colours models work in CSS, I found myself wondering how many  different ways there are in modern CSS to give a box a blue background.&lt;/p&gt;

&lt;p&gt;I got to 239. &lt;a href=&quot;https://dylanbeattie.net/miscellany/blues.html&quot;&gt;Two hundred and thirty nine different ways&lt;/a&gt; to say “the box is blue”.&lt;/p&gt;

&lt;p&gt;You’ve probably heard of named colours (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;color: blue;&lt;/code&gt;), and hex codes (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;color: #0000ff&lt;/code&gt;). Hex codes can also be written as three digits (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#00f&lt;/code&gt;), four digits (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#00ff&lt;/code&gt;), and eight digits (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#0000ffff&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Then there’s the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rgb()&lt;/code&gt; function, which can take each colour component as a decimal or as a percentage. There’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hsl()&lt;/code&gt;, which takes hue, saturation, and lightness, along with a bunch of new colour models - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hwb()&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lab()&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lch&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;oklab()&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;oklch()&lt;/code&gt; - all of which also take a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hue&lt;/code&gt; component.&lt;/p&gt;

&lt;p&gt;Except &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hue&lt;/code&gt; in CSS is an angle - the colour’s position on a colour wheel - and CSS already has a unit system in place for angles, that’s used for rotations and transformation, so the colour models just reuse that. Which means you can write the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hue&lt;/code&gt; component as degrees (with or without &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;deg&lt;/code&gt;),  radians, gradians &lt;em&gt;(the cursed unit. 400 grads in a circle. Why does it even exist? I don’t know.)&lt;/em&gt;, or turns. So if any colour specification involves hue, there are five different ways to write it.&lt;/p&gt;

&lt;p&gt;Add in two different ways to specify alpha transparency - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/ 100%&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/ 1.0&lt;/code&gt; - and  you end up with well over two hundred different ways to say “the box is blue”… and this is just using the modern CSS colour syntax; we’re not even getting into the legacy &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rgb()&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hsl()&lt;/code&gt; function syntax, their aliases &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rgba()&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hsla()&lt;/code&gt;, or any of the wonderful things you can do with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;calc()&lt;/code&gt; and relative colours.&lt;/p&gt;

&lt;p&gt;OK, let’s be fair here. If the language designers hadn’t reused CSS angular units for hue, we’d be 
looking at more like 40, maybe 50 syntax variants. Pick a lane for how you want to specify transparency, you’re down to 20 or so. You’re very unlikely to use the LAB or LCH colour models in production unless you know exactly what you’re doing, so that’ll chop it down further; in reality, you’re not going to encounter more than handful of these variants, and the vast majority of sites out there just use hex codes for everything.&lt;/p&gt;

&lt;p&gt;But, y’know, if you’ve got the whole chaotic evil vibe going on, why not spec all your colours as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;oklch&lt;/code&gt;, lightness and chroma as arbitrary decimals, hue in grads, and give everything an alpha channel
that’s randomly a percentage or a decimal, even for colours which are fully opaque?&lt;/p&gt;

&lt;p&gt;I think you’ll all agree that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;color: blue;&lt;/code&gt; looks kinda dumb, but &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;color: oklch(0.452 0.31 292grad / 100%);&lt;/code&gt; is clearly the work of a genius.&lt;/p&gt;

&lt;p&gt;Check out the whole list over at &lt;a href=&quot;https://dylanbeattie.net/miscellany/blues.html&quot;&gt;https://dylanbeattie.net/miscellany/blues.html&lt;/a&gt;&lt;/p&gt;
</description>
          <pubDate>2025-07-30T09:15:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2025/07/30/css-has-239-different-ways-to-make-something-blue.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2025/07/30/css-has-239-different-ways-to-make-something-blue.html</guid>
        </item>
      
    
      
        <item>
          <title>Naming Things is Hard... Renaming Things is Harder</title>
          <description>&lt;p&gt;You know those little things that you just sort of ignore, over over and over again, until finally one day the planets align and you’re like “No! I’m not putting up with this any more! There &lt;em&gt;must&lt;/em&gt; be a way to fix it!”&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;images/canon-eos-m200.jpg&quot; alt=&quot;canon-eos-m200&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Welcome to  my cameras. I have two Canon EOS M200 cameras, which I use for streaming, recording training videos, teaching online workshops, all kinds of stuff. One is black, one is white, they are otherwise identical. They’re both connected to my main PC using Elgato Cam Link 4K HDMI capture devices, which for the remainder of this article I will call &lt;strong&gt;camlinks&lt;/strong&gt; because they don’t have a better generic name.&lt;/p&gt;

&lt;p&gt;They’re also connected using micro-USB, which means I can use Canon’s Remote Shooting utility to control things like the F-stop, white balance, and ISO. The cameras themselves are permanently mounted around my desk — one is above my main screen, the other one’s built in to a teleprompter — so it’s kinda hard to fiddle with the settings menu.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;images/my-image.png#block&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The problem is that when I go to fire up the EOS utility, it asks me to pick which camera I’m using… am I using the Canon EOS M200, or the Canon EOS M200? And then there’s the fact that both of the dongles show up in Windows as “Cam Link 4K”, which means setting up video sources in programs like OBS Studio normally involves picking the wrong camera at least once.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;images/obs-studio-duplicate-cam-link-names.png&quot; class=&quot;block&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Well, this morning I had that fateful nerd thought… &lt;em&gt;“I should fix this. How hard can it be?”&lt;/em&gt; Well, grab your razor and strap in, friends… we’re going yak shaving!
Now, most of the time, if you want to rename a thing in Windows, you right-click it, and choose “Rename”, and give it a new name.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;images/windows-device-manager-with-duplicate-device-names.png#block&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;That doesn’t work in Device Manager. I mean, sure, rename a file, but why would &lt;em&gt;anybody&lt;/em&gt; ever need to rename a &lt;em&gt;camera?&lt;/em&gt;
OK, no big deal. Those names have got to come from somewhere. Probably the registry. So I fire up regedit, Ctrl-F, “Cam Link 4K”, hit “Find Next”:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;images/regedit-search-cam-link-4k.png#block&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And I wait, and wait, and wait a bit, and then I get bored because this is a Ryzen 9950X3D with 128Gb of RAM and I didn’t spend that kind of money so I could sit around and wait for things, dammit!&lt;/p&gt;

&lt;p&gt;I tried writing a Powershell script to do the same thing — trawl the registry, look at all the keys and subkeys and entries and values — but it did that infuriating thing where it takes so long to not do anything that it wasn’t clear after 30 seconds whether the program worked, but hadn’t matched anything, or I’d screwed up the matching logic.&lt;/p&gt;

&lt;p&gt;Ok, much better approach: let’s dump the entire system registry to a text file and then I can edit it properly. File, Export, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;registry.txt&lt;/code&gt;, takes about a second. Done.&lt;/p&gt;

&lt;p&gt;I now have a 540Mb text file full of bits of Windows registry. It looks like this:&lt;/p&gt;

&lt;div class=&quot;language-ini highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;[HKEY_LOCAL_MACHINE\SYSTEM\DriverDatabase\DriverPackages\termbus.inf_amd64_7ccf415b3c0cf753\Descriptors\TI_COMPAT_DEVICE]&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;&quot;Configuration&quot;=&quot;TS_INPT_DEVICE.NT&quot;&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;&quot;Manufacturer&quot;=&quot;%msft%&quot;&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;&quot;Description&quot;=&quot;%ts_inpt_device.devicedesc%&quot;&lt;/span&gt;

&lt;span class=&quot;nn&quot;&gt;[HKEY_LOCAL_MACHINE\SYSTEM\DriverDatabase\DriverPackages\termbus.inf_amd64_7ccf415b3c0cf753\Descriptors\TS_BUS]&lt;/span&gt;

&lt;span class=&quot;nn&quot;&gt;[HKEY_LOCAL_MACHINE\SYSTEM\DriverDatabase\DriverPackages\termbus.inf_amd64_7ccf415b3c0cf753\Descriptors\TS_BUS\TS_INPT]&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;&quot;Configuration&quot;=&quot;TS_INPT_BUS.NT&quot;&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;&quot;Manufacturer&quot;=&quot;%msft%&quot;&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;&quot;Description&quot;=&quot;%ts_inpt_bus.devicedesc%&quot;&lt;/span&gt;

&lt;span class=&quot;nn&quot;&gt;[HKEY_LOCAL_MACHINE\SYSTEM\DriverDatabase\DriverPackages\termbus.inf_amd64_7ccf415b3c0cf753\Strings]&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;&quot;msft&quot;=&quot;Microsoft&quot;&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;&quot;ts_inpt_device.devicedesc&quot;=&quot;Remote&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;Desktop&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;Input&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;Device&quot;&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;&quot;ts_inpt_bus.devicedesc&quot;=&quot;Remote&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;Desktop&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;Input&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;Bus&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;Enumerator&quot;&lt;/span&gt;

&lt;span class=&quot;nn&quot;&gt;[HKEY_LOCAL_MACHINE\SYSTEM\DriverDatabase\DriverPackages\termkbd.inf_amd64_b0e97bc9e5ad246d]&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;&quot;Version&quot;=hex:ff,ff,09,00,00,00,00,00,6b,e9,36,4d,25,e3,ce,11,bf,c1,08,00,2b,&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;  &lt;span class=&quot;err&quot;&gt;e1,03,18,00,c0,17,1d,14,c2,c9,01,7e,04,f4,65,00,00,0a,00,00,00,00,00,00,00,&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;  &lt;span class=&quot;err&quot;&gt;00,00&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;&quot;Provider&quot;=&quot;Microsoft&quot;&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;&quot;SignerScore&quot;=dword:0d000003&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;&quot;FileSize&quot;=hex(b):70,34,01,00,00,00,00,00&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;&quot;StatusFlags&quot;=dword:00000112&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;@=&quot;termkbd.inf&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Each chunk is in this format:&lt;/p&gt;

&lt;div class=&quot;language-ini highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;[HKEY_LOCAL_MACHINE\SYSTEM\Path\To\Registry\Key]&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;&quot;Name&quot;=&quot;String&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;Value&quot;&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;&quot;AnotherName&quot;=dword:12345678&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;&quot;ThirdName&quot;=hex(b):aa,bb,cc,dd,ee,ff,11,22&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So I want to find every chunk that contains any entry whose value contains &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;Cam Link 4K&quot;&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;Canon EOS M200&quot;&lt;/code&gt;, and then extract:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The first row of that chunk, which is the path to the registry key I need to edit&lt;/li&gt;
  &lt;li&gt;The entry itself&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then — in theory — I’ve got a handful of registry entries in a file, which I can edit in VS Code or something, import it into regedit, and presto! rename all the things.&lt;/p&gt;

&lt;p&gt;First idea: pull it into VS Code, replace all the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\n&lt;/code&gt; with a marker &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__EOL__&lt;/code&gt; so I get every block on its own line, delete all the lines that don’t contain &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;Cam Link 4K&quot;&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;Canon EOS M200&quot;&lt;/code&gt;, turn all the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__EOL__&lt;/code&gt; back into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\n&lt;/code&gt;, and then do the rest by hand.&lt;/p&gt;

&lt;p&gt;Yeah… VS Code won’t do that.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;images/vs-code-file-is-too-large-to-perform-a-replace-operation.png#block&quot; alt=&quot;image-20250726170311811&quot; /&gt;&lt;/p&gt;

&lt;p&gt;TextPad wouldn’t do it either…&lt;/p&gt;

&lt;blockquote class=&quot;bluesky-embed&quot; data-bluesky-uri=&quot;at://did:plc:tr4fmad7fpxeyaw26ky4esys/app.bsky.feed.post/3luubl42orc2h&quot; data-bluesky-cid=&quot;bafyreidacemb5flajbdozv2ac7ga4vuh4zgk2es4t6jo5wbgzm6oow6qcu&quot; data-bluesky-embed-color-mode=&quot;system&quot;&gt;&lt;p lang=&quot;en&quot;&gt;But it&amp;#x27;s OK. We solved this problem. We solved it BEFORE I WAS BORN. The beardy wizard people who created Unix knew all about editing files that wouldn&amp;#x27;t fit in memory, because they built Unix for computers that had an ENTIRE MEGABYTE of RAM... which you had to share with the rest of the university.&lt;/p&gt;&amp;mdash; Dylan Beattie (&lt;a href=&quot;https://bsky.app/profile/did:plc:tr4fmad7fpxeyaw26ky4esys?ref\_src=embed&quot;&gt;@dylanbeatt.ie&lt;/a&gt;) &lt;a href=&quot;https://bsky.app/profile/did:plc:tr4fmad7fpxeyaw26ky4esys/post/3luubl42orc2h?ref\_src=embed&quot;&gt;26 July 2025 at 10:42&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://embed.bsky.app/static/embed.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;Yep, this is a job for awk. You probably don’t know what awk is. Awk is a stream-based editing utility originally created for Unix. So I asked Copilot to write me an awk script. &lt;em&gt;(what, you think I can remember how to write awk? University was a long time ago, friends…)&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-awk highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kr&quot;&gt;BEGIN&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;kc&quot;&gt;RS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;\n\n&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;# Blank lines separate records (blocks)&lt;/span&gt;
	&lt;span class=&quot;kc&quot;&gt;ORS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;\n\n&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# Output blocks separated by two newlines&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;~&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;/Cam Link 4K || $0 ~ /&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Canon&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;EOS&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;M200&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;lines&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;\n&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;nx&quot;&gt;output&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;lines&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lines&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;~&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;/Cam Link 4K/&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;lines&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;~&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;/Canon EOS M200/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;nx&quot;&gt;output&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;output&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;\n&quot;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;lines&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
			&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;output&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Yeah, that’s what awk looks like. Remember, this is the language that Larry Wall looked at, went “well, golly,I can do better than &lt;em&gt;that&lt;/em&gt;”, and… invented Perl.&lt;/p&gt;

&lt;p&gt;Of course, it didn’t work. Me &amp;amp; my little electric copilot buddy had missed two rather significant details… first, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RS&lt;/code&gt; record separator? We’re trawling a file created by Windows regedit. It’s using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\r\n&lt;/code&gt;, not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\n&lt;/code&gt;. Easy fix; change `RS = “\r?\n\r?\n” `&lt;em&gt;(and look, now it’ll work cross-platform if I ever need to awk the Windows registry on macOS!)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Still doesn’t work… because the Windows Registry Editor’s Export feature creates text files that are encoded as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UTF16-LE&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;awk&lt;/code&gt; don’t do &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UTF16&lt;/code&gt;. So I use VS Code to save the registry file as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UTF-8&lt;/code&gt;, and off we go…&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gawk &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; filter.awk registry.txt &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; devices.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;It works!&lt;/strong&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;devices.txt&lt;/code&gt; now has a little registry snippet for every single chunk of registry that includes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;Canon EOS M200&quot;&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;Cam Link 4K&quot;&lt;/code&gt;…&lt;/p&gt;

&lt;blockquote class=&quot;bluesky-embed&quot; data-bluesky-uri=&quot;at://did:plc:tr4fmad7fpxeyaw26ky4esys/app.bsky.feed.post/3luudsqop7c2h&quot; data-bluesky-cid=&quot;bafyreifk5zuj6hq76wtys4553s26tsgvulsj3krey7rerebasrkddscpoi&quot; data-bluesky-embed-color-mode=&quot;system&quot;&gt;&lt;p lang=&quot;en&quot;&gt;&amp;quot;Canon EOS M200&amp;quot; appears 25 times in the registry.

&amp;quot;Cam Link 4K&amp;quot; appears 60 times - and the exact entry &amp;quot;FriendlyName&amp;quot;=&amp;quot;Cam Link 4K&amp;quot; accounts for 41 of those... crikey.

That&amp;#x27;s a lot of times.&lt;/p&gt;&amp;mdash; Dylan Beattie (&lt;a href=&quot;https://bsky.app/profile/did:plc:tr4fmad7fpxeyaw26ky4esys?ref\_src=embed&quot;&gt;@dylanbeatt.ie&lt;/a&gt;) &lt;a href=&quot;https://bsky.app/profile/did:plc:tr4fmad7fpxeyaw26ky4esys/post/3luudsqop7c2h?ref\_src=embed&quot;&gt;26 July 2025 at 11:22&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://embed.bsky.app/static/embed.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;OK, let’s figure out which one goes where.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I am assuming at this point that nothing in Windows is stupid enough to actually open, connect, etc. devices based on a field called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FriendlyName&lt;/code&gt;. If I’m wrong, things are about to get extremely hilarious indeed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So I use Textpad’s really handy “sequence replace” feature, that’ll let you use a regex to find something and then include an incrementing sequence number in the replacement expression:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;images/textpad-search-replace.jpg#block&quot; alt=&quot;Textpad Search and Replace&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The result is a registry file where every &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FriendlyName&lt;/code&gt; value is now unique - so I can in theory reboot, see which names appear in which drop-down menus and dialogs, and then edit them accordingly. In theory.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Portable Devices\Devices\USB#VID_04A9&amp;amp;PID_32EF#9&amp;amp;39F7FE61&amp;amp;0&amp;amp;3]
&quot;FriendlyName&quot;=&quot;Canon EOS M200 UNIQUE12&quot;

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Portable Devices\Devices\USB#VID_04A9&amp;amp;PID_32EF#A&amp;amp;D9BD236&amp;amp;0&amp;amp;3]
&quot;FriendlyName&quot;=&quot;Canon EOS M200 UNIQUE13&quot;

[HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Class\{eec5ad98-8080-425f-922a-dabf3de3f69a}\0005]
&quot;FriendlyName&quot;=&quot;Canon EOS M200 UNIQUE14&quot;

[HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Class\{eec5ad98-8080-425f-922a-dabf3de3f69a}\0009]
&quot;FriendlyName&quot;=&quot;Canon EOS M200 UNIQUE15&quot;

[HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Enum\USB\VID_04A9&amp;amp;PID_32EF\9&amp;amp;39f7fe61&amp;amp;0&amp;amp;3]
&quot;FriendlyName&quot;=&quot;Canon EOS M200 UNIQUE38&quot;

[HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Enum\USB\VID_04A9&amp;amp;PID_32EF\a&amp;amp;d9bd236&amp;amp;0&amp;amp;3]
&quot;FriendlyName&quot;=&quot;Canon EOS M200 UNIQUE39&quot;

[HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Enum\USB\VID_0FD9&amp;amp;PID_0066&amp;amp;MI_00\a&amp;amp;1b1e3ad0&amp;amp;0&amp;amp;0000]
&quot;FriendlyName&quot;=&quot;Cam Link 4K UNIQUE40&quot;

[HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Enum\USB\VID_0FD9&amp;amp;PID_0066&amp;amp;MI_03\a&amp;amp;1b1e3ad0&amp;amp;0&amp;amp;0003]
&quot;FriendlyName&quot;=&quot;Cam Link 4K UNIQUE41&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Add the first line to the file otherwise regedit rejects it:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Portable Devices\Devices\USB#VID_04A9&amp;amp;PID_32EF#9&amp;amp;39F7FE61&amp;amp;0&amp;amp;3]
&quot;FriendlyName&quot;=&quot;Canon EOS M200 UNIQUE12&quot;

...and so on...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Import…&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;./images/registry-editor-successful-import.png#block&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Reboot… IT WORKED!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;images/cam-link-unique-device-names.png#block&quot; alt=&quot;image-20250726172549272&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Well, it worked mostly. At first, the Canon EOS Utility Launcher didn’t pick it up at all, and it turns out some of the Canon EOS utilities read the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DeviceDesc&lt;/code&gt; value from the registry, not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FriendlyName&lt;/code&gt;, so I edited that one too… at this point I got into a good half-hour of editing something, rebooting, seeing what had changed, edit again, reboot again, turn it off and on again… and quite a few moments where I thought I’d tried everything I could think of, including a reboot, and it still hadn’t worked…&lt;/p&gt;

&lt;p&gt;And then, one final, glorious reboot, and there it was.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;images/image-20250726172814992.png#block&quot; alt=&quot;image-20250726172814992&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;images/image-20250726173048019.png#block&quot; alt=&quot;image-20250726173048019&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And you know what?&lt;/p&gt;

&lt;p&gt;I will bet money, good, solid, chunky cash dollar money, that at some point, somebody at Canon said “er… boss, what happens if somebody buys two of the same camera, and has them both plugged in at the same time over USB?” and somebody’s boss said words to the effect of “stop derailing the planning meeting with your stupid edge cases, Chris, we have work to do and you’re not helping.”&lt;/p&gt;

&lt;p&gt;I see you, Chris.&lt;/p&gt;

&lt;p&gt;I see you, and I salute you. 🫡&lt;/p&gt;

&lt;p&gt;Oh, and Microsoft: if we could get right-click, Rename… in the Device Manager? That’d be, like, just &lt;em&gt;swell.&lt;/em&gt;&lt;/p&gt;
</description>
          <pubDate>2025-07-26T15:49:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2025/07/26/naming-things-is-hard-renaming-things-is-harder.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2025/07/26/naming-things-is-hard-renaming-things-is-harder.html</guid>
        </item>
      
    
      
        <item>
          <title>The Subtle Art Of Deprecating API Endpoints</title>
          <description>&lt;p&gt;I had an app fail in production the other day. Not seriously - only affected a couple of admin screens - but it failed because Hubspot had deprecated some of their API endpoints. (That’s nerd speak for “we were using a thing and they turned it off.”)&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://developers.hubspot.com/changelog/breaking-change-removed-support-for-referencing-custom-object-types-by-base-name&quot;&gt;https://developers.hubspot.com/changelog/breaking-change-removed-support-for-referencing-custom-object-types-by-base-name&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sure, they announced in October 2024 that this particular endpoint was being deprecated. Only problem is… I didn’t see the announcement, because I didn’t even start working on this integration until April 2025 - in fact, I’d never worked with Hubspot’s API at all prior to April 2025. I followed their docs, built the integration points I needed, tested it all… and I somehow managed to build and test an integration against an endpoint which was already scheduled for deprecation, without ever having the faintest clue.&lt;/p&gt;

&lt;p&gt;Wouldn’t it be nice if, the day they decide something’s going to get switched off, that feature was no longer available to any new customers? Sure, the folks who were already using it before the announcement; makes sense to give &lt;em&gt;them&lt;/em&gt; six months or whatever to update their code. But seems a bit odd to me that they’d offer completely new integrations access to a feature they already know is going to shut down soon.&lt;/p&gt;

&lt;p&gt;..then again, maybe I’m just salty ‘cos I don’t like it when other people break my stuff. I guess the lesson is to always assume that every single API request you make might randomly start returning an HTTP 400, at any point, for no good reason, and engineer around that as best you can. Fallback caching actually kept the thing running for at least a week after the endpoint in question was deprecated, which I’m kinda happy about - but I could also have wired it to actually tell somebody if it had been running on cached data for more than 24 hours.&lt;/p&gt;
</description>
          <pubDate>2025-07-16T13:46:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2025/07/16/the-subtle-art-of-deprecating-api-endpoints.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2025/07/16/the-subtle-art-of-deprecating-api-endpoints.html</guid>
        </item>
      
    
      
        <item>
          <title>We Miss You, Tommy Vance</title>
          <description>&lt;p&gt;In 1990, BBC Radio 1 broadcast the “Monsters of Rock” festival live from Donington Park. Thunder, Quireboys, Poison, Aerosmith, Whitesnake - and before, between, and after the bands, the broadcast featured live commentary and interviews from Tommy Vance, Mick Wall roaming the backstage area talking to the stars who were in attendance, and Richard Skinner’s 4-part history of the Monsters of Rock. Free to air. I know this, ‘cos I recorded the whole thing onto a stack of TDK D90 tapes and listened to it on repeat for the better part of the next decade.&lt;/p&gt;

&lt;p&gt;Today, five million people paid £25 each to watch the live stream of Ozzy Osbourne’s farewell show with Black Sabbath, broadcast live from Villa Park in Birmingham. That’s over a hundred million pounds in revenue, just from the live stream… so, of course, between the acts, the stream cut to a professional studio, where experienced presenters talked about the history and significance of the show, interviewed the stars who were performing live… no, of course it didn’t. Instead, we got 15-second cameraphone clips of Sabbath fans all over the world talking about how much they loved the band, interspersed with fundraiser clips from the various charities the day was nominally supporting.&lt;/p&gt;

&lt;p&gt;The coverage of the live acts was impeccable, but the interludes made the whole thing feel like one of those “live in my living room” streams everybody was doing back in 2020, and… well, I can’t help thinking that if Tommy Vance was still around, he wouldn’t have let that happen on his watch. But he’s not, and I didn’t realise until today that maybe when TV died, the best part of rock’n’roll broadcasting died with him.&lt;/p&gt;
</description>
          <pubDate>2025-07-06T01:15:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2025/07/06/we-miss-you-tommy-vance.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2025/07/06/we-miss-you-tommy-vance.html</guid>
        </item>
      
    
      
        <item>
          <title>Could HTTP 402 be the Future of the Web?</title>
          <description>&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/NCI-hV7OZWo?si=518VJlmRENiHCZRV&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;When Tim Berners-Lee first created HTTP, way back in the early 1990s, he included a whole bunch of HTTP status codes that seemed like they might turn out to be useful. The one everybody knows is 404 Not Found - because as human being, browsing around the web, you see that one a lot. Some of them are basically invisible to humans: every time you load a web page, there’s actually an HTTP 200 OK happening behind the scenes, but you don’t see that unless you go looking for it. Same with various kinds of redirects - if the server gives back an HTTP 301, it’s saying “hey, the page you wanted has moved permanently; here’s the new address”; a 307 Temporary Redirect is saying “that’s over there right now but it might be back here later so don’t update your bookmarks”, and a 304 is saying to your browser “hey, the version of the page that’s in your cache is still good; just use that one”.&lt;/p&gt;

&lt;p&gt;If, like me, you’ve spent a lot of time building web apps and APIs, you’ve probably spent hours of your life poring over the HTTP specifications looking for the best response code for a particular situation… like if somebody asks for something which they just deleted, should you return a 404 Not Found, or a HTTP 410 Gone - “hey, that USED to be here but it’s gone and it isn’t coming back”&lt;/p&gt;

&lt;p&gt;And along the way, you’ve probably noticed &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/402&quot;&gt;this status code&lt;/a&gt;: &lt;strong&gt;HTTP 402 Payment Required&lt;/strong&gt;. This code has been there since the dawn of the world wide web, but, to quote the Mozilla Developer Network: “the initial purpose of this code was for digital payment systems, however this status code is rarely used and no standard convention exists”.&lt;/p&gt;

&lt;p&gt;Well, that might be about to change. Earlier this week, the folks over at &lt;a href=&quot;https://blog.cloudflare.com/introducing-pay-per-crawl/&quot;&gt;Cloudflare announced&lt;/a&gt; they’re introducing something called “pay per crawl” - enabling content owners to charge AI crawlers for access.&lt;/p&gt;

&lt;p&gt;Let’s put that in context for a second. For the vast majority of commercial websites out there, their ability to make money is directly linked to how many humans are browsing their site. Advertising, engagement, growth, subscribers - however you want to slice it, if you want to monetize the web, you probably want humans looking at your content. It’s not a great model - in fact, it’s mostly turned out to be pretty horrible in all kinds of ways - but, despite multiple valiant attempts to develop better ways to pay creators for content using some sort of microtransactions, advertising is the only one that’s really stood the test of time.&lt;/p&gt;

&lt;p&gt;Then AI comes along. By which, in this context, I specifically mean language models trained on publicly available web data. I’ve published a transcript of this video on my blog. If somebody comes along in a couple of weeks and Googles “dylan beattie cloudflare pay per crawl”, Google &lt;em&gt;could&lt;/em&gt; provide an AI-generated summary of this article. Or, like a lot of folks out there, that person might skip Google completely and just ask ChatGPT what Dylan Beattie thinks about pay per crawl - and they’ll get a nice, friendly - and maybe even accurate - summary of my post, and so that person never makes it as far as my website.&lt;/p&gt;

&lt;p&gt;That is a tectonic shift in the way commercial web publishing works. For nearly thirty years, search engines have been primarily about driving traffic to websites; the entire field of SEO - search engine optimisation - is about how to engineer your own sites and your own content to make it more appealing to engines like Google and Bing. Now, we’re looking at a shift to a model where AI tools crawl your site, slurp up all your content, and endlessly regurgitate it for the edification of their users - and so the connection between the person who’s writing the thing, and the person who’s reading the thing, is completely lost.&lt;/p&gt;

&lt;p&gt;For folks like me, who enjoy writing for humans, that’s just sad. For writers who rely on human web traffic to earn their living, it’s catastrophic… and that’s what makes Cloudflare’s proposal so interesting: they’re proposing a way to charge crawlers to read your content.&lt;/p&gt;

&lt;p&gt;Now, websites have historically relied on something called a robots.txt file to control what search engines can and can’t see… but it’s advisory. The web was designed to be open. Robots.txt is like leaving all your doors and windows wide open and putting a sign on the lawn saying “NO BURGLARS ALLOWED”, and it’s just one of the many, many ways in which the architects of the web were… let’s say optimistically naïve about how humans actually behave. Which is maybe understandable, given that CERN, the nuclear research centre on the French/Swiss border where Tim Berners-Lee invented the World Wide Web, isn’t renowned for being a hotbed of unscrupulous capitalism.&lt;/p&gt;

&lt;p&gt;So you had a choice: you make your content wide open and ask the robots to play nice, or lock it away behind a paywall so that only paying subscribers can see it. Which, of course, means the robots can’t see it, so your site never shows up in Google, so we invented all kinds of clever ways to create content that was accessible to search engines but asked human visitors to register, or sign in, or create an account…&lt;/p&gt;

&lt;p&gt;Now, in theory, Cloudflare’s proposal is pretty simple. If your website is hosted behind a Cloudflare proxy - and according to Backlinko, of the world’s ten thousand busiest websites, 43% of them use Cloudflare, so that’s a LOT of websites - then when an AI crawler comes asking for content, you can reply with an HTTP 402 Payment Required, and give them a price - and if the crawler wants to pay, they try again, and include a crawler-exact-price header indicating “yes, I will pay that much” - this is reactive negotiation. Alternatively, there’s proactive negotiation, where the crawler says “hey, I’ll give you five bucks for that web page” and Cloudflare says “Yeah, sure!” - or if you’ve told your site that page costs ten bucks, the crawler gets an HTTP 402 Payment Required, and they’re free to make a better offer or go crawl somewhere else.&lt;/p&gt;

&lt;p&gt;Incidentally, folks, I’m anthropomorphising here. Crawlers are software. They don’t ask, they don’t want to pay, they don’t agree. Even “crawling” is a metaphor. We’re talking about configuration settings and binary logic in a piece of software that makes network requests. Sure, it makes a better story if you’ve got this picture in your head of some sort of creepy-crawly insect knocking on doors and haggling over prices, but it is just code. Don’t get too attached to the metaphor.&lt;/p&gt;

&lt;p&gt;Anyway. That’s the model. Sounds simple, apart from two tiny little details… how do you know the thing making the requests is an AI crawler, and who handles the money?&lt;/p&gt;

&lt;p&gt;The first part is being done using a proposal called Web Bot Auth, based on two drafts from the Internet Engineering Task Force, the IETF: one, known as the directory draft, is a mechanism for allowing crawlers and web bots to publish a cryptographic key that websites can use to authenticate those bots, and the second, the protocol draft, is a mechanism for using those keys to validate individual HTTP requests.&lt;/p&gt;

&lt;p&gt;That way, anybody running a web crawler can create a cryptographic key pair and register that key pair with Cloudflare - “hey, we’re legit, our crawler will identify itself using THIS key, here’s the URL where you can validate the key, and by the way, here’s where you send the bill”.&lt;/p&gt;

&lt;p&gt;And that’s the second part: Cloudflare is proposing to aggregate all of those requests, charge the crawler account, and distribute the revenue to the website owners whose content is being crawled. Cloudflare acts as what’s called the Merchant of Record for those transactions, which should make it all much more straightforward when it comes to things like taxation.&lt;/p&gt;

&lt;p&gt;Let’s be realistic here. The technical bits of this are not that complicated. They’re built using existing web standards and protocols. The financial elements of the proposal are far more complex, but this is Cloudflare, a company that probably understands the relationship between internet traffic, billing and international revenue models better than anybody.&lt;/p&gt;

&lt;p&gt;There’s one big question that isn’t addressed in their post: what stops AI bots just pretending to be regular humans using regular browsers, and bypassing the whole pay per crawl thing? I’m guessing that, this being Cloudflare, AI bot detection is one of the things they’re quite good at… but publishers also have the option now of putting everything behind some kind of paywall; humans have to sign in, and bots have to validate. There’s also  no indication as to what sort of amounts they have in mind - beyond the fact their examples are in US dollars, as opposed to, say, micros, which are a standard currency unit in Google’s payment API that’s worth one millionth of a US dollar. But I guess capitalism will figure that out.&lt;/p&gt;

&lt;p&gt;Folks, I have to be honest. I’ve been working on the web since it was invented, and this is the first thing I’ve seen in a long, long time that is genuinely exciting. Not necessarily at face value - I don’t care &lt;em&gt;that&lt;/em&gt; much about Cloudflare making AI bots pay to crawl websites. No, what’s exciting is that if Cloudflare goes all-in on this, this could be a big step towards a standard model, and a set of protocols, for monetising automated access to online content - even if neither Cloudflare nor AI is involved.&lt;/p&gt;

&lt;p&gt;Imagine a decentralised music streaming service, where the artists host their own media and playback apps negotiate access to that media via a central broker that validates the app requests and distributes the revenue. Playback costs ten cents; if an AI wants to ingest, mash up and remix your music? Fifty bucks. Or a local news sites that can actually make money out of covering local news… how much would you pay to know what’s &lt;em&gt;actually&lt;/em&gt; going on with all those sirens and smoke down the end of the High Street, from an experienced reporter who is actually on the scene asking questions, as opposed to somebody in an office recycling stuff they read on social media?&lt;/p&gt;

&lt;p&gt;And the fact that the proposal is based around 402 Payment Required, something that’s been part of the web since the days before Google, Facebook, something that’s older than Netscape and Internet Explorer? That just makes me happy. It reminds me of the web back in the 1990s, when the protocols and proposals were all still new, and exciting, and it seemed like there was no limit to what we’d be able to build with them. And yeah, perhaps I’m being overly optimistic… but y’know, looking around at the state of the world, and the web, these days, maybe we could all use a little optimism.&lt;/p&gt;
</description>
          <pubDate>2025-07-04T22:27:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2025/07/04/could-http-402-be-the-future-of-the-web.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2025/07/04/could-http-402-be-the-future-of-the-web.html</guid>
        </item>
      
    
      
        <item>
          <title>The Linebreakers Video Macro</title>
          <description>&lt;p&gt;Anybody who’s seen the Linebreakers live knows that backing videos are a vital part of the show - partly ‘cos that’s where all the drums come from, ‘cos it’s way easier taking a Macbook on a plane than travelling with a drum kit, and partly ‘cos having all the lyrics up on screen makes it much easier for the audience to keep up with the jokes, and there are a lot of jokes.&lt;/p&gt;

&lt;p&gt;Those backing videos are all played from PowerPoint. It works, it’s reliable, and having the entire show inside a single 5Gb PPTX file means it’s trivial to run it from a spare laptop if something goes wrong. Every slide contains a single video clip, most of which start and end with a fade from/to black - so at the end of each song I’m staring at a laptop screen showing me that the last song is finished, and the next song is… black screen. So I rely on the speaker notes area to show me which song is on each slide, and updating this by hand is incredibly tedious and error-prone… so I wrote a macro for it. It’ll scan every slide in the deck, look for the embedded video clips, and replace the slide notes with the clip name (which is the filename) of the current and the next slide.&lt;/p&gt;

&lt;p&gt;In Visual Basic for Applications, no less.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-vba&quot;&gt;Sub UpdateSlideNotes()
  For Each Slide In ActivePresentation.Slides
    ThisName = &quot;(no video)&quot;
    For Each Shape In Slide.shapes
      If Shape.Type = msoMedia And Shape.MediaType = ppMediaTypeMovie Then
        ThisName = Shape.Name
      End If
    Next
    Slide.NotesPage.Shapes(2).TextFrame.TextRange.Text = &quot;THIS: &quot; &amp;amp; ThisName
    If Not LastSlide Is Nothing Then
        Set Notes = LastSlide.NotesPage.shapes(2).TextFrame.TextRange
        Notes.Text = Notes.Text &amp;amp; vbNewLine &amp;amp; vbNewLine &amp;amp; &quot;NEXT: &quot; &amp;amp; ThisName
    End If
    If (ThisName &amp;lt;&amp;gt; &quot;&quot;) Then Set LastSlide = Slide
  Next Slide
End Sub
&lt;/code&gt;&lt;/pre&gt;
</description>
          <pubDate>2025-06-25T10:11:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2025/06/25/the-linebreakers-video-macro.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2025/06/25/the-linebreakers-video-macro.html</guid>
        </item>
      
    
      
        <item>
          <title>What The Fork Is Going On?</title>
          <description>&lt;p&gt;I got two emails recently that have weighed heavily on my mind.&lt;/p&gt;

&lt;p&gt;One was from a restaurant booking service called The Fork. On Saturday afternoon, I had used their service to try to book a table for dinner at &lt;a href=&quot;https://www.garlicandshots.com/&quot;&gt;Garlic &amp;amp; Shots&lt;/a&gt;, a legendary rock’n’roll bar in Stockholm. I tried calling them first: no answer, but hey, they’re probably just not open yet, and there’s a link on their website to reserve a table. 10 people, 7pm, email, phone number, confirm… “your reservation is pending”.&lt;/p&gt;

&lt;p&gt;I hate “pending”. You can’t do anything with pending. “Hey, is there a dinner plan?” “Yeah. Or maybe no. A reservation is pending.” I suspect even Schrödinger’s cat would baulk at dinner being in some sort of unresolved quantum state.&lt;/p&gt;

&lt;p&gt;OK, the place doesn’t open until 5pm, maybe they’ll confirm it then. 5pm comes and goes, no confirmation. I give them a call. A recorded voice says “Welcome!” - in Swedish - and then disconnects the call. This happens three times. There is no option in The Fork app to talk to a human, make a phone call, or anything of that nature. Finally I figure it’s not that far away, I get on the metro, head over, and talk to an actual human. They’ve been very busy: Iron Maiden has just played two arena shows in Stockholm, the city has been wall-to-wall metal fans for three days, and they’ve basically drunk all the beer in every rock bar in town - but no problem; they can take us for dinner. Might be 14 people, might be 8pm rather than 7pm… no problem at all. We have a delightful evening there. &lt;em&gt;So. Much. Garlic.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Sunday 15th June, around 11am, I’m on the train from Stockholm to Helsingborg, and I get an email from The Fork confirming my restaurant booking: 10 people, 7pm, on Saturday 14th.&lt;/p&gt;

&lt;p&gt;I do not know what kind of mind is required to develop, test, launch, and support a service that will confirm a restaurant reservation the day after the meal has taken place. I also strongly suspect that the proprietors of Garlic &amp;amp; Shots couldn’t honestly give half a microbollocks what The Fork is doing, but at some point some smooth-talking sales person probably said “no, it’s fine, we’ll handle all your reservations for you” and they just went “ok, whatever, now buy a drink or get out of my bar” and now it’s just a thing.&lt;/p&gt;

&lt;p&gt;The next email was from His Majesty’s Revenue and Customs, informing me that there was an &lt;strong&gt;important message&lt;/strong&gt; in my online tax account. They cannot, of course, tell me what the message &lt;em&gt;says&lt;/em&gt;, or what it’s about, or even include a link directly to it. “Security reasons”.&lt;/p&gt;

&lt;p&gt;An “important message” could actually be important. Not important like “there has been an update to the Netflix Terms &amp;amp; Conditions” important. Important like “you owe us money; pay up or go to jail” important.&lt;/p&gt;

&lt;p&gt;So I open a browser, go to HMRC’s website, sign in with my Unique Taxpayer Reference, go through the multifactor authentication, go into my tax account, and sure enough, there’s an important message. I have a new tax statement. OK, cool. What does it say?&lt;/p&gt;

&lt;p&gt;Oh, no, we can’t tell you that:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Your new Self Assessment statement has been prepared. You’ll be able to view it online within 4 working days.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So there you go. Two emails. One confirming a restaurant reservation that already happened two days earlier, the other telling me that I need to sign in to a website to see another message telling me that the thing they actually want to tell me isn’t ready yet but I should keep checking back because it’ll be there within four working days.&lt;/p&gt;

&lt;p&gt;All you folks out there who think that coding is the hard part, and AI is going to revolutionise software development because it’ll replace the slow, unreliable human programmers with artificial intelligence that cranks out millions of lines of bug-free code in seconds? No. These emails didn’t get sent at the wrong time because of bugs in the code, or because scheduling email delivery is a hard problem and the developers couldn’t quite get it working in time. No, sending email to the right person, at the right time, is a solved problem. It’s been solved for &lt;strong&gt;decades&lt;/strong&gt;. But choosing what that right time &lt;em&gt;is&lt;/em&gt;? Identifying the situation where maybe asynchronous communication and pending reservations isn’t actually the best way to solve a problem? No, those require actual human intelligence, which is rapidly closing in on astatine’s long-held status as the rarest naturally occurring element on planet Earth.&lt;/p&gt;

&lt;p&gt;I got two stupid pointless emails because somewhere behind them there are stupid pointless humans making stupid pointless decisions. Not programmers. In fact, most of the programmers I know would take one look at the ticket asking them to send an email saying “hey, your new tax statement is ready but you can’t read it yet” and go straight back to the product owner saying “um… how about we wait until the statement is actually available and &lt;em&gt;then&lt;/em&gt; send the email?”&lt;/p&gt;

&lt;p&gt;On the other hand, any startups out there using AI to replace their product owners and keeping all their human developers? Give me a call, I’ll come work for you. It would be &lt;em&gt;lovely&lt;/em&gt; to be able to provide a bit of additional context and get a feature request instantly updated to something more sensible without causing a major political incident in the process.&lt;/p&gt;

&lt;p&gt;Oh, and if you don’t know how to make sure email gets sent to the right person at the right time, I do that too. I made an entire course about it: &lt;a href=&quot;https://dometrain.com/course/from-zero-to-hero-sending-email-with-dotnet/&quot;&gt;Sending Email with .NET: From Zero to Hero&lt;/a&gt;, it’s on Dometrain, and they’re having a summer sale right now so it’s 30% off.&lt;/p&gt;
</description>
          <pubDate>2025-06-16T16:58:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2025/06/16/what-the-fork-is-going-on.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2025/06/16/what-the-fork-is-going-on.html</guid>
        </item>
      
    
      
        <item>
          <title>On The Fungibility of Trains</title>
          <description>&lt;p&gt;When’s a train not a train?&lt;/p&gt;

&lt;p&gt;I’m on my way from Antwerp to Budapest, via Amsterdam Schipol airport, on the delightfully fast and comfortable service that’s now called Eurostar but is still quite clearly a Thalys train with “Eurostar” painted on it.&lt;/p&gt;

&lt;p&gt;Except I’m on the wrong train. I have a ticket - and a seat reservation - for the 13:30 departure to Schipol, so when a Eurostar train headed for Schipol pulled into platform 22 at Antwerp at 13:27, I went to board it. Except… no. This isn’t that train. This is train 9327. My ticket is for train 9333. There’s a Eurostar to Schipol every hour… and this one is now running exactly one hour late.&lt;/p&gt;

&lt;p&gt;So I smiled very politely and asked if, since I had a ticket for the train that was leaving at 13:30 and I had a plane to catch, could I possibly use that ticket to board the train that was actually leaving at 13:30, and the train conductor agreed that this would probably be alright (and found me an empty seat - go Eurostar!)&lt;/p&gt;

&lt;p&gt;The vast majority of trains in this part of the world, you don’t reserve a seat: you just buy a ticket to your destination, and it’s valid on every train headed that way. Whereas my flight to Budapest, the ticket is very clearly for a specific seat, on a specific flight, and even if by some bizarre coincidence there are delays and a different KLM flight leaves Schipol for Budapest at the exact time mine was supposed to leave, it wouldn’t occur to me to get on the wrong plane because it’s in the right place at the right time. Not without changing my ticket, anyway.&lt;/p&gt;

&lt;p&gt;Which means Eurostar is in an interesting grey area… because although they run trains on the same rail network as EuroCity and Intercity, your Eurostar ticket is good for one seat on one train, identified by a four-digit train number… and, as I’ve learned today, the train that leaves Antwerp at 13:30 for Schipol Airport may not, in fact, be the 13:30 train to Schipol.&lt;/p&gt;

&lt;p&gt;Next stop Budapest and CraftConf, which is going to be awesome because not only have they put together a fantastic line-up of speakers and sessions, but the conference is in a railway museum. (See? It’s all about trains today!) And then home. For a week-and-a-bit before heading to Stockholm for DevSum. I’d put hyperlinks in to all of these but I’m on my phone and you know how to Google.&lt;/p&gt;
</description>
          <pubDate>2025-05-28T14:03:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2025/05/28/on-the-fungibility-of-trains.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2025/05/28/on-the-fungibility-of-trains.html</guid>
        </item>
      
    
      
        <item>
          <title>Sending Email with .NET: From Zero to Hero</title>
          <description>&lt;p&gt;Last month I published my first video course on Dometrain, “&lt;a href=&quot;https://urs.tl/email-zh-website&quot;&gt;Sending Email with .NET: From Zero to Hero&lt;/a&gt;”: I just got this review from somebody who completed the course:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“⭐⭐⭐⭐⭐ Fantastic job! When I saw the course was over 9 hours, I wondered what you would talk about for that long. Now my head is swimming and it feels like 18 hours got packed into a 9 hour course. I didn’t realize how much I didn’t know about email. Thanks for putting this together.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I didn’t set out to make a 9-hour course about sending email with .NET - I figured the course would run to 3, maybe 4 hours. But once I’d covered all the underlying standards like SMTP and MIME, modern .NET libraries like MailKit and MimeKit, tools like Mailpit, Mailtrap, ngrok, the chaos of HTML email standards and the tools like MJML that exist to work with it, how to set up all the various DNS records, SPF, DKIM, DMARC, troubleshooting, logging, sending mail from background services… there’s a lot to talk about.&lt;/p&gt;

&lt;p&gt;The course is “&lt;a href=&quot;https://urs.tl/email-zh-website&quot;&gt;Sending Email with .NET: From Zero to Hero&lt;/a&gt;”, you can buy it at dometrain.com, and it’s 40% off at the moment. Check it out. I think it’s awesome.&lt;/p&gt;
</description>
          <pubDate>2025-05-14T18:44:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2025/05/14/sending-email-with-.net-from-zero-to-hero.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2025/05/14/sending-email-with-.net-from-zero-to-hero.html</guid>
        </item>
      
    
      
        <item>
          <title>The Problem with “Vibe Coding”</title>
          <description>&lt;p&gt;The whole “vibe coding” thing is another reminder that quite a lot of people working in tech don’t understand the difference between programs and products.&lt;/p&gt;

&lt;p&gt;To me, programs are “works on my machine” code. The kind of things many of us crank out a few times every week. Experiments, prototypes… that script you hacked up to rename all the MP4 files in a folder? You know the one. No error checking. Hard-coded path names. Does it work on Windows? Who cares? I’m on Linux right now and I got work to do.&lt;/p&gt;

&lt;p&gt;I have dozens of these kinds of programs I use every day. They’re tools I use to automate bits of my work. They crash all the time (“what? Oh… that person has a backslash in the title of their presentation… interesting.”) - but that doesn’t matter; I fix them, I get the results I need, I move on. The code is just a means to an end. The result is what matters.&lt;/p&gt;

&lt;p&gt;If you’re writing software that you’re planning to ship; to distribute to other people, perhaps even sell it to paying customers? Well, now that’s a whole different ball game.&lt;/p&gt;

&lt;p&gt;Probably the single most important lesson I’ve learned in my career, the thing that I would argue is the hallmark of “experience”, is understanding just how much work it takes to turn a working &lt;em&gt;program&lt;/em&gt; into a viable &lt;em&gt;product&lt;/em&gt;. It’s why developer estimates are so notoriously optimistic - and why experienced developers are so notoriously cynical. Let’s say you crank out a bit of code that’ll take responses from a web form and add them in an Excel spreadsheet. That’s not that hard… yay! we just built a Typeform competitor in one afternoon! Except, no, you didn’t. You made one thing work one time on one computer. You haven’t considered encoding, internationalization, concurrency, authentication, telemetry, billing, branding, mobile devices, deployment. You haven’t hit any of the weird limits yet - ever had a system work brilliantly for the first 65,535 requests and then fall over? You don’t have a product. At best, you have a proof-of-concept of a good idea that, if some very smart people work very hard, might become a viable product.&lt;/p&gt;

&lt;p&gt;One of the genuinely positive things about tools like Copilot and ChatGPT is that they empower people with minimal development experience to create their own programs. Little programs that do useful things - and that’s &lt;em&gt;awesome&lt;/em&gt;. More power to the users.&lt;/p&gt;

&lt;p&gt;But that’s not product development, it’s programming. They aren’t the same thing. Not even close.&lt;/p&gt;
</description>
          <pubDate>2025-04-11T16:14:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2025/04/11/the-problem-with-vibe-coding.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2025/04/11/the-problem-with-vibe-coding.html</guid>
        </item>
      
    
      
        <item>
          <title>Lunch&apos;n&apos;Learn with the Amsterdam Java User Group</title>
          <description>&lt;p&gt;I’m in the Netherlands. Last week was running some presentation training with a group at Info Support in Veenendaal, the weekend was catching up with friends in Amsterdam, and tomorrow I’m speaking at a student conference in Utrecht. One of the things about travelling for a living is that there’s no such thing as “out of office”, and the Todo List doesn’t stop just ‘cos you’re away for a week or two. My schedule said that today I’d be preparing examples and demos for &lt;a href=&quot;https://techcornwall.co.uk/training/modern-fundamentals-of-web-application-development-masterclass/&quot;&gt;a Zoom masterclass&lt;/a&gt; I’m giving on Thursday with the folks from Tech Cornwall.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/WhatsApp Image 2025-03-24 at 14.20.20_fbd243da.jpg&quot; alt=&quot;A dozen or so developers sat around a table in a very Dutch-looking kitchen, drinking coffee and talking about the history of the web.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;So when I got an email from &lt;a href=&quot;https://www.linkedin.com/in/geertjanwielenga/&quot;&gt;Geertjan Wielenga&lt;/a&gt;, who runs the &lt;a href=&quot;https://www.meetup.com/amsterdam-java-user-group/&quot;&gt;Amsterdam Java User Group&lt;/a&gt;, asking if I wanted to do a lunch’n’learn session, I naturally said “no” - mainly ‘cos I’d already have left Amsterdam for Utrecht. But, as Geerjan pointed out, the two cities are ridiculously close together… so I decided, what the hell. Usually, when I do in-person events, I bring something that’s prepared and polished: talk, slides,  video… you know. What some folks call a “Dylan Talk”. Today, we did the complete opposite: all I brought with me was a list of topics I’m still working on, and after sandwiches and coffee in Geertjan’s kitchen, we spent a fun hour putting together demos, learning  about the weird and wonderful edge cases of modern web standards (did you know &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;border-radius&lt;/code&gt; can have EIGHT different values? 🤯)&lt;/p&gt;

&lt;p&gt;Here’s a few links to the stuff we looked at:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://www.bram.us/2021/12/26/organic-blobs-in-css-with-border-radius/&quot;&gt;Organic blobs in CSS with border-radius at bram.us&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog&quot;&gt;The HTML &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; element at MDN&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://medium.com/@valgaze/the-hidden-purple-memorial-in-your-web-browser-7d84813bb416&quot;&gt;The story of #rebeccapurple&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and the code from the session is all up on GitHub:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/dylanbeattie/modern-web-dev&quot;&gt;https://github.com/dylanbeattie/modern-web-dev&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fun session. And a nice reminder that not every tech event needs to be rehearsed and polished - sometimes all you need is people, coffee and curiosity.&lt;/p&gt;
</description>
          <pubDate>2025-03-24T17:02:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2025/03/24/lunch-n-learn-with-the-amsterdam-java-user-group.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2025/03/24/lunch-n-learn-with-the-amsterdam-java-user-group.html</guid>
        </item>
      
    
      
        <item>
          <title>&apos;s-Hertogenbosch</title>
          <description>&lt;p&gt;There is a place in the Netherlands called &lt;a href=&quot;https://en.wikipedia.org/wiki/%27s-Hertogenbosch&quot;&gt;’s-Hertogenbosch&lt;/a&gt;. Yes, that’s not a typo. Its official legal place name begins with an apostrophe. The locals all call it Den Bosch (and never, ever mention that you won’t find Den Bosch on a map ‘cos why would anybody possibly need to know that?)&lt;/p&gt;

&lt;p&gt;And, seeing it on a railway departure board earlier this morning, it occurs to me that if there’s anywhere in the world that figured out a lot of database stuff right from day 1, it’s probably the good people of ‘s-Hertogenbosch.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/den-bosch.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(obviously and shamelessly stolen from &lt;a href=&quot;https://xkcd.com/327/&quot;&gt;https://xkcd.com/327/&lt;/a&gt; so if you like it go and buy one of Randall’s books or something. The &lt;a href=&quot;https://xkcd.com/what-if/&quot;&gt;anniversary edition of What If?&lt;/a&gt; is particularly good.)&lt;/em&gt;&lt;/p&gt;
</description>
          <pubDate>2025-03-23T10:52:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2025/03/23/s-hertogenbosch.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2025/03/23/s-hertogenbosch.html</guid>
        </item>
      
    
      
        <item>
          <title>An Interactive CSS Flexbox Playground</title>
          <description>&lt;p&gt;CSS flexbox is fantastic… but it’s also incredibly complicated. A flex container has half-a-dozen different CSS rules which control how content will flex and flow within the container, and figuring out the exact combination that you’ll need to implement a particular design can be a lengthy and error-prone process.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/tools/flexbox&quot;&gt;&lt;img src=&quot;/images/posts/css-flexbox-playground.png&quot; alt=&quot;A screenshot of Dylan&apos;s Interactive CSS Flexbox Playground&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So I built a thing to make it a little easier to see what’s going on. It’s an interactive CSS flexbox playground. Based on - and with visuals inspired by - Chris Coyler’s excellent &lt;a href=&quot;https://css-tricks.com/snippets/css/a-guide-to-flexbox/&quot;&gt;CSS Flexbox Layout Guide&lt;/a&gt; over at css-tricks.com, it’ll let you pick different values for all the various CSS flex properties and see how they affect your layout.&lt;/p&gt;

&lt;p&gt;Check it out here:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://dylanbeattie.net/tools/flexbox/&quot;&gt;https://dylanbeattie.net/tools/flexbox/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you like this kind of thing, I’m running a couple of events next week talking about web standards - HTML, CSS, JavaScript, native browser APIs; all the cool stuff that used to require thousands of lines of JavaScript frameworks and polyfills that just works now because browsers are getting better all the time&lt;/p&gt;

&lt;p&gt;On Monday 24th March I’m doing a ‘lunch &amp;amp; learn’ in Amsterdam:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.meetup.com/amsterdam-java-user-group/events/306823736/?eventOrigin=your_events&quot;&gt;https://www.meetup.com/amsterdam-java-user-group/events/306823736/?eventOrigin=your_events&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;then on Thursday 27th March, I’m running an online session with Tech Cornwall. 11:00-13:00 on Zoom, it’s free for members and £99 (+ VAT and booking fees) for non-members: check that out here:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://techcornwall.co.uk/training/modern-fundamentals-of-web-application-development-masterclass/&quot;&gt;https://techcornwall.co.uk/training/modern-fundamentals-of-web-application-development-masterclass/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hopefully see you a few of you there.&lt;/p&gt;
</description>
          <pubDate>2025-03-22T17:15:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2025/03/22/an-interactive-css-flexbox-playground.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2025/03/22/an-interactive-css-flexbox-playground.html</guid>
        </item>
      
    
      
        <item>
          <title>Metaphors</title>
          <description>&lt;p&gt;Back in 2020, when we all had a lot more time on our hands, I would occasionally hang out on &lt;a href=&quot;https://www.reddit.com/r/WritingPrompts/&quot;&gt;/r/WritingPrompts&lt;/a&gt;. I stumbled across &lt;a href=&quot;https://www.reddit.com/r/WritingPrompts/comments/ha5eqs/comment/fv16xj3/&quot;&gt;this old post&lt;/a&gt; today and figured I’d post it here. You know. For posterity.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;&lt;em&gt;Writing Prompt:&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;You’re on a space ship with a bunch of your crewmates. You’re the only human, and apparently metaphors are a strictly human behavior. You’ve learned to cope with this, but today you’ve decided to speak in only figures of speech as a prank on the others.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;“Good morning, Harzz. I feel like death.”&lt;/p&gt;

&lt;p&gt;“Captain, you cannot feel like death. Death implies the absence of perception - and furthermore, is an experience with which you, unlike me, cannot be familiar.”&lt;/p&gt;

&lt;p&gt;Ah, Harzz. Great science officer… pain in the ass before I’ve had my morning coffee. I should have known better. I’m about to explain when… no. You know what? This is my ship, this is my crew, and I’m tired of having to break everything down into logical, literal phrases the whole time. Today, just for once, I’m going to talk how I want to talk. They can figure it out.&lt;/p&gt;

&lt;p&gt;“Don’t worry, Harzz. I’m just yanking your chain. I’m just a bit wiped out today, that’s all. I’ll be as right as rain after I’ve had a cup of joe.”&lt;/p&gt;

&lt;p&gt;I head to the galley, leaving the bemused Harzz staring after me. You know, this could actually be fun…&lt;/p&gt;

&lt;p&gt;We’ve been out here six weeks, along the edge of the disputed zone. One of those missions that feels like more of a box-ticking exercise than anything else - the “dispute” turned into more of a cold war decades ago, we’ve surveyed every rock in this place a dozen times over. No settlements, no life signs, no hostiles… HQ thinks having a couple of ships along our edge of it keeps things under control, and so far they’re right. Looks like today’s gonna be another long, dull day of empty space and blank scopes.&lt;/p&gt;

&lt;p&gt;Sometime around ten, I wander into the mess to find the crew gathered there. Harzz, Djanik, Kjin-ti… time to have a little fun. I pour myself a cup of coffee and sat down.&lt;/p&gt;

&lt;p&gt;“Welcome, captain. We were just reviewing today’s mission telemetry. Would you like a report?”&lt;/p&gt;

&lt;p&gt;“No, Djanik, that’s OK. I’ll review it later. I was just thinking, you know, us out here together all these weeks, maybe it’s time we got to know each other a little.”&lt;/p&gt;

&lt;p&gt;“Are you concerned about the crew’s performance, captain?”&lt;/p&gt;

&lt;p&gt;“No, nothing like that. Just… you have to admit, this mission isn’t exactly keeping us busy. Thought maybe we could swap a few stories, help to pass the time. Did I ever tell you about my parents?”&lt;/p&gt;

&lt;p&gt;“Your parents, captain?”&lt;/p&gt;

&lt;p&gt;Here we go. This was gonna be fun.&lt;/p&gt;

&lt;p&gt;“Yeah. You probably know my dad - he’s a big cheese, was one of the top brass for a while. It wasn’t always easy for him - he was always the kind of guy who called his own tune, y’know, marched to the beat of his own drum.”&lt;/p&gt;

&lt;p&gt;“Captain, forgive me, I had no idea your family were musicians. I thought your father was an officer in the Federation.”&lt;/p&gt;

&lt;p&gt;“Yes”, interjects Djanik, “and I had always believed your species was carbon-based. I did not know it was possible to create sentience from dairy products and metal alloys.”&lt;/p&gt;

&lt;p&gt;I go on. “Well, he was a real high flyer back in his academy days - gave him a reputation as a bit of a hard-ass”&lt;/p&gt;

&lt;p&gt;“I was unaware that altitude affected the composition of your species’ buttocks, Captain. This is fascinating.”&lt;/p&gt;

&lt;p&gt;I catch Kjin-ti whispering to him “don’t be an idiot, Djanik! The captain already said his father was top brass - that explains the altitude and the composition!”&lt;/p&gt;

&lt;p&gt;I’m already struggling to keep a straight face. “One night, dad and some of his classmates head out - they got some shore leave, decide to go out, paint the town red, see if they can pick up some birds”.&lt;/p&gt;

&lt;p&gt;“Your father’s dedication to urban maintenance and wildlife conservation must have impressed the examiners at the Academy, captain”&lt;/p&gt;

&lt;p&gt;“Perhaps, Djanik. Perhaps. Now, you gotta realise, my dad was normally an early bird - up at the crack of dawn most days…”&lt;/p&gt;

&lt;p&gt;(I overhear Kjin-ti again: “Djanik, I have never heard the dawn crack… is this an Earth phenomenon?”. Djanik replies “I am confused as well, Kjin-ti. The captain’s father is now a bird made of brass and cheese. I am finding the aerodynamics difficult to calculate.”)&lt;/p&gt;

&lt;p&gt;“…but my mum was a night owl; the kind of woman who was always burning the candle at both ends. Well, story goes, their eyes met across a crowded dance floor.”&lt;/p&gt;

&lt;p&gt;“Their eyes? Did the rest of their bodies meet as well, captain?”&lt;/p&gt;

&lt;p&gt;I laugh. “I guess you could say that, Kjin-ti. They were like glue after that night.”&lt;/p&gt;

&lt;p&gt;Djanik nods. “Your mother must have used the heat from her candle to catalyse the proteins in your father and create an adhesive. I hope her feathers were not damaged during the process.”&lt;/p&gt;

&lt;p&gt;Just as I can’t hold it any more, the alarm rings out. Djanik’s face is impassive. “High alert, captain. All stations.”&lt;/p&gt;

&lt;p&gt;“Wait.”&lt;/p&gt;

&lt;p&gt;The crew stop and stare at me. This was irregular.&lt;/p&gt;

&lt;p&gt;“High alert, Mr Djanik? How high?”&lt;/p&gt;

&lt;p&gt;“I… do not understand the question, captain.”&lt;/p&gt;

&lt;p&gt;“Djanik, you said there was a high alert. I would like to know: how high is the alert?”&lt;/p&gt;

&lt;p&gt;“I… captain, it is a high alert. We should respond at once!”&lt;/p&gt;

&lt;p&gt;“Computer, cancel alert.” The crew are all staring at me now. “I’m sorry I deceived you. There is no alert. I was merely trying to make a point. When the alarm went off, you all recognised it as a high alert, correct?”&lt;/p&gt;

&lt;p&gt;They nod.&lt;/p&gt;

&lt;p&gt;“But that doesn’t mean anything, really, does it? The alert doesn’t actually have a height.”&lt;/p&gt;

&lt;p&gt;They’re still staring, but the penny is starting to drop (I must remember to try that one on them later.)&lt;/p&gt;

&lt;p&gt;“So… a high alert is more serious than a regular alert… yes?”&lt;/p&gt;

&lt;p&gt;They nod again.&lt;/p&gt;

&lt;p&gt;“Why do you think that is?”&lt;/p&gt;

&lt;p&gt;Kjin-ti figures it out first. “Gravity, captain. Your species evolved language on a planet with gravity. You associate height with magnitude - size, scale, danger. For your ancestors, big things were high, and big things were dangerous.”&lt;/p&gt;

&lt;p&gt;I smile. “Go on…”&lt;/p&gt;

&lt;p&gt;“…and…” the leap in cognition is right there. She goes for it. “…and your language is built on comparisons. This is why you speak of turning up audio signals and turning down invitations, when neither of these involves a change of altitude.”&lt;/p&gt;

&lt;p&gt;Djanik is deep in thought. “So, you say your father was a big cheese… and large things are important in your society. Was your father made of cheese, captain?”&lt;/p&gt;

&lt;p&gt;“No, Djanik. We humans don’t just use spatial metaphors about size and height. We use images from nature, from our surroundings. Among my ancestors, to own a large piece of cheese was a sign of wealth and affluence, at a time when many humans had insufficient food.”&lt;/p&gt;

&lt;p&gt;“And your mother… was not really an owl.”&lt;/p&gt;

&lt;p&gt;“No, Harzz. She was 100% human, same as my father, same as me. But the owl was a nocturnal bird back on Earth, so people who stayed up past dark were called night owls.”&lt;/p&gt;

&lt;p&gt;“I see. Captain, your species’ style of communication is highly illogical… but I begin to see how, in the absence of telepathy or pheromones, it may have given you an evolutionary advantage. Fascinating.”&lt;/p&gt;

&lt;p&gt;The crew sit in contemplative silence for a few minutes. I pour another cup of coffee and return to the bridge, check the readouts, lay in a course, and the ship thunders to life, tongues of flame licking from her engines, an arrowhead streaking through the heavens, the stars around us an infinity of diamonds scattered across the velvet darkness of space.&lt;/p&gt;
</description>
          <pubDate>2025-03-16T10:34:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2025/03/16/metaphors.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2025/03/16/metaphors.html</guid>
        </item>
      
    
      
        <item>
          <title>&quot;Open Source, Open Mind&quot; - A Free Meetup with Info Support in Veenendaal</title>
          <description>&lt;p&gt;On March 19th, I’ll  be speaking at a free meetup in Veenendaal in the Netherlands, hosted by the wonderful folks over at &lt;a href=&quot;https://www.infosupport.com/&quot;&gt;Info Support&lt;/a&gt;, all about the history - and future - of “free software”. We’re coming up on half a century of folks giving their code away for free, but we’re still not entirely sure what “free software” is, how it works - or whether it’s possible to create a truly sustainable ecosystem around free and open source software.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.infosupport.com/open-source-open-mind/&quot;&gt;&lt;img src=&quot;/images/posts/bafkreidkrfykvml6irfyyjymm264crw6oa5n2tbwgult56fm5y6vw3qmbi.jpg&quot; alt=&quot;A promo banner for the meetup &amp;quot;Open Source, Open Mind Met Dylan Beattie&amp;quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Come along and hear about the history of open source, free software, the GNU project, Commander Keen, WordPress, FluentAssertions, Redis… and more.&lt;/p&gt;

&lt;p&gt;Sign up here: https://www.infosupport.com/open-source-open-mind/&lt;/p&gt;

&lt;p&gt;See you there!&lt;/p&gt;
</description>
          <pubDate>2025-03-04T11:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2025/03/04/open-source-open-mind-a-free-meetup-with-info-support-in-veenendaal.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2025/03/04/open-source-open-mind-a-free-meetup-with-info-support-in-veenendaal.html</guid>
        </item>
      
    
      
        <item>
          <title>Martin reckons it&apos;ll take me 20 minutes to write a blog post.</title>
          <description>&lt;p&gt;Martin is apparently dubious as to just how awesome Sveltia CMS is. (Martin also apparently isn’t entirely sure of the difference between Sveltia and Svelte, but I’m sure he’ll figure it out eventually).&lt;/p&gt;

&lt;p&gt;For the record, he made this comment at 18:29 UTC on 2025-02-27.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2025-02-27_18-29-42.png&quot; alt=&quot;Martin saying it&apos;ll take 20 minutes to write a blog post.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;20 minutes? I make that 3 minutes, plus another 2 for GitHub Actions to build and publish the site.&lt;/p&gt;
</description>
          <pubDate>2025-02-27T18:29:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2025/02/27/martin-reckons-it-ll-take-me-20-minutes-to-write-a-blog-post/html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2025/02/27/martin-reckons-it-ll-take-me-20-minutes-to-write-a-blog-post/html</guid>
        </item>
      
    
      
        <item>
          <title>Iconography is Hard: VS2022 Refresh Edition</title>
          <description>&lt;p&gt;I’ve &lt;a href=&quot;https://www.youtube.com/watch?v=qbCniw-BcW0&quot;&gt;spoken before&lt;/a&gt; about the four different styles of visual communication - pictographic, phonographic, ideographic and logographic:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Pictographic&lt;/strong&gt; writing is where the symbol is a &lt;strong&gt;picture of a thing&lt;/strong&gt;. The smiley face emoji 😃 is a good example - regardless of what languages and alphabets you can read, you’ll see that picture and think “hey, a happy person!”&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Phonographic&lt;/strong&gt; writing is where pictures stand for sounds. It’s how most Western alphabets work - remember learning to read in school? Sounding out “r e d b a l l” until your brain put the sounds together into “red ball” and you went “oh, yeah, a red ball!”&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Ideographic&lt;/strong&gt; writing is where pictures stand for ideas - and unless somebody’s explained it in advance, you have no clue what it means. You know the “eyes” emoji 👀 ? Does that mean “I’m watching this to see what happens?” Or “I’m looking into this for you now?” Or “I’m rolling my eyes in despair because I &lt;em&gt;knew&lt;/em&gt; this was going to happen?” Depends on the context, culture, team.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Logographic&lt;/strong&gt; writing is where an image stands for a word - not an idea, but a specific word. You don’t find it much in Western languages and alphabets but it’s used in Chinese, and in kanji, one of the three forms of written Japanese: the kanji 父 represents the word “father”, for example.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When it comes to building things like toolbars and user interfaces, things can get properly gnarly. Most modern desktop apps use a delightful mashup of pictograms, ideograms and phonograms - the little yellow folder where you stored all your documents, the floppy disk that used to be a pictogram but most folks using computers today have never actually seen one so it’s become an ideogram.&lt;/p&gt;

&lt;p&gt;I noticed earlier today that the menu in Visual Studio 2022 has two almost identical icons on it… circular arrows, which we’ve come to associate with the idea of refreshing something (“go around again”, I guess?)&lt;/p&gt;

&lt;p&gt;Except in VS2022, if the arrow’s rotating clockwise, it means “Browser Link”, and if the arrow’s rotating counter-clockwise, it means “Restart (Ctrl+Shift+F5)”. I guess… righty refreshy, lefty-lets-recompily?&lt;/p&gt;

&lt;p&gt;But hey - iconography is hard. &lt;strong&gt;You&lt;/strong&gt; try explaining the difference between a Browser Link refresh and a project restart in 24x24 pixels. 😉&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/vs-arrows.png&quot; alt=&quot;A screenshot of the Visual Studio 2022 menu bar, showing an arrow rotating clockwise with the tooltip \&amp;quot;Browser Link - Disabled\&amp;quot; and an arrow rotating counterclockwise with the tooltip \&amp;quot;Restart \(Ctrl+Shift+F5\)\&amp;quot;&quot; /&gt;&lt;/p&gt;
</description>
          <pubDate>2025-02-26T13:35:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2025/02/26/iconography-is-hard-vs2022-refresh-edition.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2025/02/26/iconography-is-hard-vs2022-refresh-edition.html</guid>
        </item>
      
    
      
        <item>
          <title>Visual Studio 2022 17.13.1 Broke My Syntax Highlighting</title>
          <description>&lt;p&gt;Another day, another software update that breaks something you can’t quite believe anybody’s still messing around with… but hey, even here in 2025 I guess somebody at the Visual Studio team is looking at syntax highlighting and going “yeah, we could improve that”.&lt;/p&gt;

&lt;p&gt;Here’s a snippet of code from the workshop I’m writing, that I snapped a few days ago. This is Visual Studio 2022 Professional, version 17.12.3, with a slightly modified version of h4zm1’s awesome Cyberpunk theme (&lt;a href=&quot;https://github.com/h4zm1/Cyberpunk-Theme&quot;&gt;GitHub&lt;/a&gt;) (&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=T0uchM3.CTVS19&quot;&gt;VS Marketplace&lt;/a&gt;):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/vs2022-17.12.3-snippet.png&quot; alt=&quot;A screenshot of some C# code in Visual Studio 2022, with a synthwave colour scheme applied.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And here’s that same snippet of code when I opened it up this morning, after installing the latest update to VS 2022 17.13.1:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/vs2022-17.13.1-snippet.png&quot; alt=&quot;A screenshot of some C# code in Visual Studio 2022, with a synthwave colour scheme applied. Several code elements are rendered in a muted color, indicating they are unused - but they ARE used. Stupid computer.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You see how the constructor parameters aren’t hot pink any more, they’re a sort of muted navy blue? And &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GetWebsiteBaseUri&lt;/code&gt; has gone from being Sunny Delight to being a colour I think we will call “crusty mustard”?&lt;/p&gt;

&lt;p&gt;Yeah. That’s a problem. Those muted colours, that’s Visual Studio’s way of saying “hey… this thing isn’t actually being used; maybe we can remove it?” - except those bits of code absolutely are being used.&lt;/p&gt;

&lt;p&gt;Tried the usual things. Restarted VS2022. Updated Resharper. Restarted the computer. Nope. Eventually had to revert the update - which, of course, clobbered the colour theme installation - but eventually I managed to get VS downgraded back to 17.12.3, get the color scheme reinstalled, and generally get things looking the way they’re supposed to. There’s 90 minutes of my life I’m never getting back.&lt;/p&gt;

&lt;p&gt;Why does it matter? I’m halfway through making a training course. Lots of video. Lots of recording screens of code. So I guess we add another rule to the rulebook: &lt;strong&gt;don’t update Visual Studio when you’re in the middle of a video project.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I’ve filed a feedback issue with the VS team here:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://developercommunity.visualstudio.com/t/VS2022-17131-update-has-incorrect-synt/10856882&quot;&gt;https://developercommunity.visualstudio.com/t/VS2022-17131-update-has-incorrect-synt/10856882&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;if you end up on this post searching for the same problem, go give it an upvote 😉&lt;/li&gt;
&lt;/ul&gt;
</description>
          <pubDate>2025-02-24T12:22:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2025/02/24/visual-studio-2022-17.13.1-broke-my-syntax-highlighting.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2025/02/24/visual-studio-2022-17.13.1-broke-my-syntax-highlighting.html</guid>
        </item>
      
    
      
        <item>
          <title>Replaying Keystrokes with AutoHotkey macros</title>
          <description>&lt;p&gt;&lt;a href=&quot;https://autohotkey.com/&quot;&gt;AutoHotkey&lt;/a&gt; is one of the strangest and most powerful things I’ve ever encountered in all the years I’ve been working with computers. It’s a macro programming language and interpreter for Windows that will let you automate just about anything - keystrokes, mouse clicks, MIDI, game controllers, window handles… if you for some bizarre reason wanted to control your Windows audio volume using a Thrustmaster throttle controller, AutoHotkey could totally do that.&lt;/p&gt;

&lt;p&gt;I’ve been using it to build little macros that will replay keystrokes from the clipboard, so that when I’m recording training videos, I can pause the camera, type in the code, cut it to the clipboard, un-pause the camera, and then replay those few lines without any pauses or typos while I explain what they’re doing. It’s not perfect, but it’s the best solution I’ve found yet in terms of minimising prep time vs ending up with unusable footage.&lt;/p&gt;

&lt;p&gt;Here’s the latest version of the script I’m using for this.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;F13&lt;/strong&gt; reloads the script. It’ll also reload if you press Ctrl-S while the active window has “autohotkey” in the title, so when I’m editing the script in VS Code it’ll reload every time I save my changes.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;F14&lt;/strong&gt; will replay the entire contents of the clipboard, including line breaks, and pause for 100ms at the end of each line.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;F15&lt;/strong&gt; will replay the next line from the clipboard each time you press it, including the linebreak.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;F16&lt;/strong&gt; will replay the next line from the clipboard &lt;em&gt;without the linebreak&lt;/em&gt;.&lt;/li&gt;
  &lt;li&gt;If &lt;strong&gt;Scroll Lock is ON&lt;/strong&gt;, every line will be trimmed before replaying it - this works around the quirk where most editors (VS Code, Visual Studio, etc) use “smart indentation” and don’t start new lines from column 0, but if you’re copying code it’ll normally have a bunch of leading tabs/spaces included.&lt;/li&gt;
  &lt;li&gt;It’ll play a little &lt;strong&gt;R2/D2&lt;/strong&gt; noise whenever you reload the script (you’ll need the file &lt;a href=&quot;/assets/r2d2.wav&quot;&gt;r2d2.wav&lt;/a&gt; go to with it)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, you’ve got to use physical keys. Don’t ask me why. I tried remapping some of the buttons on my StreamDeck to simulate F13-F16; didn’t work – I guess AutoHotkey’s hooking into the keyboard events at a lower level than StreamDeck’s injecting them? But I reprogrammed the “playstation keys” on my &lt;a href=&quot;https://www.keychron.com/products/keychron-k5-max-qmk-via-wireless-custom-mechanical-keyboard&quot;&gt;Keychron K5&lt;/a&gt; to be F13-F16 and it that works just fine.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/keychron-k5-playstation-keys.png&quot; alt=&quot;A Keychron K5 mechanical keyboard showing the extended function key cluster labelled as \&amp;quot;PlayStation keys\&amp;quot;&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#Requires AutoHotkey v2.0
FileEncoding &quot;UTF-8&quot;

~^s:: {
    if WinActive(&quot;autohotkey&quot;) {
        SoundPlay &quot;r2d2.wav&quot;, 1
        Sleep 200
        Reload
    }
}

SetKeyDelay 64  

F13:: {
    SoundPlay &quot;r2d2.wav&quot;, 1
    Sleep 200
    Reload
}
; F14 will replay the entire clipboard, pausing for 100ms between lines
F14:: {
    SendMode &quot;Event&quot;
    firstLine := true
    loop parse A_Clipboard, &quot;`n&quot;, &quot;`r&quot; {
        if (firstLine) {
            firstLine := false
        } else {
            Send(&apos;{Esc}&apos;)
            Send(&apos;{Enter}&apos;)
            Sleep 100
        }
        trimLines := GetKeyState(&quot;ScrollLock&quot;, &quot;T&quot;)
        trimmedText := A_LoopField
        if (trimLines = 1) {
            trimmedText := Trim(trimmedText)
        }
        SendText trimmedText
    }
}

; F15 will replay the next line from the clipboard, including the trailing newline
F15:: {
    SendMode &quot;Event&quot;
    firstLine := true
    loop parse A_Clipboard, &quot;`n&quot;, &quot;`r&quot; {
        if (firstLine) {
            firstLine := false
        } else {
            Send(&apos;{Esc}&apos;)
            Send(&apos;{Enter}&apos;)
            KeyWait &quot;F15&quot;, &quot;D&quot;
            KeyWait &quot;F15&quot;
        }
        trimLines := GetKeyState(&quot;ScrollLock&quot;, &quot;T&quot;)
        trimmedText := A_LoopField
        if (trimLines = 1) {
            trimmedText := Trim(trimmedText)
        }
        SendText trimmedText
    }
}

; F16 will replay the next line from the clipboard 
; lines are trimmed to remove leading/trailing whitespace
; but will NOT send newlines - you gotta type Enter yourself.
F16:: {
    SendMode &quot;Event&quot;
    loop parse A_Clipboard, &quot;`n&quot;, &quot;`r&quot; {
        trimLines := GetKeyState(&quot;ScrollLock&quot;, &quot;T&quot;)
        trimmedText := A_LoopField
        if (trimLines = 1) {
            trimmedText := Trim(trimmedText)
        }
        SendText trimmedText
        Send(&apos;{Esc}&apos;)
        KeyWait &quot;F16&quot;, &quot;D&quot;
        KeyWait &quot;F16&quot;    
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
          <pubDate>2025-02-18T09:06:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2025/02/18/replaying-keystrokes-with-autohotkey-macros.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2025/02/18/replaying-keystrokes-with-autohotkey-macros.html</guid>
        </item>
      
    
      
        <item>
          <title>“How To Be a Rockstar Developer” at DDD North</title>
          <description>&lt;p&gt;I’m off to Hull this weekend for &lt;a href=&quot;https://dddnorth.co.uk/&quot;&gt;DDD North&lt;/a&gt; where I’ll be presenting &lt;a href=&quot;https://dylanbeattie.net/talks/how-to-be-a-rockstar-developer.html&quot;&gt;&lt;strong&gt;How To Be A Rockstar Developer&lt;/strong&gt;&lt;/a&gt; - the story of how a programming language I invented in a bar as a joke got way out of hand, and ended up as a real thing, complete with a web assembly runtime, test suite, documentation… and lots of stupid jokes about Bon Jovi.&lt;/p&gt;

&lt;p&gt;I love DDD (which is short for Developer! Developer! Developer!, inspired by a thing Steve Ballmer did when he was CEO of Microsoft, and not to be confused with Domain-Driven Design which is a completely different thing). It’s a series of free, one-day developer events around the UK. And in Australia, but that’s a bit far to go for a one-day event. And apparently they had one in Seoul. DDD’s historical associations with Microsoft mean it attracts a lot of folks who work with .NET, but with an open CFP and sessions chosen via a public vote, there’s usually a pretty good balance of speakers and topics.&lt;/p&gt;

&lt;p&gt;It’s free, it’s on a Saturday - this Saturday, in fact! - and it’s always a really friendly bunch of people. There’s still a few tickets left at &lt;a href=&quot;http://dddnorth.co.uk/&quot;&gt;dddnorth.co.uk&lt;/a&gt;, and if you’re going to be around, come and say hi.&lt;/p&gt;
</description>
          <pubDate>2025-02-17T13:15:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2025/02/17/how-to-be-a-rockstar-developer-at-ddd-north.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2025/02/17/how-to-be-a-rockstar-developer-at-ddd-north.html</guid>
        </item>
      
    
      
        <item>
          <title>Faking the HTTP Request URL in ASP.NET MVC</title>
          <description>&lt;p&gt;Because I always, &lt;em&gt;always,&lt;/em&gt; forget how to do this: here’s how you fake, spoof, whatever you want to call it the HTTP request in ASP.NET Core. Useful if you’ve got controller actions that do things like generate absolute URL links to put into outgoing emails, and you want them to reflect the URL where the code was actually running (so code running on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;localhost&lt;/code&gt; sends mails with links to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;localhost&lt;/code&gt;, code running on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test.example.com&lt;/code&gt; links to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test.example.com&lt;/code&gt;, and so on), and you want to write tests for that code because why wouldn’t you write tests?&lt;/p&gt;

&lt;p&gt;Looks like this:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ControllerContext&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;SimulateControllerContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uriString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uri&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uriString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ControllerContext&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;HttpContext&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DefaultHttpContext&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;Request&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Scheme&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Scheme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Host&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;HostString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;controller&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;WhateverController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;ControllerContext&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;SimulateControllerContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://example.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I’ll normally mix this up with a little extension method, usually while wishing that the people who wrote the very excellent &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Path.Combine&lt;/code&gt; had elbowed their way into the room where they were building &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;System.Uri&lt;/code&gt; and Got Involved:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UriExtensions&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Uri&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Uri&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;params&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Aggregate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AbsoluteUri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;TrimEnd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;&apos;/&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;TrimStart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;&apos;/&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Uri&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;GetWebsiteBaseUri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HttpRequest&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;$&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Scheme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;://&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(also, yay blogging with Sveltia means no more messing around with git. Write, publish. done.)&lt;/p&gt;
</description>
          <pubDate>2025-02-15T17:57:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2025/02/15/faking-the-http-request-url-in-asp.net-mvc.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2025/02/15/faking-the-http-request-url-in-asp.net-mvc.html</guid>
        </item>
      
    
      
        <item>
          <title>Posting with SveltiaCMS from my phone</title>
          <description>&lt;p&gt;And here’s the ultimate acid test for low-friction blogging… how quickly can I write and publish a post using my phone? Well, the layouts are a little janky:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/IMG_7410.png&quot; alt=&quot;Composing a blog post in Chrome on an iPhone using Sveltia CMS&quot; title=&quot;Composing a blog post in Chrome on an iPhone using Sveltia CMS&quot; /&gt;&lt;/p&gt;

&lt;p&gt;…but it works. Nice. Expect more off-the-cuff blogging.&lt;/p&gt;
</description>
          <pubDate>2025-02-13T20:55:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2025/02/13/posting-with-sveltiacms-from-my-phone.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2025/02/13/posting-with-sveltiacms-from-my-phone.html</guid>
        </item>
      
    
      
        <item>
          <title>SveltiaCMS, Jekyll and GitHub Pages</title>
          <description>&lt;p&gt;I used to blog a lot. Back in the good old days of Windows Live Writer and Blogger, when I could click “New post”, bash out a few hundred words, paste an image or two, and… done.&lt;/p&gt;

&lt;p&gt;In 2020 I moved all my stuff from Blogger to Jekyll and GitHub Pages, because I wanted more control over the content on my site. It worked, and I love Jekyll dearly and it’s a fantastic tool, but it kinda killed my blogging because even a two-paragraph post meant git pull, create a new Markdown file, make sure to get the filename right (and filenames are based on dates, so it if you don’t get around to posting it the day you started it you’ve gotta rename stuff), git add, git commit, build… yeah.&lt;/p&gt;

&lt;p&gt;So I asked around for whatever’s the closest thing to Windows Live Writer - with the caveats that cntent stays in Markdown, and for now it stays on Github. I own my content. I want it under my control, in a portable format.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://chrissimon.au/&quot;&gt;Chris Simon&lt;/a&gt; pointed me at &lt;a href=&quot;https://decapcms.org/&quot;&gt;Decap CMS&lt;/a&gt;, which looked really interesting: it’s a JS app that runs from a subfolder inside your static site, connects directly to GitHub, and edits Markdown files right in your repo. Except (a) I managed to crash their live demo editor within fifteen seconds by trying to paste an image into it &lt;em&gt;(I know! Pasting an image! Into a blog post! Totally weirdo edge case that’ll never happen in real life!),&lt;/em&gt; and (b) once I’d figured out how to Not Paste Images, I couldn’t get the auth for it running reliably. It uses Netlify to authenticate with GitHub – GitHub can’t do pure JS authentication ‘cos it needs a server to handle the post-authentication callback – and seemed to have some problems using Netlify to authenticate when the site I was trying to edit wasn’t hosted on Netlify.&lt;/p&gt;

&lt;p&gt;Then I found &lt;a href=&quot;https://github.com/sveltia/sveltia-cms&quot;&gt;Svelti&lt;/a&gt;&lt;a href=&quot;https://github.com/sveltia/sveltia-cms&quot;&gt;a CMS&lt;/a&gt;. It’s the same idea: CMS as a JS app, in a subfolder of a static website, that pushes stuff straight to a GitHub repo - along with a Cloudflare worker app that handles the authentication bit.&lt;/p&gt;

&lt;p&gt;So if you’re reading this? It worked: this was posted from Sveltia, which is hooked into dylanbeattie.net/admin, and maybe means bashing out those quick 2-paragraph blog posts might just be as easy as it was in the days of Live Writer.&lt;/p&gt;
</description>
          <pubDate>2025-02-13T20:38:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2025/02/13/sveltiacms-jekyll-and-github-pages.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2025/02/13/sveltiacms-jekyll-and-github-pages.html</guid>
        </item>
      
    
      
        <item>
          <title>Windows Defender detects Trojan:Script/Wacatac.B!ml in a zipped .NET 9 AOT binary</title>
          <description>&lt;p&gt;Earlier today, somebody alerted me that Rockstar binary releases for Windows were being blocked by Windows Defender… and sure enough, since I switched the build system from .NET 8 to .NET 9, the Windows ZIP file releases on the &lt;a href=&quot;https://github.com/RockstarLang/rockstar/releases&quot;&gt;releases page&lt;/a&gt; are being incorrectly identified as being infected with  Trojan:Script/Wacatac.B!ml malware.&lt;/p&gt;

&lt;p&gt;It’s not just Rockstar, though. It’s any .NET 9 binary compiled with AOT enabled and then compressed in a ZIP file. Try it. First, create a new .NET 9 console app:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dotnet new console -o HelloWorld
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then edit the .csproj file and add StripSymbols and PublishAot directives:&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;Project&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Sdk=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Microsoft.NET.Sdk&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;OutputType&amp;gt;&lt;/span&gt;Exe&lt;span class=&quot;nt&quot;&gt;&amp;lt;/OutputType&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;TargetFramework&amp;gt;&lt;/span&gt;net9.0&lt;span class=&quot;nt&quot;&gt;&amp;lt;/TargetFramework&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;ImplicitUsings&amp;gt;&lt;/span&gt;enable&lt;span class=&quot;nt&quot;&gt;&amp;lt;/ImplicitUsings&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;Nullable&amp;gt;&lt;/span&gt;enable&lt;span class=&quot;nt&quot;&gt;&amp;lt;/Nullable&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- ADD THIS PropertyGroup to create native binary EXE builds --&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;PropertyGroup&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Condition=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&apos;$(Configuration)&apos;==&apos;Release&apos;&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;DebugSymbols&amp;gt;&lt;/span&gt;False&lt;span class=&quot;nt&quot;&gt;&amp;lt;/DebugSymbols&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;DebugType&amp;gt;&lt;/span&gt;None&lt;span class=&quot;nt&quot;&gt;&amp;lt;/DebugType&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;PublishAot&amp;gt;&lt;/span&gt;true&lt;span class=&quot;nt&quot;&gt;&amp;lt;/PublishAot&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;StripSymbols&amp;gt;&lt;/span&gt;true&lt;span class=&quot;nt&quot;&gt;&amp;lt;/StripSymbols&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now publish a binary release:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dotnet publish -c Release -o published
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Look in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/published&lt;/code&gt; and you’ll see a single binary &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HelloWorld.exe&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Create a ZIP file containing that EXE - in Windows Explorer, right-click, Send to &amp;gt; Compressed (zipped) folder:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2024/dotnet-9-trojan-add-to-zip.png&quot; alt=&quot;creating a ZIP file using Windows Explorer&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Right-click the ZIP file, Scan with Microsoft Defender…&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2024/dotnet-9-trojan-result.png&quot; alt=&quot;Microsoft Defender Trojan result&quot; /&gt;&lt;/p&gt;

&lt;p&gt;For a second opinion, upload the ZIP file to VirusTotal.com, and you’ll probably see that Microsoft Defender thinks it’s infected with a trojan. I’ve had multiple hits reporting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Trojan:Script/Wacatac.B!ml&lt;/code&gt; and &lt;a href=&quot;https://www.virustotal.com/gui/file/dfc668d9a8b10a65e29a3dbb4cf15c7392a1b6933d0092fa9fe78160f8cb81e4&quot;&gt;one reporting Trojan:Win32/AgentTesla!ml&lt;/a&gt;&lt;/p&gt;

</description>
          <pubDate>2024-12-09T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2024/12/09/dotnet-9-aot-zip-file-windows-defender-false-positives.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2024/12/09/dotnet-9-aot-zip-file-windows-defender-false-positives.html</guid>
        </item>
      
    
      
        <item>
          <title>Running Corel Linux on QEMU</title>
          <description>&lt;p&gt;In my latest YouTube video, I talk about Corel Linux, which I think is one of the most interesting “what if?” scenarios in the history of mainstream computing, and show some clips of Corel Linux actually running. It’s not easy getting a 25-year-old Linux distro to boot on a modern PC - but here’s how I did it.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/jgR5c6CcB_w?si=YqJ7MCkju2cvo4IW&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h3 id=&quot;install-qemu&quot;&gt;Install QEMU&lt;/h3&gt;

&lt;p&gt;VirtualBox, HyperV et al won’t work here - Corel Linux is just too old to support most of the virtual hardware they expose to their guest operating systems. Instead, we’re going to use QEMU.&lt;/p&gt;

&lt;p&gt;Download it from &lt;a href=&quot;https://www.qemu.org/&quot;&gt;https://www.qemu.org/&lt;/a&gt;, install it, get it running. Check it works by running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qemu-system-i386&lt;/code&gt; at a terminal window:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt; qemu-system-i386 --version
QEMU emulator version 9.0.0 (v9.0.0-12054-g923cf646f4)
Copyright (c) 2003-2024 Fabrice Bellard and the QEMU Project developers
&amp;gt; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;create-a-virtual-disk&quot;&gt;Create a virtual disk&lt;/h3&gt;

&lt;p&gt;This will create a 2Gb virtual disk file called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;corel_linux_hd.img&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;qemu-img create corel_linux_hd.img 2G
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;download-corel-linux&quot;&gt;Download Corel Linux&lt;/h3&gt;

&lt;p&gt;I used the ISO image of Corel Linux 1.2 deluxe from &lt;a href=&quot;https://archive.org/details/corel_linux_1.2&quot;&gt;https://archive.org/details/corel_linux_1.2&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While you’re there, you might also want to download CorelDRAW and WordPerfect Office &lt;a href=&quot;https://archive.org/details/CorelForLinux/CorelDRAW%209%20Linux/&quot;&gt;https://archive.org/details/CorelForLinux/CorelDRAW%209%20Linux/&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;run-the-installer&quot;&gt;Run the installer&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;qemu-system-i386 -hda corel_linux_hd.img -cdrom corel_linux_1.2.iso -m 256 -vga cirrus
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You should get this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2024/image-20240524131917442.png&quot; alt=&quot;image-20240524131917442&quot; /&gt;&lt;/p&gt;

&lt;p&gt;and then this…&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2024/image-20240524131927733.png&quot; alt=&quot;image-20240524131927733&quot; /&gt;&lt;/p&gt;

&lt;p&gt;and then this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2024/image-20240524132009470.png&quot; alt=&quot;image-20240524132009470&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Yeah. This is where it gets interesting: the Cirrus VGA driver provided by QEMU displays garbled text until we get into the OS and hack it, but we can’t do that until we’ve got it installed, so we’ll need to walk through the installation without being able to read anything.&lt;/p&gt;

&lt;p&gt;You probably also don’t have any mouse support, so use Tab to move forwards, Ctrl-Tab to move backwards, and the Spacebar to click:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2024/image-20240524132852073.png&quot; alt=&quot;image-20240524132852073&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Don’t change anything here - “Install Standard Desktop” is already selected so just press Enter:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2024/image-20240524132431754.png&quot; alt=&quot;image-20240524132431754&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The next few screens, accept all the defaults - tab to Next&amp;gt; if it’s not already focused, press Enter:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2024/image-20240524132552504.png&quot; alt=&quot;image-20240524132552504&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2024/image-20240524132659002.png&quot; alt=&quot;image-20240524132659002&quot; /&gt;&lt;/p&gt;

&lt;p&gt;and finally, &lt;strong&gt;Install&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2024/image-20240524132725616.png&quot; alt=&quot;image-20240524132725616&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If you get to here, you’re on the right track:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2024/image-20240524133012196.png&quot; alt=&quot;image-20240524133012196&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Once it’s installed, let it reboot, then at the loading screen select &lt;strong&gt;Linux - Text Mode&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2024/image-20240524133627515.png&quot; alt=&quot;image-20240524133627515&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It’ll happily chunter away for a few screens worth of messages, and then you’ll get a login prompt.&lt;/p&gt;

&lt;p&gt;Log in a root with a blank password. &lt;em&gt;(Don’t ask. It was the early 2000s. Things were different then.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now we need to configure X11 so that it won’t try to use various accelerated hardware features that don’t work in QEMU.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;This bit comes from Ethan Gates’ &lt;a href=&quot;https://forum.eaasi.cloud/t/corel-linux-in-qemu/64&quot;&gt;Corel Linux in QEMU&lt;/a&gt; post on forums.eeasi.cloud – thank you Ethan! – and that post was in turn inspired by Hayden Barnes’ post &lt;a href=&quot;https://boxofcables.dev/corel-linux-1-2/&quot;&gt;The one in which I kind of get Corel Linux 1.2 to work 21 years later&lt;/a&gt; - thank you Hayden!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Use vim to edit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/X11/XF86config&lt;/code&gt;. Find the section called “Device”, add three lines:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Option &quot;no_bitblt&quot;
Option &quot;noaccel&quot;
Option &quot;sw_cursor&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2024/image-20240524140407311.png&quot; alt=&quot;image-20240524140407311&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Save the file, reboot (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shutdown -r now&lt;/code&gt;), and select &lt;strong&gt;Corel Linux&lt;/strong&gt; from the boot menu:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2024/image-20240524140634742.png&quot; alt=&quot;image-20240524140634742&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;networking-support&quot;&gt;Networking support&lt;/h2&gt;

&lt;p&gt;Networking support in QEMU is powerful, flexible, and incredibly complicated.&lt;/p&gt;

&lt;p&gt;Here’s how I made it work. Disclaimer: I don’t 100% understand exactly what all this does… I tried just about every combination I could think of until I found something that worked.&lt;/p&gt;

&lt;p&gt;First, you’ll need to install a TAP network driver. This adds another network interface to Windows, which emulates a physical network card. Apparently.&lt;/p&gt;

&lt;p&gt;I installed mine using &lt;a href=&quot;https://chocolatey.org/&quot;&gt;chocolatety&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;choco install tapwindows
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then, in Windows network settings:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Rename the new TAP connection to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TapWindows&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Right-click your main network connection (ethernet, wifi - whatever connects your Windows machine to the internet)&lt;/li&gt;
  &lt;li&gt;Properties &amp;gt; Sharing &amp;gt; Allow other network users to connect…&lt;/li&gt;
  &lt;li&gt;Choose your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TapWindows&lt;/code&gt; connection as the “Home networking connection”&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You’ll get a popup about your LAN adapter. The wording here is misleading - it’s not your LAN adapter, it’s whichever adapter you selected as you” Home networking connection”:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;If you remember using Internet Connection Sharing with a dial-up modem… this is the same tech, only we’re pretending that QEMU is our house network, TapWindows is the network card connecting our PC to the rest of the house, and “Ethernet” is the dial-up modem that connects to the internet.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2024/image-20240524141328089.png&quot; alt=&quot;image-20240524141328089&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now, run QEMU using this command line:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I’m using Powershell so a backtick ` is a line continuation character.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;qemu-system-i386 -hda corel_linux_hd.img `
-m 256 -vga cirrus -audiodev driver=dsound,id=pa1 -device sb16,audiodev=pa1 `
-netdev tap,id=mynet0,ifname=TapWindows `
-device pcnet,netdev=mynet0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s telling QEMU “create a network connection using TAP, call it &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mynet0&lt;/code&gt;, and connect it to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TapWindows&lt;/code&gt; interface on the host PC”, and then on the next line “then create a virtual device using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pcnet&lt;/code&gt; and connect it to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mynet0&lt;/code&gt; network”.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;For me, this worked 80% of the time. When it didn’t work, it’s because something meant the Linux guest couldn’t get an IP address from Internet Connection Sharing’s DHCP server. Manually setting the guest IP to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;192.168.137.2&lt;/code&gt;, subnet &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;255.255.255.0&lt;/code&gt;, gateway &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;192.168.137.1&lt;/code&gt; usually fixed this.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That got me to the point where I could boot Linux, open Netscape, point it at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://info.cern.ch/&lt;/code&gt;, and browse the world’s first website.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2024/image-20240524142321136.png&quot; alt=&quot;image-20240524142321136&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;stuff-i-couldnt-figure-out&quot;&gt;Stuff I Couldn’t Figure Out&lt;/h3&gt;

&lt;p&gt;I couldn’t get audio working. QEMU audio works fine - I created a Windows 2000 guest just to test this - and I tried both the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ac97&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sb16&lt;/code&gt; virtual audio devices in QEMU, but no luck yet.&lt;/p&gt;

&lt;p&gt;I also couldn’t figure out how to change the CD-ROM. When you’re running QEMU, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl-Alt-F2&lt;/code&gt; drops you into an emulation console where you can use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;info block&lt;/code&gt; to see which devices/images are connected, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;change ide1-cd0 &amp;lt;filename.iso&amp;gt;&lt;/code&gt; to change the virtual disk:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2024/image-20240524142715085.png&quot; alt=&quot;image-20240524142715085&quot; /&gt;&lt;/p&gt;

&lt;p&gt;But… then I couldn’t work out how to get Corel Linux to mount the disk image, so to install WordPerfect, CorelDRAW!, etc. I had to shut down the VM and then boot it specifying the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-cdrom&lt;/code&gt; command line switch:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;qemu-system-i386 -hda corel_linux_hd.img `
-m 256 -vga cirrus -audiodev driver=dsound,id=pa1 -device ac97,audiodev=pa1 `
-netdev tap,id=mynet0,ifname=TapWindows `
-device pcnet,netdev=mynet0 `
-cdrom .\wordperfect_office_2000_deluxe_cd_1.iso
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ah, the nostalgia.&lt;/p&gt;

</description>
          <pubDate>2024-05-27T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2024/05/27/running-corel-linux-on-qemu.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2024/05/27/running-corel-linux-on-qemu.html</guid>
        </item>
      
    
      
        <item>
          <title>Using the Contour ShuttleXpress with Camtasia</title>
          <description>&lt;p&gt;The Contour ShuttleXpress is a controller designed for editing audio and video. I use it all the time in applications like Logic Pro X; the jog dial and shuttle wheel are brilliant for, well, jogging and shuttling around your project’s timeline.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2024/contour-shuttlexpress-camtasia-settings.png&quot; alt=&quot;contour-shuttlexpress-camtasia-settings&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.techsmith.com/video-editor.html&quot;&gt;Camtasia&lt;/a&gt; is a screen recording and video editing app from the folks at TechSmith, which I use for doing the screen recordings I use in my presentations and workshops.&lt;/p&gt;

&lt;p&gt;Out of the box, the ShuttleXpress doesn’t support Camtasia, so I created an application profile for it.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Action&lt;/th&gt;
      &lt;th&gt;Keystroke&lt;/th&gt;
      &lt;th&gt;What it does&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Jog Left&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;,&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Step backwards in timeline&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Jog Right&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Step forwards in timeline&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Shuttle left/right&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;,&lt;/code&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Move forwards backwards in timeline. Each position on the shuttle is independently programmable, so “shuttle in left 7” sends &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;,&lt;/code&gt; as fast as possible, “shuttle in left 6” sends 60 strokes per second, and so on. It’s not a perfect timeline shuttle but it’s close enough.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Button 1&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl&lt;/code&gt; + &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Home&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Jump to beginning of timeline&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Button 2&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl&lt;/code&gt;+&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alt&lt;/code&gt;+&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;,&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Move playhead to previous clip&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Button 3&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift&lt;/code&gt;+&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl&lt;/code&gt;+&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alt&lt;/code&gt;+&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Left Arrow&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Extend selection to previous clip&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Button 4&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl&lt;/code&gt;+&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alt&lt;/code&gt;+&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Move playhead to next clip&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Button 5&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift&lt;/code&gt;+&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl&lt;/code&gt;+&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;S&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Split all tracks at playhead&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The workflow here is:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Shuttle until you find the start of the mistake&lt;/li&gt;
  &lt;li&gt;Button 5 to split all tracks&lt;/li&gt;
  &lt;li&gt;Shuttle forward to the end of the mistake&lt;/li&gt;
  &lt;li&gt;Button 5 again to split all tracks&lt;/li&gt;
  &lt;li&gt;Button 3 to select the new region with the mistake in it&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Delete&lt;/code&gt; to remove the region.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;and if you switch on “magnet mode” for the track you’re editing, when you delete the mistake it’ll automatically snap the remaining pieces together so it doesn’t leave a gap.&lt;/p&gt;

&lt;p&gt;You can download the settings file here:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/download/CamtasiaStudio-ShuttleXpress.pref&quot;&gt;CamtasiaStudio-ShuttleXpress.pref&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and import it using Settings Management &amp;gt; Options &amp;gt; Import Settings in the Contour Shuttle Device Configuration app:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2024/image-20240317193728441.png&quot; alt=&quot;image-20240317193728441&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Happy shuttling :)&lt;/p&gt;

</description>
          <pubDate>2024-03-17T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2024/03/17/using-the-contour-shuttlexpress-with-camtasia.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2024/03/17/using-the-contour-shuttlexpress-with-camtasia.html</guid>
        </item>
      
    
      
        <item>
          <title>Recording Meetups</title>
          <description>&lt;p&gt;For the last few months, I’ve been recording the talks at the London .NET User Group on video so we can post them on YouTube after the event. The videos are online at &lt;a href=&quot;https://www.youtube.com/@LondonDotNet&quot;&gt;https://www.youtube.com/@LondonDotNet&lt;/a&gt;, and I’ve had some questions about the setup I use to record them. Some from interested people looking to do something similar at their own events, some from people who show up early while I’m still setting up and go “wow, what’s with all the shiny things?” So here’s how it works.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2024/dylanbeattie-recording-meetups-1708431475830-8.png&quot; alt=&quot;dylanbeattie-recording-meetups&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(I made a diagram! Want it as a PDF? Here you go: &lt;a href=&quot;/assets/dylanbeattie-recording-meetups.pdf&quot;&gt;dylanbeattie-recording-meetups.pdf&lt;/a&gt;.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Short answer:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Canon EOS M200 camera&lt;/li&gt;
  &lt;li&gt;Elgato Camlink&lt;/li&gt;
  &lt;li&gt;Elgato Game Capture HD60S+&lt;/li&gt;
  &lt;li&gt;AnkerWork 650 wireless microphones&lt;/li&gt;
  &lt;li&gt;OBS Studio on an M1 Macbook Pro&lt;/li&gt;
  &lt;li&gt;Lots of HDMI and USB cables&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the long answer: the key here is &lt;strong&gt;minimal post-production&lt;/strong&gt;. When it comes to editing two hours of video footage, “tomorrow” turns into “later” turns into “never” with astonishing regularity. Other than editing out any major technical problems, I want to leave the meetup with a video file that’s ready to upload to YouTube.&lt;/p&gt;

&lt;p&gt;I also don’t want presenters to have to do any more than plug in HDMI, clip on a microphone, and go. I’ve done a few events where I’ve had to install drivers or sharing software for their “wireless smart display” system: I really don’t like doing that, and I’m not going to ask our presenters to do it either.&lt;/p&gt;

&lt;p&gt;In terms of portability, I assume the venue will provide HDMI for the presenter’s slides, and there’s a power socket available to plug everything in. Everything else needs to fit in a backpack and set up in less than fifteen minutes.&lt;/p&gt;

&lt;h3 id=&quot;camera-canon-eos-m200--elgato-camlink&quot;&gt;&lt;strong&gt;Camera: Canon EOS M200 + Elgato Camlink&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;The camera I use is a &lt;a href=&quot;https://www.canon.co.uk/cameras/eos-m200/&quot;&gt;Canon EOS M200&lt;/a&gt;. It’s a mirrorless SLR with clean HDMI out: what this means in practice is you can plug it into an &lt;a href=&quot;https://www.elgato.com/uk/en/p/cam-link-4k&quot;&gt;Elgato Camlink&lt;/a&gt;, plug that into your laptop, and it shows up as a webcam - albeit a really, &lt;em&gt;really&lt;/em&gt; good one. The camera runs off a USB power supply so I don’t have to worry about running out of battery halfway through a recording.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Bad news: Canon has &lt;a href=&quot;https://www.canonrumors.com/canon-eos-m-has-been-quietly-discontinued/&quot;&gt;apparently discontinued the M200&lt;/a&gt;, along with the rest of the M series, in favour of the &lt;a href=&quot;https://www.canon.co.uk/cameras/eos-r/&quot;&gt;Canon EOS R&lt;/a&gt; series.  This makes me sad, because the M200 is a wonderful camera.&lt;/p&gt;

  &lt;p&gt;Good news: Elgato maintain a list of supported cameras, so any of the cameras on this list will do the trick:&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;https://www.elgato.com/uk/en/s/cam-link-camera-check&quot;&gt;https://www.elgato.com/uk/en/s/cam-link-camera-check&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If I can, I’ll bring a proper camera tripod along. If not, I’ll bring a &lt;a href=&quot;https://www.amazon.co.uk/Version-SMALLRIG-Magic-Articulating-3-3lb/dp/B076HLBZDX/&quot;&gt;SmallRig “magic arm” clamp&lt;/a&gt; and find a convenient chair, lamp, window frame or something to clamp it to.&lt;/p&gt;

&lt;h3 id=&quot;slides-elgato-game-capture-hd60s&quot;&gt;&lt;strong&gt;Slides: Elgato Game Capture HD60S+&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;To capture the speaker’s laptop, I use an Elgato Game Capture HD60S+ (now superseded by the &lt;a href=&quot;https://www.elgato.com/uk/en/p/game-capture-hd60-x&quot;&gt;Game Capture HD60 X&lt;/a&gt;) This works like a Camlink - turns HDMI into a USB video signal - but it has an HDMI output that uses passthrough, so I can plug it in between the speaker’s laptop and the venue’s projector/screen and then tap a USB feed off the side into my laptop.&lt;/p&gt;

&lt;h3 id=&quot;audio-ankerwork-m650-wireless-microphones&quot;&gt;&lt;strong&gt;Audio: Ankerwork M650 Wireless Microphones&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;The first time I tried this, I used a RØDE shotgun microphone attached to the camera, but the audio quality wasn’t great, so I invested in a set of &lt;a href=&quot;https://uk.ankerwork.com/products/a3320-m650-wireless-microphone&quot;&gt;AnkerWork M650 wireless microphones&lt;/a&gt;. These are wonderful and amazing and excellent and I can’t say enough good things about them, but I’m a big fan of Anker gear, and folks I know who use the &lt;a href=&quot;https://rode.com/en/microphones/wireless/wireless-me&quot;&gt;RØDE Wireless ME&lt;/a&gt; or the &lt;a href=&quot;https://www.dji.com/uk/mic&quot;&gt;DJI MIC system&lt;/a&gt; say the same things about their setup.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2024/A3320011_TD01_V1_2048x.jpg&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;recording-macbook-pro--obs-studio&quot;&gt;&lt;strong&gt;Recording: Macbook Pro + OBS Studio&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;I run all these devices into an M1 Macbook Pro, and then record everything using OBS Studio.&lt;/p&gt;

&lt;p&gt;Set OBS to record MKV format, and enable “automatically remux to MP4” in Settings &amp;gt; Advanced.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;If you record direct to MP4, a system crash will leave you with an unusable video file, but MKV files aren’t as widely supported, so recording to MKV and then automatically remuxing to MP4 gives you the best of both worlds.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2024/2024-02-20_11-39-52 (1).png&quot; alt=&quot;2024-02-20_11-39-52 (1)&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This is where the “minimal post-production” bit kicks in. I’ve got four scenes set up in OBS:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Camera Only:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2024/2024-02-20_11-08-57.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Slides + Camera:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2024/2024-02-20_11-09-02.jpg&quot; alt=&quot;2024-02-20_11-09-02&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Camera + Slides&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2024/2024-02-20_11-09-11.jpg&quot; alt=&quot;2024-02-20_11-09-11&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Slides Only&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2024/2024-02-20_11-09-18.jpg&quot; alt=&quot;2024-02-20_11-09-18&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Plus, before the meetup I’ll make a title slide for each speaker and create a scene with that title slide plus the microphone, which means you can record audio over the title - great for introductions and preamble:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2024/2024-02-20_11-09-22.jpg&quot; alt=&quot;2024-02-20_11-09-22&quot; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Note that “Slides + Camera” has the slides on the left, and “Camera + Slides” is the same thing, but with the slides on the right. Composition works better if the speaker is facing towards the middle of the frame, so which one of these gets used depends on where the camera is in relation to the speaker on the night.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;OK, showtime. Plug everything in, get it working, probably reboot a couple of things because HDMI can be tricksy.&lt;/p&gt;

&lt;p&gt;Mic up your presenter, and check the audio &lt;strong&gt;in every scene&lt;/strong&gt; - nothing worse than recording the whole event and finding out later that one of your presents wasn’t recording any sound. Ask your speaker to stand where they’re going to stand, check the camera angle. I clip the other mic on to myself so I can introduce the speaker, repeat questions, etc. and have it come through on the recording.&lt;/p&gt;

&lt;p&gt;Then bring up the title slide, hit Record, introduce the talk, and then switch scenes as you go, and all being well, you’ll end up with a MP4 file you can upload straight to YouTube when you get home.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shopping List:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.amazon.co.uk/Canon-M200-EF-M-15-45mm-3-5-6-3/dp/B07YBMYPYR&quot;&gt;Canon EOS M200 digital camera&lt;/a&gt; (about £500)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.amazon.co.uk/5V-8-4V-ACK-E12-Mobile-Coupler-Battery-DR-E12/dp/B07P7MW7GY&quot;&gt;USB power + dummy battery kit for Canon EOS M200&lt;/a&gt; (£17.99)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.amazon.co.uk/Elgato-Cam-Link-Broadcast-camcorder/dp/B07K3FN5MR&quot;&gt;Elgato Camlink&lt;/a&gt; (£109.99)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.amazon.co.uk/Elgato-External-Capture-1080p60-ultra-low/dp/B07XB6VNLJ&quot;&gt;Elgato Game Capture HD60 S+&lt;/a&gt; (£299.99)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.amazon.co.uk/AnkerWork-Microphone-Cancellation-Transmission-Conference-Black/dp/B0BQMGJG7V&quot;&gt;AnkerWork M650 Wireless Lavalier Microphone&lt;/a&gt; (£179.99)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cables:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;5 metres seems to be the sweet spot for cable length. Too short, and you’re going to be in the way. Too long and you get weird signal problems, or no signal at all.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;MicroHDMI &amp;gt; HDMI cable (camera HDMI OUT &amp;gt; Camlink HDMI)&lt;/li&gt;
  &lt;li&gt;Regular HDMI cable (presenter laptop &amp;gt; GameCapture IN)&lt;/li&gt;
  &lt;li&gt;USB-C cable (GameCapture OUT &amp;gt; OBS)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total investment if you’re starting from scratch will come to just over £1200.&lt;/p&gt;

&lt;h3 id=&quot;how-much&quot;&gt;How much?!&lt;/h3&gt;

&lt;p&gt;OK, you don’t need to spend that all in one go. Get a copy of Reincubate Camo and use your phone instead of buying an SLR camera + Camlink. For slides, you can pick up cheap HDMI splitter and an HDMI &amp;gt; USB capture gizmo on Amazon for about £20 each, and see how you go.&lt;/p&gt;

&lt;p&gt;I wouldn’t skimp on microphones, though: as they say in Hollywood, “your ears never blink”, and you can probably get away with significantly worse video quality if the audio is up to scratch.&lt;/p&gt;

&lt;p&gt;Here’s two clips I recorded using the old camera-mounted RØDE shotgun mic:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=u2wmcxezm2o&quot;&gt;Guy Royse: Tracking Aircrafft with Redis and Software-Defined Radio&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=qYrF1Q-PjPs&quot;&gt;Eirik Tsarpalis: What’s New in .NET 8&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and here’s some recorded with the AnkerWork mics:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=QS1fx86U_V0&quot;&gt;Eli Holderness: A Brief History of Data Storage&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=nA2kPF4o3Yc&quot;&gt;Martin Costello: What’s New in .NET 8&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=E4Vt88bQs64&quot;&gt;Mark Rendle: How JavaScript Happened&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, that’s your lot. Good luck with it, and let me know if you found this useful.&lt;/p&gt;

</description>
          <pubDate>2024-02-20T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2024/02/20/recording-meetups.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2024/02/20/recording-meetups.html</guid>
        </item>
      
    
      
        <item>
          <title>Custom Validation Attributes in ASP.NET Core 8</title>
          <description>&lt;p&gt;ASP.NET Core 8 rocks. It’s fast, powerful, cross-platform… and, yes, includes jQuery.&lt;/p&gt;

&lt;p&gt;Chill. jQuery’s fine. It’s solid, it’s proven, it works, and we’re not here to impress hipsters hiring frontend devs for their Web3 startup, we’re here to build stuff that works (and will probably still work next year.)&lt;/p&gt;

&lt;p&gt;Out of the box, ASP.NET Core includes jQuery, jquery.validate and jquery.validation.unobtrusive, which together provide client-side validation that integrates really closely with the server-side validation provided by ASP.NET and System.ComponentModel.&lt;/p&gt;

&lt;p&gt;Most of the stuff you’ll find online about how to extend ASP.NET Core validation still works, but there’s a few new things in .NET 8 which make it a little cleaner. Specifically, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IDictionary&amp;lt;TKey,TValue&amp;gt;&lt;/code&gt; in .NET 8 includes a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.TryAdd(key, value)&lt;/code&gt; method, which means the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MergeAttributes&lt;/code&gt; helper method you’ll find in lots of examples of custom validation attributes isn’t required any more.&lt;/p&gt;

&lt;p&gt;Here’s how to extend ASP.NET with a custom validation attribute that’ll make a checkbox a required field – the classic “You must accept the terms and conditions” scenario.&lt;/p&gt;

&lt;p&gt;The attribute itself:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MustBeTrueAttribute&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ValidationAttribute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IClientModelValidator&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;IsValid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;AddValidation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ClientModelValidationContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;errorMessage&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;FormatErrorMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ModelMetadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;GetDisplayName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Attributes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;TryAdd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;data-val&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Attributes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;TryAdd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;data-val-must-be-true&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;errorMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You’ll need to add two lines of custom JavaScript to your pages - I add these to the end of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_ValidationScriptsPartial.cshtml&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;nx&quot;&gt;jQuery&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;validator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addMethod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;must-be-true&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;checked&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;nx&quot;&gt;jQuery&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;validator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;unobtrusive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;adapters&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addBool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;must-be-true&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Don’t&lt;/strong&gt; put this code inside the jQuery onload handler (the one that’s normally wrapped in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$(function() { })&lt;/code&gt;  – it doesn’t rely on any DOM elements and the call to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jquery.validator.unobtrusive.adapters.addBool()&lt;/code&gt; has to run &lt;em&gt;before&lt;/em&gt; the validation code parses your form.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To use this attribute in your view model:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SignupPostData&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;MustBeTrue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ErrorMessage&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;You must accept the terms and conditions&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AcceptTerms&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;and then in your Razor view:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;@model SignupPostData

&lt;span class=&quot;nt&quot;&gt;&amp;lt;form&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;method=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;post&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;label&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;asp-for=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;@Model.AcceptTerms&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;input&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;checkbox&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;asp-for=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;@Model.AcceptTerms&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
        I accept the terms and conditions
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;span&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;form-text text-danger&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;asp-validation-for=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;@Model.AcceptTerms&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;

@section Scripts {
	@{ await Html.RenderPartialAsync(&quot;_ValidationScriptsPartial&quot;); }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;or, if you prefer using the HTML Helper syntax:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;@model SignupPostData

@using (Html.BeginForm(FormMethod.Post)) {
	@Html.ValidationSummary()
	@Html.LabelFor(model =&amp;gt; model.AgreeToPayment)
	@Html.CheckBoxFor(model =&amp;gt; model.AgreeToPayment)
	@Html.ValidationMessageFor(model =&amp;gt; model.AgreeToPayment)
	&lt;span class=&quot;nt&quot;&gt;&amp;lt;input&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;submit&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
}

@section Scripts {
	@{ await Html.RenderPartialAsync(&quot;_ValidationScriptsPartial&quot;); }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</description>
          <pubDate>2024-01-24T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2024/01/24/custom-validation-attributes-in-aspnet-core-8.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2024/01/24/custom-validation-attributes-in-aspnet-core-8.html</guid>
        </item>
      
    
      
        <item>
          <title>Choosing a Chat App For Your Tech Event</title>
          <description>&lt;p&gt;WhatsApp, Telegram, Slack, Discord, Signal, Teams… seems like every event you go to these days has at least one “official” chat app, and probably at least half-a-dozen unofficial chat groups set up by attendees and speakers.&lt;/p&gt;

&lt;p&gt;If you’re running a conference or seminar, should you set one up? And if so, which one should you use?&lt;/p&gt;

&lt;p&gt;Let’s get the inevitable out of the way first: even a small community conference is going to attract a few dozen people, and there’s no way they’re going to agree on which chat app is best. Microsoft Teams is great when all the people in it work for the same company; for guests, even signing in can be a showstopper. For everybody who likes WhatsApp because it’s ubiquitous, there’s somebody who refuses to install it because they won’t use Meta products. For everybody who likes Slack because it’s already installed on their company laptop, there’s somebody who hates Slack because of its convoluted authentication system &lt;em&gt;(hi!)&lt;/em&gt;. For everybody who loves Discord because it’s where all their friends hang out, there’s somebody who can’t use it on their laptop because corporate IT think it’s only for gamers and crypto.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2024/image-20240118122057910.png&quot; alt=&quot;a woman in clown make-up juggling icons for popular chat systems - WhatsApp, Signal, Discord, Telegram, Slack, Teams&quot; style=&quot;zoom:50%;&quot; title=&quot;chat apps: definitely not a total clown show&quot; /&gt;&lt;/p&gt;

&lt;p&gt;That said, having a chat system for folks participating in your event is an excellent idea. For first-time attendees who don’t know anybody, it’s a great way to connect with people, find out where people are having drinks or meeting for dinner. It’s an easy way for organisers to share updates and last-minute agenda changes.&lt;/p&gt;

&lt;p&gt;If you’re running coding workshops as part of your event, setting up a dedicated channel for each workshop works brilliantly for sharing code snippets, URLs and diagrams. When I’m teaching workshops, I always ask the organisers to set up a chat with all the attendees, using something people can run &lt;strong&gt;on their laptops&lt;/strong&gt; – there’s no point sharing 25 lines of JavaScript with somebody if the only place they can see it is a WhatsApp notification on their iPhone.&lt;/p&gt;

&lt;p&gt;So, here’s a few guiding principles I’d encourage you to consider if you’re creating a chat for your event.&lt;/p&gt;

&lt;p&gt;First, ask yourself: &lt;strong&gt;are you supporting a physical event, or creating an online community?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It’s lovely to think that people can use your chat to keep in touch after the event, but &lt;em&gt;they don’t need your event chat to do that&lt;/em&gt;. Social media exists. Attendees can spin up their own chats and groups any time, on whatever platform they want to, and keep the conversation going there – somewhere where it isn’t your responsibility to answer questions, provide event support, and potentially enforce your event’s code of conduct. Doing those things well for three days is hard enough; do you really want to do it all year round, for free?&lt;/p&gt;

&lt;p&gt;Online communities are inclusive: anybody can join, just send them the link. In-person events are exclusive: you’ve actually got to show up. If you’re not there, you’re not part of it. Sure, there’s a huge amount of overlap, but the kind of chat that happens when twenty people are together in an unfamiliar city trying to find a good sushi place is not the same as the chat that happens when those same twenty people are back home, spread across a dozen timezones, and somebody’s asking if anybody knows a good way to show Git commits in PowerPoint.&lt;/p&gt;

&lt;p&gt;Consider creating a workspace just for your event: one of the big advantages of creating a new instance/server/workspace/whatever for each event is that it’s easy to invite the right people, and easy to shut it down when you’re done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use channels sparingly&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You probably need a general chat, a speaker chat, an announcements channel, and if you have separately ticketed events like workshops or seminars, create a channel for each of those. Don’t create fifty different channels for web, JavaScript, ethics, .NET, IOT, crypto, Java: you’ll overwhelm people with choice, and when they see most of those channels are empty and nobody’s posted anything, they’ll lose interest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use familiar tools&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Online platforms like Pine and gathertown serve a purpose, but the majority of your participants won’t have used them before, which makes them far less likely to get involved in discussion. Stick to something people already know.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;During the COVID lockdowns when many events went entirely online, I saw far more engagement and participation during events that used Slack and Discord than I did in events relying on proprietary platforms - we’re talking thousands of chat messages over a few days, vs fewer than a dozen on some of the dedicated event platforms. Familiarity is important.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Encourage your team to get involved&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Folks will use chat to ask questions, ask for help, ask if anybody has an extension cord or a Macbook charger or where the vegetarian food is served at lunchtime. Make sure your event staff are around to give helpful answers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don’t rely on it if something’s urgent&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One of the things I do before I give a talk is to put my phone in airline mode. You ping me on WhatsApp five minutes before I give a presentation? I’m not going to see that until after I’m done. If you need to talk to somebody urgently, call them – and if that doesn’t work, go and find them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shut it down when you’re done&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the one that I personally find the most frustrating. I’m still in Slack workspaces for events that took place in 2020, and like the Hotel California, I can sign out any time I like but I can never leave. Whenever I set up Slack on a new device, they’re all still listed there. I have Telegram chats and WhatsApp groups and Signal chats for old events… and, once in a while, somebody will try to get hold of me via one of those chats. You know if you archive a WhatsApp chat it doesn’t tell other people you’ve archived it… so they have no idea you’re not seeing their messages?&lt;/p&gt;

&lt;p&gt;Go on. Delete your old event chats - and I mean delete. Gone. Forever.&lt;/p&gt;

&lt;p&gt;Think how much better you’ll feel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If it was up to me?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Set up a Slack a few months early. Use it to coordinate programme committee, crew, volunteers. Create an announcements channel, a speaker channel and a hallway track for general chit-chat. A few days before you open the doors, invite everybody who’s going to be there. Keep it open for a week or so after the event. Post an announcement that you’ll be shutting it down, give folks the chance to share their LinkedIn, Twitter, email - and then delete the workspace.&lt;/p&gt;

</description>
          <pubDate>2024-01-18T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2024/01/18/choosing-a-chat-app-for-your-tech-event.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2024/01/18/choosing-a-chat-app-for-your-tech-event.html</guid>
        </item>
      
    
      
        <item>
          <title>Fixing &apos;Version conflict detected for Microsoft.CodeAnalysis.Common&apos;</title>
          <description>&lt;p&gt;I’ve been upgrading projects from .NET 7 to .NET 8, and mostly it’s been remarkably smooth: edit the .csproj files, replace net7.0 with net8.0, then upgrade any 7.x NuGet packages to their 8.x counterparts, and so far, it’s just worked.&lt;/p&gt;

&lt;p&gt;Today I got a weird error message:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;error: NU1107: Version conflict detected for Microsoft.CodeAnalysis.Common. Install/reference Microsoft.CodeAnalysis.Common 4.5.0 directly to project Rockaway.WebApp to resolve this issue.

error:  Rockaway.WebApp -&amp;gt; Microsoft.EntityFrameworkCore.Tools 8.0.0 -&amp;gt; Microsoft.EntityFrameworkCore.Design 8.0.0 -&amp;gt; Microsoft.CodeAnalysis.CSharp.Workspaces 4.5.0 -&amp;gt; Microsoft.CodeAnalysis.Common (= 4.5.0)
error:  Rockaway.WebApp -&amp;gt; Microsoft.VisualStudio.Web.CodeGeneration.Design 7.0.10 -&amp;gt; Microsoft.DotNet.Scaffolding.Shared 7.0.10 -&amp;gt; Microsoft.CodeAnalysis.CSharp.Features 4.4.0 -&amp;gt; Microsoft.CodeAnalysis.Common (= 4.4.0).
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I’m a big fan of error messages that tell you how to fix the error which caused them, like “Install/reference Microsoft.CodeAnalysis.Common 4.5.0 directly to project Rockaway.WebApp to resolve this issue.” in this one… except, if you look at the notes for &lt;a href=&quot;https://www.nuget.org/packages/Microsoft.CodeAnalysis.Common/&quot;&gt;Microsoft.CodeAnalysis.Common&lt;/a&gt;, it quite clearly says:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“Do not install this package manually, it will be added as a prerequisite by other packages that require it.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here’s what’s actually going on.&lt;/p&gt;

&lt;p&gt;Before upgrading, my project uses two NuGet packages, both version 7.x because they shipped with .NET 7:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Microsoft.EntityFrameworkCore.Tools&lt;/code&gt; 7.0.12&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Microsoft.VisualStudio.Web.CodeGeneration.Design&lt;/code&gt; 7.0.10&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both of these packages have an indirect dependency on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Microsoft.CodeAnalysis.Common 4.4.0&lt;/code&gt; – this doesn’t appear anywhere in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.csproj&lt;/code&gt; file, so I’m guessing it’s controlled by this line:&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;IncludeAssets&amp;gt;&lt;/span&gt;runtime; build; native; contentfiles; analyzers; buildtransitive&lt;span class=&quot;nt&quot;&gt;&amp;lt;/IncludeAssets&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So you try to upgrade &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Microsoft.EntityFrameworkCore.Tools&lt;/code&gt; to 8.x and it fails, because that package requires &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Microsoft.CodeAnalysis.Common&lt;/code&gt; 4.5.0, but your project includes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Microsoft.VisualStudio.Web.CodeGeneration.Design&lt;/code&gt; 7.x, which requires &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Microsoft.CodeAnalysis.Common&lt;/code&gt; v4.4.0&lt;/p&gt;

&lt;p&gt;The solution that worked for me was to remove the other package (v7.x), upgrade EF Core Tools, then install the other package (which installs the latest version, 8.x):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dotnet remove package Microsoft.VisualStudio.Web.CodeGeneration.Design
dotnet add package Microsoft.EntityFrameworkCore.Tools --version 8
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
          <pubDate>2024-01-17T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2024/01/17/version-conflict-detected-for-microsoft-codeanalysis-common-(copy).html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2024/01/17/version-conflict-detected-for-microsoft-codeanalysis-common-(copy).html</guid>
        </item>
      
    
      
        <item>
          <title>The Hang of Thursdays #7</title>
          <description>&lt;p&gt;Hey folks. So… you know the thing where you agree to do something, because it’s literally &lt;em&gt;months&lt;/em&gt; away, and you’ll totally have time to get it all prepared by then — and then somehow it’s not months away any more, it’s two weeks… and it’s not one thing, it’s about five things?&lt;/p&gt;

&lt;p&gt;Welcome to my week! I’m updating the course materials for the &lt;a href=&quot;https://cphdevfest.com/workshops/modern-web-development-with-c-and-net/957e969dd365&quot;&gt;workshop I’m giving at the Copenhagen Developers Festival&lt;/a&gt;, all about how to build web apps with C# using all the amazing new shiny stuff that ships with .NET 7. &lt;em&gt;(Did I mention that tickets are still on sale, and as well as the workshops there’s an amazing programme of talks, shows and live entertainment?)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I’m also figuring out how to fit Guitaraoke on an aeroplane to take it to Denmark, preparing a new talk about how I created Guitaraoke (including a bit about how to fit it all on an aeroplane and take it to Denmark) — oh, and planning my 45th birthday party, which is not only going to be awesome, but is also going to be the public beta test of Guitaraoke: Portable Edition, including a bunch of changes to the software, which I’m going to do on *&lt;looks at=&quot;&quot; calendar=&quot;&quot;&gt;* Saturday morning, about eight hours before the show.&lt;/looks&gt;&lt;/p&gt;

&lt;p&gt;So, yes. Life is a little crunchy, and for some reason in the middle of all this I’m writing a newsletter to tell you all about how crunchy it is.&lt;/p&gt;

&lt;p&gt;I may have chosen… poorly.&lt;/p&gt;

&lt;h2 id=&quot;moq-sponsorlink-and-open-source-funding&quot;&gt;Moq, Sponsorlink, and Open Source Funding&lt;/h2&gt;

&lt;p&gt;If you follow any of the .NET content on Twitter, Reddit, Github, or just about any online community with a strong .NET presence, you’ll have caught last week’s drama about Moq.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2023/substack-a2815a8a-b66a-4b05-b859-cb9ed0a04b59_6000x4000.jpeg&quot; alt=&quot;A photograph of a cute otter making a funny face. She has cute little otter paws and does not want you to get angry about NuGet packages.&quot; style=&quot;zoom:80%;&quot; /&gt;&lt;/p&gt;
&lt;figcaption&gt;If you find reading about open source makes you angry, please take a moment to contemplate this otter. Look at her little otter hands! Aaah… isn’t that better?  Photo © Ondrej Chvatal / Shutterstock.&lt;/figcaption&gt;

&lt;p&gt;Whatever you think about the specifics of this scenario, it got a lot of people talking about open source funding again — and that’s a good thing, because open source is completely broken. Companies like Meta, Google, Apple — not to mention your bank, your airline, your car, and every single app you have installed on your smartphone — use open source software. It’s a fact of modern software development. But they’re not giving anything back. These companies use open source libraries to create products that turn over hundreds of millions of dollars in revenue, and the developers who create and maintain those libraries are struggling to make ends meet.&lt;/p&gt;

&lt;p&gt;David Whitney wrote about this way back in 2021, in his fantastic essay “&lt;a href=&quot;https://davidwhitney.co.uk/Blog/2021/12/13/open_source_exploitation&quot;&gt;Open Source Exploitation&lt;/a&gt;”, and he recorded a &lt;a href=&quot;https://www.youtube.com/watch?v=CYKdNphIr-k&quot;&gt;.NET Rocks episode&lt;/a&gt; on the same topic with Carl Franklin and Richard Campbell at NDC Oslo last year. SixLabors, the creators of the excellent ImageSharp library, have been similarly vocal — and eloquent — about &lt;a href=&quot;https://sixlabors.com/posts/license-changes/&quot;&gt;making license changes&lt;/a&gt; they consider necessary to create sufficient sustainable income to support the development of their product.&lt;/p&gt;

&lt;p&gt;I’ve been involved with open source since the 1990s, and one of the most consistent tenets of open source evangelism is the principle that &lt;em&gt;if the project maintainers do something you don’t like, you can keep using the code you’ve already got.&lt;/em&gt; That’s one of the key value propositions of using open source software in the first place: &lt;em&gt;it gives you control&lt;/em&gt;. Nobody can take it away, nobody can stop you using it, and nobody can force you to upgrade to a version you don’t want to use.&lt;/p&gt;

&lt;p&gt;The folks who created Moq owe you &lt;em&gt;absolutely nothing&lt;/em&gt; unless you are in possession of a commercial support contract that says otherwise. It’s open source. You don’t like what they did with 4.20? Fork 4.19. Maintain that fork. It’s yours now; you can do whatever you want to with it. You need a feature? Build it. You find a bug? Fix it.&lt;/p&gt;

&lt;p&gt;The people saying we all need to remove Moq from our code right now? How about just… don’t upgrade? Nobody’s forcing you to. If Moq 4.19 solved your problems last month, Moq 4.19 will still solve your problems this month, and next month. Besides, I’d wager good money that the people obsessing about a minor point upgrade are probably the same people who are still running .NET Framework 2.0 in production.&lt;/p&gt;

&lt;p&gt;This is a topic I’ll come back to in a future article, but for now, I wanted to leave you with a number to think about. The creator of Moq, Daniel Cazzulino, aka &lt;a href=&quot;https://twitter.com/kzu&quot;&gt;kzu&lt;/a&gt;, wrote a &lt;a href=&quot;https://www.cazzulino.com/software-as-music.html&quot;&gt;great article in February&lt;/a&gt; in which he compared open source licensing models to music subscription platforms. I thought this was an interesting idea, and I wondered what the numbers would look like.&lt;/p&gt;

&lt;p&gt;NuGet has a page at https://www.nuget.org/stats/packages where they publish the total package downloads for the past 6 weeks. At the time of writing, Moq is the 12th most popular package on NuGet, with 19,992,598 downloads.&lt;/p&gt;

&lt;p&gt;Spotify pays around $0.003 per stream.&lt;a href=&quot;#footnote-1&quot;&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If NuGet paid as well as Spotify, Moq would have earned $63,576.46 in revenue in the past six weeks. That’s over $10,000 per week.&lt;/p&gt;

&lt;p&gt;Just putting the number out there.&lt;/p&gt;

&lt;h2 id=&quot;london-net-september-meetup&quot;&gt;London .NET September Meetup&lt;/h2&gt;

&lt;p&gt;The next London .NET meetup is going to be on September 13th, at Accurx’s office in Shoreditch.&lt;/p&gt;

&lt;p&gt;We’ve got &lt;a href=&quot;https://twitter.com/lottepitcher?lang=en&quot;&gt;Lotte Pitcher&lt;/a&gt; coming along to give us an introduction to Umbraco for .NET developers. Umbraco’s the leading content management system (CMS) on the .NET platform; it’s free, it’s open source, and it’s supported by one of the most committed developer communities I’ve ever seen. Lotte’s going to give us the lowdown on all the latest Umbraco features, like headless content management and EF Core support, as well as everything a .NET developer needs to know to get started with the latest release.&lt;/p&gt;

&lt;p&gt;Our second guest speaker is &lt;a href=&quot;https://twitter.com/david_whitney&quot;&gt;David Whitney&lt;/a&gt; — yes, the same one — talking about how he built a Gameboy emulator, for fun, in C#. I’ve seen a version of this talk before and I absolutely loved it: building an emulator means tackling some properly hardcore tech problems, and David’s an excellent speaker who always manages to strike a great balance between engaging storytelling and gritty technical detail.&lt;/p&gt;

&lt;p&gt;If either or both of those sounds like your kind of thing, registration’s open now — sign up and come along!&lt;/p&gt;

&lt;p&gt;https://www.meetup.com/london-net-user-group/events/295373351/&lt;/p&gt;

&lt;h2 id=&quot;ultraram&quot;&gt;ULTRARAM&lt;/h2&gt;

&lt;p&gt;So, this week I learned that (1) there’s a Flash Memory Summit in Santa Clara, (2) it has an awards ceremony, (3) one of the awards is for “Most Innovative Flash Memory Startup”, and (4) it’s just &lt;a href=&quot;https://www.electronicsweekly.com/news/business/825195-2023-08/&quot;&gt;been won by something called ULTRARAM&lt;/a&gt;, invented at the University of Lancaster, which sounds like it could be an absolute game-changer.&lt;/p&gt;

&lt;p&gt;We’ve always had to choose between two kinds of digital storage: volatile and non-volatile. Volatile memory — DRAM — is fast, but it’s expensive, and it only works as long as it’s drawing power. You turn off the power, anything you haven’t saved to disk is gone forever. Non-volatile memory — flash memory, SSDs, mechanical hard drives — is orders of magnitude slower, but it’s also orders of magnitude larger, and doesn’t require power to remember what’s stored on it. ULTRARAM sounds like the best of both worlds: high-capacity, non-volatile memory that’s as fast as DRAM.&lt;/p&gt;

&lt;p&gt;It’s early days yet, but if it turns out to be viable, ULTRARAM would mean we could build devices that drew literally&lt;a href=&quot;#footnote-2&quot;&gt;2&lt;/a&gt; zero power while they were in sleep mode, and woke up instantly when you pressed a key or moved the mouse.&lt;/p&gt;

&lt;p&gt;Their official site is at &lt;a href=&quot;https://ultraram.tech/&quot;&gt;ultraram.tech&lt;/a&gt;, and Dave James has a good &lt;a href=&quot;https://www.pcgamer.com/ultraram-may-be-a-silly-name-but-its-the-holy-grail-for-memory-tech-and-means-your-pc-could-hibernate-for-over-1000-years/&quot;&gt;write-up over at pcgamer.com&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;this-week-ive-been&quot;&gt;This week I’ve been…&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Reading&lt;/strong&gt;: “&lt;a href=&quot;https://maryrobinettekowal.com/writing/the-calculating-stars/&quot;&gt;The Calculating Stars&lt;/a&gt;” by Mary Robinette Kowal. There’s something wonderful about finishing a book by an author you’ve not read before, and that you thoroughly enjoyed, and then discovering there’s &lt;em&gt;all this other stuff they wrote&lt;/em&gt; — and so, after finishing “The Spare Man”, I’m working through her back catalogue. “The Calculating Stars” is the story of Dr. Elma York’s endeavours to become the first woman in space, set against an immaculately realised alternative history of the United States of America in the mid-20th century.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listening to:&lt;/strong&gt; “&lt;a href=&quot;https://open.spotify.com/album/5VPHis9kklY9G0JbggEHtq&quot;&gt;Drive&lt;/a&gt;” by The Defiants, featuring current and former members of Danger Danger. “Drive” is apparently their third album — I honestly had no idea they existed until a review popped up in one of my social media feeds earlier this week, so I looked them up on Spotify, and I’ve had this album on repeat ever since. It’s not challenging, it’s not progressive, it’s not revolutionary — it’s just a big fat slab of very good, very slick, 80s-inspired melodic hard rock, and in a few places Paul Laine’s vocal sounds uncannily reminiscent of Jon Bon Jovi circa “New Jersey”.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watching:&lt;/strong&gt; Nothing. Seriously. Which is a little weird… when I decided to include a weekly rundown of my media habits, I honestly didn’t think there would be weeks when I watched literally zero minutes of film or TV, but here we are; there’s not been a whole lot of downtime this week and what little I’ve had has been spent reading books and playing guitar. Mind you, last year when I had COVID I watched the whole of Stranger Things in a week — I binged season 1 on Monday, season 2 on Tuesday, and so on, because I wanted to know why everybody was going on about “Master of Puppets” — so I guess it all averages out in the end.&lt;/p&gt;

&lt;p&gt;And that’s a wrap. Have fun, take it easy, be excellent to each other (yes, &lt;em&gt;especially&lt;/em&gt; when talking about open source on Reddit), and catch you next week.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Dylan&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#footnote-anchor-1&quot;&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;According to &lt;a href=&quot;https://producerhive.com/music-marketing-tips/streaming-royalties-breakdown/&quot;&gt;producerhive.com&lt;/a&gt;, Deezer pays $0.0011, Apple pays $0.008 and Tidal pays a whopping 1.28 cents per stream.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#footnote-anchor-2&quot;&gt;2&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yes, literally, as in &lt;em&gt;literally&lt;/em&gt; literally.&lt;/p&gt;
</description>
          <pubDate>2023-08-17T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2023/08/17/the-hang-of-thursdays-007.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2023/08/17/the-hang-of-thursdays-007.html</guid>
        </item>
      
    
      
        <item>
          <title>The Hang of Thursdays #6</title>
          <description>&lt;p&gt;Hey folks. I’m still down in Camber, on the south coast of England, but the weather this week has cheered up significantly; blue skies, sunshine, a nice gentle breeze… which means it’s been a perfect week to sit at a laptop doing workshop prep, booking travel for the autumn conference season, designing new stickers and swag, and generally being a bit of a nerd.&lt;/p&gt;

&lt;h2 id=&quot;dr-timezone-and-the-airport-mayhem-autumn-2023-edition&quot;&gt;Dr. Timezone and the Airport Mayhem: Autumn 2023 Edition&lt;/h2&gt;

&lt;p&gt;Europe kinda shuts down over the summer. When I first set up my own company back in January 2020 (yeah, I know, timing, LOL), I talked to a lot of folks I knew who’d done something similar, and one thing that really stuck with me was a great bit of advice from &lt;a href=&quot;https://about.me/kevlin&quot;&gt;Kevlin Henney&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“Don’t worry too much about getting bookings in July and August, because even if the person who wants to hire you isn’t on holiday, the accountant who’s supposed to pay your invoice probably is.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s turned out to be great advice so far: budget for two quiet months in summer (and two quiet months over Christmas), and just accept you’re not going to be doing much. In fact, one of the things I enjoy most about being self-employed is the absence of busywork. When I’m busy, I’m &lt;em&gt;busy&lt;/em&gt; — but when I’m not? No big deal. Chill out, design some stickers, write a few songs, do a little carpentry, and only occasionally wake up in a cold sweat at 4 am wondering what I’ll do if things never pick up again. But so far, they always have.&lt;/p&gt;

&lt;p&gt;Here’s what picking up again looks like. On Thursday 24 August I’m flying to Denmark. The Linebreakers have booked a long weekend in Rågeleje, on the north coast of Zealand, where we’ll get to actually do some proper rehearsal for a change. Then on Sunday we head to Copenhagen for the CPH Developers Festival. Two days of in-person workshops about full stack web development with C# and .NET, followed by three days of conference/festival. Home for six days, then off to Nottingham, where I’m running a speaker workshop for the folks who have been selected to speak at &lt;a href=&quot;https://dddeastmidlands.com/&quot;&gt;DDD East Midlands&lt;/a&gt; this year. I &lt;em&gt;love&lt;/em&gt; DDDEM; it’s a fantastic conference run by awesome people, and offering this kind of opportunity to their speakers is just one of the many, many ways they’re helping to support the developer community.&lt;/p&gt;

&lt;p&gt;I’ll get home from Nottingham late on Saturday night, and have a day at home. Monday 11 September I’m on a Eurostar to Rotterdam and then the Intercity to Utrecht, where I’m a keynote speaker at the &lt;a href=&quot;https://tweakers.net/partners/devsummit2023/1900/sprekerstracks/&quot;&gt;Tweakers Developer Summit&lt;/a&gt;. Then I’m heading back first thing on Wednesday morning to host the London .NET User Group meetup.&lt;/p&gt;

&lt;p&gt;The following week I’m at &lt;a href=&quot;https://digit.dev/&quot;&gt;Digit 2023&lt;/a&gt; in Tartu, Estonia, where I’m presenting a keynote, running a seminar, and doing the music at the after-party. I spoke at Digit last year and had a great time, and Estonia is a wonderful country to visit. The only slight complication is that, although Tartu &lt;em&gt;does&lt;/em&gt; have an airport&lt;a href=&quot;#footnote-1&quot;&gt;1&lt;/a&gt;, as of September 2022 it doesn’t have any scheduled commercial flights — so getting there involves flying to Tallinn via Helsinki and taking a local train.&lt;/p&gt;

&lt;p&gt;Two days at home. Back on Eurostar to Brussels, onwards to Antwerp to run a day of speaker training with the crew from Axxes, then on to Amsterdam for &lt;a href=&quot;https://drivun.co/&quot;&gt;DrivUn&lt;/a&gt; on 27 September, where I’m doing a keynote and the Linebreakers are performing at the conference party.&lt;/p&gt;

&lt;p&gt;After that, we have a Schrõdingerian bit of scheduling where I might be coming home or I might be going to Malmõ&lt;a href=&quot;#footnote-2&quot;&gt;2&lt;/a&gt;. Then from the start of October through to December I’m doing a talk at the University of Sussex, DDD East Midlands in Nottingham, a talk at the &lt;a href=&quot;https://www.meetup.com/birmingham-dotnet-and-xamarin-user-group/events/293566551/&quot;&gt;.NET group in Birmingham&lt;/a&gt;, then I’m off to &lt;a href=&quot;https://ndcporto.com/&quot;&gt;NDC Porto&lt;/a&gt; in Portugal, &lt;a href=&quot;https://codecamp.ro/conferences/codecamp_iasi/&quot;&gt;CodeCamp Iasi&lt;/a&gt; &lt;em&gt;(pronounced “yash”)&lt;/em&gt; in Romania, &lt;a href=&quot;https://www.xpandconf.com/&quot;&gt;XPand 2023 in Jordan&lt;/a&gt;, probably another trip to Copenhagen, &lt;a href=&quot;https://www.buildstuff.events/&quot;&gt;BuildStuff in Lithuania&lt;/a&gt;, and then at the start of December I’m doing the YOW! Tour of Australia: back-to-back conferences in &lt;a href=&quot;https://yowcon.com/melbourne-2023&quot;&gt;Melbourne&lt;/a&gt;, &lt;a href=&quot;https://yowcon.com/brisbane-2023&quot;&gt;Brisbane&lt;/a&gt;, and &lt;a href=&quot;https://yowcon.com/sydney-2023&quot;&gt;Sydney&lt;/a&gt;, and a few days in Perth because it turns out I know a surprising number of excellent people who live there.&lt;/p&gt;

&lt;p&gt;There’s about 50 hours of Zoom training and online stuff sprinkled in there, too, and that’ll &lt;em&gt;probably&lt;/em&gt; be a wrap for 2023… but hey, if you see a gap somewhere that lines up with your event (or, even better, you want to book me to run a workshop or give a talk in a country I’ll already be visiting!), &lt;a href=&quot;mailto:dylan@dylanbeattie.net&quot;&gt;get in touch&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On a completely unrelated note, my latest hot startup idea is a service where you drop off your suitcase full of dirty laundry when you land at your home airport, tell them what your next flight is, and they unpack, wash, dry, repack, and check your suitcase in for your next flight for you. If anybody wants to invest £50 million in my startup, I promise to think about really hard for a few months and then explain why it didn’t work.&lt;/p&gt;

&lt;h2 id=&quot;new-stickers&quot;&gt;New Stickers&lt;/h2&gt;

&lt;p&gt;I love parodies, I love stickers, I love rock bands… and I love banging things together to see what happens. Here’s the latest from the bit of my brain that likes to play with Adobe Illustrator:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2023/substack-6f2c0158-dc5e-4c5a-93fc-2d4270a56510_1920x1080.jpeg&quot; alt=&quot;A parody of the Guns n&apos; Roses band logo. The words GUNS N  ROSES have been replaced with ONES N ZEROS, the pistols on the original logo are stylised metal digit 1s, and the roses have been replaced with stylised red roses in the shape of a digit 0.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Welcome to the jungle, baby, we’ve got video games.&lt;/p&gt;

&lt;p&gt;I’ll be ordering a bunch of these designs from the lovely people at &lt;a href=&quot;https://stickerapp.co.uk/&quot;&gt;StickerApp&lt;/a&gt; very soon, along with a few other new designs and fresh batches of perennial favourites like the “certified Rockstar developer” stickers. For now, the only way to get them is in person, so come along to one of the events I’m at and ask nicely — but I’m hoping to have some kind of online ordering available later this year, for all you folks out there who think a set of parody rockstar developer stickers would make an ideal Christmas gift.&lt;/p&gt;

&lt;p&gt;Incidentally, the “rose shaped like a zero” that appears in this design marks the first time I’ve ever used AI in any of my sticker artwork; it was generated by Midjourney. Asking for a “digit one number 1, steel, gun metal, polished metal, photograph”, and countless other variations on the same theme, didn’t even come &lt;em&gt;close&lt;/em&gt; to producing anything usable, so the rest is 100% good old organic human creativity.&lt;/p&gt;

&lt;p&gt;Want this in your inbox? Sure you do. Then you can read it in your email and it’ll look like you’re working! Go on. Subscribe. It’ll be fun.&lt;/p&gt;

&lt;h2 id=&quot;this-week-ive-been&quot;&gt;This week I’ve been…&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Watching:&lt;/strong&gt; Honestly? Not a lot, unless walking on the beach watching juvenile herring gulls counts as watching. They’re very cute, and extremely squeaky.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2023/substack-c001d238-6891-4a13-99ff-e2f8b3edfcb0_5184x3456.jpeg&quot; alt=&quot;A young herring gull, with brown and white plumage, is hassling its parent, a slightly larger bird with white and grey feathers. The parent does not look amused.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;“Mum! Mum! I want chips. Can we get chips? I’m bored. I want a snack! MUM! That man has chips. I’m gonna steal his chips. MUM WATCH THIS… Mum?”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listening to:&lt;/strong&gt; One from the vaults this week: “&lt;a href=&quot;https://en.wikipedia.org/wiki/Dead_Air_for_Radios&quot;&gt;Dead Air for Radios&lt;/a&gt;” by Chroma Key. I was a &lt;em&gt;huge&lt;/em&gt; Dream Theater fan in the 1990s — or perhaps it’s more accurate to say that I’m still a huge fan of the music Dream Theater released in the 1990s; “Awake” is one of my favourite albums of all time, and a big part of that is down to the musical influence of their original keyboard player, Kevin Moore.&lt;/p&gt;

&lt;p&gt;Kevin left the band after releasing “Awake”, and “Dead Air for Radios” was his first solo project, released under the band name Chroma Key. It’s an album of textures: haunting, lyrical soundscapes, stark piano over rich waves of ambient synth; the sound of a man who walked out of one of the most promising prog metal bands in history, sold his belongings, packed what was left into a station wagon and hit the open road.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reading:&lt;/strong&gt; “&lt;a href=&quot;https://www.goodreads.com/book/show/55077658-the-spare-man&quot;&gt;The Spare Man&lt;/a&gt;” by Mary Robinette Kowal. Another one from this year’s Hugo Awards shortlist, it’s a rollicking good murder mystery disguised as science fiction. There’s enough hard science here for the premise, a murder on board a passenger spacecraft in transit between Earth and Mars, to ring true. The fun, though, is in the characters, the plot twists, and the occasional sense that you’re enjoying a literary cocktail that’s one part “&lt;a href=&quot;https://en.wikipedia.org/wiki/Spaceship_Medic&quot;&gt;Spaceship Medic&lt;/a&gt;”, one part “&lt;a href=&quot;https://en.wikipedia.org/wiki/Murder_on_the_Orient_Express&quot;&gt;Murder on the Orient Express&lt;/a&gt;”, and a generous dash of something you can’t quite place but it’s really rather good.&lt;/p&gt;

&lt;p&gt;And that’s a wrap, folks. Take it easy, have fun, and be excellent to each other.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Dylan&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#footnote-anchor-1&quot;&gt;1&lt;/a&gt; The international airport code for Tartu is TAY, and before Finnair shut down the only commercial route late last year, you could fly from TAY to HEL and back.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#footnote-anchor-2&quot;&gt;2&lt;/a&gt; A certain tolerance for uncertainty is very much a prerequisite for this kind of job… it turns out that the most mysterious part of being an International Man of Mystery is never knowing whether a provisional booking is actually going to happen or not.&lt;/p&gt;
</description>
          <pubDate>2023-08-10T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2023/08/10/the-hang-of-thursdays-006.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2023/08/10/the-hang-of-thursdays-006.html</guid>
        </item>
      
    
      
        <item>
          <title>The Hang of Thursdays #5</title>
          <description>&lt;p&gt;Hello! This week’s THOTH is coming to you from Camber, a tiny village on the south coast of England, where I’m staying at my parents’ house for a couple of weeks while they’re away. This week so far has been torrential rain and 40 mph winds — aka “traditional English seaside holiday weather” — so the usual Camber activities of walking on the beach, swimming in the sea and cycling into Rye to enjoy its many excellent pubs have been postponed in favour of sitting indoors poking the internet.&lt;/p&gt;

&lt;h2 id=&quot;the-framework-16-laptop&quot;&gt;The Framework 16 laptop&lt;/h2&gt;

&lt;p&gt;My main travel laptop these days is a &lt;a href=&quot;https://frame.work/gb/en&quot;&gt;Framework 13&lt;/a&gt;. It’s a fantastic machine that I love dearly, and I was really excited when Framework announced earlier this year that they’d be launching a 16” modular laptop. Looks like I wasn’t the only one: the Framework 16 opened for pre-orders last week, and the first batch sold out in less than 12 hours.&lt;/p&gt;

&lt;p&gt;substack.dylanbeattie.net is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.&lt;/p&gt;

&lt;p&gt;Sean Hollister at The Verge has &lt;a href=&quot;https://www.theverge.com/22665800/framework-laptop-16-hands-on-preview-modular-gaming-laptop&quot;&gt;a great write-up&lt;/a&gt; about what makes it special — and how Framework are hoping to succeed where so many other brands have failed in delivering the first laptop that genuinely supports a modular, replaceable gaming GPU.&lt;/p&gt;

&lt;p&gt;I haven’t ordered one yet… but I know a few folks who have, and I suspect that after I get my hands on one of theirs to try out, it won’t be long before I’m putting in an order for my own.&lt;a href=&quot;#footnote-1&quot;&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;cree-and-ancestral-code&quot;&gt;Cree# and Ancestral Code&lt;/h2&gt;

&lt;p&gt;This has been around for a few years, but I’d never heard of it until somebody pinged me a link last week: Jon Corbett has created a programming language based on Cree, an Indigenous language spoken by around 120,000 people across Canada. Cree# isn’t just another esoteric language, though; it’s been explicitly created to help preserve the Cree language and culture. As Jon puts it:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“My primary target communities here are Cree communities that are looking for new (and exciting) ways to encourage students (especially in the K-12 grades) to use their heritage language as much as possible, and resist using English as their primary language.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2023/substack-92d7e97a-d41d-446c-b8b6-20b6797603c7_839x830.jpeg&quot; alt=&quot;A radial computer keyboard, where each key is labelled with glyphs used in the Cree alphabet&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Jon Corbett’s design for a keyboard (Creeboard?) based on the Cree star chart, from https://esoteric.codes/blog/jon-corbett&lt;/p&gt;

&lt;p&gt;Cree#’s syntax and conventions reflect the visual art and storytelling traditions of the culture that inspired it, and it’s not quite like anything I’ve seen before.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://esoteric.codes/blog/jon-corbett&quot;&gt;Interview with Jon Corbett at esoteric.codes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://news.ycombinator.com/item?id=26771369&quot;&gt;Cree# on Hacker News&lt;/a&gt; (April 2021)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;fun-with-printers&quot;&gt;Fun with Printers&lt;/h2&gt;

&lt;p&gt;For somebody who works with code, web pages and digital media, I print a lot. I use printed notes and schedules when I’m running online workshops, I rely on printed checklists when I’m packing for conference trips and gigs, and I print proofs of my sticker designs and other odd bits of artwork.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2023/substack-3d4bca3e-4b4f-422e-adc1-bc27da88a6e9_1000x1034.jpeg&quot; alt=&quot;CDN media&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Printers get a bad rap. Sure, you can buy a colour inkjet printer for £50. That’s &lt;em&gt;ridiculously&lt;/em&gt; cheap. Printers are big and they’re packed with moving parts; you can’t sell a printer for £50 and make a profit. No, a £50 printer is a loss leader. A £50 printer is a way to get you to spend another £50 on cartridges every few months — cartridges that have microchips in them, which &lt;a href=&quot;https://www.cnet.com/tech/services-and-software/lexmark-invokes-dmca-in-toner-suit/&quot;&gt;in some cases&lt;/a&gt; have been used to store encryption keys so that anybody trying to reverse-engineer them to create compatible low-cost replacements can be prosecuted under the Digital Millennium Copyright Act.&lt;/p&gt;

&lt;p&gt;Last month, my printer died. Something something print head won’t align beep beep no more prints for you. It was a Canon Pixma iP4900 that I’d salvaged from an office clear-out nearly 10 years ago; apart from costing me a small fortune on ink cartridges over the years, it was fantastic.&lt;/p&gt;

&lt;p&gt;My new printer’s also a Canon. It’s a &lt;a href=&quot;https://www.canon.co.uk/printers/pixma-g6050/&quot;&gt;Canon Pixma G6050&lt;/a&gt;. It didn’t cost £50; it cost £240. It’s an all-in-one printer, scanner and colour copier; it’s got built-in wired and wireless ethernet; but most important of all: &lt;strong&gt;it doesn’t use cartridges&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2023/substack-4f6b4b34-9942-472d-941c-c63154ceae91_2028x1576.jpeg&quot; alt=&quot;a photograph of an inkjet printer with four plastic bottles of printer ink.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If you really want, you can mix all the inks together to make Brown Flavour!&lt;/p&gt;

&lt;p&gt;See that? Bottles of ink. Just ink. No microchips, no DRM. Just a couple of big old plastic tanks full of ink. I particularly love that instead of angry messages about how it can’t print because it’s out of cyan, there’s just a message in the UI that says “Hey, ah, please check there’s ink in the tanks before printing and if there isn’t then maybe don’t print right now?”&lt;/p&gt;

&lt;p&gt;It’s been a month, and I’ve printed about 150 pages; so far I’m delighted with it, and I’m sure that when I eventually have to fork out the princely sum of £19.99 for a full set of ink refills I’ll be even more delighted.&lt;/p&gt;

&lt;h2 id=&quot;ndc-london-call-for-papers&quot;&gt;NDC London Call for Papers&lt;/h2&gt;

&lt;p&gt;NDC London, one of my favourite conferences, will be back in January 2024, and the Call for Papers is open until 1 September. If you’d like to be part of it, this is your chance. I’m on the programme committee; we’re looking for talks on everything from embedded systems to ethics to web assembly to language models. What are you and your team working on? What problems are you trying to solve? How did you get there?&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://sessionize.com/ndc-london-2024/&quot;&gt;NDC London Call for Papers at Sessionize&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wrote a blog post a while ago about how the selection process works, which is still very much relevant, so maybe take a look at &lt;a href=&quot;https://dylanbeattie.net/2017/10/17/why-you-werent-picked-for-ndc-london.html&quot;&gt;Why Your Talk Wasn’t Picked For NDC London&lt;/a&gt; before submitting anything.&lt;/p&gt;

&lt;h2 id=&quot;kevin-mitnick-rip&quot;&gt;Kevin Mitnick RIP&lt;/h2&gt;

&lt;p&gt;I was sad to see the news that Kevin Mitnick has died from pancreatic cancer. Mitnick was probably the world’s most famous computer hacker; his exploits were legendary in the fledgling infosec community when I was an undergraduate back in the 1990s, and following his release from prison in 2000 he reinvented himself as a security consultant.&lt;/p&gt;

&lt;p&gt;My favourite Mitnick story has nothing to do with computers. As a teenager, he noticed that when the buses in his home town of Los Angeles broke down or were taken out of service, passengers would be issued with transfer tickets so they could switch to another bus and continue their journey. Mitnick found a stack of unused transfer tickets in a dumpster near the bus station, and realised that if he could find a way to validate them, he could ride the buses for free — so he told staff he was working on a school project about transportation and wanted to buy a ticket punch for his project, and voilà! Free bus rides. I always thought that was a particularly elegant hack: observe the system, find the loopholes, wait for a lucky break, and round it all off with a little social engineering.&lt;/p&gt;

&lt;p&gt;Want this in your inbox? Sure you do. Then you can read it in your email and it’ll look like you’re working! Go on. Subscribe. It’ll be fun.&lt;/p&gt;

&lt;h2 id=&quot;this-week-ive-been&quot;&gt;This week I’ve been…&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Watching:&lt;/strong&gt; &lt;a href=&quot;https://www.imdb.com/title/tt1517268/&quot;&gt;Barbie&lt;/a&gt; — at the first completely sold-out cinema showing I’ve been to since 2019. It’s good. It’s very good. Charming, visually stunning, and laugh-out-loud funny.&lt;/p&gt;

&lt;p&gt;Special shout out to the most unlikely credit you’re likely to see in a motion picture soundtrack any time soon, for “I’m Just Ken” — written by Mark Ronson, performed by Ryan Gosling, and featuring Wolfgang van Halen and Slash. Now &lt;em&gt;that’s&lt;/em&gt; a supergroup I’d pay good money to see in concert.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listening to:&lt;/strong&gt; “&lt;a href=&quot;https://www.youtube.com/watch?v=4tBF7x0d1CY&quot;&gt;Wild in the City&lt;/a&gt;”, the new single from Nitrate. Founded by songwriter Nick Hogg, Nitrate are a melodic rock band from Nottingham whose last album “&lt;a href=&quot;https://open.spotify.com/album/3rUgNcaIBXsF73ZWsWCn7A&quot;&gt;Renegade&lt;/a&gt;” has been top of my Spotify playlist for months. Slick, polished 80s-style rock, luscious waves of shimmering synthesizers and screaming guitars, and some seriously catchy hooks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reading:&lt;/strong&gt; “&lt;a href=&quot;https://www.youtube.com/watch?v=k59iTTdt6G4&quot;&gt;DOOM Guy: Life in First Person&lt;/a&gt;” by John Romero. John’s autobiography is as much the story of id Software as it is the story of his own life, from the infamous Super Mario PC demo to Doom, Quake, Daikatana and beyond. I think it’s fair to say that John is better at writing code than he is at writing prose; there’s a wealth of detail here, but the writing can get a little clunky from time to time. Nevertheless, John remains one of the world’s most celebrated programmers, and the story of how a bunch of long-haired D&amp;amp;D fans revolutionised the gaming industry is compelling.&lt;/p&gt;

&lt;p&gt;And that’s a wrap, folks. Take it easy, have fun, and be excellent to each other.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Dylan&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#footnote-anchor-1&quot;&gt;1&lt;/a&gt; It’s my eyes, you see. 13” screens just aren’t &lt;em&gt;quite&lt;/em&gt; big enough for all the really important programming work I have to do. Honest.&lt;/p&gt;
</description>
          <pubDate>2023-08-03T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2023/08/03/the-hang-of-thursdays-005.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2023/08/03/the-hang-of-thursdays-005.html</guid>
        </item>
      
    
      
        <item>
          <title>The Hang of Thursdays #4</title>
          <description>&lt;p&gt;&lt;em&gt;Grüße aus Berlin!&lt;/em&gt; I’m in town for the WeAreDevelopers World Congress (WADWC), so this week’s newsletter is all about what’s happening, who’s here, and what they’re talking about. And, yes, it’s Friday. I know. Relax. It’s fine.&lt;/p&gt;

&lt;p&gt;This event is &lt;em&gt;huge&lt;/em&gt; — somebody at the speaker dinner last night said there were 12,000 people here, and having been on site for two days, I can easily believe it. Fifteen stages, hundreds of speakers and sessions, two expo halls; it’s so big it fills both buildings of the Berlin Messe convention centre, and outside in the plaza area between the building there are more stages, food trucks and a few exhibitor stands. It’s absolutely colossal, and I can’t hope to do more than scratch the surface… but here are the bits I did manage to see.&lt;/p&gt;

&lt;h2 id=&quot;tim-berners-lee-and-the-future-of-the-open-web&quot;&gt;Tim Berners-Lee and the Future of the Open Web&lt;/h2&gt;

&lt;p&gt;The big name on this year’s programme at WADWC is Sir Tim Berners-Lee, inventor of the World Wide Web, whose opening keynote was an engaging riff on the history and future of the open web. Tim’s main research project for the past few years has been &lt;a href=&quot;https://solidproject.org/&quot;&gt;SOLID&lt;/a&gt;, an initiative to add authentication, identity management and standardised data APIs to the web, and he talked about the challenges of decentralising identity, the problems with the data silos that have emerged post web-2.0 — “If you want to share a LinkedIn post with your Twitter followers, you can’t; you have to create a Twitter account to do that” — and the kinds of apps that we could build on top of decentralised identity and authentication.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2023/substack-f358a4e3-28b6-42eb-a6de-ccd41e123919_4032x3024.jpeg&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;He also talked about AI, which is very much a recurring theme at WADWC this year, and in particular the idea that we should have access to AIs that work for us — “the way your doctor works for you, or your lawyer works for you; they have access to your private data, and you trust them not to share it.”&lt;/p&gt;

&lt;p&gt;The keynote was followed by a “fireside chat” session with Tim and &lt;a href=&quot;https://www.linkedin.com/in/seadahmetovic/?originalSubdomain=de&quot;&gt;Sead Ahmetovic&lt;/a&gt;, the CEO of WeAreDevelopers, about the web, AI, and the career challenges which developers face in 2023. A couple of quotes really stood out for me. On whether he’d do anything differently if he was creating the web over again, Tim said:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“I used the domain name system, and at the time it was managed by people like Jon Postel, and managed in a very responsible way; it wasn’t just a fighting ground for investors. So maybe that was a mistake, or maybe it’s something we can fix.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I love DNS, and I think it’s a fantastic system that works incredibly well; I’ve always thought the design of DNS was one of the key factors behind the success of the web, so it was interesting to hear that the creator of the web thinks DNS was a weak point.&lt;/p&gt;

&lt;p&gt;On whether developers should generalise or specialise:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“From the point of view of running open source projects, as the CTO of a company that needs talent: it’s good that there’s a variety of talent. We need generalists, we need specialists, we need T-shaped people, or pi-shaped people, where you’ve got two deep specialities.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s the first time I’d ever heard the phrase “pi-shaped”, and I think it’s delightful.&lt;/p&gt;

&lt;p&gt;The quote that stuck with me the most, though, was when he was asked about social and ethical responsibility:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“I suppose… one way to think about it is when you write your line of code, when you write a function: work local, but think global. Code it as though you are creating part of a huge, wonderful, very powerful system, that people are using to build effective democracy, effective science, to allow society to function much better than it does.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I loved that.&lt;/p&gt;

&lt;h2 id=&quot;john-romero-on-creating-doom&quot;&gt;John Romero on Creating Doom&lt;/h2&gt;

&lt;p&gt;The afternoon keynote speaker was John Romero, sharing the story of how Id Software created Doom in 12 months.&lt;/p&gt;

&lt;p&gt;It’s no exaggeration to say that Doom revolutionised computer gaming. It introduced the world to multiplayer network gaming. It pioneered the free-to-play model by making episode 1 a free download, back in the days of dial-up bulletin board systems. The 3D engine was more sophisticated than anything we’d ever seen before, the music still triggers pangs of nostalgia in an entire generation of gamers — and the word “deathmatch” was created to describe Doom’s competitive multiplayer mode.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2023/substack-2d165add-9f94-4adf-8c57-196f9ca92492_4032x3024.jpeg&quot; alt=&quot;John Romero on stage, in front of a slide showing DOOM: 30 Years of RIP and TEAR. John wears a red and white floral shirt, black jeans, New Rock boots. He looks fabulous. &quot; /&gt;&lt;/p&gt;

&lt;p&gt;John Romero at WeAreDevelopers World Congress 2023&lt;/p&gt;

&lt;p&gt;I’ve geeked out over the history of Id Software before; my talk “&lt;a href=&quot;https://www.youtube.com/watch?v=9CSjlZeqKOc&quot;&gt;The Web That Never Was&lt;/a&gt;” even has a whole alternate-history riff on what might have happened if the two Johns had been hired by Nintendo after they ported Super Mario to the IBM PC. Even so, I learned a few things watching John’s talk that I’d never heard of before, like the story of John Carmack walking through the snow to the bank to withdraw $11,000 in cash, so he could pay cash-on-delivery for his new NeXT workstation.&lt;/p&gt;

&lt;p&gt;John also revealed that 20th Century Fox offered Id the “Aliens” licence — cue a few astonished gasps from the audience — “but it only had aliens; there weren’t any demons. We talked about it for, like, half an hour, and decided no”.&lt;a href=&quot;#footnote-1&quot;&gt;1&lt;/a&gt; We also learned that Carmack’s decision to use BSP trees as the game’s main data structure came after a sunken octagonal staircase caused a recursion bug that slowed gameplay down to a crawl and sent Carmack digging through computer science papers looking for a solution.&lt;/p&gt;

&lt;h2 id=&quot;other-highlights&quot;&gt;Other Highlights&lt;/h2&gt;

&lt;p&gt;I caught &lt;a href=&quot;https://www.thegiftcode.dev&quot;&gt;Gift Egwuenu&lt;/a&gt;’s session “Going Beyond Passwords: The Future of User Authentication”, which was a great overview of the history of password-based security, what’s wrong with it, and the current state of authentication — single sign-on, multifactor auth systems, and how security standards like FIDO fit into your system’s authentication flow. I “met” Gift at a Zoom workshop I ran back in 2020, when we were all doing everything online; she’s been doing all sorts of awesome things, so it was great to finally meet her in person.&lt;/p&gt;

&lt;p&gt;Stack Overflow has a huge presence here. They sponsored the speaker dinner/reception event on Wednesday night, and they’re taking advantage of WADWC to announce a whole load of new features — basically their vision for incorporating generative AI into the Stack Overflow platform. I must say that Prashanth Chandrasekar’s product announcement session didn’t really grab me, but then later Prashanth returned to the stage alongside Joel Spolsky (you know, Joel on Software, founder of Stack Overflow, FogBuz, CityDesk, etc.) and Sead Ahmetovic for a Q&amp;amp;A session about the past and future of Stack Overflow. One comment that stood out for me was Joel talking about embracing the idea of Google as a user interface:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“When we built Stack Overflow, we knew that Google was going to be the UI. We weren’t expecting people to come to Stack Overflow; we expected them to Google their problem and find the answer on Stack Overflow. Now, ChatGPT, and to a lesser extent Copilot, have become the frontend to Stack Overflow’s knowledge base. You don’t just get the answer, you get the answer modified to suit your codebase, ready to copy and paste into your solution, and I think that’s great; it makes life so much easier for developers.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;They talked a great deal about trust as well. Prashanth shared the statistic that, according to the most recent Stack Overflow developer survey, “70% of developers want to use AI tools, but only 40% of them trust AI”. He discussed the work they’ve been doing to close that gap, particularly with regard to sources and citations so a developer can find out exactly where the AI found the solution to a particular problem.&lt;/p&gt;

&lt;p&gt;Personally, I’m optimistic about it all. Code reuse is clearly a good idea. Prashanth used the example of parsing phone numbers, which I thought was great: as a user, it sucks using platforms that keep rejecting your phone number as invalid, but it also doesn’t make sense for every dev team out there to build their own algorithm for parsing and validating phone numbers. We’ve had healthy package management ecosystems for long enough now to have a pretty good idea of what they can do, and there’s clearly still a whole class of problems out there that we don’t want to solve from scratch every time, but that don’t really lend themselves to importing a package or standard library.&lt;/p&gt;

&lt;p&gt;The challenge, of course, is that AI is easily capable of producing an infinite amount of crap, and if we train the next generation of language models on the SEO clickbait produced by the previous generation, we’re in a race to the bottom in terms of quality. We’ll see how we go.&lt;/p&gt;

&lt;h2 id=&quot;rocking-in-the-rain&quot;&gt;Rocking in the rain&lt;/h2&gt;

&lt;p&gt;And then there’s my bit — two live sets of developer comedy rock’n’roll. I was playing on the Airstream Stage, a converted Airstream trailer outside next the food trucks and beer tents… which was absolutely fantastic until it started raining about an hour before I was due to go on… but you know what? I’m British. I’ve survived dozens of English summers and five Glastonbury Festivals,  and it takes more than a bit of rain to stop the show.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2023/substack-37fd5b25-2f4d-47f4-8633-6cb912855bc8_4032x3024.jpeg&quot; alt=&quot;A handwritten set list next to a Zoom G1on guitar pedal. Both are soaked with rain.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I think I figured out where “Wet Wet Wet” got their name.&lt;/p&gt;

&lt;p&gt;Thankfully, the audience agreed. Somebody even came up to me afterwards and said “you got our lead developer to sing along — we didn’t think we’d ever see that!”&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2023/substack-05fe871a-cd94-4950-847f-e7fb9e992485_2048x1536.jpeg&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;On stage with Hila Fish and Keren Kenzi at WeAreDevelopers World Congress&lt;/p&gt;

&lt;p&gt;Huge thanks to everybody who came along — and especially to &lt;a href=&quot;https://twitter.com/Hilafish1&quot;&gt;Hila Fish&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/KerenKenzi&quot;&gt;Keren Kenzi&lt;/a&gt; for some outstanding (and unrehearsed!) guest vocal performances. And a shout out to &lt;a href=&quot;https://frame.work/at/en&quot;&gt;Framework&lt;/a&gt;, whose 13” laptop turns out to be mostly showerproof as well as fast, light, modular and generally awesome.&lt;/p&gt;

&lt;p&gt;And that was that, apart from a few well-earned beers and the obligatory post-gig currywurst and Fritz-kola.&lt;/p&gt;

&lt;p&gt;Time to grab lunch, say hello and goodbye to a few people, and head to the airport.&lt;/p&gt;

&lt;p&gt;Catch you next time.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Dylan&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#footnote-anchor-1&quot;&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few years later, the “&lt;a href=&quot;https://www.moddb.com/mods/alien-quake&quot;&gt;Alien Quake&lt;/a&gt;” community mod turned the Quake engine into aliens vs space marines, and did a much better job than the official Alien vs Predator game.&lt;/p&gt;
</description>
          <pubDate>2023-07-28T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2023/07/28/the-hang-of-thursdays-004.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2023/07/28/the-hang-of-thursdays-004.html</guid>
        </item>
      
    
      
        <item>
          <title>The Hang of Thursdays #3</title>
          <description>&lt;h2 id=&quot;coding-streams-on-twitch-and-youtube&quot;&gt;Coding Streams on Twitch and YouTube&lt;/h2&gt;

&lt;p&gt;I spent a substantial chunk of last week building a web app for running guitar karaoke nights. I streamed the whole thing live&lt;a href=&quot;#footnote-1&quot;&gt;1&lt;/a&gt;, which means if you really want to, you can watch almost the entire development of the app on video — all &lt;a href=&quot;https://www.youtube.com/playlist?list=PLw0jj21rhfkOZ2qu5zREX4xTQC0NoKbUe&quot;&gt;19 hours and 31 minutes of it&lt;/a&gt;. (And for the record: I ran it on Saturday night, and it worked flawlessly. There were a few features I’ll add for next time, and I definitely need to add some kind of security to make sure we don’t get internet weirdos signing up and messing with the stats,  but it worked. Which is nice.)&lt;/p&gt;

&lt;p&gt;I really enjoy those kind of live-streaming coding sessions. It’s fun, I enjoy the folks asking questions in chat, but I also find that knowing there’s even a handful of people out there watching what I’m doing really helps my focus.&lt;/p&gt;

&lt;p&gt;I’d like to do a lot more streaming, but averaged over, say, a year, I probably only spend about 30% of my working time actually writing code — and at least half of that is stuff I’d never stream because it’s full of customer API keys and other stuff I don’t want going anywhere near a Twitch stream.&lt;/p&gt;

&lt;p&gt;I do a lot of other stuff that might work well on a stream, though. Designing stickers in Illustrator; preparing workshop material and demos, making PowerPoint decks; I figure maybe some folks out there might like to see how it all comes together, so keep an eye on my &lt;a href=&quot;https://www.twitch.tv/dylanbeattie&quot;&gt;Twitch&lt;/a&gt; or &lt;a href=&quot;https://www.youtube.com/dylanbeattie&quot;&gt;YouTube&lt;/a&gt; channels if you’re interested.&lt;/p&gt;

&lt;p&gt;When I hit a thousand subscribers, I’ll release a new version of Rockstar. Go on. Sign up. Make me regret saying that.&lt;/p&gt;

&lt;h2 id=&quot;is-this-react-or-nuxt&quot;&gt;“Is this React or Nuxt?”&lt;/h2&gt;

&lt;p&gt;Among the many fascinating questions that viewers shared in the chat during last week’s live stream, one the really stuck with me was somebody who dropped in while I was &lt;a href=&quot;https://github.com/guitaraoke/app.guitaraoke.live/blob/main/GuitaraokeWebApp/wwwroot/js/script.js&quot;&gt;adding some client-side JavaScript&lt;/a&gt; to handle a few specific interaction scenarios, and asked “is this React or Nuxt”?&lt;/p&gt;

&lt;p&gt;I often wonder if there’s a generation of developers out there now who have been so immersed in frameworks and abstractions for their entire careers that they literally don’t grok what JavaScript actually is. Then I wonder if there was a generation of machine code programmers who felt the same way about those young whippersnappers with their assembly languages, and then a generation of assembly hackers who didn’t trust C compilers, and C programmers who didn’t trust Java, and that maybe that’s just the way the world works. I suspect that &lt;a href=&quot;https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/&quot;&gt;Spolsky’s Law of Leaky Abstractions&lt;/a&gt; remains as relevant now as it was 20 years ago. You want to do React or Nuxt? Cool — but you probably need to know enough JavaScript to understand what’s happening when the abstractions break down.&lt;/p&gt;

&lt;p&gt;On the other hand, I watched &lt;a href=&quot;https://twitter.com/binjimint&quot;&gt;Ben Smith&lt;/a&gt;’s &lt;a href=&quot;https://joyofcoding.org/speakers/ben-smith.html&quot;&gt;presentation at Joy of Coding&lt;/a&gt; in Rotterdam last month. Ben codes demos, in web assembly, by hand: less than 2 kilobytes of hand-coded instructions that’ll run a &lt;a href=&quot;https://binji.github.io/posts/raw-wasm-making-a-maze-race-part-2/&quot;&gt;maze race game&lt;/a&gt; right in your web browser, without a framework or compiler in sight. Yep, turns out that hand-coded assembly is alive and well right here in 2023. Pointless? Maybe. But sometimes, the point of a good demo is to remind you that not everything needs to have a point.&lt;/p&gt;

&lt;h2 id=&quot;we-are-developers-world-congress-in-berlin&quot;&gt;We Are Developers World Congress in Berlin&lt;/h2&gt;

&lt;p&gt;July was supposed to be downtime. No conferences, no gigs, no travel, just a nice quiet month at home — catch up with friends, work on some new talks, workshops, write a couple of songs… you know the bit in the agile manifesto where it talks about “responding to change over following a plan?”&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2023/substack-9b9c3d6a-3eac-453e-9585-6f8bb4976eec_1280x720.jpeg&quot; alt=&quot;A &amp;quot;speaker card&amp;quot; from We Are Developers World Congress, advertising Dylan Beattie Live: Specs, Bugs &apos;n&apos; Rock&apos;n&apos;Roll. 27-28 July, Berlin CityCube. Also a grinning picture of me before I had any grey in my beard. I gotta get some new pictures, you know.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Yep, next week I’m off to Berlin to do my developer comedy rock music thing at We Are Developers World Congress. This event is &lt;em&gt;huge&lt;/em&gt; — not only in terms of attendance, but it’s also the first time I’ve been on a conference bill alongside names like Tim Berners-Lee, John Romero, and Joel Spolsky. Y’know, the guys who invented (or co-invented) the web, the first person shooter, and Stack Overflow, respectively. Plus a whole bunch of other amazing speakers — and did I mention it’s in Berlin, one of the coolest cities in the world? Tickets &lt;a href=&quot;https://www.wearedevelopers.com/world-congress/tickets&quot;&gt;are on sale now&lt;/a&gt; — €579, but they’re doing a €299 ticket for early-stage startups and a €199 ticket for students. I’ll be rocking out on the Airstream Stage on Thursday evening; if you’re there, come &amp;amp; say hi. I might even be giving out stickers.&lt;/p&gt;

&lt;h2 id=&quot;the-horizon-it-scandal&quot;&gt;The Horizon IT Scandal&lt;/h2&gt;

&lt;p&gt;In my talk “&lt;a href=&quot;https://www.youtube.com/watch?v=Vk2fi7NZ3OQ&quot;&gt;Failure is Always an Option&lt;/a&gt;”, I share the story of the &lt;a href=&quot;https://www.postofficescandal.uk/&quot;&gt;Horizon IT scandal&lt;/a&gt; which led Post Office Limited, the UK company that administers high street post office branches, to falsely prosecute more than 700 employees between 2000 and 2014 after a faulty IT system reported accounting discrepancies and financial shortfalls. Horizon is &lt;a href=&quot;https://www.theguardian.com/business/2023/jul/17/post-office-inquiry-chair-criticises-horizon-compensation-scheme&quot;&gt;back in the news&lt;/a&gt; this week, after the chair of the inquiry expressed concerns about whether the compensation programme set up in the wake of the scandal would be able to deliver on their commitment to fully compensate the affected parties by August next year.&lt;/p&gt;

&lt;p&gt;Liability has always been a tough question when it comes to software engineering: compared to industries like healthcare and aviation, even the most cautious software projects still look like four drunks wrestling for control of a stolen car. We can’t build perfect software; in a world where we write abstractions on top of abstractions on top of abstractions, there are just too many variables — often both literally and figuratively. What I believe sets the Horizon scandal apart, though, is the managers’ and executives’ absolute refusal to acknowledge the possibility that the software might be to blame.&lt;/p&gt;

&lt;p&gt;Software has bugs, and if somebody is prepared to testify under oath that it doesn’t, they’re either lying, or they’re stupid&lt;a href=&quot;#footnote-2&quot;&gt;2&lt;/a&gt; — or quite possibly both. It’d just be nice to see the executives behind these kind of fiascos face some consequences once in a while.&lt;/p&gt;

&lt;h3 id=&quot;this-week-ive-been&quot;&gt;This week I’ve been…&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Listening to:&lt;/strong&gt; &lt;a href=&quot;https://extreme-band.com/&quot;&gt;“SIX” by Extreme&lt;/a&gt;. I suspect I’m not the only Extreme fan who loves every moment of their second album&lt;a href=&quot;#footnote-3&quot;&gt;3&lt;/a&gt; but then finds most of their subsequent output rather less compelling. SIX is growing on me, though. A little whimsical in places, and a track or two that wouldn’t be out of place on a Muse album, but when SIX  kicks into gear, it kicks &lt;em&gt;hard&lt;/em&gt;. Nuno Bettencourt remains one of the finest guitar players the world has ever seen — check out &lt;a href=&quot;https://www.youtube.com/watch?v=fqkKFhFMaIw&quot;&gt;this video&lt;/a&gt; of Queen’s Brian May reacting to Nuno’s solo on Get The Funk Out; it’s simply wonderful — and on tracks like BANSHEE and lead single RISE, the guitar work here is just sublime. No idea why they set all the song titles in CAPITALS, though.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watching:&lt;/strong&gt; &lt;a href=&quot;https://www.imdb.com/title/tt1462764/&quot;&gt;Indiana Jones and the Dial of Destiny&lt;/a&gt;. Yes, they made another Indiana Jones film. Yes, Harrison Ford is 81 years old. Yes, the fourth one was a horrible mistake. Yes, that sound you can hear is &lt;em&gt;taurus pecunias&lt;/em&gt; going “please, father, I’m so tired… let me sleep now?”&lt;/p&gt;

&lt;p&gt;The thing is, I &lt;em&gt;love&lt;/em&gt; cinema. A film has to be very, very bad indeed for me not to enjoy it on the big screen, and Indy V is not bad at all. The story is endearingly daft, there’s the odd spot of slightly wonky CGI, and plot holes you can fly a plane though — quite literally, in one case. But the cast are on cracking form; it has charm, and pace, and is packed with the kind of set-pieces that are so much fun to watch you don’t even think about how daft the whole thing is until afterwards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reading:&lt;/strong&gt; &lt;a href=&quot;https://www.waterstones.com/book/nettle-and-bone/t-kingfisher/9781803360997&quot;&gt;Nettle and Bone&lt;/a&gt; by T. Kingfisher. I knew nothing about this book other than that it made the shortlist for this year’s &lt;a href=&quot;https://www.thehugoawards.org/&quot;&gt;Hugo Awards&lt;/a&gt;. I’m planning to read — or at least start reading — all six shortlisted novels between now and the awards ceremony in October; I’d already read and thoroughly enjoyed John Scalzi’s &lt;em&gt;The Kaiju Preservation Society&lt;/em&gt;, but T. Kingfisher is a new name to me, and so far I like it a lot. If dark fantasy with witches and dustwives and bone dogs and even an actual fairy godmother sounds like your kind of thing, check it out.&lt;/p&gt;

&lt;p&gt;Yeah, I know. You never subscribe to things. Go on. Break the chains of your conditioning. Dare to dream the impossible dream. SUBSCRIBE!&lt;/p&gt;

&lt;p&gt;And that’s the week, folks. Next week’s issue will be a special THOTH&lt;a href=&quot;#footnote-4&quot;&gt;4&lt;/a&gt; from We Are Developers World Congress in Berlin, and quite probably a review of &lt;a href=&quot;https://en.wikipedia.org/wiki/Barbenheimer&quot;&gt;#barbenheimer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Until then, stay safe, have fun, and be excellent to each other.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Dylan&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#footnote-anchor-1&quot;&gt;1&lt;/a&gt; At the moment, I’m using &lt;a href=&quot;http://restream.io&quot;&gt;restream.io&lt;/a&gt; to stream to both Twitch and YouTube; Twitch generally seems to be a better platform for this kind of streaming, but I have way more followers on YouTube so I’m hedging my bets for now.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#footnote-anchor-2&quot;&gt;2&lt;/a&gt; Ok, or maybe they work for NASA.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#footnote-anchor-3&quot;&gt;3&lt;/a&gt; Which I shan’t name here, because the title contains a naughty word and the spam filters won’t like it.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#footnote-anchor-4&quot;&gt;4&lt;/a&gt; The Hang Of Thursdays. THOTH — you know, the &lt;a href=&quot;https://en.wikipedia.org/wiki/Thoth&quot;&gt;Egyptian god of maths, science and magic&lt;/a&gt;.&lt;/p&gt;
</description>
          <pubDate>2023-07-28T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2023/07/28/the-hang-of-thursdays-003.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2023/07/28/the-hang-of-thursdays-003.html</guid>
        </item>
      
    
      
        <item>
          <title>Travelling with Tiny Guitars</title>
          <description>&lt;p&gt;Last week &lt;a href=&quot;https://twitter.com/gutek/status/1679569628211605512&quot;&gt;Jakub Gutkowski asked on Twitter&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;a href=&quot;https://twitter.com/dylanbeattie&quot;&gt;@dylanbeattie&lt;/a&gt; hey, what travel guitar (when you fly etc) are you using? Thanks!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sounds like a really good opportunity to geek out about guitars for a bit.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2023/substack-4786ef17-eed2-4250-bac4-64660ba50f36_650x825.jpeg&quot; alt=&quot;Six Steinberger Spirit headless guitars.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The current Steinberger Spirit guitar line-up. Gotta catch ‘em all… right?&lt;/p&gt;

&lt;p&gt;My travel guitars — I currently own two — are Steinberger Spirit GT-PROs, and there’s a bit of history behind them. They were originally designed by a guy called &lt;a href=&quot;https://www.steinberger.com/Ned-Steinberger-The-Steinberger-Q-and-A-Interview.html&quot;&gt;Ned Steinberger&lt;/a&gt; in the 1980s. Steinberger was a true innovator; while most guitar companies were churning out the same wooden solid-body guitar in various different shapes and colours, Steinberger’s guitars were made of carbon fibre. To eliminate the headstock, he moved the tuning machines into a special bridge, and built his guitars to use specially built strings with a brass ball at both ends.&lt;/p&gt;

&lt;p&gt;Personally, I think Steinberger’s greatest innovation was a thing called the “TransTrem”. See, the tremolo on most guitars will let you wobble the strings around a bit, get some gentle vibrato or a screeching “dive bomb” effect — but when you do, every string detunes by a different amount. The TransTrem used calibrated strings so when you grabbed the trem bar, the strings stayed in tune relative to each other — if you want to see one in action, check out “Get Up”  by Van Halen, from the 5150 album; here’s a live clip from a 1986 show showing Eddie van Halen playing one of his custom red-striped Steinbergers, complete with liberal use of the TransTrem:&lt;/p&gt;

&lt;iframe src=&quot;https://www.youtube-nocookie.com/embed/EreBDnb_V4Y?rel=0&amp;amp;autoplay=0&amp;amp;showinfo=0&amp;amp;enablejsapi=0&quot; frameborder=&quot;0&quot; loading=&quot;lazy&quot; gesture=&quot;media&quot; allow=&quot;autoplay; fullscreen&quot; allowautoplay=&quot;true&quot; allowfullscreen=&quot;true&quot; width=&quot;728&quot; height=&quot;409&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;Steinberger sold the company to Gibson in 1987, who continued to manufacture the guitars until the mid-1990s. Then grunge happened, everybody got really miserable, the music industry unilaterally decided that the 1980s had been a horrible mistake, and Steinberger guitars went out of production for a while.&lt;/p&gt;

&lt;h2 id=&quot;life-the-universe-and-guitars&quot;&gt;Life, the Universe, and… Guitars?&lt;/h2&gt;

&lt;p&gt;I had no idea any of this was going on; I think I might have seen a Steinberger on &lt;em&gt;Top of the Pops&lt;/em&gt; once or twice, but I couldn’t have told you what it was called or anything more about it. Then, during the 1990s, the Guardian newspaper in the UK ran a short-lived column called “Me and My Gizmo”. They’d interview celebrities about a particular tool or piece of technology they loved — explorers talking about their Swiss Army knife, chefs talking about a piece of kitchen equipment. One week, the featured guest was Douglas Adams, talking about his favourite guitar: a white, left-handed Steinberger.&lt;/p&gt;

&lt;p&gt;I’ve been a huge fan of Douglas Adams since I first read &lt;em&gt;The Hitch-Hiker’s Guide to the Galaxy&lt;/em&gt; when I was a kid. I love his writing, I love his fascination with technology and gadgets… and it turned out he was also a huge guitar nerd. I clipped out the column, and it was pinned up above my desk in my bedroom for &lt;em&gt;years&lt;/em&gt;. That original clipping went missing somewhere among my many undergraduate house moves, but with a bit of help from the lovely folks at &lt;a href=&quot;https://zz9.org/&quot;&gt;ZZ9 Plural Z Alpha&lt;/a&gt;, I managed to track down a copy via the newspapers.com online archive. I’ve reproduced it here:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2023/substack-dc59d2f7-fb17-46e8-8e98-d42b8c8f879c_2939x1242.jpeg&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;“ME AND MY GIZMO No 6: Douglas Adams’s guitar”, &lt;em&gt;The Guardian&lt;/em&gt;, June 23, 1994&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;THE STUDY AT the top of Douglas Adams’s house owes less to Ford Prefect than it does to Spinal Tap. The author of the Hitchhiker’s Guide to the Galaxy and other ingenious comic and scientific observations works in a room festooned with guitars. Acoustic guitars, a brand new Santa Cruz guitar customised with exploding earths and dolphins in mother of pearl, ancient electric guitars, and a frightening aquamarine green guitar. Admittedly, it is a techie’s study, with a powerful Macintosh Quadra 950, a couple of lesser discarded Macs, a PowerBook, video entryphone and enough mixing and recording equipment to supply a modest studio, but it is the guitars that dominate. Adams owns about 30, but the one that really got him playing again is a white headless Steinberger. Adams began when he was 12, but the music dwindled as he travelled in his quest for the oddly named endangered fauna recorded in his book Last Chance to See.&lt;/em&gt;&lt;/p&gt;

  &lt;p&gt;&lt;em&gt;Three years ago, he bought the Steinberger secondhand in Hollywood, for about $3,000, from the better known of the world’s two extant left-handed guitar shops (the other is in East London). It has full-length strings, but everything else is pared right down so it looks like a truncated toy. It plugs in to a Walkman-sized amp attachment, which feeds into tiny unfolding headphones, so it is perfect for Adams to take on his travels. He can be a guitar hero in hotel rooms worldwide, and even admits to practising mid-flight, “though you do get some strange looks from other passengers”. The Steinberger is made of graphite, so it is extremely strong. “If Pete Townshend had used one of these,” Adams says, “he would have done more damage to the stage than to the guitar.” Rocker lore is weighing heavy on Adams’s mind, as his buddy Dave Gilmour made him the birthday present of inviting him to join Pink Floyd for one number at Earl’s Court in October. Adams is already frantically practising the tricky full tone bends in Wish You Were Here.&lt;/em&gt;&lt;/p&gt;

  &lt;p&gt;&lt;em&gt;That Floyd influence extends to Adams’s great ambition: to write an album. “Concept is the term I’m trying to avoid, but I’d like to write a whole piece, with a collection of songs that would make a story.” He messes about on his bank of synthesizers, electronically tweaking the sounds and storing them on disk. Performer software analyses each sound – its exact pitch, length and volume – and can transcribe any noise onto a musical score. “Singing is not the high point of my act,” he says, but he demands a whole-hearted and harmonious performance from friends at his annual Christmas carol party. “We don’t go out on the street shaking our tins, but we have a really good sing. We only invite friends who are proper singers.”&lt;/em&gt;&lt;/p&gt;

  &lt;p&gt;&lt;em&gt;Still, Adams is stronger on the qwerty keyboard than the ivories. “I do a bit of Net-surfing, to newsgroups on music, computers, palaeontology, anthropology. And there’s a Douglas Adams fan group, so I have a look there.” His latest plaything is a virtual landscape building program, sort of cyber Capability Brown, for creating arty archipelagos, tormented seas, grassy undulations and other twiddly bits. Another handy piece of software is Now Up-to-Date, a calendar manager that schedules appointments into his wife and sister’s computers. The Adamses live in a well-wired household; when they were redecorating to make way for their imminent baby, Douglas stopped his wife from getting rid of the room’s Ethernet cable. “She’ll be needing that soon”, he predicts, “I’m just trying to work out what computer to get her.” The child will clearly be born with a mouse in her hand.&lt;/em&gt;&lt;/p&gt;

  &lt;p&gt;&lt;em&gt;- Ruth Shurman&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Douglas Adams died in 2001. Then in 2015, his original white Steinberger &lt;a href=&quot;https://web.archive.org/web/20150508163535/http://www.ebay.co.uk/itm/VERY-RARE-STEINBERGER-GL4-TA-LEFT-HANDED-ELECTRIC-GUITAR-OWNED-BY-DOUGLAS-ADAMS-/151666385474&quot;&gt;showed up for sale on eBay&lt;/a&gt; and somebody sent me a link going “hey, look, you can buy Douglas Adams’s guitar!”&lt;/p&gt;

&lt;p&gt;There were only two problems: one, I wasn’t left-handed&lt;a href=&quot;#footnote-1&quot;&gt;1&lt;/a&gt;, and two, I didn’t have three and a half grand knocking around. It got me thinking, though. This was around the time that I started travelling to tech conferences abroad, and writing tech parody songs, and putting them up on YouTube; I’m not saying I &lt;em&gt;needed&lt;/em&gt; a travel guitar, but I really, really &lt;em&gt;wanted&lt;/em&gt; one — and if it could be a white Steinberger like Douglas’s one, then so much the better. So I did a little digging.&lt;/p&gt;

&lt;h2 id=&quot;the-steinberger-spirit-revival&quot;&gt;The Steinberger Spirit revival&lt;/h2&gt;

&lt;p&gt;Turns out Gibson had started making Steinberger-style guitars again in the early 2000s, under the “Spirit” model name. Now, the Steinberger Spirit is a very, very different beast from the original 1980s guitars. They’re made of wood rather than carbon fibre, they don’t have the transposing tremolo, they don’t have active pickups, and they’re not made in the USA… and they cost a few hundred pounds, rather than several thousand.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2023/substack-4168c442-d3a6-438b-bd32-4f54ac6ca722_4032x3024.jpeg&quot; alt=&quot;A white Steinberger Spirit headless guitar&quot; /&gt;&lt;/p&gt;

&lt;p&gt;My white Steinberger Spirit GT-PRO.&lt;/p&gt;

&lt;p&gt;I bought my white Steinberger from &lt;a href=&quot;https://www.guitarguitar.co.uk/&quot;&gt;Guitar Guitar&lt;/a&gt; in Epsom in 2016 for £299. It was love at first strum: I plugged it in, played it for about thirty seconds, and that was it. That guitar’s been to Australia twice, seven trips to Russia, six trips to Ukraine, the USA, Belarus, Israel, and dozens of shows all over Europe — if all those trips were in a straight line it’d be three quarters of the way to the moon by now. I’ve swapped out the regular strap buttons for Straplocks, and moved the neck strap button on to the top shoulder of the body, but apart from that it’s completely stock.&lt;/p&gt;

&lt;p&gt;The first few trips I did with this guitar, I took it in the cabin with me, but flying with a guitar — even a tiny travel one — is a bit of a grey area. Technically, it’s not allowed, because it exceeds the cabin baggage allowance. I’ve never been &lt;em&gt;stopped&lt;/em&gt;, but I’ve also never been explicitly told it’s OK… it was always a case of smile, don’t ask, and hope it works out, and I was never entirely sure what I would do if it &lt;em&gt;didn’t&lt;/em&gt; work out.&lt;a href=&quot;#footnote-2&quot;&gt;2&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2023/substack-99437490-0ca5-48e7-9d21-3c4b8b021244_4032x3024.jpeg&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;These days, it goes in the hold. I have an &lt;a href=&quot;https://www.eastpak.com/uk-en/collections/travel-c8354/transit-r-l-black-pEK-0A5BA9-008-OS--1-.html&quot;&gt;Eastpak roller duffel case&lt;/a&gt; which is big enough to stash my guitar in the bottom, solid enough to keep it safe in transit, and small enough to check as regular baggage, so most trips the guitar goes in the big case with clothes packed around it, and the aluminium flight case with my mixing desk, wireless gear, etc. shoved in the top.&lt;/p&gt;

&lt;p&gt;Yeah, checked baggage doesn’t always arrive in the right place at the right time. I’ve been remarkably lucky with air travel — the only time my bag ever went missing was on the way home from St. Petersburg a few years ago, and I got a phone call less than an hour after I left the airport to say they’d found it. Still, it’s a risk, so a year or so later, I went shopping for a backup guitar in case the primary guitar went walkabout.&lt;/p&gt;

&lt;p&gt;Turns out that the supply chain for these guitars can be a little volatile: none to be had in guitar stores, for love nor money. Eventually I tracked down a second-hand one, but trying to keep it in tune gave me all kinds of headaches. Turns out there was a huge crack in the tremolo block, which was slowly bending out of shape under the tension of the strings, and eventually snapped off altogether.&lt;/p&gt;

&lt;p&gt;That one hung on the wall for a long while, until earlier this year, I decided to overhaul it: a new EVH-inspired paint job, new electrics, a replacement tremolo block, and a headstock adapter so I could use regular strings with it instead of special Steinberger double ball-end strings.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2023/substack-ed3acbb3-1074-485a-85b6-2c6ea88e41fd_4032x3024.jpeg&quot; alt=&quot;An image gallery showing a headless guitar being stripped down, sanded, repainted, and reassembled. By the end, it&apos;s blue with black and white stripes, and looks extremely shiny.&quot; /&gt;&lt;img src=&quot;/images/posts/2023/substack-af526a94-7687-46cc-b23a-fb611def1456_4032x3024.jpeg&quot; alt=&quot;An image gallery showing a headless guitar being stripped down, sanded, repainted, and reassembled. By the end, it&apos;s blue with black and white stripes, and looks extremely shiny.&quot; /&gt;&lt;img src=&quot;/images/posts/2023/substack-c4af7d03-2e54-4464-9a36-bbed93fa4ee8_4032x3024.jpeg&quot; alt=&quot;An image gallery showing a headless guitar being stripped down, sanded, repainted, and reassembled. By the end, it&apos;s blue with black and white stripes, and looks extremely shiny.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2023/substack-78221e59-f13f-47dc-846b-211fb531d35e_4032x3024.jpeg&quot; alt=&quot;An image gallery showing a headless guitar being stripped down, sanded, repainted, and reassembled. By the end, it&apos;s blue with black and white stripes, and looks extremely shiny.&quot; /&gt;&lt;img src=&quot;/images/posts/2023/substack-e175319d-0bfc-4115-9e5a-202a0bc67d44_4032x3024.jpeg&quot; alt=&quot;An image gallery showing a headless guitar being stripped down, sanded, repainted, and reassembled. By the end, it&apos;s blue with black and white stripes, and looks extremely shiny.&quot; /&gt;&lt;img src=&quot;/images/posts/2023/substack-8886e0d9-3a12-4545-823c-a53ccb38076e_4032x3024.jpeg&quot; alt=&quot;An image gallery showing a headless guitar being stripped down, sanded, repainted, and reassembled. By the end, it&apos;s blue with black and white stripes, and looks extremely shiny.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2023/substack-afa5d582-d0ed-4d6a-a72d-a59cdf813ef8_4032x3024.jpeg&quot; alt=&quot;An image gallery showing a headless guitar being stripped down, sanded, repainted, and reassembled. By the end, it&apos;s blue with black and white stripes, and looks extremely shiny.&quot; /&gt;&lt;img src=&quot;/images/posts/2023/substack-76af56f1-d0ee-413e-91e6-ab9e1d78beb8_4032x3024.jpeg&quot; alt=&quot;An image gallery showing a headless guitar being stripped down, sanded, repainted, and reassembled. By the end, it&apos;s blue with black and white stripes, and looks extremely shiny.&quot; /&gt;&lt;img src=&quot;/images/posts/2023/substack-a6e31eaf-bd03-4101-99c2-a46c99ba7c64_4032x3024.jpeg&quot; alt=&quot;An image gallery showing a headless guitar being stripped down, sanded, repainted, and reassembled. By the end, it&apos;s blue with black and white stripes, and looks extremely shiny.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Overhauling a Steinberger Spirit GT-PRO guitar.&lt;/p&gt;

&lt;p&gt;I’m really happy with the new paint job and the electrics… but the new trem block and the string adapter, not so much. It’s not easy to find authentic Steinberger replacement parts, so I ordered after-market replacements which are apparently made by OVERLORD OF MUSIC, and, well, they’re terrible. The string adapter lasted about four hours before tuning problems drove me to put the original headstock back on, and I ended up completely dismantling the new tremolo assembly and rebuilding the original around the replacement block from the new one, and I’m still not entirely convinced.&lt;/p&gt;

&lt;p&gt;Replacement part woes aside, they are genuinely wonderful guitars. They’re fun, they look fantastic, they sound great and play remarkably well considering they retail for under £500. Given that the famously fickle supply chain appears to be firing on all cylinders right now, I suspect that before long I might end up buying another one.&lt;/p&gt;

&lt;p&gt;After all, no matter how many guitars you already have, the ideal number of guitars to own is N+1.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#footnote-anchor-1&quot;&gt;1&lt;/a&gt; I’m still not, in case you’re wondering.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#footnote-anchor-2&quot;&gt;2&lt;/a&gt; On my fourth, maybe fifth, trip to Russia, the attendant at the airline check-in desk told me that “guitars were not allowed on flights to Russia”. I explained I’d taken the same guitar on the same airline several times before. She said that wasn’t allowed. I thanked her for the information and said I’d remember it for next time. She handed me my boarding pass, and that was that. Even at the best of times, Russian bureaucracy never made the slightest bit of sense.&lt;/p&gt;
</description>
          <pubDate>2023-07-24T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2023/07/24/travelling-with-tiny-guitars.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2023/07/24/travelling-with-tiny-guitars.html</guid>
        </item>
      
    
      
        <item>
          <title>The Hang of Thursdays #2</title>
          <description>&lt;h2 id=&quot;the-mvp-renewal-cycle-mvpbuzz&quot;&gt;The MVP Renewal Cycle #mvpbuzz&lt;/h2&gt;

&lt;p&gt;This week my networks have been buzzing with people sharing the news that they’d been renewed as a Microsoft MVP. MVPs — “Most Valuable Professionals” — are “technology experts who passionately share their knowledge with the community”. People who work with Microsoft technology and who run meetups, speak at conferences, write books, write blogs, maintain open source projects; activities which aren’t really your &lt;em&gt;job&lt;/em&gt;, but which benefit the wider community.&lt;/p&gt;

&lt;p&gt;Microsoft aren’t the only company that does this; I know a few Google Developer Experts and Java Champions, and I even met a Docker Captain once. I know there are some mixed feelings about these kinds of programmes in general, and the Microsoft MVP programme in particular — usually sentiments along the lines of “wow, you did all this free marketing for a trillion-dollar company and they sent you a certificate!”&lt;/p&gt;

&lt;p&gt;But, whatever you think about Microsoft, corporations, and these kinds of programmes, just about every Microsoft MVP I’ve ever met has been a thoroughly excellent person, and that alone makes it a programme I’m proud to be part of.&lt;/p&gt;

&lt;p&gt;So &lt;strong&gt;congratulations&lt;/strong&gt; to all the Microsoft MVPs out there who got renewed, and particular congratulations to everybody who’s just been awarded for the first time. For me, this will be my seventh award, but I can still remember vividly what it felt like when I got my first MVP award: I was absolutely delighted&lt;em&gt;.&lt;/em&gt; Recognition is a big deal.&lt;/p&gt;

&lt;h2 id=&quot;london-net-meetup-with-angeliki-patsiavou-and-nick-chapsas&quot;&gt;London .NET Meetup with Angeliki Patsiavou and Nick Chapsas&lt;/h2&gt;

&lt;p&gt;Thanks to everybody who came along to our &lt;a href=&quot;https://www.meetup.com/london-net-user-group/&quot;&gt;London .NET User Group&lt;/a&gt; meetup last night — and especially to our hosts &lt;a href=&quot;https://www.codat.io/&quot;&gt;Codat&lt;/a&gt; for providing the venue, drinks, pizzas and snacks.&lt;/p&gt;

&lt;p&gt;Our first speaker was &lt;a href=&quot;https://twitter.com/a_patsiavou?lang=en-GB&quot;&gt;Angeliki Patsiavou&lt;/a&gt;, who presented a brand new talk about building and enabling cross-functional teams inspired by “The Avengers”, packed with insight and humour — not to mention one of the best slide decks I’ve seen in a while.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2023/substack-f225516e-5b2d-4a2d-9cc1-b97af6d3aa15_6000x3368.jpeg&quot; alt=&quot;img&quot; /&gt;&lt;img src=&quot;/images/posts/2023/substack-2a1d8c87-8f43-4a14-b1b4-919b365eea4b_6000x3368.jpeg&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2023/substack-c0fcfc97-9944-40f6-ae23-7d89988681c5_6000x3368.jpeg&quot; alt=&quot;img&quot; /&gt;&lt;img src=&quot;/images/posts/2023/substack-1600c0ea-8fd3-4dcf-8965-7d9078139f13_6000x3368.jpeg&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;London .NET meetup at Codat, July 12 2023&lt;/p&gt;

&lt;p&gt;Next up was &lt;a href=&quot;https://www.youtube.com/@nickchapsas&quot;&gt;Nick Chapsas&lt;/a&gt;, with a great talk which was ostensibly about logging in .NET, but actually took us on a deep dive into the gritty internals of how strings are managed by the .NET runtime and how something as seemingly innocuous as formatting your log messages can actually have a big impact on your application’s performance and memory consumption.&lt;/p&gt;

&lt;p&gt;For all the folks asking “did you record it?” — not this time, sorry! (although there’s a version of Nick’s talk from NDC Oslo &lt;a href=&quot;https://www.youtube.com/watch?v=NlBjVJPkT6M&quot;&gt;available on YouTube&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;There’s good news on that front, though. LDNUG is taking a break in August, but when we come back in September, we’re planning to video all the talks from our meetups and share them online, so those of you who weren’t able to join us in person can catch up afterwards.&lt;/p&gt;

&lt;h2 id=&quot;rejected-github-profile-achievements&quot;&gt;Rejected GitHub Profile Achievements&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/Flet&quot;&gt;Flet&lt;/a&gt; has created this &lt;a href=&quot;https://github.com/Flet/rejected-github-profile-achievements&quot;&gt;list of rejected GitHub Profile Achievements&lt;/a&gt;, which made me laugh out loud. I’ve definitely earned a few Procrastinators, two Secret Santas that I’m aware of, and at least one Sith Lord from the days when my team was switching from Subversion to git — but I think my most interesting achievement would have been a Tee Hee. Many years ago, I had a web project that I was working on using both Windows and macOS, and I thought it’d be a good idea to share the project folder from macOS via SMB and then map a Windows drive to it.&lt;/p&gt;

&lt;p&gt;Turns out that if you clone a Git repo on macOS and then push it on Windows, you mess up every single line ending in the entire project.&lt;/p&gt;

&lt;p&gt;So… yeah, don’t do that.&lt;/p&gt;

&lt;p&gt;Flet’s list is at https://github.com/Flet/rejected-github-profile-achievements if you fancy a laugh. 😁&lt;/p&gt;

&lt;p&gt;You know what would be awesome? Getting enough subscribers on here that I could justify deleting all my other social media accounts. Yeah. That would rule.&lt;/p&gt;

&lt;h2 id=&quot;guitar-karaoke-at-the-copenhagen-developers-festival&quot;&gt;Guitar Karaoke at the Copenhagen Developers Festival&lt;/h2&gt;

&lt;p&gt;At the end of August, I’ll be heading to Denmark along with a lot of other familiar faces from the conference circuit to speak (and play, and sing, and generally be Loud and Extroverted) at the &lt;a href=&quot;https://cphdevfest.com/&quot;&gt;Copenhagen Developers Festival&lt;/a&gt;, a new event from the folks behind NDC Conferences, which combines technical tracks during the day with live music, karaoke, science, gaming, and other fun stuff in the evenings.&lt;/p&gt;

&lt;p&gt;I’m going to be sharing the story of how I created &lt;a href=&quot;https://guitaraoke.live/&quot;&gt;Guitaraoke&lt;/a&gt; — so between now and August, I need to figure out how to virtualise a bunch of the hardware I’ve been using to run it.&lt;/p&gt;

&lt;p&gt;One big part of that is replacing paper sign-up sheets with a web app, which I’m working on this week and streaming live at &lt;a href=&quot;https://www.twitch.tv/videos/1869840548&quot;&gt;twitch.tv/dylanbeattie&lt;/a&gt;. Like all the best software development, the stream so far is about 50% actually adding features, and 50% going “but why is that test failing? &lt;em&gt;why!?!&lt;/em&gt;?”&lt;/p&gt;

&lt;p&gt;I’ll be running the new app for real at the next Guitaraoke night, which is this Saturday 15th July, at &lt;a href=&quot;https://ignition.beer/&quot;&gt;Ignition Brewery&lt;/a&gt; in south-east London. After all, beta testing is best done live, in a brewery, with a guitar in one hand, a beer in the other, and room full of drunk people cheering you on.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2023/substack-c0ea2539-712f-43f4-af0a-f477683550db_6000x4000.jpeg&quot; alt=&quot;A neon sign reads &amp;quot;Ignition Open&amp;quot;; three musicians are playing and singing underneath a projector screen showing the song lyrics and chords. Everybody is very sweaty and there&apos;s cables and guitars everywhere. It looks like fun.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Guitaraoke at Ignition Brewery in London, 20 May 2023&lt;/p&gt;

&lt;h3 id=&quot;modern-web-development-with-c-and-net&quot;&gt;Modern Web Development with C# and .NET&lt;/h3&gt;

&lt;p&gt;I’m also going to be running a two-day workshop in Copenhagen on modern web development with ASP.NET Core. The web is wonderful, .NET rocks, and… well, I don’t like JavaScript frameworks.&lt;a href=&quot;#footnote-1&quot;&gt;1&lt;/a&gt; So I’m going to be showing folks how to build a website entirely in .NET, from frontend features like TagHelpers and SASS compilers, to minimal APIs and controllers, to some of the things .NET doesn’t really do well out of the box — email, timezones, y’know. Nice straightforward easy stuff.&lt;/p&gt;

&lt;p&gt;Check out the workshop here: &lt;a href=&quot;https://cphdevfest.com/workshops/modern-web-development-with-c-and-net/957e969dd365&quot;&gt;&lt;strong&gt;Modern Web Development with C# and .NET&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And if you want to know more about how I prepare these kinds of workshops, you might find &lt;a href=&quot;https://substack.dylanbeattie.net/p/structuring-workshops&quot;&gt;this post interesting&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;prompts-are-unsafe-and-that-means-language-models-are-not-fit-for-purpose&quot;&gt;&lt;strong&gt;“Prompts are unsafe, and that means language models are not fit for purpose”&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;I really liked &lt;a href=&quot;https://softwarecrisis.dev/letters/prompts-are-not-fit-for-purpose&quot;&gt;this article by &lt;strong&gt;Baldur Bjarnason&lt;/strong&gt;&lt;/a&gt; on the challenges of avoiding injection-style attacks when using large language models like ChatGPT. We’ve known about &lt;a href=&quot;https://owasp.org/www-community/attacks/SQL_Injection&quot;&gt;SQL injection attacks&lt;/a&gt; for literally decades; avoiding them is trivial, but they’re still one of the most common vulnerabilities seen in the wild. Securing language models against “jailbreaking” is orders of magnitude more complex than avoiding SQL injection attacks; Baldur’s basically saying we have no idea how to do this so we shouldn’t be integrating language models with external services or exposing them to external users. Interesting and unsettling in equal measure.&lt;/p&gt;

&lt;h2 id=&quot;threads-twitter-and-the-collapse-of-social-media&quot;&gt;Threads, Twitter, and the Collapse of Social Media&lt;/h2&gt;

&lt;p&gt;It’s been an entertainingly chaotic few weeks in the wonderful world of social media. My &lt;a href=&quot;https://substack.dylanbeattie.net/p/the-social-meltdown&quot;&gt;thoughts on the whole thing are here&lt;/a&gt;; I also enjoyed Alex Kirshner’s “&lt;a href=&quot;https://slate.com/technology/2023/07/threads-app-meta-review-twitter-musk-facebook-winner.html&quot;&gt;Meta’s New Threads App is Terrible. It Might Just Bury Twitter&lt;/a&gt;” on Slate.com, but only time will tell whether this is the beginning of the end for social media, or just the end of the beginning.&lt;/p&gt;

&lt;h2 id=&quot;this-week-ive-been&quot;&gt;This week I’ve been…&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Reading&lt;/strong&gt;: “&lt;a href=&quot;https://www.oreilly.com/library/view/code-that-fits/9780137464302/&quot;&gt;Code That Fits in Your Head&lt;/a&gt;” by Mark Seemann. I’ve met Mark at a few conferences over the years, I find the way he talks about software complexity really refreshing, and the book is no exception: a step-by-step, case-by-case walkthrough of building a restaurant reservation system. My personal highlight so far is what Mark calls the &lt;a href=&quot;https://blog.ploeh.dk/2019/10/07/devils-advocate/&quot;&gt;Devil’s Advocate&lt;/a&gt; technique: given a perfectly reasonable-looking unit test, what’s the weirdest, most off-the-wall code you can write to make the test pass — and then how can you improve the test so that it catches those loopholes and edge cases?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watching:&lt;/strong&gt; &lt;a href=&quot;https://en.wikipedia.org/wiki/South_Park_(season_25)&quot;&gt;South Park, season 25&lt;/a&gt;. I signed to Paramount+ to watch Star Trek: Strange New Worlds, which is &lt;em&gt;excellent&lt;/em&gt; — and then found every season of South Park is also on there and I had a bit of catching up to do. I’ve been a fan of South Park since watching season 1 on RealPlayer 25 years ago, and it still makes me laugh until milk comes out of my nose.&lt;/p&gt;

&lt;p&gt;(&lt;em&gt;On a South Park-related note, the show’s creators, Trey Parker and Matt Stone, have recently&lt;/em&gt; &lt;a href=&quot;https://www.nytimes.com/2023/06/06/us/casa-bonita-restaurant.html&quot;&gt;&lt;em&gt;spent literally millions of dollars&lt;/em&gt;&lt;/a&gt; &lt;em&gt;refurbishing and reopening&lt;/em&gt; La Casa Bonita&lt;em&gt;, the Colorado restaurant where they spent their childhood birthdays — complete with waterfalls, cliff divers, puppets, and a “person in a gorilla costume being chased by a sheriff”. I think that all sounds rather lovely.&lt;/em&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listening to:&lt;/strong&gt; Iron Maiden, live at the O2 here in London last Friday. A fantastic live show by a band who show absolutely no signs of slowing down; triple guitar harmonies, thundering backline, and Bruce Dickinson happy bouncing between joking with the crowd, belting out those high notes like an air-raid siren, and exchanging pyrotechnic laser fire with Eddie, Maiden’s monstrous zombie mascot who would occasionally prowl the stage in the form of a giant 12’ puppet.&lt;/p&gt;

&lt;p&gt;Shout out to opening act &lt;a href=&quot;https://www.youtube.com/channel/UCKmi2SZRPoCqXA01qtzIqrw&quot;&gt;Lord of the Lost&lt;/a&gt; as well; as somebody who remembers the Eurovision Song Contest being the least credible thing &lt;em&gt;imaginable&lt;/em&gt;, I find it wonderfully weird that an industrial goth rock band (!) can represent Germany in Eurovision (!!), finish last (!!!) — and then open for Iron Maiden at the O2 in front of over ten thousand people less than two months later. But we do, and they did, and it was very good indeed.&lt;/p&gt;

&lt;p&gt;And that’s the week, folks. Take it easy, stay safe, and be excellent to each other.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Dylan&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#footnote-anchor-1&quot;&gt;1&lt;/a&gt; Apparently “server-side React” is a real thing that people do now. Just… wow.&lt;/p&gt;
</description>
          <pubDate>2023-07-21T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2023/07/21/the-hang-of-thursdays-002.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2023/07/21/the-hang-of-thursdays-002.html</guid>
        </item>
      
    
      
        <item>
          <title>The Hang of Thursdays #1</title>
          <description>&lt;p&gt;Hello! Welcome! So, this is a completely new thing and I really don’t know yet what kind of shape it’s going to take, but as &lt;a href=&quot;https://yungpueblo.com/&quot;&gt;yung pueblo&lt;/a&gt; said:&lt;a href=&quot;#footnote-1&quot;&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;main goal for july: no more waiting.&lt;/em&gt;&lt;/p&gt;

  &lt;p&gt;&lt;em&gt;don’t get stuck in the planning stage.&lt;/em&gt;&lt;/p&gt;

  &lt;p&gt;&lt;em&gt;you know what you need to do.&lt;/em&gt;&lt;/p&gt;

  &lt;p&gt;&lt;em&gt;be bold and start making moves, even if the plan is not clear.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That hit a nerve. I’ve been saying for years that I need to write more. Folks who know me, or follow me online, know I do all kinds of stuff — tech, computers, music, travel. Lots of what I do is quite good fun, and even the bits that aren’t normally throw up the odd funny story along the way. I do a lot of stuff, and I like to write about what I’m doing, but recently a lot of the stuff I write ends up on platforms like Facebook.&lt;/p&gt;

&lt;h4 id=&quot;but-hang-on-dont-you-already-have-a-blog&quot;&gt;…but hang on, don’t you already have a blog?&lt;/h4&gt;

&lt;p&gt;Yeah, I do! I got a whole website over at &lt;a href=&quot;https://dylanbeattie.net/&quot;&gt;dylanbeattie.net&lt;/a&gt;. It’s mine, I own it, I built it myself using Jekyll, it’s currently hosted for free using GitHub Pages.&lt;/p&gt;

&lt;p&gt;But… after five years, I’ve decided that blogging with Jekyll doesn’t work for me. Jekyll and GitHub Pages are brilliant for hosting static web content, but I want to be able to pull out my phone at the airport, connect my Bluetooth keyboard, write some words, paste a couple of photos, push a button, and boom, it’s live. That’s why so much of what I write ends up on Facebook instead of my own site, but I’ve been following a few folks who publish on Substack, and I like what I hear, I decided to give it a shot.&lt;a href=&quot;#footnote-2&quot;&gt;2&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So here it is: a weekly update on where I’m going, what I’m doing, and anything I thought was interesting. Like it? Want more? Here’s a button!&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;%%checkout_url%%&quot;&gt;Subscribe now&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;whats-happening&quot;&gt;What’s Happening?&lt;/h2&gt;

&lt;p&gt;July is delightfully quiet, and that’s just fine. May and June were back-to-back conferences and tech events - Bucharest, Essen, Hamburg, Berlin, Oslo, Stockhom, Athens, Odense, Rotterdam, Utrecht and Amsterdam, bouncing home in between for just long enough to do the laundry, host a London.NET meetup and run a guitar karaoke night. Being on the road is fun, but it’s nice to be home for a while.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2023/substack-08bbb305-ecae-4ed0-b4bd-fb394ec4c80a_3088x2320.jpeg&quot; alt=&quot;img&quot; /&gt;&lt;img src=&quot;/images/posts/2023/substack-c16ee640-6cab-4a74-88e0-82dec973ab6d_3088x2320.jpeg&quot; alt=&quot;img&quot; /&gt;&lt;img src=&quot;/images/posts/2023/substack-44ececa4-ab45-4eb2-9029-f1f30a673905_4032x3024.jpeg&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2023/substack-f9e6082d-81fd-42aa-bc37-dc921b3678ee_1200x1600.jpeg&quot; alt=&quot;img&quot; /&gt;&lt;img src=&quot;/images/posts/2023/substack-eb0d3eba-ed65-4479-bb61-62e02d1b8bd3_4032x3024.jpeg&quot; alt=&quot;img&quot; /&gt;&lt;img src=&quot;/images/posts/2023/substack-54afc19d-f9d7-473d-96cd-11eb3e42ef49_3088x2320.jpeg&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2023/substack-8bcaf318-c3ef-40cb-9685-87be041855ad_4032x3024.jpeg&quot; alt=&quot;img&quot; /&gt;&lt;img src=&quot;/images/posts/2023/substack-1224f410-adc4-4e99-8499-341da5050ec6_4032x3024.jpeg&quot; alt=&quot;img&quot; /&gt;&lt;img src=&quot;/images/posts/2023/substack-22352e83-e9e2-4cf8-90c8-5c02696a3460_4032x3024.jpeg&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Berlin, Hamburg, Bucharest, Stockholm, Oslo, Athens, Rotterdam… I need a vacation.&lt;/p&gt;

&lt;h2 id=&quot;email-vs-capitalism-or-why-we-cant-have-nice-things&quot;&gt;Email vs Capitalism, or Why We Can’t Have Nice Things&lt;/h2&gt;

&lt;p&gt;At NDC Oslo last month, I presented a completely new talk about email, spam, and the challenges of running a free, open, decentralised communication network in a world that’s increasingly controlled by billion-dollar multinationals.&lt;/p&gt;

&lt;iframe src=&quot;https://www.youtube-nocookie.com/embed/mrGfahzt-4Q?rel=0&amp;amp;autoplay=0&amp;amp;showinfo=0&amp;amp;enablejsapi=0&quot; frameborder=&quot;0&quot; loading=&quot;lazy&quot; gesture=&quot;media&quot; allow=&quot;autoplay; fullscreen&quot; allowautoplay=&quot;true&quot; allowfullscreen=&quot;true&quot; width=&quot;728&quot; height=&quot;409&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;I’m pretty happy with how it came out, but watching the recording back, I’m seriously thinking this one might warrant splitting into two separate talks - a fun, accessible talk with all the weird history, the RFC edge cases, the story of junk mail, and then a second one aimed at developers that gets into the gritty detail of things like DMARC records and how to mash up Mailjet and Razor to build a .NET mail templating engine. Or maybe keep the live presentation as the fun talk, and move all the gritty detail into some sort of video course folks can find online.&lt;/p&gt;

&lt;p&gt;And yes, as several commenters have already pointed out, I screwed up one of the “let’s break email!” experiments you see in the video.&lt;/p&gt;

&lt;p&gt;Oops.&lt;/p&gt;

&lt;p&gt;I’ll fix it for next time.&lt;/p&gt;

&lt;h3 id=&quot;london-net-meetup-at-codat-on-july-12th&quot;&gt;London .NET Meetup at Codat on July 12th&lt;/h3&gt;

&lt;p&gt;After a long hiatus during the pandemic, the London .NET User Group is very much back up and running. We’re going to be meeting at Codat’s offices in Farringdon on Wednesday 12th July; Angeliki Patsiavou is going to be talking about what “The Avengers” taught her about working with developers, and then Nick Chapsas will be talking about logging in .NET and why we’re all doing it wrong. Nick’s basically an internet superstar with &lt;a href=&quot;https://www.youtube.com/channel/UCrkPsvLGln62OMZRO6K-llg&quot;&gt;about a bazillion followers on YouTube&lt;/a&gt; — and as of right now, we’ve got 100 people RSVP’d for next week and 40 of them are first-timers who’ve never been to a London .NET meetup before, which is fantastic.&lt;/p&gt;

&lt;p&gt;There’s a few places still available if you want to come along:&lt;/p&gt;

&lt;p&gt;https://www.meetup.com/london-net-user-group/events/294304925/&lt;/p&gt;

&lt;p&gt;And, note to myself: I am &lt;em&gt;absolutely&lt;/em&gt; going to remember to print name badges this time, because the number of folks I recognise but can’t remember their names is getting embarrassing.&lt;/p&gt;

&lt;h2 id=&quot;this-week-ive-been&quot;&gt;This week I’ve been…&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Reading:&lt;/strong&gt; “&lt;a href=&quot;https://store.orbit-books.co.uk/products/season-of-skulls?_pos=2&amp;amp;_sid=2b784b872&amp;amp;_ss=r&quot;&gt;Season of Skulls&lt;/a&gt;” by Charles Stross. It’s the latest novel set in the world of the “Laundry Files”, which started life as a wonderfully macabre mashup of HP Lovecraft and the Jargon File, and now gleefully bounces from trope to trope trashing genre conventions as it goes. Daft, delightfully written, and enormous fun.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listening:&lt;/strong&gt; “&lt;a href=&quot;https://open.spotify.com/album/5mENWT44VJdv0bkkjYOjIa?si=PUdCYozLQlSwWpr566jgCQ&quot;&gt;Rosalie Cunningham&lt;/a&gt;” by Rosalie Cunningham. I saw Rosalie and her band at Prognosis Festival at the O2 back in April, and have had the album on constant repeat ever since. It’s — for me, anyway — a rare example of music that sounds genuinely progressive without just being “complicated heavy metal with lots of time signatures”, and I’m thoroughly enjoying it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watching:&lt;/strong&gt; “&lt;a href=&quot;https://www.youtube.com/watch?v=lBrwkqlg1jA&quot;&gt;Still: A Michael J. Fox Movie&lt;/a&gt;” popped up unexpectedly on Apple TV, and it’s excellent. It’s the story of Michael J. Fox, his acting career — Family Ties, Back to the Future, Spin City — and how his diagnosis with Parkinson’s disease at the age of 29 affected his life and his work. It’s brilliantly put together, combining interview footage with Fox today with staccato clips cut together from his movies and TV shows — watch out for the montage showing how he’d always find something to do with his left hand so viewers wouldn’t notice the tremors; it’s quite amazing.&lt;/p&gt;

&lt;p&gt;And that’s a wrap. Drop a comment below, let me know what you think?&lt;/p&gt;

&lt;p&gt;Until next time: rock on, have fun, and be excellent to each other.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Dylan&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#footnote-anchor-1&quot;&gt;1&lt;/a&gt; or maybe didn’t say, but somebody shared it on Facebook with his name on it and that’s basically the same thing, right?&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#footnote-anchor-2&quot;&gt;2&lt;/a&gt; I figure any publishing platform that’s hosting writing by &lt;a href=&quot;https://tomcox.substack.com/&quot;&gt;Tom Cox&lt;/a&gt;, &lt;a href=&quot;https://catvalente.substack.com/&quot;&gt;Cat Valente&lt;/a&gt; and &lt;a href=&quot;https://tinabeattie.substack.com/&quot;&gt;my mum&lt;/a&gt; is probably doing something right.&lt;/p&gt;
</description>
          <pubDate>2023-07-14T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2023/07/14/the-hang-of-thursdays-001.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2023/07/14/the-hang-of-thursdays-001.html</guid>
        </item>
      
    
      
        <item>
          <title>The Social Meltdown</title>
          <description>&lt;p&gt;I just joined &lt;a href=&quot;https://www.threads.net/@dylanbeattie&quot;&gt;Threads&lt;/a&gt;. If you’ve not been following the news, Threads is the new social media app from Meta, the company behind Facebook, WhatsApp and Instagram. It’s basically a carbon copy of all the worst bits of Twitter, right up to the capricious billionaire chief executive who’d probably kill their own users if they thought they could sell advertising space on the headstones.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2023/substack-af7d609c-1c60-4d28-b0e5-95e698be2b20_1920x1163.jpeg&quot; alt=&quot;A graveyard in the cold winter moonlight. headstone reads &amp;quot;IN LOVING MEMORY OF SOCIAL MEDIA 2008-2023. NOT TECHNICALLY DEAD, BUT SURE SMELLS LIKE IT&amp;quot;. Cold mist crawls across the frozen ground and the whole thing is hella spooky.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Not dead, only Thread.Sleep()ing?&lt;/p&gt;

&lt;p&gt;Threads will never replace Twitter, though, because what Twitter was, at its peak, was something remarkable. It was not &lt;strong&gt;a&lt;/strong&gt; microblogging service, it was &lt;strong&gt;the&lt;/strong&gt; microblogging service. If you wanted to post something publicly, you’d post it on Twitter, whether you were the &lt;a href=&quot;https://twitter.com/potus&quot;&gt;President of the United States of America&lt;/a&gt;, or a &lt;a href=&quot;https://twitter.com/bobservant&quot;&gt;parody account based on a sitcom about a burger van in Scotland&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Twitter had &lt;strong&gt;clout&lt;/strong&gt;. It had influence. For better or worse, tweeting could actually change the world. The Arab Spring, Obama, Trump, Brexit… sure, it wasn’t the biggest, most popular, or most profitable social media platform. But ask yourself: have you ever seen a “serious” newspaper use LinkedIn or Facebook as a source when quoting somebody? Nope. Twitter drove the news.&lt;/p&gt;

&lt;p&gt;I joined Twitter in 2008 (&lt;a href=&quot;https://twitter.com/dylanbeattie/status/813023431&quot;&gt;here’s my first tweet&lt;/a&gt;!), around the same time I started getting involved in tech community events. Twitter was perfect for the kind of loosely-coupled connections and friendships that arise from those events. Facebook was too personal, LinkedIn was too corporate, email too time-consuming. But tagging somebody on Twitter was a wonderfully unobtrusive way to say “hey, we’re talking about this thing you might find interesting.”&lt;/p&gt;

&lt;p&gt;It wasn’t always like that. Before microblogging, there was blogging. Blogging was great. Google Reader was a great product, Blogger and WordPress were easy enough to get up and running, and writing a blog became a &lt;em&gt;de facto&lt;/em&gt; rite of passage for anybody trying to establish a name for themselves in tech, journalism, art, music.&lt;/p&gt;

&lt;p&gt;Social media killed that, and I believe that’s because it changed the dynamic of what people do when when have five minutes to kill at their desk between meetings —  not to mention the paradigm shift from desktop to mobile that was taking place around the same time. I used to check my blog roll a few time a day. I’d post in the comments, reply to threads, write my own follow-up posts. Then social media came along, and blogs got relegated to something I’d check when I’d caught up on Facebook and Twitter… and before long, that became impossible. Too many people, too much content, and if you’ve read everything your friends posted, the algorithm can always find something else to show you.&lt;/p&gt;

&lt;p&gt;I’m as guilty as anybody of falling for the convenience of social media. Over the years, I spent more and more time posting on Twitter and Facebook, and less and less writing on my own blog. The &lt;a href=&quot;https://twitter.com/dylanbeattie/status/1541546213564194816&quot;&gt;occasional&lt;/a&gt; tweet &lt;a href=&quot;https://twitter.com/dylanbeattie/status/103804183274192897&quot;&gt;going viral&lt;/a&gt; was a thrill… the most vapid, inconsequential kind of thrill, a completely arbitrary and unverifiable number on a web page going up every time the my post racked up another page impression to sell to their advertisers — but hell, they had some incredibly smart people getting paid an obscene amount of money to make going viral on those platforms feel like an accomplishment, and it worked.&lt;/p&gt;

&lt;p&gt;Then, in November last year, Elon Musk bought Twitter.&lt;/p&gt;

&lt;p&gt;I don’t know anything about electric cars, so based on the media coverage of Tesla’s success, I assumed Musk must be some sort of genius.&lt;/p&gt;

&lt;p&gt;I do actually know a thing or two about rockets, and when I saw SpaceX landing two reusable launch vehicles side-by-side like something out of a 50s science fiction movie, I continued to assume that Musk must be some sort of genius.&lt;/p&gt;

&lt;p&gt;Then he bought a software company — and as it happens, I know quite a lot about software, and about what’s involved in running complex software platforms.&lt;/p&gt;

&lt;p&gt;I no longer believe Elon Musk is a genius. Not even close. I think a ginger cat with one brain cell would have done a better job running Twitter than Elon Musk has, unless he’s actually trying to destroy the platform and bankrupt himself in the process.&lt;a href=&quot;#footnote-1&quot;&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Twitter is dying. Sure, it’s still up, I’m still using it, and so are lots of other people. But it feels like nobody’s in it for the long haul any more. It’s like when you realise your favourite bar isn’t bothering to fix the broken washroom taps any more: we’re still visiting, but we’re waiting for the day we show up to find the doors locked and the repossession notice stuck in the window, and when the day comes, nobody will really be surprised.&lt;/p&gt;

&lt;p&gt;The problem is: there’s no compelling replacement.&lt;/p&gt;

&lt;p&gt;In the immediate aftermath of Musk’s acquisition, a lot of folks I know set up accounts on Mastodon. Quite a lot of us discovered we already had accounts on Mastodon, which we’d completely forgotten about… and quite a few of those, myself included, discovered that the Mastodon instance we’d signed up to wasn’t a Mastodon instance any more and was now hosting some, ah, specialist adult content.&lt;/p&gt;

&lt;p&gt;At the  time of writing, I have active accounts on Mastodon, BlueSky and Threads — not to mention &lt;a href=&quot;https://substack.com/@dylanbeattie&quot;&gt;Substack’s own Notes platform&lt;/a&gt;, which looks suspiciously like Twitter but with better typography and no users. Plus all the platforms I was already on: Facebook, Instagram, LinkedIn, Telegram, WhatsApp, 43 different Slack workspaces (don’t ask), Discord, Signal, Meetup…&lt;/p&gt;

&lt;p&gt;In a way, it’s a blessing. The sheer overload of networks means that social media no longer feels remotely convenient — and as much as I wish a few of them would hurry up and actually shut down, I suspect that’s about as likely as Nadine Dorries actually resigning.&lt;/p&gt;

&lt;p&gt;Fundamentally, though, the process of re-evaluating Twitter’s role in my online career has led me to realise that I use — used? — Twitter for four things.&lt;/p&gt;

&lt;p&gt;First: I use it to tell stupid jokes. No big deal. Jokes move easily onto another platform.&lt;/p&gt;

&lt;p&gt;Second: I use it to publicly engage with companies whose behaviour I think is worthy of comment. Sometimes because they do something I think is exceptionally cool; sometimes because they do something I think is exceptionally uncool. Companies who aren’t answering the phone or replying to email will often respond to a tweet — especially from somebody with thousands of followers. I suspect this is just gone &lt;em&gt;(thanks Elon)&lt;/em&gt;: I honestly can’t imagine an airline paying as much attention to Threads, Mastodon or BlueSky as they did to Twitter in its heyday, and as Twitter itself becomes increasingly irrelevant I’m sure customer service departments all over the world are breathing a huge sigh of relief.&lt;/p&gt;

&lt;p&gt;Third: I use it to talk about tech. Fifteen years ago, when I didn’t know anybody to talk to when I needed help with .NET and Firebug and jQuery, posting on Twitter had an uncanny knack of connecting with the right people. For me, that’s not a factor any more; my network of helpful tech people has reached critical mass and is no longer beholden to a specific channel or platform.&lt;/p&gt;

&lt;p&gt;Finally, I use Twitter to talk to people. Sometimes, this is personal: I talk to a lot of people via Twitter DMs, and in many cases, they’re people I have no other way to reach - no email, no phone number, no common Slacks or Discords. Over the next week or so, I’ll be pinging all of those people saying “hey, here’s how to reach me when this whole place finally burns down”.&lt;/p&gt;

&lt;p&gt;Often, though, it’s more of a broadcast. It’s announcements about meetups I’m running, conferences, community events, workshops, Linebreakers concerts — and what I’d really like is to be able to use email for this.&lt;/p&gt;

&lt;p&gt;Email is far from perfect, but what makes it unique is that it’s &lt;em&gt;portable&lt;/em&gt;. When Twitter finally goes dark, I’ll lose the 15,000+ followers I’ve built up over the last fifteen years — they’ll just be gone. There’s no way for me to download a list and sync it to Threads or BlueSky. But if I have a list of email addresses, I can take that with me - and, if you’re running your own domain, so can you. I’ve moved dylanbeattie.net from my friends’ Qmail box, to my own mail server, to Fastmail; it’s not trivial, but it’s possible.&lt;/p&gt;

&lt;p&gt;I’ve never run any kind of newsletter or mailing list before; to me, email has always felt more personal than just posting stuff online and hoping the right people will see it. But, y’know, with everything that’s happening to social media at the moment, it feels like it might be time to give it a shot.&lt;/p&gt;

&lt;p&gt;You wanna be involved? Sign up, let’s see how this thing goes.&lt;/p&gt;

&lt;p&gt;It’s just the same dumb stuff I was doing already, but in your inbox instead of on somebody else’s advertising platform.&lt;/p&gt;

&lt;p&gt;And if not? I’m on &lt;a href=&quot;https://twitter.com/dylanbeattie&quot;&gt;Twitter&lt;/a&gt;, &lt;a href=&quot;https://bsky.app/profile/dylanbeattie.net&quot;&gt;BlueSky&lt;/a&gt;, &lt;a href=&quot;https://www.threads.net/@dylanbeattie&quot;&gt;Threads&lt;/a&gt;, &lt;a href=&quot;https://www.instagram.com/dylanbeattie/&quot;&gt;Instagram&lt;/a&gt;, &lt;a href=&quot;https://www.facebook.com/dylanbeattie&quot;&gt;Facebook&lt;/a&gt;, &lt;a href=&quot;https://www.linkedin.com/in/dylanbeattie/&quot;&gt;LinkedIn&lt;/a&gt;, &lt;a href=&quot;https://github.com/dylanbeattie&quot;&gt;GitHub&lt;/a&gt;, &lt;a href=&quot;https://www.tiktok.com/@dylan_beattie&quot;&gt;TikTok&lt;/a&gt; — and let’s face it, whatever daft thing launches next week, I’ll probably be dylanbeattie on that as well.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#footnote-anchor-1&quot;&gt;1&lt;/a&gt; We shall leave aside, for now, the question of how moribund the automotive and aerospace industries must have been for a hack like Musk to disrupt them as successfully as he did.&lt;/p&gt;
</description>
          <pubDate>2023-07-12T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2023/07/12/the-social-meltdown.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2023/07/12/the-social-meltdown.html</guid>
        </item>
      
    
      
        <item>
          <title>What Should &apos;Cancel&apos; Do?</title>
          <description>&lt;p&gt;Good user experience design is about balance. Software lives in this weird liminal space where a perfectly innocent word has two contradictory and completely obvious meanings: anybody who’s ever written a Java program to to manage school enrolment knows that creating a class isn’t the same as creating a class, but most of the time, there’s a way to get the balance right.&lt;/p&gt;

&lt;p&gt;Most of the time.&lt;/p&gt;

&lt;p&gt;I got this absolute gem of a screen today, from some event management software I’m integrating with:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2023/substack-444865c0-d962-4ab0-ad09-0c9b76025a90_624x411.png&quot; alt=&quot;A screenshot from an event management app, showing the form used to cancel an event. There is a single button on the form. It says &amp;quot;Cancel&amp;quot;. We don&apos;t know what it does.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;No, I’m not going to tell you where it’s from. They’re still figuring a lot of this stuff out, bless ‘em.&lt;/p&gt;

&lt;p&gt;To be fair, if you end up on this screen by mistake and instinctively reach for the Cancel button, it won’t &lt;em&gt;actually&lt;/em&gt; cancel the event unless there’s some text in the “CANCELATION REASON” box.&lt;/p&gt;

&lt;p&gt;If there is? You’re one click away from accidentally refunding all your tickets and emailing all the ticket holders saying the event’s been called off.&lt;/p&gt;

&lt;p&gt;I don’t know about you, but I’d have gone with a big angry red button labelled “Cancel This Event”, and perhaps a smaller, friendlier one labelled “Go Back”… y’know, just to make it obvious which one is which.&lt;/p&gt;

&lt;p&gt;And, just in case you ever think the software industry is paying attention and learning from our own mistakes, here’s a post I wrote over a decade ago about SagePay doing exactly the same thing: “&lt;a href=&quot;https://dylanbeattie.net/2010/11/03/should-cancel-cancel-cancellation-or.html&quot;&gt;Should ‘Cancel’ cancel the cancellation, or just cancel the cancellation cancellation?&lt;/a&gt;”&lt;/p&gt;
</description>
          <pubDate>2023-07-11T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2023/07/11/what-should-cancel-do.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2023/07/11/what-should-cancel-do.html</guid>
        </item>
      
    
      
        <item>
          <title>What&apos;s in a (Version) Number?</title>
          <description>&lt;p&gt;My Mastodon feed has been buzzing for the last few days with discussions about version numbers.&lt;/p&gt;

&lt;p&gt;First, there was this post from &lt;a href=&quot;https://hachyderm.io/@Martindotnet&quot;&gt;Martin&lt;/a&gt;, asking what JS folks think about &amp;lt; 1.0 versions:&lt;/p&gt;

&lt;iframe src=&quot;https://hachyderm.io/@Martindotnet/109683757201594015/embed&quot; class=&quot;mastodon-embed&quot; style=&quot;max-width: 100%; border: 0&quot; width=&quot;400&quot; height=&quot;400&quot; allowfullscreen=&quot;allowfullscreen&quot;&gt;&lt;/iframe&gt;
&lt;script src=&quot;https://hachyderm.io/embed.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;

&lt;p&gt;And then this one:&lt;/p&gt;

&lt;iframe src=&quot;https://hachyderm.io/@Martindotnet/109700844080933628/embed&quot; class=&quot;mastodon-embed&quot; style=&quot;max-width: 100%; border: 0&quot; width=&quot;400&quot; height=&quot;400&quot; allowfullscreen=&quot;allowfullscreen&quot;&gt;&lt;/iframe&gt;
&lt;script src=&quot;https://hachyderm.io/embed.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;

&lt;p&gt;Now, this kind of rough’n’ready survey can be great to kick off some entertaining conversations, but this is software. The correct answer in both cases is “it depends”. Because it &lt;em&gt;always&lt;/em&gt; depends.&lt;/p&gt;

&lt;p&gt;Turns out the specific scenario they’re dealing with here is a project that’s basically ready to go. It’s running on production systems, it’s solid, it’s feature complete, but for political reasons the team behind it isn’t prepared to stamp it as version 1.0.0 - and probably won’t be for a year…&lt;img src=&quot;/images/posts/2023/image-20230119133925733.png&quot; alt=&quot;a doge meme. so politics. much bureaucrat. such efficient. WOW.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;So the actual question here is something closer to:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;We have a solid, finished product that can’t be shipped as 1.0.0 because of politics. What version number would mean you were comfortable using our package on your projects?&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;▢&lt;/strong&gt; 0.9.x&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;▢&lt;/strong&gt; 1.0.0-rcx&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;▢&lt;/strong&gt; I can’t/won’t use either of those versions&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;▢&lt;/strong&gt; Either would be OK for me&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now that’s a &lt;em&gt;much&lt;/em&gt; more interesting question. I’ve run plenty of stuff in production that uses 0.9.x packages, but I have only very occasionally deployed a prerelease package as part of a production release, and only under very specific circumstances.&lt;/p&gt;

&lt;p&gt;Let’s say I have a project which uses Foobar 2.1.0. I know Foobar, I’ve used it on a bunch of projects, it’s solid, I trust it. I find a bug. The bug will be fixed in the forthcoming 2.1.1 release… but I need it now. In that situation, I’ll install 2.1.1-rc1, but as a temporary fix. This is reinforced by the way the NuGet package management ecosystem usually requires an explicit opt-in to find and install prerelease packages: It’s not something you’re likely to do by mistake:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2023/image-20230119125322592.png&quot; alt=&quot;A screenshot of the NuGet package manager interface, with the &amp;quot;Include prerelease&amp;quot; checkbox highlighted&quot; /&gt;&lt;/p&gt;

&lt;p&gt;To me, checking that “Include prerelease” box is about getting the latest version a few weeks before everybody else, and it’s &lt;strong&gt;completely different&lt;/strong&gt; thought process to what happens when I install a 0.9.x package.&lt;/p&gt;

&lt;p&gt;To me, 0.9.x means “we’re still working on it.” There are TODOs in the source code, there are open issues on GitHub we haven’t triaged yet, and there are specific features which will be part of a 1.0 release which are not available yet.&lt;/p&gt;

&lt;p&gt;That said - and as counterintuitive as this might seem - I’m probably more comfortable depending on a 0.9.x package than I am depending on a pre-release version. I’m certainly far more likely to install 0.9.x, take a bit of time to satisfy myself that’s doing the right thing, and then run with it until 1.0 eventually ships. I think this is probably because I associate pre-release packages with “coming soon” – but I don’t think I’ve ever actually encountered the specific scenario here, where the entire product is a 1.0.0-rc and is likely to be like that for a while.&lt;/p&gt;

&lt;p&gt;There’s also a strong implication that the difference between 1.0.0-rc and 1.0.0 is technical: no new features, no new documentation, just give it a week or two for folks to kick the tyres and flush out any bugs we missed and then we’ll ship 1.0. Whereas the difference between 0.9.x and 1.0.x could be down to any number of reasons - code, branding, politics, documentation.&lt;/p&gt;

&lt;p&gt;If you’d asked me &lt;em&gt;before&lt;/em&gt; I started writing this post, I’d have said that the right approach in this specific situation would be ship 0.9.x releases. And remember - semver is numeric, so if you add significant new features, you can ship 0.10.x, 0.11.x, 0.12.x and keep going until the stars align and you’re ready to bump the major version to 1. The process of writing it has made me think hard about why that’s the case, though… and after careful reflection, I realise that it doesn’t actually make a huge amount of sense. But I suspect most folks out there won’t have thought about it in this much detail, which is why I think that the vast majority of developers working in .NET would be more likely to install and use a 0.9.x release than a 1.0.0-rc release.&lt;/p&gt;

&lt;p&gt;And at the end of the day, though, it’s a version number. Your team might have taken weeks of tests, checks, reviews and sign-off procedures to finally stamp a release as 1.0.0, while the package that’s next to yours in the NuGet interface was one dev wired on Red Bull and synthwave YOLO’ing their code to NuGet at 4am and marking it 1.0.0 because they hadn’t found any bugs for, like, &lt;em&gt;hours&lt;/em&gt;… and to all us folks out here, they look &lt;em&gt;exactly the same&lt;/em&gt;. It’s what happens after we install them that actually matters.&lt;/p&gt;

</description>
          <pubDate>2023-01-19T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2023/01/19/whats-in-a-number.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2023/01/19/whats-in-a-number.html</guid>
        </item>
      
    
      
        <item>
          <title>Modern Frontends Live</title>
          <description>&lt;p&gt;A lot of people have been sharing their experiences of the Modern Frontends Live conference that ran in London last week – an event that was advertised as having “25+ workshops” and “3000  developers”.&lt;/p&gt;

&lt;p&gt;Well, here’s mine. I thought long and hard about posting this, for reasons which will become apparent, but here it is. I’m not going to comment on any of the things I’ve heard or read. This is my own personal experience of being part of this event.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;June 2022:&lt;/strong&gt; Gen Ashley contacted me via Twitter to ask if I would give a talk and/or run a workshop at a new conference she is working on, Modern Frontends Live, taking place at ExCeL in London in November. I’ve known Gen for over a decade; I wouldn’t say I knew her particularly well, but I’ve spoken at a few of her meetups over the years and seen her around at various tech events. I assume that the organisers have hired Gen to handle speaker liaison because of her connections in the London tech community. I submit a workshop and a talk – actually a placeholder “to be confirmed” talk, because Gen explains that they want to get speakers listed on the website as soon as possible. I include a note with my workshop submission asking to discuss revenue share arrangements before anything is published.&lt;/p&gt;

&lt;p&gt;July, August… nothing happens. Total silence.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2022/image-20221122184842088.png&quot; alt=&quot;image-20221122184842088&quot; style=&quot;zoom:67%;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;September 7th:&lt;/strong&gt; I notice that my talk and workshop are both listed on the event website. I contact them to ask what’s going on; I have a call with Gen. She suggests a ticket price – £899 early bird, £1099 full price. That’s broadly in line with what I would expect for a two-day in-person workshop at an event on the scale they’re talking about.&lt;/p&gt;

&lt;p&gt;Then Gen asks me if I’ll “donate” my share of the workshop revenue to support another event she’s running, &lt;a href=&quot;https://www.techknowday.com/&quot;&gt;TECH(K)NOWDAY&lt;/a&gt;, which is a “non-profit”.&lt;/p&gt;

&lt;p&gt;I decline, politely, because &lt;strong&gt;I do this for a living&lt;/strong&gt;. A substantial part of my income comes from running workshops at conferences, and while I don’t believe that Modern Frontends Live is going to reach the 3000 attendees their website is advertising, I think they’ll probably get 1500, maybe 2000. That should translate into 15-20 tickets for my workshop; after deducting overheads – room hire, catering, marketing costs – that should leave a respectable chunk of revenue for both of us.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;October 31st:&lt;/strong&gt; Gen asks me to “touch base” regarding my workshop. It turns out they’d sold three tickets. For most events, sales that poor would be grounds for cancellation. But Modern Frontends Live was happening in London, where I live; I’d already blocked out the time, and I didn’t want to disappoint the people who had bought a ticket.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;November 10th:&lt;/strong&gt; With a week to go until the conference, the agenda still hadn’t been published – I knew I would be speaking some time on Thursday or Friday, but had no idea exactly when. I got an email confirming that my workshop was going ahead – four attendees, two days of in-person training on Tuesday and Wednesday, 9am–5pm, in the “London Suite” at ExCeL – but no sign of the conference agenda. On Thursday morning, all the speakers received an email saying the agenda would be published later that day. Thursday came… Thursday went… no agenda.&lt;/p&gt;

&lt;p&gt;Friday 11th: I received another email:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Subject: SCHEDULE UPDATE&lt;/p&gt;

  &lt;p&gt;Hi Dylan.&lt;/p&gt;

  &lt;p&gt;We’re having some technical issues with the platform we’re using, so there’s a bit of delay.&lt;/p&gt;

  &lt;p&gt;We also have a team member had a death in the family, and that has affected us a bit.&lt;/p&gt;

  &lt;p&gt;So bear with us!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The schedule was finally announced on Sunday 13th November at 17:09; my talk was scheduled for Thursday afternoon.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The week of the event:&lt;/strong&gt; I arrived on Tuesday morning for the first day of my workshop. I couldn’t find the London Suite. I asked the venue staff; they’d never heard of it. No signage. No mention anywhere of Modern Frontends Live. I eventually find it – it wasn’t quite on display in a locked filing cabinet stuck in a disused lavatory with a sign on the door saying “&lt;a href=&quot;https://www.goodreads.com/quotes/40705-but-the-plans-were-on-display-on-display-i-eventually&quot;&gt;Beware of the Leopard&lt;/a&gt;”, but it wasn’t far off.&lt;/p&gt;

&lt;p&gt;Of the 25+ workshops advertised on the website, mine is one of only two that are actually running.&lt;/p&gt;

&lt;p&gt;There’s no registration desk or attendee badges; Gen is there in person to meet people as they arrive. There’s no catering. No lounge, no break area, no coffee machine. We have the training room, and that’s it. After half an hour, Gen arrives with a tray of coffees that she’s quite clearly just bought from a place upstairs. Shortly after that she comes back with bottles of water.&lt;/p&gt;

&lt;p&gt;It’s embarrassing. Considering how much the attendees have paid to attend the workshop, I’m absolutely mortified. The room we’re in has no windows or natural light. Two more attendees join unexpectedly at 10am – it turns out they couldn’t find the room either. But hey, no problem. We have plenty of space. We break for lunch – which turns out to be Gen paying for sandwiches at one of the concession stands because apparently London ExCeL is “too busy” to provide catering.&lt;/p&gt;

&lt;p&gt;My workshop group is actually great: smart people with lots of really insightful questions. It’s a lot of fun working with them.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I discover later that most of the attendees in my workshop are only there because the workshops they’d booked and paid for had been cancelled. Chris Perry, one of the people who attended my workshop, has posted a &lt;a href=&quot;https://christopherallanperry.github.io/blog/2022/11/20/modern_frontends-an_attendees_perspective.html&quot;&gt;detailed write-up of his own experience of the event&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After we call it a day, Gen takes me to one side. She wants to know why so many people are posting negative things about the conference on social media.&lt;/p&gt;

&lt;p&gt;I explain, as politely as I can, that every single piece of communication and interaction I’ve had with the event has been shambolic – the emails, the schedule, the pricing, the sales, the lack of catering – and that if the other speakers’ experience is in any way comparable to mine, it’s no wonder they are angry. Gen is dismissive; she says that Twitter is not her problem and she needs to concentrate on running the event. I tell her she’s probably right but that if she ever wants any tech speaker to participate in one of her events again, she will need to follow that up with a full public explanation and an apology.&lt;/p&gt;

&lt;p&gt;I think she thinks I’m joking. I’m not joking. I’m tired, I’m fed up, and I’m starting to suspect I will never get paid.&lt;/p&gt;

&lt;p&gt;Wednesday goes much the same – except ExCeL is a lot busier: there are more events happening, with a lot more attendees; once Gen has paid for everybody’s sandwiches (again), there are no tables free anywhere to sit and eat lunch together. We go back to our windowless training room. If I’d been working with any other event, I would have taken the attendees out to lunch and charged it to the organisers… but, like I said, I’m already starting to doubt whether I’ll ever see any of the revenue share from this event.&lt;/p&gt;

&lt;p&gt;On Thursday morning I head along to the actual conference. It’s underwhelming, to say the least. The sponsor “booths” are tables in a hallway. There are a few hundred attendees – nothing close to the 3000 developers advertised on the Modern Frontends Live website. Most of the rooms are laid out cabaret style – a trick that venues use when turnout is far below expectations: put all the chairs around big dining tables, so the room looks full even if there are only a few dozen people in it.&lt;/p&gt;

&lt;p&gt;My talk goes pretty well. Maybe 50 people show up to learn about ray-tracing and 3D computer graphics. The projector works, the sound works. I don’t see a camera anywhere in the room. I cannot comment on the so-called “live stream” or the controversy about virtual tickets. I didn’t buy a virtual ticket, I didn’t try to access any streams of the event. All I know is that there was no camera in the Ada Lovelace room where I was presenting my talk.&lt;/p&gt;

&lt;p&gt;I didn’t go back on Friday. After two days of training and a day of conference sessions, trying hard to create a positive experience for the paying attendees despite the logistical challenges, I was completely wiped out.&lt;/p&gt;

&lt;p&gt;I’ve seen comments online about how polarised the discussion around the event has been, that everybody either loved it or hated it. Well, I think a lot of attendees actually had a great time – and a lot of that is down to the speakers, some of whom I now understand had paid their own travel costs in order to participate. But I’ve also chatted with the sponsors, and I have some idea how much they paid to be there.&lt;/p&gt;

&lt;p&gt;By my reckoning, the total sponsorship revenue for this event was well in excess of £100,000. That’s over a hundred grand that is no longer available to support any other events. For some partners, that’s their entire event sponsorship budget for this year – wiped out for two folding tables in a hallway and a few hundred attendees. And &lt;em&gt;wow&lt;/em&gt;, that pisses me off – especially when I consider that my participation, and that of speakers like me, was probably a factor in those partners’ decisions to sponsor the event.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And this brings us to the difficult part.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Modern Frontends Live owes me money. I don’t know how much, because I haven’t had any confirmation yet of the final ticket prices or the room hire cost. I’m guessing they owe me somewhere between £1000 and £2000 - and when somebody owes you that kind of money, it’s generally not good business sense to publish openly critical articles about them&lt;sup&gt;&lt;em&gt;[citation needed]&lt;/em&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;Well, I’m not going to keep quiet and play nice about this. People who I know and respect have stuck their heads above the parapet to publicise what happened with this event. I care more about supporting them than I do about the money, and the more I find out about how this event was organised, the less comfortable I am with realising any kind of profit from it.&lt;/p&gt;

&lt;p&gt;But I’m also not prepared to walk away and let Modern Frontends Live keep the proceeds, or claim that my speaking up about this justifies them not paying me my share.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So here’s the deal.&lt;/strong&gt; I have asked Gen Ashley to work out the numbers and tell me how much my share of the revenue comes to. I expect to invoice Modern Frontends Live for the the full amount, I expect them to pay it – and in return, in the spirit of Gen’s request all those months ago, I will use that money to support the developer community. I’m one of the organisers of the &lt;a href=&quot;https://www.meetup.com/en-AU/london-net-user-group/&quot;&gt;London .NET User Group&lt;/a&gt;. Since 2002, our group has run free community meetups for developers working on .NET, web, and associated technologies.&lt;/p&gt;

&lt;p&gt;If Modern Frontends Live pays me what I’m owed, I will use 100% of those funds to support London .NET User Group events during 2023, with a specific emphasis on improving diversity and inclusivity at our events. That would allow us to offer travel and accommodation costs to speakers from outside London; to invite more speakers from under-represented groups to participate in our events, and help us build connections with the wider developer community.&lt;/p&gt;

&lt;p&gt;And if they don’t pay me? I think that’ll send a pretty clear message to any speakers, trainers, or sponsors who might be considering working with Gen Ashley or any of her conference brands ever again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your move, Gen.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here’s what other people have been saying about Modern Frontends Live:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Niall Maher at &lt;a href=&quot;https://www.youtube.com/@codu&quot;&gt;Codú&lt;/a&gt; has posted a YouTube video “&lt;a href=&quot;https://www.youtube.com/watch?v=Ekn-qiH8Ozw&quot;&gt;10 Tips on Running a Terrible Conference - Lessons from Modern Frontends&lt;/a&gt;”&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Niamh McCooey: &lt;a href=&quot;https://dev.to/niamhmccoo/my-experience-at-modern-frontends-live-1lcn&quot;&gt;My Experience at Modern Frontends Live&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;Jo Franchetti: &lt;a href=&quot;https://dev.to/thisisjofrank/my-experience-of-modern-frontends-conference-1cgg&quot;&gt;My experience of Modern Frontends Conference&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Cassie Evans: &lt;a href=&quot;https://www.cassie.codes/posts/modern-frontends/&quot;&gt;Modern Frontends&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;JD Hillen: &lt;a href=&quot;https://jdhillen.com/blog/my-experience-at-modern-frontends-live/&quot;&gt;My Experience at Modern Frontends Live&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Mike Hartington: &lt;a href=&quot;https://mhartington.io/post/modern-frontends-live/&quot;&gt;Modern Frontends Live&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Chris Perry: &lt;a href=&quot;https://christopherallanperry.github.io/blog/2022/11/20/modern_frontends-an_attendees_perspective.html&quot;&gt;Modern Frontends: An Attendee’s Perspective&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Hidde de Vries: &lt;a href=&quot;https://hidde.blog/modern-frontends-live/&quot;&gt;My experience at Modern Frontends Live&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Todd Libby: &lt;a href=&quot;https://toddl.dev/posts/modern-frontends/&quot;&gt;Modern Frontends&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
          <pubDate>2022-11-22T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2022/11/22/modern-frontends-2022.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2022/11/22/modern-frontends-2022.html</guid>
        </item>
      
    
      
        <item>
          <title>The Road To Guitaraoke, Part 1: Vamp, Chordino, ImageSharp, and ffmpeg</title>
          <description>&lt;p&gt;One of my side projects at the moment is running a monthly karaoke night at my local brewery tap, the very awesome &lt;a href=&quot;https://ignition.beer/&quot;&gt;Ignition Brewery in Sydenham&lt;/a&gt; – but it’s karaoke with a twist.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://guitaraoke.live/&quot;&gt;&lt;img src=&quot;/images/posts/2022/guitaraoke-social-media-banner.jpg&quot; alt=&quot;guitaraoke-social-media-banner&quot; style=&quot;zoom:50%;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As well as singing, you can get up and play guitar or bass; instruments and backline are provided, and I’ve created 5-channel mixes of all the backing tracks so we can fade out specific instruments if somebody wants to play them live.&lt;/p&gt;

&lt;p&gt;I ran the first &lt;a href=&quot;https://guitaraoke.live/&quot;&gt;Guitaraoke&lt;/a&gt; night in August, and it went great (despite a heat wave and a train strike!) – but by far the most common bit of feedback I got was “it would be great it we could see the guitar chords on the video”. Well… yeah, it would. After all, the whole point of karaoke is that the singer doesn’t have to know the words… if we’re going to do Guitaraoke properly, the players shouldn’t need to know the songs.&lt;/p&gt;

&lt;p&gt;So, that’s the requirement: &lt;strong&gt;guitar chords on screen on the night.&lt;/strong&gt; For the last couple of weeks, I’ve been investigating possible ways of doing this – from editing the videos by hand in Premiere (which is way too time-consuming), to building some sort of custom video player that’ll read chord charts from a text file and overlay them onto the video (turns out building a video player is &lt;em&gt;hard&lt;/em&gt;), to complicated systems involving OBS.&lt;/p&gt;

&lt;p&gt;Today I hit a bit of a milestone, though. Here’s how I got there.&lt;/p&gt;

&lt;h3 id=&quot;vamp-and-chordino&quot;&gt;Vamp and Chordino&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://www.vamp-plugins.org/&quot;&gt;Vamp&lt;/a&gt; is “an audio processing plugin system for plugins that extract descriptive information from audio data”. I’d never heard of Vamp until a few days ago, and I have to say, it’s not the most user-friendly platform I’ve ever seen – the “documentation” for quite a few of the plugins is the scientific paper published by the authors describing what the plugin does. (&lt;a href=&quot;https://code.soundsoftware.ac.uk/projects/beatroot-vamp&quot;&gt;No, really…&lt;/a&gt;)&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Caveat: I spent a few days trying to get this stuff running on my M1 Macbook Pro, with limited success. Arm64 binary hosts can’t load x64 binary plugins, and a lot of this stuff involves getting program A to load library B which requires library C. Windows 10 has been straightforward, though - and I suspect Intel Macs and Linux would be just as easy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To try it out and see how well it worked, I installed the &lt;a href=&quot;https://www.vamp-plugins.org/pack.html&quot;&gt;Vamp Plugin Pack&lt;/a&gt;, and then used Audacity as a host application to play around with the plugins. Once you’ve installed the plugin pack, click Analyze, Add / Remove Plug-ins… and enable the “Chordino: Chord Estimate” plugin:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2022/image-20220919193238986.png&quot; alt=&quot;image-20220919193238986&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Then open an audio file, highlight the audio waveform, go to Analyze &amp;gt; Chordino: Chord Estimate… and you’ll get this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2022/image-20220919202250234.png&quot; alt=&quot;image-20220919202250234&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This was my first “wow, this might actually work” moment… the plugin has done a pretty good job identifying the chords to “Man! I Feel Like A Woman” by Shania Twain, but it’s also extracted the exact timing of the chord changes. This is going to be useful.&lt;/p&gt;

&lt;p&gt;The next step was to get this data out into a format I could actually work with. Turns out that the &lt;a href=&quot;https://www.vamp-plugins.org/develop.html&quot;&gt;Vamp developer SDK&lt;/a&gt; includes a simple command-line host, so I grabbed a copy of that.&lt;/p&gt;

&lt;p&gt;To use the command line host, I had to convert my input file to WAV audio (Audacity will quite happily analyze MP3 audio and MP4 video files if you’ve got the ffmpeg library installed). It took a bit of trial and error to get the exact syntax right; the command I needed is:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;D:\projects&amp;gt; VampSimpleHost.exe nnls-chroma:chordino man-i-feel-like-a-woman.wav

VampSimpleHost.exe: Running...
Reading file: &quot;man-i-feel-like-a-woman.wav&quot;, writing to standard output
Running plugin: &quot;chordino&quot;...
Using block size = 16384, step size = 2048
Plugin accepts 1 -&amp;gt; 1 channel(s)
Sound file has 2 (will mix/augment if necessary)
Output is: &quot;simplechord&quot;
 0.185759637: N
 0.464399093: Bmaj7
 3.854512471: Bb
 5.061950113: Gm
 5.619229025: Bb
 20.944399093: Eb7
 22.244716553: Bb
 28.653424036: Eb7
 29.907301587: Bb
 31.950657596: Gm
 32.554376417: Bb
 34.411972789: Ab
 34.690612245: Bb
 38.220045351: Ab
 38.545124716: Bb
 ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Easy. That’s the timestamp (in seconds, as a decimal fraction), and the chord (“N” denotes “no chord”).&lt;/p&gt;

&lt;p&gt;OK. Later, we can wire this into some kind of automated processing pipeline. For today, what I did was to redirect that into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chords.txt&lt;/code&gt; and move on to the next part: turning this into video.&lt;/p&gt;

&lt;h3 id=&quot;turning-chord-data-into-video&quot;&gt;Turning Chord Data Into Video&lt;/h3&gt;

&lt;p&gt;There’s a bunch of ways to turn data into video, but there’s two things to remember. One – it always comes down to rendering individual frames and then sticking them together. And two – if &lt;a href=&quot;http://ffmpeg.org/&quot;&gt;ffmpeg&lt;/a&gt; can’t do it, you don’t need it. If you’ve not worked with ffmpeg before, it’s a command-line application that can read, write, convert and stream just about every audio and video format ever invented.&lt;/p&gt;

&lt;p&gt;So, here’s my approach:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Create a program that’ll read that chord data and turn it into individual video frames&lt;/li&gt;
  &lt;li&gt;Feed those frames to ffmpeg to produce a transparent video file&lt;/li&gt;
  &lt;li&gt;Composite that video file onto the top of the original karaoke track (kinda like showing a subtitle track on a movie).&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;rendering-video-in-net-using-imagesharp-and-ffmpegcore&quot;&gt;Rendering video in .NET using ImageSharp and FFMpegCore&lt;/h4&gt;

&lt;p&gt;I used .NET here because I know it, and I like it. To actually draw the chord names onto each frame, I’m using &lt;a href=&quot;https://sixlabors.com/products/imagesharp/&quot;&gt;ImageSharp&lt;/a&gt;; each frame is then wrapped in an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ImageVideoFrameWrapper&lt;/code&gt;, a class I wrote to pass data from ImageSharp to ffmpeg. I’m then using a .NET library called &lt;a href=&quot;https://github.com/rosenbjerg/FFMpegCore&quot;&gt;FFMpegCore&lt;/a&gt; to read those frames directly from memory and render them into the output video stream. Note that I’m using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.webm&lt;/code&gt; format here, and specifying &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;libvpx-vp9&lt;/code&gt; as the video codec – this is because I want my output video to support alpha transparency.&lt;/p&gt;

&lt;p&gt;Here’s the program code:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;ChordMaker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;FFMpegCore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;FFMpegCore.Arguments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;FFMpegCore.Pipes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;SixLabors.Fonts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;SixLabors.ImageSharp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;SixLabors.ImageSharp.Drawing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;SixLabors.ImageSharp.Drawing.Processing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;SixLabors.ImageSharp.PixelFormats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;SixLabors.ImageSharp.Processing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FPS&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// frames per second&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SPF&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1f&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FPS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// seconds per frame&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chords&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ReadAllLines&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;chords.txt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Select&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;line&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Chord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ToList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chords&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;chords&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Duration&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chords&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Time&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chords&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;frameCount&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chords&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pair&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pair&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FPS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;width&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1920&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;height&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1080&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;speed&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;frames&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CreateFramesSD&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;frameCount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chords&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;videoFramesSource&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;RawVideoPipeSource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;frames&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FrameRate&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FPS&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;FFMpegArguments&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;FromPipeInput&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;videoFramesSource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;OutputToFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;chords.webm&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;overwrite&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WithVideoCodec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;libvpx-vp9&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ProcessSynchronously&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WriteLine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Done&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IEnumerable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IVideoFrame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CreateFramesSD&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Chord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chords&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;family&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;FontCollection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Combine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;fonts&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;ttf&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;FreeSansBold.ttf&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    
    &lt;span class=&quot;n&quot;&gt;DrawingOptions&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;GraphicsOptions&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;ColorBlendingMode&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PixelColorBlendingMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Normal&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;playheadPosition&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;width&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;8f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chordBarHeight&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;height&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;8f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chordBarTop&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chordBarHeight&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;6.5f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;font&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;family&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateFont&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chordBarHeight&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0.6f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chordTextTop&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chordBarTop&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chordBarHeight&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0.2f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transBlack&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Brushes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Solid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;FromRgba&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transRed&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Brushes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Solid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;FromRgba&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;127&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;white&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Brushes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Solid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;White&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;frame&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;frame&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;frame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WriteLine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;$&quot;Frame &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;frame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;: &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;frame&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SPF&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Rgba32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;image&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Transparent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Mutate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Fill&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transBlack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;RectangularPolygon&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chordBarTop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chordBarHeight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lastChord&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Empty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;foreach&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chord&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chords&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;N&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// No chord&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lastChord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// No change&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Length&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// ignore chords like Bm7b5 which Chordino sometimes returns&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Duration&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0.5f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// skip chords which are very, very short&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;lastChord&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;offset&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;playheadPosition&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;speed&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Time&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;offset&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;playheadPosition&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;offset&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;point&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;PointF&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chordTextTop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;$&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Mutate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;DrawText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PrettyName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;font&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;White&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;point&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Mutate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Fill&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transRed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;RectangularPolygon&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chordBarTop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;playheadPosition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chordBarHeight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Fill&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;white&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;RectangularPolygon&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;playheadPosition&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chordBarTop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chordBarHeight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;ImageVideoFrameWrapper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Rgba32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wrapper&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wrapper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Chord&lt;/code&gt; class:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Chord&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Time&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Duration&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PrettyName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Replace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;b&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;♭&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Replace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;♯&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Chord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tokens&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;:&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Time&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tokens&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Trim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tokens&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Trim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ImageVideoFrameWrapper&amp;lt;T&amp;gt;&lt;/code&gt; class that I’m using to get data from ImageSharp into the FFMPEG input buffer:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;System.Runtime.CompilerServices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;FFMpegCore.Pipes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;SixLabors.ImageSharp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;SixLabors.ImageSharp.PixelFormats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;ChordMaker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; 

&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ImageVideoFrameWrapper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IVideoFrame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IDisposable&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;unmanaged&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IPixel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Width&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Height&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Format&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;rgba&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Source&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ImageVideoFrameWrapper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Source&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ArgumentNullException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;nameof&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Serialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Stream&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pixelBytes&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Width&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Height&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Unsafe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SizeOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Rgba32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;()];&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CopyPixelDataTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pixelBytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pixelBytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pixelBytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;SerializeAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Stream&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CancellationToken&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pixelBytes&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Width&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Height&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Unsafe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SizeOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Rgba32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;()];&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CopyPixelDataTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pixelBytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WriteAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pixelBytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pixelBytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Dispose&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Dispose&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’ll create a transparent video file with moving chords on it in  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chords.webm&lt;/code&gt;.  The final step is to composite this video onto the original backing video, which is another job for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ffmpeg&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ffmpeg
  &lt;span class=&quot;c&quot;&gt;# input file #0:&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; original.mp4
  &lt;span class=&quot;c&quot;&gt;# video codec for input file #1: libvpx-vp9&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt;:v libvpx-vp9 
  &lt;span class=&quot;c&quot;&gt;# input file #1&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; chords.webm
  &lt;span class=&quot;c&quot;&gt;# video filter: scale video #1 to 1280x720, store that in [z], then overlay [z] onto video #0&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-filter_complex&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;[1:v]scale=1280:720[z];[0:v][z]overlay&quot;&lt;/span&gt; 
  &lt;span class=&quot;c&quot;&gt;# video codec for output: libx264&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt;:v libx264 
  &lt;span class=&quot;c&quot;&gt;# video bitrate for output@: 2500kbps&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-b&lt;/span&gt;:v 2500k 
  &lt;span class=&quot;c&quot;&gt;# output filename&lt;/span&gt;
  composite.mp4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’ll produce &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;composite.mp4&lt;/code&gt;, which is the original backing video with the transparent chord overlay composited onto the top of it. It’s also a 4-minute video I don’t have permission to publish online, with a 5.1 surround sound mix that’s going to sound really weird unless you’re running it through the right audio gear, so just for all you folks following along at home, I ran it through &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ffmpeg&lt;/code&gt; one more time:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ffmpeg 
  &lt;span class=&quot;c&quot;&gt;# input file: &lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; .&lt;span class=&quot;se&quot;&gt;\c&lt;/span&gt;omposite.mp4 
  &lt;span class=&quot;c&quot;&gt;# audio channels: 2&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-ac&lt;/span&gt; 2 
  &lt;span class=&quot;c&quot;&gt;# audio filter: render 5.1 down to 2.0 audio.&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;#  FL (front left) = 0.7 * FC (front center), + 0.70 * FL (front left) + 1.0 * BL (back left)&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;#  FR (front left) = 0.7 * FC (front center), + 0.70 * FR (front right) + 1.0 * BR (back right)&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-af&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;pan=stereo|FL=0.7*FC+0.70*FL+1.0*BL|FR=0.7*FC+0.70*FR+1.0*BR&quot;&lt;/span&gt; 
  &lt;span class=&quot;c&quot;&gt;# skip start time to 45 seconds &lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-ss&lt;/span&gt; 00:00:45 
  &lt;span class=&quot;c&quot;&gt;# trim video length to 30 seconds&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; 00:00:30 
  &lt;span class=&quot;c&quot;&gt;# use the codec for video: libx264&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt;:v libx264 
  &lt;span class=&quot;c&quot;&gt;# set the video bitrate to 2500kbps&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-b&lt;/span&gt;:v 2500k 
  &lt;span class=&quot;c&quot;&gt;# use the codec for audio: aac&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt;:a aac 
  &lt;span class=&quot;c&quot;&gt;# output filename&lt;/span&gt;
  guitaraoke-demo.mp4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’ll produce a 30-second clip, in regular stereo, which I’ve uploaded to YouTube so you can see the results:&lt;/p&gt;

&lt;iframe width=&quot;640&quot; height=&quot;360&quot; src=&quot;https://www.youtube.com/embed/r_e4PJLCEdg&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;That’s a pretty convincing proof of concept… but there’s a world of difference between doing it once, with one clip, and being able to churn out the 50+ tracks you need to actually run a karaoke night. So next steps are to add beat detection, so I can quantise the timing of the chord changes to land on the beat exactly. Not essential, but nice to have.&lt;/p&gt;

&lt;p&gt;I also need to automate the various steps - video &amp;gt; WAV &amp;gt; chords &amp;gt; overlay &amp;gt; composite - so I can churn this thing across a folder full of tracks and spit out dozens of videos at a time. I suspect some tracks will need some manual editing of the chord data before rendering the video, so I also need to figure out a way to run a high-speed version, probably 12fps at 640x360, to check the results, and then the high-quality 1280x720 60fps version to produce the final video.&lt;/p&gt;

&lt;p&gt;And if you want to see it in action, come along to the &lt;a href=&quot;https://ignition.beer/&quot;&gt;Ignition Brewery and Taproom&lt;/a&gt; on the third Saturday of the month and check it out. Bonus points if you actually get up and play something. 🎸🤘🍻&lt;/p&gt;

&lt;h3 id=&quot;links&quot;&gt;Links&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;ImageSharp: &lt;a href=&quot;https://sixlabors.com/products/imagesharp/&quot;&gt;https://sixlabors.com/products/imagesharp/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;FFMPEGCore: &lt;a href=&quot;https://github.com/rosenbjerg/FFMpegCore&quot;&gt;https://github.com/rosenbjerg/FFMpegCore&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Vamp Plugins: &lt;a href=&quot;https://www.vamp-plugins.org/&quot;&gt;https://www.vamp-plugins.org/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Chordino plugin for Vamp: &lt;a href=&quot;https://code.soundsoftware.ac.uk/projects/nnls-chroma/&quot;&gt;https://code.soundsoftware.ac.uk/projects/nnls-chroma/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Guitaraoke: &lt;a href=&quot;https://guitaraoke.live/&quot;&gt;https://guitaraoke.live/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Karaoke tracks and videos purchased from &lt;a href=&quot;https://www.karaoke-version.co.uk/karaoke/&quot;&gt;https://www.karaoke-version.co.uk/karaoke/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
          <pubDate>2022-09-19T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2022/09/19/the-road-to-guitaraoke-part-1-vamp-chordino-imagesharp-ffmpeg.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2022/09/19/the-road-to-guitaraoke-part-1-vamp-chordino-imagesharp-ffmpeg.html</guid>
        </item>
      
    
      
        <item>
          <title>Fun with Docker and /etc/hosts</title>
          <description>&lt;p&gt;I was running my Distributed Systems with .NET workshop with a team in Bucharest this week – two days of learning all about microservices, HTTP APIs, message queues, gRPC, SignalR, and how they all work with .NET, and then on the third day we looked at the various ways teams can use Docker on these kinds of projects: hosting external services like RabbitMQ for local development; creating stable build environments, and, finally, hosting .NET apps and services directly in a Docker container.&lt;/p&gt;

&lt;p&gt;All the exercises and demos went great right up until the last part of the third day… we went over how to package a .NET hosted service application as a Docker image, and then when we started up a container from that image… nothing. The service in question was a tiny component that would subscribe to messages coming in via a RabbitMQ queue, translate them into JSON, and forward them to a SignalR hub running in our web app. But when we fired up the component inside Docker, it couldn’t connect to SignalR. We tried a half-dozen different things - DNS, creating a bridged network… but none of it worked, and I was completely stumped as to why. I’d never seen anything like this before. I know that to get network traffic &lt;em&gt;into&lt;/em&gt; a Docker image you’ve got to open ports and things, but getting traffic &lt;em&gt;out&lt;/em&gt;? I don’t remember that ever even being a problem… it just works, right?&lt;/p&gt;

&lt;p&gt;So we wrapped it up, without ever getting the final demo to work. Which I &lt;em&gt;hate&lt;/em&gt; doing - it always feels like missing the end of the movie or something… so after I got home, I dug into what had gone wrong.&lt;/p&gt;

&lt;p&gt;Turns out it was incredibly simple – but for complicated reasons.&lt;/p&gt;

&lt;p&gt;There was absolutely nothing wrong. The code was fine, the Dockerfile was correct. And if I had run it on any machine, anywhere on the planet, &lt;strong&gt;other than the machine I use to run my workshop demos&lt;/strong&gt;, it would have worked perfectly; I’d inadvertently created the diametric opposite of “works on my machine”.&lt;/p&gt;

&lt;p&gt;For running workshops, I use a Windows 11 VM hosted in Azure as my demo machine, and I have a bunch of ports open from this machine to the internet. I point a real DNS record – &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;workshop.ursatile.com&lt;/code&gt; – at it, and install a real HTTPS certificate, so that I can run apps in Visual Studio, and the attendees can connect to https://workshop.ursatile.com on various ports and get a real, live HTTP connection, with a proper certificate, with my demo/example code running on the other end of it.&lt;/p&gt;

&lt;p&gt;Except… virtual machines running in Azure can’t see their own public IP addresses. If I want to connect to my own code, I have to connect to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;localhost&lt;/code&gt; - but because I’ve replaced the certificate, that doesn’t work.&lt;/p&gt;

&lt;p&gt;So what I do is to edit the hosts file and add a record that says&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;127.0.0.1	workshop.ursatile.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So when I connect to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;workshop.ursatile.com&lt;/code&gt; locally, it’s connecting to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;127.0.0.1&lt;/code&gt;, but the rest of the world connects to the public IP in the DNS record.&lt;/p&gt;

&lt;p&gt;But when I’m using that same VM to host Docker containers? &lt;strong&gt;Those containers can’t see the hosts file.&lt;/strong&gt; Which means when I try to run one of the example apps in Docker, it does a public DNS query, gets the Azure external IP address for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;workshop.ursatile.com&lt;/code&gt;, tries to connect… and can’t, because as far as the Azure network is concerned, that traffic’s coming from inside the VM and so can’t see that external address.&lt;/p&gt;

&lt;p&gt;So there you go. It’s always DNS, and when it’s not DNS, it’s probably the hosts file. Which is kind of also DNS. And for future reference, there is a very simple, standalone .NET application and Dockerfile here:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/dylanbeattie/simple-dotnet-docker-service&quot;&gt;https://github.com/dylanbeattie/simple-dotnet-docker-service&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;which demonstrates how to build a .NET Hosted Service, run it inside Docker, and connect to external network resources.&lt;/p&gt;
</description>
          <pubDate>2022-09-09T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2022/09/09/fun-with-docker-and-etc-hosts.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2022/09/09/fun-with-docker-and-etc-hosts.html</guid>
        </item>
      
    
      
        <item>
          <title>Reinstalling Windows (again)</title>
          <description>&lt;p&gt;In November last year I upgraded my main workstation to Windows 11. I didn’t like it much. Lots of unnecessary window dressing - rounded corners, changes to the Start menu, context menu, stuff that wasn’t a &lt;em&gt;problem&lt;/em&gt; but didn’t really improve anything. I found Windows 11 slower than Windows 10. Booting my PC in the morning took nearly 3 minutes, most of which it was just sat there with a blank screen, doing &lt;em&gt;something&lt;/em&gt; – I have no idea what? –  before it finally brought up the login screen.&lt;/p&gt;

&lt;p&gt;But the thing that clinched it was that something in Windows 11 couldn’t handle certain kinds of image data on the clipboard; copying images to the clipboard would cause parts of the UI to flicker, crash, redraw, and basically make Windows unusable. Most noticeably anything in Adobe Photoshop - there’s a &lt;a href=&quot;https://community.adobe.com/t5/photoshop-ecosystem-bugs/p-windows-11-icons-and-menus-quot-flicker-quot-after-copying-a-selection-in-photoshop/idc-p/12727108#M47174&quot;&gt;support thread here&lt;/a&gt; all about it, but it’s not a Photoshop problem; various other applications caused the same behaviour, up to and including &lt;a href=&quot;https://twitter.com/dylanbeattie/status/1505859188811083782&quot;&gt;right-clicking the icon in the Google Chrome Help &amp;gt; About page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So bye-bye Windows 11.&lt;/p&gt;

&lt;p&gt;When it comes to reinstalling Windows, this is not my first rodeo. I keep everything important on a separate drive, with all my work either pushed to GitHub or backed up in Dropbox, so my D: drive just comes along for the ride completely intact, meaning it’s not &lt;em&gt;that&lt;/em&gt; big a deal to reformat C: and put a fresh Windows install on it. So I &lt;a href=&quot;https://dylanbeattie.net/2021/11/12/copying-your-hard-drive-with-robocopy.html&quot;&gt;took a backup copy of my C: drive&lt;/a&gt; – just in case – and then grabbed the latest Windows 10 Pro ISO, used Rufus to burn it to an 8Gb USB stick, and off we go.&lt;/p&gt;

&lt;p&gt;Windows 10 install took about 15 minutes. Here’s what goes on afterwards:&lt;/p&gt;

&lt;h4 id=&quot;configure-windows&quot;&gt;Configure Windows&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;Explorer, View, Folder Options
    &lt;ul&gt;
      &lt;li&gt;Check “Display the full path in the title bar”&lt;/li&gt;
      &lt;li&gt;Uncheck “Hide extensions for known file types”&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Set desktop wallpaper to something suitably atmospheric from &lt;a href=&quot;https://www.deviantart.com/kvacm&quot;&gt;kvacm&lt;/a&gt; (and if you like Michal’s stuff, &lt;a href=&quot;https://www.patreon.com/kvacm&quot;&gt;support him on Patreon&lt;/a&gt;!)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;the-essentials&quot;&gt;The Essentials&lt;/h3&gt;

&lt;p&gt;I use &lt;a href=&quot;https://ninite.com/&quot;&gt;Ninite&lt;/a&gt; to install Chrome, Firefox, Evernote, Steam, Zoom, Discord, Dropbox, Python x64 3, WinSCP, PuTTY, Visual Studio Code, Winamp, Audacity, Spotify, K-Lite Codecs, WinDirStat, and 7-Zip.&lt;/p&gt;

&lt;p&gt;For the last few months I’ve been using Vivaldi as my default browser, but this time around I’m gonna stick with Edge for a while and see how it goes.&lt;/p&gt;

&lt;p&gt;Sign in to Chrome, that syncs my bookmarks and browser extensions, including &lt;a href=&quot;https://www.lastpass.com/&quot;&gt;LastPass&lt;/a&gt;, which has credentials for pretty much everything I do online.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;It always surprises me how usable the system is at this point. So much stuff is web/cloud based now that a connection, a browser and LastPass is enough to get into Google Drive, Gmail, Twitter, Facebook, GitHub&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;the-big-bag-o-fonts&quot;&gt;The Big Bag O’ Fonts&lt;/h3&gt;

&lt;p&gt;I have a &lt;em&gt;lot&lt;/em&gt; of fonts. They’re all backed up in Dropbox. Ctrl-A, right-click, “Install for All Users…”&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2022/image-20220708150027713.png&quot; alt=&quot;image-20220708150027713&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I like fonts.&lt;/p&gt;

&lt;h3 id=&quot;the-command-line-stuff&quot;&gt;The Command Line Stuff&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Windows Subsystem for Linux&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;installed by opening an Administrator command prompt and running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wsl --install&lt;/code&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Powershell&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;Not to be confused with Windows Powershell, which ships as part of Windows 10&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://apps.microsoft.com/store/detail/windows-terminal/9N0DX20HK701?hl=en-gb&amp;amp;gl=GB&quot;&gt;Windows Terminal&lt;/a&gt;&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;not to be confused with Windows Terminal Preview, which is the same, but different.&lt;/li&gt;
    &lt;/ul&gt;

    &lt;p&gt;&lt;img src=&quot;/images/posts/2022/image-20220708150542169.png&quot; alt=&quot;image-20220708150542169&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://ohmyposh.dev/docs/installation/windows&quot;&gt;oh-my-posh&lt;/a&gt;&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;…the necessary nerd fonts are already installed at this point ‘cos they’re part of the Big Bag O’ Fonts I installed earlier.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://www.cygwin.com/&quot;&gt;Cygwin64&lt;/a&gt;&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;Default selection, plus &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;curl&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wget&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;openssh&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;Everything goes into C:\Windows\Cygwin\bin, and I add this to the system path. This becomes my default location for any utilities  I want to be available from the command line, like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ffmpeg&lt;/code&gt; – like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/local/bin&lt;/code&gt; on a *nix system.&lt;/li&gt;
    &lt;/ul&gt;

    &lt;p&gt;&lt;img src=&quot;/images/posts/2022/image-20220708151906343.png&quot; alt=&quot;image-20220708151906343&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://git-scm.com/download/win&quot;&gt;git for windows&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;http://slproweb.com/products/Win32OpenSSL.html&quot;&gt;OpenSSL for Windows&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;ffmpeg&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;using the Windows precompiled binaries from &lt;a href=&quot;https://github.com/BtbN/FFmpeg-Builds/releases&quot;&gt;https://github.com/BtbN/FFmpeg-Builds/releases&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;Usually the &lt;a href=&quot;https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-lgpl.zip&quot;&gt;master latest win64 lgpl&lt;/a&gt; build is the one to go for.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;languages-and-sdks&quot;&gt;Languages and SDKs&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;.NET Core SDK 3.1&lt;/li&gt;
  &lt;li&gt;.NET Core SDK 5&lt;/li&gt;
  &lt;li&gt;.NET Core SDK 6&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://nodejs.org/en/download/&quot;&gt;nodeJS 16.x&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;and select the option to install build tools for building native C/C++ modules.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://rubyinstaller.org/downloads/&quot;&gt;Ruby for Windows&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;gui-apps&quot;&gt;GUI Apps&lt;/h3&gt;

&lt;p&gt;Then it’s a big long list of apps I use on a daily basis, which I’m listing here as much for my benefit as yours:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Zoom, Slack, WhatsApp, Signal, Telegram, Discord…
    &lt;ul&gt;
      &lt;li&gt;yes, every single one of these platforms is the communication tool of choice for at least one friend, collaborator or online community that I’m connected to.  And that’s not counting the ones who use Twitter DMS, Facebook Messenger, Facetime, Skype… or even good old fashioned email.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://obsproject.com/&quot;&gt;OBS Studio&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;great for streaming, great for screen recording, great for doing cool stuff with overhead cameras and pens and paper during Zoom training sessions.&lt;/li&gt;
      &lt;li&gt;Scenes and profiles are all copied over from the old system; they’re stored at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;C:\Users\dylan\AppData\Roaming\obs-studio\&lt;/code&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.techsmith.com/download/snagit/&quot;&gt;SnagIt&lt;/a&gt; and &lt;a href=&quot;https://www.techsmith.com/download/camtasia&quot;&gt;Camtasia&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;I set up SnagIt with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift&lt;/code&gt;+&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl&lt;/code&gt;+&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\&lt;/code&gt; as a quick capture hotkey.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Microsoft Office
    &lt;ul&gt;
      &lt;li&gt;Powerpoint, Word, and Excel, I use on a daily basis. Access, maybe once in a blue moon. Teams when I have no choice. Outlook, OneNote, and Publisher, I don’t think I’ve ever opened except by mistake, but hey, they’re part of the installer now, and it looks like the screen in the Office installer where you choose the bits you actually want has been banished to the great recycle bin in the sky, so I guess I’ve got them too. Meh. Storage is cheap.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Microsoft Visio
    &lt;ul&gt;
      &lt;li&gt;Visio rocks. For architecture diagrams, floor plans, presentation slides that are just a bit too complicated for PowerPoint, I find it indispensable.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Visual Studio 2019
    &lt;ul&gt;
      &lt;li&gt;including .NET Framework 4.6.2, 4.7 and 4.8, for running training with clients using .NET Framework 4.x&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Visual Studio 2022&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;After installing Visual Studio and Resharper, to into Tools &amp;gt; Options &amp;gt; Source Control &amp;gt; Plugin Selection and set it to None. Otherwise you’ll get an error message “Files still read-only” when trying to do various refactoring and renaming things with Resharper.&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;https://stackoverflow.com/questions/26715783/resharper-function-shows-files-still-read-only&quot;&gt;https://stackoverflow.com/questions/26715783/resharper-function-shows-files-still-read-only&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;Docker Desktop for Windows
    &lt;ul&gt;
      &lt;li&gt;using the WSL2 subsystem, not the old HyperV system&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/sql/ssms/download-sql-server-management-studio-ssms&quot;&gt;SQL Server Management Studio&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;these days I always run the actual server inside a Docker container, but the SQL client tools are still useful&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Adobe Creative Cloud
    &lt;ul&gt;
      &lt;li&gt;Photoshop, Illustrator, Premiere, AfterEffects, Dimension, Audition, Character Animator.&lt;/li&gt;
      &lt;li&gt;…and remember to go into Preferences in the Creative Cloud Desktop app, enable “older apps”, and install the Extendscript Toolkit CC for running JS automation scripts in AfterEffects.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Axialis &lt;a href=&quot;https://www.axialis.com/iconworkshop/&quot;&gt;IconWorkshop&lt;/a&gt; and &lt;a href=&quot;https://www.axialis.com/cursorworkshop/&quot;&gt;CursorWorkshop&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;JetBrains Toolbox
    &lt;ul&gt;
      &lt;li&gt;and then DataGrip (for poking PostgreSQL stuff running in Docker containers), WebStorm (for nodeJS), Resharper, Rider, and Pycharm&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.ncrunch.net/&quot;&gt;NCrunch&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://ngrok.com/&quot;&gt;NGrok&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://openhardwaremonitor.org/&quot;&gt;Open Hardware Monitor&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/microsoft/PowerToys&quot;&gt;Microsoft PowerToys&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://typora.io/&quot;&gt;Typora&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.scootersoftware.com/&quot;&gt;Beyond Compare&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://instant-eyedropper.com/downloads/&quot;&gt;Instant Eyedropper&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/sysinternals/downloads/sysinternals-suite&quot;&gt;SysInternals suite&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;unzip the whole thing into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;C:\Program Files\SysInternals\&lt;/code&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.postman.com/&quot;&gt;Postman&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.qbittorrent.org/&quot;&gt;qBittorrent&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/protocolbuffers/protobuf/releases/&quot;&gt;Protocol Buffers&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.elgato.com/en/downloads&quot;&gt;Elgato StreamDeck&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And there you go - fresh install to a usable system in less than a day. Sure, I’ll be finding odd bits that aren’t set up quite right for a week or two, and there will invariably be some app I reach for one day a few months from now and discover I forgot to install it, but WIndows 10 just feels so much snappier than 11 ever did – and best of all, I can use the clipboard in Photoshop again. 😊&lt;/p&gt;
</description>
          <pubDate>2022-07-08T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2022/07/08/reinstalling-windows-again.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2022/07/08/reinstalling-windows-again.html</guid>
        </item>
      
    
      
        <item>
          <title>Rebooting the London .NET User Group</title>
          <description>&lt;p&gt;It’s been a difficult couple of years for tech events&lt;sup&gt;&lt;em&gt;[citation needed]&lt;/em&gt;&lt;/sup&gt;, but  in-person conferences are back, venues and event spaces are open for business as usual, and we’re really happy to announce that the London .NET User Group (aka &lt;a href=&quot;https://twitter.com/search?q=%23ldnug&amp;amp;f=live&quot;&gt;LDNUG&lt;/a&gt;) will be running regular in-person meetings again starting in August.&lt;/p&gt;

&lt;p&gt;Here’s a quick FAQ to get you up to speed on who we are, what we’re doing, and how to get involved.&lt;/p&gt;

&lt;h3 id=&quot;what-is-the-london-net-user-group&quot;&gt;What is the London .NET User Group?&lt;/h3&gt;

&lt;p&gt;It’s a monthly, free community meetup for people based around London who develop software using .NET and related technologies.&lt;/p&gt;

&lt;h3 id=&quot;whos-behind-it&quot;&gt;Who’s behind it?&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://twitter.com/icooper&quot;&gt;Ian Cooper&lt;/a&gt; started the group in 2002, and he’s been running it ever since. &lt;a href=&quot;https://dylanbeattie.net/&quot;&gt;Dylan Beattie&lt;/a&gt; (that’s me!) has been helping run the group since 2015, and &lt;a href=&quot;https://twitter.com/westleyl&quot;&gt;various&lt;/a&gt; &lt;a href=&quot;https://twitter.com/holytshirt&quot;&gt;other&lt;/a&gt; &lt;a href=&quot;https://twitter.com/robinem&quot;&gt;people&lt;/a&gt; help out from time to time.&lt;/p&gt;

&lt;h3 id=&quot;why-do-you-do-it&quot;&gt;Why do you do it?&lt;/h3&gt;

&lt;p&gt;Three reasons:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;It’s a great way to meet people who are working on the same things that you are. Maybe they can help you, maybe they can hire you, maybe you can hire them? Over the 20 years we’ve been running the group, people have found jobs, contracts, employees, open-source collaborators – and made quite a few friends along the way, too.&lt;/li&gt;
  &lt;li&gt;It’s a great way to hear first-hand from folks who are working with the latest &amp;amp; greatest tech – or who are applying more established technology in some interesting and unexpected ways. Listening to people talk about what they actually did, how they did it, what worked and what didn’t work, can provide a really valuable perspective if you’re thinking about trying out a new technology on one of your own projects.&lt;/li&gt;
  &lt;li&gt;It’s a platform for speakers to try out their talks and content in front of a friendly audience, figure out what works, and get constructive feedback on their presentations. Many of the people you’ll see speaking at big international conferences did their first talk at a group like LDNUG – and, just as importantly, many of those same “big name” speakers use the group to try out new talks and present new material before submitting it to those same big conferences.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;what-happens-at-the-meetups&quot;&gt;What happens at the meetups?&lt;/h3&gt;

&lt;p&gt;Folks normally start arriving around 18:30. Around 19:00, we’ll kick off with a few announcements about forthcoming events and interesting stuff that’s going on in the wide world of .NET. We aim to have at least two speakers at each event; they’ll do 45-60 minutes each, with a break in the middle for drinks and chat. Talks should finish by 21:30, and there’s usually a few people heading to a nearby pub afterwards for drinks and chat.&lt;/p&gt;

&lt;h3 id=&quot;will-your-meetups-be-streamed-online&quot;&gt;Will your meetups be streamed online?&lt;/h3&gt;

&lt;p&gt;Probably not. For now we’re relying on companies to provide event space to host our meetups, and live streaming adds a whole extra level of complexity both for the organisers (us!) and for the venue that’s hosting us.&lt;/p&gt;

&lt;h3 id=&quot;will-your-meetups-be-recorded&quot;&gt;Will your meetups be recorded?&lt;/h3&gt;

&lt;p&gt;We’re looking into it. We’d love to be able to publish talks and videos on YouTube after each event, but we’re still figuring out the details.&lt;/p&gt;

&lt;h3 id=&quot;what-sort-of-things-do-you-talk-about&quot;&gt;What sort of things do you talk about?&lt;/h3&gt;

&lt;p&gt;Anything and everything, as long as it’s vaguely connected with building software using .NET. We’ve hosted lots of deep-dive technical talks about things like C# language features, .NET memory management, and performance optimisations. We’ve hosted introductory talks to technologies like Blazor and SignalR, we’ve had talks about related technologies like SQL, frontend development, and node.JS, and we’ve hosted speakers talking about all kinds of topics related to working in development – security, culture, diversity, recruitment.&lt;/p&gt;

&lt;h3 id=&quot;can-i-do-a-talk&quot;&gt;Can I do a talk?&lt;/h3&gt;

&lt;p&gt;Sure you can. &lt;strong&gt;&lt;a href=&quot;https://sessionize.com/london-dotnet-user-group/&quot;&gt;Submit your talk via our Sessionize&lt;/a&gt;&lt;/strong&gt;, and we’ll be in touch to find a date that works and figure out some details.&lt;/p&gt;

&lt;h3 id=&quot;i-cant-join-in-person---can-i-do-an-online-talk-via-zoom&quot;&gt;I can’t join in person - can I do an online talk via Zoom?&lt;/h3&gt;

&lt;p&gt;Probably not. For now, we’re prioritising in-person events, with live speakers talking to an in-person audience; after two years of online events we’re all a bit burned out on Zoom talks and “hey, leave a comment in the chat”.&lt;/p&gt;

&lt;h3 id=&quot;can-i-come-along-and-watch&quot;&gt;Can I come along and watch?&lt;/h3&gt;

&lt;p&gt;Yeah, absolutely! Join our &lt;a href=&quot;https://www.meetup.com/london-net-user-group/&quot;&gt;Meetup group&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/LondonDotNet&quot;&gt;follow us on Twitter&lt;/a&gt;, and we’ll let you know when we announce new dates.&lt;/p&gt;

&lt;h3 id=&quot;can-my-company-host-a-meetup&quot;&gt;Can my company host a meetup?&lt;/h3&gt;

&lt;p&gt;Probably! If you’ve got some venue or event space in central London (zone 1 &amp;amp; 2), and you’d like to invite a few dozen .NET developers to see your offices and meet some of your team, hosting a meetup is a great way to make it happen. We’ve done this many times with companies who are hiring developers, which tends to work out well, but even if you’re not hiring it’s a great way to support the London .NET community.&lt;/p&gt;

&lt;p&gt;You’ll need to provide a meeting space, auditorium, or similar, equipped with a projector/TV screen, ideally 1920x1080 HD or better, with HDMI. We’ll need audio in case speakers have music/sound in their presentations, and speakers will need to have their own laptop on stage with them when they’re presenting so they can run live demos.&lt;/p&gt;

&lt;p&gt;We’ll also ask you to provide refreshments: water, tea, coffee, soft drinks and snacks. (If you want to provide beer, wine, pizza, buffet snacks, etc. for attendees, that’s very much appreciated, but not required.) Guest access to wifi is normally a good idea too, although we can work around this as long as we know about it in advance. In exchange, you’ll get a chance to show off your offices, tell our attendees a bit about your company and what you’re working on, and feel good about supporting local community events. 😊&lt;/p&gt;

&lt;p&gt;If that all sounds like something you’re interested in, drop us an email and let’s work something out. We aim to schedule events at least three months in advance.&lt;/p&gt;

&lt;h3 id=&quot;where-do-i-find-out-more&quot;&gt;Where do I find out more?&lt;/h3&gt;

&lt;p&gt;All our events are announced via our Meetup group:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.meetup.com/london-net-user-group/&quot;&gt;https://www.meetup.com/london-net-user-group/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’re also on Twitter:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://twitter.com/LondonDotNet&quot;&gt;https://twitter.com/LondonDotNet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re interested in speaking, submit a talk via our Sessionize:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://sessionize.com/london-dotnet-user-group/&quot;&gt;https://sessionize.com/london-dotnet-user-group/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to get in touch with the organisers, you can email us here:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Dylan Beattie: &lt;a href=&quot;mailto:dylan@dylanbeattie.net?subject=London+.NET+User+Group&quot;&gt;dylan@dylanbeattie.net&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Ian Cooper: &lt;a href=&quot;mailto:ian_hammond_cooper@outlook.com?subject=London+.NET+User+Group&quot;&gt;ian_hammond_cooper@outlook.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
          <pubDate>2022-06-20T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2022/06/20/rebooting-london-dotnet-user-group.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2022/06/20/rebooting-london-dotnet-user-group.html</guid>
        </item>
      
    
      
        <item>
          <title>A VBA macro for storyboarding PowerPoint slide decks</title>
          <description>&lt;p&gt;I’m working on a couple of new talks at the moment, and thought people might be interested in seeing a bit about how I actually put them together.&lt;/p&gt;

&lt;h3 id=&quot;step-1-write-the-talk&quot;&gt;Step 1: Write the Talk&lt;/h3&gt;

&lt;p&gt;All my talks start life as a written transcript. I speak at 1,000 characters per minute, so for a 1-hour talk, that’s about 60,000 characters – between 10,000 and 12,000 words. But the first draft of a talk is normally more like 15,000-20,000 words, because this is where I throw absolutely everything at the wall to see what sticks. All the ideas, all the stories, everything that’s been rattling around in my brain or my Evernote library since I started thinking about writing the talk.&lt;/p&gt;

&lt;p&gt;Once I’ve got everything drafted, I edit, edit, and edit again. I move things around, figure out how to connect the sections so the talk flows properly, and then go through and edit out the &lt;a href=&quot;https://twitter.com/dylanbeattie/status/1515650147119779850&quot;&gt;bombadil&lt;/a&gt; until I have something about the right length which I think hangs together nicely.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Good morning, everybody! I’m Dylan, and I’m here today to talk to you about yak shaving. You’re probably wondering what yak shaving is. Well… you know that thing where you need to do something really, really trivial? Like changing the year from 2021 to 2022 in the copyright footer on your website… shouldn’t be a big deal, right? So you check out the code, and change it, and commit the change, and then you go to deploy it to production and discover that the SSL certificate used by your deployment pipeline has expired. So you roll a new certificate… but you can’t install the new key onto the production server because your user account doesn’t have permission to do that. So you try to sudo onto the box, but you don’t know the root password, and the only person who does know it is backpacking in the Cordillera Huayhuash until March, so the only way to get it live is to physically log onto the server in recovery mode, but nobody’s been into the server room for so long that the lock on the door has rusted shut, so you go to the hardware store and get some WD40, and you squirt it into the lock, and whack it a few times with a hammer to try and loosen it up… and somebody walks past and sees you attacking the server room door lock with a hammer and asks what you’re doing, and you reply “CAN’T YOU SEE I’M TRYING TO UPDATE THE COPYRIGHT FOOTER ON THE WEBSITE?”&lt;/p&gt;

  &lt;p&gt;Well, that’s yak shaving.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I’ll run through that at least once, just me, talking into a camera. No slides or anything yet; this is just me trying all the words on and checking they fit properly, making sure I know how to pronounce them all (how &lt;em&gt;do&lt;/em&gt; you pronounce “Cordillera Huayhuash”?), and double-checking the timing.&lt;/p&gt;

&lt;p&gt;If that’s a train wreck, I’ll go back, edit some more, and do it again. Eventually, I’ll have a written transcript which I know is pretty much the right words, in the right order, and the right length.&lt;/p&gt;

&lt;h3 id=&quot;step-2-work-out-what-slides-i-want&quot;&gt;Step 2: Work out what slides I want&lt;/h3&gt;

&lt;p&gt;Then, I’ll go through that transcript and put in slide markers. This is all real low-tech - I’m just hacking around plain text files here - but I’ll identify the exact points in the presentation where I want to show a slide (or add/change something on an existing slide), and put in text markers. Markers will often be right in the middle of a sentence – if that’s the moment I want the slide to appear, that’s where I put the marker:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;[SLIDE: Title slide]&lt;/strong&gt; Good morning, everybody! I’m Dylan, and I’m here today to talk to you about &lt;strong&gt;[SLIDE: that yak shaving cartoon from Ren &amp;amp; Stimpy]&lt;/strong&gt; yak shaving. You’re probably wondering what yak shaving is. Well… you know that thing where you need to do something really, really trivial? Like &lt;strong&gt;[SLIDE: screenshot of website footer copyright message]&lt;/strong&gt; changing the year from 2021 to 2022 in the copyright footer on your website… shouldn’t be a big deal, right? So you check out the code, and change it, and commit the change, and then you go to deploy it to production and discover that &lt;strong&gt;[SLIDE: expired certificate error message]&lt;/strong&gt; the SSL certificate used by your deployment pipeline has expired. So you roll a new certificate… but you can’t install the new key onto the production server because &lt;strong&gt;[SLIDE: insufficient permisssions message]&lt;/strong&gt; your user account doesn’t have permission to do that. So you try to sudo onto the box, but you don’t know the root password, and the only person who does know it is &lt;strong&gt;[SLIDE: picture of somebody hiking in the mountains]&lt;/strong&gt; backpacking in the Cordillera Huayhuash until March, so the only way to get it live is to physically log onto the server in recovery mode and reset the root password, but nobody’s been into the server room for so long that &lt;strong&gt;[SLIDE: a really, REALLY rusty door lock]&lt;/strong&gt; the lock on the door has rusted shut, so you go to the hardware store and &lt;strong&gt;[SLIDE: picture of WD40]&lt;/strong&gt; get some WD40, and you squirt it into the lock, and &lt;strong&gt;[SLIDE: picture of a big old hammer]&lt;/strong&gt; whack it a few times with a hammer to try and loosen it up… and somebody walks past and sees you attacking the server room door lock with a hammer and asks what you’re doing, and you reply &lt;strong&gt;[SLIDE: picture of me looking really really stressed, with a speech bubble]&lt;/strong&gt; “CAN’T YOU SEE I’M TRYING TO UPDATE THE COPYRIGHT FOOTER ON THE WEBSITE?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I save that as a text file.&lt;/p&gt;

&lt;h3 id=&quot;step-3-storyboard-the-powerpoint-deck&quot;&gt;Step 3: Storyboard the PowerPoint deck&lt;/h3&gt;

&lt;p&gt;I have a PowerPoint template that contains my preferred fonts and colour scheme – and which also contains a VBA macro I wrote, which will take the transcript TXT file and turn it into a slide deck. It’ll put the slide marker text as the headline on each slide, and put the relevant chunk of the transcript into the slide notes area:&lt;/p&gt;

&lt;div class=&quot;language-visualbasic highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;Public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Sub&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;BuildSlideDeckFromTranscript&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;Dim&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;filePath&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;As&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;Dim&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;contents&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;As&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;
    
    &lt;span class=&quot;n&quot;&gt;filePath&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PromptForFilePath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;If&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filePath&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Then&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Exit&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Sub&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;contents&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ReadUtf8TextFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filePath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;Dim&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;slideTexts&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;As&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Variant&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;&apos; vbTextCompare indicates case-insensitive string comparison&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;slideTexts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;contents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;[SLIDE: &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vbTextCompare&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;slideCount&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UBound&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slideTexts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;If&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slideCount&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Then&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;MsgBox&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;I didn&apos;t find any slide markers in that file.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;Exit&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Sub&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;End&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;If&lt;/span&gt;
        
    &lt;span class=&quot;k&quot;&gt;If&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MsgBox&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Found &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slideCount&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot; slide markers. Do you want to create slides now?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vbYesNo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vbYes&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Then&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Exit&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Sub&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;Dim&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pptLayout&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;As&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CustomLayout&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;Set&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pptLayout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ActivePresentation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Slides&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CustomLayout&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;SlideIndex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ActivePresentation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Slides&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Count&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;For&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Index&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LBound&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slideTexts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;To&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UBound&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slideTexts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;slideText&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slideTexts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;tokens&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slideText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;If&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UBound&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tokens&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Then&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;SlideIndex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SlideIndex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;Set&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pptSlide&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ActivePresentation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Slides&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AddSlide&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SlideIndex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pptLayout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;pptSlide&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Shapes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TextFrame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TextRange&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tokens&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;pptSlide&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NotesPage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Shapes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TextFrame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TextRange&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tokens&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;End&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;If&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;Next&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Index&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;End&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Sub&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;Function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;PromptForFilePath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;As&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;Dim&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;filePicker&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;As&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FileDialog&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;Set&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filePicker&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FileDialog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;msoFileDialogFilePicker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;   
    &lt;span class=&quot;k&quot;&gt;Dim&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;selectedItem&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;As&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Variant&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;Dim&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;filePath&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;As&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;With&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filePicker&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;InitialFileName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ActivePresentation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Path&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Filters&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Add&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Text files&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;*.txt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;If&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Show&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Then&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;For&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Each&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;selectedItem&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;In&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SelectedItems&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;filePath&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;selectedItem&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;Next&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;selectedItem&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;End&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;If&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;End&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;With&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;PromptForFilePath&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filePath&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;End&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Function&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;Function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ReadUtf8TextFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filePath&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;As&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;As&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;Dim&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;contents&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;As&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;Dim&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;stream&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;Set&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CreateObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ADODB.Stream&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;With&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Charset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;utf-8&quot;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Open&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LoadFromFile&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filePath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;contents&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ReadText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Close&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;End&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;With&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;Set&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Nothing&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ReadUtf8TextFile&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;contents&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;End&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Function&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’ll give me a deck full of blank slides, with each slide’s title set to the slide marker text, and the relevant bit of transcript in the slides’ speaker notes area:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2022/image-20220418111446698.png&quot; alt=&quot;image-20220418111446698&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;step-4-make-the-actual-slides&quot;&gt;Step 4: Make the actual slides&lt;/h3&gt;

&lt;p&gt;Finally, I go through the entire deck and replace each placeholder slide with the image, video, quote, or animation. Some slides will go through a couple of iterations, especially if I’m making custom visualisations or animations. Sometimes, I’ll join 2-3 sections into a single slide with lots of animation; sometimes I’ll split a slide out into a couple of separate ones.&lt;/p&gt;

&lt;p&gt;I find this a really efficient way to work, but it also means that when I’m thinking about what I want to show on my slides, I’m not thinking in terms of PowerPoint layouts and bullet points; I’m thinking about what I &lt;em&gt;actually&lt;/em&gt; want people to be looking at. It also means if I share a PDF of the deck, I can include the speaker notes, so underneath each slide in the PDF you see what I was actually talking about during that part of the presentation. Which I think is rather neat.&lt;/p&gt;
</description>
          <pubDate>2022-04-18T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2022/04/18/making-powerpoint-slides-from-a-transcript.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2022/04/18/making-powerpoint-slides-from-a-transcript.html</guid>
        </item>
      
    
      
        <item>
          <title>“The Art of Code” Workshops with fwdays to support Ukraine</title>
          <description>&lt;p&gt;Ukraine is a wonderful country. I’ve been there many times, speaking at conferences and meeting Ukrainian developers; it’s a fascinating place to visit, and one I was very much looking forward to revisiting when the pandemic was over. What is happening there right now is horrific. Vladimir Putin’s illegal invasion, and the death and destruction that his actions have inflicted on the people of Ukraine, are unforgivable.&lt;/p&gt;

&lt;p&gt;Donations of money are very, very welcome, whether to help equip the Ukrainian military or to assist humanitarian efforts providing medical assistance, transportation, and support to the millions of people who have lost their homes and livelihoods.&lt;/p&gt;

&lt;p&gt;But the people I know in Ukraine are also working really hard to keep the Ukrainian economy working: delivering services, paying salaries, paying taxes. Over the past few years, I’ve run lots of online workshops with the crew from &lt;a href=&quot;https://fwdays.com/en/&quot;&gt;fwdays&lt;/a&gt;. Theyre obviously not able to run any of their regular conferences and in-person training at the moment, so instead they’re organising a series of online workshops with all the profits going to support humanitarian and defence causes in Ukraine.&lt;/p&gt;

&lt;p&gt;To help them out with this, I’m running a series of half-day workshops based around ideas from my talk “&lt;a href=&quot;https://www.youtube.com/watch?v=6avJHaC3C2U&quot;&gt;The Art of Code&lt;/a&gt;” and the idea of programming as a creative art form – and I’ll be donating all my fees from these workshops to support Ukraine.&lt;/p&gt;

&lt;p&gt;We’ll be kicking off on April 14th with a workshop about ray tracing in JavaScript. During four hours of live hands-on coding, we’ll build a fully featured ray tracer, in pure JavaScript, that runs directly in your web browser. You’ll learn the principles of 3D computer graphics, and how to simulate lighting, shading, reflection, and visual effects to create photo-realistic scenes. Not only that, but along the way you’ll learn about some state-of-the-art JavaScript and web technologies, including the HTML Canvas API, web worker, ES modules, clamped arrays, and many more.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2022/image-20220403171225597.png&quot; alt=&quot;image-20220403171225597&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://fwdays.com/en/event/the-art-of-code-ray-tracing-in-java-script&quot;&gt;Tickets are on sale now&lt;/a&gt;, with prices starting from UAH2534 (about £65/€75). It will all be online, using Zoom, Slack, GitHub, and various web-based collaboration tools. Find out more and book now at:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://fwdays.com/en/event/the-art-of-code-ray-tracing-in-java-script&quot;&gt;https://fwdays.com/en/event/the-art-of-code-ray-tracing-in-java-script&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Any questions about it? &lt;a href=&quot;https://twitter.com/dylanbeattie&quot;&gt;Find me on Twitter&lt;/a&gt; or drop me an email at &lt;a href=&quot;mailto:dylan@dylanbeattie.net&quot;&gt;dylan@dylanbeattie.net&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We’ll have more workshops coming up soon, covering everything from designing esoteric programming languages to fractal geometry and Conway’s Game of Life, so keep an eye out for those. You’ll learn some useful real-world programming techniques, you’ll discover fun, creative, and inspirational ways to apply those techniques – and you’ll be helping to support Ukraine.&lt;/p&gt;

&lt;p&gt;Hope to see you there.&lt;/p&gt;
</description>
          <pubDate>2022-04-02T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2022/04/02/the-art-of-code-workshops-with-fwdays.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2022/04/02/the-art-of-code-workshops-with-fwdays.html</guid>
        </item>
      
    
      
        <item>
          <title>Database already exists when running EF Core migrations</title>
          <description>&lt;p&gt;I’m working on a project that uses Entity Framework Core, and I’m using EF Core Migrations to manage database state. Earlier today, I grabbed a fresh (obfuscated) snapshot of the production database, copied it across to my workstation, and tried to run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotnet ef database update&lt;/code&gt; to apply the latest migrations from my current branch:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;14:37:27 INF] Entity Framework Core 6.0.1 initialized &lt;span class=&quot;s1&quot;&gt;&apos;MyDbContext&apos;&lt;/span&gt; using provider &lt;span class=&quot;s1&quot;&gt;&apos;Microsoft.EntityFrameworkCore.SqlServer:6.0.1&apos;&lt;/span&gt; with options: using lazy loading proxies
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;14:37:27 ERR] Failed executing DbCommand &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;15ms&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Parameters&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=[]&lt;/span&gt;, &lt;span class=&quot;nv&quot;&gt;CommandType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;Text&apos;&lt;/span&gt;, &lt;span class=&quot;nv&quot;&gt;CommandTimeout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;60&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
CREATE DATABASE &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;my-database]
COLLATE SQL_Latin1_General_CP1_CI_AI&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
Failed executing DbCommand &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;15ms&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Parameters&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=[]&lt;/span&gt;, &lt;span class=&quot;nv&quot;&gt;CommandType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;Text&apos;&lt;/span&gt;, &lt;span class=&quot;nv&quot;&gt;CommandTimeout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;60&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
CREATE DATABASE &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;my-database]
COLLATE SQL_Latin1_General_CP1_CI_AI&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
Microsoft.Data.SqlClient.SqlException &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;0x80131904&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;: Database &lt;span class=&quot;s1&quot;&gt;&apos;my-database&apos;&lt;/span&gt; already exists. Choose a different database name.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It’s failing because the database exists… but I know the database exists – I just created it! It shouldn’t be trying to create a new database at all… right?&lt;/p&gt;

&lt;p&gt;It turns out that the database exists, but the user account doesn’t. My application’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;appSettings.development.json&lt;/code&gt; contains a database connection string which specifies a username and password:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;ConnectionStrings&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;MyDatabase&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Server=localhost;Database=my-database;User Id=my-user;Password=p@ssw0rd;MultipleActiveResultSets=true;Connect Timeout=1&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That username/password is still a valid server login on localhost (and, hey, look at that super-secret password right there!), but there’s no corresponding user in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;my-database&lt;/code&gt; database, so I’m guessing what happens here is EF Core connects to the server (which works), tries to open the database, can’t open it, and so assumes it doesn’t exist – and so attempts to create it, which fails because it already exists.&lt;/p&gt;

&lt;p&gt;I recreated the database user linked to the login (and added them to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;db_owner&lt;/code&gt; role so that EF Core has permission to create tables, indexes, etc.):&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;USE&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;my&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;database&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;GO&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;USER&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;my&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;FOR&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LOGIN&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;my&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;WITH&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DEFAULT_SCHEMA&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dbo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;GO&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;USE&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;my&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;database&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;GO&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ROLE&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;db_owner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ADD&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MEMBER&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;my&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;GO&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;and it worked just fine.&lt;/p&gt;
</description>
          <pubDate>2022-02-01T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2022/02/01/database-already-exists-running-dotnet-ef-database-update.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2022/02/01/database-already-exists-running-dotnet-ef-database-update.html</guid>
        </item>
      
    
      
        <item>
          <title>Securing Admin Pages with ASP.NET and Azure AD</title>
          <description>&lt;p&gt;I recently had to implement an authentication pattern which I must have built a dozen times over the course of my career, on various platforms ranging from classic ASP to Ruby on Rails. This time around it’s in ASP.NET Core on .NET Core 3.1, and although all the individual components are easy enough to plug in, I couldn’t find a resource describing how to implement this exact use case, so here’s how I did it.&lt;/p&gt;

&lt;h4 id=&quot;the-scenario&quot;&gt;The Scenario&lt;/h4&gt;

&lt;ol&gt;
  &lt;li&gt;There’s a website at &lt;strong&gt;https://example.com/&lt;/strong&gt; that’s available to the general public. No security, no user registration, no authentication.&lt;/li&gt;
  &lt;li&gt;There is an admin area at &lt;strong&gt;https://example.com/admin&lt;/strong&gt; which is only accessible to employees of Example Company Ltd&lt;/li&gt;
  &lt;li&gt;We can assume that anybody with an &lt;strong&gt;@example.com&lt;/strong&gt; email address is an employee&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What makes this a little easier than the Olden Days is that Example Company Ltd runs all their email on Office 365, so there’s an Azure OpenID Connect endpoint I can use to authenticate users. What makes it a little more difficult is that that although Example Company Ltd use Office 365 and they all have Microsoft accounts, they don’t actually have an Azure subscription (and don’t want to set one up), so the organisation that’s managing the Azure app registrations (i.e. me) isn’t the same as the organisation that employs the authorised users.&lt;/p&gt;

&lt;p&gt;Anyway. Here’s how it all works. You’ll need to register your app in the &lt;strong&gt;App registration&lt;/strong&gt; area of the Azure dashboard, which &lt;a href=&quot;https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade&quot;&gt;you should be able to find here&lt;/a&gt;. You’ll also need a section in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;appsettings.json&lt;/code&gt; called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;AzureAd&quot;&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;AzureAd&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Instance&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://login.microsoftonline.com/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Domain&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;example.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;ClientId&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;YOUR-AZURE-REGISTERED-APP-CLIENT-ID&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;TenantId&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;common&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ClientId&lt;/code&gt; there is the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Application (client) Id&lt;/code&gt; from the Azure dashboard.&lt;/p&gt;

&lt;p&gt;Install the NuGet packages for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Microsoft.Identity.Web&lt;/code&gt; middleware:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dotnet add package Microsoft.Identity.Web
dotnet add package Microsoft.Identity.Web.UI
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then we need to add a few things to our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Startup.cs&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;Define a string constant for the &lt;strong&gt;policy name&lt;/strong&gt;, and another one for the &lt;strong&gt;authorised domain&lt;/strong&gt;:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EMPLOYEES_ONLY_POLICY&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;EmployeesOnly&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AUTHORIZED_DOMAIN&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;example.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next, add a chunk of code to the top of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ConfigureServices&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;azureConfig&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;GetSection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;AzureAd&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AddAuthentication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;OpenIdConnectDefaults&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AuthenticationScheme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AddMicrosoftIdentityWebApp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ClientId&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;azureConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ClientId&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Instance&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;azureConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Instance&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TenantId&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;azureConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;TenantId&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Events&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;OnRedirectToIdentityProvider&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ProtocolMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DomainHint&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AUTHORIZED_DOMAIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CompletedTask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There’s an overload for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AddMicrosoftIdentityWebApp&lt;/code&gt; which lets you just pass in the config section, but I couldn’t find any way to do this and then register the event handler for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OnRedirectToIdentityProvider&lt;/code&gt;, which we’re using to pass a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DomainHint&lt;/code&gt; to the Azure/Microsoft login endpoint. This means if the current user has ever signed in using an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@example.com&lt;/code&gt; email address, it’ll default to using that one in future, which makes for a smoother login process.&lt;/p&gt;

&lt;p&gt;Where we add Razor pages to our app, we’re adding the Microsoft Identity UI, which gives us prebaked UI bits like the “access denied” page:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AddRazorPages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AddMicrosoftIdentityUI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next, we define our authorization policy, but we don’t actually attach it to anything yet. This means that all our app content will be available by default (so we don’t need to go around putting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[AllowAnonymous]&lt;/code&gt; on all our controllers), but we can lock down specific areas, controllers or actions by attaching this policy to them. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RequireAssertion&lt;/code&gt; here is the extension point we can use to run arbitrary code checks on the authorization context - in this example, verifying that the user’s identity name (which is their email address when using Azure AD) ends with the authorized domain.&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AddAuthorization&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AddPolicy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;EMPLOYEES_ONLY_POLICY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;AuthorizationPolicyBuilder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;RequireAuthenticatedUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;RequireEmailDomain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AUTHORIZED_DOMAIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RequireEmailDomain&lt;/code&gt; isn’t one of the policies provided out of the box, but that’s no problem; we can add it using an extension method:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AuthorizationPolicyExtensions&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AuthorizationPolicyBuilder&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;RequireEmailDomain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AuthorizationPolicyBuilder&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;domain&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;domain&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;StartsWith&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;@&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;domain&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$&quot;@&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;RequireAssertion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;email&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Identity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;EndsWith&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;ℹ️ Notice that we’re prepending an @-sign here. If we omitted this, you could sign in by registering a domain like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fakeexample.com&lt;/code&gt; and setting up a Microsoft login using that domain, because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;script.kiddie@fakeexample.com&quot;.EndsWith(&quot;example.com&quot;)&lt;/code&gt; will return true.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Finally, in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Configure&lt;/code&gt; method, we need to make sure the app is using both authentication and authorisation &lt;em&gt;(as an aside, don’t you love how clear this syntax is?)&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;UseAuthentication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;UseAuthorization&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And then map a controller route for our secure area, which includes the authorization policy:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;UseEndpoints&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;endpoints&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    
    &lt;span class=&quot;n&quot;&gt;endpoints&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;MapControllerRoute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;secure&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
        &lt;span class=&quot;s&quot;&gt;&quot;{area:exists}/{controller=Home}/{action=Index}/{id?}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;RequireAuthorization&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;EMPLOYEES_ONLY_POLICY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        
    &lt;span class=&quot;n&quot;&gt;endpoints&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;MapControllerRoute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;default&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;pattern&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;{controller=Home}/{action=Index}/{id?}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;endpoints&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;MapRazorPages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This means any controllers and actions that are part of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;secure&lt;/code&gt; area will have this authorization policy applied, and the rest of the app is still available to unauthenticated and unauthorised users.&lt;/p&gt;

&lt;p&gt;Here’s the whole &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Startup.cs&lt;/code&gt; with those extra bits in place:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Startup.cs&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;System.Threading.Tasks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Microsoft.AspNetCore.Authentication.OpenIdConnect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Microsoft.AspNetCore.Authorization&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Microsoft.Identity.Web&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Microsoft.Identity.Web.UI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Microsoft.AspNetCore.Builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Microsoft.AspNetCore.Hosting&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Microsoft.Extensions.Configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Microsoft.Extensions.DependencyInjection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Microsoft.Extensions.Hosting&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;WebApp_OpenIDConnect_DotNet&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Startup&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EMPLOYEES_ONLY_POLICY&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;EmployeesOnly&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AUTHORIZED_DOMAIN&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;example.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Startup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IConfiguration&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;Configuration&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IConfiguration&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Configuration&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// This method gets called by the runtime. Use this method to add services to the container.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// &amp;lt;Configure_service_ref_for_docs_ms&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ConfigureServices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IServiceCollection&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;azureConfig&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;GetSection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;AzureAd&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AddAuthentication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;OpenIdConnectDefaults&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AuthenticationScheme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AddMicrosoftIdentityWebApp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ClientId&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;azureConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ClientId&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Instance&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;azureConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Instance&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TenantId&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;azureConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;TenantId&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Events&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;OnRedirectToIdentityProvider&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ProtocolMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DomainHint&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AUTHORIZED_DOMAIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CompletedTask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AddControllersWithViews&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AddRazorPages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AddMicrosoftIdentityUI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AddAuthorization&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AddPolicy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;EMPLOYEES_ONLY_POLICY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;AuthorizationPolicyBuilder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;RequireAuthenticatedUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;RequireEmailDomain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AUTHORIZED_DOMAIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Configure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IApplicationBuilder&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IWebHostEnvironment&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;IsDevelopment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;UseDeveloperExceptionPage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;UseExceptionHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/Home/Error&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;UseHsts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;UseHttpsRedirection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;UseStaticFiles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;UseRouting&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;UseAuthentication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;UseAuthorization&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;UseEndpoints&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;endpoints&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;endpoints&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;MapControllerRoute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;admin&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;{area=admin:exists}/{controller=Home}/{action=Index}/{id?}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;RequireAuthorization&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;EMPLOYEES_ONLY_POLICY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;endpoints&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;MapControllerRoute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;default&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;pattern&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;{controller=Home}/{action=Index}/{id?}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;endpoints&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;MapRazorPages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AuthorizationPolicyExtensions&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AuthorizationPolicyBuilder&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;RequireEmailDomain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AuthorizationPolicyBuilder&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;domain&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;domain&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;StartsWith&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;@&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;domain&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$&quot;@&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;RequireAssertion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;email&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Identity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;EndsWith&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;One thing I haven’t figured out yet is how to override the built-in bits of UI for things like “Access Denied”. The policy works fine, but if you sign in with a valid Microsoft account that isn’t a @example.com email, you get this generic “Access Denied” message:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2022/image-20220125173200419.png&quot; alt=&quot;image-20220125173200419&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It’d be nice to replace this with something that explains only people with @example.com email addresses are allowed in.&lt;/p&gt;

&lt;p&gt;I also have no idea yet how you go about testing this - this sort of federated authentication is very much in the “sufficiently advanced technology and therefore indistinguishable from magic” category, and security has always been hard to test even when it doesn’t involve exchanging encrypted tokens with Actual Microsoft Infrastructure.&lt;/p&gt;

&lt;p&gt;But, hey, it works, and only adds a few lines of code. I’m quite happy with that – and the fact that all the authentication, password resets, locked accounts, setting up accounts for new users is all running on somebody else’s stack?&lt;/p&gt;

&lt;p&gt;That, my friends, is what we call progress.&lt;/p&gt;
</description>
          <pubDate>2022-01-25T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2022/01/25/securing-admin-pages-with-aspnet-and-azure-ad.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2022/01/25/securing-admin-pages-with-aspnet-and-azure-ad.html</guid>
        </item>
      
    
      
        <item>
          <title>Copying Your Hard Drive With Robocopy</title>
          <description>&lt;p&gt;Time to reinstall Windows - after 18 months of running Insider builds, hacking around with weird WSL/Docker network configurations, and trying out more weird video streaming hardware than you can possibly imagine, I’ve hit the point where certain things just don’t work any more.&lt;/p&gt;

&lt;p&gt;The plan:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Copy everything from the C: drive to somewhere safe.&lt;/li&gt;
  &lt;li&gt;Do a clean reformat &amp;amp; reinstall.&lt;/li&gt;
  &lt;li&gt;Yay everything works now.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Turns out it’s actually way harder to copy everything from your C: drive onto another drive when you’re still running from that C: drive. Lots of files are locked because they’re in use, and a bunch of weird junction points (Windows-speak for symbolic links) that in some cases can actually lead to infinitely deep recursive nested directories.&lt;/p&gt;

&lt;p&gt;Here’s the command that worked in the end:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;robocopy /s /b /z /xo /xj /V /R:0 /W:0 /copy:DT C:\ G:\backups\c\&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And here’s how that breaks down:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;robocopy	
/s		   Copy all subdirectories (except empty ones - use /e for that)
/b		   Copies files in backup mode (overrides file &amp;amp; folder permissions)
/z		   Copy files in restartable mode
/xo		   Ignore older files - useful if you need to restart the whole thing
/xj		   Ignore junction points.
/V		   Verbose. List everything as it&apos;s copied. Handy to see what&apos;s going on.
/R:0	   Retry files zero times if there&apos;s an error
/W:0	   Wait zero seconds before retrying
/copy:DT   Copy only data and timestamps (i.e. do not copy permissions, attributes)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Check out the full Robocopy docs at &lt;a href=&quot;https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/robocopy&quot;&gt;https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/robocopy&lt;/a&gt;&lt;/p&gt;

</description>
          <pubDate>2021-11-12T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2021/11/12/copying-your-hard-drive-with-robocopy.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2021/11/12/copying-your-hard-drive-with-robocopy.html</guid>
        </item>
      
    
      
        <item>
          <title>On The Road Again</title>
          <description>&lt;p&gt;This time last week, I was in &lt;a href=&quot;https://en.wikipedia.org/wiki/Camber_Sands&quot;&gt;Camber&lt;/a&gt;, a tiny village on the English coast. After two weeks of mostly vacation, including all the meteorological delights that you get for free when you holiday on the south coast of England, I was catching up on emails and looking at the next few months and beginning to, maybe, make some tentative plans around some in-person events and travel before the end of the year.&lt;/p&gt;

&lt;p&gt;Then I got an email out of the blue from the folks at &lt;a href=&quot;https://www.saltpay.co/&quot;&gt;SaltPay&lt;/a&gt; – they were opening a new tech hub in Porto, and would I be able to give a talk? We set up a Zoom call, and after chatting for a few minutes I realised what they were proposing… “whoa, hang on a second, you mean in person? You’re inviting me to fly to Portugal on Monday?” Yes, that’s exactly what they were inviting me to do – and, it turns out that’s actually possible right now, thanks to SCIENCE! I’ve had both my COVID vaccinations, and Portugal is one of the European countries that recognises the UK vaccination certificate, which means I can travel without having to quarantine at either end, and TAP are flying direct from Gatwick to Porto a few times a week.&lt;/p&gt;

&lt;p&gt;Now, on one hand, travel is a lot more complicated than it used to be. Between COVID and Brexit, the days when you could book a flight or Eurostar and be in Europe in time for dinner probably aren’t coming back any time soon. But on the other hand, it’s still a lot easier and cheaper than going to, say, Russia or Tanzania. The European Union is a wonderful anomaly; for most of the folks on this planet, travel is an expensive mess of visas, paperwork, bureaucracy - and vaccinations. Big deal. You sign the forms, you get the jabs, you pay the fees, and off you go.&lt;/p&gt;

&lt;p&gt;The logistics are a bit complicated, partly because so much of this is time-sensitive, and partly because the rules and procedures keep changing. There are the lateral flow tests you do at home which aren’t valid for travel; there’s the old-style PCR tests that &lt;em&gt;are&lt;/em&gt; valid for travel but results take 24 hours, and there’s the new lateral flow tests which &lt;em&gt;are&lt;/em&gt; valid for travel if you get them done at a clinic or get a lab to certify the results… PCR tests have to be done within 72 hours before departure, LFD tests within 48, and there are passenger locator forms to fill out in both directions. Mainland Portugal is on the UK’s “amber list”, so to re-enter the UK you need to have booked (and paid for!) another COVID test that you’ll take within 2 days of arriving home - and, you guessed it, it can’t be a free NHS one.&lt;/p&gt;

&lt;p&gt;I had to show my test result and vaccination certificate when I checked in for my flight, and again at passport control on arrival in Porto. although at no point did anybody ask me to remove my mask to check my face matched my passport photo, which I thought was a little incongruous. There was also a slight entertaining moment when the airline stewardess doing the safety demonstration reminded people to take off your COVID mask before you put on your oxygen mask, but apart from that, it was really nothing remarkable. Gatwick Airport is much the same - everything’s flying out of North Terminal at the moment, and South is completely closed while they’re doing some sort of building work. Porto’s much the same as it was when I was last here in January 2020, albeit a lot warmer and full of British holidaymakers.&lt;/p&gt;

&lt;p&gt;One thing I hadn’t expected which really stood out for me was the sense of anticipation. Even with the best tech and the best organisation in the world, online events just sort of blur into the ennui of lockdown life. I’ve done dozens of online talks over the last 18 months, and the night before an online talk, I’m not thinking about the talk; I’m thinking about the tech; all the various cables and devices that need to work flawlessly for the presentation to work. But on Sunday evening, I was out in Porto having a &lt;em&gt;francesinha&lt;/em&gt; and a Super Bock and I realised that for the first time in over a year, all I was thinking about was what I was actually going to talk about. I wasn’t worrying about microphones and screen-sharing and whether I’d be able to read the questions in the chat; I was thinking about the stories I wanted to tell. You know what? It felt great.&lt;/p&gt;

&lt;p&gt;The event was excellent, and an interesting insight for me into what in-person events look like in the post-COVID world. Everybody wearing masks, everybody taking lateral flow tests on arrival, colour-coded wristbands showing how comfortable you were with social proximity.&lt;/p&gt;

&lt;p&gt;SaltPay’s new tech hub is fantastic – and did I mention this was my first in-person talk since March 2020? ACTUAL AUDIENCE! IN THE ROOM! Well, mostly; it was a hybrid event – some folks joined remotely over Zoom, some folks had flown in specially to be there in person; some speakers were there in person, some were there online, and of course being a brand new venue this was the first time the AV tech had really been put through its paces and there was the odd technical glitch, but on the whole, it all worked beautifully.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I should probably mention that SaltPay are hiring for all kinds of tech and tech-adjacent roles in Porto, Prague, London, Lisbon and all over Europe. They’re a great bunch of people, doing some very interesting stuff with payment systems – if you’re interested, check out  &lt;a href=&quot;https://www.saltpay.co/about.html#jobs&quot;&gt;saltpay.co/about.html#jobs&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And now I’m at &lt;a href=&quot;https://armazemporto.com/en/&quot;&gt;Armazem&lt;/a&gt;, a wonderful “barely converted warehouse” down by the river, opposite the old customs warehouse (which you might know as the venue we use for NDC Porto when that’s in town). It’s a weird mashup of a bar, a cafe, an art gallery and an antiques shop, and it’s absolutely delightful. They have wifi, they have cold beer, and believe me, after walking the long way round from Castelo do Queijo, down the coast to Foz and back into town along the Douro, the cold beer is very much necessary.  But it definitely feels like things are slowly returning to some semblance of normality.&lt;/p&gt;

&lt;p&gt;Of course, the caveat to all of this is that daily COVID tests are just fine unless one of them comes back positive. So far, so good, but if one of those little indicator strips suddenly shows a second red line, all bets are off and instead of catching my flight home I’m suddenly working out what’s involved in self-isolating in Portugal for 10 days. I keep repeating that line from the agile manifesto about “responding to change over following a plan”, because if there’s one thing we’ve all learned over the last 18 months, it’s that COVID-19 couldn’t give a toss about anybody’s plans. At the moment it definitely feels like things are opening up again; I’m hoping to make it over to Antwerp next month to spend a few days with the folks from &lt;a href=&quot;https://axxes.com/en/&quot;&gt;Axxes&lt;/a&gt;, &lt;a href=&quot;https://www.dddeastmidlands.com/&quot;&gt;DDD East Midlands&lt;/a&gt; is still on track for an in-person conference in Nottingham in October, and November sees the return of &lt;a href=&quot;https://gotocph.com/&quot;&gt;GOTO Copenhagen&lt;/a&gt;, &lt;a href=&quot;https://www.buildstuff.events/&quot;&gt;BuildStuff&lt;/a&gt; and &lt;a href=&quot;https://ndcoslo.com/&quot;&gt;NDC Oslo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I’m sure that there are going to be some bumps on the road back to something approaching normality - all the event organisers I know are taking COVID very seriously indeed, but I won’t be surprised if one or two events find themselves having to improvise in a hurry if travel restrictions change or somebody reports a positive test at a speaker hotel or something. But, y’know, the unpredictability is part of what makes international travel exciting, and I have to say, the rest of 2021 is looking pretty damn exciting right now.&lt;/p&gt;

</description>
          <pubDate>2021-08-17T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2021/08/17/on-the-road-again.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2021/08/17/on-the-road-again.html</guid>
        </item>
      
    
      
        <item>
          <title>UK Road Signs (Redesigned by Google)</title>
          <description>&lt;p&gt;Last year, Google redesigned the icons used for many of their web and mobile apps, as part of their “rebranding” of G Suite to Google Workspace. And they took a bunch of distinctive, easily recognisable icons and… well, they kinda made them all look the same. There is a much more in-depth discussion of it over on &lt;a href=&quot;https://techcrunch.com/2020/10/06/googles-new-logos-are-bad/&quot;&gt;TechCrunch&lt;/a&gt;, but it got me thinking: what if you applied the same idea to something really familiar? Something that millions of people rely on every day, something that hasn’t changed in decades, something where clear signage you can recognise at a glance might literally be a matter of life and death?&lt;/p&gt;

&lt;p&gt;What if &lt;a href=&quot;https://www.gov.uk/guidance/the-highway-code/traffic-signs&quot;&gt;UK road signs&lt;/a&gt; were redesigned by the Google Workspace team?&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/uk-road-signs-redesigned-by-google-1600px.png&quot; class=&quot;screenshot do-not-autolink&quot; alt=&quot;A mockup showing how five UK road signs might look if redesigned to reflect Google Workspace design conventions.&quot; /&gt;&lt;/p&gt;
</description>
          <pubDate>2021-07-25T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2021/07/25/uk-road-signs-redesigned-by-google.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2021/07/25/uk-road-signs-redesigned-by-google.html</guid>
        </item>
      
    
      
        <item>
          <title>How (Not) to Respond to a CFP</title>
          <description>&lt;p&gt;As the world tentatively relaxes COVID restrictions and we contemplate the prospect of actual in-person conferences again before the end of the year, it seems like a good time to revisit one of the perennial questions that people ask about speaking at conferences and technology events: &lt;strong&gt;how do I write a good CFP submission?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Many big technology conferences hold an open CFP&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;, and competition for speaking spots can be fierce; events I’ve worked with typically accept less than 10% of the talks that are submitted. Your submission will be reviewed by the programme committee, who decide which talks to accept, but it may also end up in the conference programme, often in the exact form you submitted it, where the conference attendees will use it to decide which talks to attend. The talks that get accepted normally represent a diverse range of speakers, technologies, presentation styles and audience levels, and consequently they’re all different – there is no secret formula for writing a good proposal.&lt;/p&gt;

&lt;p&gt;That said, there are some very common factors among the talks that &lt;strong&gt;don’t&lt;/strong&gt; get accepted, so rather than telling you how to write a proposal, I’m going to share some tips about what &lt;strong&gt;not&lt;/strong&gt; to do.&lt;/p&gt;

&lt;h3 id=&quot;1-too-short&quot;&gt;1. Too short&lt;/h3&gt;

&lt;p&gt;There’s a particular kind of Hollywood action movie that can be perfectly described in fewer than ten words.  “&lt;em&gt;Die Hard&lt;/em&gt; on a bus”,  “&lt;em&gt;The Matrix&lt;/em&gt;, but with vampires”,  “Gangsters killed his dog… now he wants revenge.”&lt;/p&gt;

&lt;p&gt;Well, dear readers, this isn’t Hollywood, and if you want to get your talks accepted, you’ll need to work a little harder than that. I have reviewed submissions before where the entire talk description was “this is a talk about blockchain”. Literally six words. No event is going to offer you a speaking spot –  let alone cover hotel and airfare – based on a six word pitch. (&lt;em&gt;Especially&lt;/em&gt; if the only one of those words that’s remotely related to technology is “blockchain”)&lt;/p&gt;

&lt;p&gt;A good proposal should probably be around 200-400 words; long enough that you can establish some context, tell us a bit about who you are and what you’re talking about, and give the audience some idea what they can expect to see if they come to your talk.&lt;/p&gt;

&lt;h3 id=&quot;2-too-long&quot;&gt;2. Too long&lt;/h3&gt;

&lt;p&gt;At the other extreme are the essay submissions. You don’t see too many of these in the wild, because most events use platforms like Sessionize that can impose a character limit on the talk description, but I’ve seen proposals that looked like somebody had pasted an entire blog post into the submission form. Save the detail for the talk itself (or, y’know, publish a blog post?) – because when it comes to responding to the CFP, it is all about striking a balance between brevity and detail.&lt;/p&gt;

&lt;h3 id=&quot;3-nothing-special&quot;&gt;3. Nothing special&lt;/h3&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;“In this talk I will show you how to get started with machine learning in Python”.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;OK, great subject, interesting topic, and very relevant to a lot of folks working in tech.&lt;/p&gt;

&lt;p&gt;Ask yourself: what would somebody get from attending this talk that they can’t find on the web, or by watching YouTube videos? Are you a key contributor to an open-source project in that area? Have you or your team used that technology to do something particularly unusual or innovative? Is there something interesting about how you personally got involved with machine learning in Python? If the answer to all these questions is “no”, your submission will probably be rejected in favour of something that offers more in the way of perspective and personality.&lt;/p&gt;

&lt;p&gt;There’s another saying in Hollywood that a successful director’s first movie is always a movie that nobody else could have made. Think about your talks the same way; try to pitch something that nobody else could do.&lt;/p&gt;

&lt;h3 id=&quot;4-sales-pitch&quot;&gt;4. Sales pitch&lt;/h3&gt;

&lt;p&gt;Also known as the “single vendor solution talk”, programme committees can smell these a mile away:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;“GDPR is hard! In this talk, Bob will explain how OmniCorp DataMulch 3.0 will solve all your organisation’s data management headaches”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s not to say you can’t talk about your own company’s products or platforms, but the rule of thumb here should be that the audience should go away having learned something that’s useful &lt;em&gt;even if they never buy your product.&lt;/em&gt; Explain the problem, explain the principles and ideas that are relevant – and by all means throw in a demo that shows how your product applies those principles and ideas.&lt;/p&gt;

&lt;p&gt;But if your primary motivation for going to a conference is to sell licences, get a booth on the expo floor like everybody else.&lt;/p&gt;

&lt;h3 id=&quot;5-wrong-audience&quot;&gt;5. Wrong audience&lt;/h3&gt;

&lt;p&gt;Every conference has an audience. Some are more diverse than others, and they change from year to year, but think about who’s likely to be attending and what they want to hear about. If an event attracts an audience who predominantly work with Java and JVM-based languages, your talk about building APIs with C# probably isn’t going to get accepted. Events with a frontend focus don’t tend to accept talks about relational databases, and vice versa.
That doesn’t mean you can’t adapt your material; a topic like “Blazor for JavaScript Developers” will probably get a lot of attention, and if it’s delivered well it could inspire some of those developers to go away and experiment with some new tech. Again, it’s about what perspective and personality you can bring to the table beyond the videos and “getting started with X” guides that are available online.&lt;/p&gt;

&lt;h3 id=&quot;6-poor-writing&quot;&gt;6. Poor writing&lt;/h3&gt;

&lt;p&gt;As a native English speaker, I’m acutely aware that many of the brightest people in technology speak English as a second, third, or even fourth language, and so critiquing submissions on the quality of the written English can provoke a certain amount of controversy. Most programme committees I’ve served on have been relatively forgiving of grammar and colloquialisms; English is a horrifically complicated language and the odd grammatical oversight is usually no barrier to comprehension. The red flags here are things like mis-spelling technical terms, or confusing the names of specific technologies. I’ve seen proposals talk about the Kestral [sic] web server that ships with ASP.NET, about Microsoft Sequel Server (yes, that’s how we say it out loud, but nobody ever writes it that way), and one memorable submission about Angular, the popular Java framework.&lt;/p&gt;

&lt;p&gt;In a perfect world, you’d follow up on every one of these - “hey, did you mean Angular, the popular Java&lt;strong&gt;&lt;em&gt;Script&lt;/em&gt;&lt;/strong&gt; framework?” – but when a programme committee is trying to pick 75 talks out of 980 submissions, those kinds of errors indicate a lack of care on behalf of the person writing the proposal, and are often enough to justify an immediate rejection.&lt;/p&gt;

&lt;p&gt;Finally, read through your submission and check it makes sense. I’ve seen proposals where I’m guessing somebody edited something for length, and removed a key sentence or phrase, leaving an abstract that really didn’t make a huge amount of sense. Careless editing can leave you with a proposal like: “machine learning is revolutionising the world of financial analysis. In this talk, we’ll look at how you can use FoobarJS to streamline your deployments.” OK, fine – but what does FoobarJS and deployments have to do with machine learning or financial analysis?&lt;/p&gt;

&lt;h3 id=&quot;7-lazy-copypasting&quot;&gt;7. Lazy copy/pasting&lt;/h3&gt;

&lt;p&gt;Good talks have a shelf life; it’s normal to see the same material being presented at a dozen different events before the speaker finally retires it, and that’s OK. Preparing a talk is a huge investment in terms of time and effort, and there’s no reason you shouldn’t seek to maximise your return on that investment by presenting it to as many different audiences as they can.&lt;/p&gt;

&lt;p&gt;When you’re submitting an existing talk to a new event, though, make sure you check the detail carefully. The most glaring error is a submission that still includes the name of another event: “Join Dylan Beattie at DDD East Midlands” doesn’t &lt;em&gt;really&lt;/em&gt; work if it’s a submission to NDC Porto. Check dates, check product versions and codenames, check whether anything in the submission needs to be updated – whether it’s just a cosmetic detail, or it’s a whole chunk of your talk you’ll need to go away and revise.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Incidentally, it’s well known and generally accepted that certain parts of our industry move significantly faster than some conference selection processes… if you’ve submitted a talk about WombleJS version 2, and by the time the event actually takes place six months later WombleJS 2 is ancient history and everybody’s on WombleJS version 5, then (a) be prepared to update your talk, references and demos, and (b) let the conference organisers know, ideally with an updated abstract, a few weeks ahead of time so they can make sure it’s reflected in the schedule.&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;8-too-generic&quot;&gt;8. Too generic&lt;/h3&gt;

&lt;p&gt;You want to present “Introduction to Python” in half-an-hour? Good luck. That’s just about long enough to explain that Python exists, it’s a programming language, it comes in version 2 and version 3, and they’re different because… oh my goodness look at that, we’re just about out of time.&lt;/p&gt;

&lt;p&gt;That’s not to say you can’t present broad intro-level talks – but your CFP submission needs to make it very clear what your angle is. Show the committee you’ve thought about who’s likely to be in the audience, what level of knowledge and experience you’re expecting them to have prior to your session, and exactly what it is that they can expect to learn by the end of your presentation.&lt;/p&gt;

&lt;h3 id=&quot;9-too-specific&quot;&gt;9. Too specific&lt;/h3&gt;

&lt;p&gt;At the other extreme from the “introduction to Python” are the talks that aren’t likely to attract any audience because they’re only relevant to a handful of developers working in a very specialist area. You may be the world’s foremost expert on aspect-oriented programming in Object COBOL, but face it, if you drew a Venn diagram of people who are interested in AOP and people who are interested in Object COBOL, you’d probably end up with two very small circles, and the only person on the overlap is you.&lt;/p&gt;

&lt;p&gt;If you’re submitting to an AOP conference, or an event that has an Object COBOL track, then hey, you might be in with a shot. But for events that target a broader audience, this kind of extremely specialist topic is likely to be rejected in favour of something that’s relevant to more of the audience.&lt;/p&gt;

&lt;h3 id=&quot;10-no-further-resources&quot;&gt;10. No further resources&lt;/h3&gt;

&lt;p&gt;If your submission ends up shortlisted and the committee is trying to work out which talks to pick, they’ll often look online for anything else which supports your inclusion on the programme. If you’ve spoken at events before, include links to these – especially if it went OK and there’s video of it happening. If you’ve published books, training courses or open source projects related to your submission, say so, and include links where you can.&lt;/p&gt;

&lt;p&gt;And if you haven’t, get started! Write some blog posts. Record a couple of short videos of you talking about something you’re passionate and knowledgeable about, stick them up on YouTube, start getting your name and your personal brand out there where people can see it.&lt;/p&gt;

&lt;p&gt;The key thing is – whatever you’ve got, make it easy for the programme committee to find it. They’re unlikely to go googling your name when it’s 9pm, there are 143 more talks to review and the only snacks left in the committee room are Norwegian salted liqorice.&lt;/p&gt;

&lt;h3 id=&quot;so-what-should-i-do&quot;&gt;So what SHOULD I do?&lt;/h3&gt;

&lt;p&gt;Come up with an idea. Something nobody else can talk about; you’re unlikely to know more about F# than &lt;a href=&quot;https://en.wikipedia.org/wiki/Don_Syme&quot;&gt;Don Syme&lt;/a&gt;, or more about Entity Framework than &lt;a href=&quot;https://thedatafarm.com/&quot;&gt;Julie Lerman&lt;/a&gt;, but I guarantee there will be something in your own experience, a chapter of your own story on which you are the undisputed expert.&lt;/p&gt;

&lt;p&gt;Figure out what you want to talk about. Maybe you’re already written the talk; run through it, pick out the high points. Maybe it’s something new, in which case sketch out your narrative arc: where do you start, where do you end, what do you talk about along the way? Condense that down to 500 words. Go for a walk. Come back to it, take out another 100 words. Spell check it. Double-check the product names, acronyms, and technical terms. Ask somebody else to take a look. Read it out loud, check it all makes sense, then hit “submit” and cross your fingers.&lt;/p&gt;

&lt;p&gt;Above all, &lt;strong&gt;don’t get discouraged&lt;/strong&gt;. The acceptance rate for most public CFPs is somewhere below 10% – so if you’re getting 1 acceptance for every 10 submissions, you’re beating the odds.&lt;/p&gt;

&lt;p&gt;There are literally hundreds of events going on all over the world, all the time. Two that you really don’t want to miss are &lt;a href=&quot;https://ndcoslo.com/cfp&quot;&gt;NDC Oslo&lt;/a&gt; &lt;em&gt;(closes Aug 1st)&lt;/em&gt; and &lt;a href=&quot;https://sessionize.com/ndc-london-2022&quot;&gt;NDC London&lt;/a&gt; &lt;em&gt;(closes September 26)&lt;/em&gt;, but there are hundreds of events going on all over the world, all the time; online, in-person, covering just about any technology you can think of. Check out sites like &lt;a href=&quot;https://www.papercall.io/events?cfps-scope=open&quot;&gt;papercall.io&lt;/a&gt; and the &lt;a href=&quot;https://sessionize.com/app/speaker/discover&quot;&gt;Discover Events feature on Sessionize&lt;/a&gt; to see what’s open.&lt;/p&gt;

&lt;p&gt;Get out there, good luck with it, and see you on a conference stage somewhere soon!&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;I run live, interactive workshops for technology professionals who want to get started with public speaking, or to improve their presentation skills. The next one is on 1 &amp;amp; 2 of September 2021; 2pm-6pm BST, online. using Zoom and Slack. Tickets are strictly limited and on sale now from just £165; check out &lt;a href=&quot;https://ursatile.com/workshops/from-keyboard-to-keynote&quot;&gt;https://ursatile.com/workshops/from-keyboard-to-keynote&lt;/a&gt; for more details and booking:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://urs.tl/kb2kn&quot;&gt;&lt;img class=&quot;screenshot do-not-autolink&quot; src=&quot;/images/posts/2021/from-keyboard-to-keynote-web-banner-september-2021.png&quot; alt-=&quot;A banner advertisement for Dylan Beattie&apos;s public speaking workshop&quot; /&gt;&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;CFP stands for “call for papers”, a term we’ve adopted from academic conferences where presentations are usually based on scientific papers published in academic journals. I’ve also seen it retconned as “call for proposals” or “call for presentations”, but really everybody just calls them CFPs without worrying too much about what it stands for. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
          <pubDate>2021-07-23T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2021/07/23/how-not-to-respond-to-a-cfp.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2021/07/23/how-not-to-respond-to-a-cfp.html</guid>
        </item>
      
    
      
        <item>
          <title>Java Criminally Underhyped? Not Back in 1997.</title>
          <description>&lt;p&gt;Earlier today, a fun little moment of &lt;a href=&quot;https://twitter.com/bitfield/status/1410589012969074688&quot;&gt;Twitter serendipity&lt;/a&gt; alerted me to an article by Jackson Roberts, a computer science student at the University of Colorado, entitled “&lt;a href=&quot;https://jackson.sh/posts/2021-04-java-underrated/&quot;&gt;Java is criminally underhyped&lt;/a&gt;”. It’s a really interesting article, and Jackson’s observations correlate with a lot of my own thinking about languages and platforms, although I am squarely in the .NET / CLR camp on that particular front.&lt;/p&gt;

&lt;p&gt;But Jackson ends his article:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I am curious why Java lost its hype in the first place. Programmer culture history is poorly documented and if you have insight, please &lt;a href=&quot;mailto:jacksonroberts25@gmail.com&quot;&gt;email me&lt;/a&gt; or leave a comment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And, well, the comment I was leaving got a bit long, so I figured I’d blog about it instead. You see, I studied computer science at the University of Southampton, from 1997 to 2000, and when I was an undergraduate, the hype about Java was unbelievable. Java was too important to ignore; it was released in January 1996, and by the time I started university in September 1997 it was a mandatory course for all first year students; it was used throughout my degree course, right up to the final year module on programming language design where our coursework assignment was to build a Scheme interpreter – in Java.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Java circa 1997 was probably the most hyped thing I have ever seen in my career.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For starters, it was the first really mainstream language to introduce a lot of features we now take for granted in most high-level languages. Memory management? Readers, even to a first-year comp sci undergraduate who had spent a grand total of six weeks working with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;malloc&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;free&lt;/code&gt;, Java’s automatic garbage collection was clearly a revelation. Concurrency? It has threads. They work. Job done. Exceptions? You mean I can just write functions that return what I want, instead of functions that leave the result in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;char*&lt;/code&gt; pointer somewhere and return an arbitrary non-zero integer if it worked? Java introduced a lot of incredibly powerful ideas and patterns, and it’s hard to underestimate how much impact it would have on the next two decades of programming language design.&lt;/p&gt;

&lt;p&gt;But Java wasn’t content with that. Java was going to solve ALL the problems. JavaScript? The language that would end up taking over the world wide web? JavaScript was a glue language. The idea was that all your &lt;strong&gt;serious&lt;/strong&gt; applications would be deployed as Java applets, with JavaScript used for only the most superficial wiring between applets. Java applets were such a big deal in 1997 that some people seriously believed that they represented the end of desktop applications, the end of Microsoft Windows; even the end of the computer as we knew it.&lt;/p&gt;

&lt;p&gt;Corel, who at the time were still a credible threat to Microsoft’s emerging monopoly, announced in 1996 that they were porting their WordPerfect office suite and their market-leading CorelDRAW! graphics software to Java. Check out &lt;a href=&quot;https://www.infoworld.com/article/2077194/office-productivity-comes-to-java.html&quot;&gt;this Infoworld article from 1996&lt;/a&gt; to see just how deep the hype hole really went:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“The success of Java-based network computers, and perhaps Java itself, will likely require the availability of vertical productivity applications for word processing, spreadsheets and databases. At the JavaOne conference, Corel Corp. (www.corel.com) made a first stab at offering such products with the announcement that it plans to deliver a suite of office applications based on its QuattroPro and WordPerfect products. Both products should be available for sale by early next year.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Java was going to revolutionise desktop development: Java Swing and the Java Native Interface (JNI) meant you could write your app once, and run it on Windows, Linux and Solaris.&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;  And thanks to the amazing power of JavaBeans, once you had implemented a particular service or algorithm, you could create a “bean” for it and it could be reused anywhere, by anybody, and building software would be as easy as putting together Lego bricks. JavaBeans were often cited as the last component model we would ever need.&lt;/p&gt;

&lt;p&gt;There was an industry spec for a “&lt;a href=&quot;https://en.wikipedia.org/wiki/Network_Computer&quot;&gt;Network Computer&lt;/a&gt;”, a device profile that mandated support for HTTP, Java, JPEG and a handful of other standards… hardware companies would build and sell Network Computers, developers would use Java’s “write once, run anywhere” model to build software for Network Computers, and the entire future would be Java applets running on set-top boxes (or “net-top boxes” as they were often dubbed.) Sun Microsystems actually shipped a thing called the JavaStation, a device based on their SPARCstation platform that ran JavaOS.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/Sun_Microsystems_JavaStation_right_side.jpg&quot; alt=&quot;The Sun Microsystems JavaStation&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The problem is: Java was good, but it wasn’t &lt;em&gt;that&lt;/em&gt; good. For Windows desktop apps, Visual Basic and Visual C++ could run rings around it. For games and anything where performance was a consideration, C++ all the way, especially as we entered an age of affordable 3D graphics cards and OpenGL became a big deal. And for server-side web programming, we had Perl, PHP, Microsoft Active Server Pages, and good old &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cgi-bin&lt;/code&gt;, all of which would plug in alongside your hand-coded HTML website and let you gradually add interactivity without having to rebuild your whole site. We also had a handful of coursework assignments that bought into the hype, too. I remember having to build an interactive quiz game using Java Studio, a truly horrific piece of software where you had to wire together Java beans using a buggy IDE that had a frustrating tendency to crash and lose your work with it.&lt;/p&gt;

&lt;p&gt;Java evolved. It continues to evolve. But that hysterical hype cycle of the late 1990s, when people seriously believed that the web was going to change the world &lt;em&gt;and everything connected to it would run Java&lt;/em&gt;? That didn’t survive the first dotcom crash.&lt;/p&gt;

&lt;p&gt;Java eventually succumbed to the inevitable dichotomy of software platforms: sooner or later, every software project has to decide whether it wants to be exciting, or wants to be important. By the mid-2000s, the things in Java that turned out to be important – garbage collection, checked exceptions, threads – weren’t exciting any more&lt;sup id=&quot;fnref:2&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;, and the things that used to be exciting – Network Computers, applets, office suites written in Java – turned out not to be important. Not even important enough to invest time and money in getting them right. I still dream of a world where Microsoft Office 97 was toppled from its throne by a cross-platform WordPerfect office suite running on Java, but that timeline’s well and truly deviated from ours and it isn’t coming back.&lt;/p&gt;

&lt;p&gt;More than that, though, the best way to destroy hype is credible success. Hype is about excitement; it’s about the tantalising possibility that if you jump on board at just the right time, you’ll become part of something unprecedented and maybe end up rich and famous along the way. In the late 90s and early 2000s, a lot of people did exactly that – and, yes, many of them used Java along the way, and a fair few of those got rich and famous by getting in right at the beginning, and getting out before anybody realised their idea was never going to work. But by the mid-2000s, Java was being used in business and industry all over the world; not to build applets or to run CorelDRAW on set-top boxes, but to create stable, reliable, boring software. Java became the language of choice for calculating insurance premiums and booking railway tickets, and friends, nothing is ever going to be exciting to university undergraduates if people in suits are already using it to write pension management software.&lt;/p&gt;

&lt;p&gt;The University of Southampton still teaches Java to first-year undergraduates, although I don’t see Java applets anywhere &lt;a href=&quot;https://www.southampton.ac.uk/courses/modules/comp1206#syllabus&quot;&gt;on the syllabus&lt;/a&gt; – but not because it’s new and exciting and they’re afraid of getting left behind. Java succeeded. It’s a solid, stable, reliable, cross-platform programming language with excellent tooling and a healthy ecosystem, but it’s also a known quantity. And in the last decade, Java has seen an astonishing resurgence thanks to first-class support on Google’s Android platform.&lt;/p&gt;

&lt;p&gt;And, of course, the exquisite irony is that, with over &lt;a href=&quot;https://www.theverge.com/2021/5/18/22440813/android-devices-active-number-smartphones-google-2021&quot;&gt;3 billion devices&lt;/a&gt; around the planet running Android, the Java dream of “write once, run anywhere” has damn near come true. It’s just that back in ‘97, we didn’t realise that would be because half the planet would be walking around all day with a JVM in their pocket.&lt;/p&gt;
&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Apple and macOS were having a bit of a crisis at this exact point in history. The blue iMac G3 in August 1998 marked the start of a slow journey back to the mainstream, that would continue with the release of macOS X in 2001 and conclude with the switch to Intel processors in 2006. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;well, except to Comp Sci undergraduates who’ve just spent two months learning &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;malloc&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;free&lt;/code&gt; &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
          <pubDate>2021-07-01T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2021/07/01/java-is-criminally-underhyped.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2021/07/01/java-is-criminally-underhyped.html</guid>
        </item>
      
    
      
        <item>
          <title>Introduction to Distributed Systems with .NET: A Live Online Workshop with fwdays</title>
          <description>&lt;p&gt;On July 6th and 7th, I’m running a &lt;a href=&quot;https://urs.tl/dsnet-fwdays-202106&quot;&gt;live online workshop with the folks from fwdays&lt;/a&gt;, all about designing and building distributed systems with .NET. It’s an introductory workshop aimed at developers who have some familiarity with C# and .NET; over two four-hour sessions, we’re going to look at:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Building HTTP APIs using REST and .NET WebAPI&lt;/li&gt;
  &lt;li&gt;GraphQL, SPAs, and the “backends for frontends” (BFF) pattern&lt;/li&gt;
  &lt;li&gt;Message queues, RabbitMQ, and the pub/sub pattern&lt;/li&gt;
  &lt;li&gt;Remote procedure calls with gRPC&lt;/li&gt;
  &lt;li&gt;Realtime browser communication using SignalR&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check out the video below for more info about what we’ll be learning and how it all works.&lt;/p&gt;

&lt;iframe class=&quot;youtube&quot; width=&quot;640&quot; height=&quot;360&quot; src=&quot;https://www.youtube.com/embed/syRFvVt6k8Y&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;It’s a fully online workshop, using a combination of Zoom, Slack, and Visual Studio LiveShare; we’ll discuss the principles and patterns involved in designing distributed systems, and then actually implement them so you can see how they work in practice. Attendees will implement their own services and endpoints, and by the end of the workshop we’ll be running a live system, sending real requests and messages across the internet between us in real time to see how it all fits together.&lt;/p&gt;

&lt;p&gt;Here’s what previous attendees had to say about this workshop:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;“Extremely professional… I have been a developer for just two years so all of this is pretty new to me even though I’ve touched on some of the topics a little bit in the past. It was a great overview and it made me more confident that I’ll be able to actually use these technologies when the opportunity will rise.”&lt;/em&gt;&lt;/p&gt;

  &lt;p&gt;&lt;em&gt;“I liked the fact that everything was put into action. Seeing the code helps us understand how should work.”&lt;/em&gt;&lt;/p&gt;

  &lt;p&gt;&lt;em&gt;“The live coding is really great, it’s more engaging than just being shown some pre-written code. But generally enjoyed all of it, you explain things really well”&lt;/em&gt;&lt;/p&gt;

  &lt;p&gt;&lt;em&gt;“The delivery is easy to follow, and the examples and code along bits are really helpful”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s happening on July 6th and 7th, 2021; 4pm-8pm Ukraine time / EEST (that’s 2pm-6pm if you’re in the UK)&lt;/p&gt;

&lt;p&gt;Tickets start at 3900 UAH (about €120), and are on sale now from &lt;strong&gt;fwdays&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Buy tickets: &lt;a href=&quot;https://urs.tl/dsnet-fwdays-202106&quot;&gt;https://fwdays.com/en/event/introduction-to-distributed-systems-second&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I’ll be lining up some public course dates for the second half of 2021 soon – the prospect of some in-person events taking place before the end of the year; check out &lt;a href=&quot;https://ursatile.com/workshops/&quot;&gt;https://ursatile.com/workshops/&lt;/a&gt; for details of all the courses I’m teaching, keep an eye on &lt;a href=&quot;https://twitter.com/ursatile&quot;&gt;Twitter&lt;/a&gt; and &lt;a href=&quot;https://linkedin.com/company/ursatile&quot;&gt;LinkedIn&lt;/a&gt; for announcements about new course dates (or &lt;a href=&quot;https://ursatile.com/contact&quot;&gt;join my mailing list&lt;/a&gt;), and hopefully see you online.&lt;/p&gt;
</description>
          <pubDate>2021-06-21T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2021/06/21/distributed-systems-with-dotnet-workshop.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2021/06/21/distributed-systems-with-dotnet-workshop.html</guid>
        </item>
      
    
      
        <item>
          <title>The ONYX BOOX Note Air: Android 10.0 on an E-Ink Tablet</title>
          <description>&lt;p&gt;I write a lot. Conference talks, blog posts, articles, song lyrics, training material – almost everything I do starts off as words. Maybe the odd heading or bullet list, but really it’s just getting a few thousand words out of my head and into an Evernote document, where I can review, edit, and eventually do something useful with them. For a long while, I’ve been looking for something that does for writing what Kindle does for reading. Something that’s digital, connected, lightweight, syncs to the cloud - but with an e-ink display I can use outdoors, in bright sunlight, and that doesn’t feel like using a computer.&lt;/p&gt;

&lt;p&gt;There are some wonderful handwriting devices out there – I tried out a &lt;a href=&quot;https://remarkable.com/&quot;&gt;reMarkable&lt;/a&gt; tablet a few years ago and was blown away by how good the UX was. But I’m one of those people who vastly prefers typing to handwriting. I own many pencils, and an embarrassing collection of notepads, sketch pads and jotters, but when it comes to getting words out of my head and onto a page, give me a QWERTY keyboard every time. &lt;em&gt;(I recall reading that Neal Stephenson wrote all 2,600 pages of the Baroque Cycle &lt;a href=&quot;https://www.josephpatrickpascale.com/post/physical-writing-process-pt-3&quot;&gt;longhand, using a fountain pen&lt;/a&gt;, and I get carpal tunnel syndrome just&lt;/em&gt; thinking &lt;em&gt;about that.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I’ve owned every generation of Kindle, and loved them all dearly, but they’re closed devices. They run an “experimental” web browser that’s just about good enough to sign in to airport Wi-Fi if you’re patient, but there’s no filesystem access, no app store, no way to connect an external keyboard or install your favourite writing app. Ditto the reMarkable – although there’s an active hacker community working on extending the capabilities of those devices, they’re still a closed platform and for me that’s a deal-breaker.&lt;/p&gt;

&lt;p&gt;Last week, after a particularly bright and sunny day which I spent indoors, with the curtains drawn, because I had work do to and every screen I own is so &lt;a href=&quot;https://en.wikipedia.org/wiki/Black_Mirror_(disambiguation)&quot;&gt;super-shiny and reflective&lt;/a&gt;, I asked on Twitter for any recommendations for an e-ink device that supported an external keyboard, and &lt;a href=&quot;https://twitter.com/shanselman&quot;&gt;Scott Hanselman&lt;/a&gt; pointed me to a &lt;a href=&quot;https://www.youtube.com/watch?v=lJWwlXS_cTM&quot;&gt;review he’d just done&lt;/a&gt; comparing the reMarkable 2, the ONYX BOOX Note Air and the ONYX BOOX Nova 3. The ONYX BOOX Note Air &lt;em&gt;(which I shall refer to hereafter as just the Note Air)&lt;/em&gt; looked like exactly what I was after, and, well, it’s been a rough couple of weeks and buying gadgets is one of my coping mechanisms, so I ordered one from Amazon. I’ve been playing with it for a couple of days now and it’s delightful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check out the full video review at &lt;a href=&quot;https://youtu.be/LaL8TQ-PzYs&quot;&gt;https://youtu.be/LaL8TQ-PzYs&lt;/a&gt; to see all the various features in action:&lt;/strong&gt;&lt;/p&gt;

&lt;iframe class=&quot;youtube&quot; width=&quot;640&quot; height=&quot;360&quot; src=&quot;https://www.youtube.com/embed/LaL8TQ-PzYs&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h3 id=&quot;tldr&quot;&gt;TL;DR:&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;It’s an e-ink tablet that runs full Android 10.0, and costs about £420 here in the UK.&lt;/li&gt;
  &lt;li&gt;It’s not Google “Play Protect” certified, so you need to jump through a few hoops to get the Google Play store on it.&lt;/li&gt;
  &lt;li&gt;Most apps &lt;em&gt;run&lt;/em&gt;; whether they’re usable or not all depends how well they handle the e-ink display.&lt;/li&gt;
  &lt;li&gt;Bluetooth keyboards work just fine.&lt;/li&gt;
  &lt;li&gt;Bluetooth audio works beautifully, and it’ll run Spotify.&lt;/li&gt;
  &lt;li&gt;The pen stylus works brilliantly on the built-in Notes app, but most third-party apps have too much display lag to use with handwriting.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Evernote&lt;/strong&gt;, &lt;strong&gt;Notion&lt;/strong&gt;, &lt;strong&gt;Microsoft Word&lt;/strong&gt; work (as long as you’re typing rather than handwriting)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Google Keep&lt;/strong&gt; and &lt;strong&gt;Microsoft OneNote&lt;/strong&gt; are unusable.&lt;/li&gt;
  &lt;li&gt;Battery lasts about 15 hours - that’s using various apps, with Bluetooth and Wi-Fi enabled the whole time. A full charge takes  about 2 hours.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2021/2021-06-11-onyx-boox-note-air.jpg&quot; alt=&quot;2021-06-11-onyx-boox-note-air&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;first-impressions&quot;&gt;First Impressions&lt;/h3&gt;

&lt;p&gt;When you first open the box, it’s clear ONYX has paid a lot of attention to first impressions. The packaging is seriously classy, the device itself looks amazing - 6mm thick, fantastic build quality, and the e-ink screen is stunning. It’s crisp, it’s smooth, it’s perfectly even without any rogue bright spots.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;This is the point where I should confess that this is the first Android device I have ever owned. I’ve run Windows, Linux, macOS, and multiple iPhones and iPads, but until today I’ve never actually owned a device that ran Android. So on one hand, I have absolutely no idea where anything is or how any of it works. But, on the other hand, the Note Air runs a heavily modified version of Android with a bunch of bespoke apps and stuff, so perhaps that’s not such a bad thing.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It also has a rather nice feature whereby the display has two separate backlights that you can adjust independently; one has a bluish light, one is more red, so you can adjust the colour balance of the display to get a more relaxed reddish light if you’re reading before bedtime, for example. It’s hard to capture on video (one of the tricky things about reviewing and comparing e-ink devices is that looking at picture of an e-ink display online is nothing like looking at the display itself in real life) but here’s a screen recording where I’ve animated the colour balance to show you how it works:&lt;/p&gt;

&lt;video width=&quot;720&quot; height=&quot;360&quot; controls=&quot;&quot;&gt;
  &lt;source src=&quot;/images/posts/onyx-boox-brightness-backlight.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;

&lt;h3 id=&quot;second-impressions&quot;&gt;Second Impressions&lt;/h3&gt;

&lt;p&gt;Once you get over the initial wow factor, there’s a definite moment of “hang on a second… this smells a little odd”. The Note Air includes an “App Store”, but it’s not the official Google Play store, and the first time you fire it up you get a rather unsettling message about how “all of the proposed apps in the App Store are collected from the Internet, for study reference only” - and when the first app on that list is &lt;em&gt;Amazon Kindle for Andorid9\10&lt;/em&gt; [sic] the alarm bells start ringing just a little – mainly since most of the stuff I actually want to do with this tablet requires signing in to assorted cloud providers, and, well, I’m not 100% OK with typing my Google or Amazon credentials into a device that offers the &lt;em&gt;Kindle for Andorid9\10&lt;/em&gt; app “for study reference only”.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/image-20210608173658080.png&quot; alt=&quot;image-20210608173658080&quot; style=&quot;zoom:33%;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I did a little digging and &lt;a href=&quot;https://blog.the-ebook-reader.com/2019/03/19/how-to-enable-google-play-app-on-onyx-boox-ereaders-video/&quot;&gt;found some instructions on how to enable the Google Play store&lt;/a&gt;. First, you need to enable the Google Play framework - there’s an option for this in the device settings. I hit the button, a dialog popped up saying “Checking info…”, and then nothing happened for fifteen minutes - but when I got bored of waiting and reset the device, when it booted back up, the Google Play store had appeared in the Apps menu. I opened it up and got another warning - “This device isn’t Play Protect certified”. Now, I’m guessing that’s some kind of Google certification program that ONYX hasn’t enrolled in; the workaround is to sign in to your Google account, and manually register the device ID as a trusted device.&lt;/p&gt;

&lt;p&gt;The Note Air actually includes an interface for doing this - but to use it, you need to enter your Google credentials, and the form provided to do that is part of the Air Note UI – which means no address bar, no information about where they’re going, and no way to view or verify any certificate information. I wasn’t entirely happy with that, so rather than registering from the device itself, I opened up &lt;a href=&quot;https://www.google.com/android/uncertified/&quot;&gt;https://www.google.com/android/uncertified/&lt;/a&gt; on my desktop, signed in to my Google account, typed in the GSF ID of the device, waited a few minutes for the cloud to catch up, and I was in business.&lt;/p&gt;

&lt;h3 id=&quot;using-the-note-air-as-an-e-reader&quot;&gt;Using the Note Air as an e-reader&lt;/h3&gt;

&lt;p&gt;Once I’d installed the Kindle app from the Play Store and signed in to my Amazon account, it worked flawlessly. There’s not a huge amount to say beyond that - it just works.&lt;/p&gt;

&lt;h3 id=&quot;handwriting-support-and-the-notes-app&quot;&gt;Handwriting support and the Notes app&lt;/h3&gt;

&lt;p&gt;The Note Air ships with a handwriting stylus. It’s an &lt;a href=&quot;https://essentialpicks.com/emr-stylus-how-wacom-pens-work/&quot;&gt;EMR stylus&lt;/a&gt; (aka a “Wacom stylus”); it’s powered by an electromagnetic field generated by the tablet itself, so it doesn’t require any batteries, and it works incredibly well. Used with the built-in Notes app, it provides a really immediate, tactile experience, that feels  closer to digital paper than anything I’ve ever tried.&lt;img src=&quot;/images/posts/2021/Screenshot_20210610-174008.png&quot; alt=&quot;Doing the Guardian cryptic crossword using the Notes app on the ONYX BOOX Note Air tablet&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The acid test of handwriting devices for me is whether I can use them to do a cryptic crossword, and this is the first device I’ve used that just works. It takes a bit of tweaking - to get to this point, I had to download the PDF of the puzzle from the Guardian website, open it up in Acrobat, grab a screenshot, and then insert that screenshot as an image into the Notes app – but once that’s done, it just feels like solving the puzzle on digital paper. The e-ink means it works outside in bright sunlight, the backlight means you can use it in the dark as well. The images and screenshots here really don’t do justice to how responsive it feels actually using the device.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;One thing that I noticed is that the Note Air uses some sort of two-pass rendering. When you’re using the built-in Notes app with the stylus, lines and pen strokes appear immediately, but if you’re screen-casting or capturing a screen recording, the same pen strokes don’t appear in the recording for a few seconds. My guess is that there’s some kind of immediate feedback system built into the device itself that’s running on top of the Android display subsystem, so that when you move the pen, your line shows up on the screen immediately - before the operating system even knows you’ve drawn it.&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;The downside of this is that it means third party apps like OneNote that aren’t specifically optimised for the device just don’t work with the stylus for any kind of handwriting or sketching&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;writing-with-evernote-and-a-bluetooth-keyboard&quot;&gt;Writing with Evernote and a Bluetooth keyboard&lt;/h3&gt;

&lt;p&gt;The main thing I want to do with the Note Air is writing; I’ve previously used my iPad with a Logitech K380 Bluetooth keyboard and Evernote to write all kinds of things, so I wanted to do a side-by-side comparison of the two. Here they are, literally side by side, outside in my garden in bright sunshine:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2021/IMG_0595.JPG&quot; alt=&quot;Side-by-side photo of the ONYX BOOX Note Air alongside an iPad&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The iPad is running at full brightness there - and it’s completely unusable; it’s just too reflective. The Note Air, on the other hand, is just like paper: bright sunlight makes it &lt;em&gt;easier&lt;/em&gt; to read.&lt;/p&gt;

&lt;p&gt;Like most apps, the Evernote UI is a little hit and miss – app menus with lots of nice photography and animation are never going to work well on this kind of display. But once you open the document you’re working on and make it full-screen, it works just fine.&lt;/p&gt;

&lt;h3 id=&quot;other-note-taking-apps&quot;&gt;Other Note-Taking Apps&lt;/h3&gt;

&lt;p&gt;I tried out a couple of other third party note taking apps to see how well they work with the e-ink display:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Notion:&lt;/strong&gt; Works nicely with an external keyboard hooked up; I could easily see myself using it as an alternative to Evernote if I had to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Microsoft Word:&lt;/strong&gt; works pretty well. I downloaded the Office 365 edition from the Play store, and found it very usable apart from some UI glitches, like the “new document” icon being a white page, with a pale grey hairline outline, on a white background… on the e-ink display it just looks like blank space. But once you know where it is, it’s fine :)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Google Keep notes:&lt;/strong&gt; Nope. It works, but the way it renders text onto the e-ink display is so poor as to be illegible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Microsoft OneNote:&lt;/strong&gt; OneNote was by far the worst of the apps I tried – it’s just completely incompatible with the e-ink display. The UI is illegible (lots of white-buttons-on-white-backgrounds), and as discussed above, the stylus doesn’t really work with third-party apps so you can’t use any of the handwriting features. (Well, you can, but you can’t see what you’re writing until you finish writing it, which means it’s effectively unusable.)&lt;/p&gt;

&lt;h3 id=&quot;termux-vim-ssh-nodejs-and-git-oh-my&quot;&gt;Termux, vim, ssh, nodeJS and git, oh my!&lt;/h3&gt;

&lt;p&gt;The Note Air supports &lt;a href=&quot;https://termux.com/&quot;&gt;Termux&lt;/a&gt;, a teminal emulator and Linux environment for Android – which means you can open a terminal, install nodeJS, Python, Ruby, git, vim; pretty much any text mode applications that will run on Linux. And it supports SSH, so you can spin up a Linux VM in the cloud, take your Note Air to the park or the beach, and ssh into the cloud from there – and yes, because it’s e-ink, bright sunlight isn’t a problem.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Did I mention it works in direct sunlight? It works in direct sunlight!)&lt;/em&gt; 🌞&lt;/p&gt;

&lt;h3 id=&quot;music-video-and-screen-casting-to-chromecast&quot;&gt;Music, video, and screen casting to ChromeCast&lt;/h3&gt;

&lt;p&gt;The Note Air has a built-in speaker and microphone. They’re nothing particularly special – the speaker is perhaps a little louder and clearer than the speaker in most smartphones, but we’re not talking high fidelity audio here. It supports Bluetooth audio, though, so you can pair an external speaker or Bluetooth headset; I’ve installed Spotify on mine; the UI looks a little odd but it’s usable and plays music just fine.&lt;/p&gt;

&lt;p&gt;As for video: you’re probably wondering why anybody would watch video on a device that can just about manage 8 shades of grey at 4 frames per second… but because the Note Air runs Android, it can cast your display to a Google ChromeCast, which will give you full HD HDMI, in colour, on an external monitor or TV. I wouldn’t use it for anything particularly interactive – there’s a tiny bit of latency on the video signal – but for streaming Netflix or Amazon Prime to the TV in your hotel room, it works just great.&lt;/p&gt;

&lt;h3 id=&quot;remote-desktop&quot;&gt;Remote Desktop&lt;/h3&gt;

&lt;p&gt;There’s an official Microsoft Remote Desktop client for Android, so I installed that just to see what it could do – and again, I was surprised at how usable it is. I’m writing this right now in Typora, via an RDP connection to my main Windows 10 machine, and it’s working remarkably well. Enabling Windows’ “high contrast” display mode makes a huge difference here, especially if you have dark mode enabled; those lovely muted navy-blue-on-midnight-blue colour schemes that look so good on a 32” TFT aren’t gonna work here, but nice bold black on white works just fine.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2021/IMG_8329.JPG&quot; alt=&quot;Visual Studio 2019 running via Remote Desktop on an e-ink display&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;miscellany&quot;&gt;Miscellany&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Yes, it runs Doom. Is it playable? Not remotely. It’s hard to dell the difference between a wall and a monster when you only have 8 shades of grey at 4fps. But it runs.&lt;/li&gt;
  &lt;li&gt;A few folks on Twitter asked about using it for sheet music: it’ll render PDFs just fine, and I tried it out with a &lt;a href=&quot;https://www.airturn.com/products/airturn-pedpro&quot;&gt;PEDPro AirTurn Bluetooth footpedal&lt;/a&gt; and it worked perfectly.&lt;/li&gt;
  &lt;li&gt;Charging is via a single USB-C port, which you can also use to connect peripherals. I hooked up a powered USB hub and was able to run a wired keyboard and external HD quite happily. Note that the USB-C port appears to support either charging or data, but not both - so if you’re taking an external HD on the road, you’re going to be running the drive off the Note Air’ internal battery, which probably isn’t a good idea.&lt;/li&gt;
  &lt;li&gt;WiFi and Bluetooth both seem pretty solid; there’s no SIM card support so for mobile internet you’ll need to tether to your phone or rely on public Wifi.&lt;/li&gt;
  &lt;li&gt;The screen scratches easily, so be really careful about protecting it with something when you’re carrying the device around.&lt;/li&gt;
  &lt;li&gt;The stylus attaches to the edge of the device with magnets, but it’s not particularly secure and if you lose it you’re looking at £50 for a new one. The stylus has a replaceable nib (£18 for 5 from Amazon) since they apparently wear down in normal use. When I’m holding a pen in my hand I tend to, y’know, scratch my nose with it, twiddle it between my fingers, that kind of thing, and I actually managed to accidentally remove the nib from mine a few times and drop it; the nibs are extremely tiny and the stylus doesn’t work without them, so might be worth carrying a few spares just in case.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;conclusions&quot;&gt;Conclusions&lt;/h3&gt;

&lt;p&gt;First off, it’s a huge amount of fun. I’ve had a blast setting this thing up, seeing what I can get it to do. For specific applications – handwriting notes, crosswords, Evernote, reading books via the Kindle app – it works very, very well, but the vast majority of apps in the Android ecosystem aren’t even tested on e-ink displays, let alone optimised for them, and it shows. For travelling, the Note Air probably replaces the Kindle + iPad combo that I used to carry around with me everywhere, but I’ll still be taking a laptop and a smartphone along for the ride.&lt;/p&gt;

&lt;p&gt;This is still very much an expensive gadget for people who like expensive gadgets and aren’t afraid to tinker with them. Some things work even though they probably shouldn’t, like Windows Remote Desktop; other things, like OneNote, should in theory work really nicely with this kind of device but in practice are completely unusable. There is also clearly a whole world of applications out there – music composition springs to mind — that could work beautifully on this kind of device if they were designed for it.&lt;/p&gt;

&lt;p&gt;It’s an interesting device, though. Being just about old enough to remember the days of connecting home computers up to a portable black &amp;amp; white TV, there’s a certain amount of nostalgic novelty to it, and if you think back to the 4-colour LCD displays in the earliest laptops, this is infinitely better than those ever were:&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Very cool!&lt;br /&gt;&lt;br /&gt;And, you know… That screen refresh is possibly faster than my first Mitac 386 laptop had on its greyscale lcd 😂&lt;/p&gt;&amp;mdash; Stacy Cashmore (@Stacy_Cash) &lt;a href=&quot;https://twitter.com/Stacy_Cash/status/1402664436398276608?ref_src=twsrc%5Etfw&quot;&gt;June 9, 2021&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;The key question I always ask about gadgets is: if I lost this one, would I buy another one? I’m not sure, but only because I think within a year we’ll probably see something twice as good for half the price. I’m looking forward to seeing what happens with e-ink over the next few years. ONYX are already shipping a colour e-ink device, the &lt;a href=&quot;https://www.boox.com/nova3color/&quot;&gt;BOOX Nova3 Color&lt;/a&gt;, and I’m sure it’s only a matter of time before the industry fixes the problems with ghosting and refresh rate and we’re looking at e-ink displays that can handle 8-bit colour at 24 fps.&lt;/p&gt;

&lt;p&gt;But, hey – summer is here, the sun is shining, the prospect of being able to sit outside in the sunshine and get a little writing done at the same time is too good to pass up, and this is the best device I’ve seen yet for doing that.&lt;/p&gt;

&lt;h3 id=&quot;links&quot;&gt;Links&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://youtu.be/LaL8TQ-PzYs&quot;&gt;My YouTube video review&lt;/a&gt; of the ONYX BOOX Note Air&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.boox.com/noteair/&quot;&gt;www.boox.com/noteair/&lt;/a&gt; - ONYX BOOX Note Air official product page:&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/r/Onyx_Boox&quot;&gt;/r/Onyx_Boox&lt;/a&gt; - Reddit community all about Boox devices.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://youtu.be/lJWwlXS_cTM&quot;&gt;Scott Hanselman’s video review&lt;/a&gt; of the ONYX BOOX Note Air, the BOOX Nova 3, and the reMarkable 2&lt;/li&gt;
&lt;/ul&gt;
</description>
          <pubDate>2021-06-11T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2021/06/11/onyx-boox-note-air.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2021/06/11/onyx-boox-note-air.html</guid>
        </item>
      
    
      
        <item>
          <title>Guru Meditation and Bad Error Messages</title>
          <description>&lt;p&gt;Today’s &lt;a href=&quot;https://www.theguardian.com/technology/2021/jun/08/massive-internet-outage-hits-websites-including-amazon-govuk-and-guardian-fastly&quot;&gt;Fastly outage&lt;/a&gt; is a nice moment to reflect on the fact that developers are absolutely terrible at writing error messages. Earlier today, some of the biggest sites on the web were offline for nearly an hour, including the BBC, the New York Times, Reddit, Stack Overflow, GitHub, and many UK government sites. During the outage, visitors to those sites got a cryptic error message:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Error 503 Service Unavailable.&lt;/strong&gt;&lt;/p&gt;

  &lt;p&gt;Service Unavailable&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;Guru Mediation:&lt;/strong&gt;&lt;/p&gt;

  &lt;p&gt;Details: cache-lcy19240-LCY &lt;em&gt;(and then a long, meaningless string of numbers)&lt;/em&gt;&lt;/p&gt;

  &lt;p&gt;Varnish cache server&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/image-20210608142115007.png&quot; alt=&quot;iPhone screenshot showing a Varnish cache error from the Guardian website&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now, a quick recap about how to write good error messages. It comes down to remembering four things.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Be helpful&lt;/strong&gt;. Explain what has gone wrong - and whether there’s anything the user might be able to do to work around or fix the problem. “The website you’re trying to reach isn’t available right now. Please try again in a few minutes.”&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Be human.&lt;/strong&gt; Don’t use meaningless jargon. “The network is unavailable” is always better than “Unhandled connection timeout 0x80004005 (WCOM SystemTimeoutException)”. The people who need error numbers and stack traces will know where to look for it.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Be honest.&lt;/strong&gt; If you screwed up, you screwed up. It happens. Never make your error messages so ambiguous that the user might think they’ve done something wrong. Life is scary enough as it is.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Be humble.&lt;/strong&gt; Apologise. Your software just broke and now somebody else is having a bad day and that’s your fault. “We’re really sorry. One of our systems has failed and so that website isn’t available right now. Please try again in a few minutes.”&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;(&lt;em&gt;The jury is still out on whether the fifth H should be “humorous”… humour is difficult and extremely subjective. If you know your audience, and you know that whatever went wrong is probably just inconvenient rather than disastrous, then sure, be funny. But be careful with it.&lt;/em&gt;)&lt;/p&gt;

&lt;p&gt;Now, let’s talk about “503 Service Unavailable Guru Mediation.” That’s a joke. No, really. To about a hundred people on the planet, that is incredibly funny, because “Guru Meditation” is what their old Amiga 500 used to say when something went badly wrong.&lt;/p&gt;

&lt;p&gt;Now today’s outage was definitely caused by Fastly, but the actual error message is coming from &lt;a href=&quot;https://varnish-cache.org/&quot;&gt;Varnish&lt;/a&gt;, an open-source software application that’s many organisations, including Fastly, to improve website performance. Varnish is really, really good, but… let’s just say that “user friendly” wasn’t on their list of requirements. Varnish is a high-performance HTTP caching proxy and URL rewriting engine. You configure it by writing rules in a domain-specific language, VCL, which is based on C (yes, that C). Those rules are compiled into a shared binary that runs as part of the Varnish network pipeline. It’s an app built by nerds, for nerds, to do one specific thing, and do it as fast as possible - and it does that one thing very well indeed. But at some point, one of the Varnish developers had to make a decision: “hey, what should we return if something goes wrong?” – and they decided that the best thing to do would be to return an HTTP 503 error, and include a joke about Amigas &lt;em&gt;(although keen eyes will notice that the original “Guru Med&lt;strong&gt;ita&lt;/strong&gt;tion” has actually changed to “Guru Med&lt;strong&gt;ia&lt;/strong&gt;tion” in the Varnish error message. Cute.)&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;UPDATE: It looks like the change from “meditation” to “mediation” isn’t part of Varnish, it’s part of Fastly. Somebody actually took the time to dig into the Varnish configuration they’re running, find the Amiga joke, and change it to a slightly modified version of the Amiga joke. Some folks on Twitter hypothesised that this is so Fastly can tell the difference between a problem with their own Varnish stack and a problem with one of their customers’ Varnish stacks, which kinda makes sense - but, hey, while they were in there, they could have, y’know, replaced it with something… helpful?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This morning, I reckon about 100 million people probably saw that error message. I reckon less than 1% of them understood it, and of the 1% who understood it, probably 1% of those even realised it was a joke, let alone found it funny. The other 99 million people who saw it? Most of them probably freaked out a bit, because it looked like half of the internet had suddenly stopped working.&lt;/p&gt;

&lt;p&gt;If the whole internet stops working, you assume it’s your problem. Check the wifi. Reboot the router. Call your ISP. If one website goes down? Big deal. It’ll be back. But when the BBC, the New York Times, the Guardian, Reddit, GitHub, Stack Overflow and the UK government all suddenly start returning “503 Service Unavailable Guru Mediation”, people get a bit rattled. So they check Google and fast.com and Netflix, and those sites are all ticking along just fine - and that’s when people start really freaking out. Virus? Malware? Terrorist attack?&lt;/p&gt;

&lt;p&gt;No. Somebody probably just messed up the Varnish config… and, probably, had no idea that an innocuous typo in a block of not-quite-C code was going to result in hundreds of millions of people going “argh… what the heck is a guru mediation?”&lt;/p&gt;

&lt;p&gt;If you want to capture stack traces and correlation IDs and other diagnostic details when something goes wrong? Awesome. Log them. Don’t have logs? Get logs. Don’t have time to get logs? Go and set up some logging. Go on. Do it now. We can wait. But when it comes to the error message that ends up on your screen when something goes wrong… somebody else is going to see that. I guarantee it. And you have no idea who that will be, or what they’ll be doing when they see it.&lt;/p&gt;

&lt;p&gt;So remember: Helpful. Human. Honest. Humble. Doesn’t matter if you think nobody except your colleagues will ever see it. Doesn’t matter if it’s an error that you’re absolutely certain is never going to happen. If you’re wrong, your carefully-crafted error message could make a huge difference to somebody else’s day.&lt;/p&gt;

&lt;p&gt;And if you’re right? No big deal. You wrote a good error message anyway.&lt;/p&gt;
</description>
          <pubDate>2021-06-08T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2021/06/08/guru-meditation-and-bad-error-messages.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2021/06/08/guru-meditation-and-bad-error-messages.html</guid>
        </item>
      
    
      
        <item>
          <title>Online Workshops in 2021</title>
          <description>&lt;p&gt;I don’t do New Year‘s Resolutions at New Year. January 1st is a terrible day to start anything important. I generally kick off new things at the start of February, and perhaps line up another project or resolution or two to kick in at the end of March when the clocks change. This time last year, I started my own company, Ursatile. I had a business plan and everything – but, as Mike Tyson memorably put it, “everybody has a plan until they get punched in the mouth”…&lt;/p&gt;

&lt;p&gt;Yeah. 2020 was definitely a metaphorical punch in the mouth. Compared to a lot of people, I had it pretty easy: I already had a proper office set up at home, I didn’t have to worry about losing my job, I don’t have any kids to home-school. But I had to figure out, fast, how to make my new company viable in the new world of online events and remote working. For a while, I was running three online events a week, just to give us all a chance to try things out, figure out how online events worked, and what we could expect from them, and on the back of that, NDC hired me for a few months to help translate their developer conferences into online events. At the same time, I was developing my own workshops and online courses, giving talks at dozens of online events, and spending a small fortune on cameras, microphones and lights - some of which became instantly indispensable; some of which went into the cupboard within 24 hours and never came back out.  By the end of last year, I’d racked up something like 50 days of online teaching, and lost count of how many online conferences and meetups I’ve spoken at. I’ve done online concerts, online quizzes, online panel shows – I even got a couple of bookings to do the entertainment at online Christmas parties, which turned out to be a lot of fun. And I rounded out the year with a &lt;a href=&quot;https://www.youtube.com/watch?v=Q1wOzdc8Co8&quot;&gt;live streamed online party show&lt;/a&gt; – two hours of music, comedy, greenscreen effects, special guests and festive entertainment, which was also a lot of fun.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=Q1wOzdc8Co8&quot;&gt;&lt;img src=&quot;/images/posts/2021/holidays-in-the-holodeck-social-media-card.png&quot; alt=&quot;A poster for Dylan Beattie&apos;s livestreamed Christmas party&quot; class=&quot;do-not-autolink&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But for me, the biggest revelation of 2020 was how well programming workshops work online. Hands-on, deep-dive technical training, with all the attendees writing and running code throughout the workshop. I’ve run my &lt;a href=&quot;https://ursatile.com/workshops/intro-to-distributed-systems-dotnet.html&quot;&gt;Introduction to Distributed Systems&lt;/a&gt; workshop online several times now, and it’s been absolutely excellent. Instead of being in a conference room, hunched over laptops and struggling with bad hotel wi-fi, everybody’s at home, with their dual monitors and their favourite mechanical keyboard; everybody’s running code on their own workstation, we’re connecting to cloud services and passing messages and requests between us. A day is long enough to build a working prototype; two days is long enough to turn the prototype into something you could actually use - monitoring, security, logging, integration tests. We build the whole thing, line by line, and as we go, I explain how it all works and why it’s built that way: what’s a subscriber ID, and why does it matter? What do those magic numbers in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;proto&lt;/code&gt; file mean? What happens if one of those services fails?&lt;/p&gt;

&lt;p&gt;It’s a great way to teach programming, and it’s a format that works really well for online workshops – and so, for 2021, I’ve put together several new workshops that are specifically designed to teach online. Lots of coding, lots of collaboration; things it would be impractical – or impossible – to do in a classroom.&lt;/p&gt;

&lt;p&gt;I’ll be posting details of all these new workshops over the next few days, so keep an eye out for that.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://urs.tl/htrow&quot;&gt;&lt;img src=&quot;/images/posts/2021/how-to-run-online-workshops.jpg&quot; alt=&quot;Dylan Beattie sitting at a computer, behind a banner promoting How To Run Online Workshops&quot; class=&quot;do-not-autolink&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also created a workshop last year about &lt;a href=&quot;https://urs.tl/htrow&quot;&gt;how to run online workshops&lt;/a&gt;, which proved really popular and had great feedback from everybody who attended it, so I’m going to be running that regularly throughout 2021 as well. It’s a half-day, hands-on workshop, four hours or so, covering everything from how to set up your mic and webcam to using online tools like &lt;a href=&quot;https://miro.com/app/dashboard/&quot;&gt;Miro&lt;/a&gt; and &lt;a href=&quot;https://jamboard.google.com/&quot;&gt;Jamboard&lt;/a&gt; to run remote training courses, collaboration and design sessions. Dates are on sale now for February and March; find out more at &lt;a href=&quot;https://urs.tl/htrow&quot;&gt;https://urs.tl/htrow.&lt;/a&gt;&lt;/p&gt;
</description>
          <pubDate>2021-02-01T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2021/02/01/online-workshops-in-2021.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2021/02/01/online-workshops-in-2021.html</guid>
        </item>
      
    
      
        <item>
          <title>Adding Events to a Google Calendar via a Link</title>
          <description>&lt;p&gt;It’s the 12th January 2021. Or the 43rd of December, 2020… or maybe the 317th of March, 2020? With so much of our lives being forced online, people all over the world working from home, it’s becoming harder than ever to keep track of what day it is and what’s happening.&lt;/p&gt;

&lt;p&gt;I’m absolutely dependent on Google Calendar to keep track of what I’m doing – events, appointments, social calls, birthdays, even which day I need to put the bins out. I know I’m not the only person who does this, and so for a long time I’ve wanted a way to publish a link that will allow other people to trivially add an event to their own Google Calendar. This isn’t the same as actually sending an &lt;em&gt;invitation&lt;/em&gt; – this is for things like meetups, live streamed performances; the kind of events where you’re not hugely concerned about whether a specific person can make it or not, but for folks who are interested, you want to help them remember that it’s happening.&lt;/p&gt;

&lt;p&gt;Well, I found out recently that, through an undocumented feature of the Google Calendar platform, you can actually create a link that will do exactly this. And since it’s undocumented, I had to do a little digging to figure out the exact format of the URL you need to use.&lt;/p&gt;

&lt;p&gt;First, the root URL: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://calendar.google.com/calendar/u/0/r/eventedit&lt;/code&gt;. If you &lt;a href=&quot;`https://calendar.google.com/calendar/u/0/r/eventedit`&quot;&gt;open that link without any query string parameters&lt;/a&gt;, it opens a blank “Add event” screen on Google Calendar.&lt;/p&gt;

&lt;p&gt;Now for the query string parameters. These are the ones I’ve figured out so far; I suspect things like notifications and guests can probably be prefilled via the query string as well.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;name&lt;/th&gt;
      &lt;th&gt;description&lt;/th&gt;
      &lt;th&gt;Example / Comments&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;text&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The one-line summary of the event.&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alice and Bob&apos;s Weekly Cocktail Night &lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dates&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The event start and finish times, in &lt;a href=&quot;https://tools.ietf.org/html/rfc5545#section-3.3.5&quot;&gt;RFC 5545 date format&lt;/a&gt;, separated by a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;20210108T170000Z/20210108T190000Z&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;details&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The longer event description. This field supports a subset of HTML, so you can include formatting, bullet points and links by URL-encoding your HTML code.&lt;/td&gt;
      &lt;td&gt;(see below)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;location&lt;/td&gt;
      &lt;td&gt;The intended venue for the activity. This field correlates to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Location&lt;/code&gt; property defined in &lt;a href=&quot;https://tools.ietf.org/html/rfc5545#section-3.8.1.7&quot;&gt;RFC5545 section 3.8.1.7&lt;/a&gt;.&lt;/td&gt;
      &lt;td&gt;Google Calendar integrates this field with Google Maps, so entering something like “Zoom” will give you a handy link to the ZOOM+Care on Martin Luther King Blvd on Portland. Probably best to enter the URL again if it’s for an online event.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;recur&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The recurrence pattern for recurring events, supplied as an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RRULE&lt;/code&gt; pattern conforming to &lt;a href=&quot;https://tools.ietf.org/html/rfc5545#section-3.8.5.3&quot;&gt;RFC5545 section 3.8.5.3&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RRULE:FREQ=WEEKLY;UNTIL=20210331T000000Z&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;details&lt;/code&gt; field can include a URL-encoded HTML fragment, like this one:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
%3Ch2%3EWe%27re+having+a+Zoom+party %3C/h2%3E%3Cul%3E%3Cli%3EVirtual%20backgrounds!%3C/li%3E %3Cli%3ESilly%20stories!%3C/li%3E%3Cli%3ESnacks!%3C/li%3E %3C/ul%3E%3Cp%3EYou%20can%20join%20it%20 %3Ca%20href=%22https://example.com/some/link%22%3Ehere%3C/a%3E%3C/p%3E

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Putting it all together, you get a link that looks like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
https://calendar.google.com/calendar/u/0/r/eventedit?text=Alice+and+Bob%27s+Weekly+Cocktail+Night&amp;amp;dates=20210301T190000Z/20210301T210000Z&amp;amp;details=%3Ch2%3EWe%27re+having+a+Zoom+party%20%3C/h2%3E%3Cul%3E%3Cli%3EVirtual%20backgrounds!%3C/li%3E%20%3Cli%3ESilly%20stories!%3C/li%3E%3Cli%3ESnacks!%3C/li%3E%20%3C/ul%3E%3Cp%3EYou%20can%20join%20it%20%20%3Ca%20href=%22https://example.com/some/link%22%3Ehere%3C/a%3E%3C/p%3E&amp;amp;location=https://example.com/some/link&amp;amp;recur=RRULE:FREQ=WEEKLY;UNTIL=20210601T000000Z

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;which looks unwieldly, but, hey, stick it behind some nice friendly text, and you’ve got your &lt;a href=&quot;https://calendar.google.com/calendar/u/0/r/eventedit?text=Alice+and+Bob%27s+Weekly+Cocktail+Night&amp;amp;dates=20210301T190000Z/20210301T210000Z&amp;amp;details=%3Ch2%3EWe%27re+having+a+Zoom+party%20%3C/h2%3E%3Cul%3E%3Cli%3EVirtual%20backgrounds!%3C/li%3E%20%3Cli%3ESilly%20stories!%3C/li%3E%3Cli%3ESnacks!%3C/li%3E%20%3C/ul%3E%3Cp%3EYou%20can%20join%20it%20%20%3Ca%20href=%22https://example.com/some/link%22%3Ehere%3C/a%3E%3C/p%3E&amp;amp;location=https://example.com/some/link&amp;amp;recur=RRULE:FREQ=WEEKLY;UNTIL=20210601T000000Z&quot;&gt;add to Google Calendar&lt;/a&gt; link ready to include in your event page.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2021/image-20210112131255896.png&quot; alt=&quot;image-20210112131255896&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Nice.&lt;/p&gt;

</description>
          <pubDate>2021-01-12T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2021/01/12/adding-events-to-google-calendar-via-a-link.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2021/01/12/adding-events-to-google-calendar-via-a-link.html</guid>
        </item>
      
    
      
        <item>
          <title>Petrucci: A Rockstar interpreter in TypeScript: Part 1 &amp; 2</title>
          <description>&lt;p&gt;Happy New Year, readers. 2020 is done, 2021 is here – and it’s time to do a new thing. I had a blast streaming Advent of Code on Twitch during December, and so for the next month or so I’m going to be building a &lt;a href=&quot;https://codewithrockstar.com/&quot;&gt;Rockstar&lt;/a&gt; interpreter, using TypeScript, live on Twitch, and then writing about it here. If you want to join and watch live, I’m on &lt;a href=&quot;https://www.twitch.tv/dylanbeattie&quot;&gt;twitch.tv/dylanbeattie&lt;/a&gt; most days around 16:00 UTC – and if you want to catch up afterwards, there’s a &lt;a href=&quot;https://www.youtube.com/playlist?list=PLw0jj21rhfkPElFkx-BFsdDlxtIKhjPxD&quot;&gt;YouTube playlist&lt;/a&gt; of the videos.&lt;/p&gt;

&lt;p&gt;Why TypeScript? Partly because I want to learn it. I’ve not worked with TypeScript before, at all, but I hear lots of very good things about it, and given it was designed by Anders Hejlsberg, who created C#, I suspect I’ll quite like working with it. I also want something that transpiles to JavaScript, so I can ship code into the wild and stick it up on a website where anybody can run it in their browser or on their phone. And partly because I really enjoy working with JavaScript; it’s a fun language that can do a lot of very cool things but which also affords a lot of experimentation and exploration, and I’m really interested in the TypeScript philosophy of allowing you to enhance JS with types and compile-time type checks if you want to, and allowing you to run plain old vanilla JS when that’s what you need.&lt;/p&gt;

&lt;p&gt;So… part 1 (&lt;a href=&quot;https://youtu.be/nrUboBuGaWo?list=PLw0jj21rhfkPElFkx-BFsdDlxtIKhjPxD&amp;amp;t=13&quot;&gt;YouTube&lt;/a&gt;) was literally getting up and running. Installing TypeScript, having a look at &lt;a href=&quot;https://deno.land/&quot;&gt;Deno&lt;/a&gt;, figuring out how to get a &lt;a href=&quot;https://pegjs.org/&quot;&gt;PEG.js parser&lt;/a&gt; working nicely with TypeScript classes. Part 2 (&lt;a href=&quot;https://youtu.be/_09LCZpd2E4?list=PLw0jj21rhfkPElFkx-BFsdDlxtIKhjPxD&amp;amp;t=205&quot;&gt;YouTube&lt;/a&gt;) took this idea a bit further, and got as far as a very simple grammar for a tiny language, a parser and an interpreter for that language.&lt;/p&gt;

&lt;p&gt;What makes things a little more interesting is that PEGjs, the parser generator that I’m planning to use on this project, only understands plain JavaScript - no TypeScript. So the high-level architecture looks something like this.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2021-rockstar/image-20210106202510794.png&quot; alt=&quot;image-20210106202510794&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;syntax.ts&lt;/code&gt; is the abstract syntax tree node classes - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AdditionNode&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MultiplicationNode&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NumberNode&lt;/code&gt;, and so on. This is referenced directly by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;interpreter.ts&lt;/code&gt; (which is fine, because they’re both TypeScript), but I also need to include a reference to these classes in the parser that’s generated by PEGjs, and because PEGjs can’t understand TypeScript, I need to use the TypeScript &amp;gt; JavaScript compiler, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tsc&lt;/code&gt;, to transpile &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;syntax.ts&lt;/code&gt; into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;syntax.js&lt;/code&gt;. PEGjs uses the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;require()&lt;/code&gt; syntax, so there’s a line in the header block of the grammar file to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;require(&apos;syntax.js&apos;)&lt;/code&gt;. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pegjs&lt;/code&gt; tool translates the PEG grammar definition, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;parser.peg&lt;/code&gt;, into a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;parser.js&lt;/code&gt; file containing the actual parser. Finally, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;interpreter.ts&lt;/code&gt; imports the parser and the syntax definitions.&lt;/p&gt;

&lt;p&gt;Because each different kind of language feature – addition, multiplication, output, etc – is now implemented as TypeScript class, it’s possible to implement an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;evaluate()&lt;/code&gt; method on each of these node types, which makes the design of the interpreter itself beautifully simple.&lt;/p&gt;

&lt;p&gt;The interpreter presented here supports the following language constructs:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Arithmetic: integer literals, addition, multiplication, and parentheses.&lt;/li&gt;
  &lt;li&gt;Output: the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;say&lt;/code&gt; keyword will print the subsequent expression to STDOUT.&lt;/li&gt;
  &lt;li&gt;Flow control: the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;return&lt;/code&gt; keyword will halt the program with the specified return code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s a sample program:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;output 1
output (4+5)*(6+7)
return 0
output 9
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The parse tree for this program should be something like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;output:
  number: 1
output
  product:
    lhs: sum:
      lhs: number: 4
      rhs: number: 5
    lhs: sum:
      lhs: number: 6
      rhs: number: 7
return:
  number: 0
output
  number: 9
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The output from this program should be:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1
117
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;and it should exit cleanly with the operating system return code (aka error level) &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt;. The final &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;output 9&lt;/code&gt; should NOT be executed.&lt;/p&gt;

&lt;p&gt;Here’s the full code so far - this is also &lt;a href=&quot;https://github.com/dylanbeattie/petrucci/releases/tag/v0.0.2&quot;&gt;on Github&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&quot;syntaxts&quot;&gt;syntax.ts&lt;/h4&gt;

&lt;p&gt;This file contains the TypeScript classes for building the nodes of our abstract syntax tree.&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// syntax.ts: abstract syntax tree nodes&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;LanguageNode&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;evaluate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Action&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Result&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;action&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;action&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;BinaryNode&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;LanguageNode&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;evaluate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;lhs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;LanguageNode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;rhs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;LanguageNode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lhs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;LanguageNode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;rhs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;LanguageNode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lhs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;lhs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;rhs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;rhs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;AdditionNode&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;BinaryNode&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;evaluate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; 
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lhs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;evaluate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;rhs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;evaluate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;  
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;MultiplicationNode&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;BinaryNode&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;evaluate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; 
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lhs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;evaluate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;rhs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;evaluate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; 
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;NumberNode&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;LanguageNode&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;evaluate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;OutputNode&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;LanguageNode&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;LanguageNode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;LanguageNode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;evaluate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;evaluate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ReturnNode&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;LanguageNode&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;LanguageNode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;LanguageNode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;evaluate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;   
        &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;evaluate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;parserpeg&quot;&gt;parser.peg&lt;/h4&gt;

&lt;p&gt;The grammar definition for the language supporting in this prototype:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{ const Syntax = require(&apos;./syntax.js&apos;); }

program = head:statement EOL tail:program { return [ head ].concat(tail) }
        / item:statement { return [ item ] }

statement = output / return

output = &apos;output&apos; _ node:expression { return new Syntax.OutputNode(node) }
return = &apos;return&apos; _ node:expression { return new Syntax.ReturnNode(node) }

expression = sum

sum = lhs:product _ &quot;+&quot; _ rhs:sum { return new Syntax.AdditionNode(lhs, rhs) }
    / product

product = lhs:primary _ &quot;*&quot; _ rhs:product { return new Syntax.MultiplicationNode(lhs, rhs) }
        / primary

primary = number
        / &quot;(&quot; expr:expression &quot;)&quot; { return expr; }

number = digits:$[0-9]+ { return new  Syntax.NumberNode(parseInt(digits)) }

_ = [ \t]*
EOL = &apos;\r&apos;? &apos;\n&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;interpreterts&quot;&gt;interpreter.ts&lt;/h4&gt;

&lt;p&gt;Finally, the interpreter, with the program itself inline as a JavaScript multiline string literal:&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Parser&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;./parser.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Syntax&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./syntax&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;program&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`output 1
output (4+5)*(6+7)
return 2
output 9`&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ast&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;program&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;ast&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;evaluate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; 
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Syntax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To run this via the command line, you’ll need to:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Install NodeJS. I’m running v12.16.2 but this should all work with any version &amp;gt; 12.&lt;/li&gt;
  &lt;li&gt;Install TypeScript: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm install -g typescript&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Install PEGjs: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm install pegjs&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To build and run the example, type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node run start&lt;/code&gt; - or to run each step individually:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ tsc syntax.ts
$ pegjs parser.peg
$ tsc interpreter.ts
$ node interpreter.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the next instalment, I’ll be setting up some tooling to make things a little easier to work with, and figuring how to start incorporating some of the existing test fixtures and coverage from Rockstar into the project.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2021-rockstar/40906113795_e570ceccc9_o.jpg&quot; alt=&quot;40906113795_e570ceccc9_o&quot; /&gt;&lt;/p&gt;

&lt;figcaption&gt;Joe Satriani and John Petrucci on stage at the Portsmouth Guildhall. Photo © &lt;a href=&quot;https://www.flickr.com/photos/117614565@N05/40906113795&quot;&gt;rmk2112 via Flickr&lt;/a&gt;&lt;/figcaption&gt;

&lt;p&gt;Oh, and the name I’ve chosen for this one is &lt;strong&gt;Petrucci&lt;/strong&gt;… the current JS interpreter for Rockstar is Satriani, after Joe Satriani (JS), and, well, John Petrucci is definitely younger than Satch, he’s more technically ambitious, and folks who are so inclined could get into a long argument about whether he’s better or not. But the world is definitely a better place for having both of them in it – and as it is with virtuoso metal guitar players, so it is with Rockstar interpreters. 😎&lt;/p&gt;

&lt;h4 id=&quot;links&quot;&gt;Links:&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.youtube.com/playlist?list=PLw0jj21rhfkPElFkx-BFsdDlxtIKhjPxD&quot;&gt;The YouTube playlist&lt;/a&gt; where I’m publishing video of this project&lt;/li&gt;
  &lt;li&gt;The &lt;a href=&quot;https://github.com/dylanbeattie/petrucci&quot;&gt;Petrucci code repo on GitHub&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;The &lt;a href=&quot;https://codewithrockstar.com/&quot;&gt;Rockstar language website&lt;/a&gt; and &lt;a href=&quot;https://codewithrockstar.com/docs&quot;&gt;language specification&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.twitch.tv/dylanbeattie&quot;&gt;twitch.tv/dylanbeattie&lt;/a&gt;, where I’m streaming live most days around 16:00 UTC&lt;/li&gt;
&lt;/ul&gt;

</description>
          <pubDate>2021-01-06T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2021/01/06/rockstar-typescript-part-1.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2021/01/06/rockstar-typescript-part-1.html</guid>
        </item>
      
    
      
        <item>
          <title>Dylan&apos;s Advent of Cool Nerd Things Day 25: Ninite</title>
          <description>&lt;p&gt;Happy Christmas, friends! Have we saved the best for last? Well, I’ll let you decide… we’ve had 24 days of fantastic tools, utilities, websites and apps.&lt;/p&gt;

&lt;p&gt;In the first week of &lt;a href=&quot;/nerdvent&quot;&gt;#nerdvent&lt;/a&gt;, we &lt;a href=&quot;https://dylanbeattie.net/advent2020/2020/12/01/aocnt-sizer.html&quot;&gt;resized windows with Sizer&lt;/a&gt;, visualised hard drive space with &lt;a href=&quot;https://dylanbeattie.net/advent2020/2020/12/02/aocnt-windirstat.html&quot;&gt;WinDirStat&lt;/a&gt;, turned our &lt;a href=&quot;https://dylanbeattie.net/advent2020/2020/12/03/aocnt-camo.html&quot;&gt;iPhones into webcams using Camo&lt;/a&gt;, powered up Windows with the &lt;a href=&quot;https://dylanbeattie.net/advent2020/2020/12/04/aocnt-xbox-game-bar.html&quot;&gt;X-Box Game Bar&lt;/a&gt;, kept on top of timezones with &lt;a href=&quot;https://dylanbeattie.net/advent2020/2020/12/05/aocnt-wclock.html&quot;&gt;WClock&lt;/a&gt;, managed all our &lt;a href=&quot;https://dylanbeattie.net/advent2020/2020/12/06/aocnt-franz.html&quot;&gt;social media apps with Franz&lt;/a&gt;, and tunnelled the internet to our &lt;a href=&quot;https://dylanbeattie.net/advent2020/2020/12/07/aocnt-ngrok.html&quot;&gt;local machines with ngrok&lt;/a&gt;. Week 2 brought us &lt;a href=&quot;https://dylanbeattie.net/advent2020/2020/12/08/aocnt-instant-eyedropper.html&quot;&gt;Instant Eyedropper&lt;/a&gt;, &lt;a href=&quot;https://dylanbeattie.net/advent2020/2020/12/09/aocnt-microsoft-powertoys.html&quot;&gt;PowerToys&lt;/a&gt; and the &lt;a href=&quot;https://dylanbeattie.net/advent2020/2020/12/10/aocnt-fira-code.html&quot;&gt;Fira Code programming font&lt;/a&gt;; we edited &lt;a href=&quot;https://dylanbeattie.net/advent2020/2020/12/11/aocnt-audacity.html&quot;&gt;audio files with Audacity&lt;/a&gt;, kept an eye on our systems with &lt;a href=&quot;https://dylanbeattie.net/advent2020/2020/12/12/aocnt-open-hardware-monitor.html&quot;&gt;Open Hardware Monitor&lt;/a&gt;, jumped cacti and dodged pterodactyls with the &lt;a href=&quot;https://dylanbeattie.net/advent2020/2020/12/13/aocnt-chrome-dino-game.html&quot;&gt;Chrome Dino Game&lt;/a&gt;, and wrote beautiful &lt;a href=&quot;https://dylanbeattie.net/advent2020/2020/12/14/aocnt-typora.html&quot;&gt;Markdown documents with Typora&lt;/a&gt;. Week 3 we &lt;a href=&quot;https://dylanbeattie.net/advent2020/2020/12/15/aocnt-paletton.html&quot;&gt;got colourful with Paletton&lt;/a&gt;, &lt;a href=&quot;https://dylanbeattie.net/advent2020/2020/12/16/aocnt-7-zip.html&quot;&gt;compressed files with 7-Zip&lt;/a&gt;, made &lt;a href=&quot;https://dylanbeattie.net/advent2020/2020/12/17/aocnt-carbon.html&quot;&gt;beautiful images of source code with Carbon&lt;/a&gt;, got &lt;a href=&quot;https://dylanbeattie.net/advent2020/2020/12/18/aocnt-gitkraken.html&quot;&gt;cracking with GitKraken&lt;/a&gt;, got creative with &lt;a href=&quot;https://dylanbeattie.net/advent2020/2020/12/19/aocnt-canva-and-unsplash.html&quot;&gt;Canva and Unsplash&lt;/a&gt;, &lt;a href=&quot;https://dylanbeattie.net/advent2020/2020/12/20/aocnt-obs.html&quot;&gt;broadcast ourselves with OBS&lt;/a&gt; and got our volume controls under control with &lt;a href=&quot;https://dylanbeattie.net/advent2020/2020/12/21/aocnt-eartrumpet.html&quot;&gt;EarTrumpet&lt;/a&gt;. Finally, we met the incomparable &lt;a href=&quot;https://dylanbeattie.net/advent2020/2020/12/22/aocnt-beyond-compare.html&quot;&gt;Beyond Compare&lt;/a&gt;, gave Windows a spring clean with &lt;a href=&quot;https://dylanbeattie.net/advent2020/2020/12/23/aocnt-autoruns.html&quot;&gt;Autoruns&lt;/a&gt;, and tested our emails with &lt;a href=&quot;https://dylanbeattie.net/advent2020/2020/12/24/aocnt-mailtrap.html&quot;&gt;Mailtrap&lt;/a&gt;. And yes, I did actually publish a blog post every day for 25 days. I’m as surprised as you are. Especially since I’ve also managed to do Advent of Code every single day as well, and stream it live on &lt;a href=&quot;https://twitch.tv/dylanbeattie&quot;&gt;Twitch&lt;/a&gt; and &lt;a href=&quot;https://www.youtube.com/playlist?list=PLw0jj21rhfkNzudewWxn4HVobz8hB__Tm&quot;&gt;YouTube&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Anyway. It’s Christmas! Let’s meet the last awesome tool I’m going to write about for #nerdvent: &lt;a href=&quot;https://ninite.com/&quot;&gt;Ninite&lt;/a&gt;. Ninite is amazing. Open the Ninite website, tick the apps you want, and it’ll give you a single, bundled installer that will install all of them on your Windows PC, with sensible defaults, no spyware or bundled crap. And, er, that’s it. There’s not a huge amount to say about it other than that it’s very, very good indeed.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/image-20201224015701170.png&quot; alt=&quot;image-20201224015701170&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When I’m setting up a new PC or reinstalling Windows, I use the default browser to get my Ninite installer, and Ninite does the rest. Web browsers, image editors, utilities, developer tools - in fact, quite a few of the tools I’ve written about over the last few months are available via Ninite, so why not check it out by using it to check them out?&lt;/p&gt;

</description>
          <pubDate>2020-12-25T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/advent2020/2020/12/25/aocnt-ninite.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/advent2020/2020/12/25/aocnt-ninite.html</guid>
        </item>
      
    
      
        <item>
          <title>Dylan&apos;s Advent of Cool Nerd Things Day 24: Mailtrap</title>
          <description>&lt;p&gt;When you develop websites, or just about any kind of internet application, it’s only a matter of time before you need to send email. I don’t mean sending an email manually - I mean writing a feature into your software that sends templated, personalised emails you’ve never seen, to people you’ve never met, who have put their email address into one of your web pages. Order acknowledgements, receipts, password reminders, registration confirmations… nerds like me build applications that use email for all kinds of useful things. On one hand, email is amazing – a secure, reliable way to send messages to anybody, anywhere on the planet, regardless of what device, platform or internet provider they’re using.&lt;/p&gt;

&lt;p&gt;On the other hand… email is complicated. The SMTP standard that governs most internet email was ratified in 1982, and by the time it began to creak in the mid 1990s, there were already so many people using it that replacing it with something better was, and remains, basically impossible. We’ve added all kinds of security features, encryption and encoding improvements, but they’re all fundamentally built on the same set of protocols we’ve been using since &lt;em&gt;Return of the Jedi&lt;/em&gt; first came out.&lt;/p&gt;

&lt;p&gt;Integrating email into your applications is a headache. People expect slick HTML emails now, with formatting, images, and links – but, believe it or not, there is no standard for adding rich formatting to emails. Most email systems rely on a labyrinth of nested tables and old-school inline styles straight out of 2006, and that’s before you even start thinking about alternate body encodings, internationalization, security, validation… yep, emails are hard to design, hard to build, and hard to test.&lt;/p&gt;

&lt;p&gt;When it comes to designing them, find a good designer who’s worked with email as a medium. Or find a design you like, and copy it. When it comes to building them, the best tool I’ve ever found for layouts is the fantastic &lt;a href=&quot;https://get.foundation/emails.html&quot;&gt;Foundation for Emails 2&lt;/a&gt; from Zurb; the MailChimp email designer is also pretty good, although it’s not quite so easy to integrate into your own apps and websites.&lt;/p&gt;

&lt;p&gt;But when it comes to testing emails, my go-to is a web app called Mailtrap; I’ve used it for years, and it works beautifully. Mailtrap gives you a set of SMTP server credentials, along with sample code for connecting and sending email in just about every web development platform out there:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/image-20201224011600585.png&quot; alt=&quot;image-20201224011600585&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Plug the appropriate config into your test or staging application, send your email, then open up Mailtrap’s web interface and see what you’ve got.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/image-20201224011359482.png&quot; alt=&quot;image-20201224011359482&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can preview your email as it will appear on desktop, tablet and mobile devices - it’s not 100% perfect, but it’s easily good enough to see any glaring omissions or problems with the layout. It also gives you a detailed technical breakdown of all sorts of useful metrics and stats about your emails&lt;/p&gt;

&lt;p&gt;The free plan gives you 500 test emails a month and a single test inbox - which is more than enough for hobby projects and trying it out. The various paid plans unlock higher limits and features like auto-forwarding, so you can bounce your test emails through Mailtrap and forward them to your real mailbox (or your boss, or your client), and the Team Plan is available for free to non-profits and open source projects.&lt;/p&gt;

&lt;p&gt;Check it out at &lt;a href=&quot;https://mailtrap.io/&quot;&gt;https://mailtrap.io/&lt;/a&gt; - it’s great. In fact, I have only one criticism of Mailtrap, and that’s their current branding. It’s OK, I guess, nothing special… but until a few years ago, their homepage had this absolutely fantastic illustration on it; a grinning octopus riding in a bin, chasing down stray emails with a fly-swatter and a rolled-up newspaper. I thought it was wonderful - memorable and distinctive and so much more engaging than just another bland corporate homepage… but hey, I guess they decided that “mad octopus riding in a bin” wasn’t really on brand for them. Thankfully, the Wayback Machine never forgets – and hey, Mailtrap is still an excellent platform, and highly recommended.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/image-20201224013002203.png&quot; alt=&quot;image-20201224013002203&quot; /&gt;&lt;/p&gt;

</description>
          <pubDate>2020-12-24T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/advent2020/2020/12/24/aocnt-mailtrap.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/advent2020/2020/12/24/aocnt-mailtrap.html</guid>
        </item>
      
    
      
        <item>
          <title>Dylan&apos;s Advent of Cool Nerd Things Day 23: Autoruns</title>
          <description>&lt;p&gt;I’ve been using Microsoft Windows since &lt;a href=&quot;https://en.wikipedia.org/wiki/Windows_2.1x&quot;&gt;version 2.1&lt;/a&gt;, and one of the things that has held true of almost every version of Windows I’ve ever run is that they get slower. A fresh installation of Windows is a joy - snappy, responsive, all those lovely acres of empty hard drive space… and then after a year or so, it’s just not so snappy any more. I’m sure it’s partly psychological, and I’m sure some of it is also related to security patches and essential updates (hey, it’s easy to go fast when you don’t care about data security) – but a large part of it is to do with clutter. Clutter is the stuff you don’t know you’ve got. The operating system equivalent of the drawer in your kitchen that was empty when you moved in, and now somehow it’s full even though you never really decided what to put in it, or that pile of dead biros and scratched CDs that lives in the glovebox of your car.&lt;/p&gt;

&lt;p&gt;In most modern operating systems, including Windows, desktop search has got so good that cleaning up is easy. Just shove everything in a folder called Clutter (yes, really), and get on with your life. It’s out of sight, out of mind, and if you need it again, just search for it. But there’s some stuff that isn’t quite so easy to clean up.&lt;/p&gt;

&lt;p&gt;Often, when you install an application or utility, it’ll configure something to run automatically when Windows starts. Sometimes, this is a good thing - I have apps like &lt;a href=&quot;https://discord.com/&quot;&gt;Discord&lt;/a&gt; and &lt;a href=&quot;https://www.dropbox.com/&quot;&gt;Dropbox&lt;/a&gt; that run automatically when I start Windows, because it makes sense for them to be running all the time. They sit in the background and do useful work. Sometimes, it’s not. You’d be astonished how many applications and devices install a program whose job is just to run in the background all the time, that serve no purpose at all other than occasionally pinging the company’s website to see if there’s a new version available. Then there are the drivers for devices you don’t use any more; the utilities that insist they want to run at startup even though you’re quite happy to start them yourself when you want to use them, and the various extensions that add features to things like the Windows Explorer right-click menu.&lt;/p&gt;

&lt;p&gt;Well, it’s time to get &lt;a href=&quot;https://www.vox.com/culture/2019/1/11/18175683/marie-kondo-tidying-up-netflix-life-changing-magic-konmari-explained&quot;&gt;Marie Kondo&lt;/a&gt; on your Windows install, and to do that, we’re going to use a tool called Autoruns. Autoruns is part of the &lt;a href=&quot;https://docs.microsoft.com/en-gb/sysinternals/&quot;&gt;SysInternals suite&lt;/a&gt;, an astonishingly powerful set of free tools for managing Windows computers. SysInternals was founded by &lt;a href=&quot;https://en.wikipedia.org/wiki/Mark_Russinovich&quot;&gt;Mark Russinovich&lt;/a&gt;, who also wrote most of the tools in the suite. Mark is now the CTO of Microsoft Azure, and the SysInternals suite has been maintained and distributed by Microsoft since they acquired the company in 2006.&lt;/p&gt;

&lt;p&gt;SysInternals is absolutely packed with goodies, but let’s take a look at Autoruns first. When you fire it up, you’ll get a surprisingly long list of things:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/image-20201223110409808.png&quot; alt=&quot;image-20201223110409808&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Windows has a lot of different mechanisms for running things automatically, and each “group” here reflects one of those mechanisms - registry keys, system folders, scheduled tasks, and so on. I fire up Autoruns every month or so to see what new delights have been added to my system by over-enthusiastic installers. There’s normally a few that I obviously don’t need, and one or two I need to take a closer look at to work out what they actually are. What’s really useful here is that using the checkbox alongside each entry, you can disable it without deleting it. Untick the boxes you think you don’t need, restart your computer, see how you go. If you suddenly realise one of them was actually something really important, no problem - fire up Autoruns again, check the box to switch it back on, and reboot.&lt;/p&gt;

&lt;p&gt;You can download the whole SysInternals suite, including Autoruns, from &lt;a href=&quot;https://docs.microsoft.com/en-gb/sysinternals/&quot;&gt;https://docs.microsoft.com/en-gb/sysinternals/&lt;/a&gt; - but if you don’t want (or aren’t allowed) to install anything, the entire suite is available as standalone portable EXEs at &lt;a href=&quot;https://live.sysinternals.com/&quot;&gt;https://live.sysinternals.com/&lt;/a&gt; - grab the tool you want, save it to the desktop, run it, then delete it when you’re done.&lt;/p&gt;

&lt;p&gt;Oh, and if you want to know what the CTO of Microsoft Azure does for fun… there’s a VM configuration in Azure with 420 CPUs and 24Tb of RAM, and a few weeks back Mark wrote a program for it that will max out individual CPU cores in turn so the CPU load meter display plays a game of Tetris:&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Achievement unlocked: playing Tetris on Task Manager on the Azure 24TB Mega Godzilla Beast VM: &lt;a href=&quot;https://t.co/4ntTsRqHEi&quot;&gt;pic.twitter.com/4ntTsRqHEi&lt;/a&gt;&lt;/p&gt;&amp;mdash; Mark Russinovich (@markrussinovich) &lt;a href=&quot;https://twitter.com/markrussinovich/status/1335651115958894593?ref_src=twsrc%5Etfw&quot;&gt;December 6, 2020&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;Absolute legend. I bet the CTOs of AWS and Google Cloud never do anything this cool. 😎&lt;/p&gt;
</description>
          <pubDate>2020-12-23T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/advent2020/2020/12/23/aocnt-autoruns.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/advent2020/2020/12/23/aocnt-autoruns.html</guid>
        </item>
      
    
      
        <item>
          <title>Holidays in the Holodeck</title>
          <description>&lt;p&gt;I’m throwing a party! It’s been a bit of a rough year and with the new lockdown restrictions that have just kicked in across London
and parts of the UK, Christmas is basically cancelled for a lot of folks… so on Sunday 27th December, I’m going to stream a live show on the internet, including loads of my &lt;a href=&quot;/music&quot;&gt;parody songs&lt;/a&gt;, some PubConf-style comedy lightning talks, a couple of musical surprises, and almost certainly a feline cameo or two from &lt;a href=&quot;https://www.youtube.com/watch?v=TPS-eFmdepw&quot;&gt;Lionel&lt;/a&gt;. 
&lt;a href=&quot;/holodeck&quot;&gt;&lt;img class=&quot;do-not-autolink&quot; src=&quot;/images/posts/2020-12-22-holidays-in-the-holodeck/holodeck.png&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It&apos;s at Sunday December 27th, at 19:00 UTC. I&apos;ll be streaming for around 2 hours, then we&apos;ll have drinks on Zoom.&lt;/p&gt;
&lt;p&gt;Want to add it to your Google calendar? &lt;a href=&quot;https://www.google.com/calendar/render?action=TEMPLATE&amp;amp;text=Dylan+Beattie%27s+Holidays+in+the+Holodeck&amp;amp;details=An+evening+of+comedy%2C+music%2C+nerd+jokes+and+silliness%2C+performed+live+by+Dylan+Beattie+and+streamed+live+on+the+internet.&amp;amp;location=https%3A%2F%2Ftwitch.tv%2Fdylanbeattie&amp;amp;dates=20201227T190000Z%2F20201227T210000Z&quot;&gt; Here you go:&lt;img border=&quot;0&quot; style=&quot;display: block; margin: 8px auto; padding: 0; width: auto; height: auto; border: 0;&quot; src=&quot;https://www.google.com/calendar/images/ext/gc_button1_en-GB.gif&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;There&apos;s also a &lt;a href=&quot;https://fb.me/e/3LxT0f759&quot;&gt;Facebook event&lt;/a&gt; for it if that&apos;s your thing, or if you want an ICS file, you can find one &lt;a href=&quot;/miscellany/dylan-beattie-holidays-in-the-holodeck.ics&quot;&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It would be lovely to see you there. Well, I won’t see you, obviously, ‘cos that’s not how streaming works. But you’ll see me, and if you put a comment in the chat, I’ll know you’re out there, and that would make me (and Lionel) very happy.&lt;/p&gt;
</description>
          <pubDate>2020-12-22T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2020/12/22/holidays-in-the-holodeck.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2020/12/22/holidays-in-the-holodeck.html</guid>
        </item>
      
    
      
        <item>
          <title>Dylan&apos;s Advent of Cool Nerd Things Day 22: Beyond Compare</title>
          <description>&lt;p&gt;Today’s &lt;a href=&quot;https://dylanbeattie.net/nerdvent&quot;&gt;#nerdvent&lt;/a&gt; entry is another one of my must-have tools, a tool that’s saved me so much time over the years that it’s probably paid for itself many, many times over. It’s a file comparison tool called Beyond Compare, it’s available on macOS, Linux and Windows, and it absolutely rocks.&lt;/p&gt;

&lt;p&gt;Let’s start with the easy stuff. You’ve got two text files, you want to know if they’re different – and if so, how. No problem. Open them up in Beyond Compare and it’ll show you exactly what’s changed between them:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/image-20201222104826351.png&quot; alt=&quot;image-20201222104826351&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Beyond Compare has comparison tools and viewers for many different file formats, so you can compare images visually to highlight any differences between them - and it’ll automatically scale images so your sources don’t even need to be the same size:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/image-20201222111707433.png&quot; alt=&quot;image-20201222111707433&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It’s got a three-way merge feature, too. For readers who haven’t encountered the joys of the three-way merge, it works kinda like this… imagine you and a friend are writing a book together - a thrilling airport blockbuster. You get a first draft that you’re both happy with… then a few days later, you email your friend saying “hey, I’ve had a brilliant idea - I’ve moved the story from London to Chicago”  - and they email you back going “Oh, crap… I also had a brilliant idea… the lead character isn’t a professor any more, she’s a helicopter pilot!”&lt;/p&gt;

&lt;p&gt;Now you have three different versions of your story - the original &lt;strong&gt;base&lt;/strong&gt; draft that you both started with, &lt;strong&gt;your&lt;/strong&gt; version, with all the landmarks and street names changed from London to Chicago, and &lt;strong&gt;their&lt;/strong&gt; version, where our protagonist is a helicopter pilot instead of a professor. The changes aren’t &lt;em&gt;fundamentally&lt;/em&gt; incompatible - we can quite happily end up with a story about a pilot that’s set in Chicago; we just need a bit of help reconciling the changes. That’s how a three-way merge works - it’ll compare your changes with those made by your co-author. Any passages where &lt;strong&gt;they&lt;/strong&gt; didn’t change the text will be updated to reflect &lt;strong&gt;your&lt;/strong&gt; changes; any passages where &lt;strong&gt;you&lt;/strong&gt; didn’t change the text will be updated to reflect &lt;strong&gt;their&lt;/strong&gt; changes, and any passages that you’ve &lt;strong&gt;both&lt;/strong&gt; edited will be shown side-by-side so you can cherry-pick the bits you want to keep and/or rewrite that passage so it makes sense.&lt;/p&gt;

&lt;p&gt;Turns out that kind of scenario actually happens a lot in collaborative software development (although we’re normally writing websites and mobile apps, rather than airport thrillers) so having a decent comparison tool that supports three-way merges is incredibly useful.&lt;/p&gt;

&lt;p&gt;It’ll compare folders as well as files, which is useful in all kinds of scenarios - particularly if you’ve ended up with two different versions of a project that contains hundreds of files and subfolders, and you just need to know which bits are missing from each one. You can do a “quick compare”, which just looks at the file paths, dates and sizes, or you can compare files based on their contents – and even configure the comparison rules to ignore things that don’t matter (like Windows vs Unix-style line endings).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/image-20201222114631853.png&quot; alt=&quot;image-20201222114631853&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Unlike a lot of the tools in my &lt;a href=&quot;https://dylanbeattie.net/nerdvent&quot;&gt;Nerdvent&lt;/a&gt; list, Beyond Compare isn’t free – but it does have a feature that I haven’t seen in any other software which I think is absolutely brilliant. When you install it, you get a 30 day free trial, but &lt;em&gt;the trial only counts days that you actually use the software&lt;/em&gt;. All too often, I’ll set up a 14-day free trial of something, use it for a day or two, then get sidetracked by another project; come back to it after a few weeks to continue evaluating it and… ah, no, it expired. Oh well. But the Beyond Compare 30-day free trial can last for &lt;strong&gt;months&lt;/strong&gt; if you’re not using it very often - which means you get to evaluate it based on 30 days when you actually used it. And by the time it eventually runs out, you’ll probably find it much easier to justify paying the $30 (Standard) or $60 (Pro) license fee.&lt;/p&gt;

&lt;p&gt;Beyond Compare is available at &lt;a href=&quot;https://scootersoftware.com/&quot;&gt;https://scootersoftware.com/&lt;/a&gt; for Windows, macOS, and several popular Linux distributions.&lt;/p&gt;

</description>
          <pubDate>2020-12-22T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/advent2020/2020/12/22/aocnt-beyond-compare.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/advent2020/2020/12/22/aocnt-beyond-compare.html</guid>
        </item>
      
    
      
        <item>
          <title>Dylan&apos;s Advent of Cool Nerd Things Day 21: EarTrumpet</title>
          <description>&lt;p&gt;In the beginning was the PC, and the PC had a speaker, and the speaker went “beep”. And the PC users listened to the Amiga 500s and Atari STs, with their glorious 8-bit FM synthesizer chips, and got a bit jealous. And so we saved up and bought an Adlib card so that we could bask in the luxurious musical possibilities of 9 channels of FM synthesized music. And then there was the SoundBlaster II, which had an FM synthesizer and a PCM chip for playing digital sampled audio &lt;em&gt;(speech! actual actors! &lt;a href=&quot;https://en.wikipedia.org/wiki/Wing_Commander_II:_Vengeance_of_the_Kilrathi&quot;&gt;in a computer game&lt;/a&gt;! amazing!)&lt;/em&gt;… and then there was wavetable synthesis, and SoundFonts, and the Gravis Ultrasound, and then sometime in the late 1990s PC manufacturers finally figured out that audio was a Thing People Wanted, and now every PC, laptop and motherboard on the market comes with some sort of audio capability, and most of the time you don’t ever think about it beyond turning the volume up and down and maybe pairing some Bluetooth headphones.&lt;/p&gt;

&lt;p&gt;A few years back, Windows worked out that a lot of folks were using their PC to make voice &amp;amp; video calls, so they added a feature that let you use one device for normal audio (music, games, etc.), and a second device for calls (a “communications device”). For most people, that works pretty well, but if you’re an audio nerd - or you’ve just got more than two audio devices hooked up at the same time - you’ve probably found yourself wishing you had a little more control. And if you’re doing any kind of remote training or live streaming, the ability to route different audio through different channels is an absolute lifesaver… we’ve all heard the horrible echo you get when someone’s on a Zoom call on their speakers and you hear your own voice a second or two after you’ve said something; imagine that, only it keeps echoing and getting louder every time and you have no idea what button you press to make it stop, and you’ve got some idea what can go wrong. Imagine you’re doing a remote workshop; you’ve got a PowerPoint deck with some audio in it. You need to hear the audio from your presentation; you need to hear the other people on the call, and you want to hear Windows desktop audio so you can still get chat notifications and stuff. Your audience need to hear your voice, and your presentation audio, but you don’t want them to hear your chat notifications, and you definitely don’t want them to hear themselves. That’s the “easy” setting. Now imagine a presentation with audio in it, but now you have a co-presenter who’s on a Zoom call with you and your audience are watching live on YouTube, so you need to make sure you’re routing the Zoom audio into OBS to stream it to YouTube, and your co-presenter can see your presentation and hear the audio. Oh, yeah, and the YouTube stream has 25 seconds of latency so if you make a mistake, you won’t find out about it for 25 seconds, then the YouTube chat will suddenly fill up with messages like “AUDIO PROBLEMS! BAD ECHO!” – and will CONTINUE to do this for another 25 seconds because that’s how long it takes the audience to see that you’ve noticed the problem and you’re fixing it…&lt;/p&gt;

&lt;p&gt;And… breathe. Let’s meet EarTrumpet. EarTrumpet is a volume control app – all it really does is let you pick which apps play through which audio devices, and how loud those apps should be. It’s technically possible to do this via the Windows device settings if you know where to find it, but EarTrumpet is simple, it works, and for doing anything complicated with multiple audio devices, it’s an absolute lifesaver:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/image-20201221115454144.png&quot; alt=&quot;image-20201221115454144&quot; /&gt;&lt;/p&gt;

&lt;p&gt;What’s particularly useful is being able to pin any app to a specific audio device – for example, if you always want Zoom to use your headset, regardless of what else you’re doing:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/image-20201221121008271.png&quot; alt=&quot;image-20201221121008271&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Finally, one thing to remember – not just with EarTrumpet, but with any device that can mute audio: it’s way too easy to turn something way down, forget about it, and then come back to it next week and be absolutely baffled as to why your audio isn’t working. A few weeks ago I used EarTrumpet to mute my web browser. Worked beautifully. A few days later… “why isn’t YouTube playing any sound? What? I AM CONFUSED…” until I remembered I’d turned it off. But to be fair, I’ve done the same thing before with guitar audio pedals (“what’s wrong with my amplifier? Maybe it’s the cable. Maybe my guitar is broken. Maybe all my guitars and cables are broken. Oh, no, hang on, that pedal is turned all the way down… d’oh.”)&lt;/p&gt;

&lt;p&gt;EarTrumpet is slightly unusual, in that it doesn’t actually have a website. Like many of the things I’ve shared during #nerdvent, the code is open source and it’s &lt;a href=&quot;https://github.com/File-New-Project/EarTrumpet&quot;&gt;available on GitHub&lt;/a&gt;, but to actually install it you can either &lt;a href=&quot;https://www.microsoft.com/en-gb/p/eartrumpet/9nblggh516xp?activetab=pivot:overviewtab&quot;&gt;download it from the Microsoft Store&lt;/a&gt;, or get it via &lt;a href=&quot;https://chocolatey.org/&quot;&gt;Chocolatey&lt;/a&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;choco install eartrumpet&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;Oh, and if you want to know where the synthwave wallpaper came from, it’s &lt;a href=&quot;https://www.deviantart.com/kvacm/art/Falling-Sun-791350935&quot;&gt;Falling Sun by Michal Kváč, via DeviantArt&lt;/a&gt;.&lt;/p&gt;

</description>
          <pubDate>2020-12-21T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/advent2020/2020/12/21/aocnt-eartrumpet.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/advent2020/2020/12/21/aocnt-eartrumpet.html</guid>
        </item>
      
    
      
        <item>
          <title>Dylan&apos;s Advent of Cool Nerd Things Day 20: OBS</title>
          <description>&lt;p&gt;In November 2019, I spoke at an event here in London (yes, a real one – you remember those?) where the venue “forgot” to mention that one of the rooms had a glass ceiling. Now, there isn’t a &lt;em&gt;lot&lt;/em&gt; of daylight in London in November, but there’s definitely enough that if you’re in a room with a glass ceiling, your PowerPoint slides aren’t going to project terribly well. I watched a few talks early in the day, realised this might be a problem, and thought “hmm… maybe I can stream my slides to YouTube or something so that folks who can’t see the projector screen well can watch it on their phone?” So I googled “stream powerpoint to youtube” and found out about something called OBS - Open Broadcaster Software. I installed it, poked around a bit, managed to stream my PowerPoint presentation to YouTube, mentally filed it away under “OK, that might be useful one day”, and got on with my life.&lt;/p&gt;

&lt;p&gt;Fast-forward to March 2020, and &lt;em&gt;those&lt;/em&gt; emails start coming in… a trickle at first, then a wave: &lt;strong&gt;“We would like to announce that $conference is now a Virtual Online Event!”&lt;/strong&gt; And I think “ok, this could get interesting. Perhaps that OBS thing can help.”&lt;/p&gt;

&lt;p&gt;And it did. OBS is brilliant, and it’s been an absolute game-changer for me running online events and doing livestreamed shows during 2020. It’s an incredibly powerful, and flexible, set of software tools – and it can be a little confusing until you figure out what’s what.&lt;/p&gt;

&lt;p&gt;First up: OBS is an open source project. This means you’re free to download it, use it, share it, modify it - and you’re even allowed to modify it and publish your modifications under a different name. That’s exactly what the folks at Streamlabs have done, which means there are actually two very similar apps out there, “plain” OBS, and Streamlabs OBS. The Streamlabs version adds a bunch of bells &amp;amp; whistles aimed at folks who do a lot of live streaming, but this article is about the original OBS, the one that’s available on &lt;a href=&quot;https://obsproject.com/&quot;&gt;https://obsproject.com/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;At a very high level, here’s how it works:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/image-20201220113738261.png&quot; alt=&quot;image-20201220113738261&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It takes input from video and audio sources. Cameras, microphones, screen sharing, local video files - even online sources like live webcam feeds. You use the OBS Studio software to arrange all those sources into what’s called a &lt;strong&gt;scene&lt;/strong&gt;, and then record the results to a local file, and/or stream it live to services like YouTube and Twitch. You can create multiple scenes, set up hotkeys to switch between them, and get a pretty professional-looking stream going relatively quickly.&lt;/p&gt;

&lt;p&gt;You can use OBS quite happily with just your laptop’s built-in webcam and a regular headset mic - capture your screen, put a “talking head” window in one corner using your webcam feed, enable your audio, and you’re good to go. It’s great for recording demos, training videos, or just doing some cheap &amp;amp; cheerful live streaming.&lt;/p&gt;

&lt;p&gt;Things get &lt;em&gt;really&lt;/em&gt; exciting when you add a bluescreen or greenscreen into the mix; OBS has built-in filters to do things like removing your greenscreen background so you can get a “floating head” effect (anyone old enough to remember the days when humans relied on television for weather forecasts will recognise this as the “TV weather person” effect). Here’s a shot from one of my programming live streams:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/vlcsnap-2020-12-20-10h57m08s270.png&quot; alt=&quot;vlcsnap-2020-12-20-10h57m08s270&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And if, like me, you get totally obsessed with things and end up buying four greenscreens so you can have greenscreen backgrounds on three walls and the floor of your office, you can do some fantastically clever things with OBS and multiple camera angles. Here are a few shots from the conference party show I did for BuildStuff back in November – me playing guitar and singing, live, on a virtual stage, with various backing videos and effects composited into the shot live via OBS:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/vlcsnap-2020-12-20-10h59m34s139.png&quot; alt=&quot;vlcsnap-2020-12-20-10h59m34s139&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/vlcsnap-2020-12-20-10h57m47s735.png&quot; alt=&quot;vlcsnap-2020-12-20-10h57m47s735&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/vlcsnap-2020-12-20-10h59m56s265.png&quot; alt=&quot;vlcsnap-2020-12-20-10h59m56s265&quot; /&gt;&lt;/p&gt;

&lt;p&gt;OBS is absolutely packed with features, but one that’s particularly useful is the Virtual Camera - this used to be a separate plugin, but now it’s built in to to OBS Studio. This lets you take your composited video with all your effects and backgrounds and whatever else, and share it as a virtual camera that you can select in Zoom, Google Meet or whatever else you’re using. Use it carefully - remember that Zoom, etc. think that it’s a camera and will apply some fairly hardcore video compression that works pretty well on real cameras pointing at real faces, and if your OBS scene includes source code, diagrams or anything with a lot of detail, it will probably look dreadful, so make sure you test it out before doing anything important.&lt;/p&gt;

&lt;p&gt;If you want to see somebody who’s really taken their OBS fu to the next level, take a look at &lt;a href=&quot;https://www.twitch.tv/videos/841678791&quot;&gt;LaylaCodesIt on Twitch&lt;/a&gt;. Layla’s partnered with Jim Mc̮̑̑̑͒G and his &lt;a href=&quot;https://blogs.siliconorchid.com/&quot;&gt;Silicon Orchid&lt;/a&gt; project to really push the limits of what’s possible on a live video stream - immersive 3D backgrounds, realtime raytracing, all kinds of cool effects; check out &lt;a href=&quot;https://blogs.siliconorchid.com/post/projects/unity-virtual-studio/&quot;&gt;Jim’s blog post&lt;/a&gt; on how it’s all put together.&lt;/p&gt;

&lt;p&gt;But like I said, you don’t need all that to get started - just a laptop with a webcam and a microphone. OBS is available at &lt;a href=&quot;https://obsproject.com/&quot;&gt;https://obsproject.com/&lt;/a&gt; - and it’s free, so download it, play around with it, and see what you can do. And if this post inspires you to start streaming, give me a shout on &lt;a href=&quot;https://twitch.tv/dylanbeattie&quot;&gt;Twitch&lt;/a&gt; or on &lt;a href=&quot;https://twitter.com/dylanbeattie&quot;&gt;Twitter&lt;/a&gt; and I’ll drop by and say hi :)&lt;/p&gt;
</description>
          <pubDate>2020-12-20T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/advent2020/2020/12/20/aocnt-obs.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/advent2020/2020/12/20/aocnt-obs.html</guid>
        </item>
      
    
      
        <item>
          <title>Dylan&apos;s Advent of Cool Nerd Things Day 19: Canva and Unsplash</title>
          <description>&lt;p&gt;Visual design is one of those things that many people appreciate but don’t understand - and by “appreciate”, I mean that design can change the way we perceive things, even if they don’t necessarily realise why that’s happening. In a world of 4G, wifi and smartphones, we’re surrounded by information, all day, every day (although the fact you’re reading this blog post suggests you haven’t quite had your fix for today, and hey, I’m glad you’re here!). A bit of visual design can be what you need to make your message stand out from the background, whether you’re using social media to promote something you’re working on - a project, an event, a charity campaign - or you just want to wind up one of your mates by taking something stupid they said and turning it into an inspirational quote poster.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.canva.com/&quot;&gt;Canva&lt;/a&gt; is a web app for designing things. I found out about it from my friend &lt;a href=&quot;https://twitter.com/emmadingle?lang=en&quot;&gt;Emma Dingle&lt;/a&gt;, who runs &lt;a href=&quot;https://www.socialresultsltd.com/&quot;&gt;Social Results Ltd&lt;/a&gt; and manages social media for all sorts of big names - and thanks Emma for the heads-up, because Canva is &lt;em&gt;brilliant&lt;/em&gt;.  If you’ve ever played around with the clipart and templates in something like Microsoft Word or Publisher… well, Canva feels like it’s what those things were &lt;em&gt;trying&lt;/em&gt; to be. Start by picking a template for the thing you’re trying to make, whether that’s an invitation, a poster, a banner to share on social media. Tweak the wording, maybe pick a different background, and you’re done.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/canva-example-01.png&quot; alt=&quot;canva-example-01&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Like a lot of creative tools, you can get sucked into a spiral of trying out different options - fonts, colours, backgrounds, layouts. The secret is to not worry about it too much. Find a template that’s close to what you’re looking for, tweak the wording, save the results, and get on with your life.&lt;/p&gt;

&lt;p&gt;I want to mention another tool today that works beautifully with Canva, and that’s &lt;a href=&quot;https://unsplash.com/&quot;&gt;Unsplash&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/image-20201219124640650.png&quot; alt=&quot;image-20201219124640650&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Unsplash is a huge archive of &lt;a href=&quot;https://unsplash.com/license&quot;&gt;free photography&lt;/a&gt; - beautiful images you can download, redistribute, and use freely in your own work. You can’t sell them, and you can’t use them to create a competing photo library, but apart from that you can do pretty much anything you like. Using a background I found on Unsplash and a Canva template, it took me about five minutes to put together this image, based on one of my favourite quotes ever:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/canva-example-02.png&quot; alt=&quot;canva-example-02&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It’s got some really nice touches, like automatically generating a colour palette from the tones in your background image, and if you do want to dive down the rabbit hole you can tweak just about every detail - layout, font, size, colours, effects. But primarily, it’s perfect for that sweet spot where plain text is a little too plain but firing up Photoshop or Illustrator feels like overkill.&lt;/p&gt;

&lt;p&gt;Check out Canva at &lt;a href=&quot;https://canva.com/&quot;&gt;https://canva.com/&lt;/a&gt; and Unsplash at &lt;a href=&quot;https://unsplash.com/&quot;&gt;https://unsplash.com/&lt;/a&gt;, and let’s fill the internet with beautiful things.&lt;/p&gt;
</description>
          <pubDate>2020-12-19T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/advent2020/2020/12/19/aocnt-canva-and-unsplash.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/advent2020/2020/12/19/aocnt-canva-and-unsplash.html</guid>
        </item>
      
    
      
        <item>
          <title>Dylan&apos;s Advent of Cool Nerd Things Day 18: GitKraken</title>
          <description>&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Git&quot;&gt;Git&lt;/a&gt; is a phenomenally powerful system for managing source code files - so powerful, in fact, that I’ve joked before that Git was actually invented in the future, and sent backwards in time as a way of teaching human minds how to deal with non-linear history and parallel timelines. Like a lot of developers, Git never really clicked for me until I started using GitHub to host my projects - but these days, working with branches, forks, pull requests and merge heads is so fundamental to how I build software it’s hard to imagine working without it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(As an aside, I also talk to more and more up-and-coming developers now whose first experience of programming wasn’t sitting in a room by themselves writing little programs, but contributing a few lines of code or documentation to a significant open source project - and I think that’s a very good thing indeed, and I think GitHub deserves a lot of the credit for making collaborative coding so accessible. Nice work, GitHub.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The Git data model is still incredibly complex, though, and… look, I know there are folks out there who are absolute virtuosos on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git&lt;/code&gt; command line; folks who can branch, stash, merge, pop, rebase, commit and push without even skipping a beat. I am not those people. I find it much, much easier to reason about complexity if I can see it, and working with git is one of those areas where I have a handful of familiar commands that I’ll run in a terminal, and for anything even slightly complex I’ll reach for a GUI.&lt;/p&gt;

&lt;p&gt;My favourite git GUI tool for the last few years has been Axosoft’s &lt;a href=&quot;https://www.gitkraken.com/&quot;&gt;GitKraken&lt;/a&gt;, and it just keeps getting better and better.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/image-20201218020137847.png&quot; alt=&quot;image-20201218020137847&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Fundamentally, it presents the history of all the branches, merges and tags in your project in a way that I find very easy to understand. It’s got built-in merge tools, and in recent versions has added first-class support for working directly with GitHub features like issues and pull requests. Git and GitHub are fantastic tools, but they definitely have a learning curve, and I’ve always found tools like GitKraken invaluable for helping me visualise what’s going on.&lt;/p&gt;

&lt;p&gt;Oh, and their logo/mascot? He’s a kraken called Keif, and he loves to dress up - check out the &lt;a href=&quot;https://www.gitkraken.com/keif-gallery&quot;&gt;Keif Gallery&lt;/a&gt; on their site for some wonderfully whimsical variations on the standard logo. I think this one here is my personal favourite:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://www.gitkraken.com/img/keif-gallery/gallery-slash.jpg&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

</description>
          <pubDate>2020-12-18T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/advent2020/2020/12/18/aocnt-gitkraken.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/advent2020/2020/12/18/aocnt-gitkraken.html</guid>
        </item>
      
    
      
        <item>
          <title>Dylan&apos;s Advent of Cool Nerd Things Day 17: Carbon</title>
          <description>&lt;p&gt;Source code is weird stuff. On one hand, it’s just regular text with a whole lot of weird punctuation. On the other hand, it’s indistinguishable from magic – arcane spells and incantations that we use to control the lightning that we trapped inside the rocks, solving hundreds of millions of calculations in a heartbeat, sending images and sounds and ideas flying around the world…&lt;/p&gt;

&lt;p&gt;Code is hard to read. Any experienced developer will relate to the experience of opening up a file you’ve not seen before and staring at it blankly, trying to figure out what’s going on… why does a function called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JoinStrings&lt;/code&gt; return an integer? What on earth does the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HelperManagerFactory&lt;/code&gt; do? Why doesn’t &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Array.map&lt;/code&gt; return a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Map&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;Alas, we haven’t yet invented something that makes code readable… but today’s &lt;a href=&quot;/nerdvent&quot;&gt;#Nerdvent&lt;/a&gt; tool can at least make it look good. It’s a website called &lt;a href=&quot;https://carbon.now.sh/&quot;&gt;Carbon&lt;/a&gt;, which I found thanks to &lt;a href=&quot;https://twitter.com/KevlinHenney&quot;&gt;Kevlin Henney&lt;/a&gt;, which lets you &lt;em&gt;“create and share beautiful images of your source code.”&lt;/em&gt; And I mean, this thing is stunning. Just lovely. Here’s part of my &lt;a href=&quot;https://dylanbeattie.github.io/advent-of-code-2020/day15/index.html&quot;&gt;solution to Advent of Code day 15&lt;/a&gt;, using the &lt;a href=&quot;https://en.wikipedia.org/wiki/Synthwave&quot;&gt;SynthWave&lt;/a&gt; ‘84 theme and the &lt;a href=&quot;https://dylanbeattie.net/advent2020/2020/12/10/aocnt-fira-code.html&quot;&gt;Fira Code font we saw the other day&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/carbon-advent-of-code-in-js.png&quot; alt=&quot;carbon-advent-of-code-in-js&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Isn’t that glorious? You can almost smell the rain sizzling on the neon lights…&lt;/p&gt;

&lt;p&gt;Here’s Duff’s Device, with the Blackboard theme and the JetBrains Mono font:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/carbon-duffs-device.png&quot; alt=&quot;carbon-duffs-device&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Carbon will export your code images as PNG or SVG files, which is great for making presentation slides - but if you want to embed them online, there’s an IFRAME option as well, which gives you beautifully highlighted code that your readers can still copy &amp;amp; paste:&lt;/p&gt;

&lt;iframe src=&quot;https://carbon.now.sh/embed/fzhYhqgR9mwHIuzRYHS7&quot; style=&quot;width: 680px; height: 362px; border:0; transform: scale(1); overflow:hidden;&quot; sandbox=&quot;allow-scripts allow-same-origin&quot;&gt; &lt;/iframe&gt;

&lt;p&gt;Carbon is over at &lt;a href=&quot;https://carbon.now.sh/&quot;&gt;https://carbon.now.sh/&lt;/a&gt; - check it out, give them a shout out on Twitter &lt;a href=&quot;https://twitter.com/carbon_app&quot;&gt;@carbon_app&lt;/a&gt;, and have fun!&lt;/p&gt;
</description>
          <pubDate>2020-12-17T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/advent2020/2020/12/17/aocnt-carbon.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/advent2020/2020/12/17/aocnt-carbon.html</guid>
        </item>
      
    
      
        <item>
          <title>Dylan&apos;s Advent of Cool Nerd Things Day 16: 7-Zip</title>
          <description>&lt;p&gt;I was working desktop support back in the days when email didn’t really work yet, and about once a week, somebody would call Helpdesk to say they couldn’t open an email attachment. By the time I eventually left that job, I knew more than you can possibly imagine about compression and archiving formats. ZIP, BZIP, GZIP, TAR, UUEncode, LZH, LZW… and those were just the ones we had back in 1997. These days, email has got a lot better, but once in while I’ll still get an attachment in a format I’ve not seen in a while. And now that almost everything is distributed online via the web, you’ll also find virtual CD and DVD images floating around in .ISO format, macOS installers baked into DMG files,  VMDK files containing virtual hard drives, MSIs and CAB files containing software installers… isn’t progress wonderful?&lt;/p&gt;

&lt;p&gt;That’s where &lt;a href=&quot;https://www.7-zip.org/&quot;&gt;7-Zip&lt;/a&gt; comes in. 7-Zip is a free Windows application that will open just about any archive file format you can think of, as well as creating and managing archives in most popular formats. I’ve used it on every Windows computer I’ve run since Windows 2000, and found it absolutely indispensable – it’s one of the first things I install on any new machine I’m setting up, and one of the first things I notice is missing if I’m working on someone else’s PC and they don’t have it installed.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/image-20201216000749769.png&quot; alt=&quot;image-20201216000749769&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It’s free (GPL2), it’s incredibly useful, and it’s available at &lt;a href=&quot;https://www.7-zip.org/&quot;&gt;7-zip.org&lt;/a&gt; - check it out.&lt;/p&gt;

</description>
          <pubDate>2020-12-16T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/advent2020/2020/12/16/aocnt-7-zip.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/advent2020/2020/12/16/aocnt-7-zip.html</guid>
        </item>
      
    
      
        <item>
          <title>Dylan&apos;s Advent of Cool Nerd Things Day 15: Paletton</title>
          <description>&lt;p&gt;Colour (or color, if you’re an HTML parser, a .NET class specification or a human being from the United States of America) is complicated. Way complicated. I’m a pretty monotone kind of person - my clothes are black, my walls are white, most of my favourite foods are beige, and I’m still not entirely comfortable with the existence of green LEGO bricks.&lt;/p&gt;

&lt;p&gt;But colour matters. There’s a wonderful infographic from &lt;a href=&quot;https://thelogocompany.net/&quot;&gt;The Logo Company&lt;/a&gt; called the &lt;a href=&quot;https://thelogocompany.net/blog/infographics/psychology-color-logo-design/&quot;&gt;Color Emotion Guide&lt;/a&gt;, that illustrates how dozens of popular brands use colour to evoke particular emotions and connections when consumers look at their logos:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://thelogocompany.net/wp-content/uploads/2013/01/Color_Emotion_Guide221.png&quot; alt=&quot;Logo Color Psychology&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When it comes to colour - and all sorts of aspects of visual design - I tend to group things into three categories. Category 1 is the things that just blow you away with how brilliant they are. Category 2 is the stuff you don’t really notice. And category 3 is the things that are so awful you can’t really ignore them.&lt;/p&gt;

&lt;p&gt;If you want your projects to end up in category 1, find a brilliant designer, pay them, and trust them; if you want your projects to end up in category 3, let your database engineers design your user interface. But if you’re happy sitting quietly in category 2, you can do a lot worse than learning some basic design principles, and then relying on tools that can help you avoid common mistakes.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://paletton.com/&quot;&gt;Paletton&lt;/a&gt; is such a tool. It’s an online tool for creating colour schemes, and it’s delightfully simple; you pick a colour you like, and tell it how many complementary colours you’d like to go with it and what sort of generator to use, and it’ll give you a palette. It’ll generate pretty much any kind of scheme, from bold contrasting primary colours to subtle muted pastels, but it’s based around mathematical colour models that should help you create usable colour schemes and avoid any horrible clashes and unpleasantness.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/image-20201213010534428.png&quot; alt=&quot;image-20201213010534428&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Formerly known as Color Scheme Designer, Paletton is online at &lt;a href=&quot;https://paletton.com/&quot;&gt;https://paletton.com/&lt;/a&gt;. It’s ad-supported, but it’s much more usable with an ad blocker running, so hey, why not block the ads and throw them a few bucks via the Donate link if you find it useful?&lt;/p&gt;

</description>
          <pubDate>2020-12-15T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/advent2020/2020/12/15/aocnt-paletton.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/advent2020/2020/12/15/aocnt-paletton.html</guid>
        </item>
      
    
      
        <item>
          <title>Solving Advent of Code in Amstrad LOGO</title>
          <description>&lt;p&gt;&lt;a href=&quot;https://adventofcode.com/&quot;&gt;Advent of Code&lt;/a&gt; is an “Advent calendar of small programming puzzles”; every year, the creators come up with a set of 25 programming puzzles and release them each day from the 1st to the 25th of December. This year, I resolved to do Advent of Code every day, and to do it live on Twitch – most days I’m streaming at 16:00 UTC for an hour or two, so &lt;a href=&quot;https://twitch.tv/dylanbeattie&quot;&gt;feel free to stop by&lt;/a&gt;! It’s been a lot of fun, and so far I’ve managed to solve both parts of every day’s puzzle live on the stream; I think the longest time to solve so far is nearly 2.5 hours for the &lt;a href=&quot;https://adventofcode.com/2020/day/7&quot;&gt;nested luggage problem on day 7&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On &lt;a href=&quot;https://adventofcode.com/2020/day/12&quot;&gt;day 12, “Rain Risk”, the puzzle was about navigating a ship&lt;/a&gt; – and the puzzle input was provided as a set of commands that looked a little reminiscent of the first programming language I ever used: &lt;a href=&quot;https://en.wikipedia.org/wiki/Logo_(programming_language)&quot;&gt;LOGO&lt;/a&gt;. My first computer was an &lt;a href=&quot;https://en.wikipedia.org/wiki/Amstrad_CPC&quot;&gt;Amstrad CPC6128&lt;/a&gt;, and as kid I’d sit there for hours using LOGO to draw shapes and build little quiz games. When I created my talk “&lt;a href=&quot;https://www.youtube.com/watch?v=6avJHaC3C2U&quot;&gt;The Art of Code&lt;/a&gt;” last year, I wanted the intro sequence to reflect that, so I found an Amstrad CPC emulator (the extremely brilliant &lt;a href=&quot;http://www.winape.net/&quot;&gt;WinAPE&lt;/a&gt;), worked out how to boot LOGO on it, and how to edit LOGO programs and instructions in a proper editor on the host PC and then use WinAPE’s auto-typing feature to replay those instructions into the LOGO interpreter.&lt;/p&gt;

&lt;p&gt;(As an aside, WinAPE is so detailed that it not only plays sampled audio of the 3” disk drive used on the Amstrad CPC6128 when you’re reading and writing files, it plays it through the right stereo channel because on the CPC the disk drive was on the right… the attention to detail that’s gone into this thing is just unbelievable.)&lt;/p&gt;

&lt;p&gt;So when I looked at yesterday’s Advent of Code puzzle, my first thought was “hey… maybe I could solve this one using Amstrad LOGO”.&lt;/p&gt;

&lt;p&gt;Want to know if it worked? Check out the YouTube video of the stream.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/sAzRc78TK0M&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;If you want to catch the rest of Advent of Code &lt;em&gt;live&lt;/em&gt;, I’m on &lt;a href=&quot;https://www.twitch.tv/dylanbeattie&quot;&gt;twitch.tv/dylanbeattie&lt;/a&gt; most days at 16:00 UTC – why not stop by and say hello?&lt;/p&gt;
</description>
          <pubDate>2020-12-14T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2020/12/14/solving-advent-of-code-in-amstrad-logo.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2020/12/14/solving-advent-of-code-in-amstrad-logo.html</guid>
        </item>
      
    
      
        <item>
          <title>Dylan&apos;s Advent of Cool Nerd Things Day 14: Typora</title>
          <description>&lt;p&gt;Writing things on the web can be harder than it looks. At the one extreme, you can just publish plain text files – and, yes, I know there’s no such thing as “plain text”, but a UTF-8 file served with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;text/plain&lt;/code&gt; MIME type header will be readable on just about any system that can render the alphabet it’s written in.&lt;/p&gt;

&lt;p&gt;At the other extreme, there’s the rich tapestry of tags, layouts and formats provided in modern HTML – grid layouts, flow layouts, drop caps, web fonts, and all sorts of wonderful typographic things that we never even dreamed of back when I started hacking web pages together in the 1990s.&lt;/p&gt;

&lt;p&gt;The problem is that &lt;em&gt;they both kinda suck&lt;/em&gt;. Managing online web content is significantly harder than it looks - online content management is a multi-billion-dollar industry, and some very smart people have &lt;a href=&quot;https://www.apress.com/gp/book/9781484257494&quot;&gt;written entire books&lt;/a&gt; about managing the information and presentation architecture that’s required by modern websites. And at the other end, plain text is… well, it’s a bit &lt;em&gt;plain&lt;/em&gt;, y’know?&lt;/p&gt;

&lt;p&gt;Everyone has their own “sweet spot” – the point where you have just enough control to achieve the things you want to do, without incorporating so much flexibility that managing the options becomes a maintenance headache. For me, that sweet spot is a file format called Markdown, It’s plain text with just enough special sauce to plug in images, headings, code snippets and bullet points – the kinds of things I &lt;em&gt;want&lt;/em&gt; to think about when I’m writing articles – but without having to think about layouts, fonts and typography, which are the kind of things I want to think about once every five years and then forget about until the next redesign.&lt;/p&gt;

&lt;p&gt;The last big overhaul of this site was in August 2019, when I &lt;a href=&quot;https://dylanbeattie.net/2019/08/14/migrating-from-blogger-to-github-pages.html&quot;&gt;migrated the whole site from Blogger to GitHub Pages&lt;/a&gt;, using a publishing system called &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt;. Jekyll loves Markdown files, and all of the posts and articles I’ve published since then have been written in Markdown, using an editor called &lt;a href=&quot;https://typora.io/&quot;&gt;Typora&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/image-20201213003003044.png&quot; alt=&quot;image-20201213003003044&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Typora is wonderful. It’s a lightweight, minimalist editor for Markdown files that is what I called “just WYSIWYG enough” - things like headings, bullets and code snippets get rendered on the fly, so you can get an idea of the layout of your article, but if you need to include chunks of HTML to do things like embedding IFRAMES for playing YouTube videos, just do it - if Typora knows how to render them, it will, otherwise it’ll just leave the HTML tags visible and you can check it in a browser.&lt;/p&gt;

&lt;p&gt;Typora has a bunch more features I find really, really useful, like being able to use Jekyll frontmatter to define where to store your images, and then copy’n’paste images from the Windows clipboard and have them automatically converted to PNGs and dropped into the right folder.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/image-20201213003111024.png&quot; alt=&quot;image-20201213003111024&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It’s got a whole bunch more cool features that I’ve hardly scratched the surface of - sequence diagrams, tables, mathematical equations - but fundamentally, it’s just a really, really good tool for writing blog posts and articles.&lt;/p&gt;

&lt;p&gt;The only open question about Typora is how much it’ll cost. At the moment it’s in beta, and it’s free-as-in-beer - it isn’t open source, but it doesn’t cost money. It’s been on version 0.9.9.something for a while now, though, so I’m expecting it to ship 1.0 any day now, and I’m curious to see what their pricing model is going to be when that happens. I have no problem at all paying for good software, it’s just a little disconcerting becoming this attached to a tool without knowing what it’ll end up costing. But hey, right now it’s in beta, the beta is rock-solid, and it’s free for Windows, macOS and Linux. And it’s excellent.&lt;/p&gt;

&lt;p&gt;Check it out at &lt;a href=&quot;https://typora.io/&quot;&gt;https://typora.io/&lt;/a&gt;&lt;/p&gt;

</description>
          <pubDate>2020-12-14T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/advent2020/2020/12/14/aocnt-typora.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/advent2020/2020/12/14/aocnt-typora.html</guid>
        </item>
      
    
      
        <item>
          <title>Dylan&apos;s Advent of Cool Nerd Things Day 13: Chrome Dino Game</title>
          <description>&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/image-20201212234716361.png&quot; alt=&quot;image-20201212234716361&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You’ve probably seen this screen a few times… maybe you’ve been on your phone on the train and you’ve travelled into an area with no signal, maybe your home wifi has died again… or  maybe you went for a nice long walk in the countryside and realised too late that the countryside is actually quite big, most of it doesn’t have terribly good mobile phone signal, and Google Maps doesn’t work if you don’t have signal. In any case, you’ve had to figure out how to survive Without The Internet for a spell. We know it is theoretically possible to survive Without The Internet because apparently human beings did it for, well, most of history… but hey, just because you’ve got no 4G is no reason to go back to the cretaceous period… or is it?&lt;/p&gt;

&lt;p&gt;You see, the “No Internet” screen built into Google Chrome, and in most other browsers that use the Chromium browser engine, has a little picture of a dinosaur as a sort of in-joke about life without internet being prehistoric. But you might not have known that there’s actually an entire game hidden in there… try it! You &lt;em&gt;could&lt;/em&gt; disconnect from wifi, but you’re reading my blog, so that would be a really bad idea. Instead, try browsing to &lt;a href=&quot;chrome://dino/&quot;&gt;chrome://dino/&lt;/a&gt;, press Space (or tap the screen if you’re on a touchscreen)… and away you go!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/aocnt-day-13-chrome-dino-game.gif&quot; alt=&quot;aocnt-day-13-chrome-dino-game&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Run merrily through the desert, jump over the cacti, avoid the pterodactyls… like Space Invaders and Tetris, the gameplay is incredibly simple but remarkably well-balanced and it’s surprisingly easy to get hooked on it. Especially if you’re stuck on a train that’s in a tube tunnel waiting for a vacant platform at Liverpool Street (y’all do remember trains, right?)&lt;/p&gt;

&lt;p&gt;Check it out at &lt;a href=&quot;chrome://dino/,&quot;&gt;chrome://dino/,&lt;/a&gt; or just wait for the internet to go down. It’s on most Chrome-based browsers, and it’s also built into Chrome for iOS, even though Chrome on iOS devices is actually a Chrome wrapper around the Safari rendering engine. Have fun, and watch out for the absolutely wonderful expression of stunned surprise on the T-rex’s face when he gets hit right in the unfortunates by a low-flying pterodactyl. It’s delightful.&lt;/p&gt;

&lt;p&gt;And to think people say there’s no character development in computer games.&lt;/p&gt;
</description>
          <pubDate>2020-12-13T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/advent2020/2020/12/13/aocnt-chrome-dino-game.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/advent2020/2020/12/13/aocnt-chrome-dino-game.html</guid>
        </item>
      
    
      
        <item>
          <title>Dylan&apos;s Advent of Cool Nerd Things Day 12: Open Hardware Monitor</title>
          <description>&lt;p&gt;Until this year, my main computer was a 2014 Macbook Pro – in fact, for much of that time, it was the only computer I owned. I still use it for almost all my music production work, but when 2020 kicked in and it became apparent that there wasn’t likely to be a whole lot of travel going on, I invested in building a proper powerhouse PC workstation. I wanted something that struck a very delicate balance between processing power and fan noise - it’s pretty easy to get modern PCs to run ridiculously fast if you can just stop them overheating, but finding something that’s fast &lt;em&gt;and&lt;/em&gt; quiet is a bit trickier.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/image-20201211193353095.png&quot; alt=&quot;image-20201211193353095&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Enter Open Hardware Monitor. It’s a free open source application that’ll read just about every hardware sensor on any modern PC motherboard - CPU voltages, memory utilisation, various load metrics for SSD and NVRAM storage devices, and, most usefully for me, fan speeds and temperatures. I keep Open Hardware Monitor running pretty much all the time, and if one of my apps starts grinding or becomes unresponsive, a quick glance at OHM shows me whether something’s overheating or not. The first couple of weeks running in my new system, I’d often have to tweak fan profiles and settings to get everything nicely balanced; these days it’s all settled in quite nicely but it’s still reassuring to look over at OHM and see that everything’s under control.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/image-20201211194157139.png&quot; alt=&quot;image-20201211194157139&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Oh, and it’s a .NET WinForms app. The BEST application development framework in the history of software development, ever, bar none. Remember when you could build apps without having a meeting to decide how to hire the design agency who would tell you what colour to make the buttons? A framework from a day when computers were beige, buttons were grey, and rounded corners were for soft play areas. And, hey, it’s just been ported to .NET Core and will be part of the .NET 5 runtime, so who’s laughing now, hey? Did somebody say WPF?&lt;/p&gt;

&lt;p&gt;You can get Open Hardware Monitor from &lt;a href=&quot;https://openhardwaremonitor.org/&quot;&gt;https://openhardwaremonitor.org/&lt;/a&gt; - and if you fancy a bit of old-school WinForms hacking, the &lt;a href=&quot;https://github.com/openhardwaremonitor/openhardwaremonitor&quot;&gt;source code is on GitHub&lt;/a&gt; and released under the Mozilla Public License 2.0, so grab the code and see how we used to build apps back in the good old days. And if anyone wants to add “dark mode” to it, I’ll buy them a beer… 🍺😃&lt;/p&gt;
</description>
          <pubDate>2020-12-12T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/advent2020/2020/12/12/aocnt-open-hardware-monitor.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/advent2020/2020/12/12/aocnt-open-hardware-monitor.html</guid>
        </item>
      
    
      
        <item>
          <title>Dylan&apos;s Advent of Cool Nerd Things Day 11: Audacity</title>
          <description>&lt;p&gt;Audacity is a free program for recording and editing audio files - sound recordings, samples, music. More than an audio player, but simpler than a full-blown digital audio workstation like ProTools or Logic Pro, it’s another one of those tools that I find myself reaching for way more often than you might think.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/image-20201206202339257.png&quot; alt=&quot;image-20201206202339257&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If you’ve ever wanted to edit a sound clip to include it in a presentation, record a voiceover, or even just record your own microphone to see what you sound like before that Zoom job interview you’ve got coming up, Audacity is ideal. It’ll read and write just about any audio format you can think of. It also includes dozens of professional-quality effects, filters and tone generators - so if you want to know what you’d sound like as a Dalek, Audacity can help.&lt;/p&gt;

&lt;p&gt;Check it out at https://www.audacityteam.org/&lt;/p&gt;

</description>
          <pubDate>2020-12-11T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/advent2020/2020/12/11/aocnt-audacity.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/advent2020/2020/12/11/aocnt-audacity.html</guid>
        </item>
      
    
      
        <item>
          <title>Dylan&apos;s Advent of Cool Nerd Things Day 10: Fira Code</title>
          <description>&lt;p&gt;Today’s Cool Nerdvent Thing isn’t a tool, or a utility, or a website, or a hidden Windows feature… it’s a font. But not just any font – this font is Fira Code, and it is absolutely the best font ever created for computer programming.&lt;/p&gt;

&lt;p&gt;Modern computer font systems like TrueType and OpenType support a feature called &lt;strong&gt;ligatures&lt;/strong&gt;. Ligatures date all the way back to the days of mechanical typesetting and movable type, when printers (as in, people who run printing presses, as opposed to, say, HP Laserjets) would pick a specific glyph to represent common pairs of characters:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/ligatures.png&quot; alt=&quot;ligatures&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can get lots of fonts that include ligatures for common letter sequences like those above, but Fira Code takes things to a whole new level by using ligatures to replace common programming character sequences with symbols. It’s not the only programming font that uses ligatures – there’s a &lt;a href=&quot;https://betterwebtype.com/articles/2020/02/13/5-monospaced-fonts-with-cool-coding-ligatures/&quot;&gt;great article on Better Web Type about monospaced fonts with coding ligatures&lt;/a&gt; – but it’s one of the best, and it’s definitely my personal favourite.&lt;/p&gt;

&lt;p&gt;Here’s a snippet of F# code using the regular Consolas typeface, and the same code shown in Fira Code:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/image-20201208180855193.png&quot; alt=&quot;image-20201208180855193&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This is &lt;strong&gt;exactly the same source code&lt;/strong&gt; – it’s a regular ASCII text file; the only thing that’s changed is the font. At a glance, the ligatures might look like an eye-catching gimmick, but I find them really, really useful for working on code that has lots of gnarly operators - if you’ve ever had a JavaScript bug caused by double-equals (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;==&lt;/code&gt;’) vs triple-equals (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;===&lt;/code&gt;), you’ll appreciate how visually distinct Fira Code makes these two character sequences.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://repository-images.githubusercontent.com/26500787/bf313080-6b02-11ea-9cd5-c3dca880736d&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Underneath the ligatures, though, there’s a really solid, readable monospaced font, available in various weights for different kinds of editors and displays. &lt;strong&gt;Fira Code&lt;/strong&gt; is released under the SIL Open Font License 1.1, and it’s available as a TrueType TTF file that’ll work on most operating systems, most text editors and programming tools, and it’s available in Open Font WOFF format for use on your website as well.&lt;/p&gt;

&lt;p&gt;Fira Code is created by &lt;a href=&quot;https://twitter.com/nikitonsky&quot;&gt;Niki Tonsky&lt;/a&gt;, and it’s available at &lt;a href=&quot;https://github.com/tonsky/FiraCode&quot;&gt;https://github.com/tonsky/FiraCode&lt;/a&gt;.&lt;/p&gt;
</description>
          <pubDate>2020-12-10T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/advent2020/2020/12/10/aocnt-fira-code.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/advent2020/2020/12/10/aocnt-fira-code.html</guid>
        </item>
      
    
      
        <item>
          <title>Dylan&apos;s Advent of Cool Nerd Things Day 9: Microsoft PowerToys</title>
          <description>&lt;p&gt;Way back in the days of Windows 95, Microsoft released a set of freeware utilities called the &lt;a href=&quot;https://en.wikipedia.org/wiki/Microsoft_PowerToys#PowerToys_for_Windows_95&quot;&gt;PowerToys for Windows 95&lt;/a&gt;. It included some things that were genuinely useful – like being able to right-click any folder and open a command prompt in that folder – and some stuff that was, um, less useful, like the FindX tool that added a drag’n’drop capability nobody wanted to a Find menu nobody ever used. Anyone remember FindX? &lt;a href=&quot;https://www.youtube.com/watch?v=uhiCFdWeQfA&quot;&gt;Anyone…? Anyone…?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fast-forward a few decades, and &lt;a href=&quot;https://github.com/microsoft/PowerToys&quot;&gt;the PowerToys are back&lt;/a&gt; – this time for Windows 10. They’re free-as-in-beer, they’re free-as-in-speech (MIT license), and they’re really rather good. There’s two that particularly stand out for me. One is the &lt;a href=&quot;https://github.com/microsoft/PowerToys/wiki/Shortcut-Guide-Overview&quot;&gt;Windows key shortcut guide&lt;/a&gt; – hold down the Windows key for more than a second and an onscreen guide pops up telling you all the available shortcuts:&lt;img src=&quot;/images/posts/2020-12-advent/image-20201206195822478.png&quot; alt=&quot;image-20201206195822478&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The other is the  &lt;a href=&quot;https://github.com/microsoft/PowerToys/wiki/Image-Resizer-Overview&quot;&gt;Image Resizer&lt;/a&gt;, a shell extension that offers bulk image resizing via a right-click context menu, which is incredibly useful when you’ve got a bunch of 20 megapixel images off your DSLR camera and you want to email copies to somebody without breaking the internet.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/image-20201206200322529.png&quot; alt=&quot;image-20201206200322529&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Alongside those, there are a bunch of tools for video conferencing, batch renaming files, and organising your Windows desktop – I’ve heard that &lt;a href=&quot;https://github.com/microsoft/PowerToys/wiki/FancyZones-Overview&quot;&gt;Fancy Zones&lt;/a&gt; is fantastic if you’ve got one of those &lt;a href=&quot;https://www.samsung.com/uk/monitors/gaming/qled-gaming-monitor-with-329-super-ultra-wide-screen-49-inch-lc49hg90dmuxen/&quot;&gt;ultra-super-mega-widescreen wraparound displays&lt;/a&gt;. They’re definitely worth a look if you’re a Windows power user, and the PowerToys team is adding new experimental tools and utilities all the time so check back often to see what’s new.&lt;/p&gt;

&lt;p&gt;Check them out at &lt;a href=&quot;https://github.com/microsoft/PowerToys&quot;&gt;https://github.com/microsoft/PowerToys&lt;/a&gt;, or go straight to the  &lt;a href=&quot;https://github.com/microsoft/PowerToys/releases/&quot;&gt;releases page&lt;/a&gt; for the latest installer.&lt;/p&gt;
</description>
          <pubDate>2020-12-09T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/advent2020/2020/12/09/aocnt-microsoft-powertoys.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/advent2020/2020/12/09/aocnt-microsoft-powertoys.html</guid>
        </item>
      
    
      
        <item>
          <title>Dylan&apos;s Advent of Cool Nerd Things Day 8: Instant Eyedropper</title>
          <description>&lt;p&gt;This one’s a tool I’ve used for years and years, and it’s absolutely wonderful. You know the eyedropper tool in Photoshop? It’s basically that, but it will pick up the colour of any pixel on your desktop and copy the HTML hex colour code to the clipboard.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/instant-eyedropper-example.gif&quot; alt=&quot;instant-eyedropper-example&quot; /&gt;&lt;/p&gt;

&lt;p&gt;That’s pretty much it. You can configure the mode; “click” mode is one click to activate the tool, and again to select a colour and copy it, and if that’s just too many clicks, put it in “drag” mode and then just drag the mouse from the tool onto any pixel on the screen. You can also tweak the exact HTML format to get your hex digits in your preferred format – but, really, it’s just a wonderfully simple, useful, unobtrusive tool. It sits in your system tray until you need it, it does exactly what it’s supposed to, and then it gets out the way so you can get on with whatever you were doing.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/image-20201206194134042.png&quot; alt=&quot;image-20201206194134042&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It’s free, &lt;a href=&quot;https://github.com/miaupaw/instant-eyedropper&quot;&gt;open source&lt;/a&gt;, runs on Windows XP through Windows 10 (and I know, because I’ve run it on every single version of Windows from XP to 10) – check it out at &lt;a href=&quot;http://instant-eyedropper.com/&quot;&gt;http://instant-eyedropper.com/&lt;/a&gt;, and if you find it useful, why not throw $5 their way to support the project?&lt;/p&gt;

</description>
          <pubDate>2020-12-08T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/advent2020/2020/12/08/aocnt-instant-eyedropper.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/advent2020/2020/12/08/aocnt-instant-eyedropper.html</guid>
        </item>
      
    
      
        <item>
          <title>Dylan&apos;s Advent of Cool Nerd Things Day 7: ngrok</title>
          <description>&lt;p&gt;Most of them time, when I’m building web apps and websites, I follow a fairly common workflow: get stuff working locally, test it locally, and then push it out on the Actual Internet somewhere where people can see it. Most of the time, this works pretty well, although there are definitely occasional cases of &lt;a href=&quot;https://blog.codinghorror.com/the-works-on-my-machine-certification-program/&quot;&gt;“well, it worked on &lt;strong&gt;my&lt;/strong&gt; machine!”&lt;/a&gt; when code that was working perfectly on localhost doesn’t do what you expect when it goes live.&lt;/p&gt;

&lt;p&gt;But there are a handful of workflows where testing on localhost isn’t an option because something else, out there on the wild wild web, needs to interact with your code. Maybe you’re building something like an OAuth2 flow so users can sign in to your apps using their social media accounts. Maybe you’re using a cloud-based CRM or a deployment system that’s using webhooks to pass notifications back to your own code, and you need a way of testing that the webhooks are working. For these kinds of applications, localhost isn’t enough - you need a way that somebody else’s code, running out there on the web, can talk to the development code that you’re running on your machine right now.&lt;/p&gt;

&lt;p&gt;Now, you could set up a certificate, open a port on your home or office firewall, register a domain name, set up some DNS host records… in fact, I’ve posted recently about how I’ve done exactly this. But it’s a lot of effort and it’s easy to overlook something and end up leaving something exposed on the internet that shouldn’t be there.&lt;/p&gt;

&lt;p&gt;Alternatively, you can use ngrok. Ngrok is brilliant. It’s a tiny executable. You download it, you run it, you tell it what port to listen on, and it creates a secure, temporary tunnel from the internet to that port on your local machine. The tunnel stays open for a long as ngrok is running – and when you stop ngrok, the tunnel goes away.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/image-20201206172913178.png&quot; alt=&quot;image-20201206172913178&quot; /&gt;&lt;/p&gt;

&lt;p&gt;There’s a “free forever” plan that’ll give you a single process supporting up to 4 tunnels on randomly assigned URLs; if you upgrade to one of their paid plans you can get reserved domains, reserved TCP addresses, SSO, and higher connection limits. Like a lot of the tools on this list, I used the free version for a while and then paid for a pro license because I realised it would save me so much time it would easily offset the cost of licensing it; it’s one of those tools that once you get the hang of it, you’ll find it’s useful for all kinds of things. I’ve used it to test “sign in with Twitter”-style OAuth2 flows; I’ve used it to debug webhooks from Paypal and from Microsoft Dynamics 365; I’ve even used it to share work-in-progress code with folks over a Zoom call or something so they can check it on their own browsers &amp;amp; mobile devices.&lt;/p&gt;

&lt;p&gt;Check out ngrok at &lt;a href=&quot;https://ngrok.com/&quot;&gt;https://ngrok.com/&lt;/a&gt;&lt;/p&gt;

</description>
          <pubDate>2020-12-07T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/advent2020/2020/12/07/aocnt-ngrok.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/advent2020/2020/12/07/aocnt-ngrok.html</guid>
        </item>
      
    
      
        <item>
          <title>Dylan&apos;s Advent of Cool Nerd Things Day 6: Franz</title>
          <description>&lt;p&gt;Have you ever had that thing where you wake up in the morning, and you &lt;em&gt;think&lt;/em&gt; you might have seen a message from somebody on your phone at 5am, but now you can’t see it? And you don’t know whether you dismissed the notification or dreamed the whole thing? And you check email, and WhatsApp, and Telegram, and Slack, and don’t find it, and so you assume you must have dreamed it and then a week later you find it in your LinkedIn inbox?&lt;/p&gt;

&lt;p&gt;It’s kinda nice living in a world where it’s so easy to stay connected to people, but, well, gosh, things have gotten a bit out of hand. Right now I’m in 34 Slack workspaces, 5 Discord servers, 4 WhatsApp group chats, plus Twitter DMs, Facebook Messenger, Skype, LinkedIn messages, email… and when you’re an independent consultant like I am, you need to work with whatever channel your clients are using.&lt;/p&gt;

&lt;p&gt;That’s where Franz comes in. It’s a Windows desktop app that pulls together basically ALL THE THINGS and gathers them into one place:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/image-20201206122152793.png&quot; alt=&quot;image-20201206122152793&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The free tier lets you add up to three services; paid subs give you more connected apps, plus cool things like the Franz Todo List. I’ve been running it for a couple of weeks, and recently activated the free trial to see how it copes with lots of connected apps - but so far I’ve been really, really impressed.&lt;/p&gt;

&lt;p&gt;I’ve got to admit, there’s a point in the setup where I stopped and thought “hang on… I downloaded this random app from the internet and now I’m giving it credentials to send messages, as me, on basically every platform I use” - which is a little scary. But behind the scenes, I &lt;em&gt;think&lt;/em&gt; it’s just pulling down the web interface versions of all those different services and wrapping them into a single desktop app with separate views for each service. Don’t quote me on that, but it would explain how they’ve managed to integrate things like WhatsApp that don’t really publish any kind of API.&lt;/p&gt;

&lt;p&gt;Oh, and their SEO strapline made me giggle:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Franz* is a free messaging app /former emperor of Austria, that combines chat &amp;amp; messaging services into one application.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Check it out at &lt;a href=&quot;https://meetfranz.com/&quot;&gt;https://meetfranz.com/&lt;/a&gt;&lt;/p&gt;

</description>
          <pubDate>2020-12-06T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/advent2020/2020/12/06/aocnt-franz.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/advent2020/2020/12/06/aocnt-franz.html</guid>
        </item>
      
    
      
        <item>
          <title>Dylan&apos;s Advent of Cool Nerd Things Day 5: WClock</title>
          <description>&lt;p&gt;A few years back, I had the singular experience of flying from Novosibirsk to London via Moscow, on the weekend that the clocks change. Novosibirsk is 4 hours ahead of Moscow and it’s a 4-hour flight - so you get on a plane at 10pm and land four hours later, at 10pm, and then somewhere in the air between Sheremetyevo and Gatwick the UK jumps around by an hour and you land sometime in the morning completely discombobulated.&lt;/p&gt;

&lt;p&gt;Because, yeah, timezones. Just one more way in which 2020 has been an endless amount of fun - folks who used to spend half the year in airport lounges and hotels are now spending at at home, trying to keep up with all the events and meetings and people they would normally, and doing it all remote means lots of fun trying to schedule meetings and work out whether that conference you’re going to has published their schedule on your time, their time, or UTC.&lt;/p&gt;

&lt;p&gt;Enter WClock. It’s a desktop clock for Windows computers. It supports multiple timezones, it’s extremely customisable, it’s “always on top” - so whenever I’ve been running a training course or speaking at a conference where we’ve got folks from multiple timezones joining, I’ll take a moment to put all their clocks into my WClock so I can remember who’s when.&lt;/p&gt;

&lt;p&gt;My regular one currently looks like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/image-20201204135627107.png&quot; alt=&quot;image-20201204135627107&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Well, what, did you expect? High-def raytraced graphics and lots of lens flare? &lt;strong&gt;It’s a clock!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;OK, here it is with some options:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/SNAGHTML938b4d.PNG&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It’s wonderfully old school, it’s simple, it works, and I’ve found it really, really useful. Check it out: &lt;a href=&quot;https://www.di-mgt.com.au/wclock/download.html&quot;&gt;https://www.di-mgt.com.au/wclock/download.html&lt;/a&gt;&lt;/p&gt;

</description>
          <pubDate>2020-12-05T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/advent2020/2020/12/05/aocnt-wclock.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/advent2020/2020/12/05/aocnt-wclock.html</guid>
        </item>
      
    
      
        <item>
          <title>Dylan&apos;s Advent of Cool Nerd Things Day 4: X-Box Game Bar</title>
          <description>&lt;p&gt;You know when you stumble across something that makes you go “wow, how did I have no idea this was a thing?” Well, I’ve run some version of Microsoft Windows almost every day since Windows 3.1, and until a few months ago I had no idea the &lt;strong&gt;X-Box Game Bar&lt;/strong&gt; even existed.&lt;/p&gt;

&lt;p&gt;If you’re on Windows, try it. Press &lt;strong&gt;Win-G&lt;/strong&gt; - there’s a whole bunch of interesting things hidden in there:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/image-20201130165138264.png&quot; alt=&quot;image-20201130165138264&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The one that stands out for me is the Capture tool - one-click screen recording, which is really useful if you need to record a demo or reproduction steps for a bug report and don’t have anything like Camtasia available. There’s also audio volume controls, CPU/GPU performance metrics, and a chat system that’ll connect to Steam, Reddit, Discord, Facebook, Twitch and various other chat services. And if you’re running Windows 10, you have all this installed already. Just press Win-G. Pretty cool, huh?&lt;/p&gt;
</description>
          <pubDate>2020-12-04T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/advent2020/2020/12/04/aocnt-xbox-game-bar.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/advent2020/2020/12/04/aocnt-xbox-game-bar.html</guid>
        </item>
      
    
      
        <item>
          <title>Dylan&apos;s Advent of Cool Nerd Things Day 3: Reincubate Camo</title>
          <description>&lt;p&gt;2020 was the year that webcams suddenly became a big deal. For folks who’d already been working remote for a while, it was no surprise how important camera and audio quality are for video meetings - Scott Hanselman wrote a &lt;a href=&quot;https://www.hanselman.com/blog/good-better-best-creating-the-ultimate-remote-worker-webcam-setup-on-a-budget&quot;&gt;great post about this&lt;/a&gt; back in August last year - but with the entire world suddenly working from home, webcams sold out pretty much everywhere back in March and lots of folks have been working remote using their laptop’s built-in camera.&lt;/p&gt;

&lt;p&gt;Of course, most of those folks also have a smartphone, and the camera in most modern smartphones is truly astonishing, which raises the question: can I use the camera in my phone as a webcam? There are several apps out there that claim to do this, and I’m pretty sure I’ve tried all of them, but the first one that’s actually worked for me is &lt;a href=&quot;https://reincubate.com/camo/&quot;&gt;Camo&lt;/a&gt; by Reincubate.&lt;/p&gt;

&lt;p&gt;I’ve tried it with an iPhone 6S and an iPhone SE, on a 2014 Macbook Pro and a couple of different Windows machines, and been really, really impressed. It just works. Run the installer, install the app, connect your phone using a USB cable, and it works. The image quality is fantastic; the free version gives you control over the camera’s focus and resolution, and if you pay £34.99 for the Pro version, you get full control over your phone camera’s exposure, white balance and various other settings.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/image-20201130162419841.png&quot; alt=&quot;image-20201130162419841&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Plus - did I mention the image quality is good? Here is in Zoom, side-by-side with my Logitech C920 HD webcam:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/SNAGHTML45e6c29.PNG&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It’s on macOS and Windows, but it’s iPhone only (sorry, Android fans.) The consumer version of Camo for Windows is still in beta - there’s a signup form on their site; I’ve been running it on Windows for a month or so without any problems at all, including running it alongside four other cameras in a really elaborate multi-cam streaming setup.&lt;/p&gt;

&lt;p&gt;Oh, and apparently there’s a 40% off until December 4th if you sign up with the code &lt;strong&gt;CYBERCAMO&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Check it out: &lt;a href=&quot;https://reincubate.com/camo/&quot;&gt;https://reincubate.com/camo/&lt;/a&gt;&lt;/p&gt;

</description>
          <pubDate>2020-12-03T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/advent2020/2020/12/03/aocnt-camo.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/advent2020/2020/12/03/aocnt-camo.html</guid>
        </item>
      
    
      
        <item>
          <title>Dylan&apos;s Advent of Cool Nerd Things Day 2: WinDirStat</title>
          <description>&lt;p&gt;&lt;strong&gt;“But I had LOADS of disk space… where’s it all gone?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We’ve all been there, right? Shiny new computer with endless gigabytes of free disk space… and then suddenly one day you’ve got 128Kb free and you’re like “No way…”&lt;/p&gt;

&lt;p&gt;Well, you can spend hours and hours opening folders, poking around, trying to work out where it all went – or you can use WinDirStat. (If you’re on macOS, &lt;a href=&quot;http://www.derlien.com/&quot;&gt;DiskInventoryX&lt;/a&gt; does pretty much the same thing; on Linux there’s &lt;a href=&quot;https://github.com/shundhammer/qdirstat&quot;&gt;QDirStat&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Point it a drive or a folder, give it a few minutes to figure everything out, and boom – there’s a map of all the files on your hard drive. Bigger rectangles are bigger files, file types are colour-coded, and there’s loads of useful metadata about them all too. Here’s my D: drive:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/image-20201130140641899.png&quot; alt=&quot;image-20201130140641899&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Yep, that’s a 333Gb Dropbox with 120Gb of musical parodies in it – and yes, I have over 50Gb of Powerpoint presentations in my Dropbox. Good thing storage is so cheap, right?&lt;/p&gt;

&lt;p&gt;WinDirStat is free (it’s released under a GPLv2 license), and you can find it at &lt;a href=&quot;https://windirstat.net/index.html&quot;&gt;https://windirstat.net/index.html&lt;/a&gt;.&lt;/p&gt;

</description>
          <pubDate>2020-12-02T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/advent2020/2020/12/02/aocnt-windirstat.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/advent2020/2020/12/02/aocnt-windirstat.html</guid>
        </item>
      
    
      
        <item>
          <title>Dylan&apos;s Advent of Cool Nerd Things Day 1: Sizer</title>
          <description>&lt;p&gt;I’m a stickler for detail. If I’m taking screenshots to use in a blog post or a PowerPoint slide, I like to get the window I’m capturing to be exactly the right size and shape before I snap it. This used to involve a lot of very, very patient dragging – and then I found &lt;a href=&quot;http://www.brianapps.net/sizer4/&quot;&gt;Sizer&lt;/a&gt;. It’s a tiny Windows app that runs in the background until you right-click a window frame:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-12-advent/sizer-1606743424916.png&quot; alt=&quot;sizer&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It’s got loads of cool extra features as well, like snap to grid, custom sizes, and it’ll move windows as well as resizing them. Check out the &lt;a href=&quot;http://www.brianapps.net/sizer/userguide.html&quot;&gt;user guide&lt;/a&gt; to see what else it can do.&lt;/p&gt;

&lt;p&gt;Sizer is online at &lt;a href=&quot;http://www.brianapps.net/sizer4/&quot;&gt;http://www.brianapps.net/sizer4/&lt;/a&gt; – check it out.&lt;/p&gt;

</description>
          <pubDate>2020-12-01T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/advent2020/2020/12/01/aocnt-sizer.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/advent2020/2020/12/01/aocnt-sizer.html</guid>
        </item>
      
    
      
        <item>
          <title>Spark - a tiny standalone HTTP server</title>
          <description>&lt;p&gt;&lt;em&gt;TL;DR: Spark (&lt;a href=&quot;https://github.com/rif/spark&quot;&gt;https://github.com/rif/spark&lt;/a&gt;) is a tiny standalone web server, written in Go, with binaries available for most platforms. And it’s very cool.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When it comes to web dev, I’m kinda old school. I’ve been building web apps long enough that I remember when you could build an entire web site just by pointing your browser at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;file:///c:/websites/mysite/index.htm&lt;/code&gt;, hack it around until it worked, then upload the whole folder via FTP and boom, job done.&lt;/p&gt;

&lt;p&gt;These days, you can build some pretty amazing things using HTML, JavaScript and CSS; everything runs in the browser and all you need on the server side is a way of sharing static files over HTTP. But for all sorts of reasons - mainly to with security around things like AJAX requests - you’re gonna need an actual web server running on your dev machine; most of the cool stuff that the modern web does just doesn’t work over &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;file:///&lt;/code&gt; URLs. So I’ve been looking around for a while for a really fast, lightweight HTTP server.&lt;/p&gt;

&lt;p&gt;For a while, I was using Python for this - python 2.x has a builtin module called SimpleHTTPServer that will share the current directory over HTTP:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;D:\projects\github\&amp;gt;python -m SimpleHTTPServer 80
Serving HTTP on 0.0.0.0 port 80 ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-11-24-spark-the-emergency-web-server/image-20201124104555262.png&quot; alt=&quot;image-20201124104555262&quot; /&gt;&lt;/p&gt;

&lt;p&gt;That works pretty well, but it’s a bit verbose. Plus, the module syntax has changed in the Python 3 - it’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http.server&lt;/code&gt; instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SimpleHTTPServer&lt;/code&gt; - and, obviously, it won’t work without Python installed.&lt;/p&gt;

&lt;p&gt;Enter &lt;strong&gt;&lt;a href=&quot;https://github.com/rif/spark&quot;&gt;Spark&lt;/a&gt;&lt;/strong&gt;. It’s a tiny standalone web server, written in Go by &lt;a href=&quot;https://www.fericean.ro/&quot;&gt;Radu Ioan Fericean&lt;/a&gt;, with &lt;a href=&quot;https://github.com/rif/spark/releases&quot;&gt;binaries available for Windows, macOS, and Linux&lt;/a&gt;. Add it to your system path, and then you can just type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spark&lt;/code&gt; in any directory and it’ll share it over HTTP on port 8080.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;D:\Projects\github&amp;gt;spark
2020/11/24 11:19:49 Warning: serving files without any filter!
2020/11/24 11:19:49 Serving . on 0.0.0.0:8080/...

D:\Projects\github&amp;gt;spark -port 80
2020/11/24 11:19:58 Warning: serving files without any filter!
2020/11/24 11:19:58 Serving . on 0.0.0.0:80/...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It’ll serve static content, from a file or provided on the command line:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;D:\Projects\github&amp;gt;spark &quot;&amp;lt;h1&amp;gt;Hello World!&amp;lt;/h1&amp;gt;&quot;
2020/11/24 11:22:17 Serving &amp;lt;h1&amp;gt;Hello World!&amp;lt;/h1&amp;gt; on 0.0.0.0:8080/...

D:\Projects\github&amp;gt;spark offline.html
2020/11/24 11:25:52 Serving offline.html on 0.0.0.0:8080/...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It supports SSL certificates, and some very rudimentary security features to exclude specific paths when sharing a directory. It’s tiny, it’s fast, and it’s incredibly awesome.&lt;/p&gt;

&lt;p&gt;In fact, the only problem with Spark is the name… when I went to put this post together, all I could remember about it is that it was a web server called Spark. It took me a good 10 minutes to find it again; it turns out there are &lt;a href=&quot;https://www.google.com/search?q=spark+web+server&quot;&gt;quite a lot of web server related things called spark&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-11-24-spark-the-emergency-web-server/image-20201124113121663.png&quot; alt=&quot;[image-20201124113121663](https://news.ycombinator.com/item?id=7224162)&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Yeah, I know, naming things is hard. But hey, one of the reasons I write posts like this is so that it really helps me remember stuff for next time. And if it doesn’t, I can always google my own blog, right?&lt;/p&gt;
</description>
          <pubDate>2020-11-24T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2020/11/24/spark-the-emergency-web-server.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2020/11/24/spark-the-emergency-web-server.html</guid>
        </item>
      
    
      
        <item>
          <title>Using HTTPS with Kestrel in .NET Core</title>
          <description>&lt;p&gt;During 2020, I’ve been running a bunch of online workshops about building distributed systems with .NET Core. Over the course of the day, we build a REST API, hook it up to some microservice-style components using RabbitMQ and EasyNetQ, use gRPC to connect to a pricing engine, and then send the whole lot back into your web browser in real time using SignalR.&lt;/p&gt;

&lt;p&gt;As it’s a hands-on workshop where attendees are writing their own clients and sending requests to my demo code, I need to run a real, public HTTP server as part of this - and because we’re hacking code as we go, I’m actually mapping an incoming port to my workstation here, which makes things a little interesting. If you’re running Kestrel in production, you’d normally stick it behind something like nginx, and terminate any HTTPS/SSL connections at the load balancer - but here I’m running it as a standalone .NET app, so I needed a way to use a real, properly signed SSL certificate for a local standalone Kestrel instance.&lt;/p&gt;

&lt;p&gt;Here’s how I set it all up.&lt;/p&gt;

&lt;p&gt;I’m using a free certificate from &lt;a href=&quot;https://zerossl.com/&quot;&gt;ZeroSSL&lt;/a&gt; - I’m hosting my example code here on workshop.ursatile.com, so I signed up with ZeroSSL and registered a free 90-day certificate for that hostname. When you click on “Install” here, you get the option to choose a Server Type - but there’s no mention of .NET Core, Kestrel or IIS in that list, so I went for “Default Format”:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-11-18-using-https-with-kestrel/image-20201118163816449.png&quot; alt=&quot;image-20201118163816449&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This will download a ZIP file containing three files - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;certificate.crt&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ca_bundle.crt&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;private.key&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That’s the certificate itself, the chain of certificates used to verify it, and the RSA private key that was used to sign the certificate. In order to use the cert with Kestrel, though, we need to convert it from a CRT format into something called a PFX file.&lt;/p&gt;

&lt;p&gt;To convert it, I’m using OpenSSL and following the instructions &lt;a href=&quot;https://www.ssl.com/how-to/create-a-pfx-p12-certificate-file-using-openssl/&quot;&gt;documented here&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;D:\workshop.ursatile.com&amp;gt;openssl pkcs12 -export -out certificate.pfx -inkey private.key -in certificate.crt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It’ll prompt you for an export password - if you’re using this for anything even remotely sensitive, it’s a good idea to specify a secure password here.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;If you leave the export password blank, anybody who gets hold of the .PFX file can impersonate you&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Do not check your PFX file into Github or publish it online anywhere&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Remember - there’s no point securing your PFX file with a secure password if you then hardcode that password into your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Program.cs&lt;/code&gt; and check it into Github along with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.pfx&lt;/code&gt; file itself.&lt;/p&gt;

&lt;p&gt;Once you’ve got your certificate exported as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;certificate.pfx&lt;/code&gt;, we need to tell the Kestrel web server to use it.&lt;/p&gt;

&lt;p&gt;If you’ve created your ASP.NET Core web app the usual way, you’ll find a method in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Program.cs&lt;/code&gt; called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CreateHostBuilder&lt;/code&gt; - this is where we can specify the options passed to the Kestrel server when it’s started up. We’ll need to add a call to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;webBuilder.ConfigureKestrel()&lt;/code&gt; method like this:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IHostBuilder&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CreateHostBuilder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateDefaultBuilder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ConfigureWebHostDefaults&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;webBuilder&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            
            &lt;span class=&quot;n&quot;&gt;webBuilder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ConfigureKestrel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;               
                &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;port&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;5001&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pfxFilePath&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;@&quot;D:\workshop.ursatile.com\certificate.pfx&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;// The password you specified when exporting the PFX file using OpenSSL.&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;// This would normally be stored in configuration or an environment variable;&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;// I&apos;ve hard-coded it here just to make it easier to see what&apos;s going on.&lt;/span&gt;
                &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pfxPassword&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;green cairo angle piano&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; 

                &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Listen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IPAddress&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;listenOptions&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;c1&quot;&gt;// Enable support for HTTP1 and HTTP2 (required if you want to host gRPC endpoints)&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;listenOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Protocols&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HttpProtocols&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Http1AndHttp2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                    &lt;span class=&quot;c1&quot;&gt;// Configure Kestrel to use a certificate from a local .PFX file for hosting HTTPS&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;listenOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;UseHttps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pfxFilePath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pfxPassword&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

            &lt;span class=&quot;n&quot;&gt;webBuilder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UseStartup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Startup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To check it’s all working, fire it up with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotnet run&lt;/code&gt; and then point a browser at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:5001&lt;/code&gt; - you should see an error message just like this one:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-11-18-using-https-with-kestrel/image-20201118171317975.png&quot; alt=&quot;Screenshot of a browser privacy error message&quot; /&gt;&lt;/p&gt;

&lt;p&gt;but if you click “Advanced” and read the small print:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-11-18-using-https-with-kestrel/image-20201118171411000.png&quot; alt=&quot;image-20201118171411000&quot; /&gt;&lt;/p&gt;

&lt;p&gt;That’s actually exactly what we want. To run code locally using your new certificate, hack your hosts file and add a line to the end pointing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;workshop.ursatile.com&lt;/code&gt; or whatever domain you’re using at 127.0.0.1; if you want the rest of the world to play along too, you’ll need to register a DNS CNAME record pointing at your IP address and make sure that port 5001 (or whatever you used) is mapped to your workstation - oh, and remember to to disable the Windows firewall for that port.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-11-18-using-https-with-kestrel/image-20201118171710454.png&quot; alt=&quot;image-20201118171710454&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But… is it safe?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Well, like most things in IT - it depends. If you do this, you’re effectively running a public web server in your house - and, yes, it’s definitely better doing this with a proper HTTPS certificate than just leaving it wide open. But it’s still not a great idea in terms of security.&lt;/p&gt;

&lt;p&gt;When I do this for workshops and training, I open the ports in the morning, run it while I’m actually working with the group, then at the end of the day I shut the whole thing down and disable the port mappings - so if anybody did try to do anything malicious with it, I’d probably notice straight away, especially since I’m normally watching every single HTTP request because I’m curious to see how my workshop attendees are getting on. So as a teaching tool, I find it incredibly useful - but I’d never run an actual website from a development machine like this.&lt;/p&gt;

</description>
          <pubDate>2020-11-18T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2020/11/18/using-https-with-kestrel.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2020/11/18/using-https-with-kestrel.html</guid>
        </item>
      
    
      
        <item>
          <title>Whatever Happened to &quot;Don&apos;t Be Evil&quot;?</title>
          <description>&lt;p&gt;How to make software be Not Evil:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Be transparent - show your user exactly what you’re doing.&lt;/li&gt;
  &lt;li&gt;Don’t use productivity tools as a marketing channel for your other business activities.&lt;/li&gt;
  &lt;li&gt;Don’t hijack your users’ day-to-day business activities to promote your own agenda.&lt;/li&gt;
  &lt;li&gt;Don’t email things on behalf of your users that they haven’t seen first.&lt;/li&gt;
  &lt;li&gt;Don’t do things that could make your users look foolish in front of customers and colleagues.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not that hard, right? Like, that’s all stuff you’d have to do on purpose, by design - we’re not talking about bugs here, we’re talking about don’t deliberately put things into your software that will upset your users.&lt;/p&gt;

&lt;p&gt;Well, I found a feature in Google Calendar today that scores zero out of five on this scale. If you create a new appointment in GCal, add a  guest, and click Save, it will:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Add a Google Meet meeting URL to your appointment. Unprompted.&lt;/li&gt;
  &lt;li&gt;Do this in the background, in the shaded area behind a modal dialog box&lt;/li&gt;
  &lt;li&gt;Include this URL in the invitations that’ll get sent to your meeting attendees.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Go on. Look.&lt;/p&gt;

&lt;iframe id=&quot;youtube&quot; name=&quot;youtube&quot; src=&quot;https://www.youtube.com/embed/y88e_e9DhMA&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;&lt;em&gt;(yes, I know the video is on YouTube. Yes, I know YouTube is owned by Google. No, I don’t believe for one second that Google is going to change anything because I wrote a blog post. But maybe reading this might inspire one of you to Not Be Evil when you’re doing your own stuff, and perhaps that’s enough.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You want to have a meeting on Webex or Zoom or Jitsi? Go for it. But Google’s gonna add a Google Meet URL as well.&lt;/p&gt;

&lt;p&gt;Of course you can switch this off. There’s a settings flag for it. But this “feature” was rolled out at some point this year, with users opted-in by default - I certainly never switched it on, because I don’t use Google Meet and I positively do not want a second URL added to the invitations to all my online meetings and calls. I generally don’t like software doing things I haven’t asked for, and I &lt;em&gt;really&lt;/em&gt; don’t like software doing things I haven’t asked for and them sending it in an email straight away without giving me a chance to see it first.&lt;/p&gt;

&lt;p&gt;But hey. “&lt;a href=&quot;https://en.wikipedia.org/wiki/Don%27t_be_evil&quot;&gt;Don’t Be Evil&lt;/a&gt;” was a long time ago. I guess things have moved on a bit since then.&lt;/p&gt;

</description>
          <pubDate>2020-09-01T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2020/09/01/what-happened-to-dont-be-evil.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2020/09/01/what-happened-to-dont-be-evil.html</guid>
        </item>
      
    
      
        <item>
          <title>Where&apos;s the YouTube Video?</title>
          <description>&lt;p&gt;I’ve spoken at a few meetups recently where people have asked later where the YouTube video is. Well, folks, here’s the deal. I’m asking
for the time being that community events and meetups don’t publish recorded videos of talks that I’m giving remotely - and here’s why.&lt;/p&gt;

&lt;p&gt;First: I want people to actually attend these events, live - and so do the organisers. We want an audience, we want questions, we want people 
hanging around afterwards for a drink and a Zoom chat. The social contact is part of the experience. You don’t get that watching a video afterwards.&lt;/p&gt;

&lt;p&gt;Second: as much as I’m enjoying being part of these online events, I’ll be the first to admit that they are very different experience to
speaking at a live, in-person event. Even if it’s the same talk; the venue, audience, camera angles, all that comes through in a recorded video. I’ve always been happy having a camera in the room when I’m speaking and sharing the video afterwards, because it’s obvious that watching the video afterwards is like watching sports on TV. It’s &lt;strong&gt;obvious&lt;/strong&gt; that it’s a recording of a &lt;strong&gt;real thing that actually happened&lt;/strong&gt;, and that the folks who were in the room at the time had a very different experience to the one you’re having watching it on YouTube - and, let’s face it, you’re probably watching it at 1.5x normal speed on your iPad while you’re doing emails or writing some JavaScript. &lt;em&gt;Definitely&lt;/em&gt; not the same experience.&lt;/p&gt;

&lt;p&gt;But with online events, the boundary between “live” and “recorded” gets a bit.. fuzzy. When I speak at remote events, it’s always me, in the same room, doing the same talk, to the same camera. Sure, I change things around to suit the event, wear a different shirt, add slides, take questions from the audience, those kind of things - but fundamentally, it’s the same thing. And I don’t see a huge amount of value in having five different versions of the same talk on five different YouTube channels when all of them are just me doing the same talk to the same camera in the same room. Audience Q&amp;amp;A and live chat don’t come through terribly well on these recordings, either.&lt;/p&gt;

&lt;p&gt;Finally: if I’m going to stand in my home studio for an hour, mic’ed up, lights on, talking to a high-definition camera, it seems a little silly 
that the recording of that which ends up getting published is an unedited single take recorded over the wrong end of a YouTube stream, y’know?&lt;/p&gt;

&lt;p&gt;If you’re paying me to speak? Different story. You give me money, you get to make the rules. That’s different. We’re all trying to figure out a 
whole new revenue model for events and conferences right now, and if you have an audience who want to pay for my talks, I’m more than happy to work with you on producing something that works for everybody.&lt;/p&gt;

&lt;p&gt;But for free meetups and community events - I’ll join you live, I’ll give you the best talk I possibly can, I’ll stick around and answer questions 
and chat… but after that, it’s over. Missed it? That’s OK. There will be another one - and hey, it’s not like the travel will be a problem. 😉&lt;/p&gt;

</description>
          <pubDate>2020-08-26T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2020/08/26/wheres-the-youtube-video.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2020/08/26/wheres-the-youtube-video.html</guid>
        </item>
      
    
      
        <item>
          <title>How I Write Timed Talks</title>
          <description>&lt;p&gt;This is one of those blog posts that started life as a tweet, and then a tweet thread, and quickly grew to a point where it actually makes more sense as a post. It started out with this:&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Folks who give lots of talks, and create new *timed* talks from scratch, do you follow a specific process/use a template etc? &lt;br /&gt;&lt;br /&gt;Trying to learn how to get better at this. 🙃&lt;/p&gt;&amp;mdash; C:\hristina 👩🏽‍💻 (@divinetechygirl) &lt;a href=&quot;https://twitter.com/divinetechygirl/status/1281260967423283200?ref_src=twsrc%5Etfw&quot;&gt;July 9, 2020&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;So, here’s how I do it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First: figure out how fast you talk.&lt;/strong&gt; In my case, I took some videos of a couple of talks I’ve given that went well, and transcribed them - literally word for word, number for number. Every single word. Then I measured the word and character counts compared to the length of the videos. I talk at almost exactly 1,000 characters per minute - which works out around 178 words per minute, give or take.&lt;/p&gt;

&lt;p&gt;That’s useful, because it means I can easily turn a time limit into a word limit. A 5-minute lightning talk needs about 5,000 characters worth of material - just under 1,000 words. A one-hour keynote talk needs roughly 60,000 characters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second: learn how to write the way you speak.&lt;/strong&gt; This can take a while, but it’s a really useful skill to develop. When I write talks, I write them exactly the way I talk when I’m talking - jokes, contractions, I spell out numbers long form (262,144 is only six characters on a word count, but “two hundred and sixty two thousand, one hundred and forty four” takes about four seconds to say out loud).&lt;/p&gt;

&lt;p&gt;I’m not writing a script - what I actually say on the day probably correlates about 50% with what I wrote - but they include all the important bits. The key statistics and figures, the important points (and how I want to phrase them), the punchlines to the jokes. Think of it like the score for a jazz standard - the intro, the melody, the refrain and the ending are all pretty clearly mapped out, but they leave structured spaces for improvisation.&lt;/p&gt;

&lt;p&gt;Sometimes, at this point, I just start writing. Not necessarily at the beginning - I’ll often start writing in the middle, I’ll put in placeholders and headings, I’ll move things around. If I know I’m using prerecorded video clips during the talk, I’ll check the durations of these and adjust the word limit accordingly. Other times, I’ll map out the talk into chunks:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Intro: 5 minutes (5,000 characters / 950 words)&lt;/li&gt;
  &lt;li&gt;History of radio, signal processing: 15 minutes (15,000 characters / 2,880 words)&lt;/li&gt;
  &lt;li&gt;Digital cameras &amp;amp; JPEG compression: 15 minutes (15,000 characters / 2,880 words)&lt;/li&gt;
  &lt;li&gt;Unicode, text encoding: 10 minutes (10,000 characters /  1,920 words)&lt;/li&gt;
  &lt;li&gt;Conclusions and wrap up: 5 minutes (5,000 characters / 950 words)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ll keep writing and editing and tweaking until I have something that’s about the right length. Then I’ll record myself &lt;strong&gt;reading&lt;/strong&gt; it out loud. This is normally where a bunch of things jump out that sounded good on paper… so I’ll tweak and edit some more.&lt;/p&gt;

&lt;p&gt;Then I’ll go through the whole thing and identify the points where I need a slide. Sometimes I’ll talk for 2-3 minutes around a single image; sometimes I’ll literally have a slide or animation for each word in a sentence. Sometimes a line or two becomes the storyboard for a piece of animation I’ll create.&lt;/p&gt;

&lt;p&gt;Then I’ll fire up an empty Powerpoint presentation, and for each chunk of prose that accompanies a slide, I’ll paste that chunk of text onto the Notes area on the slide and put in a one-line placeholder (“PICTURE OF ELEPHANT HERE”, “ANIMATION ABOUT 16QAM HERE”) as the slide heading. That’ll let me start using Powerpoint’s outline view to navigate around. Finally, I’ll go through each slide and add the relevant images, text, titles, video clips - whatever’s needed to support that part of the talk.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-07-09-how-i-write-timed-talks/image-20200709180850917.png&quot; alt=&quot;image-20200709180850917&quot; /&gt;&lt;/p&gt;

&lt;p&gt;For each slide, I’ll also add a few bullet points in the speaker notes summarizing what I need to say - and what I need to &lt;strong&gt;end&lt;/strong&gt; with for the transition to the next slide to make sense.&lt;/p&gt;

&lt;p&gt;Then I’ll do a “dress rehearsal” - run through the whole thing, check the timings, transitions, animations, make sure my own speaker notes make sense - and I’m good to go.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-07-09-how-i-write-timed-talks/Untitled Project.gif&quot; alt=&quot;Untitled Project&quot; /&gt;&lt;/p&gt;

&lt;figcaption&gt;Using &amp;lt;a href=&quot;https://agendadefender.app/&amp;gt;AgendaDefender.app&amp;lt;/a&amp;gt; to keep track of time during a talk&lt;/figcaption&gt;

&lt;p&gt;When I’m actually giving a talk, I’ll use a tool I built called &lt;a href=&quot;https://agendadefender.app&quot;&gt;Agenda Defender&lt;/a&gt; to stay on track with timing - I’ll put in the actual start times for each section of the talk, and Agenda Defender draws live animated progress bars for each section of the talk so I can tell whether I need to speed up a bit, or have time for a bit of audience Q&amp;amp;A, maybe throw in a few jokes or stories that weren’t in the original talk outline.&lt;/p&gt;

&lt;p&gt;That’s pretty much it. A couple of things to add:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The first time I deliver a new talk, I normally don’t refer to the notes at all - it’s all fresh in my mind. But the notes are &lt;em&gt;invaluable&lt;/em&gt; if you’re invited to give the same talk again six months later and need to refresh your memory.&lt;/li&gt;
  &lt;li&gt;Breaking down the talk structure into timed sections makes it almost “modular” - if you need to adapt a 45-minute talk to fit a 30-minute time slot, sometimes there’s literally a chunk you can just cut out and tweak the rest a bit to make it flow across the gap.&lt;/li&gt;
  &lt;li&gt;When I first started out, I used the 2-minutes-per-slide approach - for a 60 minute talk, I need to make 30 slides, then for each slide, come up with 2 minutes of content for it. I worked like that for a year or two and absolutely hated it.&lt;/li&gt;
  &lt;li&gt;A one-hour talk is about 10,000 words. That’s the same length as an undergraduate dissertation at many UK universities. Preparing talks this way is a lot of work - I normally reckon on about 30-50 hours of prep time from the first rough outline to the having the talk “stage ready.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And for the sake of comparison, this blog post is 1,156 words - it’d make a pretty good 5-minute lightning talk with a bit of trimming - and it’s taken me just over 45 minutes to write it. Maybe I should just have replied on Twitter after all… :)&lt;/p&gt;
</description>
          <pubDate>2020-07-09T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2020/07/09/how-to-write-timed-talks.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2020/07/09/how-to-write-timed-talks.html</guid>
        </item>
      
    
      
        <item>
          <title>Workshop: Introduction to Distributed Systems with .NET</title>
          <description>&lt;p&gt;Next &lt;strong&gt;Thursday, July 16th,&lt;/strong&gt; I’m running a workshop with the folks from Fusion Workshops in Birmingham, all about building distributed systems using .NET Core. It’ll be completely online, hands-on, delivered using Zoom and various online tools - plus a lot of live coding, hooking our code into various cloud services and watching things running live over the internet. I’ve run this workshop with a couple of different groups and the online format actually works brilliantly - plus it’s &lt;em&gt;way&lt;/em&gt; more impressive running a distributed application when you’re sending messages a few hundred miles between cities, instead of between two laptops in a classroom.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tickets are on sale now: &lt;a href=&quot;https://www.eventbrite.com/e/distributed-systems-net-with-dylan-beattie-tickets-107811475024&quot;&gt;https://www.eventbrite.com/e/distributed-systems-net-with-dylan-beattie-tickets-107811475024&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What’s a distributed system?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Good question. Fundamentally, it’s a software system where you have multiple components that communicate across a network. After that, it can get very complicated, very quickly… before long you’re reading about microservices and REST APIs, GraphQL, gRPC, message queues, pub-sub, “backends for frontends” - there’s a &lt;em&gt;lot&lt;/em&gt; of good ideas, and some very powerful patterns out there, but it can all be a bit overwhelming if you’re not sure where to start.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What’s in the workshop?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is very much a hands-on workshop - lots of code, lots of demos, and by the end of the day you’ll have working examples on your own PC of most of the things we’re looking at. Using a (very simplified!) used car sales platform as our example, we’ll kick off by building a simple REST API (and yes, it will &lt;em&gt;actually&lt;/em&gt; be RESTful - with HATEOAS and everything!) that dealers can use to add new cars to our platform. Then we’ll plug that into a set of message queues, using EasyNetQ, and build some microservices that receive messages from those queues. We’ll plug in some gRPC endpoints to handle price calculation, and finally hook the whole lot into a SignalR hub to display live notifications in a web browser.&lt;/p&gt;

&lt;p&gt;Along the way, we’ll discuss the pros and cons of each of these models, look at why and when you’d used them in production, and where you can go to find out more about running them on production systems.&lt;/p&gt;

&lt;p&gt;The best part is that, because we’re all remote, the code we write during the day will be an actual real-world distributed system - we’ll be bouncing messages and HTTP requests around the country, triggering workflows on each other’s laptops, seeing messages from other participants being filtered through our own code, and get a chance to see how things like latency and bandwidth affect the reliability and performance of real-world distributed systems.&lt;/p&gt;

&lt;p&gt;You’re not going to learn everything in a day. We’re not going to talk about security, monitoring, logging, deployment… the idea of this course is to give you an overview of how everything fits together, and a solid foundation to go away and learn more (or take another course!) before putting these ideas into production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who’s it aimed at?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Developers who have done at least some .NET, who want to understand more about the different patterns and architectural styles used to create distributed applications, and the frameworks that exist in .NET Core to help you build those kinds of applications.&lt;/p&gt;

&lt;p&gt;The course assumes some familiarity with .NET - if you’ve built websites with ASP.NET MVC or done any kind of .NET development, you should be fine.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-07-07-distributed-systems-workshop/summary.png&quot; alt=&quot;image-20200707170109366&quot; /&gt;&lt;/p&gt;
</description>
          <pubDate>2020-07-07T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2020/07/07/distributed-systems-workshop.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2020/07/07/distributed-systems-workshop.html</guid>
        </item>
      
    
      
        <item>
          <title>Using Canon DSLR Cameras as Webcams</title>
          <description>&lt;p&gt;I love Canon DSLR cameras; my main DSLR at the moment is an EOS 700D &lt;em&gt;(which is sold as the Rebel T5i outside the UK, for reasons I will never, ever understand)&lt;/em&gt;, which for me hits the perfect sweet spot between affordability and quality. And, with the whole lockdown thing going on, I’ve been spending a lot of time looking for ways to use gear I already own to produce better live streams and studio recordings.&lt;/p&gt;

&lt;p&gt;If you’ve read &lt;a href=&quot;https://www.hanselman.com/blog/GoodBetterBestCreatingTheUltimateRemoteWorkerWebcamSetupOnABudget.aspx&quot;&gt;Scott Hanselman’s post&lt;/a&gt; from August last year about remote working, you’ll know that the best webcams aren’t actually webcams at all - they’re proper cameras, running through some kind of HDMI &amp;gt; USB converter like the Elgato Camlink. I’ve been trying to pick up one of these for a while, but they’ve been out of stock everywhere since March, so instead I managed to track down an &lt;a href=&quot;https://www.elgato.com/en/gaming/game-capture-hd60-s&quot;&gt;Elgato Game Capture HD60 S&lt;/a&gt;, which does kinda the same thing.&lt;/p&gt;

&lt;p&gt;Well, turns out my beloved Canon EOS 700D doesn’t &lt;em&gt;quite&lt;/em&gt; work as a webcam. Let’s clarify that. Out of the box, it doesn’t work at all - there’s no way to produce what’s called “clean HDMI” - i.e. a video stream with no overlays, menus, focus grids, or anything.&lt;/p&gt;

&lt;p&gt;But, it turns out there’s an open source firmware upgrade for Canon DSLR cameras called &lt;a href=&quot;https://magiclantern.fm/&quot;&gt;Magic Lantern&lt;/a&gt;. Now, this is some seriously gnarly stuff - if you’re not happy downloading binaries from a website and then running them through the firmware upgrade routine on your DSLR camera, then it is probably not for you, and it is absolutely possible to brick your camera by fooling around with this kind of thing. But hey, nobody ever found fortune and glory by following the terms and conditions, right?&lt;/p&gt;

&lt;p&gt;Magic Lantern actually worked really well for me. It allows me to switch off all the overlays and focus grids to get a clean HDMI signal out of the EOS 700D - and with a 50mm f1.8 lens, it looks fantastic. But it’s not perfect. I used it for a live streamed gig last night, and it worked beautifully for about the first 45 minutes… then the shutter cycled (no idea why), and when it came back after about a second, there were some weird letterboxing artefacts around the video frame. Finally, after about 75 minutes of streaming, it stopped autofocusing completely. All temporary - turning it all off and on again got it all back to normal - but not ideal for a live broadcast.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-07-05-canon-dslr-webcams/vlcsnap-2020-07-05-19h28m24s890.png&quot; alt=&quot;vlcsnap-2020-07-05-19h28m24s890&quot; /&gt;&lt;/p&gt;

&lt;figcaption&gt;Working beautifully...&lt;/figcaption&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-07-05-canon-dslr-webcams/vlcsnap-2020-07-05-19h29m02s746.png&quot; alt=&quot;vlcsnap-2020-07-05-19h29m02s746&quot; /&gt;&lt;/p&gt;

&lt;figcaption&gt;...then some weird letterboxing...&lt;/figcaption&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-07-05-canon-dslr-webcams/vlcsnap-2020-07-05-19h29m17s571.png&quot; alt=&quot;vlcsnap-2020-07-05-19h29m17s571&quot; /&gt;&lt;/p&gt;

&lt;figcaption&gt;...and finally the focus grid appeared in shot, and wouldn&apos;t go away. (And then the autofocus stopped working.)&lt;/figcaption&gt;

&lt;p&gt;Of course, having seen just how good it looks when it’s working, I’m now in the market for something that looks that good but doesn’t suffer from the same reliability issues. &lt;strong&gt;Just so we’re perfectly clear: this is absolutely not any kind of problem with the EOS 700D. I’m trying to use it for things it was never, ever designed to do, and the fact it worked at all is pretty remarkable!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So, there’s two options. One is to get a DSLR that is known to produce good clean HDMI for indefinite periods - there’s a &lt;a href=&quot;https://www.elgato.com/en/gaming/cam-link/camera-check&quot;&gt;list of these over on Elgato’s website&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The other is that a few months ago, Canon’s engineers released &lt;a href=&quot;https://www.usa.canon.com/internet/portal/us/home/support/self-help-center/eos-webcam-utility/&quot;&gt;a beta of a USB utility&lt;/a&gt; that would allow you to use certain Canon DSLR and compact cameras as webcams directly, via the camera’s onboard USB interface. This is officially only supported in the United States, which isn’t a big deal - except that, for some ridiculous reason, many Canon DSLR cameras are sold under different names in the UK than they are in the US, and the &lt;a href=&quot;https://www.usa.canon.com/internet/portal/us/home/support/self-help-center/eos-webcam-utility/&quot;&gt;list of supported models&lt;/a&gt; refers only to the US model names.&lt;/p&gt;

&lt;p&gt;So I’ve spent a couple of hours pulling together the list of supported cameras, adding the UK model names, cross-referencing it with the &lt;a href=&quot;https://www.elgato.com/en/gaming/cam-link/camera-check&quot;&gt;Elgato camera checklist&lt;/a&gt;, and, just for good measure, adding in the Canon recommended retail price and a link to Amazon for those cameras that are currently available.&lt;/p&gt;

&lt;p&gt;Here’s what I found:&lt;/p&gt;

&lt;table class=&quot;grid&quot;&gt;
  &lt;tr&gt;
    &lt;th&gt;Camera Model (US)&lt;/th&gt;
    &lt;th&gt;Camera Type&lt;/th&gt;
    &lt;th&gt;UK Model Name&lt;/th&gt;
    &lt;th&gt;RRP &lt;/th&gt;
    &lt;th&gt;Amazon&lt;/th&gt;
    &lt;th&gt;&lt;a href=&quot;https://www.elgato.com/en/gaming/cam-link/camera-check&quot;&gt;Elgato CamLink / clean HDMI?&lt;/a&gt;&lt;/th&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;a href=&quot;https://www.usa.canon.com/internet/portal/us/home/support/details/cameras/eos-dslr-and-mirrorless-cameras/dslr/eos-1d-x-mark-ii&quot;&gt;EOS-1D
        X Mark II&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;DSLR&lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;https://www.canon.co.uk/for_home/product_finder/cameras/digital_slr/eos_1dx_mark_ii/&quot;&gt;same as US&lt;/a&gt;
    &lt;/td&gt;
    &lt;td&gt;£5,429&lt;/td&gt;
    &lt;td&gt;&lt;/td&gt;
    &lt;td&gt;Yes&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;a href=&quot;https://www.usa.canon.com/internet/portal/us/home/support/details/cameras/eos-dslr-and-mirrorless-cameras/dslr/eos-1d-x-mark-iii-body/eos-1d-x-mark-iii-body-only&quot;&gt;EOS-1D
        X Mark III&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;DSLR&lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;https://www.canon.co.uk/cameras/eos-1d-x-mark-iii/&quot;&gt;same as US&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;£6,499&lt;/td&gt;
    &lt;td&gt;&lt;/td&gt;
    &lt;td&gt;Yes&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;a href=&quot;https://www.usa.canon.com/internet/portal/us/home/support/details/cameras/eos-dslr-and-mirrorless-cameras/dslr/eos-5d-mark-iv&quot;&gt;EOS
        5D Mark IV&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;DSLR&lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;https://store.canon.co.uk/canon-eos-5d-mark-iv-body/1483C026/&quot;&gt;same as US&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;£2,799&lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;https://www.amazon.co.uk/Canon-EOS-5D-Mark-IV/dp/B01LVZBXRP/ref=sr_1_10?dchild=1&amp;amp;keywords=eos+1dx&amp;amp;qid=1593969390&amp;amp;sr=8-10&quot;&gt;£2,799&lt;/a&gt;
    &lt;/td&gt;
    &lt;td&gt;Yes&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;a href=&quot;https://www.usa.canon.com/internet/portal/us/home/support/details/cameras/eos-dslr-and-mirrorless-cameras/dslr/eos-5ds&quot;&gt;EOS
        5DS&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;DSLR&lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;https://store.canon.co.uk/canon-eos-5ds-body/0581C008/&quot;&gt;same as US&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;£2,499&lt;/td&gt;
    &lt;td&gt;&lt;/td&gt;
    &lt;td&gt;Not listed&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;a href=&quot;https://www.usa.canon.com/internet/portal/us/home/support/details/cameras/eos-dslr-and-mirrorless-cameras/dslr/eos-5ds-r&quot;&gt;EOS
        5DS R&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;DSLR&lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;https://store.canon.co.uk/canon-eos-5ds-r-body/0582C014/&quot;&gt;same as US&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;£2,699&lt;/td&gt;
    &lt;td&gt;&lt;/td&gt;
    &lt;td&gt;Not listed&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;a href=&quot;https://www.usa.canon.com/internet/portal/us/home/support/details/cameras/eos-dslr-and-mirrorless-cameras/dslr/eos-6d-mark-ii&quot;&gt;EOS
        6D Mark II&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;DSLR&lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;https://store.canon.co.uk/canon-eos-6d-mark-ii-body/1897C027/&quot;&gt;same as US&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;£1,349&lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;https://www.amazon.co.uk/Canon-EOS-6D-Digital-Camera/dp/B00BHXMO3A/ref=sr_1_5?dchild=1&amp;amp;keywords=eos+1dx+mark+ii&amp;amp;qid=1593969340&amp;amp;sr=8-5&quot;&gt;£1,349&lt;/a&gt;
    &lt;/td&gt;
    &lt;td&gt;No&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;a href=&quot;https://www.usa.canon.com/internet/portal/us/home/support/details/cameras/eos-dslr-and-mirrorless-cameras/dslr/eos-7d-mark-ii&quot;&gt;EOS
        7D Mark II&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;DSLR&lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;https://store.canon.co.uk/canon-eos-7d-mark-ii-body-w-e1-wi-fi-adapter/9128B156/&quot;&gt;same as US&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;£1,429&lt;/td&gt;
    &lt;td&gt;&lt;/td&gt;
    &lt;td&gt;Yes (manual focus only)&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;a href=&quot;https://www.usa.canon.com/internet/portal/us/home/support/details/cameras/eos-dslr-and-mirrorless-cameras/dslr/eos-77d/eos-77d&quot;&gt;EOS
        77D&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;DSLR&lt;/td&gt;
    &lt;td&gt;same as US&lt;/td&gt;
    &lt;td&gt;discontinued&lt;/td&gt;
    &lt;td&gt;&lt;/td&gt;
    &lt;td&gt;Yes (manual focus only)&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;a href=&quot;https://www.usa.canon.com/internet/portal/us/home/support/details/cameras/eos-dslr-and-mirrorless-cameras/dslr/eos-80d&quot;&gt;EOS
        80D&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;DSLR&lt;/td&gt;
    &lt;td&gt;same as US&lt;/td&gt;
    &lt;td&gt;discontinued&lt;/td&gt;
    &lt;td&gt;&lt;/td&gt;
    &lt;td&gt;Yes (manual focus only)&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;a href=&quot;https://www.usa.canon.com/internet/portal/us/home/support/details/cameras/eos-dslr-and-mirrorless-cameras/dslr/eos-90d-body/eos-90d&quot;&gt;EOS
        90D&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;DSLR&lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;https://store.canon.co.uk/canon-eos-90d-body/3616C025/&quot;&gt;EOS 90D&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;£1,209&lt;/td&gt;
    &lt;td&gt;&lt;/td&gt;
    &lt;td&gt;Yes&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;a href=&quot;https://www.usa.canon.com/internet/portal/us/home/support/details/cameras/eos-dslr-and-mirrorless-cameras/dslr/eos-rebel-sl2/eos-rebel-sl2&quot;&gt;EOS
        Rebel SL2&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;DSLR&lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;https://store.canon.co.uk/canon-eos-200d-body-black/2250C016/&quot;&gt;EOS 200D&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;£369.99&lt;/td&gt;
    &lt;td&gt;&lt;/td&gt;
    &lt;td&gt;Yes (manual focus only)&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;a href=&quot;https://www.usa.canon.com/internet/portal/us/home/support/details/cameras/eos-dslr-and-mirrorless-cameras/dslr/eos-rebel-sl3-body/eos-rebel-sl3&quot;&gt;EOS
        Rebel SL3&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;DSLR&lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;https://store.canon.co.uk/canon-eos-250d-body-black/3454C004/&quot;&gt;EOS 250D&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;£529.99&lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;https://www.amazon.co.uk/EOS-250D-DSLR-Camera-18-55mm/dp/B0856BFWRV/ref=sr_1_6?dchild=1&amp;amp;keywords=eos+250D&amp;amp;qid=1593969650&amp;amp;s=photo&amp;amp;sr=1-6&quot;&gt;£539.00&lt;/a&gt;
    &lt;/td&gt;
    &lt;td&gt;Yes&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;a href=&quot;https://www.usa.canon.com/internet/portal/us/home/support/details/cameras/eos-dslr-and-mirrorless-cameras/dslr/eos-rebel-t6-body/eos-rebel-t6-body&quot;&gt;EOS
        Rebel T6&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;DSLR&lt;/td&gt;
    &lt;td&gt;EOS 1300D&lt;/td&gt;
    &lt;td&gt;discontinued&lt;/td&gt;
    &lt;td&gt;&lt;/td&gt;
    &lt;td&gt;not listed&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;a href=&quot;https://www.usa.canon.com/internet/portal/us/home/support/details/cameras/eos-dslr-and-mirrorless-cameras/dslr/eos-rebel-t6i/eos-rebel-t6i&quot;&gt;EOS
        Rebel T6i&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;DSLR&lt;/td&gt;
    &lt;td&gt;EOS 750D&lt;/td&gt;
    &lt;td&gt;unknown&lt;/td&gt;
    &lt;td&gt;&lt;/td&gt;
    &lt;td&gt;Yes (community verified)&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;a href=&quot;https://www.usa.canon.com/internet/portal/us/home/support/details/cameras/eos-dslr-and-mirrorless-cameras/dslr/eos-rebel-t7-body/eos-rebel-t7-body&quot;&gt;EOS
        Rebel T7&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;DSLR&lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;https://store.canon.co.uk/canon-eos-2000d-body/2728C004/&quot;&gt;EOS 2000D&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;£329.99&lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;https://www.amazon.co.uk/Canon-2000D-DSLR-Camera-Body/dp/B07B12XRBL/ref=sr_1_5?dchild=1&amp;amp;keywords=eos+200D&amp;amp;qid=1593969525&amp;amp;s=photo&amp;amp;sr=1-5&quot;&gt;£299.00&lt;/a&gt;
    &lt;/td&gt;
    &lt;td&gt;not listed&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;a href=&quot;https://www.usa.canon.com/internet/portal/us/home/support/details/cameras/eos-dslr-and-mirrorless-cameras/dslr/eos-rebel-t7i-body/eos-rebel-t7i&quot;&gt;EOS
        Rebel T7i&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;DSLR&lt;/td&gt;
    &lt;td&gt;EOS 800D&lt;/td&gt;
    &lt;td&gt;£739.99&lt;/td&gt;
    &lt;td&gt;&lt;/td&gt;
    &lt;td&gt;Yes&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;a href=&quot;https://www.usa.canon.com/internet/portal/us/home/support/details/cameras/eos-dslr-and-mirrorless-cameras/dslr/t100-body/t100-body&quot;&gt;EOS
        Rebel T100&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;DSLR&lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;https://store.canon.co.uk/canon-eos-4000d-body/3011C007/&quot;&gt;EOS 4000D&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;£279.99&lt;/td&gt;
    &lt;td&gt;&lt;/td&gt;
    &lt;td&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;a href=&quot;https://www.usa.canon.com/internet/portal/us/home/support/details/cameras/eos-dslr-and-mirrorless-cameras/mirrorless/eos-m6-mark-ii/eos-m6-mark-ii&quot;&gt;EOS
        M6 Mark II&amp;nbsp;&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;Mirrorless&lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;https://store.canon.co.uk/canon-eos-m6-mark-ii-body/3611C050/&quot;&gt;same&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;£869.99&lt;/td&gt;
    &lt;td&gt;&lt;/td&gt;
    &lt;td&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;a href=&quot;https://www.usa.canon.com/internet/portal/us/home/support/details/cameras/eos-dslr-and-mirrorless-cameras/mirrorless/eos-m50-body/eos-m50-body&quot;&gt;EOS
        M50&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;Mirrorless&lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;https://store.canon.co.uk/canon-eos-m50-body-white/2681C051/&quot;&gt;same&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;£539.99&lt;/td&gt;
    &lt;td&gt;&lt;/td&gt;
    &lt;td&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;a href=&quot;https://www.usa.canon.com/internet/portal/us/home/support/details/cameras/eos-dslr-and-mirrorless-cameras/mirrorless/eos-m200-ef-m-15-45mm-is-stm-kit/eos-m200-ef-m-15-45mm-is-stm-kit&quot;&gt;EOS
        M200&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;Mirrorless&lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;https://store.canon.co.uk/canon-eos-m200-body-black-ef-m-15-45mm-lens/3699C028/&quot;&gt;EOS M200&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;£549.99&lt;/td&gt;
    &lt;td&gt;&lt;/td&gt;
    &lt;td&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;a href=&quot;https://www.usa.canon.com/internet/portal/us/home/support/details/cameras/eos-dslr-and-mirrorless-cameras/mirrorless/eos-r/eos-r&quot;&gt;EOS
        R&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;Mirrorless&lt;/td&gt;
    &lt;td&gt;EOS R&lt;/td&gt;
    &lt;td&gt;£1,879.99&lt;/td&gt;
    &lt;td&gt;&lt;/td&gt;
    &lt;td&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;a href=&quot;https://www.usa.canon.com/internet/portal/us/home/support/details/cameras/eos-dslr-and-mirrorless-cameras/mirrorless/eos-rp/eos-rp&quot;&gt;EOS
        RP&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;Mirrorless&lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;https://store.canon.co.uk/canon-eos-rp-body-mount-adapter-ef-eos-r/3380C040/&quot;&gt;EOS RP&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;£1,219.99&lt;/td&gt;
    &lt;td&gt;&lt;/td&gt;
    &lt;td&gt;Yes&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;a href=&quot;https://www.usa.canon.com/internet/portal/us/home/support/details/cameras/point-and-shoot-digital-cameras/advanced-cameras/powershot-g5-x-mark-ii/powershot-g5-x-mark-ii&quot;&gt;PowerShot
        G5X Mark II&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;Compact&lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;https://store.canon.co.uk/canon-powershot-g5-x-mark-ii-compact-camera/3070C011/&quot;&gt;PowerShot G5X Mark
        II&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;£869.99&lt;/td&gt;
    &lt;td&gt;&lt;/td&gt;
    &lt;td&gt;Yes&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;a href=&quot;https://www.usa.canon.com/internet/portal/us/home/support/details/cameras/point-and-shoot-digital-cameras/advanced-cameras/powershot-g7-x-mark-iii/powershot-g7-x-mark-iii&quot;&gt;PowerShot
        G7X Mark III&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;Compact&lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;https://store.canon.co.uk/canon-powershot-g7-x-mark-iii-compact-camera-black/3637C011/&quot;&gt;PowerShot G7X
        Mark III&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;£719.99&lt;/td&gt;
    &lt;td&gt;&lt;/td&gt;
    &lt;td&gt;Yes&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;a href=&quot;https://www.usa.canon.com/internet/portal/us/home/support/details/cameras/point-and-shoot-digital-cameras/long-zoom-cameras/powershot-sx70-hs/powershot-sx70-hs&quot;&gt;PowerShot
        SX70 HS&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;Compact&lt;/td&gt;
    &lt;td&gt;&lt;a href=&quot;https://store.canon.co.uk/canon-powershot-sx70-hs/3071C011/&quot;&gt;PowerShot SX70 HS&lt;/a&gt;&lt;/td&gt;
    &lt;td&gt;£539.99&lt;/td&gt;
    &lt;td&gt;&lt;/td&gt;
    &lt;td&gt;&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;I was hoping to find a new camera body for around £300 that will work with my existing lenses, supports the new Canon EOS Utility software, and is verified for use with the Elgato HDMI capture devices; there’s no clear winner, but the EOS 2000D and EOS 4000D aren’t listed either way by Elgato so I will do some digging, see what I can find out. I’ll keep you posted. ;)&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Oh, and if anybody has any idea at all why Canon decided to give their cameras different names in the UK and the US, I’d love to know…&lt;/em&gt;&lt;/p&gt;

</description>
          <pubDate>2020-07-05T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2020/07/05/canon-dslr-webcams.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2020/07/05/canon-dslr-webcams.html</guid>
        </item>
      
    
      
        <item>
          <title>Stay In, Rock Out: I&apos;m Playing Live Online!</title>
          <description>&lt;p&gt;On Saturday, pubs in England are allowed to reopen. I love pubs. I love pubs more than you can imagine. But there is no way in a million years I am going anywhere near a pub on a Saturday in the middle of summer, when they’ve all been closed for three months, and they’re only opening now because the government think there’s probably enough hospital beds to deal with the aftermath.&lt;/p&gt;

&lt;p&gt;So instead of going to the pub on Saturday, I’m going to play a live rock show, from my house, and stream the whole thing live online. An hour or so of classic rock covers - AC/DC, Bon Jovi, Queen, Muse, Pink Floyd, Guns n’ Roses, and maybe a few surprises. I’ll be using a whole bunch of technology to put together something a bit special - if it works, it’ll be awesome… if it doesn’t work, it’ll be probably be hilarious. Either way it’d be cool if you want to tune in.&lt;/p&gt;

&lt;p&gt;Here’s the invitation so you can add it to your calendar:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/events/dylan-beattie-stay-in-rock-out.ics&quot;&gt;dylan-beattie-stay-in-rock-out.ics&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It all kicks off at 19:00 UK time this Saturday, July 4th. I’ll be streaming live on &lt;a href=&quot;https://twitch.tv/dylanbeattie&quot;&gt;https://twitch.tv/dylanbeattie&lt;/a&gt;, and on &lt;a href=&quot;https://facebook.com/dylanbeattie&quot;&gt;facebook.com/dylanbeattie&lt;/a&gt; and &lt;a href=&quot;https://youtube.com/dylanbeattie&quot;&gt;youtube.com/dylanbeattie&lt;/a&gt; as well - and then firing up a Zoom for the after-party.&lt;/p&gt;

&lt;p&gt;Get some beers in, crank up the volume, and let’s have some fun. :)&lt;/p&gt;

</description>
          <pubDate>2020-07-02T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2020/07/02/stay-in-rock-out.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2020/07/02/stay-in-rock-out.html</guid>
        </item>
      
    
      
        <item>
          <title>Locking Down &amp; Levelling Up, Part 1: New Computer Time</title>
          <description>&lt;p&gt;One of the nice things about this whole weird lockdown situation is that for the first time in a long, long while, I know exactly what my travel plans are going to be for the next few months: &lt;strong&gt;&lt;em&gt;none&lt;/em&gt;&lt;/strong&gt;. Zip. Zilch. Nada. The likelihood of being asked to throw some things in a bag and jump on a plane tomorrow is… zero. Which kinda sucks, because I love travelling, but, hey – here I am. I’m at home, it looks like I’m gonna be here for a long while, and that means I have time, and incentive, to turn my home office into an ideal workspace.&lt;/p&gt;

&lt;p&gt;Now, I have a bit of a head start here, because I do actually have a dedicated room in my house for “work”. Which means it was already a combination of  office, recording studio, library and very occasional spare room, but when the lockdown hit and so many conferences and events started going online, I really wanted a proper setup for livestreaming and recording video, complete with greenscreens, studio lightning, all that kind of stuff. &lt;em&gt;(Yes, buying hardware is a coping strategy for me. This is OK.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Plus, with everything going online, I kept getting invited to join Slack workspaces. Compiling the .NET Framework from source while running Adobe Premiere and Far Cry simultaneously and livestreaming the whole lot to Twitch in 1080p in real time is one thing, but being in 15 Slack workspaces at the same time requires some &lt;em&gt;serious&lt;/em&gt; processing power…&lt;/p&gt;

&lt;p&gt;It’s been a long while since I’ve had a proper workstation. Like most nerds of a certain age, I spent the 1990s and 2000s constantly tinkering with big PCs, adding RAM, upgrading CPUs and graphics cards - y’all remember 3Dfx and the Voodoo chipset? Some time back in 2015 the graphics card in my last one failed. I just copied everything across to my laptop and used that as my main machine until I got it fixed… and then never got it fixed. So step 1 of this process was to get a proper computer. I wanted to switch back to running Windows 10 as my main day-to-day operating system (and keep macOS on the laptop for music stuff); I wanted something that could drive a &lt;em&gt;stupid&lt;/em&gt; number of external monitors and peripherals, and I wanted something that was blazing fast and whisper quiet.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Did I mention the bit about how buying hardware is a coping strategy? Cool. Glad we’ve cleared that up.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Enter the lovely people at &lt;a href=&quot;https://www.quietpc.com/&quot;&gt;QuietPC&lt;/a&gt;. A few hours poking around their website, and one very, very helpful phone call, and I had my ideal machine spec’ed out. After having a few too many recordings ruined by the sound of the fans on my Macbook firing up halfway through a take, I wanted something completely fanless. I got pretty close: the system I ended up with has a passive-cooled power supply and CPU cooler, I ended up adding a single 140mm BeQuiet case fan, and there’s a fan on the GPU that only starts up when you drive it &lt;em&gt;hard&lt;/em&gt;. Most of the time it’s completely silent.&lt;/p&gt;

&lt;p&gt;Here’s the spec I went with:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Case: &lt;a href=&quot;https://www.quietpc.com/st-da2&quot;&gt;Streacom DA2 Black Full Aluminium Compact ITX Chassis&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;PSU: &lt;a href=&quot;https://www.quietpc.com/sst-nj450-sxl&quot;&gt;Silverstone Nightjar 450W Fanless Modular SFX Power Supply, NJ450-SXL&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;CPU: &lt;a href=&quot;https://www.quietpc.com/coffeelake-i7?product=5909&quot;&gt;9th Gen Core i7 9700 3.0GHz 8C/8T 65W 12MB Coffee Lake CPU&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;RAM: &lt;a href=&quot;https://www.quietpc.com/corsair-vs-sodimm-2400?product=5502&quot;&gt;Vengeance LPX 32GB (2x16GB) DDR4 2666MHz Memory&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Motherboard: &lt;a href=&quot;https://www.quietpc.com/asus-b360-i&quot;&gt;ROG STRIX B360-I GAMING LGA1151 Mini-ITX Motherboard&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;CPU Cooler: &lt;a href=&quot;https://www.quietpc.com/nofan-cr-80eh&quot;&gt;CR-80EH Copper IcePipe 80W Fanless CPU Cooler&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;HDD: 2 x &lt;a href=&quot;https://www.quietpc.com/samsung-m2-970evo-plus&quot;&gt;Samsung 970 EVO PLUS 500GB M.2 NVMe SSD, MZ-V7S500BW&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Graphics card: &lt;a href=&quot;https://www.quietpc.com/asus-dual-gtx1660ti-o6g-evo&quot;&gt;ASUS GeForce GTX 1660 Ti DUAL OC Edition 6GB EVO Graphics Card&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I need to take a moment here to mention how awesome the folks at QuietPC are, because when I first placed the order, I picked a different CPU cooler – and they actually rang me up, explained that the cooler I’d ordered wouldn’t fit in the case I’d ordered, offered an alternative, and sorted the whole thing out. That’s some absolutely first-class customer service. And sure enough, a few days later, a large pile of exciting cardboard boxes arrived.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-28-lockdown-level-up-part-1/IMG_3955.JPG&quot; alt=&quot;IMG_3955&quot; /&gt;&lt;/p&gt;

&lt;figcaption&gt;A large pile of exciting cardboard boxes.&lt;/figcaption&gt;

&lt;p&gt;Building computers is great fun. It’s like the best Lego &lt;em&gt;ever&lt;/em&gt;, and this one was no exception. Getting everything to fit on this system was a tiny, tiny bit fiddly… the &lt;a href=&quot;https://streacom.com/products/da2-chassis/&quot;&gt;Streacom DA2 chassis&lt;/a&gt; is a wonderful piece of engineering, based around an open chassis with mounting rails you can move to wherever you need them, but there’s &lt;em&gt;really&lt;/em&gt; not a whole lot of space inside. It all fit in the end – mainly thanks to the M.2 hard drives, which aren’t really drives at all, they’re SSD chips on their own circuit boards that mount directly onto the motherboard. I also had no idea just how big the fanless CPU cooler would actually be.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-28-lockdown-level-up-part-1/IMG_3977.JPG&quot; alt=&quot;IMG_3977&quot; /&gt;&lt;/p&gt;

&lt;figcaption&gt;The &lt;a href=&quot;https://www.quietpc.com/nofan-cr-80eh&quot;&gt;CR-80EH Copper IcePipe 80W Fanless CPU Cooler&lt;/a&gt; is... not small&lt;/figcaption&gt;

&lt;p&gt;I got it all put together, fired it up, installed Windows 10, everything ran beautifully - and absolutely silent. Like, ZERO MOVING PARTS silent. This worked great until I got Visual Studio and Adobe AfterEffects installed and starting putting it through its paces, at which point it crashed &lt;em&gt;hard&lt;/em&gt;… turns out that a completely fanless PC is ever so slightly prone to overheating.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-28-lockdown-level-up-part-1/IMG_3983.JPG&quot; alt=&quot;IMG_3983&quot; /&gt;&lt;/p&gt;

&lt;figcaption&gt;Oops.&lt;/figcaption&gt;

&lt;p&gt;So I compromised, and ordered &lt;a href=&quot;https://www.bequiet.com/en/casefans/448&quot;&gt;one very big, very quiet fan&lt;/a&gt; – it doesn’t take a whole lot of airflow to keep things cool, and there’s an ASUS utility called Fan Xpert that I used to create a fan profile, so it’ll monitor the CPU temperature and automatically spin up the fan when required.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-28-lockdown-level-up-part-1/image-20200519105650432.png&quot; alt=&quot;image-20200519105650432&quot; /&gt;&lt;/p&gt;

&lt;figcaption&gt;Creating a fan control profile using Asus Fan Xpert&lt;/figcaption&gt;

&lt;p&gt;That seems to have done the trick – it’s been running stable for 6 weeks now; I’ve been using the rather good &lt;a href=&quot;https://openhardwaremonitor.org/&quot;&gt;Open Hardware Monitor&lt;/a&gt; &lt;em&gt;(which, by the way, is &lt;a href=&quot;https://github.com/openhardwaremonitor/openhardwaremonitor&quot;&gt;written in C# and .NET&lt;/a&gt;!)&lt;/em&gt; to keep track of all the various temperatures and things, and haven’t had any more crashes or CPU temperature errors.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-28-lockdown-level-up-part-1/image-20200519112125667.png&quot; alt=&quot;image-20200519112125667&quot; /&gt;&lt;/p&gt;

&lt;figcaption&gt;Temperature readings over a typical 24 hour cycle. The CPU is the blue one.&lt;/figcaption&gt;

&lt;p&gt;In the next post, I’ll talk you through how – and why – I’m using it to drive six monitors, four cameras and four microphones, and how I ended up building a bespoke computer desk to put it all on.&lt;/p&gt;
</description>
          <pubDate>2020-05-28T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2020/05/28/lockdown-level-up-part-1.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2020/05/28/lockdown-level-up-part-1.html</guid>
        </item>
      
    
      
        <item>
          <title>Running Jekyll on WSL2</title>
          <description>&lt;p&gt;Back in August last year, &lt;a href=&quot;/2019/08/14/migrating-from-blogger-to-github-pages.html&quot;&gt;I migrated this site, including 12 years worth of blog posts&lt;/a&gt;, from Blogger to &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt;, primary so that I could host it on &lt;a href=&quot;https://pages.github.com/&quot;&gt;Github Pages&lt;/a&gt;. At the time, my main system was a Macbook Pro running macOS, I was doing a lot of work with Ruby and had a full Ruby dev environment set up, so running Jekyll locally was trivial.&lt;/p&gt;

&lt;p&gt;I’ve recently switched back to Windows as my main OS, and Windows is still very much a second-class citizen in the wonderful world of Ruby web development. It’s a &lt;em&gt;lot&lt;/em&gt; better than it used to be, and almost everything now works out of the box, but because the vast majority of Ruby devs (and sites) are running on macOS or Linux, Windows still isn’t quite as slick, or as fast, when it comes to running tools like Jekyll.&lt;/p&gt;

&lt;p&gt;But this is 2020, and that doesn’t matter, because you can run Linux on Windows! Thanks to the &lt;a href=&quot;https://docs.microsoft.com/en-us/windows/wsl/about&quot;&gt;Windows Subsystem for Linux&lt;/a&gt; (WSL), you can boot a full Linux kernel environment on your Windows PC and run native Linux apps and toolchains. I’ve been running Jekyll under WSL 1 for a long while to run my sites locally, and it works great, but it’s a little slow; spinning up a local copy of this site would take around 30 seconds, compared to 10–12 seconds on my old Macbook Pro.&lt;/p&gt;

&lt;p&gt;I’d heard good things about performance improvements in WSL2, so last night I took the plunge, enabled the &lt;a href=&quot;https://insider.windows.com/en-us/&quot;&gt;Windows Insider Program&lt;/a&gt; on my main PC, and ran a rather hefty Windows Update.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-19-jekyll-on-wsl2/image-20200519114222214.png&quot; alt=&quot;image-20200519114222214&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now, by way of establishing a baseline, here’s running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundle exec jekyll serve&lt;/code&gt; on WSL 1. This is what I’ve been using to run my site locally and test things for the past few months.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-19-jekyll-on-wsl2/image-20200519114816853.png&quot; alt=&quot;image-20200519114816853&quot; /&gt;&lt;/p&gt;

&lt;p&gt;30 seconds to build the site from scratch. Not great, not terrible.&lt;/p&gt;

&lt;p&gt;This is on a the Ubuntu-20.04 LTS Linux kernel that’s available from the Windows Store. Having upgraded Windows 10 to the 2004 build, I could now upgrade this in-situ to use WSL2 instead of WSL1:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;C:\Users\dylan&amp;gt;wsl -l -v
  NAME            STATE           VERSION
* Ubuntu-20.04    Running         1

C:\Users\dylan&amp;gt;wsl --set-version Ubuntu-20.04 2
Conversion in progress, this may take a few minutes...
For information on key differences with WSL 2 please visit https://aka.ms/wsl2
Conversion complete.

C:\Users\dylan&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ooh. Shiny. OK, let’s see what this thing can do:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-19-jekyll-on-wsl2/image-20200519115505666.png&quot; alt=&quot;image-20200519115505666&quot; /&gt;&lt;/p&gt;

&lt;p&gt;155 seconds. Gosh. That’s… not quite what I was expecting, and certainly not something I’d claim was a “performance improvement”. So I did a little digging, and found &lt;a href=&quot;https://scotch.io/bar-talk/trying-the-new-wsl-2-its-fast-windows-subsystem-for-linux&quot;&gt;this article from scotch.io&lt;/a&gt;, which includes a “&lt;a href=&quot;https://scotch.io/bar-talk/trying-the-new-wsl-2-its-fast-windows-subsystem-for-linux#toc-gotchas-with-wsl-2&quot;&gt;Gotchas with WSL 2&lt;/a&gt;” section that specifically says:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;To take advantage of all the new speed improvements in WSL 2, our files will need to be moved into the Linux filesystem.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Until now, I’d just kept everything on my Windows D: drive and used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/mnt/d/&lt;/code&gt; under WSL to run everything. Apparently that’s not a good idea any more… so I did a fresh git clone of my blog code into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/dylan/github/dylanbeattie.net/&lt;/code&gt; under WSL2 and ran it again:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-19-jekyll-on-wsl2/image-20200519120012376.png&quot; alt=&quot;image-20200519120012376&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;TWO POINT ONE TWO NINE SECONDS! HOLY BATFISH CATMAN! THAT’S FAST!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For the sake of comparison, running the same thing natively on Windows, using the Windows distribution of Ruby 2.6, takes about 21 seconds:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-19-jekyll-on-wsl2/image-20200519163007957.png&quot; alt=&quot;image-20200519163007957&quot; /&gt;&lt;/p&gt;

&lt;p&gt;So, running Jekyll on the Linux native filesystem is an &lt;strong&gt;order of magnitude&lt;/strong&gt; faster than the same thing running natively on Windows, and &lt;strong&gt;around two orders of magnitude&lt;/strong&gt; faster than running the same thing on WSL with a mounted Windows filesystem. That’s quite an astonishing difference.&lt;/p&gt;

&lt;p&gt;Of course, this means that all my code now has to reside on the Linux filesystem, so it isn’t quite so easy to get to it from Windows any more. But the Linux filesystem is shared via a network path, so I was able to go:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;C:\Users\dylan&amp;gt;net use u: \\wsl$\Ubuntu-20.04\
C:\Users\dylan&amp;gt;u:
U:\home\dylan&amp;gt;cd github\dylanbeattie.net\
U:\home\dylan\github\dylanbeattie.net&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And having got this far, I couldn’t resist running two more tests just to round the whole thing out. First, the same thing but on a full Ubuntu 20.04 desktop VM running under HyperV:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-19-jekyll-on-wsl2/image-20200519191303146.png&quot; alt=&quot;image-20200519191303146&quot; /&gt;&lt;/p&gt;

&lt;p&gt;That’s 2.45 seconds – marginally &lt;em&gt;slower&lt;/em&gt; than the WSL2 version with the native FS. And finally, just for the hell of it, here’s the Windows native port of Ruby and Jekyll, against the repo checked out onto the Linux native FS, mounted as a network drive in Windows:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-19-jekyll-on-wsl2/image-20200519163357415.png&quot; alt=&quot;image-20200519163357415&quot; /&gt;&lt;/p&gt;

&lt;p&gt;618.84 seconds… just a &lt;em&gt;tiny&lt;/em&gt; bit slower than the WSL2 native Linux result, wouldn’t you agree?&lt;/p&gt;

&lt;p&gt;Here’s how the six different scenarios end up:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/posts/2020-05-19-jekyll-on-wsl2/image-20200519191518846.png&quot;&gt;&lt;img src=&quot;/images/posts/2020-05-19-jekyll-on-wsl2/image-20200519191518846.png&quot; alt=&quot;image-20200519191518846&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;conclusions&quot;&gt;Conclusions:&lt;/h3&gt;

&lt;p&gt;I was genuinely surprised at the orders of magnitude of difference seen here. I expected a factor of maybe 2-3 times – I did not expect the slowest (albeit rather silly) option to be nearly &lt;em&gt;300 times slower&lt;/em&gt; than the fastest. But clearly whatever the WSL team has done in WSL2 has made a substantial difference when it comes to performance – as long as you’re working with a native Linux filesystem.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;If your base operating system is Windows, running WSL2 with the Linux filesystem is actually faster than any of the other options – including running Linux in a full VM.&lt;/li&gt;
  &lt;li&gt;If you &lt;em&gt;need&lt;/em&gt; Windows/Linux interop across the same filesystem, you’re probably better off sticking with WSL1. The Linux kernel in the WSL2 system is significantly slower when dealing with files stored on the Windows filesystem than it was in WSL1.&lt;/li&gt;
  &lt;li&gt;For simple Jekyll sites, using the Windows native versions of Ruby and Jekyll is probably fine.&lt;/li&gt;
  &lt;li&gt;If performance is a big deal, go for WSL2 and clone your repo onto the Linux FS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Usual caveats apply, that one guy running one Jekyll site and writing a blog post about it is &lt;em&gt;not&lt;/em&gt; a rigorous scientific analysis. I did this because I wanted to work out what solution worked best for my specific scenario. Your mileage, as the saying goes, may vary. But it’s definitely worth trying out a few different options and seeing what works best for your particular workload - and hey, when you do, publish the results. I’m curious to see just how good WSL2 is across different scenarios, but early indicators are it’s very, very good indeed.&lt;/p&gt;
</description>
          <pubDate>2020-05-19T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2020/05/19/jekyll-on-wsl2.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2020/05/19/jekyll-on-wsl2.html</guid>
        </item>
      
    
      
        <item>
          <title>Piedroit: A Raspberry Pi-powered USB Footpedal (Part 2)</title>
          <description>&lt;p&gt;In &lt;a href=&quot;/2020/05/17/turning-a-raspberry-pi-zero-into-a-usb-footpedal.html&quot;&gt;part 1 of this post&lt;/a&gt;, I described the software and config required to use a &lt;a href=&quot;https://www.raspberrypi.org/products/raspberry-pi-zero-w/&quot;&gt;Raspberry Pi Zero W&lt;/a&gt; to create a bank of footswitches that the host computer thinks is a USB keyboard. So far, so good: a little Python hacking, some Linux modules, a bit of GPIO and USB HID programming. All nice and friendly and revision-controlled.&lt;/p&gt;

&lt;p&gt;That’s only half the problem, though. The main reason I’m building this thing is that I want a way to control the computer by stamping on things. When I’m playing the guitar and doing live shows, on stage or streaming via Twitch or YouTube, I want to be able to switch shots and control backing tracks by pressing switches with my feet. Now, I’m not &lt;em&gt;exactly&lt;/em&gt; sure what would happen if 102kg of slightly excitable developer stood on a Raspberry Pi Zero W – or a normal computer keyboard, for that matter – but I suspect if it happened regularly, the devices in question might start complaining a bit.&lt;/p&gt;

&lt;p&gt;So having solved the logical elements of this puzzle, let’s figure out the physical bits. How do you connect a Pi Zero to a bank of footswitches that can cope with a big hairy guy pressing them with a pair of size 10 boots?&lt;/p&gt;

&lt;p&gt;Well, as with many problems in life, the answer is… &lt;strong&gt;metal&lt;/strong&gt;. In this case, &lt;a href=&quot;https://www.diy.com/departments/steel-panel-l-0-5m-w-250mm-t-0-6mm/253965_BQ.prd&quot;&gt;0.6mm bright mild steel sheet&lt;/a&gt;, because that’s what I could get hold of. I don’t have access to a proper machine shop or any welding gear (#lifegoals), so I had to come up with a design that I could make using the tools and materials I had available, but which would protect the Pi and other bits of delicate circuitry within.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-18-raspberry-pi-footpedal-part-2/mintice-momentary-button-electric-switch.jpg&quot; alt=&quot;mintice-momentary-button-electric-switch&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The switches I’m using for this project are a brand called Mintice, &lt;a href=&quot;https://www.amazon.co.uk/gp/product/B073S4YVF4&quot;&gt;available from Amazon for £11.79 for a pack of 5&lt;/a&gt;. They’re pretty easy to work with, other than requiring you to drill a 12mm diameter hole… making holes in sheet steel is easy, especially if you have a drill press… I don’t have a drill press. I’m working with a cordless hand drill here, so making sure the holes end up exactly where they’re supposed to be is a bit tricky.&lt;/p&gt;

&lt;h2 id=&quot;the-cardboard-prototype&quot;&gt;The Cardboard Prototype&lt;/h2&gt;

&lt;p&gt;Just like software, you should always plan to &lt;a href=&quot;https://wiki.c2.com/?PlanToThrowOneAway&quot;&gt;build one version to throw away&lt;/a&gt;. Unlike in software, when it comes to metalwork it’s normally &lt;em&gt;really&lt;/em&gt; obvious which one’s the prototype… it’s the one made out of &lt;del&gt;JavaScript&lt;/del&gt; cardboard. The prototype I’m building here is to answer a couple of key questions:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Will all the components actually fit?&lt;/li&gt;
  &lt;li&gt;How much sheet stock will I need, and what shapes will I need to cut?&lt;/li&gt;
  &lt;li&gt;Is there enough space between the switches that I won’t accidentally press two switches at once?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-18-raspberry-pi-footpedal-part-2/IMG_4736.JPEG&quot; alt=&quot;IMG_4736&quot; /&gt;&lt;/p&gt;

&lt;figcaption&gt;Sketching layouts and checking clearance for the components&lt;/figcaption&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-18-raspberry-pi-footpedal-part-2/IMG_4738.JPG&quot; alt=&quot;IMG_4738&quot; /&gt;&lt;/p&gt;

&lt;figcaption&gt;Cardboard prototype for the top part of the housing&lt;/figcaption&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-18-raspberry-pi-footpedal-part-2/IMG_4741.JPG&quot; alt=&quot;IMG_4741&quot; /&gt;&lt;/p&gt;

&lt;figcaption&gt;Testing the footswitches in the cardboard housing mockup&lt;/figcaption&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-18-raspberry-pi-footpedal-part-2/IMG_4751.JPG&quot; alt=&quot;IMG_4751&quot; /&gt;&lt;/p&gt;

&lt;figcaption&gt;The Pi Zero board and the footswitches installed in the prototype housing to check clearance&lt;/figcaption&gt;

&lt;h2 id=&quot;time-to-get-metal&quot;&gt;Time to Get Metal&lt;/h2&gt;

&lt;p&gt;The cardboard prototype worked. The components all fit, nothing’s fouling anything else, and it’s small enough to make from a single sheet of the 250mm x 400mm steel sheet stock I had available, but big enough to install five switches on the unit with enough distance between them that I’m not going to hit two switches at once.&lt;/p&gt;

&lt;p&gt;Now I just need to make the same thing again, except in steel instead of cardboard. That’s not actually as daunting as it sounds. I really enjoy working with steel. Cardboard’s easy to cut and fold, sure, but it also frays and has a tendency to unfold. Steel takes time (and the proper tools) to cut and shape, but once you fold it, it stays folded.&lt;/p&gt;

&lt;p&gt;First step was to cut a piece of steel for the top of the housing, and then drill the five mounting holes for the switches:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-18-raspberry-pi-footpedal-part-2/IMG_4759.JPG&quot; alt=&quot;IMG_4759&quot; /&gt;&lt;/p&gt;

&lt;figcaption&gt;Mounting holes for the foot switches&lt;/figcaption&gt;

&lt;p&gt;Next step was to bend the top part of the housing. There’s dozens of videos on YouTube about how to bend sheet metal – some show you how to use a machine called a metal brake; some show you how to make a metal brake if you don’t have one available, and some show you how to bend metal without a brake. &lt;a href=&quot;https://www.youtube.com/watch?v=KdMtecvnPRI&quot;&gt;This video from Cosador&lt;/a&gt; is great if you want an overview of a few different techniques. I ended up using hand bends and hammered bends for most of my project, using a couple of lengths of cold-rolled steel angle and lots of clamps to hold everything straight. It came out OK – not perfect, but good enough for what I need.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-18-raspberry-pi-footpedal-part-2/IMG_4760.JPG&quot; alt=&quot;IMG_4760&quot; /&gt;&lt;/p&gt;

&lt;figcaption&gt;Improvised metal brake made by clamping angle and square section steel onto my kitchen table.&lt;/figcaption&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-18-raspberry-pi-footpedal-part-2/IMG_4763.JPG&quot; alt=&quot;IMG_4763&quot; /&gt;&lt;/p&gt;

&lt;figcaption&gt;The main bends for the top part of the casing done.&lt;/figcaption&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-18-raspberry-pi-footpedal-part-2/IMG_4787.JPG&quot; alt=&quot;IMG_4787&quot; /&gt;&lt;/p&gt;

&lt;figcaption&gt;Test fitting the foot switches into the bent metal casing.&lt;/figcaption&gt;

&lt;p&gt;For the bottom half of the casing, I needed to bend up the edges of the case, but also to cut notches for the HDMI and USB ports on the Pi Zero.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-18-raspberry-pi-footpedal-part-2/IMG_4821.JPG&quot; alt=&quot;IMG_4821&quot; /&gt;&lt;/p&gt;

&lt;figcaption&gt;Rough-cutting the bottom casing panel using a jigsaw&lt;/figcaption&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-18-raspberry-pi-footpedal-part-2/IMG_4877.JPG&quot; alt=&quot;IMG_4877&quot; /&gt;&lt;/p&gt;

&lt;figcaption&gt;Work in progress filing out the notches for the Pi IO connectors&lt;/figcaption&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-18-raspberry-pi-footpedal-part-2/IMG_4880-1589755857669.JPG&quot; alt=&quot;IMG_4880&quot; /&gt;&lt;/p&gt;

&lt;figcaption&gt;Testing fit of the Pi Zero IO ports&lt;/figcaption&gt;

&lt;p&gt;The Pi board itself was mounted on a small piece of 6mm MDF – you can see the original sheet in this photo, that I’m using to hold the Pi in place to check the fit of the IO ports.&lt;/p&gt;

&lt;p&gt;One more thing I did was to use a needle file to enlarge the mounting holes on the Pi so I could use 3mm screws. Out of the box, the Pi uses 2.5mm mounting screws. I don’t have any of those lying around – but I have &lt;em&gt;lots&lt;/em&gt; of 3mm screws and bolts, so slightly enlarging the holes gave me way more options when it came to mounting the board.&lt;/p&gt;

&lt;h2 id=&quot;wiring-it-up&quot;&gt;Wiring it up&lt;/h2&gt;

&lt;p&gt;To wire the switches to the Pi’s GPIO ports, I cut one end off a 40-pin ribbon cable, giving me a wiring harness with about 15cm of wire on each pin. The fun part here was figuring out which wire connects to which pin. I used a Raspberry Pi breakout board for this part, and it took about an hour of testing connections with a multimeter and labelling each wire as I identified which pin it connected to. Fiddly, but not actually difficult.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-18-raspberry-pi-footpedal-part-2/IMG_4896.JPG&quot; alt=&quot;IMG_4896&quot; /&gt;&lt;/p&gt;

&lt;figcaption&gt;Tracing the individual cables in the 40-pin ribbon cable back to the source pins&lt;/figcaption&gt;

&lt;p&gt;For this project, I wired GPIO pins 04-08 to switches 1-5. At this stage I also wired in two extra breakout ports – one’s a mono 6.5mm jack socket, the other is a stereo 6.5mm jack socket. I wired these into GPIO pins 9 and 10+11, so I could hook up three extra external footswitches to give me another three inputs on the board.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-18-raspberry-pi-footpedal-part-2/IMG_4904.JPG&quot; alt=&quot;IMG_4904&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Here’s both parts of the casing, with all five switches and the two 6.5mm breakout ports all wired in and the ribbon cable seated on the Pi’s GPIO connector. You can see the 6 holes I drilled in the case here – the original plan was to tap M3 threads into the top half of the casing and assemble the case using M3 bolts. Turns out 0.6mm mild steel isn’t actually hard enough to hold a thread… two of them came out OK, one of them I stripped completely and ended up bodging by supergluing a nut in place above the hole, the other three I’m just going to ignore for now. You can also see the Pi board here mounted on a piece of 6mm MDF, which is attached to the steel using 3M Command Strips – again, that’s what I had easily to hand. They’re normally used to hold pictures on the wall, but they adhere to steel and to MDF pretty well and they’re easily removable if necessary.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-18-raspberry-pi-footpedal-part-2/IMG_4927.JPG&quot; alt=&quot;IMG_4927&quot; /&gt;&lt;/p&gt;

&lt;figcaption&gt;The Piedroit wired up and ready for final assembly&lt;/figcaption&gt;

&lt;p&gt;And – that’s it! Put the whole thing together, boot it up, and… it works. Windows detects each footswitch as a key on a USB keyboard, and now I can control WinAmp, OBS, and any other application that supports global hotkeys by pressing footswitches.&lt;/p&gt;

&lt;h2 id=&quot;ideas-for-vnext&quot;&gt;Ideas for vNext&lt;/h2&gt;

&lt;p&gt;This was a great project. Frustrating in places, especially the Bluetooth dead end, but I had a lot of fun putting it together. At a time when so much of what we’re all doing – work, social life, entertainment – is all taking place on screen, it’s also a nice change to build something physical; something you can actually pick up and touch and handle.&lt;/p&gt;

&lt;p&gt;But, of course, I have loads of ideas for building a better version.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;The circuitry. The Pi is a great little computer, but… do I really need 512Mb of RAM in a footswitch? I’m also still keen to see what I can do with Bluetooth, so I suspect the next incarnation of the project will use either something like an Arduino or a Teensy, or I’ll cannibalise a Bluetooth keyboard and hack the circuitry from that into a footpedal controller.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Status LEDs. There’s currently no way of telling whether the board has actually booted up properly. It wouldn’t be &lt;em&gt;that&lt;/em&gt; hard to wire up a couple of LEDs to the GPIO pins – something as simple as a red LED for power and a green LED that lights up once the USB keyboard event loop is running would make the device a lot friendlier.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And, of course, there’s all the “it would be cool if…” – reprogramming key mappings, macros, modifier keys… once you get into these kinds of projects, the hardest part is knowing when to stop.&lt;/p&gt;

&lt;p&gt;But this one is done. I’ll be using it for a bunch of events I’m doing this week, including &lt;a href=&quot;https://halfstackconf.com/online/&quot;&gt;HalfStack Online on Friday&lt;/a&gt;, and I’m sure you’ll be able to catch it in action on a YouTube or Twitch stream near you before too long!&lt;/p&gt;

</description>
          <pubDate>2020-05-18T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2020/05/18/turning-a-raspberry-pi-zero-into-a-usb-footpedal-part-2.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2020/05/18/turning-a-raspberry-pi-zero-into-a-usb-footpedal-part-2.html</guid>
        </item>
      
    
      
        <item>
          <title>Piedroit: A Raspberry Pi-powered USB Footpedal (Part 1)</title>
          <description>&lt;p&gt;I often find myself doing daft things with computers and playing the guitar at the same time. I use laptops on stage to run videos and backing tracks, I record my own music, and, most recently, I’ve been livestreaming music over YouTube and Twitch for some of the online conferences I’ve spoken at.&lt;/p&gt;

&lt;p&gt;Since playing the guitar tends to require both hands, many guitar amps and effects include some kind of footswitch controller, so you can switch between different sounds and effects without taking your hands off the guitar. For a while, I’ve been after something similar that’ll let me control a computer remotely using foot switches. I’ve used various kinds of foot controllers in the past, but none of them has ever done quite what I wanted. The two that have stuck around the longest are the &lt;a href=&quot;https://www.airturn.com/products/airturn-pedpro&quot;&gt;AirTurn PEDPro&lt;/a&gt; and the &lt;a href=&quot;https://www.ikmultimedia.com/products/irigblueboard/&quot;&gt;iRig Blueboard&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-17-raspberry-pi-footpedal/airturn-ped-pro.jpg&quot; alt=&quot;airturn-ped-pro&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The PEDPro runs on Bluetooth and works great as a hands-free Powerpoint clicker. It connects to Windows or macOS as a Bluetooth keyboard, but it only has two keys, which are (normally) hardwired to be Left and Right. This works great for controlling Powerpoint, but it’s a bit limited.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-17-raspberry-pi-footpedal/10158884_800-1589740727862.jpg&quot; alt=&quot;10158884_800&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://www.ikmultimedia.com/products/irigblueboard/&quot;&gt;iRig Blueboard&lt;/a&gt; has four footswitches, and works very nicely with iOS and macOS, but it connects to the host system as some kind of proprietary Bluetooth MIDI device, and it doesn’t work on Windows.&lt;/p&gt;

&lt;p&gt;So I figured building my own pedalboard might be a fun lockdown project. At a bare minimum, I wanted to be able to switch between scenes in &lt;a href=&quot;https://obsproject.com/&quot;&gt;OBS&lt;/a&gt; and control some sort of media player at the same time, so I can stop/start videos and backing tracks and switch between different camera angles when doing live streams.&lt;/p&gt;

&lt;p&gt;There’s two approaches I could have taken:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Network-based:&lt;/strong&gt; the footswitch device connects to wifi and sends signals to a host PC over the network. I write some bespoke software that runs on the host PC, which either translates those signals into emulated keystrokes, or controls the target applications directly.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Device emulation:&lt;/strong&gt; the footswitch connects directly to the host PC, via USB or Bluetooth, and shows up as a keyboard, joystick or some other kind of human interface device (HID).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I went for device emulation, mainly because the host PC is already going to be pretty busy handling multiple cameras, OBS, media playback, greenscreen effects, and live network streaming, and having my footswitch just show up as a keyboard seemed a lot more straightforward. Plus, I’ve done a whole bunch of network programming and client/server stuff, but I’ve never built something that emulated a physical keyboard before and it sounded like fun.&lt;/p&gt;

&lt;h2 id=&quot;the-raspberry-pi-zero-w&quot;&gt;The Raspberry Pi Zero W&lt;/h2&gt;

&lt;p&gt;For this version of the project, I used the Raspberry Pi Zero W. Lots of people asked why I didn’t use an Arduino or some other device: well, I used a Pi because I like them. They’re wonderful little devices, they’re lots of fun to work with, and I’ve done some interesting projects with them before so I’m not starting from scratch here.&lt;/p&gt;

&lt;p&gt;One very interesting idea which I’ll probably return to in a future project would be to cannibalise the controller from a Bluetooth keyboard and use that instead of building my own device. More on that in a moment.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-17-raspberry-pi-footpedal/image-20200518124113452.png&quot; alt=&quot;image-20200518124113452&quot; /&gt;&lt;/p&gt;

&lt;figcaption&gt;Banana! (Raspberry Pi Zero W for scale)&lt;/figcaption&gt;

&lt;p&gt;The Pi Zero is a tiny Linux computer, not much bigger than a stick of gum. It’s got a 1GHz single-core CPU, 512Mb RAM, a mini HDMI port, two USB ports – one for power, one for peripherals. Most important of all for this project, it has a bank of general-purpose input/output (GPIO) pins that you can wire up to external switches, LEDs, sensors, all kinds of things, and then write some fairly simple code to interface with them. Which is brilliant if you want to connect a bunch of mechanical footswitches to a tiny Linux computer and don’t &lt;em&gt;really&lt;/em&gt; know what you’re doing.&lt;/p&gt;

&lt;p&gt;The other nice thing about the Pi Zero W is that it’s got exactly the same hardware and programming interface as its big brother, the &lt;a href=&quot;https://www.raspberrypi.org/products/raspberry-pi-4-model-b/&quot;&gt;Raspberry Pi 4 Model B&lt;/a&gt; – so if you get into serious code-wrangling, you can use the more powerful Pi 4B as your dev platform, get your code working, then pop the MicroSD card out, put it into the Pi Zero and boot the exact same code on the smaller device.&lt;/p&gt;

&lt;h3 id=&quot;plan-a-bluetooth&quot;&gt;Plan A: Bluetooth&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-17-raspberry-pi-footpedal/EXBPCCoWoAEHpK4.jpg&quot; alt=&quot;Image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;My first approach with this project was to get the Pi Zero to connect to the host PC over Bluetooth, and emulate a Bluetooth keyboard. I spent a rather fun, if occasionally frustrating, weekend playing around with this. You can read the tweet-by-tweet accounts from &lt;a href=&quot;https://twitter.com/dylanbeattie/status/1256593530279145472&quot;&gt;day 1&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/dylanbeattie/status/1256899500242804737&quot;&gt;day 2&lt;/a&gt; – but to cut a long story short, I couldn’t get it to work well enough for what I wanted.&lt;/p&gt;

&lt;p&gt;I got to the point where I could boot the Pi, start the various Python scripts, then go onto my Windows machine and add a new Bluetooth device – and it worked. The device would show up as &lt;strong&gt;paired&lt;/strong&gt;, then &lt;strong&gt;connected&lt;/strong&gt;, and it would actually send keystrokes to the host PC:&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;I have hardware switches connected to the Pi Zero GPIO pins. Pressing a switch sends a a keystroke to the Windows PC. The PC thinks it’s talking to a Bluetooth keyboard. The whole thing is as hacky as all hell - but it works. :) &lt;a href=&quot;https://t.co/VQp5jHjG8a&quot;&gt;pic.twitter.com/VQp5jHjG8a&lt;/a&gt;&lt;/p&gt;&amp;mdash; Dylan Beattie 🇪🇺 @ 🏡🔑🔽 (@dylanbeattie) &lt;a href=&quot;https://twitter.com/dylanbeattie/status/1257017418351812608?ref_src=twsrc%5Etfw&quot;&gt;May 3, 2020&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;There was a problem, though. When I stopped the Python script that was running the Bluetooth service on the Pi, the device would go from &lt;strong&gt;connected&lt;/strong&gt; to &lt;strong&gt;paired&lt;/strong&gt; – and I could not find any way to get it to reconnect without removing and re-adding it. Which made the whole thing a bit impractical, particularly if I was going to try using it in any kind of live performance situation.&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Any folks out there who know the intimate details of the bluez Bluetooth stack? I need to force a Linux device to connect to a host that’s already paired... you can see exactly what I mean in the video here. Any help much, much appreciated. &lt;a href=&quot;https://t.co/7YOYV59NfE&quot;&gt;pic.twitter.com/7YOYV59NfE&lt;/a&gt;&lt;/p&gt;&amp;mdash; Dylan Beattie 🇪🇺 @ 🏡🔑🔽 (@dylanbeattie) &lt;a href=&quot;https://twitter.com/dylanbeattie/status/1259265653296463882?ref_src=twsrc%5Etfw&quot;&gt;May 9, 2020&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;In this video, you can see that with my Logitech K380 Bluetooth keyboard, this works perfectly – and part of me wishes I’d just cannibalised a K380, taken out the controller chips and switches, and wired my own footswitches into it. That would have made the hardware side of this a lot more straightforward… but hey, we live and learn, right?&lt;/p&gt;

&lt;h2 id=&quot;plan-b-wired-usb&quot;&gt;Plan B: Wired USB&lt;/h2&gt;

&lt;p&gt;In tech projects, it’s way too easy to get fixated on your current approach and lose sight of what you’re actually trying to accomplish. It was this &lt;a href=&quot;https://twitter.com/jonty/status/1259272725622857729&quot;&gt;tweet from Jonty&lt;/a&gt; – and particularly the phrase “you’re wayyyy down this rabbit hole” – that got me to take a metaphorical step back and remember that &lt;strong&gt;Bluetooth is not actually a requirement here&lt;/strong&gt;. The goal is to be able to control OBS and WinAmp, on a desktop PC, using my feet. I’m trying to control a stationary computer from a stationary footswitch, so having a wire connecting the two is not a problem. It turns out the Pi Zero will also quite happily emulate any number of USB gadgets, from virtual keyboards to joysticks and network interfaces.&lt;/p&gt;

&lt;p&gt;So, I recast the project a bit. The milestone for this part was to get to a point where:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The Pi Zero is connected to a Windows 10 PC via USB&lt;/li&gt;
  &lt;li&gt;The Windows PC thinks the Pi Zero is a keyboard&lt;/li&gt;
  &lt;li&gt;I can run some Python code on the Pi Zero to send arbitrary keystrokes (including modifier keys) to the PC&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was actually pretty straightforward, thanks to a really great article “&lt;a href=&quot;&quot;&gt;Composite USB Gadgets on the Raspberry Pi Zero&lt;/a&gt;” over at iSticktoit.net. I did have one slight stumbling block: when I first started working on this, I originally found &lt;a href=&quot;https://randomnerdtutorials.com/raspberry-pi-zero-usb-keyboard-hid/&quot;&gt;this tutorial&lt;/a&gt; instead. That’s almost exactly the same (the code samples are &lt;em&gt;identical&lt;/em&gt;) except the Random Nerd Tutorials article says quite clearly that you can run the whole thing off a single micro USB port:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-17-raspberry-pi-footpedal/image-20200517163434097.png&quot; alt=&quot;image-20200517163434097&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now, a little later in the article, our Random Nerd author does actually say:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-17-raspberry-pi-footpedal/image-20200517163507995.png&quot; alt=&quot;image-20200517163507995&quot; /&gt;&lt;/p&gt;

&lt;p&gt;To say this does not match my own experience would be putting it mildly. The first few times I tried booting the Pi drawing power through this USB port, it caused all sorts of chaos, including completely shutting down the entire USB bus on my Windows PC more than once, leaving me with no mouse and no keyboard. That was fun. It is &lt;em&gt;possible&lt;/em&gt; – I got a working setup a few times using only a single USB cable – but I wouldn’t recommend it. After a few rounds of this, I tried booting the Pi using standalone USB power, waiting until it was up and running, and then connecting the second USB port to the host PC – and it worked flawlessly. You have no idea how excited I got when I saw this little pop-up in the corner of my Windows 10 display:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-17-raspberry-pi-footpedal/image-20200517165722956.png&quot; alt=&quot;image-20200517165722956&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR: Boot the Pi using dedicated USB power. Once it is up and running, connect the peripheral USB port to the host PC, and everything works fine.&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-code&quot;&gt;The Code&lt;/h2&gt;

&lt;p&gt;The software side of this is really in two parts. First, there’s the configuration you’ll need to run to persuade the Pi to start pretending it’s a USB keyboard. Then there’s a bit of Python code that will detect when a circuit is closed across a pair of GPIO pins, and send a particular keystroke over the USB interface when this happens. This is wonderfully, beautifully simple on the Pi Zero, thanks to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RPi.GPIO&lt;/code&gt; library that makes it really easy to write Python code that talks to the GPIO pins. All the code for my implementation is available on Github, along with instructions about how to get it set up and running:&lt;/p&gt;

&lt;p&gt;https://github.com/dylanbeattie/piedroit&lt;/p&gt;

&lt;p&gt;The important part is here – &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;piedroit.py&lt;/code&gt;. This is the code that detects events on the GPIO pins, translates them into the specific data structure required by the USB keyboard interface, and sends those events to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev/hidg0&lt;/code&gt; device that’ll pass them to the host PC:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;#!/usr/bin/env python
# piedroit: translate GPIO pin inputs into USB keystrokes
# https://github.com/dylanbeattie/piedroit
&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;RPi.GPIO&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;modifier_keys&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;NULL_CHAR&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;chr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;send_data_to_usb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;/dev/hidg0&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;rb+&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;get_key_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gpio_pin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf
&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# Physical switches are wired to GPIO pins 4-11
&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# I want footswitch #1 (GPIO pin 04) to send F1, #2 &amp;gt; GPIO02 &amp;gt; F2, etc.
&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# F1 has USB key code 58 (0x3A), F2 is 59, etc. 
&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# So we can get the key code we need by adding 54 to the GPIO pin number.
&lt;/span&gt;  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gpio_pin&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;54&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;send_key_down&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# USB key events are an 8-byte struct containing:
&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# – a one-byte bitfield of modifier keys 
&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# – a null byte
&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# – Up to six key codes. (USB allows you to press up to six keys simultaneously)
&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;#
&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# This code will always send Ctrl+{key}
&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;modifiers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modifier_keys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LEFT_CTRL&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;key_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;chr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;modifiers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NULL_CHAR&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;chr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NULL_CHAR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;send_data_to_usb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;release_all_keys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;send_data_to_usb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NULL_CHAR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;send_key_for_gpio_pin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gpio_pin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;key_code&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;get_key_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gpio_pin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;send_key_down&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# This code will support GPIO pins 4-21, although 
# only pins 4-11 are actually connected in my device
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FIRST_GPIO_PIN&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;FINAL_GPIO_PIN&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;21&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ALL_PINS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FIRST_GPIO_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FINAL_GPIO_PIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Tell the Pi which numbering mode we&apos;re using to talk to the GPIO pins
# https://raspberrypi.stackexchange.com/questions/12966/what-is-the-difference-between-board-and-bcm-for-gpio-pin-numbering
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;setmode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BCM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pin&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ALL_PINS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pull_up_down&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PUD_UP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Remember closing a switch connects the GPIO pin to ground (GND)
# so GPIO.input(pin) will return 0 (&quot;grounded&quot;) or 1 (&quot;not grounded&quot;)
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SWITCH_CLOSED&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Now we just sit in an infinite loop, reading all the GPIO pin states 
# every time we loop, and watching to see if any pin state has changed.
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;previous_states&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FINAL_GPIO_PIN&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pin&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ALL_PINS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;previous_state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;previous_states&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;previous_state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SWITCH_CLOSED&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;GPIO PIN {} now CLOSED&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;send_key_for_gpio_pin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;GPIO PIN {} now OPEN&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;release_all_keys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;previous_states&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;
 
&lt;span class=&quot;c1&quot;&gt;# This is good practice, but since we&apos;re going to sit in our loop
# until we kill the process or disconnect the power, we&apos;ll never
# actually get here. But in this scenario, that&apos;s OK.
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pin&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ALL_PINS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cleanup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;the-case&quot;&gt;The Case&lt;/h2&gt;

&lt;p&gt;Finally, I needed a box to put it all in – a box that wouldn’t get upset if 102kg of Dylan stood on it wearing cowboy boots. Check out &lt;a href=&quot;/2020/05/18/turning-a-raspberry-pi-zero-into-a-usb-footpedal-part-2.html&quot;&gt;part 2 of this post&lt;/a&gt; which involves a lot less code – and a lot more sheet steel.&lt;/p&gt;

&lt;p&gt;🤘🏻&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-17-raspberry-pi-footpedal/IMG_4923.JPG&quot; alt=&quot;IMG_4923&quot; /&gt;&lt;/p&gt;
</description>
          <pubDate>2020-05-17T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2020/05/17/turning-a-raspberry-pi-zero-into-a-usb-footpedal.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2020/05/17/turning-a-raspberry-pi-zero-into-a-usb-footpedal.html</guid>
        </item>
      
    
      
        <item>
          <title>Microphone Tips for Remote Presentations</title>
          <description>&lt;p&gt;In my last post, I &lt;a href=&quot;/2020/05/01/online-presentation-tips.html&quot;&gt;shared some tips for giving online presentations&lt;/a&gt;. In this talk, we’re going to completely nerd out about microphones, and how to get the best possible results out of the equipment you already have.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-01-microphones/9434755558_129d6bce9e_o.jpg&quot; alt=&quot;9434755558_129d6bce9e_o&quot; /&gt;&lt;/p&gt;

&lt;figcaption&gt;&lt;a href=&quot;https://flickr.com/photos/99173313@N07/9434755558&quot;&gt;drestwn via Flicker&lt;/a&gt; / CC BY 2.0&lt;/figcaption&gt;

&lt;p&gt;Audio quality is essential to connecting with your audience when you’re presenting remotely. A conference is about &lt;strong&gt;speakers&lt;/strong&gt; giving &lt;strong&gt;talks&lt;/strong&gt;. Not looks, not stands, not dances – &lt;strong&gt;talks&lt;/strong&gt;. We are speakers, we are there to talk, and our audiences are there to listen. If your audience can’t hear you, you’ve lost them, no matter how beautiful your slides or or how many hours you’ve spent practising…  and when you’re presenting online, your microphone is the &lt;a href=&quot;https://en.wikipedia.org/wiki/Jesus_nut&quot;&gt;Jesus nut&lt;/a&gt; – the single point of failure that can bring the whole thing crashing down.&lt;/p&gt;

&lt;p&gt;You can easily spend a &lt;a href=&quot;https://www.thomann.de/gb/large_diaphragm_mics.html?oa=prd&quot;&gt;fortune&lt;/a&gt; on specialist microphones, but if you don’t know what you’re doing, it won’t make any difference – and if you &lt;em&gt;do&lt;/em&gt; know what you’re doing, you can get great results from relatively inexpensive kit.&lt;/p&gt;

&lt;h3 id=&quot;your-ears-never-blink&quot;&gt;Your ears never blink&lt;/h3&gt;

&lt;p&gt;Humans are good at dealing with visual clutter. We can focus our eyes, look at the things we care about and ignore unnecessary background detail - &lt;a href=&quot;https://www.youtube.com/watch?v=IGQmdoK_ZfY&quot;&gt;check out this video&lt;/a&gt; for a remarkable demonstration of this.&lt;/p&gt;

&lt;p&gt;Audio isn’t like that. &lt;strong&gt;Your ears never blink.&lt;/strong&gt; We hear every detail, every keystroke and passing car and buzzing fluorescent lamp - and so when we’re doing presentations, it’s absolutely vital to understand how much background noise and interference your mic is picking up.&lt;/p&gt;

&lt;p&gt;Before we go any further, take a look at this video. I recorded myself using seven different microphones at the same time, so you can hear for yourself how the different microphones sound, and how they cope with different kinds of interference and background noise.&lt;/p&gt;

&lt;iframe width=&quot;640&quot; height=&quot;360&quot; style=&quot;width: 640px; height: 360px; text-align: center;&quot; src=&quot;https://www.youtube.com/embed/STS2-IDMaa8&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h3 id=&quot;microphone-placement&quot;&gt;Microphone placement&lt;/h3&gt;

&lt;p&gt;You want your mic as close to your mouth as possible; 5 cm (2”) to 25 cm (10”) is ideal. You also want to avoid moving around too much relative to the microphone.&lt;/p&gt;

&lt;p&gt;Headset mics are great, because they keep the microphone in the same position relative to your mouth – you can turn your head, walk around, and they still sound good. Lavalier mics – the ones that clip into your clothes – and phone headsets also work pretty well, and you can stand up and walk around, as long as you remember not to turn your head. If you’re using a fixed mic on a stand, you’ll need to sit very, very still to get the best results from it.&lt;/p&gt;

&lt;h3 id=&quot;background-noise-and-isolation&quot;&gt;Background noise and isolation&lt;/h3&gt;

&lt;p&gt;You’ll notice in the video that the built-in mic in the Surface Pro sounds really good, &lt;a href=&quot;https://youtu.be/STS2-IDMaa8?t=302&quot;&gt;right up until I start typing&lt;/a&gt;. It’s actually a really good mic, but because it’s built in to the device, it’s in the worst possible position – it’s on the desk, quite a long way from my mouth, right next to my keyboard, and it’ll pick up every bit of vibration and background noise.&lt;/p&gt;

&lt;p&gt;This isn’t just a problem with built-in mics – I’ve seen folks spend quite a lot of money on specialist USB microphones like the Blue Yeti, and then sit them on the desk right next to their mechanical keyboard and wonder why they’re not getting a good sound.&lt;/p&gt;

&lt;p&gt;If your mic is attached to your clothing, or you’re using a phone headset, watch out for clothing rustle – check out &lt;a href=&quot;https://www.rode.com/blog/all/lavalier-mounting-best-practices&quot;&gt;this article from Rode about lavalier mic placement&lt;/a&gt;, which has some excellent tips.&lt;/p&gt;

&lt;h3 id=&quot;connection&quot;&gt;Connection&lt;/h3&gt;

&lt;p&gt;If you’re going shopping, you’ll need to consider how your mic connects to your computer. USB headsets will just plug straight in, and the headphone jack in most modern laptops is compatible with the &lt;a href=&quot;https://en.wikipedia.org/wiki/Phone_connector_(audio)#PDAs_and_mobile_phones&quot;&gt;TRRS plugs used on phone headsets&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you’re going for a standalone mic or a wireless system, you’ll need an audio interface to connect it to your computer. I highly recommend the &lt;a href=&quot;https://www.soundonsound.com/reviews/yamaha-ag06-ag03&quot;&gt;Yamaha AG03 and AG06&lt;/a&gt;, for this – they’re great quality, incredibly versatile and easy to use.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-12-microphones/yamaha-ag03-ag06.png&quot; alt=&quot;Photograph of Yamaha AG03 and AG06 USB mixing consoles&quot; /&gt;&lt;/p&gt;

&lt;figcaption&gt;Yamaha AG06 and AG03 portable USB mixing consoles&lt;/figcaption&gt;

&lt;h3 id=&quot;listen-to-yourself&quot;&gt;Listen to yourself&lt;/h3&gt;

&lt;p&gt;The golden rule with any microphone, though is: &lt;strong&gt;listen to yourself.&lt;/strong&gt; Install some audio recording software  – &lt;a href=&quot;https://www.audacityteam.org/&quot;&gt;Audacity&lt;/a&gt; is free, open-source and cross-platform – make some recordings, listen back to yourself (and make sure to listen through headphones.) Record 30 seconds of silence and listen back to it. What do you hear? Traffic noise? Dishwasher? Mouse clicks? If you can hear it, your audience will hear it. You might not able to eliminate background noise entirely, but figure out what you can to reduce it – close windows, close doors, take off your shoes, switch off the aircon. Make yourself a pre-flight checklist.&lt;/p&gt;

&lt;p&gt;Now record 30 seconds of you speaking. at the same volume you’d use when giving a talk, and listen back to that. Try reading these ten sentences out loud, then listen back and see what you sound like:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;The dark pot hung in the front closet.&lt;/li&gt;
  &lt;li&gt;Carry the pail to the wall and spill it there.&lt;/li&gt;
  &lt;li&gt;The train brought our hero to the big town.&lt;/li&gt;
  &lt;li&gt;We are sure that one war is enough.&lt;/li&gt;
  &lt;li&gt;Grey paint stretched for miles around.&lt;/li&gt;
  &lt;li&gt;The rude laugh filled the empty room.&lt;/li&gt;
  &lt;li&gt;High seats are best for football fans.&lt;/li&gt;
  &lt;li&gt;Tea served from the brown jug is tasty.&lt;/li&gt;
  &lt;li&gt;A dash of pepper spoils beef stew.&lt;/li&gt;
  &lt;li&gt;A zestful food is the hot-cross bun.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is one of 72 lists of stock phrases known as the “&lt;a href=&quot;http://www.cs.columbia.edu/~hgs/audio/harvard.html&quot;&gt;Harvard Sentences&lt;/a&gt;”:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“a collection of phonetically balanced sentences that measure a large range of different qualities in the human voice. These were originally published in 1969 as the Institute of Electrical and Electronics Engineers recommended practice for speech quality measurements.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;– you can find &lt;a href=&quot;http://www.cs.columbia.edu/~hgs/audio/harvard.html&quot;&gt;the whole list online here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you hear loud pop or thud on words like ‘pepper’ and ‘bubble’, that’s called a &lt;em&gt;plosive&lt;/em&gt; – it’s the mic picking up the sharp release of breath that happens when we pronounce letters like ‘b’ and ‘p’. See if you can move the mic downwards or to the side slightly so that it isn’t directly in front of your mouth. If you hear hissing or whistling on words like ‘stew’ and ‘seats’, that’s called &lt;em&gt;sibilance&lt;/em&gt; – try moving the mic a little further away from your mouth.&lt;/p&gt;

&lt;p&gt;Play with the distance until you’ve got a good clear voice with minimal background noise, but you’re not getting any hissing or popping. If your mic has a volume control, turn it up until you’re about as loud as you can go without hearing any distortion or seeing any warning lights, then back it off about 5%. Do this every time you change anything – move the furniture, get a new mic, install new drivers. And remember the golden rule: &lt;strong&gt;listen to yourself.&lt;/strong&gt; That’s what your audience is going to be listening to for an hour – if it doesn’t sound good in your own room, on your own headphones, there’s no &lt;em&gt;wa&lt;/em&gt;y that sending it halfway around the world on a video call is going to make it any better.&lt;/p&gt;

&lt;h3 id=&quot;conclusions&quot;&gt;Conclusions&lt;/h3&gt;

&lt;p&gt;A USB headset gives the best balance of quality and price – you can get excellent audio quality from a £20 headset, and it won’t pick up things like typing and background noise. The only drawbacks here are that you’ve gotta have a big chunky thing on your head, and if it’s wired, you can’t get up and walk around. If you’re going to be sat a desk for your recordings, and don’t mind how it looks (or you’re not going to be on video), go for one of these.&lt;/p&gt;

&lt;p&gt;If all you have is a phone headset, use it – but be very careful not to move around too much. You’ll get good results if you sit still and speak clearly, but watch out for the mic rubbing on your clothes and be careful you don’t snag the cable.&lt;/p&gt;

&lt;p&gt;If you’re using a fixed mic like the &lt;a href=&quot;http://www.rode.com/microphones/podcaster&quot;&gt;RØDE Podcaster&lt;/a&gt; or the &lt;a href=&quot;https://www.bluedesigns.com/products/yeti/&quot;&gt;Blue Yeti&lt;/a&gt;, make sure it’s mounted high and insulated from vibration – ideally on a proper microphone stand with a &lt;a href=&quot;https://www.neumann.com/homestudio/en/do-i-really-need-a-shock-mount&quot;&gt;shock mount&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you have no choice but to use the mic built in to your laptop, work out how to isolate it as much as possible from background noise. If your presentation doesn’t involve any typing, clicking or anything, you’ll probably be OK. If you need to type, try resting the laptop on a pillow or a cushion - maybe even use an external keyboard as well to reduce the typing noise.&lt;/p&gt;

&lt;p&gt;And remember: you don’t need to spend a lot of money on specialist equipment. Start with what you’ve already got, make some recordings, listen to yourself, figure out what’s not working, fix it, repeat.&lt;/p&gt;

</description>
          <pubDate>2020-05-12T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2020/05/12/microphones.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2020/05/12/microphones.html</guid>
        </item>
      
    
      
        <item>
          <title>Online Presentation Tips</title>
          <description>&lt;p&gt;As we enter the third month of the global lockdown, more and more events are exploring online conferences as an alternative to postponing or cancelling, which means more and more speakers are being asked if they’d be able to do their talks as an online session presented over a live stream.&lt;/p&gt;

&lt;p&gt;Since March, I’ve spoken at four virtual meetups, presented six talks at four online conferences, and helped the crew from NDC turn Copenhagen and Porto into virtual events. I’ve experimented with a ridiculous amount of gear – old and new – and I’ve talked with hundreds of speakers about the challenges we’re all facing to adapt our material, turn our homes into impromptu broadcast studios, and keep on delivering great content and inspiring presentations even when we’re all locked down for the foreseeable future.&lt;/p&gt;

&lt;p&gt;I’ve had lots people asking me for tips on this – so here it is: everything you ever wanted to know about online presentations. Well, almost everything. There’s some seriously deep-dive posts coming up about microphones and cameras, but this will get you started. :)&lt;/p&gt;

&lt;h2 id=&quot;test-test-test&quot;&gt;Test, test, test.&lt;/h2&gt;

&lt;p&gt;The absolute, cast-iron, golden rule of online presentations: &lt;strong&gt;Test everything. Test, test, test.&lt;/strong&gt; It doesn’t matter how good you are in your room – &lt;strong&gt;what matters is what the remote audience can see and hear.&lt;/strong&gt; Figure out how to see through your own camera, how to listen to your own microphone, how to see your own screen share. Put yourself in your audience’s shoes and see how much of your content actually makes it out the other end – and then tweak, improve and adapt until it’s perfect. Every time you change anything, &lt;strong&gt;test it.&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;rehearse-your-content&quot;&gt;Rehearse your content.&lt;/h2&gt;

&lt;p&gt;This sounds obvious, but it’s easily overlooked. Online isn’t just another meetup or conference stage, it’s a completely new format. Even if you’ve done a talk a dozen times, the first time you do it remote, it’s going to be weird. Rehearse. Do your talk by yourself, in the room where you’re going to be presenting. Don’t worry about equipment yet – this is about &lt;em&gt;content&lt;/em&gt;. Rehearse until you can deliver your talk flawlessly, to a blank wall that doesn’t laugh at any of your jokes or raise a hand for any of your questions. If you can handle that, you can handle anything that a virtual conference is going to throw at you.&lt;/p&gt;

&lt;h2 id=&quot;microphones&quot;&gt;Microphones.&lt;/h2&gt;

&lt;p&gt;You don’t need an expensive specialist mic to do a great remote presentation – but you will need something a little better than the built-in mic in your laptop. The golden rule here is distance. You want your mic as close to your mouth as you can get it – between 2” (5cm) and 6” (30cm)  is ideal – and as far away from any other source of noise as you can. Gaming headsets, Bluetooth earbuds, a clip-on microphone, even the cord mic on the earphones that came with your phone – any of them give a better result than relying on the mic built in to your laptop.&lt;/p&gt;

&lt;h2 id=&quot;cameras-and-video&quot;&gt;Cameras and video.&lt;/h2&gt;

&lt;p&gt;The classic setup here is a webcam for your face and a “screen share” for your slides – and in most cases, that’ll work absolutely fine. As with microphones, you’re much better off with inexpensive equipment and a bit of know-how than spending a fortune on gear you don’t know how to use properly.&lt;/p&gt;

&lt;p&gt;Here’s the secret. Even the cheapest webcams are good enough for a remote presentation if you give them enough light to work with. You want light shining onto your face, and a neutral background behind you. If there’s a window in the room where you’re presenting, move things around so you’ll be facing towards the window. I took these photographs using the same camera, same day, same time, same distance – the only difference is that I turned 180° between shots:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-05-01-online-presentation-tips/image-20200501000228875.png&quot; alt=&quot;image-20200501000228875&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://pbs.twimg.com/media/EUxC4_fXgAAjfeT?format=jpg&amp;amp;name=large&quot; alt=&quot;Image&quot; /&gt;&lt;/p&gt;

&lt;figcaption&gt;&lt;a href=&quot;https://twitter.com/TessFerrandez/status/1246444749961277440&quot;&gt;https://twitter.com/TessFerrandez/status/1246444749961277440/&lt;/a&gt;&lt;/figcaption&gt;

&lt;p&gt;Oh, and one final camera tip… stick some &lt;a href=&quot;https://www.amazon.co.uk/Environmental-Adhesive-Assorted-Kids-Crafty%C2%AE/dp/B00HWGXW9Q&quot;&gt;googly eyes&lt;/a&gt; on your webcam to remind you to look at it. If you look straight at the camera. That’s your audience now, and every speaker knows that making eye contact with your audience goes a long way.&lt;/p&gt;

&lt;h2 id=&quot;join-from-a-second-device&quot;&gt;Join from a second device.&lt;/h2&gt;

&lt;p&gt;Get a second device – your old laptop, your iPad, even your phone – and connect it, as an attendee/guest, to the same call where you’re presenting. Mute it, switch off the video, and you can see exactly what your audience will see. For rehearsals, you can also run a screen recorder on the second device, and then review the recording to see how you look and sound to your remote attendees. If you’re on macOS, Quicktime has a built-in screen recorder. If you’re on Windows, there’s a built-in screen recorder hidden in the X-Box Game Bar (no, really – press Win+G, and look for the Capture window).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note that if you’re on a slow internet connection, you won’t want two devices fighting over the available bandwidth, so consider tethering the second device to your phone to provide an isolated internet connection.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;make-a-pre-flight-checklist&quot;&gt;Make a pre-flight checklist.&lt;/h2&gt;

&lt;p&gt;One of the big challenges with the online format is that we’re having to present from home – which means the room where you’re presenting is also your living room, or your bedroom, and you can’t leave it set up as a live-streaming studio 24/7. You’ll do a tech check, it’ll look and sound great… and then you’ll unplug everything, put the furniture back where it’s supposed to go, open the curtains, switch the aircon back on. And tomorrow, when it’s time to do your talk, you’ll try to put everything back how it was in rehearsal – and you’ll forget something.&lt;/p&gt;

&lt;p&gt;Once you find a setup that works, make yourself a pre-flight checklist. Then run through this half an hour before you’re due to speak, to make sure everything’s set up just right.&lt;/p&gt;

&lt;h2 id=&quot;connect-with-your-audience&quot;&gt;Connect with your audience.&lt;/h2&gt;

&lt;p&gt;Different events have done different things here, but having some kind of connection with your audience makes a huge difference.&lt;/p&gt;

&lt;p&gt;If you’re using something like Zoom or WebEx to deliver your talk, see if you can get attendees to join the call directly, mute their mics, switch their cameras on, and be your live audience. You won’t be able to hear them, but if you enable the “gallery view” so you can see their faces, it affords a certain amount of live interaction – you’ll be able to see people laughing, raising hands, that kind of thing. If you’re streaming via a platform like YouTube or Twitch, you won’t be able to see any of your audience, but you can interact with them via chat. Get the YouTube chat window up where you can see it, keep one eye on it whilst you’re speaking, and respond directly on the stream – “hey, I see a question in chat from Carol about accessibility. Hi Carol! What I’d do is… {answer}”&lt;/p&gt;

&lt;p&gt;For larger events, a hybrid approach works well, especially for keynote talks which might have many hundreds of attendees. Invite the other speakers to join the call directly, and run a YouTube stream for everyone else. The speaker gets a tame audience of tech-savvy nerds and some decent Q&amp;amp;A and interaction, and the rest of the attendees tune in via the YouTube stream and ask questions via chat.&lt;/p&gt;

&lt;p&gt;Bear in mind YouTube and Twitch can have a bit of latency – anything up to 20 seconds – so you’ll tell a joke… n response… then 20 seconds later you’ll get a little flurry of laughing-face emojis in the chat. It’s weird, but you’ll get used to it.&lt;/p&gt;

&lt;h2 id=&quot;stand-up-dress-up-remember-biology&quot;&gt;Stand up, dress up, remember biology.&lt;/h2&gt;

&lt;p&gt;You know your desk. You sit there, day in, day out, alt-tabbing between Slack and Visual Studio and Twitter and Amazon and Facebook and Stack Overflow – and since the lockdown, that’s also where you play poker with your buddies, where you sing happy birthday to your &lt;a href=&quot;https://en.wiktionary.org/wiki/nibling&quot;&gt;niblings&lt;/a&gt;… and now you’re going to sit there for an hour, giving your absolute, undivided attention to an audience you can’t see or hear? That’s gonna be on tough gig before it even starts… so mix things up a bit. Get your webcam up on a tripod, or a bookshelf, or, hell, put a chair up on the table and put your laptop on the chair – and &lt;em&gt;stand up&lt;/em&gt;. Standing up changes your posture, it changes your voice, it keeps you focused on the fact that whatever else is happening around you, &lt;em&gt;you are on a stage right now&lt;/em&gt; and people all over the world are watching you do your thing.&lt;/p&gt;

&lt;p&gt;On the day you’re presenting, dress up. Dress like you would if you were doing this for real – shower, do your hair, makeup if that’s your thing… hit that call looking like a million dollars, ‘cos even on the ropiest connection in the world, a few hundred thousand bucks will make it out the other side. It’s also a good way of persuading your brain that &lt;em&gt;something special is happening&lt;/em&gt; and this isn’t just another pyjama day in lockdown.&lt;/p&gt;

&lt;p&gt;Finally, remember that you’re going to be presenting for an hour. Your brain doesn’t quite get this. Your brain thinks you’re at home, where the bathroom is right down the hall and you can always pop to the kitchen for a snack or a glass of water… but you’re not&lt;strong&gt;. You are at a conference, on a stage, with hundreds of people watching you.&lt;/strong&gt; Now is no time to take a toilet break – so go before you join the call for your talk. Eat a snack, make sure there’s a glass or two of water within easy reach (having a coughing fit during a talk is bad enough at the best of times, but right now it’s a &lt;em&gt;really&lt;/em&gt; bad idea) – and be prepared to take a break afterwards. If you do it right, you’ll forget you’re at home; you’ll be 100% focused on your talk and your audience – and without the feedback you’d get from a live crowd, you’ll hit the end and you’ll crash, hard. The part where you’d normally be buzzing from the adrenaline of live performance… you’ll get that, but you’ll be in a room, by yourself, staring at a Slack chat. Give yourself some time to recover. Take a break, walk around the block, sit and drink a glass of water, decompress.&lt;/p&gt;

&lt;h2 id=&quot;relax-you-can-do-this&quot;&gt;Relax. You can do this.&lt;/h2&gt;

&lt;p&gt;The transition to online events is hard. We’re having to adapt quickly, in difficult circumstances, and it’s not like you can invite a couple of friends over to help you figure this out, or organise a (physical!) meetup to talk about how to make it work.&lt;/p&gt;

&lt;p&gt;There’s a new set of skills to learn here. There’s new tools and technology, sure, but there’s also a completely new presentation style to master – one that’s familiar to radio DJs and studio TV presenters, but completely alien to most of us over here in the tech industry. And we’re doing it in some seriously weird circumstances. As somebody put it the other day:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;You are not “working from home”. You are at home, trying to work, in the middle of a global pandemic that has turned the world upside down. Cut yourself a little slack.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So relax. Rehearse, learn the tech, ask your friends to help, and get out there and be awesome.&lt;/p&gt;

&lt;p&gt;I mean stay home. Don’t get out there. STAY HOME and be awesome.&lt;/p&gt;

&lt;p&gt;And don’t forget to wash your hands :)&lt;/p&gt;

&lt;h2 id=&quot;links&quot;&gt;Links&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Scott Hanselman’s “&lt;a href=&quot;https://www.hanselman.com/blog/GoodBetterBestCreatingTheUltimateRemoteWorkerWebcamSetupOnABudget.aspx&quot;&gt;Creating the ultimate remote worker webcam setup on a budget&lt;/a&gt;” has some great tips about cameras, lighting and audio (and check out the accompanying &lt;a href=&quot;https://www.youtube.com/watch?v=xrnF6hdLrIE&quot;&gt;YouTube video&lt;/a&gt; as well)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.raymondcamden.com/2020/03/10/tips-for-giving-remote-presentations&quot;&gt;This post from Raymond Camden&lt;/a&gt; includes great remote speaking tips from many well-known speakers – John Papa, Jen Looper, Chris Heilmann, and many more.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://kahoot.com/blog/2020/03/13/host-effective-remote-presentation/&quot;&gt;Daniella Latham has an article on Kahoot&lt;/a&gt; that includes some good tips about adapting your material for the online format.&lt;/li&gt;
  &lt;li&gt;I’m running a Slack workspace at ursatile.slack.com where a bunch of speakers and meetup organisers are sharing tips and ideas about online presenting – &lt;a href=&quot;https://join.slack.com/t/ursatile/shared_invite/zt-dco7z2xz-rdUircTJiUkdtmvZ39Dmqg&quot;&gt;you’re very welcome to join us&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
          <pubDate>2020-05-01T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2020/05/01/online-presentation-tips.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2020/05/01/online-presentation-tips.html</guid>
        </item>
      
    
      
        <item>
          <title>Going Virtual with NDC Copenhagen</title>
          <description>&lt;p&gt;A month ago, I was in Denmark doing a &lt;a href=&quot;https://www.meetup.com/Copenhagen-Net-User-Group/events/268797049/&quot;&gt;promotional event for NDC Copenhagen&lt;/a&gt; - and nobody was even considering the possibility that we might have to turn it into an online-only event – but as the &lt;a href=&quot;https://agilemanifesto.org/&quot;&gt;agile manifesto&lt;/a&gt; says, developers should be prepared to respond to change instead of following a plan. And, wow, the last four weeks have been intense. We’ve had less than a month to turn a physical conference into fully virtual online event - two days of workshops, one day of talks across three tracks,  nearly 250 attendees and speakers…&lt;/p&gt;

&lt;p&gt;We did it. Right now there’s three talks streaming live, there’s 247 people online on Slack, there’s Q&amp;amp;A happening in the Slack channels, I’m watching Scott Helme stream his “Stories from the Trenches” infosec talk live on YouTube, and it’s all working remarkably well. Here’s how we did it.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/Ecfs130BDws&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;transparency-and-trust--technology-and-tools&quot;&gt;Transparency and Trust &amp;gt; Technology and Tools&lt;/h2&gt;

&lt;p&gt;We worked hard to keep speakers and attendees in the loop about what was going on. I ran webinars and Q&amp;amp;A sessions last week for all of our workshop trainers and presenters, we ran another online session on Monday for attendees, and I’ve been running ad-hoc equipment checks and Zoom sessions with speakers to help them make sure everything’s set up and good to go. It’s been a very steep learning curve for all of us, but it’s worked.&lt;/p&gt;

&lt;p&gt;We also solved a lot of potentially complex problem by deciding, early on, that were going to trust people. Attendees would get invited to a Slack workspace but we weren’t going to require authentication; we’d run live streams as unlisted YouTube streams and trust our attendees not to share the links. This meant we didn’t have to worry about passwords and security and managing access, and could just focus on the attendee experience, the speakers and the content.&lt;/p&gt;

&lt;h2 id=&quot;familiar-tools&quot;&gt;Familiar Tools&lt;/h2&gt;

&lt;p&gt;Over the last few weeks, I’ve tested every single video conferencing and online chat platform I’d ever heard of, and a few that I hadn’t. WebEx, Whereby, GoToMeeting, Zoom, Hangouts, Skype, Slack, Discord, Jitsi - and I’m sure there’s a few that I missed.&lt;/p&gt;

&lt;p&gt;Let’s put it this way. None of them is remotely close to meeting together in real life, but all of them are vastly better than getting sick with coronavirus - so it comes down to what constraints and capabilities we’re optimising for, and what we’re prepared to compromise on.&lt;/p&gt;

&lt;p&gt;There are virtual conference platforms out there - but there’s no way we were going to have enough time to evaluate the options, choose a platform, get the crew and volunteers up to speed, set up accounts for the speakers and partners, and have it all ready to go by April 1st.&lt;/p&gt;

&lt;p&gt;We also wanted to make sure that, wherever possible, our speakers could present their sessions using equipment they already had; some of our speakers create a lot of video training and so are already tricked-out with greenscreens, cameras, and microphones - but a lot of them just have their development laptop, maybe a USB webcam and a headset microphone, and the last thing we wanted to do was ask speakers to go out and buy specialist equipment when we weren’t ourselves confident about the format and the technology yet. So we decided to go with familiar tools, hooked together in interesting ways - Zoom, Slack, Slido, YouTube, and e-mail.&lt;/p&gt;

&lt;p&gt;We set up a dedicated Slack workspace for NDC Copenhagen 2020, and decided that would be the backbone of the virtual conference. Folks who bought tickets got invited to join the Slack workspace, we use Slack for discussion, announcements, Q&amp;amp;A, chat, and - here’s the key part - at the end of the conference, we’re going to delete the workspace. That’s it: conference is over, see you at the next one.&lt;/p&gt;

&lt;h3 id=&quot;how-we-set-up-slack&quot;&gt;How We Set Up Slack&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Every Slack workspace has a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#general&lt;/code&gt; channel. We renamed &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#general&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#announcements&lt;/code&gt;, and locked it down so that only administrators could post to it and nobody could start threads.&lt;/li&gt;
  &lt;li&gt;We created dedicated channels for each conference track, which we used for speaker Q&amp;amp;A and audience feedback.&lt;/li&gt;
  &lt;li&gt;We set up a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#hallway-track&lt;/code&gt; for attendees, a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#speaker-lounge&lt;/code&gt; private channel for speakers, and a dedicated &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#organisers&lt;/code&gt; channel for the NDC crew and volunteers.&lt;/li&gt;
  &lt;li&gt;We locked down most options, so that attendees couldn’t invite other people, couldn’t create custom emoji, that kind of thing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;how-we-streamed-talks&quot;&gt;How We Streamed Talks&lt;/h3&gt;

&lt;p&gt;We went with a hybrid approach here. My initial idea was to use Zoom for all the sessions - to start a Zoom meeting for each track, and have speakers and attendees join and leave those meetings over the course of the day, just like folks wandering in and out of session rooms at a physical conference. I’d run a bunch of meetups and social events on Zoom, with 50+ people in the same meeting, and it worked pretty well.&lt;/p&gt;

&lt;p&gt;We had several requests from attendees to be able to watch the live streams without actually joining a room. That might sound a bit weird, but NDC Oslo has always had an overflow room; all nine tracks are projected on big-screen TVs, you can sit with a pair of headphones and jump between talks without disturbing anybody, and we wanted to try to replicate that for the virtual event.&lt;/p&gt;

&lt;p&gt;So, with some help from the crew from &lt;a href=&quot;https://videoforweb.no/&quot;&gt;Video for Web&lt;/a&gt; in Norway, here’s the setup we ended up running.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-04-03-going-virtual-ndc-copenhagen/image-20200403142821621.png&quot; alt=&quot;image-20200403142821621&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Each conference track is a Zoom meeting, that starts around 8am and runs right through the day. One of the NDC crew stays in that meeting all day - they’re the track host; they’ll help speakers get set up and connected.&lt;/li&gt;
  &lt;li&gt;The Video for Web crew have three separate PCs, each one connected to a different Zoom track. These are running Open Broadcast System (OBS) - they’ll do a live capture of the speaker video and screen share from the Zoom call, combine this with the Slido feed and some NDC branding, and stream the result to YouTube.&lt;/li&gt;
  &lt;li&gt;We email every speaker an invitation in advance, including the Zoom link and password to join the appropriate track, and the time of their meeting, including the timezone - more about that in a moment&lt;/li&gt;
  &lt;li&gt;The speaker joins the Zoom track 10-15 minutes before their talk session, checks their camera and audio are working, and begins sharing their screen.&lt;/li&gt;
  &lt;li&gt;When the talk starts, the Video for Web team switch over each stream to the live feed coming from Zoom via OBS.&lt;/li&gt;
  &lt;li&gt;We embed the YouTube stream link in each of the Slack channels for tracks 1-3.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For a couple of my talks, I also shared the direct Zoom link in the Slack channel in case folks wanted to join directly, which actually worked really well - more on this later. We also ran Slido alongside every session, giving the audience the chance to ask questions during the talks.&lt;/p&gt;

&lt;h2 id=&quot;what-we-learned&quot;&gt;What We Learned&lt;/h2&gt;

&lt;h3 id=&quot;some-folks-like-zoom-some-folks-like-youtube&quot;&gt;Some Folks Like Zoom, Some Folks Like YouTube&lt;/h3&gt;

&lt;p&gt;For the talks where I shared the Zoom link, I got 5-10 attendees joining the Zoom call directly, with the rest watching over YouTube.&lt;/p&gt;

&lt;p&gt;As a speaker, it’s amazing how much difference it makes even just having a few people “in the room” - we kept the audiences muted, but being able to see people smiling and raising hands makes the whole thing feel so much more like a live presentation. The YouTube stream has about 30 seconds of latency - so the quality’s great, but if you tell a joke, people on YouTube laugh 30 seconds later, so there’s really no way to use the YouTube stream as part of any realtime audience interaction.&lt;/p&gt;

&lt;p&gt;So for future events, I’m going to try to run a hybrid format where there’s a real-time option like Zoom or Hangouts for people who want to be part of the “live” audience, and a close-to-real-time YouTube stream for folks who want to dip in and out or have it on in the background.&lt;/p&gt;

&lt;h3 id=&quot;zoom-socialising-nice-idea-but-doesnt-really-happen&quot;&gt;Zoom socialising? Nice idea… but doesn’t really happen&lt;/h3&gt;

&lt;p&gt;One of my favourite things about conferences is the ad-hoc conversations that happen - in the coffee queue, over lunch, over drinks at the end of the day. We tried to replicate that using Zoom by spinning up chatrooms between sessions, but for some reason it just doesn’t work - nobody joins the rooms and it never really catches on. We did run an online quiz event last night, which got about 20 people online at one point - but most of them stayed muted with their webcams switched off; I could see them playing along and answering questions on Slido but I wouldn’t say we had much in the way of conversation.&lt;/p&gt;

&lt;p&gt;That said, there’s been some great conversation happening in the workshop groups over the past two days, so it’s not the tool or the format that’s the problem…  I’ve already written several posts about the weirdness of trying to socialise online; I suspect this is just one more thing that people haven’t figured out yet. We’ll get there.&lt;/p&gt;

&lt;h3 id=&quot;slido-needs-a-nudge-to-get-people-using-it&quot;&gt;Slido needs a nudge to get people using it&lt;/h3&gt;

&lt;p&gt;We ran Slido live Q&amp;amp;A sessions for all the conference talks. Some talks, it didn’t get used at all; but those talks where it &lt;em&gt;did&lt;/em&gt; get used, it was completely silent until somebody asked the first question - and then it got very lively.&lt;/p&gt;

&lt;p&gt;I think there’s two reasons for this… first, until somebody asks a question, it’s just a static panel in the corner of the live stream. Once the first question appears, there’s some movement and some action and people go “ooh, I want to use that!” I also know there’s some weird cultural things about certain audiences where folks are more than happy to interact but nobody wants to go first, and I haven’t worked out yet whether that’s more or less pronounced when the events are running online.&lt;/p&gt;

&lt;p&gt;So - if we’re going to use Slido in future, we’ll have a couple of prepared questions standing by to start things off; once folks see it being used, they’ll probably dive in and get the conversation going.&lt;/p&gt;

&lt;h3 id=&quot;usb-powered-lights-are-literally-the-devil-&quot;&gt;USB-powered lights are literally the devil… 😈&lt;/h3&gt;

&lt;p&gt;One of the speakers was having huge problems with audio - pops, crackles, weird electrical interference. We spent literally hours trying to track down the problem - reinstalling drivers, checking different mics on different platforms… and eventually worked out that they were running a USB-powered light off one of the computer’s USB sockets, which was generating some really, really weird interference.&lt;/p&gt;

&lt;p&gt;There’s a separate post coming up about how to set up and test equipment for doing live remote presentations, but it looks like one of the golden rules is: don’t have &lt;strong&gt;anything&lt;/strong&gt; plugged in to your USB ports that isn’t required to do your talk. Webcam, microphone, whatever you need to do your presentation or live demos - but if you’re using USB ports to power lights or charge your phone or anything, unplug it until you’re done.&lt;/p&gt;

&lt;h3 id=&quot;so-whats-next&quot;&gt;So… What’s Next?&lt;/h3&gt;

&lt;p&gt;In another life, I was getting on a plane tomorrow, flying from Copenhagen to Saint-Petersburg via Helsinki, presenting The Art of Code at DotNext, travelling from there to Riga and then on to Kyiv for .NET fwdays - and then back on the NDC bandwagon at the end of April for NDC Porto.&lt;/p&gt;

&lt;p&gt;But here in the upside-down, that’s all postponed, cancelled or gone virtual, so the next big thing on the schedule is NDC Porto, which we’re now running as another online event. Copenhagen’s gone great, we’ve learned a &lt;em&gt;lot&lt;/em&gt; about what works, what doesn’t work; we’ve got lots of interesting new ideas to try and lots of valuable feedback and information to share with all our speakers…&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-04-03-going-virtual-ndc-copenhagen/image-20200403161930704.png&quot; alt=&quot;image-20200403161930704&quot; /&gt;&lt;/p&gt;

&lt;p&gt;For now, though, I’m gonna open a beer, fire up a Zoom call and enjoy a bit of post-conference relaxation. See y’all for the next one!&lt;/p&gt;
</description>
          <pubDate>2020-04-03T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2020/04/03/going-virtual-ndc-copenhagen.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2020/04/03/going-virtual-ndc-copenhagen.html</guid>
        </item>
      
    
      
        <item>
          <title>Going Virtual, Part 3</title>
          <description>&lt;p&gt;Once upon a time, a very long time ago now, about last Friday, people used to go to pubs and bars and talk to each other face to face. You remember. Now that we’re living here in Generation Lockdown, that’s not a thing any more, and so we’re all socialising on Zoom and Hangouts and WebEx. Until last week, I’d never joined a “Zoom party” in my life… this week, I’ve joined four. It’s been interesting, and fun, but humans, we got some details to figure out if this thing’s gonna work.&lt;/p&gt;

&lt;p&gt;Let’s start with some observations. Human gatherings exist on a spectrum. At one extreme there’s the business meeting, in which a group of named invidivuals meet at a specified time, for a specified time. During this time, all the attendees are expected to give the meeting their undivided attention. One person speaks at a time, the rest listen, maybe somebody takes notes, and when it’s over, everybody leaves. Now I know as well as any of you that meetings in the real world don’t necessarily work like that, but most of the software we’re using to talk to each other now was designeс сердечным приветомd for business, and so it is designed to try and replicate that experience.&lt;/p&gt;

&lt;p&gt;A little further along, there’s dinners, tabletop gaming sessions - probably still the same group for the duration of the event, but it’s a little more relaxed. Then there’s house parties and pub nights, where there’s a definite location where “the thing” is happening, but people are wandering in and out, splitting into conversation groups, branching and merging and mingling and going outside for a cigaratte. And then there’s one of those nights when you head out alone, visit four or five different bars, seeing who’s where, bumping into random friends and friendly randoms.&lt;/p&gt;

&lt;p&gt;I love parties and pubs and bar crawls, but the social dynamic of those events is not 20 people sat around taking it in turns to speak. I miss the ebb and flow of social events. What’s the online equivalent of going to the kitchen for a beer and staying in there chatting for half an hour because everyone in the living room is currently talking about school fees and you’re bored off your ass? Where’s the quiet corner where you can sit for half an hour and have a proper catch up with a friend you’ve not seen in a while before going and rejoining the main party? Video chats are very binary - you’re in, or you’re out. Sure, you can be in with your mic muted and your video switched off, but at some point when you decide you’ve had enough, do you interrupt everyone to say goodbye, or just disconnect? Interrupting feels rude. Disconnecting feels rude. The cues around body language that we rely on to know when it’s OK to speak – or that we subconsciously pick up on to know that somebody else would like to say something – are much, much harder to pick up on. And the medium amplifies people’s naturally extrovert or introvert tendencies. People who are loud tend to get louder, people who are quiet tend to get quieter.&lt;/p&gt;

&lt;p&gt;There’s also weird stuff here about boundaries. We’re all stuck at home, nobody’s going outside. Everybody’s struggling with work/life balance, and sharing neat ways to deal with it. I’ve spoken to people who go out their front door in the morning, walk around the house and go in through the back door to start work. I’ve spoken to people who have sleeping pyjamas, work pyjamas and relaxing pyjamas - because hey, pyjamas are comfortable, but wearing the same ones for 24 hours is gross. But the psychological cues we get from physical spaces are gone. Office: drink coffee, do work. Pub: tell jokes, drink beer, it’s OK to swear. Home: sit around in your underwear watching TV. Now those spaces are gone and we need to work out how to simulate them. I live alone in a house with a lot of technology; right now I’m trying to work out how to join Zoom calls from my living room TV so I’m not doing all my socialising from my office – partly ‘cos it feels friendlier somehow, and partly because drinking beer at my desk regularly is likely to be the start of a very steep and slippery slope.&lt;/p&gt;

&lt;p&gt;I think we’ll figure this out. Some new social conventions are going to emerge, and quickly. Stuff that was just about tolerable when we used video chat for paid meetings will quickly become socially taboo. We’ll figure out how to digitise liminal spaces, we’ll come up with ways to use the tools and tech we’ve already got in interesting ways. This crisis is going to do for online communication tools what World War II did for aviation; we’re going to see an unprecedented wave of innovation and invention. Not all of it will work terribly well, but the things that do will be genuinely useful and a year or two from now we’ll be astonished that we ever tried to do online comms without them.&lt;/p&gt;

&lt;p&gt;Now if you’ll excuse me, I have to go. Somebody’s telegrammed me a whatsapp with a link to the slack with the link to the discord where they’re trying to decide whether to go to zoom, webex, jitsi or hangouts later, and I should probably join in. It’d be rude not to, right?&lt;/p&gt;
</description>
          <pubDate>2020-03-22T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2020/03/22/going-virtual-part-3.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2020/03/22/going-virtual-part-3.html</guid>
        </item>
      
    
      
        <item>
          <title>Going Virtual, Part 2</title>
          <description>&lt;p&gt;There’s this whole Einstein relativity space/time continuum thing - you move faster in space, you move slower in time, and apparently vice versa. You know the one. And I gotta say, it feels like since we all decided we were going to stop moving around in space, the moving-through-time thing has gone into overdrive… today is March 19th. Two weeks ago I was in Copenhagen, celebrating my partner’s birthday at Warpigs with a bunch of old and new friends from all over the world. We spent Friday cycling around Copenhagen on rented bikes; on Saturday we went to Sweden for the day. Sunday we flew back to London. A week later, Denmark had gone into total lockdown; as of today, &lt;a href=&quot;https://twitter.com/anshulsharmaa_/status/1240386026444673026&quot;&gt;Copenhagen is deserted&lt;/a&gt;, and pretty much the entire software industry is hunkered down, working from home and waiting for this thing to blow over.&lt;/p&gt;

&lt;p&gt;Last Friday I ran an online lunch over Zoom. &lt;a href=&quot;https://dylanbeattie.net/2020/03/13/going-virtual-part-1.html&quot;&gt;On Tuesday I ran a virtual meetup&lt;/a&gt;. Tuesday night I had online drinks with the Linebreakers - and then somebody messaged me to say there was a party going on on Microsoft Teams as part of the Virtual MVP Summit, so I stopped by there for a bit, and then invited everyone back to Zoom… but let’s face it, it wouldn’t be a proper conference if I didn’t turn up to some Official Corporate Drinks and drag half the attendees away to a sleazy rock bar somewhere. Nice to know that some traditions work just as well online as they do out in the Big Room.&lt;/p&gt;

&lt;p&gt;And yes, this is all still &lt;em&gt;very&lt;/em&gt; weird; the news cycle has gone into overdrive, nobody has the faintest idea what new restrictions and cancellations are going to be announced in the next 48 hours, and we’re all figuring a lot of things out very, very quickly. As you probably saw, &lt;a href=&quot;https://dylanbeattie.net/2020/03/18/ndc-online-community-ambassador.html&quot;&gt;NDC Conferences has hired me&lt;/a&gt; as their Online Community Ambassador for the next few months whilst we’re working this stuff out - which is awesome, because it means I can give this stuff my undivided attention until we’ve all worked out the details.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2020-03-19-going-virtual-part-2/2020-03-19_13-37-45.png&quot; alt=&quot;2020-03-19_13-37-45&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Today I ran our second virtual meetup - we had &lt;a href=&quot;https://twitter.com/fekberg&quot;&gt;Filip Ekberg&lt;/a&gt; talking about pattern matching in C#, with lots of technical content and code samples; &lt;a href=&quot;https://twitter.com/CliffordAgius&quot;&gt;Clifford Agius&lt;/a&gt; talking about building a mobile flight simulator out of a burger van, and &lt;a href=&quot;https://twitter.com/Tech_Christine&quot;&gt;Christine Seeman&lt;/a&gt; talking about ashtanga yoga and even showing us a couple of yoga positions live via Zoom, which was really cool. We had 66 live attendees at one point, most of them live on camera and voice chat, and we tried out a couple of different things based on feedback and ideas from Tuesday; here’s what we learned.&lt;/p&gt;

&lt;h3 id=&quot;timezones-are-weird&quot;&gt;&lt;strong&gt;Timezones are Weird&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;I mean, we knew that anyway - but we had a bunch of folks in Europe on their lunch break, Christine presenting live from Omaha, Nebraska where it was still early morning, and folks tuning in from as far away as Australia where it was already evening.&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Doing some evening coding and joining some UK friends on their lunchtime talk testing 😁&lt;br /&gt;&lt;br /&gt;For some reason &lt;a href=&quot;https://twitter.com/dylanbeattie?ref_src=twsrc%5Etfw&quot;&gt;@dylanbeattie&lt;/a&gt; is now on my TV 😂 &lt;a href=&quot;https://t.co/zeR85c4jej&quot;&gt;pic.twitter.com/zeR85c4jej&lt;/a&gt;&lt;/p&gt;&amp;mdash; Amy 🏡👩‍💻🐺 (@Amys_Kapers) &lt;a href=&quot;https://twitter.com/Amys_Kapers/status/1240626090806112259?ref_src=twsrc%5Etfw&quot;&gt;March 19, 2020&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;h3 id=&quot;it-takes-two-a-producer-and-a-presenter&quot;&gt;&lt;strong&gt;It Takes Two: A Producer and a Presenter&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;For today’s meeting, we had &lt;a href=&quot;https://twitter.com/MartinDotNet&quot;&gt;Martin Thwaites&lt;/a&gt; “producing” the meetup - I added him as a co-host on the Zoom call, and he was the one muting and unmuting people, switching the ‘Video Spotlight’ onto the speaker, answering questions in the chatroom, and that kind of thing. This was a huge improvement over Tuesday - it meant I was free to host the meetup, stand in front of a camera and actually talk to people without having to remember to click this and select that and keep one eye on the chat session.&lt;/p&gt;

&lt;p&gt;We &lt;em&gt;might&lt;/em&gt; have found some weird behaviour around “mute all” when working with multiple meeting hosts… further research required, but at the end of the session we found a few folks were stuck on mute and couldn’t unmute themselves. We’ll run some experiments and report back.&lt;/p&gt;

&lt;p&gt;But generally, I think we file this one under “best practice” - the producer runs the meeting, the presenter does the talking. It also wasn’t a problem at all that Martin and I weren’t in the same room - or indeed in the same city; the platform coped with this just fine.&lt;/p&gt;

&lt;p&gt;Martin’s comments on Zoom:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;“Going full screen you can undock the speaker view - having the the speaker view, spotlight view and chat all on different screens helped”&lt;/li&gt;
  &lt;li&gt;“Switching the Video Spotlight during the Q&amp;amp;A needs some thought - it’s good to see you asking the question and the speaker answering it, but I’m not sure how jerky that was for the audience.”&lt;/li&gt;
  &lt;li&gt;“Having speakers as co-hosts might help - finding them (in the gallery view) to spotlight them was a pain”&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;use-slido-for-questions&quot;&gt;&lt;strong&gt;Use Sli.do for questions&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;We set up a &lt;a href=&quot;https://sli.do&quot;&gt;Slido&lt;/a&gt; event for this, and shared the link in the chat and as part of my intro slides. One great thing to remember about online events - if you want people to go to a URL, you don’t have to rely on web addresses and QR codes on a PowerPoint slide; you can just get the producer to put the link into the chat.&lt;/p&gt;

&lt;p&gt;During this part of the meetup, I used the Open Broadcast System (OBS) rig to superimpose my ‘talking head’ onto the Sli.do “presenter view” so that video attendees could see the questions, which worked great (and looked quite professional as well!)&lt;/p&gt;

&lt;p&gt;During discussion at the end, Shahid Iqbal - who  recently hosted a virtual meetup as part of NDC DevOps - pointed out that it can be really confusing having questions going on Sli.do &lt;em&gt;and&lt;/em&gt; people posting questions in the Zoom chat. Something else that we’re going to need to figure out as we go along.&lt;/p&gt;

&lt;p&gt;Martin’s comments:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;“Dismissing questions is an easy way to moderate, they disappear from the screen. Having it behind you helped. Promoting the questions needs to be a bit more insync so that the video switching happens better.”&lt;/li&gt;
  &lt;li&gt;“I’m thinking that it might be an idea to think about streaming to a location that then outputs to Zoom, then something like OBS can combine the streams – so the idea would be that all the hosts somehow join a stream, and that stream is sent to the zoom… would make coordinating the speakers easier, but has some fairly significant downsides”&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;equipment-makes-a-difference&quot;&gt;&lt;strong&gt;Equipment Makes a Difference&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;I’m sure this doesn’t come as a surprise to anybody, but when you’re running an event like this, it’s quite apparent that people fall into three broad categories when it comes to online presenting.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;People who haven’t really ever done this before - which is fine; the main reason I’m running these virtual meetups is to give everyone a chance to practise, refine their setup, adapt their material and so on.&lt;/li&gt;
  &lt;li&gt;People who are clearly experienced presenters and have also done a lot of video calls and remote meetings, but haven’t really tried presenting online before. These are the folks who don’t need a whole lot of practice, but for whom investing in a better mic or a decent lighting setup will make a huge difference to how well their material works in a virtual format&lt;/li&gt;
  &lt;li&gt;People who basically talk to cameras for a living. The folks with the professional-quality microphones, the hi-def cameras and green screen backdrops.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, there’s definitely a law of diminishing returns here - particularly for live streaming, the difference between a built-in microphone and a £20 headset is obvious, but you’re probably not going to hear much difference between a £100 mic and a £500 mic once it’s been compressed through Zoom or Google Hangouts and streamed across the internet. I’ll be doing some more research around this over the next week or so, and hoping to share some recommendations with you all for mics, headsets, cameras, lights, etc. that’ll let you up your remote presentation game without breaking the bank.&lt;/p&gt;

&lt;p&gt;If you want to see what a really top-notch remote presenting rig looks like, check out this YouTube video of  &lt;a href=&quot;https://twitter.com/aaronpk/status/1238496278561972225&quot;&gt;Aaron Parecki&lt;/a&gt; running through his seriously impressive setup to see how deep the rabbit-hole &lt;em&gt;really&lt;/em&gt; goes:&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/yNzU-TPdxR4&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;What’s next? Well, Friday afternoon we’ll be running a remote live coding workshop with &lt;a href=&quot;https://brandewinder.com/&quot;&gt;Mathias Brandewinder&lt;/a&gt; - 16:00 UK time / 09:00 PDT - to figure out some details around running workshops with a lot of hands-on coding and collaboration; next week I’ll be running a couple of webinars with NDC for speakers and attendees at their forthcoming events, and I’m talking with the crew from &lt;a href=&quot;https://www.buildstuff.events/&quot;&gt;BuildStuff&lt;/a&gt; about doing some online stuff with them as well.&lt;/p&gt;

&lt;p&gt;Exciting times. Stay tuned.&lt;/p&gt;
</description>
          <pubDate>2020-03-19T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2020/03/19/going-virtual-part-2.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2020/03/19/going-virtual-part-2.html</guid>
        </item>
      
    
      
        <item>
          <title>NDC Online Community Ambassador</title>
          <description>&lt;p&gt;Hi everyone.&lt;/p&gt;

&lt;p&gt;Things are weird, and a little scary, right now. People, companies and countries are all responding to the COVID-19 crisis in different ways, but I think we’re all agreed that getting hundreds of people together in a conference centre for days at a time isn’t a good idea over the next few months.&lt;/p&gt;

&lt;p&gt;For technology conferences and events, this creates some interesting challenges. I’ve been working with the team from &lt;a href=&quot;https://ndcconferences.com/&quot;&gt;NDC Conferences&lt;/a&gt; since January, helping to build stronger relationships between NDC’s flagship conferences and the developer communities that they serve. None of us is going to be meeting face-to-face for a while - but we’re absolutely committed to supporting those people and communities while this situation is ongoing. Big events like NDC are a lot of fun, but they’re also an incredibly important part of our industry. Conference workshops are a critical source of revenue for a lot of people working on open source software. Sponsors and partners use conferences to connect with potential customers and employees – and for attendees, an event like NDC is a chance to meet the experts, discover new technology, connect with new people, maybe even find their next job. Turning a physical conference into an online event isn’t easy; there are challenges around content, technology, infrastructure, scheduling - all sorts of things that we’ll need to figure out. So that’s exactly what we’re going to do.&lt;/p&gt;

&lt;p&gt;To that end, I’ll be joining the NDC Conferences team for the next few months as their Online Community Ambassador, to help them – and all of us – figure out how to run the best possible virtual events. I’ll be running live trials, online workshops and virtual meet-ups to help speakers figure out the best way to adapt and present their material online. I’ll be testing equipment, platforms and formats, and working closely with all our speakers and experts to make sure we give all our virtual attendees the best possible experience. Obviously our initial focus will be on the technical content - workshops and talks - but we’re also going to try and virtualise the hallway tracks, the social elements and all the wonderful conversations that take place during an event like NDC.&lt;/p&gt;

&lt;p&gt;It’s been a really difficult couple of weeks for everybody, but in a way, the timing is fortunate. NDC Copenhagen at the beginning of April is one of the smaller NDC conferences; NDC Porto at the end of April is a little bigger, and NDC Oslo in June is our biggest event. Now, obviously when everything’s gone virtual the physical locations don’t matter so much - but by the time we get to Oslo, we’ll have had plenty of opportunities to experiment with formats, figure out the details and make sure it all goes smoothly. And I know as well as you do that there’s no way an online virtual event can replicate all the conversations, coffee, drinks, dinners and boat cruises and atmosphere that make events like NDC so memorable, but we’ll be doing everything we can to maintain the character of our events whilst we’re running things online. Same speakers, same sessions, same conversations - but you won’t have to pay Norwegian prices for a beer afterwards.&lt;/p&gt;

&lt;p&gt;We’ll be contacting all the confirmed speakers and workshop trainers soon to start working out some details. In the meantime, hang in there, take care of yourselves, and if you feel like hanging out online with some friendly nerds whilst we start figuring this stuff out, I’m running an open Slack at &lt;a href=&quot;https://join.slack.com/t/ursatile/shared_invite/zt-crtm2hly-EL2OKWozXeMvrvJ1y_Vong&quot;&gt;ursatile.slack.com&lt;/a&gt; and you’re all very welcome to stop by.&lt;/p&gt;

&lt;p&gt;Thanks all.&lt;/p&gt;

&lt;p&gt;Dylan&lt;/p&gt;

</description>
          <pubDate>2020-03-18T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2020/03/18/ndc-online-community-ambassador.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2020/03/18/ndc-online-community-ambassador.html</guid>
        </item>
      
    
      
        <item>
          <title>Going Virtual, Part 1</title>
          <description>&lt;p&gt;It’s March 2020 and everything is cancelled. COVID-19 has been declared a pandemic by the World Health Organisation, Norway is closed, Italy is closed, the US has banned travel from most European countries, and the British government is, um, advising us to wash our hands while singing songs until we develop “herd immunity”.&lt;/p&gt;

&lt;p&gt;With many countries now banning all large public gatherings, this has obviously hit IT conferences and community events &lt;em&gt;hard&lt;/em&gt;. Within the last 24 hours, &lt;a href=&quot;https://ndccopenhagen.com/page/conference-update/&quot;&gt;NDC Conferences has announced&lt;/a&gt; that their forthcoming events in Copenhagen, Porto and Oslo will be virtual online events; JUG.RU - the largest conference organiser in Russia - has postponed all of their forthcoming events until later in the year; local groups are switching to running virtual online meetups, and we’re generally all trying to figure out how to make the best of a pretty rough situation.&lt;/p&gt;

&lt;p&gt;We’ve all done remote conference calls – and a lot of the time, they suck. A bunch of bored people dialled into a Slack call or something but not really paying attention; glitchy video, distorted sound. But if we’re all going to be working from home and running virtual meetups for the next three months, we probably need to figure out how to make things better.&lt;/p&gt;

&lt;p&gt;Earlier this week, the &lt;a href=&quot;https://www.meetup.com/Women-in-Agile-London-UK/&quot;&gt;London Women in Agile&lt;/a&gt; group switched their regularly monthly meetup to a be a virtual online event. I joined as a participant and was absolutely blown away - it was orders of magnitude better than any video conference or remote meeting I’ve ever seen before. This was partly thanks to the speaker, Judy Rees, who in a stroke of serendipity was actually talking about how to have difficult conversations with remote teams – but who also brought a wealth of experience and expertise about how to actually run a large online meetup. The meetup was run over Zoom, and Judy used features like breakout rooms to mix up the format and keep people engaged - and it worked brilliantly.&lt;/p&gt;

&lt;p&gt;I came away from this meeting - or rather, disconnected from the call - thinking that there’s clearly a lot more to modern video conferencing platforms than I realised, and I’m sure I’m not the only one. So earlier today I organised a virtual online lunch - put links up on Twitter, Facebook and a couple of Slack groups I’m in, just asking if anybody wanted to hang out on Zoom and eat lunch together and play around with the capabilities of the platform…&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/posts/2020-03-13-going-virtual-part-1/screenshot.png&quot;&gt;&lt;img src=&quot;/images/posts/2020-03-13-going-virtual-part-1/screenshot.png&quot; alt=&quot;Screenshot of a Zoom meeting with lots of participants&quot; class=&quot;screenshot&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It worked great. Not perfect, but very, very good. I think everybody on the call saw a couple of new features they’d never seen before - the great thing about this kind of informal session is you can goof around and explore things; we were playing with virtual background, screen sharing, sharing jokes and links in the chat, folks helping each other figure out microphone muting settings and howto get stuff set up properly. And it was also a lot of fun - at the busiest point I think we had 32 people simultaneously online, and the platform handled it all pretty well.&lt;/p&gt;

&lt;p&gt;Some specific things to think about:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;At one point my home broadband dropped out for a minute. I switched my laptop to tether from my phone and reconnected - and, thankfully, everybody was still there; one of them had been promoted to the meeting organiser whilst I was disconnected, but it didn’t shut down the meeting or kick everybody out.&lt;/li&gt;
  &lt;li&gt;It actually worked pretty well via a tethered phone on 4G - not &lt;em&gt;quite&lt;/em&gt; as good as broadband, but still pretty good.&lt;/li&gt;
  &lt;li&gt;A handful of participants were clearly on slower connections, and it makes a huge difference - in fact, I’d say if you’re going to try and participate in any kind of remote session, you absolutely &lt;strong&gt;need&lt;/strong&gt; to get a connection that provides a solid 8Mbit+ of bandwidth. If you start breaking up whilst you’re speaking, or folks can’t hear you clearly, they’re probably just going to nod and smile and hope it wasn’t important - and that’s not a great experience for anybody involved.&lt;/li&gt;
  &lt;li&gt;The Gallery view works brilliantly - you can see everyone’s faces, you can see who’s talking, you can see who’s muted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the whole, it worked really well… so I’m going to do more. I’ll be using Zoom to run regular online lunches, drinks after work, that kind of thing – partly to give more people a chance to experiment with Zoom in a friendly context, but mainly because it’s just nice chatting to people in real time. (Yes, this means another hour or two of staring at screens instead of going outside… but hey, maybe outside isn’t all it’s cracked up to be…)&lt;/p&gt;

&lt;p&gt;I’m also going to be running some experiments with virtual online events, to try out different formats and tools. A lot of big events this year are switching to virtual-only, and obviously a virtual event is never going to be the same as a physical one – but it’s also a chance to connect with a lot of folks who wouldn’t be able to attend the physical conference in person anyway. Besides, a lot of the people expressing reservations about virtual conferences are the same people who will happily spend hours playing Zelda or Minecraft, or sitting watching Netflix and then chatting about it on Twitter. It’s not the medium, it’s the content.&lt;/p&gt;

&lt;p&gt;Wanna help us? We’ll be doing experiments, with Zoom and Twitch and YouTube and OBS and Slido and TwistCam. We’ll be fooling around with greenscreens and virtual slides. We’ll be running virtual meetups and online sessions, and we need content, we need participants, we need audiences who will honestly tell us all what works and what doesn’t, and bear with us while we figure all this out. If you want to participate in any of it, get in touch - &lt;a href=&quot;mailto:dylan@dylanbeattie.net&quot;&gt;email me&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/dylanbeattie&quot;&gt;find me on Twitter&lt;/a&gt;, join my new &lt;a href=&quot;https://join.slack.com/t/ursatile/shared_invite/zt-dco7z2xz-rdUircTJiUkdtmvZ39Dmqg&quot;&gt;Ursatile Slack team&lt;/a&gt; (yay another Slack! Just what you always wanted!) and get involved. Stock your fridge with snacks and drinks, figure out how to tether your phone in case your broadband goes wonky. Get a decent headset microphone - the mic built in to your laptop is &lt;em&gt;not&lt;/em&gt; good enough for this, but even a pair of £15 earbuds with a cord microphone is probably good enough.&lt;/p&gt;

&lt;p&gt;Oh yeah, and don’t forget to wash your hands.&lt;/p&gt;
</description>
          <pubDate>2020-03-13T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2020/03/13/going-virtual-part-1.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2020/03/13/going-virtual-part-1.html</guid>
        </item>
      
    
      
        <item>
          <title>Spleeter: Like Unbaking a Cake, but for Music</title>
          <description>&lt;p&gt;I occasionally still get actual emails – you know, like letters, but sort of digital – that make me stop what I’m doing and go “oh, wow, that’s a thing now?” And so it was a few days ago, when my old friend and occasional musical sparring partner Megan dropped me an email containing the phrase “yes, &lt;a href=&quot;https://github.com/deezer/spleeter&quot;&gt;Spleeter&lt;/a&gt; is the thing, it separates music into drums, bass, vocals, piano and everything else in exactly the way unbaking a cake works” – and accompanied by an absolutely sublime mashup of Tori Amos’ “Cornflake Girl” with Soundgarden’s “Black Hole Sun” created using said eldritch un-cake-baking technology.&lt;/p&gt;

&lt;p&gt;So… I absolutely had to poke this thing around to see how it works. And I did. And I made a video. Check it out.&lt;/p&gt;

&lt;iframe style=&quot;width: 640px !important; border: 1px solid #000;&quot; width=&quot;640&quot; height=&quot;360&quot; src=&quot;https://www.youtube.com/embed/XY_DmktIRIM&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;The track is one of my parody covers, “&lt;a href=&quot;https://youtu.be/woKUEIJkwxI&quot;&gt;HTML&lt;/a&gt;” (a cover of AC/DC’s “Highway to Hell”), recorded entirely in Logic Pro X - software drums, live guitar, bass and vocals. I’ve then bounced the whole thing down to an uncompressed AIFF file, fed that through Spleeter using the 4stem preset to extract vocals, bass, drums and “everything else”; you can hear the results in the video. Spleeter is available under an MIT license and it’s on GitHub at &lt;a href=&quot;https://github.com/deezer/spleeter&quot;&gt;https://github.com/deezer/spleeter&lt;/a&gt; - you’ll need to install Python and the Conda package manager, but I got it running on macOS without too much difficulty.&lt;/p&gt;

&lt;p&gt;And yes. Living in the future is &lt;em&gt;weird&lt;/em&gt;.&lt;/p&gt;

</description>
          <pubDate>2020-02-25T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2020/02/25/spleeter.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2020/02/25/spleeter.html</guid>
        </item>
      
    
      
        <item>
          <title>Look! I Made a Company!</title>
          <description>&lt;p&gt;October 31, 2019 was an interesting date for several reasons. First, it was the date when the United Kingdom was supposed to leave the European Union. Well, one of many such dates. It didn’t. Second, it was Hallowe’en, and I had plans to fly to Cluj-Napoca, the regional capital of Transylvania, to speak at a software conference. Yep – flying to Transylvania on Hallowe’en! Pretty cool, right? #goth&lt;/p&gt;

&lt;p&gt;Things didn’t quite work out that way. I found out around lunchtime on the 31st that Skills Matter, the company where I’d worked as CTO since 2017, was being placed into administration with immediate effect. Now, I’ve left jobs before. There’s a couple of months of notice and handover, time to plan your next move, figure out where you want to go, what you want to work on. Not this time. On Thursday I was a salaried employee; on Friday I was unemployed. Or “unexpectedly promoted to freelance”, if you prefer.&lt;/p&gt;

&lt;p&gt;I took a late flight to Romania on Friday so I could still speak at Devoxx on Saturday – and by the time I got back to London on Sunday evening, I’d decided I wasn’t going to look for another job. Not that the idea of applying for every “rockstar developer” job on LinkedIn didn’t have a certain appeal… but it was definitely time to try something else. Respond to change, instead of following a plan.&lt;/p&gt;

&lt;p&gt;I’ve always loved working with computers, but over the past five years I’ve also discovered that I love teaching. I love travelling, I love working with new people and unfamiliar technologies, seeing how different teams have solved the same problems. The sort of work I wanted to do didn’t seem particularly compatible with any kind of conventional job, so, like a lot of folks who work in IT, I decided to set up my own consultancy, specialising in training, software development and technology strategy. I set myself a three-month deadline, to get something up and running in time for NDC London – and here we are.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://ursatile.com/&quot;&gt;&lt;img src=&quot;/images/posts/2020-01-28-ursatile-banner.png&quot; style=&quot;border: 0; box-shadow: none&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everybody say hello to Ursatile Ltd, Company No. 12414586. That’s right, folks – I have a certificate and everything! I have a website, I have a name, I have a logo, and I have over 20 years’ experience working with computers (which are dumb, but occasionally do some very smart things) and with people (who are smart, but occasionally do some very dumb things) – in fact, the only thing I don’t have right now is clients. Now, I’m no business expert, but I’ve taken some very solid financial advice and apparently the whole “start your own company” thing works a lot better if you have some paying customers. So I’m gonna come right out and say it:&lt;/p&gt;

&lt;h1 id=&quot;hire-me&quot;&gt;Hire me!&lt;/h1&gt;

&lt;p&gt;I can teach you to code in .NET and JavaScript. I can teach you about software architecture. I can teach you public speaking, how to write amazing presentations, and how to communicate more effectively with your customers and teammates. I can show you how to design a hypermedia API – ideally after helping you figure out whether you need one and what you’re going to use it for. I can help you grow your team, build your next prototype or untangle your legacy codebase.&lt;/p&gt;

&lt;p&gt;If we think face-to-face is the best way to do something, I’ll travel. I grew up in Africa, I’m based in London, and I genuinely love airports. I’ve taught and spoken everywhere from Sydney to Siberia, I’ve met amazing people, I’ve connected with wonderful communities all over the world, and I’d love the chance to spend a couple of days with your team, helping you figure things out but also learning about your company, your technology and your culture.&lt;/p&gt;

&lt;p&gt;Remote is good too. I run workshops about how to use Slack, email and online tools like Trello and GitHub effectively; I’m not only happy to work remote, I might even be able to make it more effective for everyone involved.&lt;/p&gt;

&lt;p&gt;My calendar for 2020 is pretty open right now, and I’m doing introductory rates for new training courses for the next few months – but get in quick: according to my business plan, by July I’ll be booked solid until the end of 2025 and charging £75K for a five-minute Skype call, so get in touch and let’s book something in before that happens. ;)&lt;/p&gt;

&lt;p&gt;So, yeah. My new website is at &lt;a href=&quot;https://ursatile.com&quot;&gt;ursatile.com&lt;/a&gt;. That’s not really for you. I mean, take a look by all means, but you’re already here and you’re still reading this, so I figure we’re already cool. The website is so that search engines will find me, and people like your boss will hire me.&lt;/p&gt;

&lt;p&gt;I’ll be at &lt;a href=&quot;https://ndc-london.com/&quot;&gt;NDC London&lt;/a&gt; this week – and I’m hosting &lt;a href=&quot;https://pubconf.io/&quot;&gt;PubConf London&lt;/a&gt; on Friday if you can’t make it to NDC – so if you’re around, come and say hi, and if you’re not, you can always reach me on email at &lt;a href=&quot;mailto:hello@ursatile.com&quot;&gt;hello@ursatile.com&lt;/a&gt;. I’d obviously love to talk to anybody who’s interested in hiring me, but I also want to know what you’re all working on and what you think your challenges are going to be this year.&lt;/p&gt;

&lt;p&gt;And finally, a huge thanks to all the people who’ve helped me get this far. It’s been a difficult few months, and there’s still a lot of stuff I need to figure out, but so far everyone I’ve turned to for advice and help has been absolutely lovely – so thank you, all of you!&lt;/p&gt;
</description>
          <pubDate>2020-01-28T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2020/01/28/look-i-made-a-company.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2020/01/28/look-i-made-a-company.html</guid>
        </item>
      
    
      
        <item>
          <title>Speaking and Playing at GOTO Oslo 2020</title>
          <description>&lt;p&gt;You’ve all heard the phrase ‘real developers ship’ – but have you ever been on a real developers’ ship? I’m really excited to be speaking at GOTO Oslo 2020 – the conference that’s taking place on a ship! We’ll kick off on board the MS Crown Seaways in Copenhagen on Tuesday morning. At 4pm, there’s a break in the schedule while we set sail for Oslo. We arrive in Oslo on Wednesday morning, for another full day of conference sessions and masterclasses - and at 4pm we set sail back to Copenhagen. I’ll be presenting The Art of Code on Wednesday evening, and then joining The Linebreakers on stage for a live set of classic rock tunes with a software twist.&lt;/p&gt;

&lt;p&gt;Early bird tickets are on sale until February 1st from &lt;a href=&quot;https://goto-oslo.com/2020/registrations&quot;&gt;https://goto-oslo.com/2020/registrations&lt;/a&gt; - it should be a really great event and I’d love to see you there.&lt;/p&gt;
</description>
          <pubDate>2020-01-17T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2020/01/17/speaking-at-goto-oslo.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2020/01/17/speaking-at-goto-oslo.html</guid>
        </item>
      
    
      
        <item>
          <title>Shaving the Jekyll Yak</title>
          <description>&lt;p&gt;UPDATE: &lt;a href=&quot;#update-the-solution&quot;&gt;Thanks to the amazing power of Detective Twitter, I think we figured out what’s going on… &lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I use Jekyll and Github Pages for pretty much all my standalone websites these days, and I love it – the combination of static HTML, Markdown and YAML provides just enough data-driven behaviour to avoid lots of unnecessary duplication, but without any of the costs or overheads of running databases and server-side processing.&lt;/p&gt;

&lt;p&gt;Until today. Today, dear reader, I hit a bump. And it starts, like so many things, at the pub. At &lt;a href=&quot;https://pubconf.io/&quot;&gt;PubConf&lt;/a&gt;, to be more exact. &lt;a href=&quot;https://twitter.com/toddhgardner&quot;&gt;Todd Gardner&lt;/a&gt; is taking a bit of a break from travel in 2020, so I’m going to be running PubConf London at the end of January – which means I get commit access to pubconf.io (yay!) for the next two months. It’s built using Jekyll and hosted on Github Pages, and I’ve had a local version of the PubConf website running on my Macbook for the last 4-5 months without any hassles… except last week, I repaved my Macbook with a clean install of macOS Catalina. And apart from a couple of weird quirks that I’ve managed to isolate, it’s all good – including all my other Jekyll sites.&lt;/p&gt;

&lt;p&gt;So I grab a fresh clone of the PubConf source tree, do the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundle install&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundle exec jekyll serve&lt;/code&gt; invocation, and… boom.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pubconf.github.io &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;jekyll serve
Configuration file: /Users/dylanbeattie/Projects/pubconf.github.io/_config.yml
NOTE: Inheriting Faraday::Error::ClientError is deprecated&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; use Faraday::ClientError instead. It will be removed &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;or after version 1.0
Faraday::Error::ClientError.inherited called from /Library/Ruby/Gems/2.6.0/gems/octokit-4.14.0/lib/octokit/middleware/follow_redirects.rb:14.
            Source: /Users/dylanbeattie/Projects/pubconf.github.io
       Destination: /Users/dylanbeattie/Projects/pubconf.github.io/_site
 Incremental build: disabled. Enable with &lt;span class=&quot;nt&quot;&gt;--incremental&lt;/span&gt;
      Generating... 
jekyll 3.8.5 | Error:  wrong number of arguments &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;given 2, expected 1&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s not good. And I hadn’t even changed anything yet. So… here begins a protracted bout of yak-shaving.&lt;/p&gt;

&lt;p&gt;First thought: perhaps macOS Catalina uses a different default version of Ruby, that’s no longer compatible with this particular Jekyll configuration. So I spend an hour or so installing rbenv, the version manager that lets you run different Rubies side-by-side. No luck – &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2.4.5&lt;/code&gt;,&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt; 2.4.9&lt;/code&gt; and the default &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2.6.3&lt;/code&gt; all produce the same result.&lt;/p&gt;

&lt;p&gt;I Google the error message. Now, what I’m looking for here is something that’s &lt;em&gt;recent&lt;/em&gt; – looks like various folks have had this error message over the years, but I’m trying to work out what might have changed with that stack recently that could be causing this error to start happening. I find this, which looks pretty promising, including &lt;a href=&quot;https://github.com/envygeeks/jekyll-assets/issues/622#issuecomment-542363774&quot;&gt;this comment from 15 October&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I believe this is actually an issue with a change in sprockets 4.0.0:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I’ve never heard of Sprockets, but according to the internet “Sprockets is a Ruby library for compiling and serving web assets.” Now, there are two things here which I think are interesting:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;The code for pubconf.io uses a plugin called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jekyll-assets&lt;/code&gt; – which I don’t use on any of my other Jekyll sites.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jekyll-assets&lt;/code&gt; relies on Sprockets.&lt;/li&gt;
  &lt;li&gt;Sprockets released version 4.0.0 on October 8th.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At this point, I remember another detail that might be interesting: the PubConf site is built using TravisCI, which means there’s a separate build pipeline I can look at that’s nothing to do with my laptop. Now, here’s where it gets really interesting. I ran a fresh build on TravisCI, using the &lt;strong&gt;exact same source tree&lt;/strong&gt; that built cleanly on November 8th -and boom. Failing build. But, interestingly, it failed with a different error message:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Configuration file: /home/travis/build/PubConf/pubconf.github.io/_config.yml
NOTE: Inheriting Faraday::Error::ClientError is deprecated&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; use Faraday::ClientError instead. It will be removed &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;or after version 1.0
Faraday::Error::ClientError.inherited called from /home/travis/build/PubConf/pubconf.github.io/vendor/bundle/ruby/2.4.0/gems/octokit-4.14.0/lib/octokit/middleware/follow_redirects.rb:14.
            Source: /home/travis/build/PubConf/pubconf.github.io
       Destination: /home/travis/build/PubConf/pubconf.github.io/_site
 Incremental build: disabled. Enable with &lt;span class=&quot;nt&quot;&gt;--incremental&lt;/span&gt;
      Generating... 
  Liquid Exception: Liquid syntax error &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;/home/travis/build/PubConf/pubconf.github.io/_includes/head.html line 111&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;: Unknown tag &lt;span class=&quot;s1&quot;&gt;&apos;stylesheet&apos;&lt;/span&gt; included &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; /_layouts/default.html
jekyll 3.8.5 | Error:  Liquid syntax error &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;/home/travis/build/PubConf/pubconf.github.io/_includes/head.html line 111&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;: Unknown tag &lt;span class=&quot;s1&quot;&gt;&apos;stylesheet&apos;&lt;/span&gt; included 
The &lt;span class=&quot;nb&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;./_tools/build&quot;&lt;/span&gt; exited with 1.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;OK, so let’s take a closer look at what happens if I lock my local Ruby for this project to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2.4.5&lt;/code&gt;, the same version that’s used on TravisCI:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pubconf.github.io $ rbenv local 2.4.5
pubconf.github.io $ ruby --version
pubconf.github.io $ ruby 2.4.5p335 (2018-10-18 revision 65137) [x86_64-darwin19]
pubconf.github.io $ rm Gemfile.lock
pubconf.github.io $ gem install jekyll
pubconf.github.io $ gem install bundler
pubconf.github.io $ bundle
pubconf.github.io $ bundle exec jekyll serve
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, it still didn’t work – but check this out: I’m now getting the same error locally as I get on TravisCI:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Liquid Exception: Liquid syntax error &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;/Users/dylanbeattie/Projects/pubconf.github.io/_includes/head.html line 111&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;: Unknown tag &lt;span class=&quot;s1&quot;&gt;&apos;stylesheet&apos;&lt;/span&gt; included &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; /_layouts/default.html
jekyll 3.8.5 | Error:  Liquid syntax error &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;/Users/dylanbeattie/Projects/pubconf.github.io/_includes/head.html line 111&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;: Unknown tag &lt;span class=&quot;s1&quot;&gt;&apos;stylesheet&apos;&lt;/span&gt; included 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is progress! Because if my local system fails the same way as TravisCI does, then it stands to reason if I can get it working locally, the same fix will work on the Travis setup.&lt;/p&gt;

&lt;p&gt;So… what the hell does &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Unknown tag &apos;stylesheet&apos; included &lt;/code&gt; mean? Well, like before, there’s a bunch of search results for this, but none of them suggest anything that’s changed within the last few months.&lt;/p&gt;

&lt;p&gt;But I did find &lt;a href=&quot;https://github.com/envygeeks/jekyll-assets/issues/342&quot;&gt;this issue&lt;/a&gt; on the jekyll-assets Github repository, which includes this comment from &lt;a href=&quot;https://github.com/envygeeks/jekyll-assets/issues/342#issuecomment-360634222&quot;&gt;envygeeks&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Our website is an unreliable source of documentation (right now, I’m working on adding that to my pipeline.. it’ll be a few weeks yet.) Yes, every tag but &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{% asset %}&lt;/code&gt; has been removed. As for the GitHub-pages issue, this issue belongs to them, we’ve not changed anything by way of integration with Jekyll, other than how we hook in, we have people who happily use Jekyll-Assets 3.x with Github Pages and have no problems.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The smoking gun here:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Yes, every tag but &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{% asset %}&lt;/code&gt; has been removed.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, this comment is from January 2018. Which suggests that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{% stylesheet %}&lt;/code&gt; tag that’s used in the PubConf code templates has been deprecated since the release of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jekyll-assets&lt;/code&gt; v3.0.0, in November 2017.&lt;/p&gt;

&lt;p&gt;Now this is seriously weird. Sure, it explains what’s broken, and the fix is easy – add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gem &quot;jekyll-assets&quot;, &quot;~&amp;gt; 2.3.2&quot;&lt;/code&gt; to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;group: jekyll_plugins do&lt;/code&gt; section in the project’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile&lt;/code&gt; – but doesn’t give us any clues at all as to why this didn’t break until now. Because as far as I can tell, on 8th November 2019 – just over a month ago – TravisCI was quite happily running that build and ending with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jekyll-assets&lt;/code&gt; version 2.something and everything just worked. Despite the fact it, apparently, should have stopped working on November 2017, two years earlier, when the first 3.x version of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jekyll-assets&lt;/code&gt; was shipped.&lt;/p&gt;

&lt;p&gt;So, the sprockets thing was a rabbit hole, it’s nothing to do with macOS Catalina, and chatting with Todd, turns out he’s got another site with an identical configuration, on a freshly-paved laptop, that’s pulling down &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jekyll-assets 2.4.0&lt;/code&gt; without having to specify a version.&lt;/p&gt;

&lt;p&gt;If anybody has any bright ideas as to what’s going on, I’d be really curious to hear them. But, as happens so often in the wonderful world of modern web development, I got better things to do than try to figure out what caused the weird impossible bug that’s not only now fixed, but according to all the available evidence should never even have happened in the first place…&lt;/p&gt;

&lt;h3 id=&quot;update-the-solution&quot;&gt;UPDATE: THE SOLUTION&lt;/h3&gt;

&lt;p&gt;One of the best reasons I’ve found for writing blog posts like this is that you get a lot more eyeballs on the problem than just your own – and thanks to some &lt;a href=&quot;https://twitter.com/shiftkey/status/1205142441579499520&quot;&gt;sterling work&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/shiftkey&quot;&gt;@shiftkey&lt;/a&gt;, we figured it out. Or at least came up with a pretty plausible explanation.&lt;/p&gt;

&lt;p&gt;Travis CI has a 28-day bundler cache, which means if you run a build within 28 days of the previous build, it’ll reuse the same set of dependencies. And it’s been a busy few years for PubConf, with events taking place frequently enough that it’s entirely possible this is the first time since 2017 that 28 days has passed without somebody or something kicking off a Travis CI build. So when I kicked off that build yesterday, that was the first time in literally years that Travis has built the whole project from scratch – and so picked up v3.x of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jekyll-assets&lt;/code&gt; plugin.&lt;/p&gt;

&lt;p&gt;But really, what’s at the root of all this grief is our old friend, the &lt;a href=&quot;https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/&quot;&gt;leaky abstraction&lt;/a&gt;. Jekyll and Github Pages offer a really simple, elegant solution for creating static websites – and most of the time, you can just run the handful of commands in the documentation, write your Markdown, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git push&lt;/code&gt; it and everything works. You don’t have to know about Ruby versions and bundler and &lt;a href=&quot;https://bundler.io/v1.3/rationale.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile.lock&lt;/code&gt;&lt;/a&gt; – all that stuff is supposed to be abstracted away so you can focus on writing content. And it all works great, right up until it doesn’t. We could probably have avoided this problem by locking the plugin to a specific version, or by &lt;a href=&quot;https://www.google.com/search?q=should+I+commit+Gemfile.lock&quot;&gt;committing Gemfile.lock to revision control&lt;/a&gt; – but there’s drawbacks to both of those approaches, and neither of them warrants any mention in the documentation for Jekyll or for the jekyll-assets plugin.&lt;/p&gt;

&lt;p&gt;The other problem with abstractions is that the more complexity they’re hiding, the harder it is to figure out what’s going on when something stops working. Remember, this thing started out as:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;jekyll 3.8.5 | Error:  wrong number of arguments (given 2, expected 1)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;when the actual thing that went wrong was something closer to:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;jekyll 3.8.5 | Error: you haven&apos;t run a fresh build in over two years and we&apos;ve just picked up a major release of the jekyll-assets plugin that hasn&apos;t been used in this project before, and which is no longer compatible with the syntax that&apos;s used in your website templates. You&apos;ll either need to update your templates so you&apos;re using the new asset tags required by jekyll-assets 3.x, or modify your _config.yml to specify that you need version 2.x of the jekyll-assets plugin.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</description>
          <pubDate>2019-12-12T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2019/12/12/shaving-the-jekyll-yak.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2019/12/12/shaving-the-jekyll-yak.html</guid>
        </item>
      
    
      
        <item>
          <title>NDC Meetups in Portugal in January</title>
          <description>&lt;p&gt;I’m going to be heading out to Portugal in January to speak at some free meetups – if you’re 
in Lisbon or Porto and want to kick off 2020 with some cool tech events, come along! I’m gonna be in &lt;a href=&quot;https://www.eventbrite.co.uk/e/ndc-meetup-with-dylan-beattie-farfetch-porto-tickets-85068182223&quot;&gt;Porto on January 7th&lt;/a&gt; and &lt;a href=&quot;https://www.eventbrite.co.uk/e/ndc-meetup-with-dylan-beattie-farfetch-lisbon-tickets-85618592515&quot;&gt;Lisbon on January 8th&lt;/a&gt; talking about the secrets to happy code. It’s a talk that covers a whole range of ideas, from psychology and neurochemistry to user experience design, logging and monitoring, and how we developers can use those ideas to ship better software and create happier teams. Plus pizza, drinks, awesome people and plenty of time to chat.&lt;/p&gt;

&lt;p&gt;Thanks to &lt;a href=&quot;https://ndc-conferences.com&quot;&gt;NDC Conferences&lt;/a&gt; for putting the whole thing together, and to &lt;a href=&quot;https://farfetchtechblog.com/en/&quot;&gt;Farfetch&lt;/a&gt; for hosting. And if you like the sound of this, you should check out &lt;a href=&quot;https://ndcporto.com/&quot;&gt;NDC Porto&lt;/a&gt; – two days of workshops and two days of conferences with a fantastic lineup of international speakers, all set in one of the most beautiful cities I’ve ever visited.&lt;/p&gt;

&lt;p&gt;The January meetups are free, but you’ll need to register if you’re coming along:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.eventbrite.co.uk/e/ndc-meetup-with-dylan-beattie-farfetch-porto-tickets-85068182223&quot;&gt;Porto on January 7th&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.eventbrite.co.uk/e/ndc-meetup-with-dylan-beattie-farfetch-lisbon-tickets-85618592515&quot;&gt;Lisbon on January 8th&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And if anyone wants to join me for &lt;a href=&quot;https://en.wikipedia.org/wiki/Francesinha&quot;&gt;francesinhas&lt;/a&gt; after the Porto meetup, I’m definitely up for that. :)&lt;/p&gt;
</description>
          <pubDate>2019-12-11T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2019/12/11/ndc-meetups-in-portugal-in-january.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2019/12/11/ndc-meetups-in-portugal-in-january.html</guid>
        </item>
      
    
      
        <item>
          <title>Better Meetings with Agenda Defender</title>
          <description>&lt;p&gt;Have you ever been to one of those meetings that has an agenda something like this?&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;13:00 Update from CEO (5 mins)&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;13:10 Update from CFO (5 mins)&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;13:20 Update on the Singapore project (Alex)&lt;/strong&gt; &lt;br /&gt;
&lt;strong&gt;13:30 Update on the OAuth2 project (Chris)&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;13:45 Any other business&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;13:55 FINISH&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Everyone arrives on time, you kick off at 13:00 as scheduled… but then after twenty minutes, the CEO is still talking, the agenda has been completely forgotten, and you’re all trying to work out whether they’re going to just skip everyone else’s updates so they can wrap up at 2pm as scheduled, or the one-hour meeting is just going to drag on and on until it’s taken up most of the afternoon and that’s the day wiped out?&lt;/p&gt;

&lt;p&gt;I have. And, yes, on a handful of occasions I’ve been the person doing the talking – sorry! But sticking to an agenda when you’re talking is hard – like a lot of so-called “soft skills”, participating in meetings effectively is way harder than most people think. Being able to plan and deliver a five-minute update in five minutes, or a half-hour presentation in exactly half an hour, is a skill that takes time and effort to improve.&lt;/p&gt;

&lt;p&gt;So I built a thing that can help. It’s called Agenda Defender, it’s at &lt;a href=&quot;https://agendadefender.app&quot;&gt;https://agendadefender.app&lt;/a&gt;, and it can help you and your team keep your meetings on schedule.&lt;/p&gt;

&lt;p&gt;First, type (or paste) your agenda. Local times, 24-hour clock, one line per item, with the finish time as the last entry.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2019-12-02-better-meetings-with-agenda-defender.assets/schedule.png&quot; alt=&quot;Agenda Defender: schedule view&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Click “Let’s go!” and it’ll turn each agenda item into a live progress bar. And that’s it.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2019-12-02-better-meetings-with-agenda-defender.assets/progress.gif&quot; alt=&quot;Agenda Defender: live view&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Sure, one little web app probably isn’t going to fix dysfunctional communication styles overnight – if you work somewhere where long rambling meetings that wander all over the place are a regular occurrence, introducing something like this might come as a bit of a shock. First few times you try it, you’ll get some resistance – especially from those people who think having a slot on the agenda means they can talk for as long as they want. But if we’re going to improve, we really need two things – feedback, so we know what we’re doing wrong, and incentive, so we have a reason to pay attention to the feedback. And once you’ve tried running meetings with &lt;a href=&quot;https://agendadefender.app/&quot;&gt;Agenda Defender&lt;/a&gt; a few times, folks will get the hang of it – the live schedule will make it easier to pace yourself when you’re speaking, and the fact everybody can see who’s hitting their scheduled slots gives everybody an incentive to keep things on track. And if you do need to reschedule, or shuffle things around mid-meeting, no problem –  just edit the agenda and start again.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tired of meetings that should have been an email? Fed up with delivering features that you thought were perfect, only to hear “um… that’s not quite what we wanted”? Wasting time on writing documentation nobody’s ever going to read? Check out my &lt;a href=&quot;https://dylanbeattie.net/workshops/communication-for-developers/&quot;&gt;Communication for Developers&lt;/a&gt; workshop, and find out how I can help you and your organisation build the right thing, faster, and go home smiling when you’re done.&lt;/em&gt;&lt;/p&gt;
</description>
          <pubDate>2019-12-02T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2019/12/02/better-meetings-with-agenda-defender.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2019/12/02/better-meetings-with-agenda-defender.html</guid>
        </item>
      
    
      
        <item>
          <title>About That Meetup.com Announcement</title>
          <description>&lt;p&gt;&lt;em&gt;Remember: everything on this site, including this post, is my personal opinion, and does not reflect any official position of either Skills Matter or of the London .NET User Group.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2019-10-15-meetup-announcement-screenshot.jpg&quot; alt=&quot;A screenshot of the meetup.com website announcing their plans to charge members a $2 fee to register for events&quot; /&gt;&lt;/p&gt;

&lt;figcaption&gt;&lt;a href=&quot;https://www.meetup.com/lp/paymentchanges&quot;&gt;https://www.meetup.com/lp/paymentchanges&lt;/a&gt;, 15 October 2019&lt;/figcaption&gt;

&lt;p&gt;The online community platform Meetup.com (hereafter known simply as Meetup) has &lt;a href=&quot;https://www.Meetup/lp/paymentchanges&quot;&gt;just announced&lt;/a&gt; that “beginning in October, members of selected groups will be charged a small fee to reserve their spot at events”. (&lt;a href=&quot;https://web.archive.org/web/20191015032536/https://www.Meetup/lp/paymentchanges&quot;&gt;archive.org link&lt;/a&gt;) From the phrasing of their announcement, it sounds like Meetup will be charging people a $2 fee to register to attend an event – even if the event is free – and that 100% of that revenue will go to Meetup and none of it is passed on to the event organisers.&lt;/p&gt;

&lt;p&gt;I’ve organised a &lt;a href=&quot;https://twitter.com/LondonDotNet&quot;&gt;.NET meetup group in London&lt;/a&gt; since 2016. Since 2018, I’ve also been CTO at &lt;a href=&quot;http://skillsmatter.com/&quot;&gt;Skills Matter&lt;/a&gt;, a company based in London that hosts &lt;a href=&quot;https://skillsmatter.com/meetups&quot;&gt;hundreds of free meetups and tech events&lt;/a&gt; every year. That’s given me some fairly unique insights into the relationship between the Meetup platform, the people who organise and attend free community events, and the logistics behind running a free event.&lt;/p&gt;

&lt;p&gt;Meetup is an odd platform. It’s a lowest common denominator, that’s used by songwriting clubs, running groups, coffee mornings and book clubs, as well as the tech meetups that we all know and love. And over the last few years, they’ve made changes to that platform that’s made it harder to run a tech meetup on their platform. For example, they’ve deprecated their rich text editor in favour of a minimal plain-text interface that makes it much more difficult to include speaker biographies and photographs in an event description.&lt;/p&gt;

&lt;p&gt;Many people also aren’t aware that group organisers have no access to our group members’ contact information. They provide tools that let us contact attendees by email, but there’s no way to download a list of members’ email addresses. And it costs. The London .NET User Group is part of the &lt;a href=&quot;https://dotnetfoundation.org/&quot;&gt;.NET Foundation&lt;/a&gt;, which means they pay our subscription costs, but even running free events on their platform costs $19.99 per group, per month.&lt;/p&gt;

&lt;p&gt;As a technology platform, it’s not great. It has mediocre usability and a very limited feature set. We can’t share slides or videos. There’s no way to share feedback with our speakers. There’s no facility for attendees to share their dietary requirements. There’s no sort of statistical modelling or forecasting to give organisers some idea of how many people will actually show up – I know of people in the LDNUG group on Meetup who have RSVPed ‘yes’ to every single event since 2016 and have never actually showed up to one, but it will still quite happily list them as ‘attending’ every single time, so good luck figuring out how much beer and pizza you’re going to need on the night. &lt;a href=&quot;https://twitter.com/pati_gallardo&quot;&gt;Patricia Aas&lt;/a&gt; very neatly summarised it thus:&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;This is basically three things:&lt;br /&gt;1) A mailing list &lt;br /&gt;2) A RSVP/waiting list &lt;br /&gt;3) An event page &lt;a href=&quot;https://twitter.com/Meetup?ref_src=twsrc%5Etfw&quot;&gt;@Meetup&lt;/a&gt; does none of these things well and already is way too expensive...&lt;/p&gt;&amp;mdash; Patricia Aas (@pati_gallardo) &lt;a href=&quot;https://twitter.com/pati_gallardo/status/1183842899450105856?ref_src=twsrc%5Etfw&quot;&gt;October 14, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;But we still run London .NET on Meetup, and here’s why. When I announce one of our events on Meetup, I know I’ll get 50-100 RSVPs within 48 hours. If it’s a well-known speaker, I’ll get more like 200. And around 20% of those RSVPs will be complete strangers – people who have never been to one of our events before, who aren’t on any of our Slack chats or following us on Twitter. The only way those people even know that our event is even happening is that they found it on Meetup – and Meetup is so dominant in this space that if your event’s not on Meetup, it may as well not exist.&lt;/p&gt;

&lt;p&gt;The recent announcement here about charging attendees to register is interesting. Now, $2 is not a big deal for tech events – here in London that’s significantly less than a cup of coffee. And I wouldn’t be surprised if it’s part of some long-term strategy by Meetup to pivot to a subscription model by jacking the prices up now, and then offering a subscription model in a few months time that looks very reasonable by comparison (“Hey, check out Meetup Unlimited – only $5/month to register for as many events as you want!”). After all, Meetup is owned by WeWork, who are &lt;a href=&quot;https://www.theguardian.com/business/2019/oct/15/wework-sack-staff-workers-adam-neumann&quot;&gt;losing vast amounts of money right now&lt;/a&gt; and probably desperate for revenue; I suspect if you took a look at their business plan, free meetup attendees are listed under ‘stuff that doesn’t make enough money’. There’s a &lt;a href=&quot;https://www.freecodecamp.org/news/the-wework-meetup-debacle-and-a-new-chapter/&quot;&gt;great write-up by Quincy Larson&lt;/a&gt;, the founder of freeCodeCamp.org, about this as well.&lt;/p&gt;

&lt;p&gt;But the impact of that change, from free to $2, is far more psychological than financial – and &lt;em&gt;that’s&lt;/em&gt; why it might be a game-changer. The announcement has already inspired people around the world to start working on their own version of Meetup – I’m on a &lt;a href=&quot;https://discord.gg/PRCXpUc&quot;&gt;Discord channel right now&lt;/a&gt; where the FreeCodeCamp folks are coordinating one such solution, and over 100 people have joined that channel in the time it’s taken to write this post. Only time will tell whether this is a flash in the pan or genuine critical mass, but their solution is called Chapter, it’s over at &lt;a href=&quot;https://github.com/freeCodeCamp/chapter/&quot;&gt;github.com/freeCodeCamp/chapter&lt;/a&gt;, and it’s definitely worth keeping an eye on.&lt;/p&gt;

&lt;p&gt;Now, I love the way the tech community rallies like this around an opportunity – but we really don’t need a dozen different open-source codebases hosted on hundreds of isolated websites, each of them running a handful of local groups. And when you get into mobile apps, where you can’t just spin up a Docker container and import your data, the fragmentation problem is even more pronounced. The problem that Meetup solves is not a hard technical problem. It’s a social problem. If people can’t find your group, nobody’s going to come to your event.&lt;/p&gt;

&lt;p&gt;That’s not a problem you can solve with database schemas and JavaScript frameworks. Sure, tech plays a role here – but if there’s one thing we can all learn from Meetup, it’s that mindshare and user adoption are way more important than features and design when it comes to growing user groups, events and communities. People will put up with poor usability and terrible design if it connects them to the right people – and if it doesn’t, there’s no amount of shiny technology and beautiful UX that’s going to make up for it.&lt;/p&gt;

&lt;p&gt;I’ll be really interested to see what happens next.&lt;/p&gt;
</description>
          <pubDate>2019-10-15T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2019/10/15/about-that-meetup-com-announcement.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2019/10/15/about-that-meetup-com-announcement.html</guid>
        </item>
      
    
      
        <item>
          <title>Publishing Talks</title>
          <description>&lt;p&gt;I used to blog a lot more than I do now. There’s a couple of reasons for that, not least that a lot of the discussion and engagement that used to happen on blog post comments has now moved to Twitter and StackOverflow. But mainly, it’s because these days almost all of my ‘writing’ time goes into creating talks for conferences and user groups - and that takes a huge amount of time.&lt;/p&gt;

&lt;p&gt;Most of my talks start life as an outline - a title and a very broad overview of what I want to talk about - and an Evernote file full of code snippets, web links, observations and notes and scribblings. The first stage of turning that into an actual talk is to sit down and write it. I find it much, much easier to develop, edit and restructure ideas when they’re still text, before I’ve got as far as any slides or visuals or anything - and that means ending up with a written version of pretty much the entire talk, or one possible version thereof.&lt;/p&gt;

&lt;p&gt;This is not an article or a report. It’s written for a very different audience, and intended to create a very different kind of engagement. When you’re talking on a stage, you have a connection with the audience. If something doesn’t work, you can normally tell immediately and can explain, clarify, apologise if necessary - live presentation is a daunting medium, but it’s also a very forgiving one if you know how to work with it.&lt;/p&gt;

&lt;p&gt;It’s also not a script. Every time I deliver a talk, it’s different - there will be phrasing and jokes and references in the written form that get skipped because of timing, or because they didn’t fit with the context of a particular event, or because I just plain forgot to mention them. It happens.&lt;/p&gt;

&lt;p&gt;It might help to think of it as an essay, in the original sense of the word:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;Essayer&lt;/em&gt; is the French verb meaning “to try” and an &lt;em&gt;essai&lt;/em&gt; is an attempt. An essay is something you write to try to figure something out.&lt;/p&gt;

  &lt;p&gt;Figure out what? You don’t know yet. And so you can’t begin with a thesis, because you don’t have one, and may never have one. An essay doesn’t begin with a statement, but with a question. In a real essay, you don’t take a position and defend it. You notice a door that’s ajar, and you open it and walk in to see what’s inside.&lt;/p&gt;

  &lt;p&gt;​	- Paul Graham, &lt;a href=&quot;http://www.paulgraham.com/essay.html&quot;&gt;The Age of the Essay&lt;/a&gt;, 2014&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, I’ve written five new talks this year - six, if you include collaborations. I write the way I talk, and I talk at about 200 words per minute - so a 60-minute talk is roughly equivalent to writing a 12,000-word essay. And that essay then forms the backbone of the talk itself - the structure, the flow and the narrative; it’s what I use to create slides, work out where to use code samples, animation and video. But until now, I’ve never actually published it.&lt;/p&gt;

&lt;p&gt;So I’m going to try an experiment. I’ve taken one of the talks I prepared this year - a talk called ‘The Cost of Code’ - and cleaned it up a bit. I’ve added most of the slides used in the live presentation, incorporated links to my source material, and put it all online.&lt;/p&gt;

&lt;p&gt;It’s at &lt;a href=&quot;https://dylanbeattie.net/articles/the-cost-of-code/&quot;&gt;https://dylanbeattie.net/articles/the-cost-of-code/&lt;/a&gt; - check it out, and let me know what you think.&lt;/p&gt;
</description>
          <pubDate>2019-10-06T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2019/10/06/publishing-talks.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2019/10/06/publishing-talks.html</guid>
        </item>
      
    
      
        <item>
          <title>Migrating from Blogger to GitHub Pages</title>
          <description>&lt;p&gt;So, you might have noticed I’ve done a bit of decorating… welcome to the 2019 incarnation of dylanbeattie.net. If you’re interested in how I migrated ten years’ worth of blog posts onto a Jekyll site hosted on GitHub Pages, read on. If you’re not, there’s funny videos over on &lt;a href=&quot;/music&quot;&gt;/music&lt;/a&gt; that you might enjoy.&lt;/p&gt;

&lt;p&gt;Still here? Cool! OK, so the very first version of dylanbeattie.net was a site I built in PHP back in 1999 or so, which I hosted on my own physical server – way back in the days when both of those things were still considered a pretty neat idea. About ten years I ago I started using Blogger, and before long I scrapped the old PHP site, pointed the main domain at my Blogger site, and just sort of got on with it.&lt;/p&gt;

&lt;p&gt;Blogger’s been a pretty good platform, but these days I find I actually need a website to do a lot more than just host blog posts and the occasional ‘about me’ page, so a few months back I started looking around for alternatives. I’d considered setting up something like Umbraco, or a custom Wordpress site, but even those felt a bit like over-engineering for what I actually needed to do. My requirements basically boiled down to:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A really easy way to post articles – text and links with some simple formatting and the occasional image&lt;/li&gt;
  &lt;li&gt;The option to use rich HTML pages and custom layouts&lt;/li&gt;
  &lt;li&gt;Preserving the URIs of all my existing posts and pages – remember, &lt;a href=&quot;https://www.w3.org/Provider/Style/URI&quot;&gt;cool URIs don’t change&lt;/a&gt;. Besides, I’ve got a decades’ worth of Google-fu invested in those pages. It’d suck if all those links and bookmarks stopped working.&lt;/li&gt;
  &lt;li&gt;Something that looked good, and a responsive layout that worked well across devices.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There’s also a couple of things I decided I definitely didn’t want:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Comments. It’s been years since I saw a decent discussion in the comments thread of a blog post. We have Twitter and Reddit for that now.&lt;/li&gt;
  &lt;li&gt;Bootstrap. I’m sure it’s lovely. I just don’t like it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Back in January, I was chatting with &lt;a href=&quot;https://twitter.com/toddhgardner&quot;&gt;Todd Gardner&lt;/a&gt; about how he created the &lt;a href=&quot;https://pubconf.io/&quot;&gt;PubConf website&lt;/a&gt;, and he suggested I take a look at GitHub Pages and a thing called Jekyll. I spent an evening playing around with it, and was basically hooked.&lt;/p&gt;

&lt;h3 id=&quot;jekyll-and-github-pages&quot;&gt;Jekyll and GitHub Pages&lt;/h3&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; the code for this site is available on GitHub at &lt;a href=&quot;https://github.com/dylanbeattie/dylanbeattie.net&quot;&gt;github.com/dylanbeattie/dylanbeattie.net&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So here’s how it works. You write your site in HTML or Markdown, and create datasets in YAML. Jekyll compiles them into a static HTML site. It supports a &lt;a href=&quot;https://shopify.GitHub.io/liquid/&quot;&gt;templating language called Liquid&lt;/a&gt;, which lets you create templates, conditionals, navigation – you can actually do some pretty sophisticated stuff with it, but everything happens at build time. Which is actually very cool.&lt;/p&gt;

&lt;p&gt;The really great part is that GitHub Pages has built-in support for Jekyll – so you build your site locally, using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundle exec jekyll serve&lt;/code&gt; to view it, then you just push the repo to GitHub and it’ll build and deploy it for you. It’s a beautifully simple workflow, but one that affords a surprising amount of flexibility once you get the hang of it.&lt;/p&gt;

&lt;p&gt;Here’s some fun little things I learned along the way, and a couple of gotchas to watch out for if this inspires you to try something similar.&lt;/p&gt;

&lt;h3 id=&quot;design-and-layout&quot;&gt;Design and layout&lt;/h3&gt;

&lt;p&gt;The design for my new site is the &lt;a href=&quot;https://html5up.net/arcana&quot;&gt;Arcana&lt;/a&gt; template by &lt;a href=&quot;https://html5up.net/&quot;&gt;HTML5 UP&lt;/a&gt;, which is not only beautiful, responsive and really well put together, but is also released – like all HTML5 UP’s templates – under a &lt;a href=&quot;https://html5up.net/license&quot;&gt;Creative Commons Attribution 3.0 License&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“…which means you can use them for personal stuff, use them for commercial stuff, change them however you like … all for free, yo. In exchange, just give HTML5 UP credit for the design and tell your friends about it :)”.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Which is lovely. You see, I’m quite happy doing my own design work – and that’s the problem. I enjoy it. I get sidetracked. I spend hours – days – tweaking layouts and playing around with things, when I should be writing content and fixing bugs… and I end up with something that’s OK, but nothing like as good as some of the amazing templates and layouts that are available online, for free. So credit (and thanks!) to HTML5 UP for the design. I’ve made a couple of tweaks and added a few extra bits, but the layouts, grid, responsive design, navigation, typography – that’s all them. Oh, and it’s all built on &lt;a href=&quot;https://sass-lang.com/&quot;&gt;SASS&lt;/a&gt; – which is also supported by GitHub Pages. Did I mention how much I love this platform?&lt;/p&gt;

&lt;h3 id=&quot;migrating-old-blog-posts&quot;&gt;Migrating old blog posts&lt;/h3&gt;

&lt;p&gt;This turned out to be a lot easier than I expected. There’s a Ruby gem designed to do exactly this – check out the &lt;a href=&quot;https://import.jekyllrb.com/docs/blogger/&quot;&gt;Jekyll documentation&lt;/a&gt; and this &lt;a href=&quot;http://krisrice.io/2017-10-06-migrating-my-blog-from-blogger-to-github-pages-with-jekyll/&quot;&gt;blog post from Kris Rice&lt;/a&gt; which goes into a bit more detail about it.&lt;/p&gt;

&lt;p&gt;Once I’d migrated all the posts from my old Blogger site, I wanted to make sure all the old page URLs would still work. I didn’t want to mess too much with Jekyll’s conventions about structuring posts, though – Jekyll uses a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;YYYY-MM-DD-title&lt;/code&gt; format for URLS compared to Blogger’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;YYYY-MM-title&lt;/code&gt; format, and so I simultaneously wanted to adopt the Jekyll convention for new posts, to make things easier, but also to preserve the addresses of all my old posts so bookmarks and links still work.&lt;/p&gt;

&lt;p&gt;Turns out the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jekyll-migrate&lt;/code&gt; gem adds some lines to the top of each migrated HTML file – what Jekyll refers to as ‘front matter’ – that can help:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;layout&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;post&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;How to *really* break the internet.&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;2016-03-23T12:08:00.000Z&apos;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;author&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Dylan Beattie&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; 
&lt;span class=&quot;na&quot;&gt;modified_time&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;2016-03-23T14:06:37.095Z&apos;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;blogger_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;tag:blogger.com,1999:blog-7295454224203070190.post-8996121207980120854&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;blogger_orig_url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;http://www.dylanbeattie.net/2016/03/how-to-really-break-internet.html&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There’s also a Jekyll plugin called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;redirect-from&lt;/code&gt;, which is part of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github-pages&lt;/code&gt; bundle (and, yes, it works a little like the infamous &lt;a href=&quot;https://en.wikipedia.org/wiki/COMEFROM&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;COMEFROM&lt;/code&gt;&lt;/a&gt; flow control statement beloved of sadistic esolang designers). So all I had to do was trawl through every file in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_posts&lt;/code&gt; folder, find that line with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blogger_orig_url&lt;/code&gt; in it, parse the path part out of the URL, and add a line underneath like this:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;blogger_orig_url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;http://www.dylanbeattie.net/2016/03/how-to-really-break-internet.html&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;redirect_from&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/2016/03/how-to-really-break-internet.html&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;(Sorry, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;awk&lt;/code&gt; fans – I actually did this with a global search &amp;amp; replace in VS Code…)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That’s it – Jekyll now hosts all those pages at their new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/2016/03/23/how-to-really-break-internet.html&lt;/code&gt; addresses, and requesting the original URL returns this:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;html&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;lang=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;en-US&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;meta&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;charset=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Redirecting&lt;span class=&quot;ni&quot;&gt;&amp;amp;hellip;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rel=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;canonical&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/2016/03/23/how-to-really-break-internet.html&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;location&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/2016/03/23/how-to-really-break-internet.html&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;meta&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;http-equiv=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;refresh&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;content=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;0; url=/2016/03/23/how-to-really-break-internet.html&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;meta&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;robots&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;content=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;noindex&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Redirecting&lt;span class=&quot;ni&quot;&gt;&amp;amp;hellip;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/2016/03/23/how-to-really-break-internet.html&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Click here...&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It’s not &lt;em&gt;quite&lt;/em&gt; perfect – an HTTP &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;301 Moved Permanently&lt;/code&gt; would strictly speaking be a better way of handling this – but hey, perfect is the enemy of good enough. It works. Ship it.&lt;/p&gt;

&lt;h3 id=&quot;events-schedule-and-flags&quot;&gt;Events schedule and flags&lt;/h3&gt;

&lt;p&gt;You see that little sidebar there, with all the events I’m speaking at and the flags in it of the countries I’m gonna be visiting? The flag images are from &lt;a href=&quot;https://github.com/gosquared/flags&quot;&gt;GoSquared&lt;/a&gt;, and available under an MIT license. To display them in the schedule, I’ve used a &lt;a href=&quot;https://sass-lang.com/documentation/values/lists&quot;&gt;SASS list&lt;/a&gt; to generate CSS rules for each flag in the set:&lt;/p&gt;

&lt;div class=&quot;language-scss highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cm&quot;&gt;/* _flags.scss: generate .flag-xx CSS classes for elements with flag backgrounds */&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;$countries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_abkhazia&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_basque-country&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_british-antarctic-territory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_commonwealth&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
  &lt;span class=&quot;n&quot;&gt;_england&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_gosquared&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_kosovo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_mars&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_nagorno-karabakh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_nato&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_northern-cyprus&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
  &lt;span class=&quot;n&quot;&gt;_olympics&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_red-cross&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_scotland&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_somaliland&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_south-ossetia&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_united-nations&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_wales&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
  &lt;span class=&quot;n&quot;&gt;AD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AI&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AQ&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AU&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AW&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AX&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AZ&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BA&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BB&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
  &lt;span class=&quot;n&quot;&gt;BG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BI&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BJ&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BW&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BZ&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CA&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CC&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CI&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CK&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
  &lt;span class=&quot;n&quot;&gt;CL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CU&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CV&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CW&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CX&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CZ&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DJ&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DK&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DZ&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EC&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ER&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
  &lt;span class=&quot;n&quot;&gt;ES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ET&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EU&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FI&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FJ&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FK&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GA&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GB&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GI&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GQ&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
  &lt;span class=&quot;n&quot;&gt;GT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GU&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GW&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HK&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HU&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IC&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IQ&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;JE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;JM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;JO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
  &lt;span class=&quot;n&quot;&gt;JP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;KE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;KG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;KH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;KI&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;KM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;KN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;KP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;KR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;KW&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;KY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;KZ&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LA&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LB&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LC&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LI&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LK&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LU&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LV&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
  &lt;span class=&quot;n&quot;&gt;LY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MA&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MC&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MK&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ML&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MQ&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MU&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MV&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MW&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MX&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
  &lt;span class=&quot;n&quot;&gt;MY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MZ&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NA&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NC&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NI&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NU&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NZ&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PA&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PK&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
  &lt;span class=&quot;n&quot;&gt;PN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PW&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;QA&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RU&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RW&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SA&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SB&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SC&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SI&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SK&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
  &lt;span class=&quot;n&quot;&gt;SN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ST&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SV&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SZ&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TC&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TJ&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TK&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TV&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; 
  &lt;span class=&quot;n&quot;&gt;TW&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TZ&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UA&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;US&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UZ&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VA&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VC&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VI&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VU&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;YE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;YT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ZA&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ZM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ZW&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;@each&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$country&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$countries&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nc&quot;&gt;.flag-&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to-lower-case&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$country&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;background-image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;url(images/flags/flat/64/&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$country&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;.png)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/dylanbeattie/dylanbeattie.net/blob/master/_data/schedule.yml&quot;&gt;actual schedule is a YAML file&lt;/a&gt;, and Jekyll generates the markup with the correct CSS classes based on the country codes from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;schedule.yml&lt;/code&gt;. It’s easy to add new dates. The only thing it won’t do is automatically archive old ones- ‘cos hey, static content, remember? – but I reckon I can live with that for now.&lt;/p&gt;

&lt;p&gt;(And yes, you can &lt;em&gt;absolutely&lt;/em&gt; invite me to speak at your event by editing schedule.yml and sending me a PR. That would be really cool. :) )&lt;/p&gt;

&lt;h3 id=&quot;speaker-bios&quot;&gt;Speaker bios&lt;/h3&gt;

&lt;p&gt;I’ve got a bunch of different speaker bios over at my &lt;a href=&quot;/about&quot;&gt;about me&lt;/a&gt; page, and I really wanted a way to pick one and copy it to the clipboard as either HTML, Markdown or plain text – different events use different formats when you’re submitting to their CFP or filling out speaker details, and it’s a little thing that would make life easier.&lt;/p&gt;

&lt;p&gt;The bios themselves are stored as multiline Markdown snippets in &lt;a href=&quot;https://github.com/dylanbeattie/dylanbeattie.net/blob/master/_data/speaker_bios.yml&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/_data/speaker_bios.yml&lt;/code&gt;&lt;/a&gt;. Instead of allowing Jekyll to just render Markdown &amp;gt; HTML automatically, I’ve got this little loop in the code for the &lt;strong&gt;about me&lt;/strong&gt; page:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{% for bio in site.data.speaker_bios %}
&lt;span class=&quot;nt&quot;&gt;&amp;lt;article&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;hr&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;span&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;clipboard-links&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;data-src-id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{{bio.id}}-html&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;copy html&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;data-src-id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{{bio.id}}-markdown&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;copy markdown&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;data-src-id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{{bio.id}}-text&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;copy text&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    {{ bio.word_count }} Word Bio ({{ bio.char_count }} characters)
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
  {{ bio.content | markdownify }}
	&lt;span class=&quot;nt&quot;&gt;&amp;lt;input&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;hidden&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{{bio.id}}-markdown&quot;&lt;/span&gt; 
	  &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{{ bio.content }}&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;nt&quot;&gt;&amp;lt;input&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;hidden&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{{bio.id}}-html&quot;&lt;/span&gt;
	  &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{{ bio.content | markdownify | escape_once }}&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;nt&quot;&gt;&amp;lt;input&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;hidden&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{{bio.id}}-text&quot;&lt;/span&gt;
	  &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{{ bio.content | markdownify | strip_html }}&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
{% endfor %}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;so each snippet is rendered to the page as HTML (via the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;markdownify&lt;/code&gt; filter), and also captured in three hidden variables – one markdown, one HTML, one plain text. Finally, there’s &lt;a href=&quot;https://github.com/dylanbeattie/dylanbeattie.net/blob/master/assets/js/main.js&quot;&gt;some JavaScript&lt;/a&gt; attached to the ‘copy xxx’ links that’ll copy the hidden input value into an invisible &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;textarea&lt;/code&gt; and copy it.&lt;/p&gt;

&lt;h3 id=&quot;syntax-highlighting&quot;&gt;Syntax Highlighting&lt;/h3&gt;

&lt;p&gt;One of the great things about writing posts and pages in Markdown is that it’s so easy to embed code samples - just wrap them in three backticks either side, something known as a &lt;em&gt;fenced code block&lt;/em&gt;. Jekyll has a code formatting plugin called &lt;a href=&quot;http://rouge.jneen.net/&quot;&gt;Rouge&lt;/a&gt;, that’s included with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github-pages&lt;/code&gt; bundle, which allows you to specify a language for your code snippets and it’ll highlight them for you:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;``` html
&lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;This HTML snippet will get &lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://rouge.jneen.net/&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;highlighted&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
```
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To get this to work, I had to enable highlighting in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_config.yml&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;markdown&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;kramdown&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# enable rouge syntax highlighting&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;highlighter&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;rouge&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I also had to add a stylesheet to the site with the various coloring rules - all Rouge does is wrap all the code keywords,
etc. in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;span&amp;gt;&lt;/code&gt; tags with CSS classes on them, so I found &lt;a href=&quot;https://github.com/mojombo/tpw/blob/master/css/syntax.css&quot;&gt;this syntax.css file&lt;/a&gt; on GitHub, dropped it into my site’s CSS, and it worked.&lt;/p&gt;

&lt;h2 id=&quot;gotchas&quot;&gt;Gotchas&lt;/h2&gt;

&lt;p&gt;Pretty much everything I tried to do with Jekyll and GitHub Pages worked as documented, but I did hit two weird gotchas that caused a fair bit of head-scratching until I figured out what was going on…&lt;/p&gt;

&lt;h3 id=&quot;case-sensitive-filesystems&quot;&gt;Case sensitive filesystems&lt;/h3&gt;

&lt;p&gt;I’ve done most of the dev work for this site locally, on macOS 10.14, using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundle exec jekyll serve&lt;/code&gt; to preview and test things. And when I finally pushed the whole thing up to GitHub and switched on the GitHub Pages feature, everything worked – except the flag images in the schedule sidebar. And it took me a good couple of hours to figure out what was going on.&lt;/p&gt;

&lt;p&gt;My original &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;flags.scss&lt;/code&gt; file looked like this:&lt;/p&gt;

&lt;div class=&quot;language-scss highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$countries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ad&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ae&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;af&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ag&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ai&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;al&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$country&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$countries&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nc&quot;&gt;.flag-&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$country&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;background-image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;url(images/flags/flat/64/&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$country&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;.png)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;which generated a bunch of CSS rules that looked like this:&lt;/p&gt;

&lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nc&quot;&gt;.flag-ad&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;background-image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;url(images/flags/flat/64/ad.png)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;.flag-ae&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;background-image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;url(images/flags/flat/64/ae.png)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, if you look closely at the &lt;a href=&quot;https://github.com/gosquared/flags/tree/master/flags/flags-iso/flat/48&quot;&gt;files in the GoSquared flagset I’m using&lt;/a&gt;, you’ll notice the filenames are:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;AD.png
AE.png
AF.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I’d dropped their entire flag set into my project, and pushed the whole thing to GitHub.&lt;/p&gt;

&lt;p&gt;Now, I’m guessing that GitHub Pages is hosted on Linux. And Linux uses case-sensitive filesystems, whereas macOS – where I’d been testing everything – uses a ‘case preserving’ filesystem. In other words, if you ask macOS for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ae.png&lt;/code&gt;, it’ll happily give you &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AE.png&lt;/code&gt;, but on Linux, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ae.png&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AE.png&lt;/code&gt; are different files.&lt;/p&gt;

&lt;p&gt;So my CSS rule was telling my browser to ask GitHub Pages for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ae.png&lt;/code&gt;, and GitHub’s Linux servers are going “nope. Not found.” – ‘cos the only file they’ve got is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AE.png&lt;/code&gt;, which is COMPLETELY DIFFERENT. Obviously.&lt;/p&gt;

&lt;p&gt;I could see two ways to fix this. One was to rename all the files in the GoSquared collection… but it turns out it’s surprising fiddly to rename a file from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FILENAME&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;filename&lt;/code&gt; on macOS. The other, easier way is just to uppercase all the ISO country codes in the SCSS list, like this. I threw a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;to-lower-case($country)&lt;/code&gt; into the class name there because, well, I think uppercase CSS rules are vulgar.&lt;/p&gt;

&lt;div class=&quot;language-scss highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$countries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AI&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$country&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$countries&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nc&quot;&gt;.flag-&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to-lower-case&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$country&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;background-image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;url(images/flags/flat/64/&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$country&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;.png)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;dns-https-cloudflare-and-github-pages&quot;&gt;DNS, HTTPS, Cloudflare and GitHub Pages&lt;/h3&gt;

&lt;p&gt;The last piece of the puzzle was to get the whole site using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dylanbeattie.net&lt;/code&gt; (no &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www&lt;/code&gt;) as the canonical URL, and to get everything running over HTTPS. I host all my DNS with Cloudflare, because it’s free and works really well – and, like a lot of people, I’d relied on Cloudflare’s HTTP+DNS proxy service for a while to provide HTTPS for my old Blogger site (check out Troy Hunt’s &lt;a href=&quot;https://www.troyhunt.com/heres-why-your-static-website-needs-https/&quot;&gt;Here’s Why Your Static Website Needs HTTPS&lt;/a&gt; for more on why this is a good idea.)&lt;/p&gt;

&lt;p&gt;I wanted to use GitHub Pages to enforce HTTPS, but when I switched the Cloudflare DNS for dylanbeattie.net to point to GitHub’s servers, I couldn’t switch on the option to enforce HTTPS – instead, I got this error:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Enforce HTTPS — Unavailable for your site because your domain is not properly configured to support HTTPS&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and when browsing my new site, I got a warning that:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;This server could not prove that it is dylanbeattie.net; its security certificate is from www.github.com. This may be caused by a misconfiguration or an attacker intercepting your connection.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After about a day of head-scratching – change a DNS setting, wait six hours to see if anything happens, change something else, repeat – I contacted GitHub Support.&lt;/p&gt;

&lt;p&gt;Turns out that Cloudflare’s DNS+HTTP Proxy feature actually interferes with the certificate issuing mechanism used to support HTTPS on GitHub Pages. GitHub asks for DNS servers so it can issue a certificate, but if you’ve enabled Cloudflare’s HTTP proxy feature (which is on by default, even on their free plan), Cloudflare responds to the DNS query with its own server addresses and so GitHub can’t see that your domain is pointing at GitHub Pages.&lt;/p&gt;

&lt;p&gt;Logged into Cloudflare, switched the records for dylanbeattie.net over to DNS only, and boom – certificate was issued within the hour and everything was up and running.&lt;/p&gt;

</description>
          <pubDate>2019-08-14T12:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2019/08/14/migrating-from-blogger-to-github-pages.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2019/08/14/migrating-from-blogger-to-github-pages.html</guid>
        </item>
      
    
      
        <item>
          <title>Saint-Petersburg, Russia - Tips for Tech Travellers</title>
          <description>As I&apos;m heading back to Saint-Petersburg for DotNext 2019, this seemed like a nice moment to repost something I wrote after my first visit to Russia back in 2017.&lt;br /&gt;
&lt;br /&gt;
1. As a guest in Russia, it is vitally important to keep moving at all times. This is because if you stop moving for more than, say, fifteen seconds, your hosts will assume this is because you have run out of roast pork and sausages. It is impossible to sit down at a table in Saint Petersburg without somebody serving you a massive plate of meat. My hosts explained that the Russian phrase for &apos;no thankyou&apos; is &apos;nyet, spasiba&quot; but based on my experience I think this literally translates as &quot;please bring me roast duck and cabbage now, and after that some more sausages&quot;.&lt;br /&gt;
&lt;br /&gt;
2. Sausages in Russia occupy the culinary niche that is normally reserved for, say, some carrots in Western cuisine. You will find sausages chopped in a salad, boiled, fried, steamed, and served as accompaniments to all sorts of things.&lt;br /&gt;
&lt;br /&gt;
3. Saint Petersburg is BIG. The average street here is wider than most London postcodes. If you have to walk three blocks, wear good shoes and take a bottle of water with you. Monuments and war memorials here are built to such a scale I can only assume they are intended to leave civilisations in neighbouring star systems in no doubt as to the nobility and sacrifice of the Russian military. We should give a special mention to the churches, which are not only huge, but we can conclude from the style of their decorated domes and minarets that the builders thought God had a bit of a thing for cupcakes.&lt;br /&gt;
&lt;br /&gt;
4. Riding the Saint Petersburg metro will seem uncannily familiar to anybody who has read Jules Verne&apos;s &quot;Journey to the Centre of the Earth&quot;. After buying your metro token from the ticket machine and passing through one of the metal detector arches - for which you are not required to empty your pockets or anything, meaning that they go off constantly and are consequently ignored by everybody including the police - you step onto an escalator approximately fourteen miles in length. The tunnels and station interchanges suggest that tunnelling machines in the former Soviet Union were available in two sizes - Extra Large and Stupidly Extra Large - and the interchanges are so big that the connected stations have different names. If, say, the Red Bull Air Race ever needed to be held indoors due to inclement weather, the pedestrian interchange tunnel at Spasskaya/Sadovaya would provide an ideal venue.&lt;br /&gt;
&lt;br /&gt;
5. For typography enthusiasts, the Cyrillic alphabet is an absolute delight... except when it comes to handwriting. Cursive Cyrillic is a minefield of hilarity and ambiguity. Any doctors who feel they have exhausted the possibilities of the Latin alphabet when it comes to writing illegible prescriptions will find Cyrillic a rich seam of possibility.&lt;br /&gt;
&lt;br /&gt;
6. The Russian people are lovely and friendly... once you get used to the fact that a Russian telling you a joke will initially sound like they&apos;re interrogating about some war crimes you may or not have committed. It helps to keep a Polish person with you, since they seem to know the correct point to start laughing, thus giving a handy cue to the slightly baffled English speakers participating in the conversation.&lt;br /&gt;
&lt;br /&gt;
7. An English person trying to speak Russian is the funniest thing that has ever happened. The Russian equivalent of the Edinburgh Festival consists entirely of English people attempting to pronounce the names of Saint Petersburg metro stations whilst the audience drink vodka and roar with laughter.&lt;br /&gt;
&lt;br /&gt;
8. Vodka must be served no warmer than -273.1499 degrees Celsius. To offer someone vodka that is merely refrigerated could cause a serious diplomatic incident.&lt;br /&gt;
&lt;br /&gt;
9. Most consonants in Russian have a &apos;hard&apos; and a &apos;soft&apos; pronunciation, which, like tonal Cantonese or the tongue-clicks of the Khoisan language family, is completely impenetrable to foreigners. It is very important, however: based on my attempts to speak Russian to waiters, I have concluded that the elusive hard vs soft &apos;T&apos; sound must be the difference between saying &quot;no thank you, I have eaten so much food I think I need to to go the hospital&quot; and &quot;could I please have some more roast pork and boiled sausages&quot;&lt;br /&gt;
&lt;br /&gt;
10. There are a lot of Chinese tourists in Saint Petersburg. Local regulations prohibit them from travelling in groups of fewer than fifty. If you arrive to check in to your hotel moments after a Chinese tour party has arrived, you may wish to pass the time whilst you wait by reading the collected works of Dostoyevsky or walking to Vladivostok and back.&lt;br /&gt;
&lt;br /&gt;
11. Every single car in Russia has a dashboard camera recording video footage of the journey, presumably so that when your cab gets cut up by another one and causes a six-car pile-up, the driver can pay the repair bills by sharing the crash footage on YouTube and hoping it goes viral. Most cab drivers keep their radio tuned to the local high-energy Europop station, so when they do inevitably have a massive crash, the resulting YouTube footage already has the appropriate soundtrack.&lt;br /&gt;
&lt;br /&gt;
In short... it was AWESOME. The city is beautiful and vast and unlike anywhere I have ever been, the people could not have been more welcoming and friendly; arrival and departure was an absolute breeze thanks to the brand new, hyper-modern airport terminal building, and the metro is a great way to get around (and clearly signed in English throughout.) And if you don&apos;t fancy jumping through the administrative hoops of getting a Russian visa, you can visit SPB on a cruise ship from Tallinn or Helsinki and stay for up to 72 hours without having to get a visa, which is kinda cool.&lt;br /&gt;
&lt;br /&gt;
Just don&apos;t stay up drinking vodka the night before your 5am departure. Trust me on this. :)</description>
          <pubDate>2019-05-14T09:42:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2019/05/14/saint-petersburg-russia-tips-for-tech.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2019/05/14/saint-petersburg-russia-tips-for-tech.html</guid>
        </item>
      
    
      
        <item>
          <title>A release notes bookmarkdownlet for Pivotal Tracker</title>
          <description>&lt;p&gt;One of the best ways to keep the rest of your team up to speed with what your dev teams are doing is release notes - even if all you&apos;re doing is gently reassuring the rest of the organisation that yes, you are patching security vulnerabilities, fixing bugs and quietly making things better.&lt;/p&gt;&lt;p&gt;I love using Slack for this - set up a channel where you post a friendly summary of everything that&apos;s being released whenever you deploy to production. Now, here at Skills Matter we&apos;re not quite doing continuous deployment yet - we work off short-lived branches that merge to master several times a day, but then once master has passed regression testing on our staging environment, the actual deployment to production is a manual process - we open a master &amp;gt; production pull request in GitHub, merge it, and Heroku does the rest.&lt;/p&gt;&lt;p&gt;We track work in progress using &lt;a href=&quot;https://www.pivotaltracker.com/&quot;&gt;Pivotal Tracker&lt;/a&gt;, and we use the various ticket state transitions as:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Start&lt;/strong&gt; &amp;gt; a developer has created a branch and begun coding the features&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Finish&lt;/strong&gt; &amp;gt; the code is done; time for code review&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deliver&lt;/strong&gt; &amp;gt; the code has been reviewed, merged to master and deployed to the staging environment&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Accept&lt;/strong&gt; &amp;gt; the code has been tested on staging; stakeholders know it&apos;s ready, and&amp;nbsp;we&apos;re good to go live.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Now, this definitely isn&apos;t the best branch/merge strategy in the world, but it&apos;s the one we&apos;ve inherited and the one we&apos;re using until we&apos;ve made enough changes to the codebase to be able to deploy PRs directly to review environments.&lt;/p&gt;&lt;p&gt;So when we do a production release, one of the things I do is to check which features are included in that release - that&apos;ll form a note that&apos;s part of the master &amp;gt; production pull request, and we&apos;ll also share it with the company via Slack. And this is a bit tedious, so today I threw together a little JavaScript bookmarklet that&apos;ll automate it for you.&lt;/p&gt;&lt;p&gt;Select the stories you want in Pivotal Tracker, click the bookmarklet, and it&apos;ll copy them to your clipboard as Markdown-formatted bullet points with the story IDs linked to your Pivotal project.&lt;/p&gt;&lt;p&gt;The JS code is here - add a bookmark, paste this whole lot (including the &lt;code&gt;javascript:&lt;/code&gt; into the URL field:&lt;/p&gt;&lt;script src=&quot;https://gist.github.com/dylanbeattie/1818634309693a32fcf99960a292a441.js&quot;&gt;&lt;/script&gt;&lt;br /&gt;
&lt;p&gt;And here&apos;s what it looks like in action:&lt;/p&gt;&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/UABvXClJI68&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen&gt;&lt;/iframe&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
</description>
          <pubDate>2019-03-22T18:58:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2019/03/22/a-release-notes-bookmarkdownlet-for.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2019/03/22/a-release-notes-bookmarkdownlet-for.html</guid>
        </item>
      
    
      
        <item>
          <title>PostgreSQL, Heroku, .NET Core and npgsql</title>
          <description>I&apos;ve been having fun this week building data visualisation dashboards that pull information straight out of our &lt;a href=&quot;https://www.postgresql.org/&quot;&gt;PostgreSQL&lt;/a&gt; databases and use &lt;a href=&quot;https://www.asp.net/core/overview/aspnet-vnext&quot;&gt;ASP.NET Core&lt;/a&gt; to do some LINQ transformations and aggregation on the data. All our data is hosted on &lt;a href=&quot;https://www.heroku.com/&quot;&gt;Heroku&lt;/a&gt;, I&apos;m using &lt;a href=&quot;https://www.npgsql.org/&quot;&gt;Npgsql&lt;/a&gt; as an ADO.NET Data Provider, and it works beautifully - once you&apos;ve worked out the exact connection string syntax needed to connect to a Heroku PostgreSQL database using ASP.NET Core.&lt;br /&gt;
&lt;br /&gt;
So here it is. You&apos;ll need to get the host, port, username, password and database from your Heroku dashboard - and don&apos;t forget that if you&apos;re connecting from apps that aren&apos;t attached to Heroku directly, you&apos;ll need to manually update the configuration if you rotate your Heroku database credentials.&lt;br /&gt;
&lt;br /&gt;
&lt;table class=&quot;highlight tab-size js-file-line-container&quot; data-tab-size=&quot;8&quot; style=&quot;background-color: white; border-collapse: collapse; border-spacing: 0px; box-sizing: border-box; color: #24292e; font-family: -apple-system, system-ui, &amp;quot;Segoe UI&amp;quot;, Helvetica, Arial, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;, &amp;quot;Segoe UI Symbol&amp;quot;; font-size: 14px; tab-size: 8;&quot;&gt;&lt;tbody style=&quot;box-sizing: border-box;&quot;&gt;
&lt;tr style=&quot;box-sizing: border-box;&quot;&gt;&lt;/tr&gt;
&lt;tr style=&quot;box-sizing: border-box;&quot;&gt;&lt;td class=&quot;blob-code blob-code-inner js-file-line&quot; id=&quot;LC9&quot; style=&quot;box-sizing: border-box; font-family: SFMono-Regular, Consolas, &amp;quot;Liberation Mono&amp;quot;, Menlo, Courier, monospace; font-size: 12px; line-height: 20px; overflow-wrap: normal; overflow: visible; padding: 0px 10px; position: relative; vertical-align: top; white-space: pre;&quot;&gt;&lt;span class=&quot;pl-k&quot; style=&quot;box-sizing: border-box; color: #d73a49;&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;pl-k&quot; style=&quot;box-sizing: border-box; color: #d73a49;&quot;&gt;string&lt;/span&gt; herokuConnectionString &lt;span class=&quot;pl-k&quot; style=&quot;box-sizing: border-box; color: #d73a49;&quot;&gt;=&lt;/span&gt; @&lt;span class=&quot;pl-s&quot; style=&quot;box-sizing: border-box; color: #032f62;&quot;&gt;&lt;span class=&quot;pl-pds&quot; style=&quot;box-sizing: border-box;&quot;&gt;&quot;
&lt;/span&gt;  Host=&amp;lt;host.domain.com&amp;gt;;
  Port=&amp;lt;port&amp;gt;;
  Username=&amp;lt;user&amp;gt;;
  Password=&amp;lt;password&amp;gt;;
  Database=&amp;lt;database&amp;gt;;
  Pooling=true;
  Use SSL Stream=True;
  SSL Mode=Require;
  TrustServerCertificate=True;
&lt;span class=&quot;pl-pds&quot; style=&quot;box-sizing: border-box;&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;;&lt;/td&gt;&lt;/tr&gt;
&lt;tr style=&quot;box-sizing: border-box;&quot;&gt;&lt;td class=&quot;blob-num js-line-number&quot; data-line-number=&quot;10&quot; id=&quot;L10&quot; style=&quot;box-sizing: border-box; color: rgba(27, 31, 35, 0.3); cursor: pointer; font-family: SFMono-Regular, Consolas, &amp;quot;Liberation Mono&amp;quot;, Menlo, Courier, monospace; font-size: 12px; line-height: 20px; min-width: 50px; padding: 0px 10px; text-align: right; user-select: none; vertical-align: top; white-space: nowrap; width: 50px;&quot;&gt;&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
Happy querying!&lt;br /&gt;
&lt;br /&gt;</description>
          <pubDate>2018-11-20T16:17:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2018/11/20/postgresql-heroku-net-core-and-npgsql.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2018/11/20/postgresql-heroku-net-core-and-npgsql.html</guid>
        </item>
      
    
      
        <item>
          <title>The Horrors Lurking in your Legacy Codebase</title>
          <description>&lt;p&gt;We’ve all come across design patterns, right? Common solutions to common problems, more specific than a language or a platform, less prescriptive than a component or a framework. The pattern movement originated in building architecture, and in the years since the Gang of Four published their groundbreaking work &lt;a href=&quot;https://www.oreilly.com/library/view/design-patterns-elements/0201633612/&quot;&gt;Design Patterns: Elements of Reusable Object-Oriented Software&lt;/a&gt;, we’ve seen patterns embraced right across the spectrum of software development. We’ve got architectural patterns and infrastructure patterns and organisational patterns. We’ve even got &lt;a href=&quot;https://sourcemaking.com/antipatterns&quot;&gt;anti-patterns&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Patterns are intentional. They have purpose. Even anti-patterns generally involve some element of premeditation. But patterns aren’t the only things in software where you’ll see familiar structures and characteristics playing out across different systems and organisations… There are also the emergent phenomena. The creeping horrors that we’ve all unwittingly summoned in the course of our illustrious careers. Things that don’t happen on purpose, that nobody ever set out to do, and yet which keep spontaneously manifesting in organisations all over the world. Here, for your education and entertainment, I present a bestiary: a field guide to the monsters and the creeping horrors that are lurking somewhere in your IT systems.&lt;/p&gt;&lt;h3&gt;1. The Reliquary&lt;/h3&gt;&lt;p&gt;The reliquary is that one repository full of really good ideas. Clean code. Brilliant algorithms. The OpenID implementation that you optimised until it shone. Classes so beautifully designed and perfectly documented that they’d make a senior architect weep.&lt;/p&gt;&lt;p&gt;You remember the big rewrite? The project that was going to fix everything, only you never worked out how to actually launch the thing, or get any revenue from it? The reliquary is where you’ve preserved it, pickled in revision control like a fabulous museum specimen. A treasury of good code and good ideas; maybe even an entire codebase that was “a couple of weeks” away from shipping before somebody finally looked at the number of critical features the team had somehow forgotten to include and discovered — to everybody’s surprise — that validated XHTML, normalised data models and 95% test coverage are not actually features any of your end users cared about. Like &lt;a href=&quot;https://en.wikipedia.org/wiki/Buran_%28spacecraft%29&quot;&gt;Buran&lt;/a&gt; or the &lt;a href=&quot;https://en.wikipedia.org/wiki/Hughes_H-4_Hercules&quot;&gt;Spruce Goose&lt;/a&gt;, the surviving artefacts stand as a testament to the quality of your engineering… and a poignant reminder of just how much fun engineers can have building high-quality stuff that nobody actually wants to use.&lt;/p&gt;&lt;h3&gt;2. The Doctor Gonzo&lt;/h3&gt;&lt;p&gt;Named for &lt;a href=&quot;https://en.wikipedia.org/wiki/Oscar_Zeta_Acosta&quot;&gt;the attorney&lt;/a&gt; in Hunter S. Thompson’s ‘&lt;a href=&quot;https://en.wikipedia.org/wiki/Fear_and_Loathing_in_Las_Vegas&quot;&gt;Fear and Loathing in Las Vegas&lt;/a&gt;’, the Doctor Gonzo is that application that’s “too weird to live, and too rare to die”. It’s written in Visual Basic 6, or Delphi, or maybe even Microsoft Access. Your dev team has to keep a couple of antique-grade virtual machines around to fix the occasional show-stopping bug — with instructions that the machines are absolutely not to be Windows Updated on pain of immediate defenestration.&lt;/p&gt;&lt;p&gt;Of COURSE you’ve tried to replace it. Team after team, project after project has come up with a plan, hired some contractors, captured some requirements, and shipped a couple of prototypes. And, as inevitably as night follows day, they have failed… all of their engineering brilliance powerless against the unholy triumvirate of bureaucracy, Stockholm syndrome and undocumented use cases.&lt;/p&gt;&lt;p&gt;In fifty years time when we’re all running genetic algorithms on bioengineered quantum hardware that eschews physical user interfaces in favour of superimposing consciousness patterns directly into our brains by inducing cross-dimensional electrical fields in a neighbouring parallel universe, at least one company will have made a fortune creating a post-singularity hosting environment for running Visual Basic 6 line-of-business applications in the quantum realm.&lt;/p&gt;&lt;h3&gt;3. The Epic of Gilgamesh&lt;/h3&gt;&lt;p&gt;You know this one. It started out as a simple database query — something that pulled out the sales figures for the last quarter. Then somebody tweaked it to account for currency fluctuations. Somebody else cross-referenced it against website traffic logs. Somebody else added a half-dozen LEFT OUTER JOIN statements so you could find out which web browsers the customers who created the accounts who raised the invoices that generated the revenue were using.&lt;/p&gt;&lt;p&gt;Sometime around 2008, the SQL query in question surpassed Queen’s Bohemian Rhapsody in length and scope. By 2012 it was longer than Beowulf - and about as readable. It now stands as one of the great literary epics of our generation, a heartbreaking work of insane genius that is as incomprehensible as it is breathtaking.&lt;/p&gt;&lt;h3&gt;4. The Chasm of Compliance&lt;/h3&gt;&lt;blockquote class=&quot;tr_bq&quot;&gt;&lt;i&gt;“It is a truth universally acknowledged that a profitable organisation in possession of a rigorous security policy must be ignoring a few things”&lt;/i&gt;&lt;br /&gt;
&lt;i&gt;— Jane Austen.&lt;/i&gt;&lt;/blockquote&gt;&lt;p&gt;Some friends of mine used to work in the software division of a company that made scientific instruments. Big government clients, universities, hospitals, research laboratories. As part of the conditions of doing business with these kinds of clients, they had a very strict policy that forbade sending email attachments — which was backed up with a set of firewall rules that would block incoming and outgoing mail with any files attached to it.&lt;/p&gt;&lt;p&gt;If you needed to share a file with a colleague who was working from home or away on a business trip, standard operating procedure was to attach it to a draft email in a Hotmail mailbox and then invite your colleague to download it and delete it when they were done. I believe they learned this particular trick from al-Qaeda. It satisfied the letter of the policy — after all, nobody ever SENT an email with any confidential material attached to it — and it got around the firewall blocks and restrictions.&lt;/p&gt;&lt;p&gt;You see that chasm? That gulf between ‘playing by the rules’ and ‘getting caught’? In there is a rich, thriving ecosystem of free-tier AWS accounts, Dropbox folders full of Excel spreadsheets and SSH proxies running on port 53 so the firewall thinks they’re DNS queries.&lt;/p&gt;&lt;p&gt;It would terrify you how much of your operating revenue comes out of that chasm.&lt;/p&gt;&lt;h3&gt;5. The Shibboleth&lt;/h3&gt;&lt;p&gt;Once upon a time, there was The Password. Being entrusted with The Password was a rite of passage. The Password was the nuclear launch codes, the keys to the city. Maybe it was the root password to the production web server. Maybe it was the sa password to the main database stack, or the master account password for the COBOL mainframe that handled all the financial records.&lt;/p&gt;&lt;p&gt;Of course, in these enlightened times, we have much, much better ways to restrict access to our systems. Federated authentication, PEM keys and one-time codes and 2FA. Single sign-on, cross linked to Sarbanes-Oxley auditing mechanisms so sensitive that if you so much as exhale whilst you’re logged in to production, it’ll analyse your breath content and record the fact that you had a beer with lunch, just in case they ever need to throw you under a bus in court.&lt;/p&gt;&lt;p&gt;Naturally, when you rolled out your new GDPR-compliant single sign-on, you changed the password. Of course you did. And then, when every single piece of software in your organisation went into a screaming panic, you immediately changed it back. And somewhere, there’s a backlog of the applications that need to be updated before you can change The Password. You’ve done the easy ones, obviously. But you haven’t got around yet to remoting into the VM where the Doctor Gonzo (qv) resides, and even if you could, you’ve no idea what algorithm the developers used to encrypt the connection string before pasting it into the INI file that’s eventually got transplanted into the Windows registry. And besides, you’re going to be replacing the Doctor Gonzo any day now, so you can change the password once that’s done.&lt;/p&gt;&lt;p&gt;The shibboleth is a powerful incentive to ensure that your tech staff leave on good terms. They know full well that if they give you any reason to doubt their integrity and trustworthiness following their departure, it’ll be a lot cheaper and easier just to have them killed than it will be to change the master password.&lt;/p&gt;&lt;h3&gt;6. The Masquerade Column&lt;/h3&gt;&lt;p&gt;This one’s a doozy. Somewhere in your organisation there’s a text column in a database. It was designed for your staff to make notes. It’s probably called something like ‘Comment’ or ‘Description’. 250+ characters of beautiful, unvalidated text. And, for many years, that’s exactly what it was used for… until that one fateful afternoon, when a couple of developers were sat around trying to plan a feature. And one of them said ‘can we add a field to the database to store the order type?’ And somebody else said ‘we COULD — but then we’d need to update the stored procedures and regression test any apps that are using column indexes and… it’ll turn a three-point ticket into a couple of weeks of work’.&lt;/p&gt;&lt;p&gt;At which point some bright spark says ‘hey, what if we use the Comment field? We can parse it, and if there’s a pipe character in there, anything after the pipe indicates the order type. And we’ll just tell the sales team not to edit anything in there that looks funny.’&lt;/p&gt;&lt;p&gt;Of course, it’s not always a pipe. Sometimes it’s a comma-separated list of product IDs, that your code knows to split, parse, translate back into orders and use those to populate the invoices. Or maybe you decided a comma was too obvious so you used a backtick instead — clever, huh? Sometimes, if you’re REALLY smart, you’ll use a newline character because you know full well that the UI element that’s bound to the field is a single-line textbox and so your users won’t see the hidden data you’ve stashed on line 2.&lt;/p&gt;&lt;p&gt;Note that if you’ve gone as far as storing actual JSON or XML in a free text field, that’s not really a masquerade any more, that’s just creative repurposing. The whole point of the masquerade is that your code has to run half-a-dozen different parsing and validation routines on that little piece of text before deciding whether it’s doing anything special or not.&lt;/p&gt;&lt;p&gt;Back in the glorious days of the first dotcom bubble, I once proposed using the description field on a table to store a SQL statement that needed to be generated when an order was raised, but not actually executed until the order was confirmed. I may have used the phrase ‘continuation passing’ to make it sound impressive. Fortunately the client pulled the plug on the entire project before it went anywhere.&lt;/p&gt;&lt;h3&gt;7. The Folly&lt;/h3&gt;&lt;p&gt;This is probably linked from your homepage. It definitely features prominently in your sales literature. Maybe you even ran a campaign about it. It’s a massively complex feature that was designed, built, shipped… and in the five years since it went live, it’s been used by exactly nine people. The folly is normally built as a sweetener. Like the senator who won’t sign off on a nuclear power bill unless they put a clause in it about reducing the excise duty on liquor sold in golf clubs, it was some weirdly specific scenario that one of your key stakeholders was absolutely hellbent on pushing through to production. Eventually, your team gives up trying to sell them on the idea of an MVP, and builds the whole damn thing just to shut them up.&lt;/p&gt;&lt;p&gt;To be a genuine folly, the system in question needs to be so inextricably wired into the rest of your software platform that shutting it down would be a major undertaking in its own right, and so it quietly purrs along in production, using up a couple of hundred bucks a month of cloud hosting and not really hurting anybody.&lt;/p&gt;&lt;h3&gt;8. The Slow Loris&lt;/h3&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*tSpy8fcIN6DlU5lwOy1-Ow.jpeg&quot; /&gt;(&lt;a href=&quot;https://www.worldatlas.com/articles/how-many-species-of-slow-lorises-live-in-the-world-today.html&quot;&gt;https://www.worldatlas.com/articles/how-many-species-of-slow-lorises-live-in-the-world-today.html&lt;/a&gt;)&lt;br /&gt;
&lt;/div&gt;&lt;p&gt;The slow loris is a nocturnal primate that’s indigenous to south-east Asia. Slow lorises have big round eyes and curious furry faces, and grow to about 30 cm long. They’re small, they’re cute, they’re not remotely threatening. And if you touch them, &lt;a href=&quot;https://en.wikipedia.org/wiki/Slow_loris#Venom&quot;&gt;they can literally kill you&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Does that remind you of anything in your software? Sure it does. You know. That one innocent-looking database table that if somebody adds a row to it the entire website crashes. The button in your intranet dashboard that says something like ‘download CSV’ and if anybody clicks it the main database server instantly hits 100% CPU and stops accepting any more connections for a good twenty minutes or so. The DLL that has to be installed into C:\Users\Temp\ (because Reasons) and if it’s not there it’s a toss-up as to whether the phone starts ringing before the website goes down, or immediately afterwards.&lt;/p&gt;&lt;p&gt;That file that can’t possibly be doing anything? Probably best not to touch it. No matter how cute and fluffy it might look.&lt;/p&gt;&lt;h3&gt;9. The Phantasm&lt;/h3&gt;&lt;p&gt;Somewhere in your organisation, there’s a piece of critical physical infrastructure that is so well hidden it’s indistinguishable from magic. We call this the phantasm. And a true phantasm always starts out with somebody trying to make things look nice.&lt;/p&gt;&lt;p&gt;Back in the days of wired networks, a really good phantasm was hard to accomplish — you could always follow the wires. But in these days of wireless networks, it’s the easiest thing in the world to stick a wi-fi access point up above a ceiling tile somewhere and forget about it. Ten years later, your successors will thank you when they get asked to find out why the internet has stopped working. After furiously arguing for three or four hours that the internet NEVER worked because there’s clearly no wi-fi points anywhere on that floor, they end up ripping the entire ceiling down in a desperate attempt to work out what’s going on — and find a dust-bunny the size of a basketball with the last few inches of a wifi antenna forlornly poking out of it.&lt;/p&gt;&lt;h3&gt;10. The Paradox&lt;/h3&gt;&lt;p&gt;The paradox is a rare and beautiful artefact in modern software systems. It’s something that can’t possibly exist according to its own rules, and yet it does. The classic paradox is a table full of customers with no email address, in a system that (a) defines a customer as invalid unless the email address is populated, and (b) rejects any update to objects that are not valid. This can lead to HOURS of fun chasing your own tail up and down the aggregate graph trying to work out why you’ve managed to get seventeen validation errors without changing a single value.&lt;/p&gt;&lt;p&gt;There are other paradoxes as well. There’s the server that appears to be responding to pings despite the fact you’ve switched it off, removed the power supply and disconnected all the hard drives. The misconfigured IAM security policy that eventually turns out to serve absolutely no purpose other than preventing it from knowing about itself. The dashboard that’s reporting 100% availability because every single other system in the organisation has failed, including the systems that are supposed to report downtime to the dashboard aggregator.&lt;/p&gt;&lt;p&gt;A really good paradox will vanish without trace when you begin investigating it, like some sort of quantum phenomenon that can only exist until an observer attempts to measure it. This shouldn’t be confused with a &lt;a href=&quot;https://en.wikipedia.org/wiki/Heisenbug&quot;&gt;Heisenbug&lt;/a&gt;, which is a bug that only becomes invisible whilst actively being observed. No, the paradox doesn’t just hide — it vanishes, leaving you sat in a retrospective meeting insisting that you really saw it whilst your team-mates look at you with raised eyebrows and wonder whether it’s time you took some holiday. The experienced paradox hunter doesn’t even run a database query until they’ve loaded Camtasia with silver bullets and started a screen recording.&lt;/p&gt;&lt;h3&gt;Survival Tips for Aspiring Code Archaeologists&lt;/h3&gt;&lt;p&gt;As you explore the canyons and catacombs of an unfamiliar codebase, keep your eyes peeled, for these are just a few of the weird and wonderful creatures you’ll find lurking in the dark places. You may seek to understand them. You may even seek to catalogue a few of your own — but be wary as you study their ways, for to familiarise yourself with these beasts is to walk a narrow and dangerous path. For as the great systems architect Nietzsche once said:&lt;/p&gt;&lt;blockquote class=&quot;tr_bq&quot; style=&quot;text-align: center;&quot;&gt;&lt;i&gt;“Beware that, when fighting monsters, you yourself do not become a monster… for when you gaze long into the abyss, the abyss gazes also into you, and when you truly understand the legacy codebase, only then will you realise you yourself have become part of the legacy.”&lt;/i&gt;&lt;/blockquote&gt;</description>
          <pubDate>2018-08-29T10:06:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2018/08/29/the-horrors-lurking-in-your-legacy.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2018/08/29/the-horrors-lurking-in-your-legacy.html</guid>
        </item>
      
    
      
        <item>
          <title>Reflections on Alt.NET Birmingham 2018</title>
          <description>&lt;br /&gt;
&lt;p&gt;Around ten years ago, a bunch of developers got together in London under the banner of ‘alt dot net’ to talk about code, the universe and everything. Ten years doesn’t sound like much, but it’s actually quite amazing to look back on how much has changed. Back at that first alt.NET &lt;a data-href=&quot;https://en.wikipedia.org/wiki/Unconference&quot; href=&quot;https://en.wikipedia.org/wiki/Unconference&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;unconference&lt;/a&gt;, a handful of people in the room had a first-generation iPhone. I don’t think anybody had an Android device yet. Stack Overflow didn’t exist. Most of us weren’t on Twitter yet — In fact, my&lt;a data-href=&quot;http://www.dylanbeattie.net/2008/02/reflections-on-altnetuk.html&quot; href=&quot;http://www.dylanbeattie.net/2008/02/reflections-on-altnetuk.html&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt; first-ever blog post was a writeup of that first alt.NET UK unconference&lt;/a&gt; and I &lt;a data-href=&quot;https://twitter.com/dylanbeattie/status/813023431&quot; href=&quot;https://twitter.com/dylanbeattie/status/813023431&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;didn’t join Twitter until three months later&lt;/a&gt;…&lt;/p&gt;&lt;p&gt;Most&amp;nbsp;.NET developers in 2008 were still running Windows XP, apart from the unlucky few who’d upgraded to Vista and couldn’t work how to go back. Visual Studio 2008 was the new shiny. Lots of shops were still hosting on Windows 2003 or even Windows 2000 Server. That first wave of alt.NET was a bunch of developers who found C# and Visual Studio to be a powerful and productive platform for building software, but a platform that came bundled with a particular set of ‘best practises’ that weren’t necessarily the way we wanted to be working. The&amp;nbsp;.NET community, such as it was, was heavily concentrated around Microsoft’s own conference and events, and there was very little discussion in that community around ideas like extreme programming, agile, model-view-controller unit testing, browser automation, dependency injection… you know. Ideas that are widely acknowledged here in 2018 to be, if not magic bullets, then well worth knowing and understanding how they might apply to the work you’re doing.&lt;/p&gt;&lt;p&gt;And now here we are, ten years later, on a glorious sunny day in Birmingham. Twenty-odd developers getting together on a Saturday under that same Alt.NET banner to talk about… well, whatever we want to talk about. That’s the beauty of the unconference format. The attendees create the schedule on the day. There’s no prepared presentations, no speakers, no keynotes or programme committee — you come along, you bring your questions and ideas and the things you want to talk about, and we see what happens.&lt;/p&gt;&lt;p&gt;An hour of spirited discussion and a LOT of post-it notes later, and we had an agenda — and what a wonderful range of topics:&lt;/p&gt;&lt;p&gt;&lt;img height=&quot;640&quot; src=&quot;https://cdn-images-1.medium.com/max/1600/1*CgGaONUjwbVX5LvsRd1N3w.jpeg&quot; width=&quot;480&quot; /&gt;&lt;/p&gt;&lt;p&gt;Messaging and reference data, Blazor, XAML, WebAPI and EF Core and OData, liberating structures and agile processes, quantum computing and Q#, the SAFE stack, devops for mobile apps, Docker and Kubernetes and network security, gRPC and Service Mesh, dot net command line tooling, “are micro services dead?”, functional programming (and functional-style programming in C#), running dot net core on Linux, Systems Thinking, building JavaScript services in&amp;nbsp;.NET Core, diversity and inclusivity, risk based software architecture, Xamarin and F#, and a crash course in Git and GitHub.&lt;/p&gt;&lt;p&gt;One of the great things about the unconference format is that you’ll end up with a bunch of curious people in a room wanting to learn about something… but ‘cos there’s no speakers and no ‘experts’, we’ll just fire up a laptop, find a quickstart or a ‘hello world’ tutorial or something, and start hacking on it. So we started off the day with a mob programming session on Blazor — one of the gang had a half-finished experimental chatbot they’d been working on, so we got that up on a big screen and played around with it for an hour until we had asynchronous callbacks and model binding working.&lt;/p&gt;&lt;p&gt;The next session I went to was one I’d proposed about quantum computing — I saw an intro session from &lt;a data-href=&quot;https://twitter.com/whywontitbuild?lang=en&quot; href=&quot;https://twitter.com/whywontitbuild?lang=en&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;Anita Ramanan&lt;/a&gt; and &lt;a data-href=&quot;https://twitter.com/frances_tibble?lang=en&quot; href=&quot;https://twitter.com/frances_tibble?lang=en&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;Frances Tibble&lt;/a&gt; at &lt;a data-href=&quot;https://twitter.com/hashtag/DDD13?src=hash&amp;amp;lang=en&quot; href=&quot;https://twitter.com/hashtag/DDD13?src=hash&amp;amp;lang=en&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;DDD13&lt;/a&gt; in Reading last week, and I’ve been itching to download Q# and try it out… and so, in the absence of any quantum computing experts to tell us the answers, we had another group hack session with a laptop plugged into a big screen. What I found really remarkable about this session was how easily we got the whole thing up and running. I’m running the&amp;nbsp;&lt;a data-href=&quot;https://www.microsoft.com/net/download/macos&quot; href=&quot;https://www.microsoft.com/net/download/macos&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;.NET SDK on macOS&lt;/a&gt;, using &lt;a data-href=&quot;https://code.visualstudio.com/&quot; href=&quot;https://code.visualstudio.com/&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;VS Code&lt;/a&gt; and &lt;a data-href=&quot;https://www.jetbrains.com/rider/&quot; href=&quot;https://www.jetbrains.com/rider/&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;JetBrains Rider&lt;/a&gt;, and it only took a few minutes to download the quantum computing preview, install it, fire up a ‘hello world’ app (or the Q# equivalent thereof) and start playing around with simulating quantum entanglement.&lt;/p&gt;&lt;p&gt;Next up was a freeform discussion all about command line tooling — gulp, grunt, yeoman, yarn, bower, npm, the dot net cli templating system — how did we get here, what did we like, and why the hell does running ‘dotnet new mvc’ drop a bunch of&amp;nbsp;.something.bower files into your repository? We talked a lot about boilerplate code — about the days when File &amp;gt; New &amp;gt; Project would drop a few hundred lines of&amp;nbsp;.designer.cs and&amp;nbsp;.csproj files into your solution, which was never intended for human consumption, but you could guarantee that eventually SOMETHING would go wrong with one of those files and you’d have to roll your sleeves up and work out how to fix it. And we talked about how for people that have experienced this, Microsoft’s move towards a far more minimalist project file syntax is a Good Thing — but that alongside that, there’s a whole bunch of complexity that we’ve adopted wholesale from the npm ecosystem for managing web assets and front-end dependencies. We talked a bit about Andrey Taritsyn’s &lt;a data-href=&quot;https://github.com/Taritsyn/BundleTransformer&quot; href=&quot;https://github.com/Taritsyn/BundleTransformer&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;BundleTransformer&lt;/a&gt;, and the idea of treating SASS, SCSS, TypeScript et al as part of your solution and relying on the&amp;nbsp;.NET pipeline to compile them at request time, and how the npm/node mindset has encouraged developers to regard these sorts of concerns as compile-time rather than runtime operations. The consensus — amongst our small and opinionated but by no means authoritative group — was that there’s still a pretty steep learning curve for the old-school&amp;nbsp;.NET developer trying to understand modern web build tooling, but that it’s generally all getting a lot better.&lt;/p&gt;&lt;p&gt;I also made an honourable mention of the latest&amp;nbsp;.NET CLI tooling by describing how an hour previously I’d installed the Quantum Computing SDK on macOS and got a couple of demos up and running, without having to reboot into Windows or fire up Visual Studio, and how that was actually quite awesome.&lt;/p&gt;&lt;p&gt;We broke for lunch — which was entirely thanks to &lt;a data-href=&quot;https://twitter.com/ijrussell?lang=en&quot; href=&quot;https://twitter.com/ijrussell?lang=en&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;Ian Russell&lt;/a&gt;, who I gather ended up not only organizing but also subsidising quite a lot of the event. For which we are all eternally grateful, but if there’s anyone out there whose company might be interested in supporting this kind of event, here’s a bit of advice for you. Community groups and events like this can make a few hundred pounds go a long, long way, but if your corporate sponsorship process means filling out forms, signing waivers and NDAs, submitting corporate investment opportunity proposals… the sort of people who run events like this are unlikely to appreciate your ‘support’. Work out how to support this kind of event with £500 out of petty cash, or offer to pick up the bill for lunch or something, and we’ll just say nice things about you and tell everyone on Twitter how grateful we are. You can get a lot of good will and positive buzz (not to mention sandwiches!) for less than it costs to hire a&amp;nbsp;.NET contractor for one day — but when it comes to this kind of thing, please remember that your accounting procedures are your problem and not anybody else’s.&lt;/p&gt;&lt;p&gt;But I digress. Lunch was delicious and Ian, next time you’re down our way, the London&amp;nbsp;.NET gang are taking you out for dinner to say thank you.&lt;/p&gt;&lt;p&gt;And on to the afternoon. First up was a session about functional programming — what is it, why does it matter, and how do you do it in C#? Partly inspired by some of &lt;a data-href=&quot;http://mikehadlow.blogspot.com/2015/08/c-program-entirely-with-static-methods.html&quot; href=&quot;http://mikehadlow.blogspot.com/2015/08/c-program-entirely-with-static-methods.html&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;Mike Hadlow’s blog posts&lt;/a&gt; about refactoring object-oriented C# into a more functional style, it was a great discussion; with some folks in the room who have very much embraced the functional paradigm, some folks who have spent years dabbling in F# but still prefer C# for everyday development, and some folks who haven’t really encountered functional programming but have heard a lot about it and would love to know why it sounds so important. Ian Cooper shared the ‘devil’s argument against F#’ — “I’m not sure I agree with all of this, but I know it well and I think it’s useful to share it” — about how pure functional programming is never going to cross the chasm from the bleeding edge to mainstream adoption; about how a lot of the ‘textbook’ benefits of functional programming are that it scales easily across multiple cores but that as an industry we’ve very much embraced the idea of scaling using services and containers rather than building monolithic applications that scale effectively on high-performance hardware. I pointed out that this notion of ‘scaling’ is very much rooted in ideas around scaling straightforward CRUD processes to cope with massive volumes of users — the predominant computational model of high-traffic web applications — and that there’s still lots of challenges around simulation, video rendering, gaming and machine learning where scaling a local process across multiple cores is still the most compelling route to improved performance. We also briefly discussed the dynamic that exists between the F# community and the rest of the&amp;nbsp;.NET ecosystem &lt;em&gt;(TL;DR: it’s mostly very friendly and lovely, but there’s still a perception that the functional community can be a bit elitist when it comes to sharing ideas with their object-oriented cousins).&lt;/em&gt;&lt;/p&gt;&lt;p&gt;There were also several recommendations to check out &lt;a data-href=&quot;https://twitter.com/scottwlaschin?lang=en&quot; href=&quot;https://twitter.com/scottwlaschin?lang=en&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;Scott Wlaschin&lt;/a&gt;’s book ‘&lt;a data-href=&quot;https://pragprog.com/book/swdddf/domain-modeling-made-functional&quot; href=&quot;https://pragprog.com/book/swdddf/domain-modeling-made-functional&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;Domain Modelling Made Functional&lt;/a&gt;’, both for an excellent overview of domain-driven design covered in the first few chapters, and for some worthwhile insight into the value of using functional programming patterns in your domain models.&lt;/p&gt;&lt;p&gt;Next was a session that ended up on the board as a combination of ‘diversity’ and ‘attracting new speakers’ — but ended up running a broad gamut from conferences and community, to speaker tips, suggestions and ideas for encouraging more people to get involved in what we do. I’m the first to admit that&amp;nbsp;.NET has a massive problem with diversity — the vast majority of&amp;nbsp;.NET developers in the UK are middle-aged white guys, and whilst there’s many brilliant speakers, engineers and evangelists on our platform who don’t conform to that particular stereotype, if you turn up to a&amp;nbsp;.NET user group pretty much anywhere in the UK, it’s going to be overwhelmingly white males of a certain age. We talked about this at some length, including the observation that a lot of the people who are still passionate about&amp;nbsp;.NET as a platform are people who have been here since the beginning, since the days of Visual Studio&amp;nbsp;.NET and C# 1.0, and there’s not a whole lot of people who have made a conscious decision to embrace&amp;nbsp;.NET as a development platform who didn’t already have some kind of investment in the Microsoft/Windows/SQL Server platform. The session convened around a question — how do we get more people looking at&amp;nbsp;.NET as a development platform? It’s free. It’s open source. It’s cross-platform. It runs on macOS, Linux and Windows, it’s got excellent hosting and infrastructure support from multiple cloud providers… so why aren’t we seeing more interest in it? &lt;em&gt;(When we figure out the answer, I’ll let you know. Promise!)&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;img height=&quot;480&quot; src=&quot;https://cdn-images-1.medium.com/max/1600/0*J-H4iAl1jPX5_ASN&quot; width=&quot;640&quot; /&gt;&lt;/p&gt;&lt;p&gt;Finally, we wrapped up with a park bench discussion about “The&amp;nbsp;.NET Renaissance — Where Are We?”. Following on from a&lt;a data-href=&quot;http://www.dylanbeattie.net/2017/02/bring-back-altnet-but-why.html&quot; href=&quot;http://www.dylanbeattie.net/2017/02/bring-back-altnet-but-why.html&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt; series&lt;/a&gt; of &lt;a data-href=&quot;https://medium.com/altdotnet/on-the-need-for-a-c-renaissance-634078d4e865&quot; href=&quot;https://medium.com/altdotnet/on-the-need-for-a-c-renaissance-634078d4e865&quot; target=&quot;_blank&quot;&gt;blog posts&lt;/a&gt; and &lt;a data-href=&quot;https://vimeo.com/223982168&quot; href=&quot;https://vimeo.com/223982168&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;conference talks&lt;/a&gt; (and at least one &lt;a data-href=&quot;https://player.fm/series/net-rocks-65612/the-net-renaissance-with-ian-cooper&quot; href=&quot;https://player.fm/series/net-rocks-65612/the-net-renaissance-with-ian-cooper&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;DotNetRocks podcast&lt;/a&gt;) that various members of the&amp;nbsp;.NET community have shared over the last eighteen months or so, and overlapping a lot with some of the questions and ideas from the session on diversity and inclusivity, we talked about&amp;nbsp;.NET — past, present and future. Ian Cooper shared some slides, graphs and stats based on various metrics — recruitment websites, TIOBE — along with a healthy disclaimer that all programming language metrics were probably wrong and there’s “lies, damned lies, and percentages”. We talked about how Unity is creating a path into programming for a lot of first-time developers who are interesting in creating games and immersive 3D environments, and how the increasing interest in devices like GearVR and Hololens is only going to encourage this. &lt;a data-href=&quot;https://twitter.com/jimbobbennett?lang=en&quot; href=&quot;https://twitter.com/jimbobbennett?lang=en&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;Jim Bennett &lt;/a&gt;&lt;em&gt;(who I should point out was here sharing his own opinions and not any official Microsoft position)&lt;/em&gt; spoke about how the diversity of platforms and runtimes almost guarantees long-term investment in C# as compared to platforms like Flutter/Dart, which are still tightly coupled to specific hardware and OS platforms.&amp;nbsp;.NET has got some absolutely first-class tooling support. It runs on a huge number of devices — not just Windows, Linux &amp;amp; macOS, but thanks to Xamarin it’s now running on Android, iOS, &lt;a data-href=&quot;https://developer.tizen.org/development/training/.net-application/creating-your-first-tizen-.net-application&quot; href=&quot;https://developer.tizen.org/development/training/.net-application/creating-your-first-tizen-.net-application&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;Samsung’s Tizen platform&lt;/a&gt; (yeah, did you know your Samsung smart TV can run&amp;nbsp;.NET Core applications?) — and with companies like AirBnB talking openly about the &lt;a data-href=&quot;https://medium.com/airbnb-engineering/react-native-at-airbnb-f95aa460be1c&quot; href=&quot;https://medium.com/airbnb-engineering/react-native-at-airbnb-f95aa460be1c&quot; target=&quot;_blank&quot;&gt;challenges they’ve had trying to use frameworks like React Native&lt;/a&gt; to deliver cross-platform solutions across web and mobile, it’s clear that there’s still a big unsolved problem around building cross-platform consumer apps, and that&amp;nbsp;.NET Core and Xamarin Forms is working really hard to offer a compelling solution to that problem that could turn out to be the incentive that attracts a new generation of developers and startups onto the platform.&lt;/p&gt;&lt;p&gt;We took a few moments to plug a couple of other&amp;nbsp;.NET events that are coming up in the UK over the next few months — the &lt;a data-href=&quot;https://skillsmatter.com/conferences/10107-prognet-london-2018&quot; href=&quot;https://skillsmatter.com/conferences/10107-prognet-london-2018&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;ProgNET conference and tutorials&lt;/a&gt; that we’re running at Skills Matter in September, and &lt;a data-href=&quot;https://www.dddeastanglia.com/&quot; href=&quot;https://www.dddeastanglia.com/&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;DDD East Anglia&lt;/a&gt;, which is another free community event that’s taking place in Cambridge in September.&lt;/p&gt;&lt;p&gt;And then, after eight hours of inspiring conversations and insightful observations, we went to the pub to reflect on how the day had gone. One thing that stood out was that, asking people how they’d heard about the event, there was very little correlation. Many of the people who came along today heard about it through word of mouth, and despite promoting the event widely on Twitter, email and social media, we probably knew a few dozen people between us who would have come along if they’d known it was happening. Asking people to give up a Saturday to come along to an unstructured conference is a bit of a big ask, sure — but actually, I think it’s one of the things that makes the unconference format work well. If you ran an event like that on a Friday, you’d probably get a lot more signups from people who persuaded their boss to give them a day off to go to a free conference… but you’d probably also get a lot more people turning up late, sitting quietly without really contributing anything, and I daresay sloping off the pub at lunchtime and not coming back. Most of the people who came along today weren’t based in Birmingham, and even though the event itself was free, somebody who gives up a day of their time and pays out of their own pocket to travel to an event like &lt;a data-href=&quot;http://alt.net/&quot; href=&quot;http://alt.net/&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;Alt.NET&lt;/a&gt; is exactly the kind of person you want involved.&lt;/p&gt;&lt;p&gt;One thing was overwhelmingly evident, though. If you’d taken today’s agenda back in time to 2008 and showed it to the people who came along to that first Alt.NET UK event, they wouldn’t have understood half of it (“coober-netties? What the hell is coober-netties?”) — and there’s no way they’d have believed the other half. C# running on an iPhone? HA! ASP.NET on Linux? HAAA! And when you sat down an hour later and worked out how install a quantum computing simulator on macOS and then hack together a quantum entanglement demo using a free open-source code editor with support for an experiment language for writing quantum algorithms, they would not have believed for a second that Microsoft and&amp;nbsp;.NET was the driving force behind all those amazing innovations.&lt;/p&gt;&lt;br /&gt;
&lt;p&gt;It was one of the most positive and inspiring events I’ve been to in a while — and when I think of how many excellent conferences and meetups I’ve been to in the last few months, that’s really saying something. Huge thanks to &lt;a data-href=&quot;https://twitter.com/ijrussell?lang=en&quot; href=&quot;https://twitter.com/ijrussell?lang=en&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;Ian Russell&lt;/a&gt; and &lt;a data-href=&quot;https://twitter.com/davedev?lang=en&quot; href=&quot;https://twitter.com/davedev?lang=en&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;Dave Evans&lt;/a&gt; for putting it together, to &lt;a data-href=&quot;https://birmingham.impacthub.net/&quot; href=&quot;https://birmingham.impacthub.net/&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;ImpactHub&lt;/a&gt; for the venue, but most of all to everyone who came along. An event like this is only as good as the people who show up, and today was absolutely awesome. See you all at the next one.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Oh, and the session on ‘are microservices dead?’ Yeah. Nobody turned up. I guess that makes it official. RIP microservices. It’s been… emotional&amp;nbsp;:)&lt;/em&gt;&lt;/p&gt;&lt;br /&gt;
&lt;h4&gt;LINKS&lt;/h4&gt;&lt;p&gt;Various things that were shown, shared, discussed or mentioned:&lt;/p&gt;&lt;ul&gt;&lt;li id=&quot;84e0&quot; name=&quot;84e0&quot;&gt;The Blazor project site at &lt;a data-href=&quot;https://blazor.net/&quot; href=&quot;https://blazor.net/&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;https://blazor.net/&lt;/a&gt;&lt;/li&gt;
&lt;li id=&quot;7bc0&quot; name=&quot;7bc0&quot;&gt;Layla Porter’s &lt;a data-href=&quot;https://github.com/Layla-P/BlazorChat&quot; href=&quot;https://github.com/Layla-P/BlazorChat&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;experimental Blazor chatbot&lt;/a&gt; on GitHub&lt;em&gt; (work in progress and VERY experimental — but we had fun playing around with the code!)&lt;/em&gt;&lt;/li&gt;
&lt;li id=&quot;ab91&quot; name=&quot;ab91&quot;&gt;The &lt;a data-href=&quot;https://www.microsoft.com/en-gb/quantum/development-kit&quot; href=&quot;https://www.microsoft.com/en-gb/quantum/development-kit&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;Microsoft Quantum Computing SDK&lt;/a&gt;&lt;/li&gt;
&lt;li id=&quot;8d2b&quot; name=&quot;8d2b&quot;&gt;&lt;a data-href=&quot;https://blogs.msdn.microsoft.com/uk_faculty_connection/2018/02/26/the-hitchhikers-guide-to-the-quantum-computing-and-q-blog/&quot; href=&quot;https://blogs.msdn.microsoft.com/uk_faculty_connection/2018/02/26/the-hitchhikers-guide-to-the-quantum-computing-and-q-blog/&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;The Hitchhikers Guide to Quantum Computing&lt;/a&gt; — a blog written by Anita Ramanan and Frances Tibble at Microsoft Research, all about Q# and quantum computing&lt;/li&gt;
&lt;li id=&quot;ea0c&quot; name=&quot;ea0c&quot;&gt;Scott Hanselman’s &lt;a data-href=&quot;https://www.hanselman.com/blog/TryingOutDotnetNewTemplateUpdatesAndCsprojWithVS2017.aspx&quot; href=&quot;https://www.hanselman.com/blog/TryingOutDotnetNewTemplateUpdatesAndCsprojWithVS2017.aspx&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;post about dotnet new templates&lt;/a&gt;&lt;/li&gt;
&lt;li id=&quot;0819&quot; name=&quot;0819&quot;&gt;Getting started with &lt;a data-href=&quot;https://developer.tizen.org/development/training/.net-application/creating-your-first-tizen-.net-application&quot; href=&quot;https://developer.tizen.org/development/training/.net-application/creating-your-first-tizen-.net-application&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;Samsung Tizen&lt;/a&gt;&lt;/li&gt;
&lt;li id=&quot;4b17&quot; name=&quot;4b17&quot;&gt;&lt;a data-href=&quot;https://twitter.com/scottwlaschin?lang=en&quot; href=&quot;https://twitter.com/scottwlaschin?lang=en&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;Scott Wlaschin&lt;/a&gt;’s book ‘&lt;a data-href=&quot;https://pragprog.com/book/swdddf/domain-modeling-made-functional&quot; href=&quot;https://pragprog.com/book/swdddf/domain-modeling-made-functional&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;Domain Modelling Made Functional&lt;/a&gt;’&lt;/li&gt;
&lt;li id=&quot;712e&quot; name=&quot;712e&quot;&gt;&lt;a data-href=&quot;https://birmingham.impacthub.net/&quot; href=&quot;https://birmingham.impacthub.net/&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;Impact Hub Birmingham&lt;/a&gt;, the co-working space that hosted us&lt;/li&gt;
&lt;li id=&quot;333e&quot; name=&quot;333e&quot;&gt;&lt;a data-href=&quot;https://medium.com/airbnb-engineering/react-native-at-airbnb-f95aa460be1c&quot; href=&quot;https://medium.com/airbnb-engineering/react-native-at-airbnb-f95aa460be1c&quot; target=&quot;_blank&quot;&gt;AirBnB’s series of posts about their experience using React Native for cross-platform development&lt;/a&gt;&lt;/li&gt;
&lt;li id=&quot;97c0&quot; name=&quot;97c0&quot;&gt;&lt;a data-href=&quot;https://skillsmatter.com/conferences/10107-prognet-london-2018&quot; href=&quot;https://skillsmatter.com/conferences/10107-prognet-london-2018&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;ProgNET 2018&lt;/a&gt; — the ninth annual progressive&amp;nbsp;.NET conference hosted by Skills Matter, and organized by many of the same people who’ve been involved with the alt.NET movement since those first UK alt.NET events way back in 2008.&lt;/li&gt;
&lt;li id=&quot;0159&quot; name=&quot;0159&quot;&gt;&lt;a data-href=&quot;https://www.dddeastanglia.com/&quot; href=&quot;https://www.dddeastanglia.com/&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;DDD East Anglia &lt;/a&gt;— which is currently open for sessions, so if you fancy going along and doing a talk, now’s the time to submit something!&lt;/li&gt;
&lt;/ul&gt;</description>
          <pubDate>2018-07-02T11:05:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2018/07/02/reflections-on-alt.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2018/07/02/reflections-on-alt.html</guid>
        </item>
      
    
      
        <item>
          <title>Tech and travel in Russia, Belarus and Ukraine</title>
          <description>&lt;p&gt;Over the past couple of years, I’ve spoken at quite a few conferences in Russia, Ukraine and Belarus – DotNext in Saint-Petersburg and Moscow, BuildStuff in Kyiv and Odessa, and .NET Summit in Minsk. Later this year, I’ll be heading back to Saint-Petersburg for &lt;a href=&quot;https://dotnext-piter.ru/en/&quot;&gt;DotNext&lt;/a&gt;, and travelling to Novosibirsk in Siberia for &lt;a href=&quot;https://2018.codefest.ru/&quot;&gt;CodeFest&lt;/a&gt;. All these events have open calls for papers, and if you’ve ever wanted to visit these places, tech conferences are a great way to do it. Lots of people have asked me what’s involved and whether it’s something they should look into - so here’s what I did, how it all worked for me, and some tips that might be useful.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Please remember I’m not an immigration lawyer. This is all based on my own experience, and I’m a straight white British cis male with no special dietary or medical requirements who is quite happy travelling alone in strange places. Laws, customs and cultures can vary wildly, so do your own research if you’re concerned about anything.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;First up – yes, do it! I’ve had fantastic experiences on all these trips. The conferences I’ve spoken at have been first-class, professional events, with excellent facilities and really attentive, engaged audiences. Travelling in a country where you can’t even read the alphabet can be a bit of a culture shock at first, but I’ve had no problems navigating hotels, public transport and conference venues as an English speaker. Apps like Uber are great for this: being able to call a taxi and see exactly where you’re going and how much it’ll cost is hugely reassuring when you’re somewhere unfamiliar and can’t speak the language.&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-2QRJDdWKVUI/WlTLLRCYI9I/AAAAAAAAFEs/yzRiZwxiLIk771Q8O33yXWXMu5kUI4dyACHMYCw/s1600-h/2017-05-19%2B16.25.32-1%255B8%255D&quot;&gt;&lt;img width=&quot;244&quot; height=&quot;184&quot; title=&quot;The church of the Saviour of the Spilled Blood, Saint-Petersburg, Russia.&quot; style=&quot;display: inline; background-image: none;&quot; alt=&quot;The church of the Saviour of the Spilled Blood, Saint-Petersburg, Russia.&quot; src=&quot;https://lh3.googleusercontent.com/-AgXYQdi0r2M/WlTLLwq-W1I/AAAAAAAAFEw/hgpbin_ZmCMxlRAgfqPtrRdQnoHAttMKQCHMYCw/2017-05-19%2B16.25.32-1_thumb%255B5%255D?imgmax=800&quot; border=&quot;0&quot;&gt;&lt;/a&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-kTBlph16rms/WlTLMq5k7tI/AAAAAAAAFE0/G4AdKZK1BLMDP3QzlohMYSselMCjkhVRwCHMYCw/s1600-h/2017-05-19%2B17.41.12%255B3%255D&quot;&gt;&lt;img width=&quot;244&quot; height=&quot;184&quot; title=&quot;The Winter Palace, Saint-Petersburg&quot; style=&quot;display: inline; background-image: none;&quot; alt=&quot;The Winter Palace, Saint-Petersburg&quot; src=&quot;https://lh3.googleusercontent.com/-Nk4MwwTqsck/WlTLNKdsHPI/AAAAAAAAFE4/z10bsABxZAsJkNEmsM0DQylWXIRAf_aIACHMYCw/2017-05-19%2B17.41.12_thumb?imgmax=800&quot; border=&quot;0&quot;&gt;&lt;/a&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-EOfPZXQ5BvE/WlTLN2WMg9I/AAAAAAAAFE8/7gneeU2_XSQy7Ai4MShonhEr3BwagURjACHMYCw/s1600-h/2017-05-19%2B17.03.14%255B9%255D&quot;&gt;&lt;img width=&quot;244&quot; height=&quot;184&quot; title=&quot;C&apos;mon, you can work out what this says...&quot; style=&quot;display: inline; background-image: none;&quot; alt=&quot;C&apos;mon, you can work out what this says...&quot; src=&quot;https://lh3.googleusercontent.com/-V78SFEsJStU/WlTLOdbRcGI/AAAAAAAAFFA/6icflfRuSlUM1IQ8xXku6eQigVq-WW52wCHMYCw/2017-05-19%2B17.03.14_thumb%255B7%255D?imgmax=800&quot; border=&quot;0&quot;&gt;&lt;/a&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-8qNC1AdcU30/WlTLPCFK_GI/AAAAAAAAFFE/MQN2yOabQBkr9yOWU3hfByrOgO2UtaXHACHMYCw/s1600-h/2017-05-19%2B17.45.47%255B3%255D&quot;&gt;&lt;img width=&quot;244&quot; height=&quot;184&quot; title=&quot;2017-05-19 17.45.47&quot; style=&quot;display: inline; background-image: none;&quot; alt=&quot;2017-05-19 17.45.47&quot; src=&quot;https://lh3.googleusercontent.com/-6ZqheQUkUBA/WlTLPs2ufvI/AAAAAAAAFFI/hEuDgLjwhEE_pGqcFZlyhH3GA17fQECAgCHMYCw/2017-05-19%2B17.45.47_thumb?imgmax=800&quot; border=&quot;0&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;If you’re up for a challenge, have a go at learning to read the Cyrillic alphabet. The Russian &lt;em&gt;language&lt;/em&gt; is hard, but you’ll find lots of the words on things like street signs and restaurant menus are familiar words, once you can read the language they’re written in. Being able to recognise things like Банка (banka – a bank), Гамбургер (hamburger) and Феттучини (fettucine) will get you a long way. (Russian, Ukrainian, and Belarusian are distinct languages with some broad similarities, but being able to recognise familiar English words in Cyrillic will stand you in good stead throughout the region.)&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-DZBwQoKtNgA/WlTLQBg50TI/AAAAAAAAFFM/8YcksWZ4jyswSoydwLUqHz61kqirAn6nQCHMYCw/s1600-h/2017-05-19%2B16.42.08%255B4%255D&quot;&gt;&lt;img width=&quot;544&quot; height=&quot;409&quot; title=&quot;Church of the Saviour of the Spilled Blood, Saint-Petersburg&quot; style=&quot;display: inline; background-image: none;&quot; alt=&quot;Church of the Saviour of the Spilled Blood, Saint-Petersburg&quot; src=&quot;https://lh3.googleusercontent.com/-XDL1Xq7dgGY/WlTLQyedF6I/AAAAAAAAFFQ/_zbZapv72xUhtdSzWvnp_ZOfshG5wJjwACHMYCw/2017-05-19%2B16.42.08_thumb%255B1%255D?imgmax=800&quot; border=&quot;0&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;h3&gt;Visiting Ukraine and Belarus&lt;/h3&gt;&lt;p&gt;If you’re a British passport holder, visiting Ukraine is easy; you can &lt;a href=&quot;https://www.gov.uk/foreign-travel-advice/ukraine/entry-requirements&quot;&gt;enter Ukraine without a visa for up to 90 days&lt;/a&gt;. Belarus has an interesting visa waiver: British citizens can &lt;a href=&quot;https://www.gov.uk/foreign-travel-advice/belarus/entry-requirements&quot;&gt;enter without a visa as long as they stay less than five days&lt;/a&gt; and they enter and leave via Minsk international Airport. You need to show proof of medical insurance to clear passport control (and the authorities don’t consider a PDF on your phone to be documentary evidence). If you don’t have insurance, you can purchase it at the airport. It’s &lt;a href=&quot;http://airport.by/en/Compulsory-medical-insurance&quot;&gt;priced in euros&lt;/a&gt;, and you can pay in cash in Belarusian rubles, Russian rubles or “freely convertible currency” – I paid in euro coins without any problems.&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-ILKznw576Cw/WlTLRTuDN2I/AAAAAAAAFFU/kCBNgvYvX3s2J6hFrlQqFNcCduBx1MgqwCHMYCw/s1600-h/2017-09-22%2B19.55.45%255B5%255D&quot;&gt;&lt;img width=&quot;244&quot; height=&quot;184&quot; title=&quot;Welcome to Belarus!&quot; style=&quot;display: inline; background-image: none;&quot; alt=&quot;Welcome to Belarus!&quot; src=&quot;https://lh3.googleusercontent.com/-mohxlG-S66w/WlTLSOAb6eI/AAAAAAAAFFY/rS3gfSTygkw6_-uRiKG5owEdCYoxhf8WQCHMYCw/2017-09-22%2B19.55.45_thumb%255B2%255D?imgmax=800&quot; border=&quot;0&quot;&gt;&lt;/a&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-hE1-zVIlwn0/WlTLS817M8I/AAAAAAAAFFc/UENy9mI45WEobxVXpFWZBvYznBal613XwCHMYCw/s1600-h/2017-09-23%2B16.46.27%255B3%255D&quot;&gt;&lt;img width=&quot;244&quot; height=&quot;184&quot; title=&quot;Minsk&quot; style=&quot;display: inline; background-image: none;&quot; alt=&quot;Minsk&quot; src=&quot;https://lh3.googleusercontent.com/-UZM-ogAd-PQ/WlTLTb1dwhI/AAAAAAAAFFg/ZddqrXibx30fI1KT4bfHTq_-VEpVY-GbgCHMYCw/2017-09-23%2B16.46.27_thumb?imgmax=800&quot; border=&quot;0&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-8CHrnWVuzFI/WlTLT_nYYlI/AAAAAAAAFFk/jo5RoiOrEXEFnFjNPIGSS-wmSUPhVXptQCHMYCw/s1600-h/2017-09-23%2B16.48.10%255B3%255D&quot;&gt;&lt;img width=&quot;244&quot; height=&quot;184&quot; title=&quot;The Red Church in Minsk&quot; style=&quot;display: inline; background-image: none;&quot; alt=&quot;The Red Church in Minsk&quot; src=&quot;https://lh3.googleusercontent.com/-QocjD1GFILg/WlTLUdHNjaI/AAAAAAAAFFo/5q3YziY5ZTIWff6XamNpmBJAvjlhxgMrQCHMYCw/2017-09-23%2B16.48.10_thumb?imgmax=800&quot; border=&quot;0&quot;&gt;&lt;/a&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-tj8qljTvbNw/WlTLVINuohI/AAAAAAAAFFs/KGNYSoyFErI_ZvfO299cm0ev_yR-iKd5QCHMYCw/s1600-h/2017-09-23%2B09.23.23%255B3%255D&quot;&gt;&lt;img width=&quot;244&quot; height=&quot;184&quot; title=&quot;The conference venue for DotNet Summit in Minsk, 2017&quot; style=&quot;display: inline; background-image: none;&quot; alt=&quot;The conference venue for DotNet Summit in Minsk, 2017&quot; src=&quot;https://lh3.googleusercontent.com/-km_Htz7OVHs/WlTLVjNoLLI/AAAAAAAAFFw/D-0WW28_Qzsc57iip_eYj3o1WDDXpQSHgCHMYCw/2017-09-23%2B09.23.23_thumb?imgmax=800&quot; border=&quot;0&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;h3&gt;Visiting Russia&lt;/h3&gt;&lt;p&gt;Visiting Russia is more complicated. British citizens will need a visa to enter Russia, which means you’ll need to apply through &lt;a href=&quot;http://ru.vfsglobal.co.uk/&quot;&gt;VFS Global&lt;/a&gt;, the agency that handles Russian visa applications in the UK. (Note that Russian embassies in other countries often won’t process visa applications for British nationals living abroad – if you’re a Brit living elsewhere in the EU you’ll probably need to travel to London to apply for and collect your visa.) There’s a dizzying array of visa types – tourist, private, student, work, business – and you’ll find various opinions online about what kind of visa you need if you’re speaking at a technical conference. Given this ambiguity, I’ve always insisted on a business visa for my trips to Russia; I’ve never been asked anything on arrival other than when I’ll be leaving, but your experience may differ.&lt;/p&gt;&lt;p&gt;Here’s how it works. The whole process takes about two months. &lt;/p&gt;&lt;p&gt;TL;DR:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Ask the conference organisers to prepare your visa invitation&lt;/li&gt;&lt;li&gt;Fill out the form on the &lt;a href=&quot;https://www.vfsglobal.co.uk/global/index.html&quot;&gt;VFS Global website&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Wait for confirmation that your invitation has been issued&lt;/li&gt;&lt;li&gt;Go to the VFS Global office in London with your passport, form and documents&lt;/li&gt;&lt;li&gt;Submit your application&lt;/li&gt;&lt;li&gt;Wait 5 days (or 24 hours if you&apos;ve paid for overnight processing)&lt;/li&gt;&lt;li&gt;Go and pick up your passport and visa&lt;/li&gt;&lt;li&gt;Go to Russia!&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Your hosts (normally the company organising the conference) will issue you with a visa invitation. This can take a while, particularly if there are public holidays in Russia; they won’t do it more than a month in advance so you need to time everything quite carefully – especially if you have other travel commitments for which you’ll need your passport. The invitation will be sent by telex from the Russian Ministry of Foreign Affairs, to the Russian consulate in London. Your first application must be for a single-entry visa; if you’ve already done one trip and it looks like you’ll be doing a lot more you can ask them to prepare an invitation for a multi-entry visa valid for one year.&lt;/p&gt;&lt;p&gt;Meanwhile, take a look at the online application form on the VFS Global website&lt;strong&gt;.&lt;/strong&gt; You can’t submit the form electronically – once you’ve finished filling it in, it’ll produce a PDF that you need to print. It asks for things like your parents’ dates and places of birth, details of every country you’ve visited in the last 10 years, information about your marital status (and, apparently, current contact information for your former spouse if you’re divorced or separated). It might take a while for you to dig out all the details you’ll need, so make sure you look at it well in advance. You won’t be able to complete your application until you’ve got your invitation, but you can make a start and save your progress.&lt;/p&gt;&lt;p&gt;Once your invitation has been issued, your hosts (or their travel agent) will send you an email with a ‘telex reference’ on it, which you you’ll need in order to complete the visa application form. Take the printed form, along with your passport and supporting documents, to the VFS Global application office in Gee Street in London, just north of Barbican tube station. There is a PDF on their website that lists the supporting documents you’ll need – passport, application form, passport photo, invitation (telex), introductory letter, and certified bank statements if you’re self-employed. I’ve never been asked to show the introductory letter or the bank statements, but take them with you anyway, just in case.&lt;/p&gt;&lt;p&gt;The VFS Global office is open 08:30-15:00 Monday to Friday, but if you turn up to submit a business visa application between 12:00 and 14:00 they’ll ask you to come back later – they’ll need to phone the Russian consulate to confirm receipt of your telex before they’ll accept your application, and they can’t do this between 12:00 and 14:00 because the consulate closes for lunch.&lt;/p&gt;&lt;p&gt;They’ll take your passport and all your paperwork. You’ll get fingerprinted, and pay the visa application fee and service charge. Cash or credit/debit card is fine, but they don’t take Amex. It’s about £120 total for a single-entry visa ready in 5 working days; if you’re in a hurry, they have an express overnight service which costs more. They’ll give you an application receipt. Don’t lose it – you can’t get your passport back without it. &lt;/p&gt;&lt;p&gt;When your visa’s ready, you’ll have to go back to pick it up – the office is open for collections between 16:00 and 17:30. Check that your visa details are all correct – it’ll be a full-page colour sheet stuck in your passport. One thing I’ve noticed is that names may not be transliterated into Russian consistently – I’ve got two visas in my passport right now; one says I’m Дилан Битти and the other one says I’m Дилан Бети (that’s roughly ‘bitti’ and ‘beti’ written in Cyrillic), but this doesn’t appear to be a problem.&lt;/p&gt;&lt;p&gt;That’s pretty much it. When you arrive in Russia you’ll be given an immigration card at passport control, which you’ll need to show when you check into your hotel to prove you’re in the country legally, and which you’ll need to go through passport control when you leave. Take a picture of this card on your phone (or ask the hotel to photocopy it for you), so you can keep the original with you and leave a copy in the safe in your hotel. The UK foreign office says you &lt;a href=&quot;https://www.gov.uk/foreign-travel-advice/russia/local-laws-and-customs&quot;&gt;must carry your original passport&lt;/a&gt; with you at all times in Russia, and can be fined if you don’t produce it when asked. I’ve never been asked to produce mine, but I keep my passport, visa and immigration card on me, with printed photocopies of them all in the hotel room safe and digital copies in Dropbox, just in case.&lt;/p&gt;&lt;h3&gt;Practicalities&lt;/h3&gt;&lt;p&gt;Most hotels will change major foreign currencies into local currency. There are also foreign exchange bureaux everywhere, but I’ve always changed money in my hotel or used an ATM. If you’re planning to change money, carry cash in a combination of euros and US dollars, since these are most widely accepted by foreign exchange bureaux. It’s easy enough to get Russian rubles from a UK foreign exchange bureau before you leave for your trip, but you probably won’t be able to get Ukrainian hryvnia or Belarusian rubles. There are also ATMs everywhere and most restaurants will take Visa and Mastercard. If you’re using something like a Monzo card or other prepaid payment card, remember you may not have roaming data on your phone so top it up before you leave the hotel.&lt;/p&gt;&lt;p&gt;Roaming data outside the EU is seriously expensive. It’s a lot cheaper than it was a few years ago – during my first visit to Ukraine in November 2015, roaming data was &lt;strong&gt;£8/MB&lt;/strong&gt; – but it’s still not cheap; in Russia, EE currently charges &lt;a href=&quot;http://ee.co.uk/help/add-ons-benefits-and-plans/call-or-going-abroad/roaming-costs/countries/russia&quot;&gt;£7.00 for 100Mb&lt;/a&gt;, valid for 24 hours. Public wifi is widely available; in Kyiv it’s pretty ubiquitous and often completely unsecured (although you’ll need to know how to accept the terms and conditions in Ukrainian!) but in Russia and Belarus you’ll often find you need a local mobile phone number to register on public wifi spots. Make sure your phone’s unlocked before you go, pick up a local pay-as-you-go SIM card, and ask one of the conference organisers or a friendly local to help you activate and register it (remember that all the confirmation messages from the phone network will be in Russian or Ukrainian).&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-g3hlRZb4GYI/WlTLWUpBlwI/AAAAAAAAFF0/wThk4IiGpao3N-OqozCx0MSxtYQAJ3q7gCHMYCw/s1600-h/2015-11-23%2B13.20.47%255B23%255D&quot;&gt;&lt;img width=&quot;254&quot; height=&quot;448&quot; title=&quot;One of these buttons will connect you to the free wi-fi... do you know which one?&quot; style=&quot;display: inline; background-image: none;&quot; alt=&quot;One of these buttons will connect you to the free wi-fi... do you know which one?&quot; src=&quot;https://lh3.googleusercontent.com/-r5IOh16qLgA/WlTLW3exxeI/AAAAAAAAFF4/BhQcHbw8A4UDmYLrXBkYFYSNgHZWmdqAQCHMYCw/2015-11-23%2B13.20.47_thumb%255B21%255D?imgmax=800&quot; border=&quot;0&quot;&gt;&lt;/a&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-JjL0exJDnBA/WlTLXpdOxCI/AAAAAAAAFF8/TuzoMhyBbVIWYhq_Y5DhHab1NbGGXBvgACHMYCw/s1600-h/2018-01-09%2B12.44.58%255B23%255D&quot;&gt;&lt;img width=&quot;254&quot; height=&quot;448&quot; title=&quot;One of these messages is confirming that I&apos;ve added prepaid credit to my phone... apparently...&quot; style=&quot;display: inline; background-image: none;&quot; alt=&quot;One of these messages is confirming that I&apos;ve added prepaid credit to my phone... apparently...&quot; src=&quot;https://lh3.googleusercontent.com/-x730BHBr5Ww/WlTLYOPKV8I/AAAAAAAAFGA/pAn4ASOn-wgtld66JkOsuDCAYvv64XMOACHMYCw/2018-01-09%2B12.44.58_thumb%255B21%255D?imgmax=800&quot; border=&quot;0&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;It’s also worth caching Google Maps offline using &lt;a href=&quot;https://www.wired.com/2014/02/offline-google-maps/&quot;&gt;the “ok maps” feature&lt;/a&gt;, and downloading the Google Translate app and the language files for Russian to your phone so that even if you’re not online you’ll be able to navigate and translate some basic phrases.&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-h8BXo6m48yo/WlTLY4IbsgI/AAAAAAAAFGE/QqTOvUk1VXkndXCAk2Guo9q2V8jlTIpjACHMYCw/s1600-h/2017-11-13%2B09.31.38%255B3%255D&quot;&gt;&lt;img width=&quot;244&quot; height=&quot;184&quot; title=&quot;Saint Basil&apos;s Cathedral, Moscow&quot; style=&quot;display: inline; background-image: none;&quot; alt=&quot;Saint Basil&apos;s Cathedral, Moscow&quot; src=&quot;https://lh3.googleusercontent.com/-fdBGK-AoNXU/WlTLZSIb5zI/AAAAAAAAFGI/z1GIfD_5uZwT41dUDiGsWoFrX1Z9Ak63ACHMYCw/2017-11-13%2B09.31.38_thumb?imgmax=800&quot; border=&quot;0&quot;&gt;&lt;/a&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-RYYCSBSYWjU/WlTLaMyBtII/AAAAAAAAFGM/SiDo8YYQfU4wqDmf2MI-BfKN8ACF2kX3QCHMYCw/s1600-h/2017-11-11%2B11.58.42%2BHDR%255B3%255D&quot;&gt;&lt;img width=&quot;244&quot; height=&quot;184&quot; title=&quot;The Monument to the Conquerors of Space, Moscow&quot; style=&quot;display: inline; background-image: none;&quot; alt=&quot;The Monument to the Conquerors of Space, Moscow&quot; src=&quot;https://lh3.googleusercontent.com/-lOefV201opY/WlTLaleBbXI/AAAAAAAAFGQ/HLpxCJymSz03usyO3iaKp3MT0WQDUC_5ACHMYCw/2017-11-11%2B11.58.42%2BHDR_thumb?imgmax=800&quot; border=&quot;0&quot;&gt;&lt;/a&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-4-p4586fals/WlTLbBcVBQI/AAAAAAAAFGU/FKu9u1zCu48d1Q-bz_vFigQPhnTuPhlJwCHMYCw/s1600-h/2017-11-13%2B09.37.33-1%255B3%255D&quot;&gt;&lt;img width=&quot;244&quot; height=&quot;184&quot; title=&quot;Lenin&apos;s Mausoleum in Moscow&quot; style=&quot;display: inline; background-image: none;&quot; alt=&quot;Lenin&apos;s Mausoleum in Moscow&quot; src=&quot;https://lh3.googleusercontent.com/--aPshrMaeEU/WlTLb0jw1bI/AAAAAAAAFGY/pg-UYoky74QIpgVX3aYN3cyQCpshCmZxwCHMYCw/2017-11-13%2B09.37.33-1_thumb?imgmax=800&quot; border=&quot;0&quot;&gt;&lt;/a&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-xPll2qFg-I0/WlTLcUJKrCI/AAAAAAAAFGc/8jUbggVOHr0XcpKm7D2QO4ENVI-Fzi9KACHMYCw/s1600-h/2017-11-11%2B16.45.03%255B7%255D&quot;&gt;&lt;img width=&quot;244&quot; height=&quot;184&quot; title=&quot;Bunker-42 - a museum set in a Cold War nuclear bunker in Moscow&quot; style=&quot;display: inline; background-image: none;&quot; alt=&quot;Bunker-42 - a museum set in a Cold War nuclear bunker in Moscow&quot; src=&quot;https://lh3.googleusercontent.com/-Hv_GOuMQcpo/WlTLdC1TXGI/AAAAAAAAFGg/Hn-Qqr7l6K4Yx17SP6h3yb5SoH9fR0kIwCHMYCw/2017-11-11%2B16.45.03_thumb%255B1%255D?imgmax=800&quot; border=&quot;0&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;And finally – don’t forget to actually see the place! It’s all too easy to travel halfway around the world for a conference and never see anything except the inside of the Radisson Blu, so if the conference organisers have arranged a tour of the city or any excursions, go along – you’ll get to see some really interesting things. Or head out on your own. Moscow, Saint-Petersburg, and Kyiv have fast, efficient metro networks, with stations and maps in English as well as Russian/Ukrainian. The metro is great for travelling around without having to talk to anybody – buy tickets at a machine, use the automated barriers, watch the locals when it comes to navigating around the stations and do what they do. There are metal detectors everywhere, which go off constantly. It looks like they’re being ignored but apparently they give the police probable cause to search anybody they don’t like the look of - personally I’ve never had any problems at all. &lt;/p&gt;&lt;p&gt;The great thing about being at a tech conference is that many of the attendees will be locals who know the city and will be perfectly happy to take you their favourite bar or a good place for dinner or some live music or whatever you fancy doing with your spare time. Enjoy the conversations, soak up the culture, try the local food, and remember not to stay up drinking vodka until 2am when your flight is at 06:30... unless you really, &lt;em&gt;really &lt;/em&gt;want to.&lt;/p&gt;&lt;h3&gt;Sounds like fun?&lt;/h3&gt;&lt;p&gt;If all that’s piqued your interest... awesome! Here are a few events that are accepting papers at the moment.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://dotnext-piter.ru/&quot;&gt;DotNext, Saint Petersburg&lt;/a&gt;, Russia, 22-23 April (&lt;a href=&quot;https://dotnext-piter.ru/en/callforpapers/&quot;&gt;CFP&lt;/a&gt;)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://dotnetsummit.by/&quot;&gt;.NET Summit, Minsk, Belarus&lt;/a&gt;, 2-3 March (&lt;a href=&quot;https://docs.google.com/forms/d/e/1FAIpQLSfsJPMYcm1mVP4kvr7ByIF_6ZESrhzeKflmCxeODEUNKZx3qg/viewform&quot;&gt;CFP&lt;/a&gt;)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://2018.codefest.ru/speakers/en/&quot;&gt;CodeFest, Novosibirsk&lt;/a&gt;, Russia, 31 March – 1st April (&lt;a href=&quot;https://2018.codefest.ru/speakers/en/call-for-papers/&quot;&gt;CFP&lt;/a&gt;)&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Увидимся!&lt;/p&gt;</description>
          <pubDate>2018-01-09T14:02:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2018/01/09/tech-and-travel-in-russia-belarus-and.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2018/01/09/tech-and-travel-in-russia-belarus-and.html</guid>
        </item>
      
    
      
        <item>
          <title>Thank you for flying Analysis Airways</title>
          <description>&lt;p&gt;If you work in software – or even if you don’t – it’s likely that, at some point, you’ll find yourself working with a team who are completely unfamiliar with your systems. A management consultancy, a development partner, a new supplier. A team of smart, capable people who are hoping to work with you to deliver something, whether that’s process improvements or a reduction in costs or some shiny new product.&lt;/p&gt;&lt;p&gt;A common pattern here is that, at the start of the engagement, they’ll appoint some business analysts to spend a whole lot of time talking with people from your organisation to get a better idea of what it is you do. They sometimes call this ‘gathering requirements’. You’ll know it’s happening when you get half-a-dozen invitations to four-hour ‘workshops’ from somebody you’ve never met, normally with a note saying something like ‘Hey everyone! Let’s get these in the diary!’&lt;/p&gt;&lt;p&gt;Now, there’s a problem here. Asking people what’s happening is almost never the best way to find out what’s actually happening. You don’t get the truth, you get a version of the truth that’s been twisted through a series of individual perspectives, and when you’re using these interviews to develop your understanding of an unfamiliar system, this can lead to an incredibly distorted view of the organisation. Components and assemblies aren’t ranked according to cost, or risk, or complexity. They’re ranked according to how many hours a day somebody spends dealing with them. And when you consider that in these days of scripted infrastructure and continuous deployment, a decent engineer can provision an entire virtual hosting environment in the time it takes to deal with one customer service phone call, what you end up with is a view of your organisation that ranks ‘phone calls’ equal to ‘hosting environment’ in terms of their strategic value and significance.&lt;/p&gt;&lt;p&gt;When you factor in the &lt;a href=&quot;https://en.wikipedia.org/wiki/Dunning%E2%80%93Kruger_effect&quot;&gt;Dunning-Kruger effect&lt;/a&gt;, the errors and omissions, the inevitable confusion about naming things, and the understandable desire to &lt;a href=&quot;https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/&quot;&gt;manage complexity by introducing abstractions&lt;/a&gt;, you can end up with a very pretty and incredibly misleading diagram that claims to be a ‘high-level view’ of an organization’s systems.&lt;/p&gt;&lt;p&gt;There’s a wonderful example of this in neurology – a thing called the ‘&lt;a href=&quot;https://en.wikipedia.org/wiki/Cortical_homunculus&quot;&gt;cortical homunculus&lt;/a&gt;’; a distorted representation of the human body where the various parts of the body are magnified based on the density of nerve endings found therein. Looks like this:&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;img alt=&quot;Homunculus&quot; src=&quot;http://www.maxplanckflorida.org/fitzpatricklab/homunculus/cms/wp-content/uploads/2015/03/homunculus-front.png&quot;&gt;&lt;a title=&quot;https://www.maxplanckflorida.org/fitzpatricklab/homunculus/&quot; href=&quot;https://www.maxplanckflorida.org/fitzpatricklab/homunculus/&quot;&gt;&lt;font size=&quot;1&quot;&gt;https://www.maxplanckflorida.org/fitzpatricklab/homunculus/&lt;/font&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;It’s recognisably human, sure. But it’s a grotesque distortion of what a human being actually looks like – brilliant for demonstrating neurology, but if you used it as a model when designing clothes or furniture your customers would be in for one hell of a shock. And we know it’s grotesque, because we know what human beings are &lt;em&gt;supposed &lt;/em&gt;to look like – in fact, it’s the difference between the ordinary and the grotesque that makes these cortical homunculi interesting.&lt;/p&gt;&lt;p&gt;The problem with software is that it’s made out of invisible electric magic, and the only way to see it at all is to rely on some incredibly coarse abstractions and some very rudimentary visualisation tools. &lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;img width=&quot;540&quot; height=&quot;360&quot; src=&quot;https://ironmaiden.com/media/gallery/ed-force-one-36186.jpg&quot;&gt;&lt;br&gt;&lt;a title=&quot;https://ironmaiden.com/ed-force-one&quot; href=&quot;https://ironmaiden.com/ed-force-one&quot;&gt;&lt;font size=&quot;1&quot;&gt;https://ironmaiden.com/ed-force-one&lt;/font&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Imagine, for one second, that we’ve hired some consultants to help us design an aircraft. They send over some business analysts, and book some time with the ‘domain experts’ to talk over the capabilities of the existing system and gather requirements. The experts, of course, being the pilots and cabin crew – which, for a Boeing 747 like &lt;a href=&quot;https://ironmaiden.com/ed-force-one&quot;&gt;Ed Force One&lt;/a&gt;, is three flight crew and somewhere around dozen cabin attendants. They spend a couple of very long days interviewing all these experts; maybe they even have the opportunity to watch them at work in the course of a typical flight.&lt;/p&gt;&lt;p&gt;And then they come up with this: the high-level architectural diagram of a long-range passenger airliner:&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-pRoHIhEjUL8/WifYYlz2ddI/AAAAAAAAE3k/MsBG7q5Z-EQ2aPa9nmjay-J-bl-sAdC6QCHMYCw/s1600-h/image%255B27%255D&quot;&gt;&lt;img width=&quot;540&quot; height=&quot;500&quot; title=&quot;image&quot; style=&quot;border: 0px currentcolor; border-image: none; display: inline; background-image: none;&quot; alt=&quot;image&quot; src=&quot;https://lh3.googleusercontent.com/-xQHR4qUvS9g/WifYZLcv6NI/AAAAAAAAE3o/ESq89YEyCtkQwZhU-G9g57VGalsmu7SOACHMYCw/image_thumb%255B17%255D?imgmax=800&quot; border=&quot;0&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Now, to an average passenger, that probably looks like a pretty reasonable representation of the major systems of a Boeing 747. Right? Take a look. Can you, off the top of your head, highlight the things that are factually incorrect? &lt;/p&gt;&lt;p&gt;That’s why this diagram is dangerous. It’s nicely laid out and easy to understand. It looks good. It inspires trust… and&lt;strong&gt; it’s a grotesque misrepresentation of what’s actually happening. &lt;/strong&gt;Like the cortical homunculus, it’s not actually &lt;em&gt;wrong&lt;/em&gt;, but it’s horribly distorted. In this case, the systems associated with the cabin attendants are massively overrepresented - because there’s 12 of them, as opposed to three flight crew – so 400% more workshop time and 400% more anecdotal insight. The top-level domains – flight deck, first class, economy class – are based on a valid but profoundly misleading perspective on the systems architecture of an airliner. The avionics and flight control systems are reduced to a footnote based on three days of interviews with the pilots, somebody with a bit of technical knowledge has connected the engines to the pedals (like a car, right?) and the rudder to the steering wheel (yes, a 747 &lt;a href=&quot;https://engineering.mit.edu/engage/ask-an-engineer/how-does-an-aircraft-steer-while-taxiing-on-a-runway/&quot;&gt;does have a steering wheel&lt;/a&gt;), the wings are connected to the engines as a sort of afterthought…&lt;/p&gt;&lt;p&gt;Now, when the project is something tangible – like an office building or a bridge or an airliner, it won’t take long at all before somebody goes ‘um… I hate to say it, but this is wrong. This is so utterly totally wrong I can’t even begin to explain how wrong it is.’ Even the most inexperienced project manager will probably smell a rat when they notice that 20% of the budget for a new transatlantic airliner has been allocated to drinks trolleys and laminated safety cards. &lt;/p&gt;&lt;p&gt;But when the project is a software application – you know, a couple of million moving parts made out of invisible electronic thought-stuff that bounce around the place at the speed of light, merrily flipping bits and painting pixels and only sitting still when you catch one of them in a debugger and poke it line-by-line to see what it does – that moment of clarity might never happen. We can’t see software. We don’t know what it’s supposed to look like. We don’t have any instinct for distinguishing the ordinary from the grotesque. We rely on &lt;a href=&quot;http://geekandpoke.typepad.com/geekandpoke/2009/11/enterprise-architecture-made-easy-part-1.html&quot;&gt;lines and rectangles&lt;/a&gt;, and we sort of assume that the person drawing the diagram knew what they were doing and that somebody else is looking after all the intricate detail that didn’t make it into the diagram. &lt;/p&gt;&lt;p&gt;And remember, nobody here has screwed up. The worst thing about these kinds of diagrams is that they’re produced by competent, honest, capable people. The organization allocates time for everybody to be involved. The stakeholders answer all the questions as honestly as they can. The consultants capture all of that detail and synthesise it into diagrams and documents, and everybody comes away with the satisfying sense of a job well done.&lt;/p&gt;&lt;p&gt;That’s not to say there’s no value in this process. But these kinds of diagrams are just one perspective on a system, and that’s dangerous unless you have a bunch of other perspectives to provide a basis for comparison. A conceptual model of a Boeing 747 based on running cost – suddenly the engines are a hell of a lot more important than the drinks trolley. A conceptual model based on electrical systems. Another based on manufacturing cost. Another based on air traffic control systems and airport infrastructure considerations. And yes, producing all these models takes a lot more than arranging a week of interviews with people who are already on the payroll, which is why so many projects get as far as that high-level system diagram and then start delivering things. &lt;/p&gt;&lt;p&gt;And why, somewhere in your system you almost certainly have the software equivalent of a hundred-million-dollar drinks trolley.&lt;/p&gt;&lt;p&gt;Thank you for flying Analysis Airways.&lt;/p&gt;</description>
          <pubDate>2017-12-05T18:44:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2017/12/05/thank-you-for-flying-analysis-airways.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2017/12/05/thank-you-for-flying-analysis-airways.html</guid>
        </item>
      
    
      
        <item>
          <title>Goodbye Spotlight… Hello Skills Matter!</title>
          <description>&lt;p&gt;I have some exciting news. I&apos;ll be leaving &lt;a href=&quot;https://www.spotlight.com/&quot;&gt;Spotlight&lt;/a&gt; at the end of January, to take up a new role as CTO at &lt;a href=&quot;https://www.skillsmatter.com/&quot;&gt;Skills Matter&lt;/a&gt;. After fourteen years at Spotlight, this is a massive change for me, it&apos;s a massive change for Spotlight, and (I hope!) it&apos;s going to be a massive change for Skills Matter as well - but it&apos;s also a very natural next step for me, and a move that I think is going to unlock all sorts of exciting possibilities over the coming years.&lt;p&gt;I first started working with Spotlight way back in 2000 - in Internet time, that&apos;s about a hundred million years ago. My first job after I graduated was working for a company who built data-driven websites in ASP; &lt;a href=&quot;http://spotlight.com/&quot;&gt;spotlight.com&lt;/a&gt; was the first big commercial site I ever built, and I&apos;ve been working with them ever since - initially as a supplier, then as webmaster (remember those?), then head of IT, then systems architect. I&apos;ve come with them on a journey from Netscape Navigator and dial-up modems to smartphones and REST APIs and progressive web apps, and it&apos;s been a blast - we&apos;ve shipped some really excellent projects, we&apos;ve learned a lot together, and I&apos;ve had the pleasure of working with awesome people and a lot of very cool technology.&lt;p&gt;Around ten years ago, up to my eyeballs in ASP.NET WebForms and wondering if everybody found them as unpleasant as I did, I started going along to some of the tech industry events that were happening here in London to get a bit of external perspective. I was at the first Future Of Web Apps conference back in 2007, the first Alt .NET UK &apos;unconference&apos;, the DDD events held at Microsoft a few times a year... and it wasn&apos;t long after that that I met Wendy Devolder and Nick Macris, who at the time were still running Skills Matter out of a basement in Sekforde Street, and hiring the crypt underneath St James Church on Clerkenwell Green for bigger events. That&apos;s where I did my first-ever technical talk - a fifteen-minute introduction to jQuery which I presented at the&lt;a href=&quot;https://skillsmatter.com/conferences/211-open-source-dot-net-exchange#program&quot;&gt; Open Source .NET Exchange&lt;/a&gt;. And, as the saying goes, I&apos;ve never really looked back. This year, I&apos;ve &lt;a href=&quot;http://www.dylanbeattie.net/p/conferences-talks-and-user-groups.html&quot;&gt;spoken at 20+ tech events in nine countries&lt;/a&gt;, from huge international conferences to local user groups around the UK. I&apos;m on the programme committee for NDC London, FullStack and Progressive.NET, I&apos;m helping to run the London .NET User Group, I&apos;ve started giving &lt;a href=&quot;http://www.dylanbeattie.net/p/workshop-real-world-rest-and-hands-on.html&quot;&gt;training courses and workshops&lt;/a&gt; on building hypermedia APIs and scalable systems. And, because I&apos;m one of those kinds of nerds, every time I take part in a tech event I come away from it buzzing with ideas about how to make it better - for attendees, organisers, volunteers, speakers... everybody. The only problem was finding the time to implement those ideas, and so when Wendy got in touch a few months ago to ask if I&apos;d be interested in joining the team at Skills Matter and putting some of those ideas into practice, the timing was just right.&lt;p&gt;Now, this isn&apos;t intended to be a puff-piece. I&apos;ve known the team here at Skills Matter for many, many years, we’ve done some really excellent things together, and I&apos;m really excited to be joining them. I’ve also spoken to literally hundreds of people whose experience at their conferences and events has been nothing but positive, and that’s played a big part in my decision to come on board here. However, I also know that a few of you have had some frustrating experiences with them in the past - about how they organise their community events, about logistics and speaker invitations for their bigger conferences... even things like having the ‘wrong kind of cider’ in the Space Bar. :) Well, I want to hear all the details. I know we can&apos;t please all of the people all of the time, but the fact the team here has hired me means they&apos;re up for a bit of spirited discussion and an influx of new ideas, so if you wanna drop me an email or bend my ear over a beer sometime, I&apos;d love to hear from you and see what we can do about it.&lt;p&gt;For the team at Spotlight, this marks the dawn of a new era. I’ve come dangerously close to becoming a &lt;a href=&quot;https://medium.com/@ziobrando/the-rise-and-fall-of-the-dungeon-master-c2d511eed12f&quot;&gt;dungeon master&lt;/a&gt;, and to me that’s a clear sign that it’s time to hand over the &lt;a href=&quot;http://geekandpoke.typepad.com/geekandpoke/2009/11/enterprise-architecture-made-easy-part-1.html&quot;&gt;lines and rectangles&lt;/a&gt; to somebody new. To quote from Alberto Brandolini’s “&lt;a href=&quot;https://medium.com/@ziobrando/the-rise-and-fall-of-the-dungeon-master-c2d511eed12f&quot;&gt;The Rise and Fall of the Dungeon Master&lt;/a&gt;”:&lt;blockquote&gt;&lt;p&gt;“I am not suggesting to hire a hitman, but to acknowledge that the project would be better off without the Dungeon Master around. If you’re looking for a paradigm shift, you’ll need a team with a different attitude and emotional bonding with the legacy.”&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I&apos;m sad not to be coming with them on the next phase of their journey, but it&apos;s clear that Spotlight&apos;s future lies along a different path from mine. They&apos;ve got an absolutely first-class team there, I&apos;m sure they&apos;re going to go on to even bigger and better things, and I&apos;m looking forward to catching up with them all over a beer every once in a while and seeing how it&apos;s all going.&lt;p&gt;First and foremost, though, this is a chance for me to get more involved with the global tech community. I absolutely love the part of my life that involves bouncing all over the world talking about tech, sharing ideas and meeting interesting people; and I&apos;ll be taking advantage of all those opportunities to chat about what Skills Matter can do to help support the tech community - whether it&apos;s open source projects or corporate development teams, tiny meetups or big international conferences. I&apos;ll also be working with the software team here to create some really exciting things, and of course I&apos;ll be getting even more closely involved in the conferences and meetups that we host here at CodeNode and at various venues around London. For Skills Matter, my experience as a developer, user group organiser and programme committee volunteer - not to mention my connections in the tech industry all over the world - will play a big part in deciding our plans and priorities for the next few years.&lt;p&gt;And the best part? I even get to work the odd shift behind the bar here at CodeNode once in a while, thus becoming the tech industry’s answer to &lt;a href=&quot;https://en.wikipedia.org/wiki/The_Brentford_Trilogy&quot;&gt;Neville the Part-Time Barman&lt;/a&gt;. So next time you’re round Moorgate of an evening, drop in and say hi.&lt;p&gt;Exciting times, indeed.&lt;/p&gt;</description>
          <pubDate>2017-11-24T17:44:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2017/11/24/goodbye-spotlight-hello-skills-matter.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2017/11/24/goodbye-spotlight-hello-skills-matter.html</guid>
        </item>
      
    
      
        <item>
          <title>London .NET User Group Open Mic Night</title>
          <description>&lt;div style=&quot;-en-clipboard: true;&quot;&gt;
Last night’s &lt;a href=&quot;https://www.meetup.com/London-NET-User-Group/&quot;&gt;London .NET meetup&lt;/a&gt; was an ‘open mic’ session. Rather than inviting speakers to talk for an hour or so as we normally do, we invited members of the group to come along and talk for 10-15 minutes about their own projects, things they’re working on, or just stuff they think is cool. It’s the first open mic session we’ve had in a long while, but based on the success of last night’s event I think we’ll try to make these more of a regular thing in 2018. Quite a few of the speakers who came along were talking about their own .NET open source projects, and showing off some very, very cool things; here’s a quick rundown.&lt;br /&gt;
&lt;br /&gt;
First up was &lt;a href=&quot;https://twitter.com/philpursglove?lang=en&quot;&gt;Phil Pursglove&lt;/a&gt;, giving us a whistle stop tour of &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/cosmos-db/introduction&quot;&gt;Cosmos DB&lt;/a&gt;, a new database platform that Microsoft are now offering on Azure. I’ve seen a couple of talks this year about Cosmos, and it looks really rather nice. It’s got protocol-level compatibility with MongoDB (what Microsoft call ‘bug compatible’), plus support for SQL and a couple of other language bindings. One of the coolest features of Cosmos is native support for &lt;a href=&quot;https://docs.microsoft.com/en-gb/azure/cosmos-db/consistency-levels&quot;&gt;multiple consistency models&lt;/a&gt;, allowing you to optimise your own application for your particular requirements - with the ability to override the global consistency model on a per-request basis. There’s a &lt;a href=&quot;https://azure.microsoft.com/en-gb/try/cosmosdb/&quot;&gt;time-limited free trial available here&lt;/a&gt; - check it out.&lt;br /&gt;
&lt;br /&gt;
Next up, &lt;a href=&quot;https://twitter.com/robinem&quot;&gt;Robin Minto&lt;/a&gt; gave us a run-through of &lt;a href=&quot;https://www.owasp.org/index.php/OWASP_Zed_Attack_Proxy_Project&quot;&gt;OWASP ZAP&lt;/a&gt;, a proxy-based web security tool created by the Open Web Application Security Project (OWASP). ZAP is beautifully simple; you install it (or fire up the Docker image), it acts as a web proxy whilst you navigate through some of the primary user journeys on your web application, but in the background it’s probing your server and scanning your HTML for a whole range of common security vulnerabilities - and when you’re done, it’ll generate a security report you can share with the rest of your team.&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;https://unop.uk/about/&quot;&gt;James Singleton&lt;/a&gt; - author of &lt;a href=&quot;https://unop.uk/book/&quot;&gt;ASP.NET&amp;nbsp;Core 2 High Performance &lt;/a&gt;- gave us a very cool live demo of some of the cross-platform capabilities of .NET Core 2.0, including using his Windows laptop to cross-compile a web app for ARM Linux and running it live on a Raspberry Pi. What I found really impressive about this is that it didn’t require any .NET framework or runtime install on the Pi - it’s just vanilla (well, raspberry!) Raspbian Linux with a couple of things like libssl, and everything else is included in the deployment package created by the dot net tooling.&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;https://twitter.com/ethomson?lang=en&quot;&gt;Ed Thomson&lt;/a&gt;, the Git program manager for Visual Studio Team System, did a great walkthrough of his open source project &lt;a href=&quot;https://github.com/libgit2/libgit2sharp&quot;&gt;libgit2sharp&lt;/a&gt; - a set of C# language bindings for working with local and remote git repositories. If you’ve ever had to parse the output from the Windows git command line tools, you’ll know how painful it can be - but with libgit2sharp, you can use C# or Powershell to create automated build tools, manipulate your git repositories and do all sorts of cool stuff.&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;https://twitter.com/jasond_s&quot;&gt;Jason Dryhurst-Smith&lt;/a&gt; gave us a demo of his &lt;a href=&quot;https://github.com/CorrelatorSharp/CorrelatorSharp&quot;&gt;CorrelatorSharp&lt;/a&gt; project - “your one-stop shop for context-aware logging and diagnostics” - a library that allows you to track the context of operations across multiple services and operations, including support for frameworks like NLog and RestSharp and a client-side JavaScript library.&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;https://twitter.com/xivk?lang=en&quot;&gt;Ben Abelshausen&lt;/a&gt; came along to show us &lt;a href=&quot;http://www.itinero.tech/&quot;&gt;Itinero&lt;/a&gt;, an open-source route planning library for .NET which you can use within your own applications to calculate routes and analyse map data.&lt;br /&gt;
&lt;br /&gt;
Finally, we had &lt;a href=&quot;https://twitter.com/citizenmatt&quot;&gt;Matt Ellis&lt;/a&gt; from Jetbrains giving us ’Ten &lt;a href=&quot;https://www.jetbrains.com/rider/&quot;&gt;Jetbrains Rider&lt;/a&gt; Debugging Tips in Ten Minutes’ - including some really neat stuff like cross-platform support for DebuggerDisplay attributes and the ability to define interdependent breakpoints in your code.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Huge thanks to all our speakers and to everyone who came along - it’s great to see so much enthusiasm and activity going in with .NET open source, and with .NET Core 2.0 going fully cross-platform and tools like Rider and Visual Studio Code, it’s only going to get more interesting.
&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;!--?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?--&gt;

&lt;br /&gt;
&lt;div&gt;
See you all at the next one!&amp;nbsp;&lt;/div&gt;
</description>
          <pubDate>2017-11-09T18:10:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2017/11/09/london-net-user-group-open-mic-night.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2017/11/09/london-net-user-group-open-mic-night.html</guid>
        </item>
      
    
      
        <item>
          <title>Why You Weren’t Picked For NDC London</title>
          <description>&lt;p&gt;Over the past two weeks, a small team of us has been putting together &lt;a href=&quot;https://ndc-london.com/agenda/&quot;&gt;the agenda&lt;/a&gt; for &lt;a href=&quot;https://ndc-london.com/&quot;&gt;NDC London 2018&lt;/a&gt;, which is happening at the Queen Elizabeth II Centre in Westminster this coming January. As somebody who’s submitted a lot of conference talks over the years, I know how exciting it is getting the email confirming that you’ve been accepted — and how demoralizing it can be getting the email saying ‘We’re sorry to say that…’&lt;p&gt;Well, now that I’ve been on the other side of that process a few times, I see things very differently, so I wanted to take this chance to tell you all how the selection programme actually works, why you didn’t get picked, and why it shouldn’t put you off.&lt;p&gt;First, though, I want to say a huge thank you to everybody who submitted, and congratulations to the people who have been selected. It’s been a lot of work to pull everything together, but I’m really happy about the programme of amazing people and top-class talks that we’ve got this year — and I’m particularly excited that we’ve been able to include so many great speakers from the developer community who will be speaking at NDC for the first time. Welcome, all of you.&lt;p&gt;For all the speakers who didn’t get selected this time around, I really hope this article might help you understand why. (TL;DR: we just had too many good talks submitted!)&lt;p&gt;First, a basic rule of conferences — they have to generate a certain amount of money in order to operate. Yes, there’s &lt;a href=&quot;https://developerdeveloperdeveloper.com/&quot;&gt;community&lt;/a&gt; &lt;a href=&quot;https://www.dddnorth.co.uk/&quot;&gt;events&lt;/a&gt; &lt;a href=&quot;http://ddd.scot/&quot;&gt;like&lt;/a&gt; &lt;a href=&quot;https://www.dddeastanglia.com/&quot;&gt;DDD&lt;/a&gt; that are free to attend and are run with minimal sponsorship — and I think those events are every bit as valuable to our community as the big commercial conferences — but once you start dealing with international speakers and multi-track events running across several days, you need to start thinking about commercial considerations. Conferences generate revenue from two sources — ticket sales and sponsorship. Both of those boil down to creating an event that people want to attend (and, in many cases, can persuade their boss is worth the cost and the time), so that’s what we, as the programme committee, are trying to do.&lt;p&gt;I should mention that on every conference I’ve worked with, the programme committee has consisted entirely of volunteers — four or five people from within the industry who are happy to donate their free time to help put the programme together. They’ll normally get a complimentary ticket to the conference in return, but it’s not a job, and nobody’s getting paid to do it.&lt;p&gt;Most software conferences run a public ‘call for submissions’ (aka ‘call for papers’ or CfP) where anybody interested in speaking can submit talks for consideration. Once the CfP has closed, the programme committee has the unenviable task of going through all the talks that have been submitted and picking the ones that they think should be included. NDC London has five tracks of one-hour talks, over three days. Once you’ve allowed for keynotes and lunch breaks, that gives you fewer than 100 talk slots. &lt;strong&gt;We had 732 sessions submitted. &lt;/strong&gt;If we’d said ‘yes’ to all of them, we’d have ended up with a conference lasting three weeks with a ticket price of over £7,500… good luck getting your manager to sign off on attending that one.&lt;p&gt;So, how do you pick the top 100 talks from 732 submissions? Well, here’s what we look for.&lt;p&gt;&lt;strong&gt;First &lt;/strong&gt;— quality. This one doesn’t really help much, because the vast majority of the talks submitted to a high-profile event like NDC are excellent, but there might be a handful that you can reject outright. These tend to be the talks that look like sales pitches — single-vendor solutions submitted by somebody who works for the vendor in question. You want to use a conference to sell your product? No problem — get a booth, or buy a sponsorship package. But we’re not going to include your sales pitch at the expense of someone else’s submission.&lt;p&gt;It’s also worth pointing out that when we get to the final round of submissions, when it’s getting really hard to make a call, we’ll look very critically at the quality of the submission itself. We’ll look for a good title, a clear, concise summary, a succinct speaker biography and a good-quality headshot — basically, a submission that makes it really clear who you are, what you’re talking about, why it’ll be interesting, and that you’re going to put in the effort required to deliver a great presentation. There’s no hard-and-fast rule to this; there are some &lt;a href=&quot;https://sendgrid.com/blog/write-speaking-proposal-tech-conference/&quot;&gt;excellent&lt;/a&gt; &lt;a href=&quot;https://medium.com/@fox/how-to-write-a-successful-conference-proposal-4461509d3e32&quot;&gt;articles&lt;/a&gt; out there about how to write good proposals. My own rule of thumb when I’m submitting is 100 characters for the title, 2,000 for the abstract and 1,000 for the speaker bio, and I often use Ted Neward’s approach of ‘&lt;a href=&quot;http://blogs.tedneward.com/post/speaking-tips-proposals/&quot;&gt;pain and promise&lt;/a&gt;’ — ‘here’s a problem you’ve had, here’s an idea that might fix it’ — when I’m writing proposals. Every speaker is different, and we all have our own style, but if your talk summary is only one or two lines or you’ve pasted in your entire professional CV instead of writing a short speaker bio, you may well get rejected in favour of somebody who’s clearly put more time into drafting their submission.&lt;p&gt;&lt;strong&gt;Second &lt;/strong&gt;— relevance. Conferences have a target audience; NDC has evolved from being primarily a .NET conference into an event with a much broader scope, but we know the kind of developers who normally come to NDC London and what sort of things they’re interested in. For example, this year we decided to decline any C++ talks, purely because there’s very few C++ developers in our target audience. That said, we do try to include things that might be interesting to our audience, even if they’re not immediately relevant. Topics like Kotlin and Elm are still relatively esoteric in terms of numbers of users, but the industry buzz around them means we try to include things like this on the programme because we’re confident that people will want to see them.&lt;p&gt;&lt;strong&gt;Third &lt;/strong&gt;— diversity. We could very easily have filled the entire programme with well-known white male speakers talking about JavaScript — but we think a good conference should feature diverse speakers, presenting diverse topics, with a range of presentation styles. We’re not just talking about gender and ethnicity, either — we want to see new faces alongside regular speakers, and bleeding-edge technology topics alongside established patterns and practices. That said, we would never accept a substandard talk purely for the sake of diversity; the way to get more balance in the programme without compromising quality is to start with a wider range of submissions. Many conferences suffer from a lack of diversity in the talks that get submitted — it’s just the same people submitting the same topics year after year — so throughout the year people like me go to tech events and conferences, look out for great speakers and talks that haven’t appeared at NDC before, and invite them to submit.&lt;p&gt;&lt;strong&gt;Finally&lt;/strong&gt;, there’s the invited speakers — the people who we definitely want to see on the programme. They fall into two broad camps. There’s the big names — the people with 100K+ Twitter followers, the published authors, the high-profile open source project leaders. Now, being famous-on-the-internet doesn’t automatically mean you’re a brilliant speaker — speaking for an hour in front of a few hundred people takes a very different set of skills from running an open-source project or writing a book — but we’re lucky in our industry to have a lot of people who are well known, well respected, and really, really good on a conference stage. And these people are important, because their involvement gives us a fantastic signal boost. More interest translates into more ticket sales — which means more budget for covering speaker travel, catering, facilities, the entertainment at the afterparty, and all the other things that make a good conference such a positive experience for the attendees.&lt;p&gt;The programme committee also invites a lot of new speakers because it’s a great way of getting some new faces and new ideas on to the programme. As I mentioned above, many of us spend a lot of time going to user groups, meetups and conferences, and when I see somebody deliver a really good talk, I’ll invariably get in touch afterwards and ask if they’d be interested in submitting it to an event like NDC.&lt;p&gt;So, those are our constraints. We want to deliver a balanced mix of big names and new faces. We want to promote diversity in an industry that’s &lt;a href=&quot;https://insights.stackoverflow.com/survey/2017#demographics&quot;&gt;still overwhelmingly white and male&lt;/a&gt; (never mind the ongoing fascination with JS frameworks and microservices). We want to offer a compelling combination of established technology and interesting esoterica; of stuff that’s interesting, stuff that’s relevant, and stuff that’s fun…&lt;p&gt;And we get to pick fewer than 100 talks out of 750 submissions, which means whatever we do, we’re going to be sending a whole lot of emails at the end of it saying ‘Sorry, your talk wasn’t accepted…’ — and that’s not much fun, because we’re turning down good content from good speakers. In many cases those people are good friends as well — the tech industry is a very friendly place and I count the people I’ve met through it among my closest friends. But that’s how it works.&lt;p&gt;It’s humbling to be part of an industry where so many talented people are willing to invest their time in sharing their own expertise. Speaking at conferences is a really rewarding experience, but it’s also a huge amount of work, preparation, rehearsal, logistics, and time away from home. Without speakers, there’d be no conferences — but, as I hope I’ve explained here, there are more excellent people and talks out there than any one event can ever hope to accommodate.&lt;p&gt;The thing is, there’s absolutely no shortage of great conferences and events. Don’t be discouraged. Keep submitting. If you didn’t make the cut for NDC London, submit to &lt;a href=&quot;https://ndcoslo.com/&quot;&gt;Oslo&lt;/a&gt; and &lt;a href=&quot;https://ndcsydney.com/&quot;&gt;Sydney&lt;/a&gt;. And &lt;a href=&quot;http://buildstuff.lt/&quot;&gt;BuildStuff&lt;/a&gt;, and &lt;a href=&quot;http://www.devsum.se/&quot;&gt;DevSum&lt;/a&gt;, and &lt;a href=&quot;http://www.oredev.org/&quot;&gt;Øredev&lt;/a&gt;, and &lt;a href=&quot;https://skillsmatter.com/conferences/9815-fullstack-2018-the-conference-on-javascript-node-and-internet-of-things&quot;&gt;FullStack&lt;/a&gt;, and &lt;a href=&quot;https://skillsmatter.com/conferences/10107-dot-net-london-2018&quot;&gt;Progressive.NET&lt;/a&gt;, and &lt;a href=&quot;https://dotnext.ru/en/&quot;&gt;DotNext&lt;/a&gt;, and &lt;a href=&quot;http://www.seladeveloperpractice.com/&quot;&gt;Sela Developer Practice&lt;/a&gt;, and &lt;a href=&quot;https://sddconf.com/&quot;&gt;SDD&lt;/a&gt;, and &lt;a href=&quot;https://qconlondon.com/&quot;&gt;QCon&lt;/a&gt;, and &lt;a href=&quot;https://websummit.com/&quot;&gt;WebSummit&lt;/a&gt;. And if none of those grab you, sign up for services like &lt;a href=&quot;http://theweeklycfp.com/&quot;&gt;The Weekly CFP&lt;/a&gt; and &lt;a href=&quot;https://tinyletter.com/techspeak&quot;&gt;Technically Speaking&lt;/a&gt; that will email you about conferences that are looking for speakers and submissions.&lt;p&gt;Finally, you know the one thing that every really good speaker I’ve ever seen has in common? &lt;strong&gt;It’s that they work hard on interesting things, and they love what they do.&lt;/strong&gt; Maybe it’s their job. Maybe they lead an open-source project, or run a user group, or they’re writing a book. But if you love what you do and you want to share that enthusiasm, it’ll happen. Just give it time.&lt;/p&gt;</description>
          <pubDate>2017-10-17T09:23:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2017/10/17/why-you-werent-picked-for-ndc-london.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2017/10/17/why-you-werent-picked-for-ndc-london.html</guid>
        </item>
      
    
      
        <item>
          <title>London, London, Uber Alles</title>
          <description>&lt;p&gt;I read with some interest yesterday that Transport for London (TfL) are &lt;a href=&quot;https://tfl.gov.uk/info-for/media/press-releases/2017/september/licensing-decision-on-uber-london-limited?intcmp=50167&quot;&gt;not renewing Uber’s license&lt;/a&gt; to operate in London. TfL have cited concerns over Uber’s driver screening and background checks, and Uber’s use of ‘&lt;a href=&quot;https://en.wikipedia.org/wiki/Greyball&quot;&gt;Greyball&lt;/a&gt;’, a software component designed and built by Uber to bypass all sorts of regulatory mechanisms, including using a phone’s GPS to recognise when the phone is at being used at Apple HQ so that the Apple engineers who review iOS applications won’t see the hidden features that Apple aren’t supposed to know about.&lt;/p&gt;&lt;p&gt;I use Uber a lot. Their service used to be absolutely excellent, and is still pretty good. It’s not as good as it used to be. It takes longer to get a car than it used to, particularly in central London. But, as a passenger (and yes, I know I’m a white male passenger, although some of my Uber experience dates from a period when I did have quite serious mobility issues whilst recovering from a skiing injury), I have found Uber to be a really good service. I’ve used it all over the world — London, Bristol, Brussels, Kyiv, Saint-Petersburg, and as of last night, Minsk. I missed it in Tel Aviv, where it’s been outlawed and everyone uses Gett instead, although Gett in Israel appears to operate exactly the same as Uber does in London so I’m not quite sure what the distinction is.&lt;p&gt;I’ve had some seriously impressive experiences as an Uber customer. In London, I once left my guitar in the back of an Uber that dropped me home at 4am after a horribly delayed flight. I realised within minutes. I used the app to phone the driver, who immediately turned around and brought it back, and was very taken aback when I insisted on paying him for the extra journey. In Kyiv I’ve used Uber to travel safely from a place I couldn’t pronounce to a place I couldn’t find, in a city where I couldn’t speak the language or even read the alphabet, with a driver who spoke no English, and I’ve done it with absolutely no fear of getting ripped off or robbed.&lt;p&gt;It’s not all been smooth. In Bristol I once had an Uber driver — sorry, “partner-driver” — who missed the turning four times in a row and then explained, giggling, that he’d never driven a cab before and didn’t know how to use satellite navigation. In London I’ve actually been in an Uber car that was pulled over by the police for speeding.. the driver panicked, drove away, realised what he’d done, thought better of it and reversed down Moorgate, in rush hour traffic, back to where a rather surprised-looking police officer immediately placed him under arrest. In both of those cases I complained, Uber investigated (something they can do really easily, thanks to GPS tracking of exactly what all their vehicles are doing at any moment) and notified me within 24 hours that the drivers in question had been suspended and would not be driving for the company again. I’ve used their app to claim refunds where I was overcharged — but also to reimburse drivers who undercharged me because of technical problems.&lt;p&gt;Now, here’s the two points I think are really important. One — Uber didn’t actually solve any hard problems. They didn’t invent GPS, or cellular phone networks, or draw their own maps of the world’s major cities. They just waited until exactly the right moment — the moment everyone had GPS, and online payments were easy, and smartphones were cheap enough that it was cost-effective to use them as the basis of a ride-sharing application — and then they pounced. Now don’t get me wrong, they did it incredibly well, and the user experience — particularly in the early days — was absolutely first-class. Usability features like being able to photograph your credit card using your phone camera instead of having to type the number in were a game-changer — the kind of features that people would show off to their friends in the pub just because no-one had ever seen it before. But Uber didn’t solve any hard engineering problems. There’s no PageRank algorithms or Falcon 9 reusable rockets being invented here. Uber’s success is down to absolutely first-class customer experience, aggressive expansion, and their willingness to enter target markets without the slightest consideration for how their service might affect the status quo. And their protestations about the ruling &lt;a href=&quot;http://www.independent.co.uk/news/business/news/uber-london-ban-taxi-drivers-lose-job-risk-40000-gig-economy-ride-hailing-tfl-no-licence-a7961556.html&quot;&gt;threatening 40,000 jobs&lt;/a&gt; is a bit rich given how adamantly they insist that they don’t actually employ any drivers, despite a &lt;a href=&quot;https://www.theguardian.com/technology/2016/oct/28/uber-uk-tribunal-self-employed-status&quot;&gt;court ruling to the contrary&lt;/a&gt;.&lt;p&gt;If it hadn’t been them, it would have been someone else, which brings me to my second point. The London taxi cab industry was just crying out for somebody to come in and kick seven bells out of it. I’ve lived in London since 2003, and I’ve taken many, many taxis over the years. For all the talk about “The Knowledge”, I have lost count of the number of times I’ve had to give a black cab driver turn-by-turn directions because they don’t know where I’m going and they’re not allowed to rely on sat-nav. I’ve lost count of the number of times I’ve had to ask a cab driver to stop at a cash machine because they don’t take credit cards. I’ve lost track of the number of hours of my life I’ve spent stood on street corners, in the rain, with more luggage than I can possibly carry home on the bus, waiting for the glow of an orange light that might take me home. Or might just ask where I’m going and drive away with a shake of the head and a ‘no, mate’ because my journey isn’t convenient for them.&lt;p&gt;The alternative, of course, was minicabs. Booked in advance from a reputable operator, they were generally pretty good. Not always, but most of the time they’d show up on time and take you where you wanted to go. Or, at the end of a long night out, you’d wander up to one of Soho’s many illustrious minicab offices, and end up in the back seat of an interesting-smelling Toyota Corolla wondering if the driver was really the person who’d passed all the necessary tests and checks, or one of their cousins who’d borrowed the cab for the night to earn an extra few quid. (On two separate occasions I’ve been offered this as an explanation for a minicab driver not knowing where they’re going…)&lt;p&gt;A lot of people are very upset about yesterday’s news — a &lt;a href=&quot;https://www.change.org/p/save-your-uber-in-london-saveyouruber&quot;&gt;petition to ‘save Uber’ has attracted nearly half a million signature&lt;/a&gt;s since the announcement — but in the long term, I don’t think it really matters whether Uber’s license is renewed or not. There’s no way people in London are going to go back to standing on rainy street corners waving their arms at every orange light that goes past. Uber has changed the game. It’s now a legal requirement that black cabs in London accept payment by card — something which even a few years ago was still hugely controversial. And that’s actually really sad, because once upon a time, London taxis had an international reputation for innovation and excellence. The London ‘black cab’ is a global icon, but it’s also an incredibly well-designed vehicle — high-visibility handholds, wheelchair accessible, capable of carrying five passengers and negotiating the narrow tangled streets of one of the world’s oldest cities. London taxis have been regulated since the 17th century. “The Knowledge” — the exam all London black cab drivers are required to pass, generally regarded as one of the hardest examinations of any profession anywhere in the world — was introduced in the 1850s, after visitors travelling to London for the Great Exhibition complained that their hackney-carriage drivers didn’t know where they were going. One of the great joys of visiting London in the days before sat-nav was hailing a cab, giving the driver the name of some obscure pub halfway across the city, and watching as they’d think for one second, nod, and then whisk you there without further hesitation. But in the age of ubiquitous satellite navigation, who cares whether your driver can remember the way from Narrow Street to Penton Place after you’ve had to stand in the rain for fifteen minutes trying to hail a cab? What Uber did — and did incredibly well — is they thought about all the elements of the passenger experience that happen outside the car. Finding a cab. Knowing how much it’s likely to cost. Paying the bill. Recovering lost property. And they made it &lt;em&gt;cheap.&lt;/em&gt; They created a user experience that, for the majority of passengers, was easier, cheaper and more convenient than traditional black cabs. It’s no wonder the establishment freaked out.&lt;p&gt;So what happens now? Maybe Uber win their appeal. Maybe someone else moves into that space. Maybe things get a bit more expensive for passengers — and, frankly, I think they should. I think Uber is too cheap. If you want the luxury of somebody else driving you to your door in a private car, — and for most of us, that IS a luxury — then I believe the person providing that service deserves to make a decent living out of providing it, and one of the most welcome features Uber’s introduced recently is the ability to tip drivers via the app.&lt;p&gt;Uber has taken a stagnant industry that was in dire need of a kick up the arse, and it’s done it. They’re not the only cab hailing app in the market. &lt;a href=&quot;https://gett.com/uk/&quot;&gt;Gett&lt;/a&gt;, &lt;a href=&quot;https://uk.mytaxi.com/welcome&quot;&gt;mytaxi (formerly Hailo)&lt;/a&gt;, &lt;a href=&quot;https://www.kabbee.com/&quot;&gt;Kabbee&lt;/a&gt;, private hire firms like &lt;a href=&quot;https://www.addisonlee.com/&quot;&gt;Addison Lee&lt;/a&gt; — not to mention just phoning a minicab office and asking them to send a car round. In a typical month in London, I’ll use buses, the Tube, national rail services, the Overground, Uber AND black cabs — not to mention a fair bit of walking and cycling. I even have an actual car, which I use about once a month to go to B&amp;amp;Q or IKEA or somewhere. None of them’s perfect, but on the whole, transport in London works pretty well, and I’ve found Uber a really welcome addition to the range of transport services that’s on offer.&lt;p&gt;But Uber vs TfL is just a tiny taste of what’s coming. I honestly believe that within the next ten years, we’ll be using our smartphones to summon driverless autonomous electric vehicles to take us home after a night out. In all sorts of shapes and sizes, too — after all, why &lt;em&gt;should &lt;/em&gt;it take an entire Toyota Prius to transport a 5’2”, 65kg human with a small shoulder bag from Wardour Street to Battersea? We’ll be going to IKEA in a tiny little electric bubble-bike, buying a new kitchen, and taking it home in a driverless van that waits outside whilst we unload it and then burbles off happily into the sunset to pick up the next waiting fare. Never mind the cab wars between the black cab drivers and the minicab drivers — what happens when a robot cab will pick you up anywhere in town and take you home for a quid? A robot cab that doesn’t have a mortgage to pay or kids to feed? That runs on cheap, green energy, that doesn’t get bored or tired or distracted?&lt;p&gt;There are going to be problems. There are going to be collisions, fatalities, lawsuits, prosecutions, appeals and counter-appeals. And the disruptions will keep coming, faster and faster. Just as Transport for London think they’ve got workable legislation for driverless cars, someone’s going to invent a drone that can fly from Covent Garden to Dulwich carrying a passenger, and whilst they’re busy arguing in court over whether it’s a helicopter or not someone’s gonna shoot one down with a flare pistol and all merry hell’s going to break loose.&lt;p&gt;What Uber shows us is that technology isn’t going to self-regulate. The digital economy moves too fast for pre-emptive legislation and licensing frameworks. There’s fortunes to made in the months or years between your product hitting the market and the authorities deciding to shut you down. The future of Uber doesn’t depend on getting their TfL license renewed. They’ve been &lt;a href=&quot;http://www.telegraph.co.uk/technology/2017/09/22/has-uber-run-trouble-around-world/&quot;&gt;kicked out of entire countries before&lt;/a&gt; and it doesn’t seem to have slowed them down. To them this is just a bump in the road. Their future is about being the first company to roll out self-driving cabs, and you can guarantee they’re working, right now, on finding the legislative loopholes in their various target markets that will allow them to launch first and ask questions later.&lt;p&gt;Black cabs aren’t going away. Buses aren’t going away. Much as I’d love it, they’re not going to decommission all the rolling stock and turn the London Underground network into a giant dodgems track. Technology is going to disrupt, government is going to react — and whilst that model doesn’t always work terribly well, I can’t see any feasible alternative. Engineers are going to solve hard problems. Touch screens, cellular networks, GPS, space travel, battery capacity, biological interfaces, machine learning. Companies like Apple and Google and SpaceX and Tesla are going to put those solutions in our pockets, and in our buildings and on our streets, and companies like Uber and Tinder and AirBnB are going to find ways to turn those solutions into products that you just can’t WAIT to show your friends in the pub.&lt;p&gt;And, short of the nightmare scenarios my friend &lt;a href=&quot;https://hpluspedia.org/wiki/Existential_risks&quot;&gt;Chris has so lovingly documented over on H+Pedia&lt;/a&gt;, society will continue. Uber will run surveillance software on your phone that’s a fraction of what the Home Office are doing all day every day, but Greyball isn’t going to cause the downfall of society. Self-driving cars will kill people. Yes, they will. But 3,000 people already die in road traffic accidents every day, and we think that’s normal, and as long as it doesn’t impact our lives directly we just sort of shrug and ignore it and get on with our lives.&lt;p&gt;I like Uber, and I feel guilty for liking them because I know they do some truly horrible things. I also travel by air, and I eat meat, and I wear leather and I use an iPhone and don’t always recycle. And I used to download MP3s all the time, and feel guilty about it, and then Spotify came along and now I don’t download MP3s any more. And I used to download movies and TV shows, and now I have Amazon Prime and Netflix and I haven’t downloaded a movie in YEARS.&lt;p&gt;Very few people are extremists. For every militant vegan, there’s someone out hunting their own meat, and a couple of hundred of us who just go with whatever’s convenient. If you’re trying to change the world, ignore the extremists. Don’t outlaw things you think people shouldn’t be doing. Change the world by using technology to deliver compelling alternatives. I want vat-grown burgers and steaks that taste as good as the real thing, and yes, I’ll pay. I want boots and jackets made from synthetic textiles that actually look properly road-worn after a couple of years instead of just falling apart. I want to travel by hypersonic maglev underground train instead of flying. OK, maybe not that last part… I’m writing this from 30,000 feet above the Lithuanian border, the setting sun behind us is catching the wingtips as we soar above an endless sea of cloud, and I’m being reminded how much I love flying. But you could definitely make air travel a LOT more expensive before I stopped doing it completely.&lt;p&gt;If TfL really want to get rid of Uber, revoking their license isn’t the way to do it. They’ll appeal, you’ll fight, a lot of lawyers will get rich and TfL will either lose and look like an idiot, or win and look like an asshole. How about TfL find the company that’s trying to beat Uber &lt;em&gt;anyway&lt;/em&gt; — they’re out there, somewhere — and offer to work with them instead? As Rowland Manthorpe &lt;a href=&quot;http://www.wired.co.uk/article/transport-for-london-uber-ban-create-a-new-app?utm_source=dlvr.it&amp;amp;utm_medium=twitter&quot;&gt;wrote in WIRED yesterday&lt;/a&gt;, why don’t we create a socially responsible, employee-owned, ride sharing platform that gives passengers everything Uber does without the institutionalised nastiness and the guilty aftertaste? What ethical transport startup would not jump at the fact to sign up the greatest city in the world as their development partner?&lt;p&gt;You think the future is about using an iPhone to summon a guy in a Toyota Prius, and the important question is who wrote the app they’re using? Come on, people. This is London calling. We can think a hell of a lot bigger than that.&lt;/p&gt;</description>
          <pubDate>2017-09-23T10:18:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2017/09/23/london-london-uber-alles.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2017/09/23/london-london-uber-alles.html</guid>
        </item>
      
    
      
        <item>
          <title>Generating self-signed HTTPS certificates with subjectAltNames</title>
          <description>&lt;p&gt;We provide online services via a bunch of different websites, using federated authentication so that if you sign in to our authentication server, you get a *.mydomain.com cookie that’s sent to any other server on our domain.&lt;/p&gt;&lt;p&gt;We use local wildcard DNS, so there’s a &lt;strong&gt;*.mydomain.com.local&lt;/strong&gt; record that resolves everything to 127.0.0.1, and for each developer machine we create a&amp;nbsp; &lt;strong&gt;*.mydomain.com.hostname&lt;/strong&gt; record that’s an alias for &lt;strong&gt;hostname&lt;/strong&gt;, so you can browse to &lt;strong&gt;www.mydomain.com.&amp;lt;machine&amp;gt; &lt;/strong&gt;to see code running on another developer’s workstation, or &lt;strong&gt;www.mydomain.com.local&lt;/strong&gt;&lt;strong&gt; &lt;/strong&gt;to view your own local development code.&lt;/p&gt;&lt;p&gt;This works pretty well, but getting a local development system set up involves running local versions of several different apps – and since Google Chrome &lt;a href=&quot;https://support.google.com/chrome/a/answer/7391219?hl=en&quot;&gt;now throws a security error&lt;/a&gt; for any HTTPS site whose certificate doesn’t include a “subject alternative name” field, getting a bunch of local sites all happily sharing the same cookies over HTTPS proved a bit fiddly.&lt;/p&gt;&lt;p&gt;So… here’s a batch file that will spit out a bunch of very useful certificates, adapted from &lt;a href=&quot;https://serverfault.com/questions/845766/generating-a-self-signed-cert-with-openssl-that-works-in-chrome-58&quot;&gt;this post on serverfault.com&lt;/a&gt;. &lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/dylanbeattie/63e2230db6c1033e5efaf9d442246aea.js&quot;&gt;&lt;/script&gt;
&lt;h4&gt;How it works&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;Get openssl.exe working - I use the version that&apos;s shipped with Cygwin, installed into C:\Windows\Cygwin64\bin\ and added to my system path.&lt;/li&gt;
&lt;li&gt;Run makecert.bat. If you don&apos;t want to specify a password, just provide a blank one (press Enter). This will spit out three files:&lt;ul&gt;
&lt;li&gt;local_and_hostname.crt&lt;/li&gt;
&lt;li&gt;local_and_hostname.key&lt;/li&gt;
&lt;li&gt;local_and_hostname.pfx&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Double-click the local_and_hostname.crt file, click &quot;Install Certificate&quot;, and use the Certificate Import Wizard to import it. Choose &quot;Local Machine&quot; as the Store Location, and &quot;Trusted Root Certification Authorities&quot; as the Certificate Store.&lt;/li&gt;
&lt;li&gt;Open IIS, select your machine, open &quot;Server Certificates&quot; from the IIS snapin, click &quot;Import...&quot; in the Actions panel&lt;/li&gt;
&lt;li&gt;Select the local_and_hostname.pfx certificate created by the batch file. If you used a password when exporting your PKCS12 (.pfx) file, you&apos;ll need to provide it here&lt;/li&gt;
&lt;li&gt;Finally, set up your IIS HTTPS bindings to use your new certificate.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Yay! Security!&amp;nbsp; &lt;/p&gt;</description>
          <pubDate>2017-08-07T15:55:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2017/08/07/generating-self-signed-https.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2017/08/07/generating-self-signed-https.html</guid>
        </item>
      
    
      
        <item>
          <title>Deployment Through the Ages</title>
          <description>&lt;p&gt;Vanessa Love just posted this intriguing little snippet on Twitter:&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;In what could be the worst idea ever, i&apos;ve opened up a google doc - add steps from your manual deployment process - &lt;a href=&quot;https://t.co/Ag56BudiM5&quot;&gt;https://t.co/Ag56BudiM5&lt;/a&gt;&lt;/p&gt;— Vanessa Love (@fly401) &lt;a href=&quot;https://twitter.com/fly401/status/891899568765272064&quot;&gt;July 31, 2017&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;p&gt;And I got halfway through sticking some notes into the Google doc, and then thought actually this might make a fun blog post. So here’s how deployment has evolved over the 14 years since I first took over the hallowed mantle of &lt;strong&gt;webmaster@spotlight.com&lt;/strong&gt;.&lt;/p&gt;&lt;h3&gt;2003: Beyond Compare (maybe?)&lt;/h3&gt;&lt;p&gt;&lt;img width=&quot;139&quot; height=&quot;139&quot; align=&quot;right&quot; style=&quot;border: 0px currentcolor; border-image: none; float: right; display: inline; background-image: none;&quot; src=&quot;https://www.scootersoftware.com/images/media_BeyondCompareIcon512x512.png&quot; border=&quot;0&quot;&gt;The whole site was classic ASP – no compilation, no build process, all connection credentials and other settings were managed as application variables in the global.asa file. On a good day, I’d get code running on my workstation, test it in our main target browsers, and deploy it using a visual folder comparison tool. It might have been &lt;a href=&quot;https://www.scootersoftware.com/&quot;&gt;Beyond Compare&lt;/a&gt;; it might have been something else. I honestly can’t remember and the whole thing is lost in the mists of time. But that was basically the process – you’d have the production codebase on one half of your screen and your localhost codebase on the other half, and you’d cherry-pick the bits that needed to be copied across.&lt;/p&gt;&lt;p&gt;Of course, when something went wrong in production, I’d end up reversing the process – edit code directly on live (via UNC share), normally with the phone wedged against my shoulder and a user on the other end; fix the bug, verify the user was happy, and then do a file sync in the other direction to get everything from production back onto localhost. Talk about a tight feedback loop – sometimes I’d do half-a-dozen “deployments” in one phone call. It was a simpler time, dear reader. Rollback plan was to hammer Ctrl-Z until it’s working again; disaster recovery was tape backups of the complete source tree and database every night, and the occasional copy’n’paste backup of wwwroot before doing something ambitious.&lt;/p&gt;&lt;p&gt;Incidentally, I still use Beyond Compare almost daily – I have it configured as my merge tool for fixing Git merge conflicts. It’s fantastic.&lt;/p&gt;&lt;h3&gt;2005: Subversion&lt;/h3&gt;&lt;p&gt;Once we hired a second developer (hey Dan!) the Beyond Compare approach didn’t really work so well any more, so we set up a &lt;a href=&quot;https://subversion.apache.org/&quot;&gt;Subversion&lt;/a&gt; server. You’d get stuff running on localhost, test it, maybe share an &lt;strong&gt;http://www.spotlight.com.dylan-pc/&lt;/strong&gt; link (hooray for local wildcard DNS) so other people could see it, and when they were happy, you’d do an svn commit, log into the production web server (yep, &lt;strong&gt;the&lt;/strong&gt; production web server – just the one!) and do an svn update. That would pull down the latest code, update everything in-place. There was still the occasional urgent production bugfix. One of my worst habits was that I’d fix something on production and then forget to svn commit the changes, so the next time someone did a deployment (hey Dan!) they’d inadvertently reintroduce whatever bug had just been fixed and we’d get upset people phoning up asking why it was broken AGAIN.&lt;/p&gt;&lt;h3&gt;2006: FinalBuilder&lt;/h3&gt;&lt;p&gt;This is where we start doing things with ASP.NET in a big way. I still dream about &lt;a href=&quot;https://stackoverflow.com/search?q=OnItemDataBound&quot;&gt;OnItemDataBound&lt;/a&gt; sometimes… and wake up screaming, covered in sweat. Fun times. The code has all long since been deleted but I fear the memories will haunt me to my grave. &lt;/p&gt;&lt;p&gt;Anyway. By this point we already had the Subversion server, so we had a look around for something that would check out and compile .NET code, and went with &lt;a href=&quot;https://www.finalbuilder.com/&quot;&gt;FinalBuilder&lt;/a&gt;. It had a GUI for authoring build pipelines and processes, some very neat features, and could deploy .NET applications to IIS servers. This was pretty sophisticated for 2006.&amp;nbsp; &lt;/p&gt;&lt;h3&gt;2008: test server and msdeploy&lt;/h3&gt;&lt;p&gt;After one too many botched FinalBuilder deployments, we decided that a dedicated test environment and a better deployment process might be a good idea. Microsoft had just released a &lt;a href=&quot;https://www.hanselman.com/blog/MSDeployNewIISWebDeploymentTool.aspx&quot;&gt;preview of a new deployment tool called MSDeploy&lt;/a&gt;, and it was &lt;em&gt;awesome.&lt;/em&gt; We set up a ‘staging environment’ – it was a spare Dell PowerEdge server that lived under my desk, and I’d know when somebody accidentally wrote an infinite loop because I’d hear the fans spin up. We’d commit changes to Subversion, FinalBuilder would build and deploy them onto the test server, we’d give everything a bit of a kicking in IE8 and Firefox (no Google Chrome until September 2008, remember!) and then – and this was &lt;em&gt;magic&lt;/em&gt; back in 2008 – you’d use msdeploy.exe to &lt;em&gt;replicate the entire test server into production!&lt;strong&gt; &lt;/strong&gt;&lt;/em&gt;Compared to the tedious and error-prone checking of IIS settings, application pools and so on, this was brilliant. Plus we’d use msdeploy to replicate the live server onto new developers’ workstations, which was a really fast, easy way to get them a local snapshot of a working live system. For the bits that still ran interpreted code, anyway.&lt;/p&gt;&lt;h3&gt;2011: TeamCity All The Things!&lt;/h3&gt;&lt;p&gt;&lt;img width=&quot;132&quot; height=&quot;132&quot; align=&quot;right&quot; style=&quot;border: 0px currentcolor; border-image: none; float: right; display: inline; background-image: none;&quot; src=&quot;https://blog.jetbrains.com/teamcity/files/2008/09/teamcity512.png&quot; border=&quot;0&quot;&gt;By now we had separate dev, staging and production environments, and msdeploy just wasn’t cutting it any more. We needed something that can actually build different deployments for each environments – connection strings, credentials, and so on. And there’s now support in Visual Studio for doing XML configuration transforms, so you create a different config file for every environment, check those into revision control, and get different builds for each environment. I can’t remember exactly why we abandoned FinalBuilder for &lt;a href=&quot;https://www.jetbrains.com/teamcity/&quot;&gt;TeamCity&lt;/a&gt;, but it was definitely a good idea – TeamCity has been the backbone of our build process ever since, and it’s a fantastically powerful piece of kit.&lt;/p&gt;&lt;h3&gt;2012: Subversion to GitHub&lt;/h3&gt;&lt;p&gt;At this point, we’d grown from me, on my own doing webmaster stuff, to a team of about six developers. Even Subversion is starting to creak a bit, especially when you’re trying to merge long-lived branches and getting dozens of merge conflicts, so we start moving stuff across to GitHub. It takes a while – I’m talking months – for the whole team to stop thinking of Git as ‘unnecessarily complicated Subversion’ and really grok the workflow, but we got there in the end.&lt;/p&gt;&lt;p&gt;Our deployment process at this point was to commit to the Git master branch, and wait for TeamCity to build the development version of the package. This would get built and deployed. Once it was tested, you’d use TeamCity to build and deploy the staging version – and if that went OK, you’d build and deploy production. Like very step on this journey, it was better than anything we’d had before, but had some obvious drawbacks. Like the fact we had several hundred separate TeamCity jobs and no consistent way of managing them all.&lt;/p&gt;&lt;h3&gt;2013: Octopus Deploy and Klondike&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://octopus.com/&quot;&gt;&lt;img width=&quot;520&quot; height=&quot;101&quot; style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://i.octopus.com/blog/201605-logo-text-blueblacktransparent-800_rgb-XGYC.png&quot; border=&quot;0&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;When we started migrating from TeamCity 6 to TeamCity 7, it became rapidly apparent that our “build everything several times” process… well, it sucked. It was high-maintenance, used loads of storage space and unnecessary CPU cycles, and we needed a better system.&lt;/p&gt;&lt;p&gt;Enter &lt;a href=&quot;https://octopus.com/&quot;&gt;Octopus Deploy&lt;/a&gt;, whose killer feature for us was the ability to compile a .NET web application or project into a deployment NuGet package (an “octopack”), and then &lt;strong&gt;apply configuration settings during deployment&lt;/strong&gt;. We could build a single package, and then use Octopus to deploy and configure it to dev, staging and live. This was an absolute game-changer for us. We set up TeamCity to do continuous integration, so that every commit to a master branch would trigger a package build… and before long, our biggest problem was that we had so many packages in TeamCity that the built-in NuGet server started creaking.&lt;/p&gt;&lt;p&gt;This started life as an experimental build of &lt;a title=&quot;https://github.com/themotleyfool/NuGet.Lucene&quot; href=&quot;https://github.com/themotleyfool/NuGet.Lucene&quot;&gt;themotleyfool/NuGet.Lucene&lt;/a&gt; – which we actually deployed onto a server we called “Klondike” (because klondike &amp;gt; gold rush &amp;gt; get nuggets fast!) – and it worked rather nicely. Incidentally, that NuGet.Lucene library is now the engine behind &lt;a href=&quot;https://github.com/themotleyfool/Klondike&quot;&gt;themotleyfool/Klondike&lt;/a&gt;, a full-spec NuGet hosting application – and I believe &lt;a href=&quot;https://disqus.com/home/discussion/chriseldredge/speeding_up_nugetserver_chris_eldredge/#comment-930656408&quot;&gt;our internal hostname was actually the inspiration for their project name&lt;/a&gt;. That was a lot of fun for the 18 months or so that &lt;a href=&quot;https://github.com/themotleyfool/Klondike&quot;&gt;Klondike&lt;/a&gt; existed but we were still running the old NuGet.Lucene codebase on a server called ‘klondike’. It’s OK, we’ve now upgraded it and everything’s lovely.&lt;/p&gt;&lt;p&gt;It was also in 2013 that we started exploring the idea of automatic semantic versioning – I &lt;a href=&quot;http://www.dylanbeattie.net/2013/10/automatic-semantic-versioning-with.html&quot;&gt;wrote a post in October 2013&lt;/a&gt; explaining how we hacked together an early version of this. Here’s &lt;a href=&quot;http://www.dylanbeattie.net/2017/01/semantic-versioning-with-powershell_26.html&quot;&gt;another post from January 2017&lt;/a&gt; explaining how it’s evolved. We’re still working on it. Versioning is &lt;em&gt;hard&lt;/em&gt;. &lt;/p&gt;&lt;h3&gt;And now?&lt;/h3&gt;&lt;p&gt;So right now, our build process works something like this.&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Grab the ticket you’re working on – we use Pivotal Tracker to manage our backlogs&lt;/li&gt;&lt;li&gt;Create a new GitHub branch, with a name like 12345678_fix_the_microfleems – where 12345678 is the ticket ID number&lt;/li&gt;&lt;li&gt;Fix the microfleems.&lt;/li&gt;&lt;li&gt;Push your changes to your branch, and open a pull request. TeamCity will have picked up the pull request, checked out the merge head and built a deployable pre-release package (on some projects, versioning for this is completely automated) &lt;/li&gt;&lt;li&gt;Use Octopus Deploy to deploy the prerelease package onto the dev environment. This is where you get to tweak and troubleshoot your deployment steps.&lt;/li&gt;&lt;li&gt;Once you’re happy, mark the ticket as ‘finished’. This means it’s ready for code review. One of the other developers will pick it up, read the code, make sure it runs locally and deploys to the dev environment, and then mark it as ‘delivered’.&lt;/li&gt;&lt;li&gt;Once it’s delivered, one of our testers will pick it up, test it on the dev environment, run it past any business stakeholders or users, and make sure we’ve done the right thing and done it right.&lt;/li&gt;&lt;li&gt;Finally, the ticket is accepted. The pull request is merged, the branch is deleted. TeamCity builds a release package. We use Octopus to deploy that to staging, check everything looks good, and then promote it to production.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;And what’s on our wishlist?&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Better production-grade smoke testing. Zero-footprint tests we can run that will validate common user journeys and scenarios as part of every deployment – and which potentially also run as part of routine monitoring, and can even be used as the basis for load testing.&lt;/li&gt;&lt;li&gt;Automated release notes. Close the loop, link the Octopus deployments back to the Pivotal tickets, so that when we do a production deployment, we can create release notes based on the ticket titles, we can email the person who requested the ticket saying that it’s now gone live, that kind of thing.&lt;/li&gt;&lt;li&gt;Deployments on the dashboards. We want to see every deployment as an event on the same dashboards that monitor network, CPU, memory, user sessions – so if you deploy a change that radically affects system resources, it’s immediately obvious there might be a correlation.&lt;/li&gt;&lt;li&gt;Full-on continuous deployment. Merge the PR and let the machines do the rest. &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;So there you go – fourteen years worth of continuous deployments. Of course, alongside all this, we’ve moved from unpacking Dell PowerEdge servers and installing Windows 2003 on them to running Chef scripts that spin up virtual machines in AWS and shut them down again when nobody’s using them – but hey, that’s another story.&lt;/p&gt;</description>
          <pubDate>2017-07-31T16:43:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2017/07/31/deployment-through-ages.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2017/07/31/deployment-through-ages.html</guid>
        </item>
      
    
      
        <item>
          <title>Securing Blogger with CloudFlare and HTTPS</title>
          <description>&lt;p&gt;As you may have read, &lt;a href=&quot;https://www.troyhunt.com/life-is-about-to-get-harder-for-websites-without-https/&quot;&gt;life is about to get a whole lot harder for websites without HTTPS&lt;/a&gt;. Now this site is hosted on Blogger – I used to run my own &lt;a href=&quot;https://www.movabletype.org/&quot;&gt;MovableType&lt;/a&gt; server, but I realised I was spending way more time messing around with the software than I was actually writing blog posts, so I shifted the whole thing across to Blogger about a decade ago and never really looked back.&lt;/p&gt;&lt;p&gt;One of the limitations of Blogger is that it doesn’t support HTTPS if you’re using custom domains – there’s no way to install your own certificate or anything. So, since Chrome’s about to crank up the warnings for any websites that don’t use HTTPS, I figured I ought to set something up. Enter CloudFlare, who are really rather splendid. &lt;/p&gt;&lt;p&gt;First, you sign up. (bonus points for them NOT forcing you to choose a password that contains a lowercase letter, an uppercase letter, a number, a special character, the poo emoji and the Mongolian vowel separator). &lt;/p&gt;&lt;p&gt;Second, you tell them which domain you want to protect:&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-MV4JbPREXA8/WXppWR1dg5I/AAAAAAAAEgg/iQI5I0kBL9oSiHjdO741ZFEb-Y85EEfegCHMYCw/s1600-h/image%255B4%255D&quot;&gt;&lt;img width=&quot;1024&quot; height=&quot;484&quot; title=&quot;image&quot; style=&quot;border: 0px currentcolor; border-image: none; display: inline; background-image: none;&quot; alt=&quot;image&quot; src=&quot;https://lh3.googleusercontent.com/-kNbpjf8jrLc/WXppXsTpwJI/AAAAAAAAEgk/z7K-YZV0y1UDXIhycOB1W6NuUDQz5ehWQCHMYCw/image_thumb%255B2%255D?imgmax=800&quot; border=&quot;0&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;They scan all your DNS records, which takes about a minute – and not only is there a nice real-time progress bar keeping you in the loop, they use this opportunity to play a really short video explaining what&apos;s going on. I think this is absolute genius.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-w2f8kueDXSs/WXppX8ri8aI/AAAAAAAAEgo/QXEMjJojtBYkFKmKzAYeO2BKD1BN5cT0ACHMYCw/s1600-h/image%255B9%255D&quot;&gt;&lt;img width=&quot;1024&quot; height=&quot;636&quot; title=&quot;image&quot; style=&quot;border: 0px currentcolor; border-image: none; display: inline; background-image: none;&quot; alt=&quot;image&quot; src=&quot;https://lh3.googleusercontent.com/-n4synUqxa04/WXppYRjNI6I/AAAAAAAAEgs/ERVHKFGsREsTjv3C5Q3SnXDZOR34-4WcwCHMYCw/image_thumb%255B5%255D?imgmax=800&quot; border=&quot;0&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Finally, after checking it&apos;s picked up all your DNS records properly (it had), you tell your domain registrar to update the nameservers for your domain to CloudFlare&apos;s DNS servers, give it up to 24 hours, and you&apos;re done. Zero downtime, zero service interruption – the whole thing was smooth, simple, and completely free-as-in-beer. &lt;/p&gt;&lt;p&gt;&lt;strong&gt;Yes, I realise this does not encrypt content end-to-end. For what we&apos;re doing here, this is absolutely fine. It&apos;ll secure your traffic against dodgy hotel wi-fi and unscrupulous internet service providers - and if anyone&apos;s genuinely intercepting HTTP traffic between CloudFlare and Google, I&apos;m sure they can think of more exciting things to do with it than mess around with my blog posts.&lt;/strong&gt; &lt;/p&gt;&lt;p&gt;Having done that, I then had to use the Google Chrome console to track down the resources – photos and the odd bit of script – that were being hosted via HTTP, and update them to be HTTPS. The only thing I couldn&apos;t work out how to fix was the search bar that&apos;s embedded in Blogger&apos;s default page layout – it&apos;s injected by JavaScript, it&apos;s hosted by Google&apos;s CDN (so I can&apos;t use any of CloudFlare&apos;s clever rewriting tricks to fix it), it&apos;s stuck inside an IFRAME, and it points to &lt;a href=&quot;http://www.dylanbeattie.net/search&quot;&gt;http://www.dylanbeattie.net/search&lt;/a&gt; – see the plain HTTP with no S?&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-ThkJ38fthrw/WXppYwtxBpI/AAAAAAAAEgw/_FLv4Z_clIUxjJHRqbiEJBVEMsI7l_p0wCHMYCw/s1600-h/image%255B14%255D&quot;&gt;&lt;img width=&quot;1024&quot; height=&quot;436&quot; title=&quot;image&quot; style=&quot;border: 0px currentcolor; border-image: none; display: inline; background-image: none;&quot; alt=&quot;image&quot; src=&quot;https://lh3.googleusercontent.com/--xvNlX5HcGA/WXppZpP0l6I/AAAAAAAAEg0/_2rm2rQyLXwM4ub3Z3ApjdKe17p1LovLwCHMYCw/image_thumb%255B8%255D?imgmax=800&quot; border=&quot;0&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;After an hour or so of messing around with CSS, I gave up, &lt;a href=&quot;https://webmasters.stackexchange.com/questions/108172/how-can-i-force-bloggers-search-widget-to-use-https/&quot;&gt;posted a question&lt;/a&gt; on the ProWebmasters Stack Exchange, and – of course, immediately found the solution; go into Blogger, Layout, find the Navbar gadget, click Edit, and there&apos;s an option to switch the nav off entirely.&lt;/p&gt;&lt;p&gt;So there you go. Thanks to CloudFlare, &lt;a href=&quot;https://www.dylanbeattie.net/&quot;&gt;https://www.dylanbeattie.net/&lt;/a&gt; now has a green padlock on it. &lt;a href=&quot;http://www.imdb.com/character/ch0003525/quotes&quot;&gt;I don&apos;t know about you, but I take comfort in that&lt;/a&gt;. &lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/--SaMNEOOq4A/WXppaGHpSJI/AAAAAAAAEg4/8b8PY3Juztc_xQaz0FyX0WvQSo_SbWcRgCHMYCw/s1600-h/image%255B22%255D&quot;&gt;&lt;img width=&quot;867&quot; height=&quot;53&quot; title=&quot;image&quot; style=&quot;border: 0px currentcolor; border-image: none; display: inline; background-image: none;&quot; alt=&quot;image&quot; src=&quot;https://lh3.googleusercontent.com/-Sm-7gXdGENg/WXppasqzfoI/AAAAAAAAEg8/BZUfYyET5yEPOjJgO_XdM9Ec6Ghl60G-gCHMYCw/image_thumb%255B14%255D?imgmax=800&quot; border=&quot;0&quot;&gt;&lt;/a&gt;&lt;/p&gt;</description>
          <pubDate>2017-07-27T13:57:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2017/07/27/securing-blogger-with-cloudflare-and.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2017/07/27/securing-blogger-with-cloudflare-and.html</guid>
        </item>
      
    
      
        <item>
          <title>Summer 2017 .NET Community Update</title>
          <description>&lt;p&gt;Summer here in the UK is normally pretty quiet, but this year there&apos;s so much going on around .NET and the .NET community that I thought this would be a great opportunity to do a bit of a round-up and let you all know about some of the great stuff that&apos;s going on.&lt;/p&gt;&lt;p&gt;First, there&apos;s the news of two new .NET user groups starting up in southern England. Earlier this week, I was down in Bournemouth speaking at the first-ever meetup of the new .NET Bournemouth group, and thoroughly enjoyed it. Three speakers – &lt;a href=&quot;https://twitter.com/im5tu&quot;&gt;Stuart Blackler&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/smudge202&quot;&gt;Tommy Long&lt;/a&gt; and me – with talks on leadership, agile approaches to information security, and an updated version of my &quot;happy code&quot; talk I&apos;ve done at a few conferences already this year. The venue and A/V setup worked flawlessly, there was a strong turnout, and some really good questions and discussion after each of the talks – I think it&apos;s going to turn out to be a really engaging group, so if you&apos;re in that part of the world, stop by and check them out. Their next few meetup dates are on &lt;a href=&quot;https://www.meetup.com/Net-Bournemouth/&quot;&gt;meetup.com/Net-Bournemouth&lt;/a&gt; already.&lt;/p&gt;&lt;p&gt;Next month, Steve Gordon is starting a new .NET South East group based in Brighton, who will be kicking off with their inaugural &lt;a href=&quot;https://www.meetup.com/dotnetsoutheast/events/241595853/&quot;&gt;meetup on August 22nd&lt;/a&gt; with Steve talking about Docker for .NET developers.&lt;/p&gt;&lt;p&gt;&lt;img width=&quot;240&quot; height=&quot;136&quot; style=&quot;border: 0px currentcolor; border-image: none; margin-right: auto; margin-left: auto; float: none; display: block; background-image: none;&quot; alt=&quot;Brighton based .NET South East user group logo&quot; src=&quot;https://www.stevejgordon.co.uk/wp-content/uploads/2017/07/cropped-logo.png&quot; border=&quot;0&quot;&gt;&lt;/p&gt;&lt;p&gt;There&apos;s a &lt;a href=&quot;https://www.stevejgordon.co.uk/announcing-dotnet-south-east-user-group&quot;&gt;great post on Steve&apos;s blog&lt;/a&gt; explaining what he&apos;s doing and what he&apos;s hoping to get out of the group, and they&apos;re also on &lt;a href=&quot;https://www.meetup.com/dotnetsoutheast/&quot;&gt;meetup.com/dotnetsoutheast&lt;/a&gt; (and I have to say, they&apos;ve done an excellent job of branding the Meetup site – nice work!)&lt;/p&gt;&lt;p&gt;It&apos;s an exciting time for .NET – between the cross-platform stuff that&apos;s happening around &lt;a href=&quot;https://www.xamarin.com/&quot;&gt;Xamarin&lt;/a&gt; and .NET Core, new tooling like &lt;a href=&quot;https://www.jetbrains.com/rider/&quot;&gt;JetBrains Rider&lt;/a&gt; and &lt;a href=&quot;https://code.visualstudio.com/&quot;&gt;Visual Studio Code&lt;/a&gt;, and the growing number of cloud providers who are supporting C# and .NET Core for building serverless cloud applications, we&apos;ve come a long, long way from the days of building Windows Forms and databinding in Visual Studio .NET.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://skillsmatter.com/conferences/8268-progressive-dot-net-2017&quot;&gt;&lt;img style=&quot;border: 0px currentcolor; border-image: none; background-image: none;&quot; src=&quot;https://pbs.twimg.com/media/DE2w2mJWAAABk3L.jpg:large&quot; border=&quot;0&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;If you&apos;re interested in really getting to grips with the future of .NET, join us at the &lt;a href=&quot;https://skillsmatter.com/conferences/8268-progressive-dot-net-2017&quot;&gt;Progressive.NET Tutorials&lt;/a&gt; here in London in September. With a great line-up of speakers including &lt;a href=&quot;http://thedatafarm.com/blog/&quot;&gt;Julie Lerman&lt;/a&gt;, &lt;a href=&quot;https://codeblog.jonskeet.uk/&quot;&gt;Jon Skeet&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/jongalloway?ref_src=twsrc%5Egoogle%7Ctwcamp%5Eserp%7Ctwgr%5Eauthor&quot;&gt;Jon Galloway&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/clemensv&quot;&gt;Clemens Vasters&lt;/a&gt; and &lt;a href=&quot;http://rachelappel.com/&quot;&gt;Rachel Appel&lt;/a&gt; – plus Carl Franklin and Rich Campbell from &lt;a href=&quot;https://www.dotnetrocks.com/&quot;&gt;DotNetRocks&lt;/a&gt;, and a few familiar faces you might recognise from the London.NET gang – it promises to be a really excellent event. It goes a lot deeper than most conferences – with one day of talks and two days of hands-on workshops, the idea is that attendees don&apos;t just go away with good ideas, they actually leave with running code, on their laptops, that they can refer back to when they take those ideas back to the office or to their own projects. Check out &lt;a href=&quot;https://skillsmatter.com/conferences/8268-progressive-dot-net-2017#program&quot;&gt;the programme&lt;/a&gt;, follow &lt;a href=&quot;https://twitter.com/hashtag/prognet&quot;&gt;#ProgNET on Twitter&lt;/a&gt;, and hopefully see some of you there.&amp;nbsp; &lt;/p&gt;&lt;p&gt;Then on Saturday 16th September – the day after Progressive.NET – is the &lt;a href=&quot;https://www.dddeastanglia.com/&quot;&gt;fourth DDD East Anglia&lt;/a&gt; community conference in Cambridge. Their call for speakers is now closed, but voting is open until July 29th – so sign up, &lt;a href=&quot;https://www.dddeastanglia.com/Session&quot;&gt;vote on the sessions you want to see&lt;/a&gt; – or just &lt;a href=&quot;http://www.dddeastanglia.com/Session/Details/2114&quot;&gt;vote&lt;/a&gt; for &lt;a href=&quot;http://www.dddeastanglia.com/Session/Details/2113&quot;&gt;mine&lt;/a&gt; if you can&apos;t make your mind up ;) - and hopefully I&apos;ll see some of you in Cambridge.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-odyurdiqm3U/WXIJVQJT74I/AAAAAAAAEe4/bzGa_g7dC2g08CLY7wlOy9Bc2f2J0iVVQCHMYCw/s1600-h/t_shirt_logo_thumb%255B23%255D%255B2%255D&quot;&gt;&lt;img width=&quot;100&quot; height=&quot;101&quot; title=&quot;t_shirt_logo_thumb[23]&quot; align=&quot;right&quot; style=&quot;margin: 0px 0px 20px 20px; border: 0px currentcolor; border-image: none; float: right; display: inline; background-image: none;&quot; alt=&quot;t_shirt_logo_thumb[23]&quot; src=&quot;https://lh3.googleusercontent.com/-vh_vaMBVSis/WXIJVzgE49I/AAAAAAAAEe8/G2zUEWMyjj4uVKObyHucMa6D5IrWbM2HwCHMYCw/t_shirt_logo_thumb%255B23%255D_thumb?imgmax=800&quot; border=&quot;0&quot;&gt;&lt;/a&gt;Finally, just in case any readers of this blog DON&apos;T know about the London .NET User Group… yep, we have .NET User Group! In London! I know, right? We&apos;re on &lt;a href=&quot;https://www.meetup.com/London-NET-User-Group&quot;&gt;meetup.com/London-NET-User-Group&lt;/a&gt;, and on Twitter as &lt;a href=&quot;https://twitter.com/londondotnet&quot;&gt;@LondonDotNet&lt;/a&gt;, and we meet every month at SkillsMatter&apos;s &lt;a href=&quot;https://skillsmatter.com/event-space&quot;&gt;CodeNode&lt;/a&gt; building near Moorgate.&lt;/p&gt;&lt;p&gt;Our &lt;a href=&quot;https://www.meetup.com/London-NET-User-Group/events/240966132/&quot;&gt;next meetup is on August 8th&lt;/a&gt;, with Ana Balica talking about the history and future of HTTP and HTTP/2, and Steve Gordon – and &lt;a href=&quot;https://www.meetup.com/London-NET-User-Group/events/240966144/&quot;&gt;on September 12th&lt;/a&gt; we&apos;ve got Rich Campbell joining us for a Progressive.NET special meetup and presenting the History of .NET as you&apos;ve never heard it before.&lt;/p&gt;&lt;p&gt;New people, new meetups, new platforms and new ideas. Like I said, it&apos;s a really exciting time to be part of the .NET community – join us, come to a meetup, follow us online, and let&apos;s make good things happen.&lt;/p&gt;</description>
          <pubDate>2017-07-21T14:01:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2017/07/21/summer-2017-net-community-update.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2017/07/21/summer-2017-net-community-update.html</guid>
        </item>
      
    
      
        <item>
          <title>Use Flatscreens</title>
          <description>&lt;p&gt;This started life as a lightning talk for PubConf after NDC in Sydney, back in August 2016… and after quite a lot of tweaking, editing and learning to do all sorts of fun things with Adobe AfterEffects and Premiere, it&apos;s finally on YouTube. The inspiration is, of course, &quot;&lt;a href=&quot;https://en.wikipedia.org/wiki/Wear_Sunscreen&quot;&gt;Wear Sunscreen&lt;/a&gt;&quot;, Baz Luhrmann&apos;s 1999 hit song based on an essay written by Mary Schmich. Video footage and stock photography is all credited at the end of the clip, and the music, vocals, video, audio and, well, basically everything else is by me. Happy listening - and don&apos;t forget to use flatscreens :)&lt;/p&gt; &lt;p&gt;&lt;iframe height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/q3G4EPGH2r0&quot; frameborder=&quot;0&quot; width=&quot;560&quot; allowfullscreen&gt;&lt;/iframe&gt;&lt;/p&gt; &lt;p&gt;Ladies and gentlemen of the class of 2017… use flat screens. If I could offer you only one tip for the future, flat screens would be it. The benefits of flat screens have been proved by Hollywood, whereas the rest of my advice has no basis more reliable than my own meandering experience.&lt;br&gt;&amp;nbsp;&lt;br&gt;I will dispense this advice... now.&lt;/p&gt; &lt;p&gt;Enjoy the confidence and optimism of greenfield projects. Oh, never mind. You will not appreciate the confidence and optimism of greenfield until everything starts going to hell. But trust me, when you finally ship, you&apos;ll look back at the code you wrote and recall in a way you can&apos;t grasp now how simple everything seemed, and how productive you really were. Your code is not as bad as you imagine.&lt;/p&gt; &lt;p&gt;Don&apos;t worry about changing database providers. Or worry, but know that every company who ever used an OR/M in case they needed to switch databases never actually did it. The real problems in your projects are the dependencies you don&apos;t control; the leaking air conditioner that floods your data centre at 5pm on the Thursday before Christmas.&lt;/p&gt; &lt;p&gt;Learn one thing every day that scares you.&lt;/p&gt; &lt;p&gt;Optimise.&lt;/p&gt; &lt;p&gt;Don&apos;t reformat other people&apos;s codebases; don&apos;t put up with people who reformat yours.&lt;/p&gt; &lt;p&gt;Rebase&lt;/p&gt; &lt;p&gt;Don&apos;t get obsessed with frameworks. Sometimes they help, sometimes they hurt. It&apos;s the user experience that matters, and the user doesn&apos;t care how you created it.&lt;/p&gt; &lt;p&gt;Remember the retweets you receive; forget the flame bait. If you succeed in doing this, tell me how.&lt;/p&gt; &lt;p&gt;Keep your old hard drives. Throw away your old network cards.&lt;/p&gt; &lt;p&gt;Refactor.&lt;/p&gt; &lt;p&gt;Don&apos;t feel guilty if you don&apos;t understand f#. Some of the most productive junior developers I&apos;ve worked with didn&apos;t know F#. Some of the best systems architects I know still don&apos;t.&lt;/p&gt; &lt;p&gt;Write plenty of tests.&lt;/p&gt; &lt;p&gt;Be kind to your keys; you&apos;ll miss them when they&apos;re gone. &lt;/p&gt; &lt;p&gt;Maybe you have a degree; maybe you don&apos;t. Maybe you have an open source project; maybe you won&apos;t. Maybe you wrote code that flew on the Space Shuttle; maybe you worked on Microsoft SharePoint. Whatever you do, keep improving, and don&apos;t worry where your next gig is coming from. There&apos;s a big old world out there, and they&apos;re always going to need good developers.&lt;/p&gt; &lt;p&gt;Look after your brain. Don&apos;t burn out, don&apos;t be afraid to take a break. It is the most powerful computer you will ever own.&lt;/p&gt; &lt;p&gt;Launch, even if you have no users but your own QA team.&lt;/p&gt; &lt;p&gt;Have a plan, even if you choose not to follow it.&lt;/p&gt; &lt;p&gt;Do NOT read the comments on YouTube : they will only make you feel angry.&lt;/p&gt; &lt;p&gt;Cache your package dependencies; you never know when they&apos;ll be gone for good.&lt;/p&gt; &lt;p&gt;Read your log files. They&apos;re your best source of information, and the first place you&apos;ll notice if something&apos;s starting to go wrong. &lt;/p&gt; &lt;p&gt;Understand that languages come and go, and that it&apos;s the underlying patterns that really matter. Work hard to fill the gaps in your knowledge, because the wiser you get,&amp;nbsp; the more you&apos;ll regret the things you didn&apos;t know when you were young.&lt;/p&gt; &lt;p&gt;Develop in 86 assembler once, but stop before it makes you smug; develop in Visual Basic once, but stop before it makes you stupid.&lt;/p&gt; &lt;p&gt;Read.&lt;/p&gt; &lt;p&gt;Accept certain inalienable truths. Your code has bugs, you will miss your deadlines, and you, too, might end up in management. And when you do, you&apos;ll fantasize that back when you were a developer, code was bug-free, deadlines were met, and developers tuned their database indexes.&lt;/p&gt; &lt;p&gt;Tune your database indexes.&lt;/p&gt; &lt;p&gt;Don&apos;t deploy your code without testing it. Maybe you have a QA team. Maybe you have integration tests. You never know when either one might miss something.&lt;/p&gt; &lt;p&gt;Don&apos;t mess too much with your user interface, or by the time you ship, it will look like a Japanese karaoke booth.&lt;/p&gt; &lt;p&gt;Be grateful for open source code, but be careful whose code you run. Writing good code is hard, and open source is a way of taking bits from your projects folder, slapping a readme on them, and hoping if you put them on GitHub somebody else will come along and fix your problems.&lt;/p&gt; &lt;p&gt;But trust me on the flat screens.&lt;/p&gt;</description>
          <pubDate>2017-07-04T00:09:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2017/07/04/use-flatscreens.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2017/07/04/use-flatscreens.html</guid>
        </item>
      
    
      
        <item>
          <title>Interview with Channel 9 at NDC Oslo</title>
          <description>&lt;p&gt;I was in Oslo earlier this month, where – as well as doing the opening keynote, a couple of talks, a workshop on hypermedia systems and PubConf – I had the chance to chat with &lt;a href=&quot;https://twitter.com/sethjuarez&quot;&gt;Seth Juarez&lt;/a&gt; from Channel 9 about code, culture, speaking at conferences, and… all kinds of things, really.&lt;/p&gt;
&lt;p&gt;The interview&apos;s here, or you can watch it over on &lt;a href=&quot;https://channel9.msdn.com/Events/NDC/NDC-Oslo-2017/C9L02&quot;&gt;Channel9.msdn.com&lt;/a&gt; - thanks Seth and co for taking the time to put this together!&lt;/p&gt; &lt;iframe src=&quot;https://channel9.msdn.com/Events/NDC/NDC-Oslo-2017/C9L02/player&quot; width=&quot;540&quot; height=&quot;300&quot; allowFullScreen frameBorder=&quot;0&quot;&gt;&lt;/iframe&gt;</description>
          <pubDate>2017-06-28T16:39:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2017/06/28/interview-with-channel-9-at-ndc-oslo.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2017/06/28/interview-with-channel-9-at-ndc-oslo.html</guid>
        </item>
      
    
      
        <item>
          <title>Interview with habrahabr.ru about HTTP APIs in .NET</title>
          <description>&lt;p&gt;Next week I&apos;ll be in Russia, where I&apos;m speaking about HTTP APIs and REST at the &lt;a href=&quot;https://dotnext-piter.ru/&quot;&gt;DotNext conference in Saint Petersburg&lt;/a&gt;. As part of this event, I&apos;ve done an interview with the Russian tech site &lt;a href=&quot;https://habrahabr.ru/&quot;&gt;Хабрахабр&lt;/a&gt; about the history and future of API development on the web and in Microsoft.NET. The interview&apos;s &lt;a href=&quot;https://habrahabr.ru/company/jugru/blog/328090/&quot;&gt;available on their site habrahabr.ru&lt;/a&gt; (in Russian), but for readers who are interested but can&apos;t read Russian, here&apos;s the original English version.&lt;/p&gt; &lt;p align=&quot;center&quot;&gt;&lt;a href=&quot;https://www.goodfreephotos.com/russia/saint-petersburg/&quot;&gt;&lt;img style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; src=&quot;https://www.goodfreephotos.com/albums/russia/saint-petersburg/cathedral-in-saint-petersburg-russia.jpg&quot;&gt;&lt;br&gt;&lt;/a&gt;&lt;font size=&quot;1&quot;&gt;&lt;em&gt;Cathedral in Saint Petersburg, Russia. &lt;a href=&quot;https://www.goodfreephotos.com/russia/saint-petersburg/cathedral-in-saint-petersburg-russia.jpg.php&quot;&gt;goodfreephotos.com&lt;/a&gt; / Photo by &lt;/em&gt;&lt;/font&gt;&lt;a href=&quot;https://pixabay.com/en/users/DEZALB-1045091/&quot;&gt;&lt;font size=&quot;1&quot;&gt;&lt;em&gt;DEZALB&lt;/em&gt;&lt;/font&gt;&lt;/a&gt;&lt;font size=&quot;1&quot;&gt;&lt;em&gt;.&lt;/em&gt;&lt;/font&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Q: What kind of APIs are you designing? Where does API design fit into software development?&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;That’s kind of an interesting question, because I think one of the biggest misconceptions in software is that designing APIs is an activity that happens separately to everything else. Sure, there are certain kinds of API projects – particularly things like HTTP APIs which are going to be open to the public – where it might make sense to consider API design as a specific piece of work. But the truth is that most developers are actually creating APIs all the time – they just don’t realise they’re doing it. Every time you write a public method on one of your classes or choose a name for a database table, you’re creating an interface – in the everyday English sense of the word – that will end up being used by other developers at some point in the future. Other people on your team will use your classes and methods. Other teams will use your data schema or your message format.  &lt;p&gt;What’s interesting, though, is that once developers realize that the code they’re working on will probably form part of an API, they tend to go too far in the other direction. They’ll implement edge cases and things that they don’t actually need, just in case somebody else might need it later. I think there’s a very fine balance, and I think the key to that balance is to be very strict about only building the features that you need right now, but to make those things as reusable and self-explanatory as you can. There’s a great essay by Pieter Hintjens,&lt;a href=&quot;http://hintjens.com/blog:94&quot;&gt; &lt;strong&gt;Ten Rules for Good API Design&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;,&lt;/strong&gt; that goes into more detail about these kinds of ideas.  &lt;p&gt;The biggest API project I’m working on at the moment is a thing I’m building at Spotlight in the UK, where I work. It’s a hypermedia API exposing information about professional actors, acting jobs in film and television, and various other kinds of data used in the casting industry. We’re building it in the architectural style known as REST – and if you’re not sure what REST is, you should come to &lt;a href=&quot;https://dotnext-piter.ru/en/talks/real-world-rest-and-hands-on-hypermedia/&quot;&gt;my talk at DotNext in Saint-Petersburg&lt;/a&gt; and learn all about it. There’s lots of different patterns for creating HTTP APIs – there’s REST, there’s &lt;a href=&quot;http://graphql.org/&quot;&gt;GraphQL&lt;/a&gt;, there’s things like SOAP and RPC – but for me, the biggest appeal of REST is that I think the constraints of the RESTful style lead to a natural decoupling of the concepts and operations that your API needs to support, which makes it easier to change things and evolve the API design over time.  &lt;p&gt;&lt;strong&gt;Q: One of the most famous applications that was &quot;killed&quot; by backward compatibility is IE. The problem of this browser was that it has too large number of applications for which it was required to have backward compatibility. Problem was solved by adding new application Edge, which is updatable and supports all new standards. Can you give an piece of advice on how not to get caught by that backward-compatibility trap? As an example could it be a modularity which doesn&apos;t have layers? May be there is a way to replace API with RESTful API, Service Oriented Architecture or something else?&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;I’ve been building web applications for a long, long time – I wrote my first HTML page a couple of years before Internet Explorer even existed, back when the only browsers were NCSA Mosaic and Erwise. It’s fascinating to look back at the history of the web, and how the web that exists today has been shaped and influenced by things like the evolution of Internet Explorer – and you’re absolutely right; one of the reasons why Microsoft has introduced a completely new browser, Edge, in the latest versions of Windows is that Internet Explorer’s commitment to backwards-compatibility has made it really difficult to implement support for modern web standards alongside the existing IE codebase.  &lt;p&gt;Part of the reason why that backwards compatibility exists is that, around the year 2000, there was a massive shift in the way that corporate IT systems were developed. There are countless corporations who have bespoke applications for doing all sorts of business operations – stock control, inventory, HR, project management, all kinds of things. Way back in the 1980s and early 1990s, most of them used a central mainframe system and employees would have to use something like a terminal emulator to connect to that central server, but after the first wave of the dotcom boom hit in the late 1990s, companies realised that most of their PCs now had a web browser and an network connection, and so they could replace their old mainframe terminal applications with web applications. Windows had enormous market share at the time, and Internet Explorer was the default browser on most Windows PCs, so lots of organizations built intranet web applications that only had to work on a specific version of Internet Explorer. Sometimes they did this to take advantage of specific features, like ActiveX support; more often I think they just did it save money because it meant they didn’t have to do cross-browser testing. This happened with some pretty big commercial applications as well; as late as 2011, Microsoft Dynamics CRM still offered no support for any browser other than Internet Explorer.  &lt;p&gt;So you’ve got all these companies who have invested lots of time and money in building applications that only work with Internet Explorer. Those applications aren’t built using web standards or progressive enhancement or with any notion of ‘forward compatibility’ – they’re explicitly targeting one version of one browser running on one operating system. And so when Microsoft releases a new version of Internet Explorer, those applications fail – and the companies don’t want to invest in upgrading their legacy intranet applications, so they blame the browser. So we end up with this weird situation where here in 2017, Microsoft are still shipping Internet Explorer 11, which has a compatibility mode where it switches back to the IE9 rendering engine but sends a user agent string claiming that it&apos;s IE7. Meanwhile, everyone I know uses Google Chrome or Safari for all their web browsing - but still has an IE shortcut on their desktop for when they have to log in to one of those legacy systems. .  &lt;p&gt;So… to go back to the original question: is there anything Microsoft could have done to avoid this trap? I think there’s a lot of things they could have done. Building IE from the ground up with a modular rendering engine, so that later versions could selectively load the appropriate engine for rendering a particular website or application. They could have made more effort to embrace the web standards that existed at the time, instead of implementing ad-hoc support for things like the MARQUEE tag and ActiveX plugins, which would have avoided the headache of having to support these esoteric features in later versions. The point is, though, none of this mattered at the time. Their focus – the driving force behind the early versions of Internet Explorer – was not to create a great application with first-class support for web standards. They were trying to kill Netscape Navigator and win market share – and it worked.  &lt;p&gt;&lt;strong&gt;Q: Let’s imagine someone is going to introduce an API. So they collect some requirements, propose a version and gets feedback. That’s rather simple and straightforward thing. But if there are any hidden obstacles there down the road?&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;Always! Requirements are going to change – in fact, one of the biggest mistakes you can make is to try and anticipate those changes and make your design ‘future-proof’. Sometimes that pays off, but what mostly happens is that you end up with a much more complicated design purely because you’re trying to anticipate those future changes. Those obstacles are often things outside your control. There’s a change to the law that means you need to expose certain data in a different way. There’s a change to one of the other systems in your organization, or one of your cloud hosting providers announces that they’re deprecating a particular feature that you were relying on.  &lt;p&gt;The best thing to do is to identify something simple and usable, ship it, and get as quickly as you can to a point where your API is stable, there’s no outstanding technical debt, and your team is free to move on to the next thing. That way when you do encounter one of those ‘hidden obstacles’, you have a stable codebase to use as a basis for your solution, and you have a team who have the time and the bandwidth to deal with it. And if by some stroke of luck you don’t hit any hidden obstacles, then you just move on to the next thing on your backlog.  &lt;p&gt;&lt;strong&gt;Q: Continue with API design. We’ve released the v1.0 of our API and now v1.1 is approaching. I believe many of us noticed&lt;/strong&gt;&lt;a href=&quot;http://example.com/v1/test&quot;&gt;&lt;strong&gt; http://example.com/v1/test&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt; and&lt;/strong&gt;&lt;a href=&quot;http://example.com/v1/test&quot;&gt;&lt;strong&gt; http://example.com/v1.1/test&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt; or something. What are the best practices (a couple of points) you can think of that can help a developer to design a good v1.1 API in respect to v1.0?&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;It’s worth reading up on the concept of semantic versioning, (SemVer) and taking the time to really understand the distinction between major, minor and patch versions. SemVer says that you shouldn’t introduce any breaking changes between version x.0 and version x.1, so the most important thing is to understand what would constitute a breaking change for your particular API.  &lt;p&gt;If you’re working with HTTP APIs that return JSON, for example, a typical non-breaking change would be adding a new data field to one of your resources. Clients that are using version 1.1 and are expecting to see this additional field can take advantage of it, whereas clients that are still using version 1.0 can just discard the unrecognised property.  &lt;p&gt;There’s a related question about how you should manage versioning in your APIs. One very common solution is to expose URLs via routing – api.example.com/v1/ as opposed to api.example.com/v1.1 – but if you’re adhering to the constraints of a RESTful system, you really need to understand whether the change in version represents a change in the underlying resource or just the representation. Remember that a URI is a Uniform Resource Identifier, and so we really shouldn’t be changing the URI that we’re using to refer to the same resource.  &lt;p&gt;For example – if we have a resource &lt;strong&gt;api.example.com/images/monalisa&lt;/strong&gt;. We could request that resource as a JPEG (&lt;strong&gt;Accept: image/jpeg&lt;/strong&gt;), or as a PNG (&lt;strong&gt;Accept: image/png&lt;/strong&gt;), or ask the server if it has a plain-text representation of the resource (&lt;strong&gt;Accept: text/plain&lt;/strong&gt;) – but they’re just different representations of the same underlying resource, and so they should all use the same URI.  &lt;p&gt;If – say – you’ve completely replaced the CRM system used by your organization, and so “version 1” of a customer represents a record used in the old CRM system and “version 2” represents that same customer after they’ve been migrated onto a completely new platform, then it probably makes sense to treat them as separate resources and give them different URIs.  &lt;p&gt;Versioning is hard, though. The easiest thing to do is never change anything   &lt;p&gt;&lt;strong&gt;Q: .NET Core - what do you think about its API?&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;When .NET Core was first announced in 2015, back when it was going to be be called .NET Core 5.0, it was going to be a really stripped-down, lightweight alternative to the .NET Framework and the Common Language Runtime. That was an excellent idea in terms of making it easier to port .NET Core to different platforms, but it also left a sizable gap between the API exposed by .NET Core, and the ‘standard’ .NET/CLR API that most applications are built against.  &lt;p&gt;I believe – and this is just my interpretation based on what I’ve read and people I’ve talked to – that the idea was that .NET Core would provide the fundamental building blocks. It would provide things like threading, filesystem access, network access, and then a combination of platform vendors and the open source community would develop the modules and packages that would eventually match the level of functionality offered by something like the Java Class Library or the .NET Framework. That’s a great idea in principle, but it also creates a chicken-and-egg situation: people won’t build libraries for a platform with no users, but nobody wants to use a platform that doesn’t have any libraries.  &lt;p&gt;So, the decision was made that cross-platform .NET needed a standard API specification that would provide the libraries that users and application developers expected to be available on the various supported platforms. This is .NET Standard 2.0, which is already fully supported by the .NET Framework 4.6.1 and will be supported in the next versions of .NET Core and Xamarin. Of course, .NET Core 1.1 is out, and works just fine, and you can use it right now to build web apps in C# regardless of whether you’re running Windows or Linux or macOS, which is pretty awesome – but I think the next release of .NET Core is going to be the trigger for a lot of framework and package developers to migrate their projects across to .NET Core, which in turn should make it easier for developers and organizations to migrate their own applications.  &lt;p&gt;&lt;img style=&quot;border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; padding-right: 0px&quot; border=&quot;0&quot; src=&quot;https://www.goodfreephotos.com/albums/russia/saint-petersburg/tram-on-moscow-gate-square-in-saint-petersburg-russia.jpg&quot;&gt;&lt;font size=&quot;1&quot;&gt;&lt;em&gt;Tram on Moscow Gate Square in Saint Petersburg. &lt;a href=&quot;https://www.goodfreephotos.com/russia/saint-petersburg/tram-on-moscow-gate-square-in-saint-petersburg-russia.jpg.php&quot;&gt;freegoodphotos.com&lt;/a&gt; /&amp;nbsp; Photo by &lt;/em&gt;&lt;/font&gt;&lt;a href=&quot;https://commons.wikimedia.org/wiki/User:Dinamik&quot;&gt;&lt;font size=&quot;1&quot;&gt;&lt;em&gt;Dinamik&lt;/em&gt;&lt;/font&gt;&lt;/a&gt;&lt;font size=&quot;1&quot;&gt;&lt;em&gt;.&lt;/em&gt;&lt;/font&gt; &lt;p&gt;&lt;strong&gt;API flexibility VS. API precision. One can design a method API so it can accept many different types of values. It’s flexibility. We also can design a method API with lots of rules on input parameters. Both ways are correct. Where is the boundary across these approaches? When should I make a “strict” API and when should I make a more “flexible” design? Don’t forget that you should take backward-compatibility into account.&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;By implementing an API where the method signatures are flexible, all you’re doing is pushing the complexity to somewhere else in your stack. Say we’re building an API for finding skiing holidays, and we have a choice between  &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;DoSearch(SearchCriteria criteria) &lt;/font&gt; &lt;p&gt;and  &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;DoSearch(string resortName, string countryCode, int minAltitude, int maxDistanceToSkiList) &lt;/font&gt; &lt;p&gt;One of those methods is pretty easily extensible, because we can extend the definition of the &lt;strong&gt;SearchCriteria&lt;/strong&gt; object without changing the method signature – but we’re still changing the behaviour of the system, we’re just not changing that particular method. By contrast, we could add new arguments to our &lt;strong&gt;DoSearch&lt;/strong&gt; method signature, but if we’re working in a language like C# where you can provide default argument values, you won’t break anything by doing that as long as you provide sensible defaults for the new arguments.  &lt;p&gt;At some point, though, you need to communicate to the API consumers what search parameters are accepted by your API, and there’s lots of ways to accomplish that. If you’re building a .NET API that’s installed as a NuGet package and used from within code, then using XML comments on your methods and properties is a great way to explain to your users what they need to specify when making calls to your API. If your API is an HTTP service, look at using hypermedia and formats like &lt;a href=&quot;https://github.com/kevinswiber/siren&quot;&gt;SIREN&lt;/a&gt; to define what parameter values and ranges are acceptable.  &lt;p&gt;I should add that I think within the next decade, we’re going to start seeing a whole different category of APIs powered by machine learning systems, where a lot of the conventional rules of API design won’t apply. It wouldn’t surprise me if we got an API for finding skiing holidays where you just specify what you want in natural language, and so there’s not even a method signature – you just call something like DoSearch(“ski chalet, in France or Italy, 1400m or higher, that sleeps 12 people in 8 bedrooms, available from 18-25 January 2018”) – and the underlying system will work it all out for us. Those sorts of development in machine learning are hugely exciting, but they’re also going to create a lot of interesting challenges for the developers and designers trying to incorporate them into our products and applications.&amp;nbsp; &lt;/p&gt;&lt;b&gt; &lt;p&gt;&lt;/b&gt;&lt;/p&gt; &lt;hr&gt; Thanks to &lt;a href=&quot;https://www.linkedin.com/in/alexejsommer/&quot;&gt;Alexej Sommer&lt;/a&gt; for taking the time to set this up (and for translating my answers into Russian – &lt;em&gt;Спасибо!&lt;/em&gt;), and if you&apos;re at &lt;a href=&quot;https://dotnext-piter.ru/en/&quot;&gt;DotNext&lt;/a&gt; next week and want to chat about APIs, hypermedia or any of the stuff in the interview, please come and say hi!   </description>
          <pubDate>2017-05-13T11:32:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2017/05/13/interview-with-habrahabrru-about-http.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2017/05/13/interview-with-habrahabrru-about-http.html</guid>
        </item>
      
    
      
        <item>
          <title>It Works On My Machine!</title>
          <description>&lt;p&gt;I &lt;a href=&quot;https://twitter.com/derickbailey/status/857500834908786688?refsrc=email&amp;amp;s=11&quot;&gt;saw on Twitter&lt;/a&gt; this morning that &lt;a href=&quot;https://derickbailey.com/2017/02/08/digging-into-the-works-on-my-machine-problem/&quot;&gt;Derick Bailey is looking for people&lt;/a&gt; to share their own “works on my machine” stories… and halfway through filling out &lt;a href=&quot;https://docs.google.com/forms/d/e/1FAIpQLSe5F59JxFOUuzWF100r3mY5xpkLL201U0p_HCkwbBkKB6bLew/viewform?c=0&amp;amp;w=1&quot;&gt;his survey&lt;/a&gt;, I decided this would probably be much more fun if I nicked his survey questions and turned them into headings in a blog post. Mainly ‘cos writing for an audience appeals to me far more than filling out survey – but Derick (and anyone else who cares?) is very welcome to use anything in this post as part of their own research.&lt;/p&gt; &lt;h4&gt;&lt;strong&gt;What&apos;s typically going through your head when you say &quot;works on my machine&quot; to a QA person or another developer?&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;I think the interesting question here is actually – what did somebody say to &lt;em&gt;you&lt;/em&gt; that caused you to respond with “it works on my machine?”&lt;/p&gt; &lt;p&gt;Here’s three fairly common scenarios:&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;Q:&lt;/strong&gt; This code throws an exception when we run it on the staging environment…&lt;br&gt;&lt;strong&gt;A: &lt;/strong&gt;It works on &lt;em&gt;my&lt;/em&gt; machine.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Q: &lt;/strong&gt;How are you getting on with that improvement to the search algorithm?&lt;br&gt;&lt;strong&gt;A: &lt;/strong&gt;It works on &lt;em&gt;my&lt;/em&gt; machine.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Q: &lt;/strong&gt;Did you get anywhere with that really weird solution to the mapping problem that Chris found on StackOverflow?&lt;br&gt;&lt;strong&gt;A: &lt;/strong&gt;It works on &lt;em&gt;my &lt;/em&gt;machine…&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;See how in each case, there’s a sort of implicit subtext? See, I think we all understand that there’s often quite a big difference between solving a problem and delivering a solution. In almost all development scenarios, the first step is to get the code you’re working on running locally and doing the right thing on your development system – and often to do that, we have to hack things around. Running web servers as a local admin user. Granting “Everyone Full Control” of all the files in the media folder. Manually tweaking registry keys, installing DLLs, reusing credentials for APIs and external services – there’s a whole lot of stuff that has to happen as well as just writing some code, but most of the time, the code is the focus and the rest just feels like friction.&lt;/p&gt; &lt;p&gt;So… to answer the original question, when I tell somebody something works on my machine, I’m thinking “ok, what &lt;em&gt;else&lt;strong&gt;, &lt;/strong&gt;&lt;/em&gt;other than my own code, do we need to do to deploy the solution, close the ticket and move on?” When you’re working on spikes and prototypes, that’s a natural part of the conversation. If you’ve submitted a ticket to QA for final pre-release testing and it doesn’t work, there’s naturally a bit of tension because implicit in the conversation is the fact that somebody thinks you haven’t done your job properly – and “well it works on &lt;em&gt;my&lt;/em&gt; machine” can come across as defensive. &lt;/p&gt; &lt;h4&gt;Can you share a story about a time when you have said, or thought, this?&lt;/h4&gt; &lt;p&gt;Ah, dozens. The most common example for me is when I’m making a change that spans code in 4-5 different projects, so I’ve checked them all out… in one project I’ve added a database column, in another there’s some new HTTP request routing, in the third there’s a new message queue subscriber, and then there’s the new feature code that relies on all of those changes to work properly. And it works on MY machine because locally I’ve already made all of those changes, but it get it working anywhere else, all five projects need to be reviewed, built, packaged, configured and deployed onto the same environment. Or, for another developer to work on it, they need to check out five specific branches from five specific projects and then probably run a couple of configuration scripts as well – and there’s invariable one or two little things that didn’t require any explicit configuration on my own workstation but then it turns out your teammate has enabled WebDAV publishing under Windows Programs and Features and so their IIS configuration isn’t the same as yours.&lt;/p&gt; &lt;h4&gt;How do you typically feel when someone says, &quot;works on my machine,&quot; to you?&lt;/h4&gt; &lt;p&gt;First off, I’m happy. See, I’ve worked with a very small number of developers who didn’t bother doing even this most basic validation of the work they were doing. They’d commit something, open a pull request and ask for a code review, and you’d look at what they did and think “that’s a bit weird”, so you’d wander over and say “hey, can you show me how this feature works?” – and they will actually say “I don’t know, I can’t run that project”.&amp;nbsp; &lt;/p&gt; &lt;p&gt;“Works on my machine” at least indicates that they’ve got all the code checked out, they’ve compiled it, and they’ve actually got it running. That’s a good start. That’s something you can work with. And at that point, it’s a great opportunity to explain things like configuration management or deployment scripts.&lt;/p&gt; &lt;h4&gt;Can you share a story about a time when someone said this to you?&lt;/h4&gt; &lt;p&gt;We had a problem a few weeks ago where we ported an old project from VS2010 to VS2015, and TeamCity wouldn’t build it properly. An absolute textbook example – another developer comes to me and says “well, it works on &lt;em&gt;my&lt;/em&gt; machine; it builds fine and I can run all the tests, but TeamCity won’t run any of the unit tests and so the build keeps failing.” Turned out to be a rogue wildcard somewhere in the TeamCity build config settings that was causing it to pick up unit test DLLs from the \obj\ folders instead of \bin\. Which, of course, doesn’t happen when you’re running tests using Resharper or NCrunch, because those tools are smart enough to understand path conventions. &lt;br&gt; &lt;h4&gt;What are the 3 largest causes of someone saying &quot;works on my machine&quot;?&lt;/h4&gt; &lt;p&gt;In my experience? The biggest one is dependencies between multiple projects. The “feature”, the unit of business value we’re trying to deliver, requires changes across several different projects and so those changes need to be coordinated and deployed together in order to run and test the new feature, and it’s very easy to miss a step when you’re trying to capture and package all of those code and configuration dependencies.  &lt;p&gt;Second biggest would have to be mismatches between developer environments. We have some people running Windows 8.1, some people running Windows 10, some people working via remote desktop onto virtual machines in the cloud, and then all the various quirks of people’s individual OS configurations like the aforementioned WebDAV Authoring support.  &lt;p&gt;Third? Probably data. Lookup tables, test records, and code that’s brittle because it depends on specific records existing in a particular state, and when you check out the code it doesn’t include the migration steps or SQL scripts that are necessary to set up those records.  &lt;p&gt;Actually, I’m going to go for four, because the one that bites me all the time – probably once a week – is that when you add a new file to a Visual Studio solution, it doesn’t save the .csproj file by default. The new file gets added to the repo and pushed up to GitHub, but the &lt;em&gt;project&lt;/em&gt; &lt;em&gt;reference &lt;/em&gt;to that new file still only exists on your local machine. Sometimes it’ll crash the build; sometimes – if it’s an image or a script file or something – it’ll build, pass tests, deploy, and then fail on the test server because the new file isn’t included in the .csproj and so wasn’t included when the deployment package was built. If you think your organisation doesn’t have this problem, search your GitHub repo for commit messages including the phrase “csproj file”… &lt;br&gt; &lt;h4&gt;How do you combat the &quot;works on my machine&quot; problem?&lt;/h4&gt; &lt;p&gt;There’s a couple of things that have definitely made a big difference to our team at Spotlight. One is setting up an internal NuGet server (we’re running Klondike), and making sure that if your project code references DLLs or any other static components, those dependencies are managed as NuGet packages. That way the first time you build the project, it’ll download all of those obscure DLLs for you instead of waiting for you to get an error message, look it up on the wiki, etc.&lt;/p&gt; &lt;p&gt;One is giving everybody the ability to build and deploy pre-release packages. &lt;a href=&quot;http://www.dylanbeattie.net/2017/01/semantic-versioning-with-powershell_26.html&quot;&gt;We have TeamCity and GitHub configured&lt;/a&gt; so that as soon as you open a pull request, TeamCity will try and build a deployable package based on the merge head of your feature branch. This means you, the developer, can get packaged builds of your work in progress, deploy it onto one of our testing environments and see for yourself whether it’s going to work or not. Which means you get the chance to fix the bugs and configuration problems before passing it on to anyone else to review or test.&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-vRHfPZ708y0/WXjBR8Yef6I/AAAAAAAAEfo/6zJSThAUfSU0oJ-rmWV9SjDxm30Z_GPmQCHMYCw/s1600-h/belgian-beers%255B7%255D&quot;&gt;&lt;img width=&quot;540&quot; height=&quot;304&quot; title=&quot;belgian-beers&quot; style=&quot;border: 0px currentcolor; border-image: none; display: inline; background-image: none;&quot; alt=&quot;belgian-beers&quot; src=&quot;https://lh3.googleusercontent.com/-aLgrUHWFUw4/WXjBSTLhLAI/AAAAAAAAEfs/0jqSypurpFQ1U32dJhH071FPVV2fo1-1ACHMYCw/belgian-beers_thumb%255B5%255D?imgmax=800&quot; border=&quot;0&quot;&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;Oh, and we have something called &lt;strong&gt;escrow beers&lt;/strong&gt;. If you want to introduce a new tool, dependency, language or something into one of our projects, you have to put a six-pack of beer (or a box of cookies or something similarly delicious) in escrow, in the kitchen. Put a post-it note on it saying what it’s for – and then when some poor developer is working late to get a feature out and they discover that they need to install grunt or gulp or bower or yeoman or FAKE or PSake or whatever, there’s goodies in the fridge that will help. That’s doesn’t necessarily inhibit the adoption of new tech, but having to go out and buy beer or cookies makes people stop to think about how their changes might affect their teammates, and so they’ll add some checkout scripts to get the new thing working, or document it on the wiki, or organise a demo to show everyone what they need to know. It&apos;s also funny how often somebody thinks a new tool or language is ABSOLUTELY TOTALLY AMAZING and there&apos;s no way we can possibly live without it… except it&apos;s not actually quite amazing enough to justify walking to a shop at lunchtime and buying a box of cookies.&lt;/p&gt; &lt;p&gt;So there you go… more than you ever wanted to know about code that works on &lt;em&gt;my&lt;/em&gt; machine. Thanks again to &lt;a href=&quot;https://twitter.com/derickbailey&quot;&gt;Derick Bailey&lt;/a&gt; for the idea – and just to be clear, you’re welcome to use it, and please credit me by name if you use the information provided here in any follow-up posts or other material.&lt;/p&gt;</description>
          <pubDate>2017-04-27T15:27:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2017/04/27/it-works-on-my-machine.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2017/04/27/it-works-on-my-machine.html</guid>
        </item>
      
    
      
        <item>
          <title>Robert M. Pirsig on &quot;Stuckness&quot;</title>
          <description>&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Robert_M._Pirsig&quot;&gt;Robert M. Pirsig&lt;/a&gt;, the author of &quot;Zen and the Art of Motorcycle Maintenance&quot;, died today aged 88. I&apos;ve read and re-read that book many times over the years. As somebody who has always found tranquillity in tinkering, I found that &quot;Zen&quot; evokes that meditative, transcendental state that one can achieve whilst doing mechanical maintenance better than anything I&apos;ve read… and in others, it captures perfectly the awful frustration that can only be experienced when a perfectly simple job turns into a protracted bout of yak-shaving.&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-Zps6pEHVPfU/WXjCh4vCOtI/AAAAAAAAEf4/4TX8IExCfisB5QLXbnt7SALGp098TIlywCHMYCw/s1600-h/ZAMMcoverold%255B4%255D&quot;&gt;&lt;img width=&quot;320&quot; height=&quot;532&quot; title=&quot;ZAMMcoverold&quot; style=&quot;border: 0px currentcolor; border-image: none; margin-right: auto; margin-left: auto; float: none; display: block; background-image: none;&quot; alt=&quot;ZAMMcoverold&quot; src=&quot;https://lh3.googleusercontent.com/-kGNubZdng90/WXjCicstAtI/AAAAAAAAEf8/Iu-3nQPFhiAta6TtyEh6yQzLqldxxxo_wCHMYCw/ZAMMcoverold_thumb%255B2%255D?imgmax=800&quot; border=&quot;0&quot;&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;Of all the passages in the book, the one that has stayed with me the most is the one I&apos;ve included below, on the subject of &apos;stuckness&apos;. After countless evenings spent tweaking and tuning mountain bikes in my dad&apos;s garage, experiencing first-hand the frustration of a £800 mountain bike rendered completely useless by stripping the head off a 50p bolt, this passage resonated with me more than anything I think I&apos;ve ever read. I still think of it frequently, normally when I find myself stuck on some hitherto inconsequential detail of a software project that&apos;s somehow managed to derail the entire team for days at a time. The book is excellent, and if you haven&apos;t read it I highly recommend it, but the passage in question is here. I hope Mr Pirsig&apos;s lawyers don&apos;t mind. :)&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;Stuckness. That&apos;s what I want to talk about today.&lt;/strong&gt; &lt;p&gt;A screw sticks, for example, on a side cover assembly. You check the manual to see if there might be any special cause for this screw to come off so hard, but all it says is &quot;Remove side cover plate&quot; in that wonderful terse technical style that never tells you what you want to know. There&apos;s no earlier procedure left undone that might cause the cover screws to stick. &lt;p&gt;If you&apos;re experienced you&apos;d probably apply a penetrating liquid and an impact driver at this point. But suppose you&apos;re inexperienced and you attach a self-locking plier wrench to the shank of your screwdriver and really twist it hard, a procedure you&apos;ve had success with in the past, but which this time succeeds only in tearing the slot of the screw. &lt;p&gt;Your mind was already thinking ahead to what you would do when the cover plate was off, and so it takes a little time to realize that this irritating minor annoyance of a torn screw slot isn&apos;t just irritating and minor. You&apos;re stuck. Stopped. Terminated. It&apos;s absolutely stopped you from fixing the motorcycle. &lt;p&gt;This isn&apos;t a rare scene in science or technology. This is the commonest scene of all. Just plain stuck. In traditional maintenance this is the worst of all moments, so bad that you have avoided even thinking about it before you come to it. &lt;p&gt;The book&apos;s no good to you now. Neither is scientific reason. You don&apos;t need any scientific experiments to find out what&apos;s wrong. It&apos;s obvious what&apos;s wrong. What you need is an hypothesis for how you&apos;re going to get that slotless screw out of there and scientific method doesn&apos;t provide any of these hypotheses. It operates only after they&apos;re around. &lt;p&gt;This is the zero moment of consciousness. Stuck. No answer. Honked. Kaput. It&apos;s a miserable experience emotionally. You&apos;re losing time. You&apos;re incompetent. You don&apos;t know what you&apos;re doing. You should be ashamed of yourself. You should take the machine to a real mechanic who knows how to figure these things out. &lt;p&gt;It&apos;s normal at this point for the fear-anger syndrome to take over and make you want to hammer on that side plate with a chisel, to pound it off with a sledge if necessary. You think about it, and the more you think about it the more you&apos;re inclined to take the whole machine to a high bridge and drop it off. It&apos;s just outrageous that a tiny little slot of a screw can defeat you so totally. &lt;p&gt;What you&apos;re up against is the great unknown, the void of all Western thought. You need some ideas, some hypotheses. Traditional scientific method, unfortunately, has never quite gotten around to say exactly where to pick up more of these hypotheses. Traditional scientific method has always been at the very best, 20-20 hindsight. It&apos;s good for seeing where you&apos;ve been. It&apos;s good for testing the truth of what you think you know, but it can&apos;t tell you where you ought to go, unless where you ought to go is a continuation of where you were going in the past. Creativity, originality, inventiveness, intuition, imagination...&quot;unstuckness,&quot; in other words...are completely outside its domain. &lt;p&gt;We&apos;re still stuck on that screw and the only way it&apos;s going to get unstuck is by abandoning further examination of the screw according to traditional scientific method. That won&apos;t work. What we have to do is examine traditional scientific method in the light of that stuck screw. &lt;p&gt;We have been looking at that screw &quot;objectively.&quot; According to the doctrine of &quot;objectivity,&quot; which is integral with traditional scientific method, what we like or don&apos;t like about that screw has nothing to do with our correct thinking. We should not evaluate what we see. We should keep our mind a blank tablet which nature fills for us, and then reason disinterestedly from the facts we observe. &lt;p&gt;But when we stop and think about it disinterestedly, in terms of this stuck screw, we begin to see that this whole idea of disinterested observation is silly. Where are those facts? What are we going to observe disinterestedly? The torn slot? The immovable side cover plate? The color of the paint job? The speedometer? The sissy bar? As Poincaré would have said, there are an infinite number of facts about the motorcycle, and the right ones don&apos;t just dance up and introduce themselves. The right facts, the ones we really need, are not only passive, they are damned elusive, and we&apos;re not going to just sit back and &quot;observe&quot; them. We&apos;re going to have to be in there looking for them or we&apos;re going to be here a long time. Forever. As Poincaré pointed out, there must be a subliminal choice of what facts we observe. &lt;p&gt;The difference between a good mechanic and a bad one, like the difference between a good mathematician and a bad one, is precisely this ability to select the good facts from the bad ones on the basis of quality. He has to care! This is an ability about which formal traditional scientific method has nothing to say. It&apos;s long past time to take a closer look at this qualitative preselection of facts which has seemed so scrupulously ignored by those who make so much of these facts after they are &quot;observed.&quot; I think that it will be found that a formal acknowledgment of the role of Quality in the scientific process doesn&apos;t destroy the empirical vision at all. It expands it, strengthens it and brings it far closer to actual scientific practice. &lt;p&gt;I think the basic fault that underlies the problem of stuckness is traditional rationality&apos;s insistence upon &quot;objectivity,&quot; a doctrine that there is a divided reality of subject and object. For true science to take place these must be rigidly separate from each other. &quot;You are the mechanic. There is the motorcycle. You are forever apart from one another. You do this to it. You do that to it. These will be the results.&quot; &lt;p&gt;This eternally dualistic subject-object way of approaching the motorcycle sounds right to us because we&apos;re used to it. But it&apos;s not right. It&apos;s always been an artificial interpretation superimposed on reality. It&apos;s never been reality itself. When this duality is completely accepted a certain nondivided relationship between the mechanic and motorcycle, a craftsmanlike feeling for the work, is destroyed. When traditional rationality divides the world into subjects and objects it shuts out Quality, and when you&apos;re really stuck it&apos;s Quality, not any subjects or objects, that tells you where you ought to go. &lt;p&gt;By returning our attention to Quality it is hoped that we can get technological work out of the noncaring subject-object dualism and back into craftsmanlike self-involved reality again, which will reveal to us the facts we need when we are stuck. &lt;p&gt;Let&apos;s consider a reevaluation of the situation in which we assume that the stuckness now occurring, the zero of consciousness, isn&apos;t the worst of all possible situations, but the best possible situation you could be in. After all, it&apos;s exactly this stuckness that Zen Buddhists go to so much trouble to induce; through koans, deep breathing, sitting still and the like. Your mind is empty, you have a &quot;hollow-flexible&quot; attitude of &quot;beginner&apos;s mind.&quot; You&apos;re right at the front end of the train of knowledge, at the track of reality itself. Consider, for a change, that this is a moment to be not feared but cultivated. If your mind is truly, profoundly stuck, then you may be much better off than when it was loaded with ideas. &lt;p&gt;The solution to the problem often at first seems unimportant or undesirable, but the state of stuckness allows it, in time, to assume its true importance. It seemed small because your previous rigid evaluation which led to the stuckness made it small. &lt;p&gt;But now consider the fact that no matter how hard you try to hang on to it, this stuckness is bound to disappear. Your mind will naturally and freely move toward a solution. Unless you are a real master at staying stuck you can&apos;t prevent this. The fear of stuckness is needless because the longer you stay stuck the more you see the Quality...reality that gets you unstuck every time. What&apos;s really been getting you stuck is the running from the stuckness through the cars of your train of knowledge looking for a solution that is out in front of the train. &lt;p&gt;Stuckness shouldn&apos;t be avoided. It&apos;s the psychic predecessor of all real understanding. An egoless acceptance of stuckness is a key to an understanding of all Quality, in mechanical work as in other endeavors. It&apos;s this understanding of Quality as revealed by stuckness which so often makes self-taught mechanics so superior to institute-trained men who have learned how to handle everything except a new situation. &lt;p&gt;Normally screws are so cheap and small and simple you think of them as unimportant. But now, as your Quality awareness becomes stronger, you realize that this one, individual, particular screw is neither cheap nor small nor unimportant. Right now this screw is worth exactly the selling price of the whole motorcycle, because the motorcycle is actually valueless until you get the screw out. With this reevaluation of the screw comes a willingness to expand your knowledge of it. &lt;p&gt;&lt;em&gt; - from &quot;Zen and the Art of Motorcycle Maintenance&quot; by Robert M Pirsig (September 6, 1928 – April 24, 2017)&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;</description>
          <pubDate>2017-04-24T22:39:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2017/04/24/robert-m-pirsig-on-stuckness.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2017/04/24/robert-m-pirsig-on-stuckness.html</guid>
        </item>
      
    
      
        <item>
          <title>There’s a problem with the phalange!</title>
          <description>&lt;iframe height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/DrwVB4vMx-Q&quot; frameborder=&quot;0&quot; width=&quot;560&quot; allowfullscreen&gt;&lt;/iframe&gt; &lt;p align=&quot;left&quot;&gt;Yesterday I was throwing together a quick Entity Framework prototype to inspect and wrangle some data held in one of our legacy databases. I’m using the Entity Framework “Code first from Database” approach, where you use the tooling to generate your initial model for you but thereafter you modify it by hand. One of the tables I’m working with here is called &lt;strong&gt;LookupRanges&lt;/strong&gt;, so when I generated a bunch of entities and &lt;strong&gt;DbSet&amp;lt;&amp;gt;&lt;/strong&gt; mappings, I was a bit surprised when I ended up with a class called &lt;strong&gt;LookupRanx&lt;/strong&gt; in my new model.&lt;/p&gt; &lt;p align=&quot;left&quot;&gt;It took a minute or two to ascertain that yes, Entity Framework had mapped my &lt;strong&gt;LookupRanges&lt;/strong&gt; (plural) table name onto a class called &lt;strong&gt;LookupRanx&lt;/strong&gt;. But where on earth did that ‘&lt;strong&gt;Ranx&lt;/strong&gt;’ come from? My hunch here is that somebody who worked on this pluralization code remembered that the English word &lt;em&gt;phalanx&lt;/em&gt; has the plural form &lt;em&gt;phalanges &lt;/em&gt;– and so implemented a rule that says ‘any word ending in &lt;em&gt;–anges&lt;/em&gt; should be singularizaed to &lt;em&gt;–anx&lt;/em&gt;. Out of curiousity, I dug out my huge ASCII file of English words, found all the words ending in *anges, and hacked up a quick SQL script to create tables named for all these words so I could run them through EF and see what class names were generated. &lt;/p&gt; &lt;p align=&quot;left&quot;&gt;Well, it gets ‘changes’ and ‘phalanges’ right – and gets literally every other case wrong. &lt;/p&gt; &lt;p align=&quot;left&quot;&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-q3BkKT_2oko/WPorXCNsMEI/AAAAAAAAEYg/dmGR8xxEEaE/s1600-h/image%25255B3%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;https://lh3.googleusercontent.com/-_5rE3biF9hI/WPorXlGnKOI/AAAAAAAAEYk/Y3Sw9WUB6eQ/image_thumb%25255B1%25255D.png?imgmax=800&quot; width=&quot;478&quot; height=&quot;217&quot;&gt;&lt;/a&gt;&lt;/p&gt; &lt;p align=&quot;left&quot;&gt;Now this, to me, is an outstanding example of one of the biggest problems in software development… smart people like working on things that are &lt;em&gt;interesting&lt;/em&gt;, and will frequently spend time doing something that’s interesting instead of something that’s important.&lt;/p&gt; &lt;p align=&quot;left&quot;&gt;Writing a library that can singularize and pluralize English words is fascinating. It’s a never-ending problem with dozens of rules and hundreds of edge cases, and you learn a lot of weird and cool esoteric facts about language and etymology whilst you’re doing it. But in this instance, something started out as a good idea (“hey – wouldn’t it be cool if the model generator would convert plural table names to singular class names?”), and got bogged down in edge cases (“is the plural of ‘tableau’ really ‘tableaux’?”) and – in this instance – ended up with a bizarre bug because one of those so-called edge cases actually ended up breaking the default – and entirely correct – behaviour.&lt;/p&gt; &lt;p align=&quot;left&quot;&gt;First, &lt;em&gt;phalanges&lt;/em&gt; is extremely unlikely to ever show up as the name of a table in an Entity Framework database model. I can think of dozens of real-world scenarios where you’d end up with a table name ending with –Ranges, –Exchanges or –Interchanges, but I’m honestly struggling to think of any remotely likely scenario where you have a Phalanges table in a SQL Server database. This is the kind of thing where the ticket or the user story probably just says ‘implement pluralization’, and then there’s no sub-prioritization or further analysis about just how much pluralization needs to be implemented. Second – it’s kind of a stupid edge case. We’re not trying to win points on University Challenge here, we’re building software. In contemporary English, &lt;strong&gt;&lt;a href=&quot;https://www.merriam-webster.com/dictionary/phalange&quot;&gt;phalange&lt;/a&gt;&lt;/strong&gt; is an acceptable singular form, and &lt;strong&gt;&lt;a href=&quot;https://www.merriam-webster.com/dictionary/phalanxes&quot;&gt;phalanxes&lt;/a&gt; &lt;/strong&gt;is an acceptable plural form. There’s no reason at all why they needed to implement support for this particular edge case. And third: if you &lt;em&gt;really &lt;/em&gt;found yourself in a scenario where you had to map the Phalanges table to the Phalanx class, you can just rename it. It’s easy. Visual Studio has first-class support for this kind of refactoring.&lt;/p&gt; &lt;p align=&quot;left&quot;&gt;And, as if that wasn’t confusing enough, there’s actually source code for an &lt;a href=&quot;https://github.com/Microsoft/referencesource/blob/90b323fe52bec428fe4bd5f007e9ead6b265d553/System.Data.Entity.Design/System/Data/Entity/Design/PluralizationService/EnglishPluralizationService.cs#L48&quot;&gt;EnglishPluralizationService.cs&lt;/a&gt; on Microsoft’s GitHub repository. Somebody obviously had a lot of fun building this, tracking down all those bizarre little edge cases like seraph/seraphim, hippopotamus/hippopotami – but according to &lt;strong&gt;this&lt;/strong&gt; implementation, the plural of phalanx is…&amp;nbsp; go on. Go and &lt;a href=&quot;https://github.com/Microsoft/referencesource/blob/90b323fe52bec428fe4bd5f007e9ead6b265d553/System.Data.Entity.Design/System/Data/Entity/Design/PluralizationService/EnglishPluralizationService.cs#L257&quot;&gt;take a look&lt;/a&gt;.&lt;/p&gt; &lt;p align=&quot;left&quot;&gt;Now, though, I’m going to eat an oranx and check my email. Using Microsoft Exchanx, of course.&lt;/p&gt;</description>
          <pubDate>2017-04-21T15:55:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2017/04/21/theres-problem-with-phalange.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2017/04/21/theres-problem-with-phalange.html</guid>
        </item>
      
    
      
        <item>
          <title>The Pursuit of APIness: The Secret to Happy Code</title>
          <description>&lt;p&gt;I&apos;ll be giving a new talk at the &lt;a href=&quot;https://www.meetup.com/London-NET-User-Group/events/237160759/&quot;&gt;London.NET User Group meetup&lt;/a&gt; here in London next Tuesday, based on an idea I&apos;ve had rattling around for a decade or more now. See, it seems to me that over the course of my career, there&apos;s been a strong correlation between happy developers and successful projects. I can&apos;t think of any examples where a miserable death-march project has resulted in high-quality working software, and I can&apos;t think of too many instances where a group of happy, motivated developers has failed to deliver a working product. I&apos;ve been thinking around this idea for a while, and started looking at it in terms of user experience – both the user experience that we as developers are creating for our end users, but also the &apos;user experience&apos; that&apos;s being provided by the libraries, frameworks and tools that we&apos;re using to do our jobs. Here&apos;s the talk synopsis:&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;We spend our lives working with systems created by other people. From the UI on our phones to the cloud infrastructure that runs so much of the modern internet, these interactions are fundamental to our experience of technology - as engineers, as developers, as users - and user experiences are viral. Great user experiences lead to happy, productive people; bad experiences lead to frustration, inefficiency and misery. &lt;p&gt;Whether we realise it or not, when we create software, we are creating user experiences. People are going to interact with our code. Maybe those people are end users; maybe they&apos;re the other developers on your team. Maybe they&apos;re the mobile app team who are working with your API, or the engineers who are on call the night something goes wrong. These may be radically different use cases, but there&apos;s one powerful principle that works across all these scenarios and more. In this talk, we&apos;ll draw on ideas and insight from user experience, API design, psychology and education to show how you can incorporate this principle, known as discoverability, into every layer of your application. We&apos;ll look at some real-world systems, and we&apos;ll discuss how discoverability works with different interaction paradigms. Because, whether you&apos;re building databases, class libraries, hypermedia APIs or mobile apps, sooner or later somebody else is going to work with your code - and when they do, wouldn&apos;t it be great if they went away afterwards with a smile on their face?&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;If that sounds interesting (or if you think I&apos;m completely wrong and you want to come along and heckle!), &lt;a href=&quot;https://skillsmatter.com/meetups/8553-londondot-net-april-meetup&quot;&gt;sign up at the SkillsMatter website&lt;/a&gt; and come along on Tuesday 11th. Hope to see you there. &lt;/p&gt;</description>
          <pubDate>2017-04-03T17:19:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2017/04/03/the-pursuit-of-apiness-secret-to-happy.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2017/04/03/the-pursuit-of-apiness-secret-to-happy.html</guid>
        </item>
      
    
      
        <item>
          <title>Goodbye ECMAScript; hello UKMAScript!</title>
          <description>&lt;p&gt;The UK government has announced it will trigger Article 50 on March 29th, beginning the two-year process of the United Kingdom leaving the European Union.&lt;/p&gt;&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&gt;&lt;div dir=&quot;ltr&quot; lang=&quot;en&quot;&gt;Once the UK trigger &lt;a href=&quot;https://twitter.com/hashtag/Article50?src=hash&quot;&gt;#Article50&lt;/a&gt;, UK web devs will have only two years to define a replacement for ECMAScript &amp;amp; migrate all their web apps&lt;/div&gt;— Nat Pryce (@natpryce) &lt;a href=&quot;https://twitter.com/natpryce/status/844592277284683777&quot;&gt;March 22, 2017&lt;/a&gt;&lt;/blockquote&gt;&lt;script async=&quot;&quot; charset=&quot;utf-8&quot; src=&quot;//platform.twitter.com/widgets.js&quot;&gt;&lt;/script&gt; &lt;br /&gt;
&lt;p&gt;That’s right - it will no longer be legal for British web developers to run ECMAScript, since the ECMAScript specification is controlled by the European Computer Manufacturers’ Association. We’re happy to announce that as of today, top engineers are starting work on a superior British programming language called &lt;strong&gt;UKMAScript&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;UKMAScript&lt;/strong&gt; will extend the core language specification with the following enhancements, which we believe will provide a massive boost to the UK tech industry and offset the immeasurable damage caused when all our EU colleagues and collaborators decide to exercise their freedom of movement.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Along with &lt;strong&gt;NaN&lt;/strong&gt; and &lt;strong&gt;Infinity&lt;/strong&gt;, UKMAScript will support a new primitive numeric value called &lt;strong&gt;MoneyForTheNhs&lt;/strong&gt;, whose value is defined to be exactly 3.5x10&lt;sup&gt;8&lt;/sup&gt; until it’s used as an argument to any function, at which point its value will be silently changed to zero after the function has returned.  &lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Math.round()&lt;/strong&gt; method will behave as before, except &lt;strong&gt;Math.round(0.52)&lt;/strong&gt; will now return &lt;strong&gt;Number.MAX_VALUE. Math.round(0.48) &lt;/strong&gt;will return a new constant &lt;strong&gt;Number.TRAITORS &lt;/strong&gt;and any attempt to use this value in calculations will throw a &lt;strong&gt;TreasonError&lt;/strong&gt;  &lt;/li&gt;
&lt;li&gt;A new “illogical implication” operator !#&amp;gt; will be introduced. This is syntactically similar to the notion of logical implication in Boolean algebra, but designed to allow the scope of arguments to be massively exaggerated. For example, the statement &lt;strong&gt;(leave_eu !#&amp;gt; leave_customs_union &amp;amp;&amp;amp; leave_eea)&lt;/strong&gt; will implicitly bind the values of &lt;strong&gt;leave_customs_union&lt;/strong&gt; and &lt;strong&gt;leave_eea&lt;/strong&gt; to the value of &lt;strong&gt;leave_eu&lt;/strong&gt;, despite this dependency not being expressed anywhere else in the codebase.  &lt;/li&gt;
&lt;li&gt;Along with &lt;strong&gt;null&lt;/strong&gt; and &lt;strong&gt;undefined&lt;/strong&gt;, a new language primitive &lt;strong&gt;brexit&lt;/strong&gt; will be introduced. This has the special equality semantics &lt;strong&gt;(brexit == brexit) == undefined. typeof(brexit) &lt;/strong&gt;will return the value &lt;strong&gt;“hard”&lt;/strong&gt;, and attempting to evaluate &lt;strong&gt;brexit.valueOf() &lt;/strong&gt;will throw a &lt;strong&gt;TreasonError&lt;/strong&gt;.  &lt;/li&gt;
&lt;li&gt;UKMAScript features a new parallel programming paradigm implemented via the Referendum.Invoke() method. This causes a thread to break away from the main sequence of program control and attempt to continue execution despite no longer having access to any processing capabilities or shared resources of the host system. Note that if a thread A has called Referendum.Invoke(), any child process B attempting to call Referendum.Invoke() will be summarily ignored by process A on the grounds that it’s clearly developed a fault.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;UKMAScript ships with no standard library or runtime, but the UKMAScript language committee assures us that platform vendors are lining up to deliver first-class support for the new language. &lt;/p&gt;&lt;p&gt;To further promote the popularity of UKMAScript, the only alternative permitted once Article 50 has been invoked is a new language called LABOUR, which takes many of the core language principles of COBOL-64 but is only accessible using the GNU/Corbyn compiler. This compiler has a tremendously exciting installation routine but then doesn’t actually do anything other than occasionally create internal process deadlocks for no reason.&lt;/p&gt;</description>
          <pubDate>2017-03-22T18:33:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2017/03/22/goodbye-ecmascript-hello-ukmascript.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2017/03/22/goodbye-ecmascript-hello-ukmascript.html</guid>
        </item>
      
    
      
        <item>
          <title>It&apos;s a bug! It&apos;s a feature! It&apos;s… a limitation of the fundamental design of your test framework?</title>
          <description>&lt;p&gt;As some of you probably know, I&apos;m a big fan of &lt;a href=&quot;http://www.ncrunch.net/&quot;&gt;NCrunch&lt;/a&gt;. When I&apos;m coding in C#, NCrunch gets a CPU core and a whole screen to itself (yes, I don&apos;t really write code on a system that &lt;a href=&quot;https://twitter.com/dylanbeattie/status/832326857798348800/photo/1&quot;&gt;looks like this&lt;/a&gt;) and sits there quietly running all my tests, all the time, and telling me the second I break anything.&lt;/p&gt; &lt;p&gt;I&apos;m also a big fan of testing things that are as close to production behaviour as you can. Unit tests are great for informing the design of your components, but without integration testing you can&apos;t be sure they&apos;re actually going to work when you stick them together.&lt;/p&gt; &lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot; data-conversation=&quot;none&quot;&gt; &lt;p lang=&quot;und&quot; dir=&quot;ltr&quot;&gt;&lt;a href=&quot;https://twitter.com/ThePracticalDev&quot;&gt;@ThePracticalDev&lt;/a&gt; &lt;a href=&quot;https://twitter.com/testobsessed&quot;&gt;@testobsessed&lt;/a&gt; &lt;a href=&quot;https://t.co/uKvhzoEPF7&quot;&gt;pic.twitter.com/uKvhzoEPF7&lt;/a&gt;&lt;/p&gt;— George Dinwiddie (@gdinwiddie) &lt;a href=&quot;https://twitter.com/gdinwiddie/status/690726656449839104&quot;&gt;January 23, 2016&lt;/a&gt;&lt;/blockquote&gt; &lt;p&gt;&lt;script async src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;So on my current project, there&apos;s a suite of unit tests using FakeItEasy and assertions, and then a suite of integration tests that connect to the live API, follow the various hypermedia links, throw assorted JSON objects at the PUT and POST endpoints to see how they respond, and then call DELETE to clean up when they&apos;re done. And, just to keep us honest, we&apos;ve got a post-deploy step in our Octopus Deploy script that will actually run the integration test suite as part of the deployment process, and roll the whole thing back if any of the tests fail. Another small step on the road to truly continuous deployment. &lt;/p&gt; &lt;p&gt;Anyway. Last week, I push a release to our dev environment, and a whole load of tests fail. Which is weird, because it worked on MY machine. And it worked on my machine when I pointed my local codebase at the database in the dev environment. And – here&apos;s the fun part – it worked on my machine when I pointed the &lt;em&gt;entire test suite &lt;/em&gt;at the dev environment. So I start eliminating variables. One of the first things I pick up on is that my local test runner is NCrunch, whereas the post-deploy step is using nunit-console. So I run the local integration tests using nunit-console and – bang. Failures. Which is good, because I know what&apos;s causing the weirdness, but &lt;em&gt;weird&lt;/em&gt;, because tests are supposed to either pass or fail regardless of what test runner you&apos;re using.&lt;/p&gt; &lt;p&gt;So I dig a little deeper, and I end up with what looks to me like a bug in NCrunch. See, we&apos;re using the TestCaseSource attribute to generate test cases for the API tests, and – because all we need is a bunch of different JSON objects – we&apos;re just spinning up new anonymous objects and passing them in as test cases.&lt;/p&gt; &lt;p&gt;Here&apos;s two anonymous objects:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;var testCase1 = new { forenames = null, surname = &quot;Batman&quot; }&lt;br&gt;var testCase2 = new { forenames = String.Empty, surname = &quot;Batman&quot; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What I noticed is that if you generate these two test cases, NCrunch will only see them as a single test – which I assumed was because their ToString() representations are equal, because null and String.Empty both return String.Empty when you ToString() them in this situation. So I opened a post about it on the &lt;a href=&quot;http://forum.ncrunch.net/yaf_postst2060_NCrunch-is-ignoring-TestCaseSource-cases-where-the-test-case-ToString---is-non-unique.aspx&quot;&gt;NCrunch forums&lt;/a&gt;, even going so far as to suggest using GetHashCode() when enumerating test names, and got &lt;a href=&quot;http://forum.ncrunch.net/yaf_postsm9876_NCrunch-is-ignoring-TestCaseSource-cases-where-the-test-case-ToString---is-non-unique.aspx#post9876&quot;&gt;this really interesting response&lt;/a&gt; from &lt;a href=&quot;https://twitter.com/remcomulder?lang=en&quot;&gt;Remco Mulder&lt;/a&gt;, the NCrunch lead developer:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Tests must be uniquely identifiable between execution and discovery runs. This isn&apos;t important for a tool like the nunit console runner where a test can be discovered and executed within the same process call (and thus identified by its memory address), but for a tool like NCrunch, there&apos;s no way to run the test or collect data from it without this. As you&apos;ve identified, generated tests with a null parameter and an empty string will return the same result under .ToString(), so NCrunch can&apos;t tell them apart. &lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The only way to solve this is to change the design of your code. Try using the NUnit .SetName() method to give each of your generated tests a distinctive name.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Unfortunately .GetHashCode() is not a reliable solution to this problem as this method is not designed to generate the same identifier across different processes. This method returns different results under x86 vs x64, and under .NET Core it will actually return an entirely different result for each process. Because your code is responsible for generating the tests, the problem can only be solved within your own code.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;I thought this was a really interesting insight into how a tool like NCrunch has to deal with situations that an in-process test runner like nunit-console will probably never encounter. It also turns out I’d dismissed that very warning a few weeks earlier – when it cropped up in response to an unrelated issue which produced the same symptons – and sure enough, after clicking the “Show all hidden warnings” button on the NCrunch toolbar, the warning popped back up – along with a very detailed explanation of what was causing it:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-AfkuRzT19Ls/WMLP8ECTHdI/AAAAAAAAEVo/SC5qALGtC8s/s1600-h/image%25255B19%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;https://lh3.googleusercontent.com/-gph4CxYGtvI/WMLP8uGjZ6I/AAAAAAAAEVs/pOOoygNV2HA/image_thumb%25255B11%25255D.png?imgmax=800&quot; width=&quot;675&quot; height=&quot;736&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Plus, I had no idea that NUnit has a &lt;a href=&quot;http://TestCaseData&quot;&gt;TestCaseData&lt;/a&gt; interface with a SetName() method on it, which gives a much nicer way of presenting these test cases in both NCrunch and NUnit. I&apos;ve ended up with something akin to:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;public static IEnumerable TestData() {
  foreach (var data in new[] { null, String.Empty }) {
    var testCase = new { forenames = data, surname = &quot;Batman&quot; };
    var json = JsonConvert.SerializeObject(testCase);
    yield return new TestCaseData(testCase).SetName(json);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Oh, and if you&apos;re interested, the deployment failures were because of a weird validation rule that treats &lt;strong&gt;null&lt;/strong&gt; as missing, which is fine, but String.Empty as an empty string which violates a string length constraint. Which is wrong, and now the API doesn&apos;t do it any more. This is just another reason why integration testing is a good idea. So there you have it – a bug that wasn’t a bug, a crash-course in how NUnit and NCrunch actually work behind the scenes, and a TIL for naming your NUnit tests explicitly. Happy Friday.&lt;/p&gt;</description>
          <pubDate>2017-03-07T14:26:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2017/03/07/its-bug-its-feature-its-limitation-of.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2017/03/07/its-bug-its-feature-its-limitation-of.html</guid>
        </item>
      
    
      
        <item>
          <title>Progressive.NET 2017 : Call for Themes</title>
          <description>&lt;p&gt;In September, SkillsMatter will be hosting the eighth annual &lt;a href=&quot;https://skillsmatter.com/conferences/8268-progressive-dot-net-tutorials-2017&quot;&gt;Progressive.NET Tutorials&lt;/a&gt;. Over the next few months, the programme committee – including me – will be working to create a line-up of themes, workshops, talks and speakers that reflects the state of the art in .NET here in 2017. We’ll be opening our call for papers next month, but before we do, we’d like your help. Yes, you!&lt;/p&gt;&lt;a title=&quot;Prog .NET Tutorials&quot; href=&quot;https://www.flickr.com/photos/skillsmatter/27733196462/&quot; data-flickr-embed=&quot;true&quot;&gt;&lt;img alt=&quot;Prog .NET Tutorials&quot; src=&quot;https://c1.staticflickr.com/8/7223/27733196462_4fdf87663e.jpg&quot; width=&quot;500&quot; height=&quot;333&quot;&gt;&lt;/a&gt;&lt;script async src=&quot;//embedr.flickr.com/assets/client-code.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;  &lt;p&gt;What sets the Progressive.NET Tutorials apart from most conferences is our emphasis on deep-dive half-day workshops. Something between a normal conference talk and a full training course, the idea is that you go away afterwards with running code, on your own laptop, that you’ve written during the workshop and can refer back to when you’re trying to implement the things you’ve learned. Over the years we’ve introduced dozens of new ideas and technologies to the wider .NET community – from technologies like &lt;a href=&quot;https://skillsmatter.com/skillscasts/534-f-tutorial&quot;&gt;F#&lt;/a&gt;, &lt;a href=&quot;https://skillsmatter.com/skillscasts/527-nhibernate-workshop-part-ii&quot;&gt;NHibernate&lt;/a&gt; and &lt;a href=&quot;https://skillsmatter.com/skillscasts/530-openrasta-an-mvc-framework-with-strong-opinions&quot;&gt;OpenRasta&lt;/a&gt;, to patterns like &lt;a href=&quot;https://skillsmatter.com/skillscasts/8280-predicting-the-future-as-a-service-with-azure-ml&quot;&gt;machine learning&lt;/a&gt;, &lt;a href=&quot;https://skillsmatter.com/skillscasts/5137-event-store&quot;&gt;event sourcing&lt;/a&gt; and &lt;a href=&quot;https://skillsmatter.com/skillscasts/5111-automated-deployments-tutorial-with-octopus-deploy&quot;&gt;continuous deployment&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;We’ve got loads of ideas for themes, tracks and workshops this year, but we’d like your input. What do you want to see? What’s “progressive” in your corner of the .NET ecosystem? Some of the themes we’re already talking about are:&lt;/p&gt; &lt;h4&gt;.NET on Linux in Production&lt;/h4&gt; &lt;p&gt;OK, so your .NET Core application runs on Linux – awesome. What else do you need to know? Security? Configuration management? Monitoring, infrastructure? What about tools like Nginx, HAProxy and Varnish? How can you combine the power of .NET Core runtime with the maturity and flexibility of the Linux platform?&lt;/p&gt; &lt;h4&gt;Contributing to .NET Core and Open Source&lt;/h4&gt; &lt;p&gt;.NET Core is now part of a rich ecosystem of open source projects, but even for experienced developers, the journey from using open source to actually contributing can be daunting. Want to learn more about contributor licenses, workflows, issues and how to find your way around an unfamiliar codebase? &lt;/p&gt; &lt;h4&gt;Cloud Native and Serverless&lt;/h4&gt; &lt;p&gt;Ten years ago we were talking about dumping physical servers for virtual servers… now we’re talking about getting rid of servers completely. Cloud native is a whole new world for app developers. 12-factor apps, microservices, API-first development and containerisation are changing the way we approach application development – and the “big three” cloud platforms - .NET Core. AWS Lambda, Google Cloud Functions and Azure Functions&amp;nbsp; -now all support running serverless code built with .NET Core 1.1. So what can you do with it? What’s involved in designing, implementing and deploying serverless and cloud native applications? &lt;/p&gt; &lt;h4&gt;Mobile, Desktop and Beyond&lt;/h4&gt; &lt;p&gt;At one extreme, we’re deploying microservice apps onto serverless infrastructure. At the other extreme, people running .NET on a wider range of devices than ever before. Xamarin gives us a true cross-platform development toolchain for building native apps for Android and iOS devices. Libraries like Unity are helping C# developers build virtual worlds, from interactive data visualisation tools to launching Kerbals into space. HoloLens, Kinect and the latest generation of VR headsets are letting us interact with applications in all sorts of unprecedented ways, and with .NET Core and Windows Nano Server, we’re even seeing .NET running on the Internet of Things. &lt;/p&gt; &lt;p&gt;&lt;a title=&quot;Prog .NET Tutorials&quot; href=&quot;https://www.flickr.com/photos/skillsmatter/27805457136/in/album-72157669126478460/&quot; data-flickr-embed=&quot;true&quot;&gt;&lt;img alt=&quot;Prog .NET Tutorials&quot; src=&quot;https://c1.staticflickr.com/8/7629/27805457136_bb686aea42.jpg&quot; width=&quot;500&quot; height=&quot;333&quot;&gt;&lt;/a&gt;&lt;script async src=&quot;//embedr.flickr.com/assets/client-code.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;&lt;/p&gt; &lt;h4&gt;Agree? Disagree? Did we miss anything?&lt;/h4&gt; &lt;p&gt;What do you think? What do YOU want to see? Akka.NET? Hexagonal architecture? ES.Next? What would you love to spend half-a-day learning about – discussing principles and patterns, asking questions, and going away with running code on your laptop that you can refer back to? &lt;/p&gt;&lt;a title=&quot;Prog .NET Tutorials&quot; href=&quot;https://www.flickr.com/photos/skillsmatter/27778601062/in/album-72157669126478460/&quot; data-flickr-embed=&quot;true&quot;&gt;&lt;img alt=&quot;Prog .NET Tutorials&quot; src=&quot;https://c1.staticflickr.com/8/7411/27778601062_9b9dfd7b0f.jpg&quot; width=&quot;500&quot; height=&quot;333&quot;&gt;&lt;/a&gt;&lt;script async src=&quot;//embedr.flickr.com/assets/client-code.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt; &lt;p&gt;Comment here, find me on Twitter (&lt;a href=&quot;https://twitter.com/dylanbeattie&quot;&gt;@dylanbeattie&lt;/a&gt;), &lt;a&gt;drop me an email&lt;/a&gt;, or come and say hi at the &lt;a href=&quot;https://www.meetup.com/London-NET-User-Group/events/237160715/&quot;&gt;next London.NET User Group meetup&lt;/a&gt;, and let me know what you think. And let’s make this the best Progressive.NET Tutorials yet.&lt;/p&gt;</description>
          <pubDate>2017-02-22T14:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2017/02/22/progressivenet-2017-call-for-themes.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2017/02/22/progressivenet-2017-call-for-themes.html</guid>
        </item>
      
    
      
        <item>
          <title>Bring back alt.NET? But… why?</title>
          <description>&lt;p&gt;Pull up a chair, dear reader. I’m going to tell you why &lt;em&gt;I&lt;/em&gt; think we should revive alt.NET, but first, we need to establish a little context, which means it’s time for a good old-fashioned origin story. &lt;p&gt;I’ve been building web apps professionally since before they were called web apps, since the days when IIS was part of the Windows NT 4 Option Pack. I cut my teeth writing Active Server Pages, although unlike most of the ASP crowd, I wrote mine in JScript. That’s right, kids — I was running JavaScript on web servers back when The Matrix didn’t have any sequels. And I &lt;em&gt;loved&lt;/em&gt; it. The web was a simpler place in those days, and JScript ASP provided a lightweight, expressive language and runtime that did 99% of everything I ever needed to do. But it was obvious that the Microsoft had a vision for the future of web development, and it wasn’t about JScript and what we now call classic ASP. &lt;p&gt;I stuck it out with JScript and ASP for a long while, but eventually a project came along that clearly required something with a bit more oomph. Multi-tenancy, internationalisation, that kind of thing. So I brushed up on my C# chops and started building WebForms. C# was — and is — a truly wonderful language to work in, but WebForms? Not even close. I was banging my head against a wall, battling with the .NET framework daily to deliver even the most basic features. The elegant, expressive programming models provided by HTTP and HTML were gone, hidden beneath endless layers of leaky abstractions and fragile redirection. I was unhappy and I was unproductive, but it was incredibly easy to rationalise the misery as a necessary part of the learning curve. It didn’t help that the few developers I discussed it with seemed to be using ASP.NET quite happily and didn’t see any problem at all with the idea of &amp;lt;form runat=”server” /&amp;gt; and OnItemDataBound. &lt;p&gt;So here’s the scene. It’s 2007, I’m fed up, I haven’t shipped any working code for literally months, and — probably via &lt;a href=&quot;https://www.hanselman.com/blog/ScottGuMVCPresentationAndScottHaScreencastFromALTNETConference.aspx&quot;&gt;Scott Hanselman’s blog&lt;/a&gt; — I start hearing noises about something called “alt dot net”. Now, this sounded exciting. Unfortunately, many of the original posts and articles have been lost to the mists of time and website redesigns, but the original movement defined itself as: &lt;blockquote&gt;What is ALT.NET?&lt;/blockquote&gt; &lt;blockquote&gt;1. You’re the type of developer who uses what works while keeping an eye out for a better way.&lt;/blockquote&gt; &lt;blockquote&gt;2. You reach outside the mainstream to adopt the best of any community: Open Source, Agile, Java, Ruby, etc.&lt;/blockquote&gt; &lt;blockquote&gt;3. You’re not content with the status quo. Things can always be better expressed, more elegant and simple, more mutable, higher quality, etc.&lt;/blockquote&gt; &lt;blockquote&gt;4. You know tools are great, but they only take you so far. It’s the principles and knowledge that really matter. The best tools are those that embed the knowledge and encourage the principles&lt;/blockquote&gt; &lt;p&gt;To somebody drowning in the prescribed chaos of ASP.NET WebForms, that sounded like a pretty attractive set of principles. &lt;p&gt;Now, before we go any further, I’d like to share two things by way of qualifying the rest of this post. Firstly, &lt;strong&gt;I’m talking very specifically here about the alt.NET movement as it happened here in the UK, &lt;/strong&gt;and why it mattered to me, personally, as a developer. Alt.NET as an international movement generated a lot of interest, a lot of opinions and a lot of controversy, and yes, to some extent we’re reviving the alt.NET “brand” because this very controversy has given it a degree of recognition among the tech community that transcends geography and specialisation. But here in the UK, I think alt.NET caught just the right people, at just the right time, and I believe many of those people found it to be a really positive thing to be part of. &lt;p&gt;Second, &lt;strong&gt;I’m well aware that it’s not all smooth sailing&lt;/strong&gt; — to highlight one recent example, last week’s &lt;a href=&quot;https://blog.jetbrains.com/dotnet/2017/02/15/rider-eap-17-nuget-unit-testing-build-debugging&quot;&gt;post from JetBrains&lt;/a&gt; about the licensing dispute with a Microsoft debugging component they were using in Rider generated a lot of controversy. I know people on both sides of that particular exchange, and until we have a bit more clarification about exactly what’s happened, all I’ll say is that having multiple vendors working on commercial IDEs, targeting an open source .NET Core platform, is somewhat unprecedented, and there’s bound to be the odd bump along the way. &lt;p&gt;OK, back to alt.NET. In February 2008, the first alt.NET UK ‘unconference’ took place here in London. For me, it was a revelation. It was about openly challenging an orthodoxy that had become so established it was easy to think it was completely non-negotiable. Instead of “you do Microsoft.NET or you do open source”, it was “find what works for you, and don’t be afraid to mix it up”. Here was a loose-knit community of developers who were cherry-picking the bits of .NET that they liked and happily ignoring the rest. Don’t like WebForms? Cool, let me show you FubuMVC and Monorail. Don’t like Windows? Check out the Mono project. Having nightmares about SOAP, WSDL and DISCO? Here, have a look at this thing called REST. Oh, and by the way, here’s a bunch of neat ideas from Ruby and Java and Haskell that you might be interested in. &lt;p&gt;It suddenly became apparent that over time “.NET” had become an umbrella term for a whole gamut of languages, frameworks and technologies; some of them were really quite good, some of them were pretty poor, but &lt;em&gt;you didn’t have to use all of them.&lt;/em&gt; &lt;p&gt;It was that first alt.NET UK conference that inspired me to start blogging — in fact, my &lt;a href=&quot;http://www.dylanbeattie.net/2008/02/reflections-on-altnetuk.html&quot;&gt;very first blog post ever&lt;/a&gt; was a write-up of the event. I stopped relying so much on MSDN documentation, and I started going to user group meetings. I stopped writing stored procedures (I know, right?) and started using ORM’s — Linq-to-SQL, NHibernate, Castle ActiveRecord, and always with a very definite mindset of “use what works, ship working code, but keep your eyes open for something better”. I never took the plunge with Monorail or Fubu, but when Microsoft came out with ASP.NET MVC, I found it a perfect antidote to the bloated misery of WebForms. I remember vividly a quote from a Scott Hanselman podcast: “you don’t need a Repeater control; you’ve got a for() loop.” — that was the ASP.NET MVC ethos, and I loved it. My team launched our first ASP.NET MVC project in August 2008, whilst it was still on RC3. By the time it went beta, we’d put nearly a million pounds in revenue through it. The WebForms project was put ‘on hold’, and we never went back to it. &lt;p&gt;There were three alt.NET UK conferences. I don’t remember exactly when the third one was — probably 2009? — but I remember &lt;a href=&quot;https://medium.com/@ICooper&quot;&gt;Ian Cooper&lt;/a&gt; saying at the time that there was unlikely to be another one any time soon. It felt like the right people had found each other, the conversations had started, the changes were happening. It wasn’t just in the UK, either. Dozens of blogs and meetup groups were spawned in the wake of alt.NET — a handful of them are still going, in &lt;a href=&quot;https://www.meetup.com/Melbourne-ALT-NET/&quot;&gt;Melbourne&lt;/a&gt; and &lt;a href=&quot;https://www.meetup.com/Sydney-Alt-Net/&quot;&gt;Sydney&lt;/a&gt;, &lt;a href=&quot;https://www.meetup.com/altnetfr/&quot;&gt;Paris&lt;/a&gt;, &lt;a href=&quot;https://www.meetup.com/nyaltnet/&quot;&gt;New York&lt;/a&gt;, and &lt;a href=&quot;http://brightonalt.net/&quot;&gt;Brighton&lt;/a&gt;. Twitter was taking off in a big way; StackOverflow (built on ASP.NET MVC) was changing the way developers asked for help and shared solutions. We did a thing, and it worked. &lt;p&gt;So here in 2017, why are we talking about doing it again? For me, the answer’s simple. Partly, it’s just the passage of time. For every developer who has loudly and publicly abandoned the .NET platform, there’s a company somewhere whose investment in .NET isn’t going away anytime soon — and in the years since alt.NET, thousands of Comp Sci graduates have left university, landed their first job and ended up working on .NET. Sure, a lot of them are probably happy just to show up, write code, get paid and go home — but I suspect there’s also a lot of them who will go on to do really great things, and who haven’t yet grasped the power and the flexibility of the platform they’re using, and the community that’s built up around it. &lt;p&gt;But more than that, I don’t want to see the post-alt .NET community become an echo chamber. The ideas that were radical a decade ago have ossified into “best practice”, and it’s time to kick things up again. My personal and professional investment in .NET runs deep, because I sincerely believe that it’s a platform and a community worth investing in. I really enjoy working in .NET. C# and JavaScript are my languages of choice. but I like the fact that I can explore F# and TypeScript without having to learn a whole new platform to go with them. I’m a big fan of — and very occasional contributor to — open-source libraries like &lt;a href=&quot;https://github.com/StackExchange/Dapper&quot;&gt;Dapper&lt;/a&gt;, &lt;a href=&quot;http://nancyfx.org/&quot;&gt;NancyFX&lt;/a&gt;, &lt;a href=&quot;http://www.newtonsoft.com/json&quot;&gt;Newtonsoft.Json&lt;/a&gt;, &lt;a href=&quot;https://github.com/shouldly/shouldly&quot;&gt;Shouldly&lt;/a&gt; and &lt;a href=&quot;https://github.com/moq/moq4&quot;&gt;Moq&lt;/a&gt;. I’m also involved in running the London.NET User Group, and lately it feels like we’re seeing a lot of familiar faces and retreading a lot of familiar ground. Which is comfortable, and reassuring — and, yes, it’s &lt;em&gt;fun&lt;/em&gt;; I love being a part of this community, and count the people I’ve met through it among my dearest friends. But I miss the cross-pollination, the fresh faces and the new ideas and that were such a vital part of alt.NET, and I want to see what we can do to reinvigorate that. &lt;p&gt;I want us to reach the junior developers — and aspiring future developers — who are looking around for a fast and free way to build their first web apps, and help them get started with .NET Core. I want to reach the people who have always dismissed .NET because they don’t want to run Windows and let them know about things like &lt;a href=&quot;https://www.visualstudio.com/vs/visual-studio-mac/&quot;&gt;Visual Studio for Mac&lt;/a&gt; and &lt;a href=&quot;https://www.jetbrains.com/rider/&quot;&gt;JetBrains Rider&lt;/a&gt;. I want to learn more about projects like &lt;a href=&quot;https://unity3d.com/unity&quot;&gt;Unity&lt;/a&gt;, and new platforms like &lt;a href=&quot;https://www.microsoft.com/microsoft-hololens/en-gb&quot;&gt;HoloLens&lt;/a&gt; — not just the “ooh, shiny!”, but to understand the patterns and principles that developers are using to create great products using these tools. And I’d like to reconnect with the people who have abandoned .NET in the years since that first wave, and say “hey! What are you working on? How’s it going? And whilst we’re chatting, have you seen what’s happened to .NET since you last looked at it?” &lt;p&gt;It’s 2017. &lt;a href=&quot;https://github.com/dotnet&quot;&gt;.NET Core is open source&lt;/a&gt;, &lt;a href=&quot;https://github.com/aspnet/EntityFramework&quot;&gt;Entity Framework is open source&lt;/a&gt; — even &lt;a href=&quot;https://github.com/OpenLiveWriter&quot;&gt;Windows Live Writer&lt;/a&gt; is open source. &lt;a href=&quot;https://msdn.microsoft.com/en-gb/commandline/wsl/about&quot;&gt;Bash runs on Windows&lt;/a&gt;, &lt;a href=&quot;https://www.microsoft.com/en-gb/sql-server/sql-server-vnext-including-Linux&quot;&gt;SQL Server runs on Linux&lt;/a&gt;, and Microsoft is doing some &lt;a href=&quot;http://www.techradar.com/reviews/surface-studio&quot;&gt;genuinely&lt;/a&gt; &lt;a href=&quot;https://www.microsoft.com/microsoft-hololens/en-gb&quot;&gt;innovative&lt;/a&gt; &lt;a href=&quot;https://www.microsoft.com/en-us/design/inclusive&quot;&gt;things&lt;/a&gt;. Visual Studio 2017 is right around the corner, and .NET is a free, fast, cross-platform development system that you can use to build just about anything. We’ve got cloud-native applications, running on hosting so cheap it’s basically free. We’ve got Universal Windows Apps running on phones, tablets, laptops, desktops and consoles. We’ve got Xamarin and .NET Core bringing .NET to Linux, Mac, iOS and Android. We’ve got VR headsets and 3D printers and autonomous drones and all sorts of fascinating and unprecedented ways to make our software do cool things… and I think it’s high time we broke down some barriers, shared our ideas and tried to restart some of those conversations. &lt;p&gt;&lt;em&gt;This essay is also published at &lt;/em&gt;&lt;a href=&quot;http:// https://medium.com/altdotnet/bring-back-alt-net-but-why-df6faf0f966b#.f0bckrbis&quot;&gt;&lt;em&gt;medium.com/altdotnet&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;</description>
          <pubDate>2017-02-20T12:50:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2017/02/20/bring-back-altnet-but-why.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2017/02/20/bring-back-altnet-but-why.html</guid>
        </item>
      
    
      
        <item>
          <title>Naming Things is Hard: Spotlight Edition</title>
          <description>Like most specialist industries, software is rife with mainstream English words that we’ve taken and misappropriated to mean something completely different. Show business is no different. The software team here at Spotlight sits smack-bang in the intersection between these two specialist fields, and so when we’re talking to our customers and product owners about the systems we build, it’s very important to understand the difference between typecasting and type casting, and exactly what sort of actor model we’re talking about. We therefore present this delightful “double glossary” of everyday terms that you’ll hear here at Spotlight Towers. Because as we all know, there’s only two hard problems in software: cache invalidation, naming things, and off-by-one errors.&lt;br /&gt;
&lt;h4&gt;
&lt;strong&gt;Actor&lt;/strong&gt;&lt;/h4&gt;
&lt;strong&gt;Software: &lt;/strong&gt;A mathematical model of concurrent computation that treats &quot;actors&quot; as the universal primitives of concurrent computation.&lt;br /&gt;
&lt;strong&gt;Showbiz&lt;/strong&gt;: A person whose profession is acting on the stage, in films, or on television.&lt;br /&gt;
&lt;h4&gt;
Agent&lt;/h4&gt;
&lt;strong&gt;Software: &lt;/strong&gt;A software agent is a computer program that acts for a user or other program in a relationship of agency&lt;br /&gt;
&lt;strong&gt;Showbiz: &lt;/strong&gt;A person who finds jobs for actors, authors, film directors, musicians, models, professional athletes, writers, screenwriters, broadcast journalists, and other people in various entertainment or broadcast businesses.&lt;br /&gt;
&lt;h4&gt;
Callback&lt;/h4&gt;
&lt;strong&gt;Software: &lt;/strong&gt;Any executable code that is passed as an argument to other code, which is expected to call back (execute) the argument at a given time.&lt;br /&gt;
&lt;strong&gt;Showbiz: &lt;/strong&gt;A follow-up interview or audition&lt;br /&gt;
&lt;h4&gt;
Casting&lt;/h4&gt;
&lt;strong&gt;Software&lt;/strong&gt;: Explicitly converting a variable from one type to another&lt;br /&gt;
&lt;strong&gt;Showbiz: &lt;/strong&gt;Employing actors to play parts in a film, play or other production. Also the act of doing same.&lt;br /&gt;
&lt;h4&gt;
Client&lt;/h4&gt;
&lt;strong&gt;Software: &lt;/strong&gt;The opposite of a server&lt;br /&gt;
&lt;strong&gt;Showbiz: &lt;/strong&gt;An actor, specifically in the context of the actor’s relationship with their agent or manager Internally at Spotlight we have both internal and external clients/customers&lt;br /&gt;
&lt;h4&gt;
Client Profile&lt;/h4&gt;
&lt;strong&gt;Software:&lt;/strong&gt; A subset of the .NET framework intended to run on mobile and low-powered devices&lt;br /&gt;
&lt;strong&gt;Showbiz: &lt;/strong&gt;An actor’s professional CV, as it appears on their agent&apos;s’ website or in various kinds of casting software and directories&lt;br /&gt;
&lt;h4&gt;
Double&lt;/h4&gt;
&lt;strong&gt;Software: &lt;/strong&gt;Primitive data type representing a floating-point number&lt;br /&gt;
&lt;strong&gt;Showbiz: &lt;/strong&gt;A performer who appears in place of another performer, i.e., as in a stunt.&lt;br /&gt;
&lt;h4&gt;
Mirror&lt;/h4&gt;
&lt;strong&gt;Software: &lt;/strong&gt;A copy of a system that updates from the original in near to real time, often a database or file storage system&lt;br /&gt;
&lt;strong&gt;Showbiz: &lt;/strong&gt;An optical device that helps a performer check they’ve applied their makeup correctly&lt;br /&gt;
&lt;h4&gt;
Principal&lt;/h4&gt;
&lt;strong&gt;Software: &lt;/strong&gt;Used in database mirroring to refer to the primary instance of the database&lt;br /&gt;
&lt;strong&gt;Showbiz: &lt;/strong&gt;A performer with lines.&lt;br /&gt;
&lt;h4&gt;
Production&lt;/h4&gt;
&lt;strong&gt;Software: &lt;/strong&gt;The live infrastructure and code environment&lt;br /&gt;
&lt;strong&gt;Showbiz: &lt;/strong&gt;A film, TV or stage show, such as a professional actor might list on their acting CV.&lt;br /&gt;
&lt;h4&gt;
REST&lt;/h4&gt;
&lt;strong&gt;Software: &lt;/strong&gt;Representational State Transfer - an architectural style used when building hypermedia APIs&lt;br /&gt;
&lt;strong&gt;Showbiz: &lt;/strong&gt;What actors do between jobs.&lt;br /&gt;
&lt;h4&gt;
Script&lt;/h4&gt;
&lt;strong&gt;Software: &lt;/strong&gt;A computer program written in a scripting language&lt;br /&gt;
&lt;strong&gt;Showbiz: &lt;/strong&gt;The written dialogue and directions for a play, film or show&lt;br /&gt;
&lt;h4&gt;
“Sequel”&lt;/h4&gt;
&lt;strong&gt;Software: &lt;/strong&gt;Standard pronunciation of SQL, referring to either the database query language. Also commonly refers to Microsoft’s SQL Server database product.&lt;br /&gt;
&lt;strong&gt;Showbiz: &lt;/strong&gt;A published, broadcast, or recorded work that continues the story or develops the theme of an earlier one.&lt;br /&gt;
&lt;h4&gt;
Server&lt;/h4&gt;
&lt;strong&gt;Software: &lt;/strong&gt;The opposite of a client&lt;br /&gt;
&lt;strong&gt;Showbiz: &lt;/strong&gt;Someone working as waiting staff in a restaurant. Who is quite possibly an actor moonlighting as a server to pay the bills between acting jobs.&lt;br /&gt;
&lt;h4&gt;
Spotlight&lt;/h4&gt;
&lt;strong&gt;Software: &lt;/strong&gt;The native macOS search application&lt;br /&gt;
&lt;strong&gt;Showbiz: &lt;/strong&gt;Our company – &lt;a href=&quot;http://www.spotlight.com/&quot;&gt;www.spotlight.com&lt;/a&gt;,&amp;nbsp; “The Home of Casting” – and the directories and services we have created since 1927. &lt;br /&gt;
&lt;h4&gt;
Staging&lt;/h4&gt;
&lt;strong&gt;Software: &lt;/strong&gt;A replica of a production hosting environment used to test new features and deployments.&lt;br /&gt;
&lt;strong&gt;Showbiz: &lt;/strong&gt;The method of presenting a play or dramatic performance; also used to refer to the stage structure itself in theatre and live performance.</description>
          <pubDate>2017-02-14T14:58:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2017/02/14/naming-things-is-hard-spotlight-edition.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2017/02/14/naming-things-is-hard-spotlight-edition.html</guid>
        </item>
      
    
      
        <item>
          <title>Semantic Versioning with Powershell, TeamCity and GitHub</title>
          <description>&lt;p&gt;Here at &lt;a href=&quot;https://www.spotlight.com/&quot;&gt;Spotlight Towers&lt;/a&gt;, we’ve been using TeamCity as our main build server since version 6; it’s a fantastic tool and we love it dearly. It got even better a few years back when we paired it with the marvellous &lt;a href=&quot;https://octopus.com/&quot;&gt;Octopus Deploy&lt;/a&gt;; TeamCity builds the code and creates a set of deployable packages known as Octopacks; Octopus deploys the packages, and everything works quite nicely. Well, almost everything. One of the few problems that TeamCity + Octopus doesn’t magically solve for us is versioning. In this post, we’re going to look at how we use Git and TeamCity to manage versioning for our individual packages.&lt;/p&gt; &lt;p style=&quot;border-top: #4488bb 2px solid; border-right: #4488bb 2px solid; border-bottom: #4488bb 2px solid; font-weight: bold; padding-bottom: 8px; padding-top: 8px; padding-left: 8px; border-left: #4488bb 2px solid; padding-right: 8px; background-color: #eeeeff&quot; align=&quot;center&quot;&gt;If this sounds like your sort of thing, why not come and work for us? That’s right – Spotlight is hiring! We&apos;re looking for developers, testers and a new UX/Web designer - check out &lt;a href=&quot;https://jobs.spotlight.com/&quot;&gt;jobs.spotlight.com&lt;/a&gt; and get in touch if you’re interested.&lt;/p&gt; &lt;p&gt;First, let’s establish some principles&lt;/p&gt; &lt;ul&gt; &lt;li&gt;We are going to respect the semantic versioning convention of MAJOR.MINOR.PATCH, as described at &lt;a href=&quot;http://semver.org/&quot;&gt;semver.org&lt;/a&gt;.  &lt;li&gt;Major and minor versions will be incremented manually. We trust developers to know whether their latest commit should be a new major or minor release according to semantic versioning principles.  &lt;li&gt;Building the same codebase from the same branch twice should produce the same semantic version number.  &lt;li&gt;Packages created from the &lt;strong&gt;master&lt;/strong&gt; branch are release packages.  &lt;li&gt;Packages created from a merge head of an open pull request are pre-release packages.  &lt;li&gt;Pre-release packages will use the version number that would be assigned if that branch was accepted for release at build time.&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;Now, here’s the part where we’re going to deviate from the semantic versioning specification, because our packages actually use a four-part version number. We want to include a &lt;strong&gt;build&lt;/strong&gt; number in our package versions, but the official semver extension for doing this – MAJOR.MINOR.PATCH&lt;strong&gt;+BUILD&lt;/strong&gt; - won’t work with NuGet, so we’re going to use a four-part version number MAJOR.MINOR.PATCH.BUILD. Pre-release packages will be appended with a suffix describing which branch they were built from – MAJOR.MINOR.PATCH.BUILD-BRANCH.&lt;/p&gt; &lt;p&gt;OK, here’s an illustrated example that demonstrates what we’re trying to achieve. Master branch is green. Two developers are working on feature branches – blue and red in this example. To create our pre-release builds, we’re using a little-documented but incredibly useful feature of GitHub known as ‘merge heads’. The idea is that if you have an open pull request, the merge head will give you a snapshot of the codebase that &lt;strong&gt;would be created by merging the open pull request into master&lt;/strong&gt; – so you’re not just testing your new feature in isolation, you’re actually building and testing your new feature &lt;strong&gt;plus the current state of the master branch&lt;/strong&gt;. There is one caveat to this, which I’ll explain below. &lt;/p&gt; &lt;p&gt;So, we’ve got TeamCity set up to build and publish packages every time there’s a commit to master or to the merge head of an open pull request, and we’re also occasionally triggering manual builds just to make sure everything’s hanging together properly. Here’s what happens:&amp;nbsp; &lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-LNOyZ0Oif5I/WInwgperRnI/AAAAAAAAETE/YAIUfUoQysQ/s1600-h/semantic%252520merging%252520500px.png&quot;&gt;&lt;img title=&quot;semantic merging 500px&quot; style=&quot;border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; border-left: 0px; display: block; padding-right: 0px; margin-right: auto&quot; border=&quot;0&quot; alt=&quot;semantic merging 500px&quot; src=&quot;https://lh3.googleusercontent.com/-6k5o1V3-208/WInwjoZ4aPI/AAAAAAAAETM/SS5zrii8saM/semantic%252520merging%252520500px_thumb.png?imgmax=800&quot; width=&quot;469&quot; height=&quot;766&quot;&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;That line there that’s highlighted in yellow is a gotcha. At this point in our workflow, we’ve merged PR1 into our master branch, but because we haven’t pushed anything to the blue branch since this happened, the blue merge head is out of date. PR2 does NOT reflect the latest changes to master, and if we trigger a build manually, we’ll end up with a package that doesn’t actually reflect the latest state of the codebase. The workaround is pretty simple; if you’re creating pre-release builds from merge heads, never run these builds manually; make sure you always trigger the build by pushing a change to the branch.&lt;/p&gt; &lt;p&gt;Now let’s look at how can we get TeamCity to automatically calculate those semantic version numbers whenever a build is triggered. We’ll start with the major and minor version. We’re going to track these by creating a &lt;strong&gt;version.txt &lt;/strong&gt;file in the root of the project codebase, which just contains the major and minor version numbers. If a developer decides that their feature branch represents a new major or minor version, it’s their responsibility to edit &lt;strong&gt;version.txt &lt;/strong&gt;as part of implementing the feature. This also means that prerelease packages built from that branch will reflect the new version number whilst master branches will continue to use the old version until the branch gets merged, which I think is rather elegant.&lt;/p&gt; &lt;p&gt;For the patch version, we’re going to assume that every commit or merge to the master branch represents a new patch version, according to the following algorithm&lt;/p&gt; &lt;ul&gt; &lt;li&gt;If the current version.txt represents a NEW major/minor version, the patch number is zero  &lt;li&gt;Otherwise, the patch number is the patch number of the latest release, incremented by the number of commits to the master branch since that release.&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;So – how do we know how many commits there have been since the last release? First, each time we build a release branch, we’re going to use Git tags to tag the repository with the version number we’ve just built. TeamCity will do this for you automatically using a &lt;strong&gt;build feature&lt;/strong&gt; called “VCS labeling”:&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-_pkE-xNbrns/WIih7yVi5AI/AAAAAAAAERU/wUgz7UCt7XY/s1600-h/image%25255B6%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;https://lh3.googleusercontent.com/-Nzy9Kjh4iRk/WIih8fsn3xI/AAAAAAAAERY/eRD8QtpQJ6o/image_thumb%25255B4%25255D.png?imgmax=800&quot; width=&quot;550&quot; height=&quot;150&quot;&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;Assuming every release has a corresponding tag, now we need to find the most recent release number, which we can do from the Git command line. &lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;git fetch –tags&lt;br&gt;git tag –sort=v:refname&lt;/font&gt;&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;Git tags aren’t retrieved by default, so we need to explicitly fetch them before listing them. Then we list all the tags, specifying &lt;a href=&quot;https://git-scm.com/docs/git-tag&quot;&gt;sort=v:refname&lt;/a&gt; which causes tag names to be treated as semantic versions when sorting. (Remember that semver sorting isn’t alphanumeric – in alphanumerics, v9 is higher than v12). Once we’ve got the latest tag, we need to count the number of revisions since that tag was created, which we can do using this syntax:&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;git rev-list v1.2.3..HEAD –count&lt;/font&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;font face=&quot;Consolas&quot;&gt;&lt;/font&gt; &lt;p&gt;To use this in our TeamCity build, we&apos;ll need to output the various different formats of that version so that TeamCity can use them. We want to do three things here:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Label the VCS root with the three-part semantic version number v1.2.3  &lt;li&gt;Update the AssemblyInfo.cs files with the four-part version number 1.2.3.456 – note that we can’t put any prerelease suffix in the AssemblyInfo version.  &lt;li&gt;Pass the full version – 1.2.3.456-pr789 – to Octopack when creating our deployable packages with Octopus.&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;I&apos;ve wrapped the whole thing up in a Powershell script which runs as part of the TeamCity build process, which is on GitHub:&lt;/p&gt;&lt;script src=&quot;https://gist.github.com/dylanbeattie/3a2fe5abca14600efee1c88009afc0f8.js&quot;&gt;&lt;/script&gt; &lt;p&gt;To use it in your project, add versions.ps1 to the root of your project repo; create a text file called version.txt which contains your major.minor version, and then add a TeamCity build step at the beginning of your build process that looks like this:&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-40opaGiyYLg/WIns840T6VI/AAAAAAAAES0/XmfHbGEPrC0/s1600-h/image%25255B7%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; display: block; padding-right: 0px; border-top-width: 0px; margin-right: auto&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;https://lh3.googleusercontent.com/-P_t6m_IFQ0g/WIns9POJU_I/AAAAAAAAES4/IxDcMnFhNYk/image_thumb%25255B4%25255D.png?imgmax=800&quot; width=&quot;520&quot; height=&quot;579&quot;&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;Finally, it’s worth mentioning that to use command-line git from Powershell, I had to set up TeamCity to use an SSH VCS root rather than HTTP, and install the appropriate SSH keys on the TeamCity build agent. I don&apos;t know whether this is a genuine requirement or a quirk of our configuration; your mileage may vary. And I still find Powershell infuriatingly idiosyncratic, but hey - you probably knew that already. :)&lt;/p&gt; &lt;p&gt;Happy versioning! And like I said, if this sort of thing sounds like something you’d like to work on, awesome - we’re hiring! Check out &lt;a href=&quot;https://jobs.spotlight.com/&quot;&gt;jobs.spotlight.com&lt;/a&gt; for more details and get in touch if you’re interested.&lt;/p&gt;</description>
          <pubDate>2017-01-26T11:46:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2017/01/26/semantic-versioning-with-powershell_26.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2017/01/26/semantic-versioning-with-powershell_26.html</guid>
        </item>
      
    
      
        <item>
          <title>Sharing a Dropbox folder using Bootcamp and exFAT</title>
          <description>&lt;p&gt;My main laptop for the last few years has been a 15” Retina MacBook Pro. As I’m primarily a .NET developer, I use &lt;a href=&quot;https://support.apple.com/en-gb/boot-camp&quot;&gt;Bootcamp&lt;/a&gt; to run Windows 10, Visual Studio and SQL Server, but I also boot into macOS almost daily for things like audio recording and video editing using Logic Pro and Adobe Premiere. I’m also a huge fan of &lt;a href=&quot;https://www.dropbox.com/&quot;&gt;Dropbox&lt;/a&gt; – I use &lt;a href=&quot;https://evernote.com/&quot;&gt;Evernote&lt;/a&gt; for text documents (notes, talk ideas and lyrics), and Dropbox for just about everything else.&lt;/p&gt; &lt;p&gt;My MacBook Pro has a 500Gb SSD, which sounds like a lot, but once you’ve got two operating systems it’s surprising how quickly you start running out of space – and one of my biggest frustrations was having two separate Dropbox folders. I have about 40Gb of stuff in Dropbox, which means a 40Gb Dropbox folder on my NTFS Windows partition, and another 4Gb Dropbox folder on the ExtFS macOS partition. And they store &lt;em&gt;exactly the same files&lt;strong&gt; – &lt;/strong&gt;&lt;/em&gt;so much so that Dropbox actually became my default sync mechanism for getting files from Windows into macOS and vice versa.&lt;/p&gt; &lt;p&gt;Being a huge nerd, I thought it would be fun to kick off 2017 by completely repaving my laptop, and I wondered if there was some way to store my Dropbox folder, and other big things like my music library, on a shared partition that both operating systems could use. It took a bit of tweaking but I think I’ve managed to get it working – here’s how. The secret sauce that made it all work is a &lt;a href=&quot;https://en.wikipedia.org/wiki/ExFAT&quot;&gt;relatively new file system called exFAT&lt;/a&gt;. The killer feature of exFAT here is that both Windows 10 and macOS can read and write it natively, but it doesn’t have any of the file size or volume size restrictions of older interoperable formats like FAT32. &lt;/p&gt; &lt;p&gt;&lt;strong&gt;&lt;font color=&quot;#ff0000&quot;&gt;Caveat: this is all completely unsupported.&lt;/font&gt;&lt;/strong&gt; If you try any of this and you lose all your files, I’m not going to help you – and neither are Apple, Microsoft or Dropbox. If you’re happy using batch scripts to toggle file attributes and manually partitioning your OS drive, then this lot might help – but messing around with disk partitions and unsupported filesystems is a good way to end up with an unbootable laptop and a lot of bad mojo. So before we go any further,&amp;nbsp; back up &lt;em&gt;everything&lt;/em&gt;. After all, Dropbox is a file sync tool – if some bizarre combination of macOS and exFAT results in it removing a load of files, the last thing I want is for Dropbox to conveniently remove the same files from all my other devices. I know it’s easy enough to revert to an older version of anything that’s in Dropbox, but I figured having a copy of it all on an external HD probably couldn’t hurt.&lt;/p&gt; &lt;p&gt;Next, work out how you want to partition your disk. In my case, I wanted to go for a ~150Gb partition for each operating system, and a 200Gb shared partition that both of them could see. Your mileage may vary, and – as I did – you might find you can’t get &lt;em&gt;exactly&lt;/em&gt; what you wanted, but I managed to get pretty close. Now do a completely clean reinstall of macOS. Give it the entire drive and let it do its thing. Once you’re done, use the Boot Camp Assistant to set up and install Windows. When Boot Camp Assistant asks how you’d like to partition your drive, make the Windows partition equivalent to the total size of your desired Windows partition plus your shared partition – so in this case, I temporarily had a 150Gb macOS partition and a 350Gb Windows partition. &lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-5sAQ4lcyb1Y/WHPyyLLL7GI/AAAAAAAAEPg/VPPY8qP5Ny8/s1600-h/Screen-Shot-2017-01-07-at-13.06.397.png&quot;&gt;&lt;img title=&quot;Screen Shot 2017-01-07 at 13.06.39&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;Screen Shot 2017-01-07 at 13.06.39&quot; src=&quot;https://lh3.googleusercontent.com/-KUZHb5lMRI4/WHPyyhWzavI/AAAAAAAAEPk/mI9p8OMKSJ0/Screen-Shot-2017-01-07-at-13.06.39_t.png?imgmax=800&quot; width=&quot;644&quot; height=&quot;451&quot;&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;Once you’ve partitioned the disk, install Windows as usual. You should end up with a regular Boot Camp system, that you can boot into macOS or Windows by holding down the alt key during system startup or by using the Boot Camp options in Windows. Now here’s the fun part. Once you’ve installed Windows, but before you install any other apps or files, you’re going to use the Windows disk utility to shrink that Windows partition down to 150Gb and then create a shared exFAT partition in the remaining space. Right-click the BOOTCAMP (C:) partition, click Shrink Volume...&lt;/p&gt; &lt;p align=&quot;center&quot;&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-6RgbWy-HZqU/WHPyzW3_lbI/AAAAAAAAEPo/BfV3UaENoSI/s1600-h/windows_disk_iutility5.png&quot;&gt;&lt;img title=&quot;windows_disk_iutility&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;windows_disk_iutility&quot; src=&quot;https://lh3.googleusercontent.com/-hRp_7u0zC_I/WHPyzt0AF9I/AAAAAAAAEPs/YR-RP8JF9ao/windows_disk_iutility_thumb3.png?imgmax=800&quot; width=&quot;644&quot; height=&quot;375&quot;&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;There’s some limit of the NTFS filesystem that means “you cannot shrink a volume beyond the point where any unmovable files are located”. Now I don’t know how much variance there is between Windows installations, but I couldn’t shrink my C: drive any smaller than 163Gb, despite the fact that the fresh Windows install only took up about 20Gb. For what I needed, this was close enough – if not, I’d have been looking for something that would let me defrag and reorganize the NTFS partition. Or reading up on how an SSD gets fragmented in the first place – I thought they were just big boxes of non-volatile solid-state memory?&lt;/p&gt; &lt;p&gt;Anyway. Next step was to format the new partition as exFAT, called it &lt;strong&gt;Shared&lt;/strong&gt;, and then reboot into macOS and make sure it was visible. I poked around a bit, created some text documents and other files, rebooted back into Windows… After an hour or so this all seemed to be working quite nicely. Next step was to see how Dropbox coped with it. Rather than throw it at my 40Gb Dropbox account, I set up a new Dropbox account, installed Dropbox on both macOS and Windows and signed into to both of them using the new account.&lt;/p&gt; &lt;p&gt;This is where things got a bit weird. Here’s my best hypothesis as to what’s happening.&lt;/p&gt; &lt;ol&gt; &lt;li&gt;macOS is &lt;a href=&quot;https://en.wikipedia.org/wiki/AppleSingle_and_AppleDouble_formats&quot;&gt;storing some additional metadata for every file&lt;/a&gt;. On a regular ExtFS filesystem, the metadata is stored somewhere in the filesystem itself. On an exFAT partition, it’s stored in a tiny hidden file (apparently known as an &lt;a href=&quot;https://en.wikipedia.org/wiki/AppleSingle_and_AppleDouble_formats&quot;&gt;AppleDouble&lt;/a&gt;) next to the original. Alongside foo.txt there’s now a 4k file called ._foo.txt. I don’t think this is a Dropbox thing, it just appears to be caused by running macOS on a non-ExtFS drive.&lt;/li&gt; &lt;li&gt;The Dropbox agent on macOS sees these extra files, thinks that they’re important, and uploads them to Dropbox. &lt;/li&gt; &lt;li&gt;The dot-underscore ._ files are then synced across all your other Dropbox devices – I can see them in the web interface as well as on my Windows partition.&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;If I wasn’t using Dropbox, the ._ files would still be there, but macOS marks them as hidden and Windows appears to respect this – doing a &lt;strong&gt;dir /a:h&lt;/strong&gt; in one of my exFAT folders will show them but by default they don’t appear in Windows Explorer. I figured this wasn’t a big deal – I could live with a bunch of hidden 4K files – so I removed the temporary Dropbox account and installed the real one. This may have been a bit overly optimistic. Dropbox happily synced 40Gb of files from Windows, but when I rebooted into macOS, it created the ._ files for all of them… and then when I rebooted into Windows again, it started trying to sync all the hidden ._ files. After twelve hours of this, I concluded that something wasn’t working quite as I’d hoped, so I modified the plan slightly.&lt;/p&gt; &lt;p&gt;I realized that I don’t actually need the Dropbox agent running on both OSes – just having the shared Dropbox folder is enough – so I uninstalled the Dropbox agent from macOS, and things suddenly got a lot more straightforward. The dot-underscore files are still there. On the exFAT shared partition they’re hidden by default, but Dropbox helpfully syncs all the ._ files into all my other Windows systems and so I’ve created&amp;nbsp; a batch script in my Dropbox folder that finds any of these and sets them to hidden so they don’t show up in Windows Explorer. It’s a one-liner:&lt;/p&gt; &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;&lt;strong&gt;for /r %1 in (._*) do attrib +h &quot;%1&quot;&lt;/strong&gt;&lt;/font&gt;&lt;/p&gt; &lt;p&gt;The last thing I wanted to try before doing it for real was to see how VMWare Fusion coped with the shared partition. As well as dual-booting between macOS and Windows, I’ll also occasionally use VMWare to run the Windows partition as a VM inside macOS, which works pretty well. Here’s what I discovered:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;As soon as you boot the Windows VM, both the BOOTCAMP partion and the Shared partition disappear from macOS.  &lt;li&gt;The BOOTCAMP partition is mounted in VMWare as drive C: (obviously), and the Shared partition appears in the VM as drive D:  &lt;li&gt;The Dropbox agent will immediately pick up and sync any files created from macOS  &lt;li&gt;When I shut down the VM, the BOOTCAMP and Shared partitions both show up in macOS again and everything’s back to normal.&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;So, here’s the final setup I ended up with. Windows 10 is running the Dropbox agent, and syncing files with my Dropbox cloud storage as intended. The Dropbox folder is on the shared exFAT partition, so macOS can read and write my local Dropbox folder, but macOS doesn’t sync anything. If I add files to Dropbox when running macOS, when I reboot into Windows again, the sync agent will pick up these new files and sync them to the cloud. Finally, if I’m in macOS and find I need a file that’s been added to Dropbox from another device and hasn’t synced yet, I can either reboot into Windows and sync it, fire up the VMWare machine for a few moments, or just download it via the web interface.&lt;/p&gt; &lt;p&gt;I&apos;ve been running it for a couple of weeks, including a week of wrangling PowerPoint slides and shared files at NDC London, and it&apos;s working really quite nicely. And it&apos;s really rather nice to have nearly 200Gb of free disk space after installing both operating systems, all my apps and utilities, and 40Gb worth of Dropbox files.&lt;/p&gt;</description>
          <pubDate>2017-01-09T20:30:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2017/01/09/sharing-dropbox-folder-using-bootcamp.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2017/01/09/sharing-dropbox-folder-using-bootcamp.html</guid>
        </item>
      
    
      
        <item>
          <title>My Life with the Microsoft Natural Keyboard</title>
          <description>A moment ago I was catching up on Twitter and I saw this:&lt;br /&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&gt;
&lt;div dir=&quot;ltr&quot; lang=&quot;en&quot;&gt;
I&apos;m sitting here trying NOT to buy this keyboard. It looks amazing. &lt;a href=&quot;https://t.co/Es70Ed7tU1&quot;&gt;https://t.co/Es70Ed7tU1&lt;/a&gt;&lt;/div&gt;
— Scott Hanselman (@shanselman) &lt;a href=&quot;https://twitter.com/shanselman/status/816355299493691392&quot;&gt;January 3, 2017&lt;/a&gt;&lt;/blockquote&gt;
Now Scott has written some &lt;a href=&quot;http://www.hanselman.com/blog/DasKeyboard.aspx&quot;&gt;great posts&lt;/a&gt; about &lt;a href=&quot;http://www.hanselman.com/blog/DasKeyboardTheNextGeneration.aspx&quot;&gt;keyboards&lt;/a&gt; – and &lt;a href=&quot;http://www.hanselman.com/blog/FindingThePerfectMouse.aspx&quot;&gt;mice&lt;/a&gt;, and &lt;a href=&quot;http://www.hanselman.com/blog/BrainBytesBackBunsTheProgrammersPriorities.aspx&quot;&gt;workstation ergonomics in general&lt;/a&gt; – over the years, so I immediately clicked the link to see what was so exciting… and wow. This is the new Microsoft Surface Ergonomic Keyboard, and I, too, am now sitting here trying not to buy it. Actually, I’m waiting for a UK layout, and then I suspect that trying not to buy it will rapidly become unmanageable. I may even fail to not buy two of them so I have one at work and one at home.&lt;br /&gt;
&lt;img alt=&quot;Top view of keyboard&quot; height=&quot;318&quot; src=&quot;https://dri1.img.digitalrivercontent.net/Storefront/Company/msintl/images/English/en-INTL-Surface-Cosmos-3RA-00022/en-INTL-XL-Surface-Cosmos-3RA-00022-mnco.jpg&quot; width=&quot;560&quot; /&gt;&lt;br /&gt;
Now, there’s a couple of things about this keyboard that are interesting only because Microsoft have consistently got them right, and then messed them up, and then got them right again, and then messed them up again - over many, many years. Like an even-numbered Star Trek movie or an odd-numbered version of CorelDraw, this latest one is a good one – and as somebody who’s used every incarnation of the Microsoft Natural keyboard, this seems like a nice opportunity to take a little wander down memory lane. &lt;br /&gt;
OK, cue the Wayne’s World dream-sequence time-travel effects… and here’s a keyboard. A 102-key IBM PS/2 keyboard, which was the standard layout for PC keyboards for about a decade, back in the days when keyboards were beige and the cool kids knew all sorts of tricks that would let you load HIMEM.SYS &lt;em&gt;and&lt;/em&gt; your mouse driver and still have enough main memory left to run Wing Commander 2.&lt;br /&gt;
&lt;img alt=&quot;IBM Model M.png&quot; height=&quot;237&quot; src=&quot;https://upload.wikimedia.org/wikipedia/commons/4/48/IBM_Model_M.png&quot; width=&quot;560&quot; /&gt;&lt;em&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;(IBM PS/2 keyboard by &lt;/span&gt;&lt;/em&gt;&lt;a class=&quot;new&quot; href=&quot;https://commons.wikimedia.org/w/index.php?title=User:Raymangold22&amp;amp;action=edit&amp;amp;redlink=1&quot; title=&quot;User:Raymangold22 (page does not exist)&quot;&gt;&lt;em&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;Raymangold22&lt;/span&gt;&lt;/em&gt;&lt;/a&gt;&lt;em&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt; via Wikipedia | &lt;/span&gt;&lt;/em&gt;&lt;a href=&quot;http://creativecommons.org/publicdomain/zero/1.0/deed.en&quot; title=&quot;Creative Commons Zero, Public Domain Dedication&quot;&gt;&lt;em&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;CC0&lt;/span&gt;&lt;/em&gt;&lt;/a&gt;&lt;em&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt; | &lt;/span&gt;&lt;/em&gt;&lt;a href=&quot;https://commons.wikimedia.org/w/index.php?curid=37410391&quot;&gt;&lt;em&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;Link&lt;/span&gt;&lt;/em&gt;&lt;/a&gt;&lt;em&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;)&lt;/span&gt;&lt;/em&gt; &lt;br /&gt;
Around the time of Windows 95, Microsoft proposed the subtle addition of a two new keys – a &lt;a href=&quot;https://en.wikipedia.org/wiki/Windows_key&quot;&gt;Windows key&lt;/a&gt; (actually two of them) in those handy gaps next to the Ctrl keys, and a key that I’ve just this second learned is called the &lt;a href=&quot;https://en.wikipedia.org/wiki/Menu_key&quot;&gt;menu key&lt;/a&gt;, which is basically a right-click key. Other than shortening the space bar slightly, these new keys didn’t really move things around much, which was great, because that hard-earned muscle memory that let you hit triple-key combinations without taking your eyes off the screen worked just fine.&lt;br /&gt;
Oh, and they also started making them in colours that weren’t beige, which was a big improvement if you actually had to share your living space with them.&lt;br /&gt;
&lt;a href=&quot;https://lh3.googleusercontent.com/-A64CKukZtsQ/WGwdYZX1VDI/AAAAAAAAEN8/cen6Vq4eoCs/s1600-h/image%25255B5%25255D.png&quot;&gt;&lt;img alt=&quot;image&quot; border=&quot;0&quot; height=&quot;206&quot; src=&quot;https://lh3.googleusercontent.com/-DXPS6t0j3mg/WGwdYxcApqI/AAAAAAAAEOA/J-Oy4wmlSYo/image_thumb%25255B3%25255D.png?imgmax=800&quot; style=&quot;background-image: none; border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;&quot; title=&quot;image&quot; width=&quot;560&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
It’s around this time that I left school and got my first IT job, which meant I was typing for a good chunk of every day, and within a year I started getting unpleasant pain in my wrists and forearms. During a chance conversation at work, someone mentioned that a former employee there had had the same problem and switched to using an ergonomic keyboard – which was still knocking around in a cupboard somewhere. I tried it out and was instantly smitten.&lt;br /&gt;
This was the original Microsoft Natural keyboard, released in 1994.&lt;br /&gt;
&lt;div align=&quot;right&quot;&gt;
&lt;a href=&quot;https://lh3.googleusercontent.com/-2Notb7vpIaY/WGwdZpPqJ7I/AAAAAAAAEOE/tcadwYi14Bk/s1600-h/image%25255B10%25255D.png&quot;&gt;&lt;img alt=&quot;image&quot; border=&quot;0&quot; height=&quot;252&quot; src=&quot;https://lh3.googleusercontent.com/-SUeK1YxtIM0/WGwdaMfftEI/AAAAAAAAEOI/o81kz-3Ug60/image_thumb%25255B6%25255D.png?imgmax=800&quot; style=&quot;background-image: none; border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;&quot; title=&quot;image&quot; width=&quot;560&quot; /&gt;&lt;/a&gt;&lt;em&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;Photo by &lt;/span&gt;&lt;/em&gt;&lt;a class=&quot;new&quot; href=&quot;https://commons.wikimedia.org/w/index.php?title=User:DeanW77&amp;amp;action=edit&amp;amp;redlink=1&quot; title=&quot;User:DeanW77 (page does not exist)&quot;&gt;&lt;em&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;DeanW77&lt;/span&gt;&lt;/em&gt;&lt;/a&gt;&lt;em&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt; via Wikipedia – &lt;span class=&quot;int-own-work&quot; lang=&quot;en&quot;&gt;own work&lt;/span&gt;, &lt;/span&gt;&lt;/em&gt;&lt;a href=&quot;http://creativecommons.org/licenses/by-sa/4.0&quot; title=&quot;Creative Commons Attribution-Share Alike 4.0&quot;&gt;&lt;em&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;CC BY-SA 4.0&lt;/span&gt;&lt;/em&gt;&lt;/a&gt;&lt;em&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;, &lt;/span&gt;&lt;/em&gt;&lt;a href=&quot;https://commons.wikimedia.org/w/index.php?curid=49643109&quot;&gt;&lt;em&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;Link&lt;/span&gt;&lt;/em&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div align=&quot;left&quot;&gt;
I absolutely loved it. I literally wore it out – I used it until some of the keys no longer worked, and then went shopping for a replacement… and discovered you couldn’t get the original Natural keyboard anymore; it had been replaced by the Natural Keyboard Elite, released in 1998.&lt;/div&gt;
&lt;div align=&quot;left&quot;&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://1.bp.blogspot.com/-3tNBuDo5fhU/XQde9eAwHrI/AAAAAAAAHU0/zUm-x2gjnRUCA2yMtkUcgSh0UYLWEv0fwCLcBGAs/s1600/dims.jpeg&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;287&quot; data-original-width=&quot;470&quot; src=&quot;https://1.bp.blogspot.com/-3tNBuDo5fhU/XQde9eAwHrI/AAAAAAAAHU0/zUm-x2gjnRUCA2yMtkUcgSh0UYLWEv0fwCLcBGAs/s1600/dims.jpeg&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div align=&quot;left&quot;&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div align=&quot;right&quot;&gt;
&lt;img src=&quot;https://s.aolcdn.com/dims5/amp:6f55618099f5714d8e7e1e64ebdded92722da363/q:100/?url=http%3A%2F%2Fmedia.engadget.com%2Fimg%2Fproduct%2F2%2F214%2Fnatural-keyboard-elite-3fy.jpg&quot; style=&quot;display: block; float: none; margin-left: auto; margin-right: auto;&quot; /&gt;&lt;/div&gt;
&lt;div style=&quot;text-align: center;&quot;&gt;
&lt;a href=&quot;https://www.engadget.com/products/microsoft/natural-keyboard/elite/&quot; title=&quot;https://www.engadget.com/products/microsoft/natural-keyboard/elite/&quot;&gt;&lt;/a&gt;&lt;a href=&quot;https://www.engadget.com/products/microsoft/natural-keyboard/elite/&quot; title=&quot;https://www.engadget.com/products/microsoft/natural-keyboard/elite/&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;https://www.engadget.com/products/microsoft/natural-keyboard/elite/&lt;/span&gt;&lt;/a&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;text-align: center;&quot;&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div align=&quot;left&quot;&gt;
Looks close enough, right? Except if you look closely, you’ll see that instead of the familiar inverted-T cursor shape and two rows of navigation keys, the cursor keys are in a sort of weird diamond formation and the navigation keys are in two columns.&lt;/div&gt;
&lt;div align=&quot;left&quot;&gt;
This was horrible. All those years of muscle memory suddenly gone – every time you’d try to hit Ctrl-PgUp or Shift-End you’d get it wrong. And when you’re in the zone, that’s a horrible, jarring experience – every time it happens it interrupts your flow, wrenches you back to reality and makes you want to throw the damn thing across the room. Imagine driving a car where the brake pedal is above the accelerator instead of alongside it – it was a truly unpleasant experience.&lt;/div&gt;
&lt;div align=&quot;left&quot;&gt;
Fortunately, it didn’t take long for them to realise this one was a bit of a mistake. A year later in 1999, they came out with the Natural Keyboard Pro, and it was &lt;em&gt;fantastic&lt;/em&gt;. &lt;/div&gt;
&lt;div align=&quot;left&quot;&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div align=&quot;right&quot;&gt;
&lt;a href=&quot;https://lh3.googleusercontent.com/-w8sdZtpQA1Y/WGwdauaIpVI/AAAAAAAAEOM/ps0Kd8EgYPg/s1600-h/image%25255B15%25255D.png&quot;&gt;&lt;img alt=&quot;image&quot; border=&quot;0&quot; height=&quot;280&quot; src=&quot;https://lh3.googleusercontent.com/-SgVOT9COlcU/WGwdbBjmtEI/AAAAAAAAEOQ/3zwmyVMm9r0/image_thumb%25255B9%25255D.png?imgmax=800&quot; style=&quot;background-image: none; border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;&quot; title=&quot;image&quot; width=&quot;560&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;em&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;By --&lt;/span&gt;&lt;/em&gt;&lt;a class=&quot;extiw&quot; href=&quot;https://en.wikipedia.org/wiki/User:PCStuff&quot; title=&quot;en:User:PCStuff&quot;&gt;&lt;em&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;--PCStuff&lt;/span&gt;&lt;/em&gt;&lt;/a&gt;&lt;em&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt; via &lt;a href=&quot;https://en.wikipedia.org/wiki/Microsoft_Natural_keyboard#/media/File:MS_Natural_Keyboard_Pro.JPG&quot;&gt;Wikipedia&lt;/a&gt;, &lt;/span&gt;&lt;/em&gt;&lt;a href=&quot;http://creativecommons.org/licenses/by-sa/2.5&quot; title=&quot;Creative Commons Attribution-Share Alike 2.5&quot;&gt;&lt;em&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;CC BY-SA 2.5&lt;/span&gt;&lt;/em&gt;&lt;/a&gt;&lt;em&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;, &lt;/span&gt;&lt;/em&gt;&lt;a href=&quot;https://commons.wikimedia.org/w/index.php?curid=12053885&quot;&gt;&lt;em&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;Link&lt;/span&gt;&lt;/em&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div align=&quot;left&quot;&gt;
All the keys were in the right places, and it included a two-port USB hub which was great for plugging in your mouse. It added a bunch of “multimedia” buttons (which I never really used) and a dedicated button for launching the Windows Calculator (which actually proved to be surprisingly useful.) I loved this keyboard dearly. You can guess what happened next… yep, I used it until it wore out, went shopping for a new one, and… yeah. No more Natural Keyboard Pro. Instead, we had this delightful triumph of form over function:&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://1.bp.blogspot.com/-qGIGAHMHae0/XQdfTzFihlI/AAAAAAAAHU8/k92XukVhK6Q7rtWkMSYiwgVIvhC65BvtACLcBGAs/s1600/microsoft_natural_wireless.jpeg&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;246&quot; data-original-width=&quot;470&quot; height=&quot;167&quot; src=&quot;https://1.bp.blogspot.com/-qGIGAHMHae0/XQdfTzFihlI/AAAAAAAAHU8/k92XukVhK6Q7rtWkMSYiwgVIvhC65BvtACLcBGAs/s320/microsoft_natural_wireless.jpeg&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div align=&quot;left&quot;&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div align=&quot;right&quot;&gt;
&lt;br /&gt;&lt;a href=&quot;https://www.engadget.com/products/microsoft/wireless-natural-multimedia-keyboard/&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;photo via Engadget&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div align=&quot;left&quot;&gt;
The Microsoft Natural Multimedia Keyboard, released in 2004. OK, it’s got the classic inverted-T cursor keys (yay, but - look at that! Not only are the navigation keys all wrong, but the Insert key isn’t even there; we’ve got a massive double-height Delete key instead. If memory serves, Insert was relegated to some sort of funky combo involving the PrtSc key. Oh, and this was also the keyboard where you had to keep F-Lock switched on all the time because the function keys were actually some sort of weird keyboard shortcuts that nobody ever used ever. You know. Like a dedicated key for “Send”, because Ctrl-Enter is just too complicated. &lt;/div&gt;
&lt;div align=&quot;left&quot;&gt;
Again, it didn’t take long for them to come out with something better… and boy, did they ever get it right with the next one. The Microsoft Natural Ergonomic Keyboard 4000.&lt;/div&gt;
&lt;div align=&quot;center&quot;&gt;
&lt;span style=&quot;font-size: xx-small;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://compass.microsoft.com/assets/a4/61/a461dad3-3d63-4d93-9b2a-e4f334fcedf5.jpg?n=pop3.jpg&quot; height=&quot;312&quot; style=&quot;background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;&quot; width=&quot;560&quot; /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;span style=&quot;font-size: xx-small;&quot;&gt; &lt;/span&gt;&lt;br /&gt;
&lt;div align=&quot;right&quot;&gt;
&lt;span style=&quot;font-size: xx-small;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;a href=&quot;https://www.microsoft.com/accessories/en-us/products/keyboards/natural-ergonomic-keyboard-4000/b2m-00012&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;photo via microsoft.com&lt;/span&gt;&lt;/a&gt; &lt;br /&gt;
&lt;div align=&quot;left&quot;&gt;
This was my main keyboard for most of the last decade. I loved this keyboard so much I actually stockpiled it – one at work, one at home, and a few spares standing by in case they wore out. And wear out they did, one by one, until late last year I decided it was time to replenish the stockpile… which is when my long-suffering colleagues half-jokingly asked if, maybe, I’d be prepared to try something quieter. See, the 4000 is lovely, but it’s noisy. The keys have plenty of travel, with a nice satisfying thump at the bottom of each keystroke, and the keyboard’s casing – which is &lt;em&gt;big&lt;/em&gt; – makes a rather effective sounding-box. Until the bottom falls out of the London real estate market and we can justify private offices for our developers, FogCreek style, the open plan office is an unfortunate fact of life for most of us… and so, in the spirit of workplace harmony, I ordered a Microsoft Sculpt keyboard.&lt;/div&gt;
&lt;div align=&quot;right&quot;&gt;
&lt;a href=&quot;https://lh3.googleusercontent.com/-NDrFEBbgOgg/WGwfiFAVyzI/AAAAAAAAEOw/riHxU6Ye-lk/s1600-h/image%25255B27%25255D.png&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;&lt;img alt=&quot;image&quot; border=&quot;0&quot; height=&quot;352&quot; src=&quot;https://lh3.googleusercontent.com/-8wdivWQexMk/WGwfivi4_xI/AAAAAAAAEO0/K0WsTxur29k/image_thumb%25255B17%25255D.png?imgmax=800&quot; style=&quot;background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;&quot; title=&quot;image&quot; width=&quot;560&quot; /&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://www.microsoft.com/accessories/en-gb/products/keyboards/sculpt-ergonomic-desktop/l5v-00006&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;photo via Microsoft&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div align=&quot;left&quot;&gt;
And you know what? It’s pretty close to perfect. It’s comfortable. It’s quiet. It takes up about 60% of the desk space that the old Ergonomic 4000 did. The numeric keypad is actually separate, which I’m completely undecided about… when you actually want to type on it, it’s annoying not having it fixed in place, but being able to pick it up and use it like an old-school calculator is actually surprisingly useful. Except – yep – the navigation key layout is all screwed up. Again. I hit Insert by mistake all the time on this thing, and frequently land on the left cursor when I’m reaching for Ctrl.&lt;/div&gt;
&lt;div align=&quot;left&quot;&gt;
But with the release of the Surface keyboard at the top of the post, it looks like, yet again, there’s a truly great keyboard hot on the heels of the not-so-great one. I’m just really curious as to why they keep bringing out models that use non-standard keyboard layouts. &lt;/div&gt;
&lt;div align=&quot;right&quot;&gt;
&lt;a href=&quot;https://lh3.googleusercontent.com/-xN3tTbXSV50/WGwdcSrhwPI/AAAAAAAAEOc/wsRHyULzMPc/s1600-h/image%25255B25%25255D.png&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;&lt;img alt=&quot;image&quot; border=&quot;0&quot; height=&quot;191&quot; src=&quot;https://lh3.googleusercontent.com/-hOvBPHGtCQc/WGwdc4aZ7qI/AAAAAAAAEOg/bgPUGFQLBIk/image_thumb%25255B15%25255D.png?imgmax=800&quot; style=&quot;background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;&quot; title=&quot;image&quot; width=&quot;560&quot; /&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://www.microsoft.com/accessories/en-us/products/surface/surface-ergonomic-keyboard&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;photo via Microsoft&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div align=&quot;left&quot;&gt;
When it hits the market here in the UK, I’ll pick one up and let you know how I get on. Now, if they’d only release one that was &lt;a href=&quot;http://www.daskeyboard.com/daskeyboard-4-ultimate/&quot;&gt;completely black – with no key markings&lt;/a&gt; – then we’d &lt;em&gt;really&lt;/em&gt; be onto something. I wonder if you can &lt;a href=&quot;http://www.geek.com/xyzcomputing/spraypaint-keyboard-mod-571196/&quot;&gt;spray-paint it&lt;/a&gt;...&lt;/div&gt;
</description>
          <pubDate>2017-01-03T21:53:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2017/01/03/my-life-with-microsoft-natural-keyboard.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2017/01/03/my-life-with-microsoft-natural-keyboard.html</guid>
        </item>
      
    
      
        <item>
          <title>IdentityServer, OpenID Connect and Microsoft CRM Portals</title>
          <description>&lt;p&gt;As readers of this blog will know, here at Spotlight we’re in the process of moving nine decades’ worth of legacy business process onto Microsoft Dynamics CRM, aka CRM Online, which I gather is now called Dynamics 365 (because hey, it’s not like naming things was hard enough already, right?)&lt;/p&gt; &lt;p&gt;We’re also investigating a couple of options for building customer-facing systems that integrate with Dynamics. Until last year, there were really three options for this – a product called &lt;a href=&quot;https://www.adxstudio.com/&quot;&gt;Adxstudio&lt;/a&gt;, a free Microsoft component called the CRM Portal Accelerator, or rolling your own solution using the CRM SDK. Around this time last year, Microsoft quietly retired the Portal Accelerator component and acquired Adxstudio, and since then, they’ve been in the process of assimilating it into the Dynamics product family – which has meant it’s been something of a moving target, both in terms of the supported features and in terms of the quality of documentation and examples. &lt;/p&gt; &lt;p&gt;I’ve previously blogged about &lt;a href=&quot;http://www.dylanbeattie.net/2016/07/aspnet-authentication-with-adxstudio.html&quot;&gt;one way to integrate Adxstudio with your existing authentication system&lt;/a&gt;, but that approach relied completely on running Adxstudio on-premise so you could run your own code as part of the request lifecycle – and as you may have noticed, there’s a bit of a trend in IT at the moment away from running your own servers and towards using hosted managed services, so that patching and backups are somebody else’s problem. Since Microsoft acquired Adxstudio, there’s been a lot of churn around what’s supported and what’s not – I’m guessing that behind the scenes they’re going through the Adxstudio codebase feature-by-feature and making sure it lines up with their plans for the Dynamics 365 platform, but that’s just guesswork on my part. &lt;/p&gt; &lt;p&gt;One of the main integration points I’ve been waiting for is the ability for a Microsoft-hosted Portal solution to use a third-party OpenID Connect endpoint to authenticate users, and it appears in the latest update this is finally supported – albeit with a couple of bumps along the way. Here’s what I’ve had to do to get a proof-of-concept up and running.&lt;/p&gt; &lt;h4&gt;Setting up Dynamics CRM Portals&lt;/h4&gt; &lt;p&gt;First, you’ll need to set up a Dynamics Portal trial. You can get a 30-day hosted trial of Dynamics CRM Online by &lt;a href=&quot;https://www.microsoft.com/en-gb/dynamics/crm-free-trial-overview.aspx&quot;&gt;signing up here&lt;/a&gt; – this actually gives you a full Office 365 organization including things like hosted Active Directory, as well as the Dynamics CRM Online instance we’re using in this example. Next, you’ll need to ask nicely for a trial of the portal add-on – which you can do by filling out the form at &lt;a href=&quot;https://crmmanagedtrials.dynamics.com/&quot;&gt;crmmanagedtrials.dynamics.com&lt;/a&gt;. &lt;/p&gt; &lt;h4&gt;Setting up IdentityServer and configuring an ngrok tunnel&lt;/h4&gt; &lt;p&gt;Whilst you’re waiting for the nice Microsoft people to send you your trial license, get up and running with IdentityServer. For this prototype, I’m using the &lt;a href=&quot;https://github.com/IdentityServer/IdentityServer3.Samples/tree/master/source/MVC%20Authentication&quot;&gt;MVC Authentication&lt;/a&gt; example from the &lt;a href=&quot;https://github.com/IdentityServer/IdentityServer3.Samples&quot;&gt;IdentityServer3.Samples&lt;/a&gt; project – clone it to your workstation, open the MVC Authentication solution, hit F5, verify you can get up and running on localhost. &lt;/p&gt; &lt;p&gt;Next – in order for Dynamics CRM Online to talk to your IdentityServer instance, you’ll need to make your IdentityServer endpoints visible to the internet. You could do this by deploying your IdentityServer sample to Azure or AWS, but for experiments like this, I like to use a tool called &lt;a href=&quot;https://ngrok.com/&quot;&gt;ngrok&lt;/a&gt;, which will create temporary, secure tunnels from the internet to your workstation. &lt;a href=&quot;https://ngrok.com/download&quot;&gt;Download ngrok&lt;/a&gt;, unzip it somewhere sensible.&lt;/p&gt; &lt;p&gt;Pick a tunnel name. I’m using &lt;strong&gt;authdemo&lt;/strong&gt; in this example but any valid DNS host name will do. Next, create a local IIS application pointing to the EmbeddedMvc folder in your samples directory, and set the host name to &lt;strong&gt;&amp;lt;your tunnel name&amp;gt;.ngrok.io&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-tQaCEIG_qs4/WCoDqKnV9_I/AAAAAAAAEJw/ncn0mcGzbng/s1600-h/image%25255B12%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;https://lh3.googleusercontent.com/-ej3DqmphVmQ/WCoDsPw97NI/AAAAAAAAEJ0/Qa3Mj8Fut_Q/image_thumb%25255B6%25255D.png?imgmax=800&quot; width=&quot;585&quot; height=&quot;569&quot;&gt;&lt;/a&gt;&lt;br&gt;&lt;/p&gt; &lt;p&gt;Now run ngrok.exe to create a tunnel from the internet to your new IIS application:&lt;/p&gt; &lt;p&gt;C:\tools\ngrok&amp;gt; &lt;strong&gt;ngrok.exe http –subdomain=authdemo 80&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;p&gt;ngrok by @inconshreveable &lt;/p&gt;&lt;p&gt;
&lt;/p&gt;&lt;p&gt;Session Status&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; online&lt;br&gt;Version&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 2.1.18&lt;br&gt;Region&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; United States (us)&lt;br&gt;Web Interface&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; http://127.0.0.1:4040&lt;br&gt;Forwarding&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; http://authdemo.ngrok.io -&amp;gt; localhost:80&lt;br&gt;Forwarding&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; https://authdemo.ngrok.io -&amp;gt; localhost:80&lt;/p&gt;
&lt;p&gt;Connections&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ttl&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; opn&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; rt1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; rt5&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; p50&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; p90&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0.00&amp;nbsp;&amp;nbsp;&amp;nbsp; 0.00&amp;nbsp;&amp;nbsp;&amp;nbsp; 0.00&amp;nbsp;&amp;nbsp;&amp;nbsp; 0.00&lt;/p&gt;
&lt;p&gt;All being&lt;/p&gt;&lt;/pre&gt;
&lt;p&gt;If that’s worked, you should be able to fire up a browser, go to &lt;a href=&quot;http://authdemo.ngrok.io/&quot;&gt;http://authdemo.ngrok.io/&lt;/a&gt; – replacing ‘authdemo’ with your own tunnel name - and see the IdentityServer3 sample landing page:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-_v32DaBMwkQ/WCoDttFvrpI/AAAAAAAAEJ4/a8XZ3e_Cv0Q/s1600-h/image%25255B10%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;https://lh3.googleusercontent.com/-njyq2Swpj_E/WCoDuo96OvI/AAAAAAAAEJ8/AVopYpeEVNc/image_thumb%25255B4%25255D.png?imgmax=800&quot; width=&quot;640&quot; height=&quot;465&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;Configuring IdentityServer&lt;/h4&gt;
&lt;p&gt;Right. Next thing we need to do is to make a couple of changes to the IdentityServer configuration, so that it’ll run happily on authdemo.ngrok.io instead of on localhost&lt;/p&gt;
&lt;p&gt;First, &lt;a href=&quot;https://identityserver.github.io/Documentation/docsv2/configuration/logging.html&quot;&gt;enable logging&lt;/a&gt;. Just do it. Use the package manager console to install the Serilog.Sinks.Trace package. Then add this to the top of your Configuration() method inside Startup:&lt;/p&gt;&lt;pre&gt;Log.Logger = new LoggerConfiguration()
                .MinimumLevel.Debug()
                .WriteTo.Trace()
                .CreateLogger();
&lt;/pre&gt;
&lt;p&gt;and add this to your web.config, specifying a path that’s writable by the application pool:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;lt;system.diagnostics&amp;gt;
  &amp;lt;trace autoflush=&quot;true&quot;
         indentsize=&quot;4&quot;&amp;gt;
    &amp;lt;listeners&amp;gt;
      &amp;lt;add name=&quot;myListener&quot;
           type=&quot;System.Diagnostics.TextWriterTraceListener&quot;
           initializeData=&quot;&lt;strong&gt;&lt;font style=&quot;background-color: #ffff00&quot;&gt;C:\logfiles\identityserver.log&lt;/font&gt;&lt;/strong&gt;&quot; /&amp;gt;
      &amp;lt;remove name=&quot;Default&quot; /&amp;gt;
    &amp;lt;/listeners&amp;gt;
  &amp;lt;/trace&amp;gt;
&amp;lt;/system.diagnostics&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, do a global search and replace, replacing any occurrence of &lt;strong&gt;localhost:44319&lt;/strong&gt; with &lt;strong&gt;authdemo.ngrok.io&lt;/strong&gt; – again, substituting your own tunnel name as required. 
&lt;p&gt;Next, add a new client to the static EmbeddedMvc.IdentityServer.Clients class the IdentityServer sample project – changing the highlighted values to your own client ID, client secret, and portal instance URL:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;
new Client {
    ClientName = &quot;Dynamics CRM Online&quot;,
    ClientId = &quot;&lt;font style=&quot;background-color: #ffff00&quot;&gt;crm&lt;/font&gt;&quot;,
    Flow = Flows.Hybrid,
    ClientSecrets = new List&lt;secret&gt;() { new Secret(&quot;&lt;font style=&quot;background-color: #ffff00&quot;&gt;secret01&lt;/font&gt;&quot;.Sha256()) },
    RedirectUris = new List&lt;string&gt; { &lt;br&gt;      &quot;&lt;font style=&quot;background-color: #ffff00&quot;&gt;https://my-portal-instance.microsoftcrmportals.com&lt;/font&gt;&quot; &lt;br&gt;    },
    PostLogoutRedirectUris = new List&lt;string&gt; { &lt;br&gt;      &quot;&lt;font style=&quot;background-color: #ffff00&quot;&gt;https://my-portal-instance.microsoftcrmportals.com&lt;/font&gt;&quot; &lt;br&gt;    },
    AllowedScopes = new List&lt;string&gt; { &quot;openid&quot; }
},
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4&gt;Adding IdentityServer as an endpoint in CRM Portals&lt;/h4&gt;
&lt;p&gt;Finally, you need to &lt;a href=&quot;https://www.microsoft.com/en-us/dynamics/crm-setup-and-administration/open-id-connect-provider-settings-for-portals.aspx&quot;&gt;add your new IdentityServer as an identity provider&lt;/a&gt;. CRM Portals uses the Dynamics CRM platform for all its configuration and data storage, so to add new settings you’ll need to log into your Dynamics CRM Online instance, go into Portals &amp;gt; Site Settings, and add the following values:&lt;/p&gt;
&lt;table cellspacing=&quot;0&quot; cellpadding=&quot;4&quot; width=&quot;696&quot; border=&quot;1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;194&quot;&gt;
&lt;p&gt;&lt;b&gt;&lt;font size=&quot;2&quot;&gt;Name&lt;/font&gt;&lt;/b&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;334&quot;&gt;
&lt;p&gt;&lt;b&gt;&lt;font size=&quot;2&quot;&gt;Value&lt;/font&gt;&lt;/b&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;166&quot;&gt;
&lt;p&gt;&lt;b&gt;&lt;font size=&quot;2&quot;&gt;Website&lt;/font&gt;&lt;/b&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;194&quot;&gt;
&lt;p&gt;&lt;font size=&quot;2&quot;&gt;Authentication/OpenIdConnect/AuthDemo/Authority&lt;/font&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;334&quot;&gt;
&lt;p&gt;&lt;font size=&quot;2&quot;&gt;http://&lt;font style=&quot;background-color: #ffff00&quot;&gt;authdemo.ngrok.io&lt;/font&gt;/identity/&lt;/font&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;166&quot;&gt;
&lt;p&gt;&lt;font size=&quot;2&quot;&gt;Customer Self-Service&lt;/font&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;194&quot;&gt;
&lt;p&gt;&lt;font size=&quot;2&quot;&gt;Authentication/OpenIdConnect/AuthDemo/Caption&lt;/font&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;334&quot;&gt;
&lt;p&gt;&lt;font size=&quot;2&quot;&gt;IdentityServer OpenID Connect Demo&lt;/font&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;166&quot;&gt;
&lt;p&gt;&lt;font size=&quot;2&quot;&gt;Customer Self-Service&lt;/font&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;194&quot;&gt;
&lt;p&gt;&lt;font size=&quot;2&quot;&gt;Authentication/OpenIdConnect/AuthDemo/ClientId&lt;/font&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;334&quot;&gt;
&lt;p&gt;&lt;font style=&quot;background-color: #ffff00&quot; size=&quot;2&quot;&gt;crm&lt;/font&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;166&quot;&gt;
&lt;p&gt;&lt;font size=&quot;2&quot;&gt;Customer Self-Service&lt;/font&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;194&quot;&gt;
&lt;p&gt;&lt;font size=&quot;2&quot;&gt;Authentication/OpenIdConnect/AuthDemo/ClientSecret&lt;/font&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;334&quot;&gt;
&lt;p&gt;&lt;font style=&quot;background-color: #ffff00&quot; size=&quot;2&quot;&gt;secret01&lt;/font&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;166&quot;&gt;
&lt;p&gt;&lt;font size=&quot;2&quot;&gt;Customer Self-Service&lt;/font&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;194&quot;&gt;
&lt;p&gt;&lt;font size=&quot;2&quot;&gt;Authentication/OpenIdConnect/AuthDemo/MetadataAddress&lt;/font&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;334&quot;&gt;
&lt;p&gt;&lt;font size=&quot;2&quot;&gt;http://&lt;font style=&quot;background-color: #ffff00&quot;&gt;authdemo.ngrok.io&lt;/font&gt;/identity/.well-known/openid-configuration&lt;/font&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;166&quot;&gt;
&lt;p&gt;&lt;font size=&quot;2&quot;&gt;Customer Self-Service&lt;/font&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Finally, it looks like you’ll need to restart the portal instance to get it to pick up the updated values – which you can do by logging into the Office 365 Admin Center, Admin Centers, CRM, Applications, Portal Add-On, clicking ‘MANAGE’, and pressing the nice big RESTART button on the Portal Actions page:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-uu5KyKtAlJI/WCsUGP90UjI/AAAAAAAAEKU/smyrIBSSx70/s1600-h/image%25255B19%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;https://lh3.googleusercontent.com/-bo87xtSVI-0/WCsUHUBWGUI/AAAAAAAAEKY/q0B9UihT2IQ/image_thumb%25255B11%25255D.png?imgmax=800&quot; width=&quot;640&quot; height=&quot;344&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And – assuming everything lines up exactly right – you should now see an additional login button on your CRM Portals instance:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-OFTZfwQ5QsQ/WCsUIHRH6EI/AAAAAAAAEKc/5gqeMtXD25s/s1600-h/image%25255B28%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;https://lh3.googleusercontent.com/-QhedA-SNclQ/WCsUIlwCDjI/AAAAAAAAEKg/21_KrrvIEuw/image_thumb%25255B16%25255D.png?imgmax=800&quot; width=&quot;640&quot; height=&quot;406&quot;&gt;&lt;/a&gt;&lt;/p&gt;


&lt;p&gt;Clicking on it will bounce you across to your ngrok-tunnelled IdentityServer MVC app running on localhost:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-0pk4mHm85Gw/WCsUJZgUVtI/AAAAAAAAEKk/QogpAmvwW9c/s1600-h/image%25255B33%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;https://lh3.googleusercontent.com/-t-z5x8edELI/WCsUJ8l7nZI/AAAAAAAAEKo/Mg1E4P9El2E/image_thumb%25255B19%25255D.png?imgmax=800&quot; width=&quot;640&quot; height=&quot;406&quot;&gt;&lt;/a&gt;Log in as bob / secret, and you’ll get the OpenID permissions check:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-8W1zdynt5ns/WCsULE26nRI/AAAAAAAAEKs/B5UxdX_b9N4/s1600-h/image%25255B39%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;https://lh3.googleusercontent.com/-Nqwca61wKV4/WCsULsA9mRI/AAAAAAAAEKw/AIhan7e_lK0/image_thumb%25255B23%25255D.png?imgmax=800&quot; width=&quot;640&quot; height=&quot;406&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;…and when you hit ‘Yes, Allow’, you’ll be redirected back to the CRM Portals instance, which will create a new CRM Contact linked to your OpenID Connect identity, and log you in to the portal. &lt;/p&gt;
&lt;h4&gt;Conclusions&lt;/h4&gt;
&lt;p&gt;Of course, in the real world there’s a lot more to it than this – there is a huge difference between a proof of concept like this and a production system. These sorts of user journeys form such a key part of delivering great user experience, and integrating multiple systems into your login and authentication/authorization journeys only makes this harder. But it did work, and it wasn’t actually all that complicated to get it up and running. It’s also interesting to see how something like OpenID Connect can be used to integrate a powerful open-source solution like IdentityServer with a heavyweight hosted platform service like CRM Portals.&lt;/p&gt;
&lt;p&gt;Whether we end up adopting a hosted solution like CRM Portals – as opposed to just building our own apps that connect to CRM via the SDK or the new OData API – remains to be seen, but it’s nice to see solutions from two radically different sources playing nicely together thanks to the joy of open protocols like OpenID Connect. Long may it continue.&lt;/p&gt;</description>
          <pubDate>2016-11-14T18:17:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2016/11/14/identityserver-openid-connect-and.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2016/11/14/identityserver-openid-connect-and.html</guid>
        </item>
      
    
      
        <item>
          <title>The Laws of Distributed Systems</title>
          <description>&lt;p align=&quot;left&quot;&gt;I’ve spent a lot of time over the last year reading, thinking, and &lt;a href=&quot;https://vimeo.com/171317252&quot;&gt;speaking&lt;/a&gt; at &lt;a href=&quot;https://www.youtube.com/watch?v=qCmEs2FPIKg&quot;&gt;conferences&lt;/a&gt; about distributed systems, organisational structures, and the eponymous laws of software development. Over the course of many conversations and countless blog posts and articles, something has crystallised from thinking about three laws in particular, which – if it’s right - could have substantial implications for all of us as software developers, and for the people who use the systems we build.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;TL;DR: if we keep having meetings, the internet will stop working.&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;(There – &lt;em&gt;that&lt;/em&gt; got your attention, didn’t it?) &lt;/p&gt; &lt;h4&gt;Moore’s Law&lt;/h4&gt; &lt;p align=&quot;left&quot;&gt;So, let’s recap. Gordon Moore was the co-founder of Intel and Fairchild Semiconductor. Back in 1965, Moore wrote a paper predicting that the number of components per integrated circuit would double every year. In 1975, he revised his forecast to doubling every two years. His predictions have proved accurate for several decades, and will probably continue to do so until the 2020s, but what’s really interesting is what’s happened since 2000.&lt;/p&gt; &lt;p&gt;Here’s the average total transistor count of CPUs against their year of introduction since 1965. Plotted on a a logarithmic axis, it’s pretty close to a straight line.&lt;font size=&quot;1&quot;&gt; &lt;/font&gt;&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-7pY27glnjyw/WBd6-L_wAmI/AAAAAAAAEIU/kqqq1pEeYTc/s1600-h/image_thumb1%25255B2%25255D.png&quot;&gt;&lt;img title=&quot;image_thumb1&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;image_thumb1&quot; src=&quot;https://lh3.googleusercontent.com/-dbijwZ6HilQ/WBd6-bdLIcI/AAAAAAAAEIY/jzTMLpX_LGg/image_thumb1_thumb.png?imgmax=800&quot; width=&quot;544&quot; height=&quot;346&quot;&gt;&lt;/a&gt;&lt;br&gt;&lt;font size=&quot;1&quot;&gt;[Data source: &lt;/font&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Transistor_count&quot;&gt;&lt;font size=&quot;1&quot;&gt;Wikipedia&lt;/font&gt;&lt;/a&gt;&lt;font size=&quot;1&quot;&gt;]&lt;/font&gt;&amp;nbsp;&lt;/p&gt; &lt;p&gt;Now here’s the same graph, but showing &lt;strong&gt;transistors per core.&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-JRCmxGAyxvE/WBd6-1EB_QI/AAAAAAAAEIc/-iLEgHxfaNA/s1600-h/image_thumb3%25255B2%25255D.png&quot;&gt;&lt;img title=&quot;image_thumb3&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;image_thumb3&quot; src=&quot;https://lh3.googleusercontent.com/-W-XVOfeggn4/WBd6_QBe6AI/AAAAAAAAEIg/_bzoG6cxnto/image_thumb3_thumb.png?imgmax=800&quot; width=&quot;544&quot; height=&quot;346&quot;&gt;&lt;/a&gt;&lt;br&gt;&lt;font size=&quot;1&quot;&gt;[Data source: &lt;/font&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Transistor_count&quot;&gt;&lt;font size=&quot;1&quot;&gt;Wikipedia&lt;/font&gt;&lt;/a&gt;&lt;font size=&quot;1&quot;&gt;]&lt;/font&gt;&lt;/p&gt; &lt;p&gt;See how around 2005 the two series suddenly diverge sharply? That’s because in the early 2000s, we began hitting the physical limits of how many transistors could be integrated into a single CPU. Somewhere around the 4Ghz mark, we hit a wall in terms of raw clock speed, and so the semiconductor industry hit upon the bright idea of multicore CPUs – basically putting more than one CPU into the same physical package.&lt;/p&gt; &lt;p&gt;In the same time frame, we’ve seen an industry-wide shift away from monolithic powerhouse servers towards distributed systems. Modern web apps – which are really just big multiuser systems – run across clusters of dozens or hundreds of ephemeral worker nodes; a radical contrast to the timesharing mainframe systems of the 1970s and 1980s.&lt;/p&gt; &lt;p&gt;The amount of computing power available at a particular price point is still increasing exponentially, but we’re no longer scaling &lt;em&gt;up&lt;/em&gt;, we’re scaling &lt;em&gt;out.&lt;/em&gt; And the reason we’re scaling is that the load on our systems – our websites, APIs and servers – is also increasing. More people are getting online, people are using more devices and connected services, and those devices are delivering increasingly rich user experiences – which means more data, more power and more bandwidth. Here’s Business Insider’s analysis and forecast of the number of connected devices from 2015 to 2020:&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;http://www.businessinsider.com/bi-intelligence-34-billion-connected-devices-2020-2015-11?IR=T&quot;&gt;&lt;img alt=&quot;unnamed&quot; src=&quot;http://static2.businessinsider.com/image/563d14a69dd7cc18008c818a-700-517/unnamed.png&quot;&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;To cope with this ever-increasing level of expectation, we need to build systems that will scale out to cope with demand. Our code needs to parallelize. We need to decompose our problems into small, autonomous units of work that can be distributed across as many cores or nodes as we have available, and combine the outputs of those operations to deliver the results our users are expecting.&lt;/p&gt; &lt;h4&gt;Amdahl’s Law&lt;/h4&gt; &lt;p&gt;This brings us to Amdahl’s Law. &lt;a href=&quot;https://en.wikipedia.org/wiki/Gene_Amdahl&quot;&gt;Gene Amdahl&lt;/a&gt; started out designing mainframe systems for IBM. He was the chief architect of the IBM System/360, and he first presented his eponymous law back in 1967. Amdahl’s Law controls the theoretical performance improvements we can expect by parallelizing some given workload.&lt;/p&gt; &lt;p&gt;Amdahl’s Law is actually &lt;em&gt;&lt;font size=&quot;3&quot; face=&quot;Times New Roman&quot;&gt;&lt;strong&gt;S&lt;sub&gt;latency&lt;/sub&gt;(s) = 1/((1-p) + (p/s)) &lt;/strong&gt;&lt;/font&gt;&lt;/em&gt;– but the gist of it is nicely explained by thinking about Christmas dinner. Or Thanksgiving, if that’s your thing. If you’ve got one person with one cooker working alone, it’ll take a good 20 hours to prepare all the trimmings for a Christmas dinner. By adding more people and more cookers, you can parallelise this and so complete it faster – but you reach a point where everything is done and everyone’s stood around waiting for Jeff to finish roasting the turkey, because you can’t roast a turkey in under four hours, no matter how many chefs and ovens you’ve got.&amp;nbsp; &lt;/p&gt; &lt;p&gt;If you need to parallelize like this, eliminate the turkey. Have steaks instead – because you &lt;em&gt;can&lt;/em&gt; cook steaks in parallel. If you suddenly get another 20 guests showing up half an hour before lunch, no problem – you don’t need to wait four more hours to roast another turkey; just get 20 more chefs to cook 20 more steaks and you’ll still be done on time. And because you’re using cloud infrastructure, you can spin up more chefs and griddles instantly to cope with the increased demand.&lt;/p&gt; &lt;p&gt;See, by designing systems to eliminate those non-parallelisable workloads, we create systems that scale smoothly with the available resources. The beauty of that is that, like all the best solutions in software, it turns “how fast is our website” into a pure business decision. You want faster pages? Pay for more servers. No need to rewrite your algorithms; just throw more power at it. &lt;/p&gt; &lt;h4&gt;Conway’s Law&lt;/h4&gt; &lt;p&gt;Finally, there’s Conway’s Law. First published in 1964, Conway’s Law is the observation that ‘any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization&apos;s communication structure.’ As with Moore’s Law, it’s an observation that has proved remarkably prescient over the intervening decades. I’ve spoken at length about how Conway’s Law has affected the teams and projects I’ve worked on personally, but there’s also some interesting examples in the software industry at large. The relationship between the Linux kernel and the various distributions based on it has interesting parallels with Linus Torvalds’ role in the Linux ecosystem. Id Software’s genre-defining Doom and Quake games – tight, cohesive, focused engines, created by a bunch of coders camped out in a beach house with soda, pizza and no distractions, with less tightly-coupled elements like music and level design handled by less close-knit development efforts.&amp;nbsp; High-profile open source projects like Chromium – the underlying rendering engine, the browser itself, and the ecosystem of plugins and extensions closely reflecting the tight-knit Webkit project, the loosely-organised contributors and pull requests that shape the development of the browser application, and the community of plugin and extension developers who don’t engage with the project directly but rely on published contracts and protocols just as their plugins and extensions do. &lt;/p&gt; &lt;p&gt;And then there’s the &lt;em&gt;really&lt;/em&gt; obvious examples, like this one:&lt;/p&gt;&lt;span id=&quot;preserve276dd7b8d11246fc9fc18b483a2b27c9&quot; class=&quot;wlWriterPreserve&quot;&gt;&lt;SCRIPT charset=&quot;utf-8&quot; src=&quot;//platform.twitter.com/widgets.js&quot; async&gt;&lt;/SCRIPT&gt;&lt;/span&gt;&lt;a href=&quot;https://twitter.com/shanselman/status/790285272021803008/photo/1?ref_src=twsrc%5Etfw&quot;&gt;&lt;img title=&quot;image[92]&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; display: block; padding-right: 0px; border-top-width: 0px; margin-right: auto&quot; border=&quot;0&quot; alt=&quot;image[92]&quot; src=&quot;https://lh3.googleusercontent.com/-F4-oBE4qTqI/WBd6_-hZsVI/AAAAAAAAEIk/HuRD9ci4Ug8/image%25255B92%25255D%25255B2%25255D.png?imgmax=800&quot; width=&quot;420&quot; height=&quot;604&quot;&gt;&lt;/a&gt;&amp;nbsp; &lt;h4&gt;Putting it all together…&lt;/h4&gt; &lt;p&gt;OK, so let’s look at what happens when we interpret those three laws together. Moore’s Law has informed half a century of user expectations about technology. More people do more stuff on more devices, and they expect those experiences to keep on getting better, faster and more responsive. As we’ve seen above, the increase in raw computing power that’s going to deliver those improvements isn’t about clock speed any more, it’s about parallelism. Amdahl’s Law tells us whether systems will benefit from that parallelism or not – and that systems based around blocking, non-parallelisable, long-running operations will benefit the least from the next decade of computing innovation. And Conway’s Law says that if we don’t want our &lt;strong&gt;systems&lt;/strong&gt; to contain these kinds of blocking, non-parallelisable operations, then we should be looking to eliminate them from our &lt;strong&gt;organisations.&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;Which brings us to the crux of the thing: &lt;strong&gt;what’s the organizational equivalent of a long-running non-parallelizable operation? &lt;/strong&gt;&lt;/p&gt; &lt;p&gt;How about sitting around reading Hacker News because the person who’s asked you to build a “Summary Dashboard” hasn’t told you where to find the data, or what the dashboard should look like, and they’re out of the office right now, they didn’t leave any notes, and you can’t do anything until they get back? &lt;/p&gt; &lt;p&gt;How about a two-hour project update meeting where a series of people sit around telling each other things they could have emailed, or written down in a ticket or on a wiki page? &lt;/p&gt; &lt;p&gt;How about sitting on a train for an hour to get to the office in Canary Wharf where you’re expected to be at 09:00 every day, despite the fact that your source code is hosted in the US, your data centre is in Ireland, your issue tracking system is hosted in Frankfurt and your customers are online 24/7 all over the world?&lt;/p&gt; &lt;p&gt;One of the &lt;a href=&quot;http://agilemanifesto.org/principles.html&quot;&gt;underlying principles of the agile manifesto&lt;/a&gt; is that ‘the most efficient and effective method of conveying information to and within a development team is face-to-face conversation’. I think that’s correct, but I think it might be optimising for the wrong metric. Sure, a conversation is a high-bandwidth, high-interaction discussion medium, and I find face-to-face great for bouncing ideas around and solving problems - but conversations are ephemeral. They’re not captured anywhere, nobody outside the conversation knows what was said, and there’s always the risk the people you’re talking to assure you they get it when they actually haven’t understood a single word you said. Perhaps we should be optimising our communication patterns for discoverability instead of raw bandwidth; trading a little temporary velocity for some long-term efficiency.&lt;/p&gt; &lt;p&gt;This isn’t just about cancelling a couple of meetings and letting people work from home on Fridays. It’s about changing the way we think about collaboration, so that the interaction patterns we want to see in our systems can emerge organically from the interaction patterns used by the people who created them. It’s about taking established architectural patterns and practises used in asynchronous distributed systems, and working out if we can apply those patterns to our teams and our projects. What if you applied event sourcing to your project backlogs, so you don’t keep having to ask people about the context behind a particular decision? Maybe you’re even doing this already – I know a lot of open-source projects that do an excellent job of capturing this history as part of their open issues and tasks so anybody who wants to pick up a particular ticket can see the complete history, the discussion, the arguments and hopefully the eventual consensus. What if you treat your documentation - wiki pages, GitHub pages, READMEs - like the query stores in a CQRS system? Rapid retrieval, read-only, optimised for consumption, and updated as necessary when processing commands (i.e. making changes) that affect the underlying systems that are being documented?&lt;/p&gt; &lt;p&gt;What I find remarkable is that Moore, Amdahl and Conway all published their eponymous laws almost exactly fifty years ago – Conway’s Law was published in 1964, Moore was 1965, Amdahl was 1967. Their observations hail from a decade of astonishing engineering achievements – Apollo, the Boeing 747, the Lockheed SR-71, the &lt;a href=&quot;https://en.wikipedia.org/wiki/Montreal_Biosph%C3%A8re&quot;&gt;geodesic dome&lt;/a&gt; – in an era when computers were still highly specialist devices. Sure, you &lt;em&gt;could &lt;/em&gt;argue that people working on timesharing systems in the 1960s couldn’t possibly have foreseen the long-term social implications of distributed systems engineering – but remember, this is the generation that landed on the moon using slide rules and No. 2 pencils. Do you &lt;em&gt;really&lt;/em&gt; want to bet on them being wrong?&lt;/p&gt;</description>
          <pubDate>2016-10-31T17:10:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2016/10/31/the-laws-of-distributed-systems.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2016/10/31/the-laws-of-distributed-systems.html</guid>
        </item>
      
    
      
        <item>
          <title>The Mystery of the Chinese Junk</title>
          <description>&lt;p align=&quot;justify&quot;&gt;You know it’s going to be one of those days when, just as you’re about to put on your headphones and get into ‘the zone’, you overhear somebody saying the fateful words ‘ok, then maybe we’ll need to get Dylan to look at it.’&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;See, amongst the many hats I wear in the course of a given week, there’s one that’s probably labelled ‘&lt;a href=&quot;https://medium.com/@ziobrando/the-rise-and-fall-of-the-dungeon-master-c2d511eed12f&quot;&gt;dungeon master&lt;/a&gt;’ I’m the one who remembers where all the bodies are buried, because – for all sorts of reasons that made very good sense at the time – I probably helped bury most of them. And on this particular day, the source of so much excitement was our venerable Microsoft Dynamics CRM v4 server. It started out with a sort of general grumbling on the support channel about CRM4 being slow… but by the time it was handed over to me to look into, it was beautifully summarised as ‘dude… there’s Chinese in the Windows event log’&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;And, sure enough, there is – complete with the lovely Courier typeface that Windows Event Viewer kicks into when you get errors so weird that good old Microsoft Sans Serif can’t even display them:&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-3IUaxe95aKA/WA_PrjO9UdI/AAAAAAAAEHM/quLqwgn71Uk/s1600-h/image4.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;https://lh3.googleusercontent.com/-xaxSLtPDR34/WA_PtN6h1xI/AAAAAAAAEHQ/VCnL_rIORmE/image_thumb2.png?imgmax=800&quot; width=&quot;631&quot; height=&quot;480&quot;&gt;&lt;/a&gt;&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;Now, whilst it’s been a while since I’ve done any serious work on our old CRM system, I’m pretty sure it’s not supposed to do &lt;em&gt;that&lt;/em&gt; – so we start investigating. Working theory #1: some sort of vulnerability has resulted in attackers injecting Chinese characters into our database – whilst CRM4 is generally pretty well insulated from any public-facing code, there’s one or two places where signup forms would generate CRM Leads, that sort of thing. So we start &lt;a href=&quot;http://stackoverflow.com/questions/9185871/how-to-search-sql-server-database-for-string&quot;&gt;grepping the entire database&lt;/a&gt; for one of the Chinese strings we’ve found in the event log.&lt;/p&gt; &lt;p&gt;Whilst this is going on – and trust me, it takes a while – I decide to share my excitement via the wonder of social media. This turns out to be a Really Good idea, because... well, here&apos;s what happened...&lt;/p&gt; &lt;p lang=&quot;ja&quot; dir=&quot;ltr&quot;&gt;&quot;The incoming tabular data stream TDS RPC protocol stream is incorrect. Parameter (&quot;䐀攀氀攀琀椀漀渀匀琀愀琀攀...&quot; Oh. It&apos;s gonna be one of THOSE days.&lt;/p&gt;— Dylan Beattie (@dylanbeattie) &lt;a href=&quot;https://twitter.com/dylanbeattie/status/788310445182558208&quot;&gt;October 18, 2016&lt;/a&gt;&lt;script async src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;  &lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;&lt;a href=&quot;https://twitter.com/dwm&quot;&gt;@dwm&lt;/a&gt; &lt;a href=&quot;https://twitter.com/dylanbeattie&quot;&gt;@dylanbeattie&lt;/a&gt; The low bits are all null, so this probably UTF-16LE being mistaken for UTF-16BE (or vice versa...?). &lt;a href=&quot;https://t.co/D0IO4px9YE&quot;&gt;pic.twitter.com/D0IO4px9YE&lt;/a&gt;&lt;/p&gt;— Fake Unicode ⁰ ⁧ (@FakeUnicode) &lt;a href=&quot;https://twitter.com/FakeUnicode/status/788327150633914369&quot;&gt;October 18, 2016&lt;/a&gt;&lt;script async src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;  &lt;p&gt;You see in @FakeUnicode&apos;s screenshot there, the words ‘DeletionState’ appear quite clearly at the bottom of the message?  &lt;p&gt;&lt;img src=&quot;https://pbs.twimg.com/media/CvCzUDaUMAAXnOi.jpg:large&quot; width=&quot;639&quot; height=&quot;480&quot;&gt;&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;Whilst this is going on, our database search comes back reporting that there’s no mysterious Chinese characters in any of our CRM database tables. Which is good, since it means we probably haven’t been compromised. So, next step is to work through that Unicode lead, see if that gets us anywhere. Because .NET has a built-in encoding for big-endian Unicode, this is pretty simple:&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;var source = &quot;䐀攀氀攀琀椀漀渀匀琀愀琀攀&quot;;&lt;br&gt;var bytes = Encoding.BigEndianUnicode.GetBytes(source);&lt;br&gt;var result = Encoding.Unicode.GetString(bytes);&lt;br&gt;Console.WriteLine(result);&lt;/font&gt;&lt;br&gt;&lt;/p&gt;&lt;/blockquote&gt; &lt;p align=&quot;justify&quot;&gt;Turns out – just as in FakeUnicode’s screenshot – that’s the text “DeletionState” with the byte order flipped. We grabbed a few examples of the ‘Chinese’ text from the event log, ran it through this – sure enough, in every single case it’s a valid CRM database query that’s somehow been flipped into wrong-endian Unicode. At this point we start suspecting some sort of latent bug – this is old software, running on an old operating system,talking to an old database server, and sure enough, a bit of googling turns up a couple of&amp;nbsp; likely-looking issues, most of which are addressed in various updates to SQL Server 2008. We take a VM snapshot in case everything goes horribly wrong, and one of the Ops gang volunteers to work late to get the server patched.&lt;/p&gt; &lt;p&gt;Next morning, turns out the server hasn’t been patched – because every single download of the relevant service pack has been corrupted. At which point all bets are off, because chances are the problem is actually network-related – which also explains where the ‘Chinese’ is coming from.&lt;/p&gt; &lt;p&gt;OK, let’s capture a stream of bytes from somewhere. Like, say, from the TDS data stream used by the MSCRMAsyncService&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-t_Ie-Up24wQ/WA_PuIw2EiI/AAAAAAAAEHU/ALilmLkJvaU/s1600-h/image%25255B5%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;https://lh3.googleusercontent.com/-SlCbLlPWg2o/WA_PupB147I/AAAAAAAAEHY/VbieOilKFMU/image_thumb%25255B2%25255D.png?imgmax=800&quot; width=&quot;589&quot; height=&quot;56&quot;&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;What does that say? If you think you know the answer, you’re wrong. Pop off and read &lt;a href=&quot;http://www.joelonsoftware.com/articles/Unicode.html&quot;&gt;The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)&lt;/a&gt; – done? Awesome. NOW what do you think it says?&lt;/p&gt; &lt;p&gt;See, we have no idea. It’s a stream of bytes. Without some indication of how we’re supposed to interpret those bytes, it’s meaningless. OK, I’ll give you a clue – it’s UTF-16. Now can you tell what it says? No, you can’t – because (1) you don’t know whether it’s big-endian or little-endian, and (2) you don’t know where it started.&lt;/p&gt; &lt;p&gt;If we assume it’s big-endian, then the first byte pair – 00 48 – would encode the character ‘H’, the second byte pair – 00 65 – would encode ‘e’, and so on. If we assume it’s little-endian, then the first byte pair – 00 48 – encodes the character 䠀 – and suddenly the mysterious Chinese characters in the event log start to make sense.&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-J41XOb2GBkA/WA_PvIy_84I/AAAAAAAAEHc/W1gws_KCZHk/s1600-h/image%25255B11%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;https://lh3.googleusercontent.com/-Aee2ymHm7z4/WA_Pvlqgz6I/AAAAAAAAEHg/CGGRXWhsAqo/image_thumb%25255B6%25255D.png?imgmax=800&quot; width=&quot;599&quot; height=&quot;175&quot;&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;Of course, the data stream between the MSCRMAsyncService and the SQL server hasn’t actually flipped from little-endian UTF-16 to big-endian – what’s happened is that the network connection between them is dropping bytes. And if you drop a single byte – or any odd number of bytes – from a little-endian Unicode stream, you get a sort of off-by-one error right along the rest of the data stream, resulting in all sorts of weirdness – including Chinese in the event logs.&lt;/p&gt; &lt;p&gt;Turns out there was a problem with the virtual network interface on the SQL Server box – which was causing poor performance, timeouts, bizarre query syntax errors, Chinese in the event logs, and corrupted service pack downloads. Fortunately the databases themselves were intact, so we offlined them, cloned the virtual disk they were sitting on, attached that to a different server and brought them back online.&lt;/p&gt; &lt;p&gt;Every once in a while, you get a weird problem like this. I’ve seen maybe half-a-dozen problems in my entire career that made absolutely no sense until they turned out to be a faulty network connection, at which point generally you not only solve the problem, but explain a whole load of other weirdness that you hadn’t got round to investigating yet. The only thing more fun than dodgy networks is dodgy memory – but that’s a post for another day.&lt;/p&gt; &lt;p&gt;Oh, and if you’re wondering about the title of this post, &lt;a href=&quot;https://en.wikipedia.org/wiki/The_Mystery_of_the_Chinese_Junk&quot;&gt;you clearly haven’t studied the classics&lt;/a&gt;.&lt;/p&gt;</description>
          <pubDate>2016-10-25T21:33:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2016/10/25/the-mystery-of-chinese-junk.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2016/10/25/the-mystery-of-chinese-junk.html</guid>
        </item>
      
    
      
        <item>
          <title>Upcoming Conferences and User Group Talks</title>
          <description>&lt;p&gt;I’m appearing at some conferences and user groups in October and November, talking about ReST, the history of the web, and how we can apply the scientific method to software development. Though not all at the same time.&lt;/p&gt;&lt;p&gt;October 19th I&apos;ll be speaking at &lt;a href=&quot;https://skillsmatter.com/meetups/8360-fullstack-nights-october&quot;&gt;FullStack Bytes&lt;/a&gt; at SkillsMatter here in London - one of a series of monthly events around the same themes and ideas as the annual FullStack conference - &quot;JavaScript, NodeJS and the Internet of Things&quot;. On October 25th I’m heading up to Telford to give my Real World REST talk at the &lt;a href=&quot;https://shropshiredevs.co.uk/&quot;&gt;Shropshire Devs&lt;/a&gt; User Group.&lt;/p&gt;&lt;p&gt;November 5th, I’m one of the keynote speakers at &lt;a href=&quot;http://tamperegoesagile.fi/&quot;&gt;Tampere Goes Agile&lt;/a&gt;, a free one-day conference in Tampere, Finland all about agile software development. The theme of the conference this year is ‘experimentation’, and I’ll be talking about the scientific method, the history of experimentation and how we can apply scientific principles to our own software projects and agile processes. It’ll be my first time visiting Finland – new country, new conference, and a new topic – and I’m looking forward to it immensely.&lt;/p&gt;&lt;p&gt;I’ll be speaking at &lt;a href=&quot;http://oredev.org/&quot;&gt;Oredev&lt;/a&gt; in Malmo, Sweden on the 8/9/10th of November, where as well as a &lt;a href=&quot;http://www.oredev.org/2016/speakers/dylan-beattie&quot;&gt;couple of technical talks&lt;/a&gt;, Rob Conery has signed me up for a ‘head to head’ session with Jimmy Bogard, which should be entertaining. Thanks, Rob... :)&lt;/p&gt;&lt;p&gt;Finally, I’ll be rounding out 2016 with a couple of talks at BuildStuff - in &lt;a href=&quot;http://www.buildstuff.lt/&quot;&gt;Vilnius on Nov 16-18&lt;/a&gt; and then in &lt;a href=&quot;http://www.buildstuff.com.ua/kiev/&quot;&gt;Kyiv on the 21-22&lt;/a&gt;. If you’ve ever been, you’ll know that BuildStuff is an excellent conference with great people and great content – and if you haven’t, &lt;a href=&quot;http://www.buildstuff.lt/#register&quot;&gt;get yourself a ticket&lt;/a&gt;, come along and see for yourself.&lt;/p&gt;</description>
          <pubDate>2016-09-15T17:49:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2016/09/15/upcoming-conferences-and-user-group.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2016/09/15/upcoming-conferences-and-user-group.html</guid>
        </item>
      
    
      
        <item>
          <title>Affordances, Signifiers, and Cartographobia</title>
          <description>&lt;p align=&quot;justify&quot;&gt;One of the teams here is putting the finishing touches on a new online version of Spotlight Contacts, our venerable and much-loved industry guide that started life as a printed handbook way back in 1947. Along the way, we’ve learned some very interesting things about data, and how people perceive that their data is being used.&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-kt10FfDFxcs/V5jxpScK8YI/AAAAAAAAECI/3xu-r880_lQ/s1600-h/image%25255B9%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;https://lh3.googleusercontent.com/-UdGzaFeCBXQ/V5jxp6f2I-I/AAAAAAAAECM/jz7LYxnnTDk/image_thumb%25255B5%25255D.png?imgmax=800&quot; width=&quot;540&quot; height=&quot;477&quot;&gt;&lt;/a&gt;&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;One of the features of the new online version is that every listing includes a location map – a little embedded Google Map showing the business’ location. When we rolled this feature out as part of a recent beta, we got some very unhappy advertisers asking us to please remove the map from their listing immediately. Now, most of these were freelancers who work from home – so you can understand their concerns. But what’s &lt;em&gt;really &lt;/em&gt;interesting is that in most cases, they were quite happy for their full street address to stay on the page – it was just the map that they were worried about.&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;Of course, this immediately resulted in a quite a lot of “what? they want to keep the address and remove the map? ha ha! that’s daft!” from developers – who, as you well know, are prone to occasional outbursts of apoplectic indignation when they have to let go of their abstractions and engage with reality for any length of time – but when you think about it, it actually makes quite a lot of sense.&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;See, street addresses are used for lots of things. They’re used on contracts and invoices, they’re used to post letters and deliver packages. Yes, you can also use somebody’s address to go and pay them a visit, but there are many, many reasons why you might need to know somebody’s address that have nothing to do with you turning up on their doorstep. In UX parlance, we’d say that the address &lt;em&gt;affords&lt;/em&gt; all of these interactions – the presence of a street address enables us to post a letter, write a contract or plan a trip.&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;A map, on the other hand, only affords one kind of interaction; it tells you how to actually visit somewhere. But because of this, a map is also a &lt;em&gt;signifier.&lt;/em&gt; It sends a message saying “come and visit us” – because if you weren’t actually planning to visit us, why would you need to know that Spotlight’s office at 7 Leicester Place is actually in between the cinema and the church, down one of the little alleys that run between Leicester Square and Chinatown? For posting a letter or writing a contract, you don’t care – the street address is enough. But by including a map, you’re sending a message that says “hey – stop round next time you’re in the neighbourhood”, and it’s easy to see why that’s not really something you want if you’re a freelancer working from your home.&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;It’s important to consider this distinction between affordances and signifiers when you’re designing your user interactions. Don’t just think about what your system can do – think about all the subtle and not-so-subtle messages that your UI is sending. &lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;Here’s the classic &lt;em&gt;Far &lt;/em&gt;Side cartoon “Midvale School for the Gifted”, which provides us with some great examples of affordances and signifiers. The fact you can pull the door is an affordance. The sign saying PULL is a signifier – but the handle is both. Looking at it gives you a clue “hey – I could probably pull that!” – and when you do, &lt;em&gt;voila&lt;/em&gt;, the door swings open. If you’ve ever found a door where you have to grasp the handle and &lt;em&gt;push,&lt;/em&gt;, then you’ve found a false affordance – a handle that’s sat there saying ‘pull me…’ and when you do, nothing happens. And, in software as in the &lt;em&gt;Far Side&lt;/em&gt;, there’s going to be times when all the affordances and signifiers in the world are no match for your users’ astonishing capacity to ignore them all and persist in doing it wrong.&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;&lt;img src=&quot;http://1.bp.blogspot.com/-AZNV9I1nBF4/UB6SvKtIR6I/AAAAAAAAAWY/fk8_oWh9JCI/s1600/midvale+school+for+the+gifted.jpg&quot;&gt;&lt;em&gt;(Far Side © Gary Larson)&lt;/em&gt;&lt;/p&gt;</description>
          <pubDate>2016-07-27T17:38:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2016/07/27/affordances-signifiers-and.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2016/07/27/affordances-signifiers-and.html</guid>
        </item>
      
    
      
        <item>
          <title>ASP.NET Authentication with Adxstudio</title>
          <description>&lt;p align=&quot;justify&quot;&gt;I’m looking into options for integrating our shiny new CRM system with our website, so we can provide all sorts of neat self-service capabilities and features. One of the applications I’m investigating is a thing called &lt;a href=&quot;https://www.adxstudio.com/&quot;&gt;Adxstudio&lt;/a&gt; – now owned by Microsoft - which claims to “transform Dynamics CRM into powerful application platform with dozens of apps and starter portals.”&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;&lt;a href=&quot;https://www.adxstudio.com/&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; border-left: 0px; display: block; padding-right: 0px; margin-right: auto&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;https://lh3.googleusercontent.com/-TK5tD9T_0to/V5ignmI3UyI/AAAAAAAAEBc/XSlMNwywMHY/image%25255B29%25255D.png?imgmax=800&quot; width=&quot;522&quot; height=&quot;480&quot;&gt;&lt;/a&gt;&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;This is one of those situations where we really are dealing with ‘solved problems.’ Email campaigns. Customers updating their own contact details, potentially things like forums, helpdesk/ticketing systems – lots of things which are nice-to-have but really aren’t strategic differentiators, and so there’s a compelling argument to find an off-the-shelf solution and just plug it in. We already have a federated authentication system here at Spotlight – something we built a few years ago that provides basic identity and authentication capabilities on top of OAuth2. At the time we built it, OpenID Connect didn’t exist yet, so we’ve got a system that does basically the same thing but isn’t actually compatible with OpenID Connect – and consequently doesn’t work out-of-the-box with Adxstudio. So I’ve been poking around, trying to work out the best way to plug Adxstudio into our infrastructure so we can evaluate it as a solution.&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;One of the options on the table was to replace our existing authentication system with IdentityServer; another was to implement OpenID Connect support on top of our existing authentication system – both quite elegant solutions, but both of which involve quite a lot more work than is actually required for what we’re trying to do. &lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;The core requirement here is:&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;div align=&quot;justify&quot;&gt;We already have a CRM Contact record for every user of our system&lt;/div&gt;&lt;/li&gt; &lt;li&gt; &lt;div align=&quot;justify&quot;&gt;We can look up a user’s CRM Contact GUID during authentication&lt;/div&gt;&lt;/li&gt; &lt;li&gt; &lt;div align=&quot;justify&quot;&gt;We want to set up the Adxstudio MasterPortal demo so that our customers are seamlessly authenticated and can use Adxstudio features as though they had registered via the Adxstudio registration facility.&lt;/div&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p align=&quot;justify&quot;&gt;Now, one of the nice things about Adxstudio is that it’s built as OWIN middleware, and uses the ASP.NET Identity framework to handle authentication – so what we need to do is work out how to translate the CRM Contact GUID into an IPrincipal/IIdentity instance that we can assign to the HttpContext.Current.User property, and hope that Adxstudio then does the right thing once the HttpContext User is set correctly.&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;Adxstudio provides an implementation of ApplicationUserManager that’s already registered with the OWIN model, which accepts a CRM Contact GUID (as a string) and returns an instance of ApplicationUser that we can use to spin up a new ClaimsIdentity. So the simplest possible approach here is this snippet of code:&lt;/p&gt; &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;protected void Application_AuthenticateRequest(object sender, EventArgs e) {&lt;br&gt;&amp;nbsp; Guid userGuid;&lt;br&gt;&amp;nbsp; var cookie = Request.Cookies[&quot;crm_contact_guid&quot;];&lt;br&gt;&amp;nbsp; if (cookie != null &amp;amp;&amp;amp; Guid.TryParse(cookie.Value, out crmContactGuid)) {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; var http = HttpContext.Current;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; var owin = http.GetOwinContext();&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; var userManager = owin.Get&amp;lt;ApplicationUserManager&amp;gt;();&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; var user = userManager.FindById(crmContactGuid.ToString());&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; var identity = user.GenerateUserIdentityAsync(userManager).Result;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; HttpContext.Current.User = new RolePrincipal(identity);&lt;br&gt;&amp;nbsp; }&lt;br&gt;}&lt;/font&gt;&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;Doing this with a Contact that’s been created via the Adxstudio registration thing works just fine – but trying to do it with a ‘vanilla’ contact blows up:&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-jZC3i_RSBJM/V5ign5_p-RI/AAAAAAAAEBg/V-4PlUYMt1k/s1600-h/image%25255B6%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;https://lh3.googleusercontent.com/-9vyeqtUFEq4/V5igoZNwyRI/AAAAAAAAEBk/JF1kGMdouVw/image_thumb%25255B4%25255D.png?imgmax=800&quot; width=&quot;564&quot; height=&quot;316&quot;&gt;&lt;/a&gt;As you can see from that stack trace, deep down buried under several layers of Adxstudio and ASP.NET Identity code, something is trying to construct a System.Security.Claims.Claim() instance and it’s blowing up because we’re passing in a null value for something that’s not allowed to be null. Unfortunately for us, because we don’t have the source for the thing that’s actually blowing up, we can’t see what the actual parameter values are that are causing the exception… so it’s time for a bit of hunch-driven development. :)&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;I’d already noticed that when you install Adxstudio into your CRM system, it adds a bunch of custom attributes to the Contact entity in Dynamics CRM; here’s a dump of those attributes for a working contact:&lt;/p&gt; &lt;table cellspacing=&quot;0&quot; cellpadding=&quot;8&quot; width=&quot;540&quot; border=&quot;2&quot;&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign=&quot;top&quot; width=&quot;228&quot;&gt;&lt;strong&gt;Attribute Key&lt;/strong&gt;&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;312&quot;&gt;&lt;strong&gt;Value&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign=&quot;top&quot; width=&quot;228&quot;&gt;adx_changepasswordatnextlogon&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;312&quot;&gt;False&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign=&quot;top&quot; width=&quot;228&quot;&gt;adx_identity_emailaddress1confirmed&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;312&quot;&gt;False&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign=&quot;top&quot; width=&quot;228&quot;&gt;adx_identity_lockoutenabled&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;312&quot;&gt;True&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign=&quot;top&quot; width=&quot;228&quot;&gt;adx_identity_logonenabled&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;312&quot;&gt;True&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign=&quot;top&quot; width=&quot;228&quot;&gt;adx_identity_mobilephoneconfirmed&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;312&quot;&gt;False&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign=&quot;top&quot; width=&quot;228&quot;&gt;adx_identity_passwordhash &lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;312&quot;&gt;&lt;em&gt;&lt;font style=&quot;background-color: #000000&quot; color=&quot;#000000&quot;&gt;(omitted for security reasons)&lt;/font&gt;&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign=&quot;top&quot; width=&quot;228&quot;&gt;adx_identity_securitystamp&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;312&quot;&gt;ca49a664-0385-4eb4-90c0-6283c9e704ea&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign=&quot;top&quot; width=&quot;228&quot;&gt;adx_identity_twofactorenabled&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;312&quot;&gt;False&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign=&quot;top&quot; width=&quot;228&quot;&gt;adx_identity_username&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;312&quot;&gt;ali_baba&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign=&quot;top&quot; width=&quot;228&quot;&gt;adx_lockedout &lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;312&quot;&gt;False&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign=&quot;top&quot; width=&quot;228&quot;&gt;adx_logonenabled&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;312&quot;&gt;False&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign=&quot;top&quot; width=&quot;228&quot;&gt;adx_profilealert&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;312&quot;&gt;False&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign=&quot;top&quot; width=&quot;228&quot;&gt;adx_profileisanonymous&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;312&quot;&gt;False&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign=&quot;top&quot; width=&quot;228&quot;&gt;adx_profilemodifiedon&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;312&quot;&gt;2016-07-20 09:18:43&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;p align=&quot;justify&quot;&gt;The ASP.NET Identity model is generally pretty flexible, but I have a hunch that the &lt;strong&gt;username&lt;/strong&gt; and the &lt;strong&gt;security stamp&lt;/strong&gt; are both required fields because they’re fundamental to the way authentication works. So, let’s try inserting some code into the AuthenticateRequest handler that will check these fields exist, and update them directly in CRM if they don’t:&lt;/p&gt; &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;protected void Application_AuthenticateRequest(object sender, EventArgs e) {&lt;br&gt;&amp;nbsp; Guid userGuid;&lt;br&gt;&amp;nbsp; var cookie = Request.Cookies[&quot;crm_contact_guid&quot;];&lt;br&gt;&amp;nbsp; if (cookie != null &amp;amp;&amp;amp; Guid.TryParse(cookie.Value, out userGuid)) {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/font&gt;&lt;font face=&quot;Consolas&quot;&gt;var http = HttpContext.Current;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; var owin = http.GetOwinContext();&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; var userManager = owin.Get&amp;lt;ApplicationUserManager&amp;gt;();&lt;br&gt;&lt;/font&gt;&lt;font face=&quot;Consolas&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; var user = userManager.FindById(userGuid.ToString());&lt;br&gt;&lt;strong&gt;&lt;font color=&quot;#ff0000&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (String.IsNullOrEmpty(user.UserName) &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; || &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; String.IsNullOrEmpty(user.SecurityStamp)&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ) {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // &quot;Xrm&quot; is the connection string name from web.config&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; using (var crm = new OrganizationService(&quot;Xrm&quot;)) {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; var entity = crm.Retrieve(&quot;contact&quot;, userGuid, new ColumnSet(true));&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; entity.Attributes[&quot;adx_identity_securitystamp&quot;] = &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Guid.NewGuid().ToString();&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; entity.Attributes[&quot;adx_identity_username&quot;] = &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Guid.NewGuid().ToString().Substring(0, 8);&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; crm.Update(entity);&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/font&gt;&lt;/strong&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; var identity = user.GenerateUserIdentityAsync(userManager).Result;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; HttpContext.Current.User = new RolePrincipal(identity);&lt;br&gt;&amp;nbsp; }&lt;br&gt;}&lt;/font&gt;&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;(For the sake of this demo, all we care about is making sure those values are no longer null. In reality, make sure you understand the significance of the username and security stamp fields in the identity model, and populate them with suitable values.)&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;OK, this now works sometimes – but only following an IISRESET. Turns out that Adxstudio is actually caching data from CRM locally, so although that new chunk of code is updating the Contact entity into a valid identity, the Adxstudio local cache doesn’t see those changes because it’s looking at an out-of-date copy of the Contact entity. So… time to configure some cache invalidation.&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;You can &lt;a href=&quot;https://community.adxstudio.com/products/adxstudio-portals/documentation/developers-guide/cache/web-notifications/&quot;&gt;read about Adxstudio’s web notifications feature here&lt;/a&gt;. Adxstudio includes some code that will call a cache invalidation handler on your own site every time an entity is updated. Which works just fine IF CRM Online can see your Adxstudio portal site. And right now I’m running CRM Online as a 30-day trial and I’m running Adxstudio on localhost, and my workstation isn’t on the internet, so CRM Online can’t see it.&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;Time to fire up my favourite toolchain – Runscope and Ngrok. First, I’ve set up ngrok so that any requests to mytunnel.ngrok.io will be forwarded to my local machine – you’ll need a paid ngrok license to use custom tunnel names, but if you’re using the free version try this:&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;&lt;font face=&quot;Consolas&quot;&gt;D:\tools\ngrok&amp;gt;&lt;strong&gt;ngrok http –host-header=adx.local 80&lt;/strong&gt;&lt;/font&gt;&amp;nbsp;&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-swh4oHhYm9g/V5igomieR2I/AAAAAAAAEBo/jrdVb3dYybU/s1600-h/image%25255B14%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; border-left: 0px; display: block; padding-right: 0px; margin-right: auto&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;https://lh3.googleusercontent.com/--BmVOe8_J98/V5igpODnO3I/AAAAAAAAEBs/QE-_ToioXyY/image_thumb%25255B8%25255D.png?imgmax=800&quot; width=&quot;613&quot; height=&quot;355&quot;&gt;&lt;/a&gt;&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;Now, as long as that NGrok process is running, you can hit that URL – &lt;a href=&quot;http://ef736c25.ngrok.io/&quot;&gt;http://ef736c25.ngrok.io/&lt;/a&gt; – from anywhere on the internet, and it’ll be tunneled to localhost on port 80 and have the host-header rewritten to be adx.local. This neatly solves the problem of CRM Online not being able to connect to my local Adxstudio instance.&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;Next, just to give us a bit of insight into what’s going on, I’m going to set up a Runscope bucket for that. Remember – we need to route requests to /cache.axd on our local Adxstudio portal instance, via ngrok, so here’s how to get the Runscope URL you’ll need:&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-Dxxx-r_wQIY/V5igpaXVyhI/AAAAAAAAEBw/b-JL1BQqQ-U/s1600-h/image%25255B22%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;https://lh3.googleusercontent.com/-T8XUoeZQILs/V5igphn3EQI/AAAAAAAAEB0/d1Bj2R6xqGY/image_thumb%25255B14%25255D.png?imgmax=800&quot; width=&quot;555&quot; height=&quot;405&quot;&gt;&lt;/a&gt;&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;So, last step – you see that big URL in the middle?&amp;nbsp; We need to tell the Adxstudio Web Notifications plugin to notify that URL every time something changes. The option is under CRM &amp;gt; Settings &amp;gt; Web Notification URLs.&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;Note that the Adxstudio documentation refers to a Configuration screen accessible from the Solutions &amp;gt; Adxstudio Portals Base. It appears this screen doesn’t exist any more – I certainly couldn’t find any trace of it in my CRM Online instance – but it also appears it isn’t necessary, because as soon as I’d created and active Web Notification URL things started happening.&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;So, now we have something that works – but it still fails on the first Portal request for a particular Contact, probably because the Adxstudio cache isn’t picking up those two new fields fast enough for the login to succeed. To work around this, I’ve put in a thread sleep and then an HTTP redirect, so the first time a user lands on the portal they’ll get a slight delay whilst we populate their Adxstudio attributes, and then they’ll get their personalised screen:&lt;/p&gt;&lt;font color=&quot;#666666&quot; face=&quot;Consolas&quot;&gt;protected void Application_AuthenticateRequest(object sender, EventArgs e) {&lt;br&gt;&amp;nbsp; Guid userGuid;&lt;br&gt;&amp;nbsp; var cookie = Request.Cookies[&quot;crm_contact_guid&quot;];&lt;br&gt;&amp;nbsp; if (cookie != null &amp;amp;&amp;amp; Guid.TryParse(cookie.Value, out userGuid)) {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/font&gt;&lt;font color=&quot;#666666&quot; face=&quot;Consolas&quot;&gt;var http = HttpContext.Current;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; var owin = http.GetOwinContext();&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; var userManager = owin.Get&amp;lt;ApplicationUserManager&amp;gt;();&lt;br&gt;&lt;/font&gt;&lt;font color=&quot;#666666&quot; face=&quot;Consolas&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; var user = userManager.FindById(userGuid.ToString());&lt;br&gt;&lt;strong&gt;&amp;nbsp;&amp;nbsp; &lt;/strong&gt; if (String.IsNullOrEmpty(user.UserName) &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; || &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; String.IsNullOrEmpty(user.SecurityStamp)&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ) {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; using (var crm = new OrganizationService(&quot;Xrm&quot;)) {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; var entity = crm.Retrieve(&quot;contact&quot;, userGuid, new ColumnSet(true));&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; entity.Attributes[&quot;adx_identity_securitystamp&quot;] = &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Guid.NewGuid().ToString();&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; entity.Attributes[&quot;adx_identity_username&quot;] = &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Guid.NewGuid().ToString().Substring(0, 8);&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; crm.Update(entity);&lt;br&gt;&lt;font color=&quot;#ff0000&quot;&gt;&lt;strong&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Thread.Sleep(TimeSpan.FromSeconds(5));&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // Redirect back to the same page, so that Adxstudio will &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // retrieve a fresh copy of the cached data.&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; http.Response.Redirect(http.Request.RawUrl);&lt;/strong&gt;&lt;/font&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; var identity = user.GenerateUserIdentityAsync(userManager).Result;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; HttpContext.Current.User = new RolePrincipal(identity);&lt;br&gt;&amp;nbsp; }&lt;br&gt;}&lt;/font&gt; &lt;p align=&quot;justify&quot;&gt;And it works. The final step for me was to spin up a separate web app that lists all the Contacts in the CRM system, with a login handler that puts the CRM Contact GUID into a cookie and redirects the browser to &lt;a href=&quot;http://adx.local/&quot;&gt;http://adx.local/&lt;/a&gt; – and it works. No registration, no login, and any user with a valid CRM Contact GUID can now log directly into the Adxstudio MasterPortal example. &lt;/p&gt;</description>
          <pubDate>2016-07-27T11:53:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2016/07/27/aspnet-authentication-with-adxstudio.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2016/07/27/aspnet-authentication-with-adxstudio.html</guid>
        </item>
      
    
      
        <item>
          <title>ASP.NET Core 1.0 High Performance</title>
          <description>&lt;p&gt;I have exciting news. My friend and erstwhile colleague &lt;a href=&quot;https://unop.uk/about/&quot;&gt;James Singleton&lt;/a&gt; has just published his first book, &lt;a href=&quot;https://www.packtpub.com/application-development/aspnet-core-10-high-performance&quot;&gt;ASP.NET Core 1.0 High Performance&lt;/a&gt;. James was gracious enough to invite me to contribute the foreword – and since the whole point of a foreword is to tell you all why the book is worth buying, I figured I’d just post the whole thing. Read the foreword, then read the book (or better still, buy it &lt;em&gt;then&lt;/em&gt; read it.)&lt;/p&gt; &lt;p&gt;TL;DR: it’s a really good book aimed at .NET developers who want to improve application performance, it’s out now, and you can &lt;a href=&quot;https://www.packtpub.com/application-development/aspnet-core-10-high-performance&quot;&gt;buy your copy direct from packtpub.com&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;&lt;img src=&quot;https://www.packtpub.com/sites/default/files/1893OS_5280_ASP.NET%20Core%201.0%20High%20Performance.jpg&quot;&gt;&lt;/p&gt; &lt;p&gt; &lt;p align=&quot;left&quot;&gt;&lt;strong&gt;And that foreword in full, in case you’re not convinced:&lt;/strong&gt;&lt;/p&gt;&lt;strong&gt;&lt;/strong&gt; &lt;table cellspacing=&quot;0&quot; cellpadding=&quot;8&quot; width=&quot;540&quot; border=&quot;2&quot;&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign=&quot;top&quot; width=&quot;540&quot;&gt;&lt;strong&gt;&lt;/strong&gt; &lt;p align=&quot;center&quot;&gt;&quot;The most amazing achievement of the computer software industry is its continuing cancellation of the steady and staggering gains made by the computer hardware industry.&quot;&lt;br&gt;- Henry Petroski&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;We live in the age of distributed systems. Computers have shrunk from room-sized industrial mainframes to embedded devices smaller than a thumbnail. However, at the same time, the software applications that we build, maintain and use every day have grown beyond measure. We create distributed applications that run on clusters of virtual machines scattered all over the world, and billions of people rely on these systems, such as email, chat, social networks, productivity applications and banking, every day. We&apos;re online 24 hours a day, 7 days a week,&amp;nbsp; and we&apos;re hooked on instant gratification. A generation ago we&apos;d happily wait until after the weekend for a cheque to clear, or allow 28 days for delivery; today, we expect instant feedback, and why shouldn&apos;t we? The modern web is real-time, immediate, on-demand, built on packets of data flashing round the world at the speed of light, and when it isn&apos;t, we notice. We&apos;ve all had that sinking feeling... you know, when you&apos;ve just put your credit card number into a page to buy some expensive concert tickets, and the site takes just a little too long to respond. Performance and responsiveness are a fundamental part of delivering great user experience in the distributed age. However, for a working developer trying to ship your next feature on time, performance is often one of the most challenging requirements. How do you find the bottlenecks in your application performance? How do you measure the impact of those problems? How do you analyse them, design and test solutions and workarounds, and monitor them in production so you can be confident they won’t happen again?  &lt;p align=&quot;justify&quot;&gt;This book has the answers. Inside, James Singleton presents a pragmatic, in-depth and balanced discussion of modern performance optimization techniques, and how to apply them to your .NET and web applications. Starting from the premise that we should treat performance as a core feature of our systems, James shows how you can use profiling tools like Glimpse, MiniProfiler, Fiddler and Wireshark to track down the bottlenecks and bugs that are causing your performance problems. He addresses the scientific principles behind effective performance tuning - monitoring, instrumentation, and the importance of using accurate and repeatable measurements when you’re making changes to a running system to try and improve performance.  &lt;p align=&quot;justify&quot;&gt;The book goes on to discuss almost every aspect of modern application development - database tuning, hardware optimisations, compression algorithms, network protocols, object-relational mappers. For each topic, James describes the symptoms of common performance problems, identifies the underlying causes of those symptoms, and then describes the patterns and tools you can use to measure and fix those underlying causes in your own applications. There’s in-depth discussion of high-performance software patterns like asynchronous methods and message queues, accompanied by real-world examples showing how to implement these patterns in the latest versions of the .NET framework. Finally, James shows how you can not only load test your applications as part of your release pipeline, but can continuously monitor and measure your systems in production, letting you find and fix potential problems long before they start upsetting your end users.  &lt;p align=&quot;justify&quot;&gt;When I worked with James here at Spotlight, he consistently demonstrated a remarkable breadth of knowledge, from ASP.NET to Arduinos, from Resharper to resistors. One day he’d be building reactive front-end interfaces in ASP.NET and JavaScript, the next he’d be creating build monitors by wiring microcontrollers into Star Wars toys, or working out how to connect the bathroom door lock to the intranet so that our bicycling employees could see from their desks when the office shower was free. Since James moved on from Spotlight, I’ve been following his work with Cleanweb and Computing 4 Kids Education. He’s one of those rare developers who really understands the social and environmental implications of technology - that whether it’s delivering great user interactions or just saving electricity, improving your systems’ performance is a great way to delight your users. With this book, James has distilled years of hands-on lessons and experience into a truly excellent all-round reference for .NET developers who want to understand how to build responsive, scalable applications. It’s a great resource for new developers who want to develop a holistic understanding of application performance, but the coverage of cutting-edge techniques and patterns means it’s also ideal for more experienced developers who want to make sure they’re not getting left behind. Buy it, read it, share it with your team, and let’s make the web a better place.&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;p&gt; &lt;p&gt;&lt;a href=&quot;https://www.packtpub.com/application-development/aspnet-core-10-high-performance&quot;&gt;Check it out&lt;/a&gt;. The chapter on caching &amp;amp; message queueing is particularly good :)&lt;/p&gt;</description>
          <pubDate>2016-07-20T14:28:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2016/07/20/aspnet-core-10-high-performance.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2016/07/20/aspnet-core-10-high-performance.html</guid>
        </item>
      
    
      
        <item>
          <title>Why I&apos;m Voting Remain: Social Media Redux</title>
          <description>&lt;p&gt;So, I &lt;a href=&quot;http://www.dylanbeattie.net/2016/06/why-im-voting-to-remain-in-european_15.html&quot;&gt;wrote a thing about why I&apos;m voting to remain in the EU&lt;/a&gt;, and lots of people liked it, but pointed out it was quite long. And they&apos;re right. It was. So here&apos;s a really nice short manifesto-style version that I think captures the gist of the thing. &lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-_hymNKeuNfU/V2GU4wGzykI/AAAAAAAAD-8/6_FqUFruK2A/s1600-h/vote_remain_poster%25255B4%25255D.png&quot;&gt;&lt;img title=&quot;vote_remain_poster&quot; style=&quot;border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px&quot; border=&quot;0&quot; alt=&quot;vote_remain_poster&quot; src=&quot;https://lh3.googleusercontent.com/-uRaT8l-jOE8/V2GU5fF8ZMI/AAAAAAAAD_E/rmdZq4syyS0/vote_remain_poster_thumb%25255B2%25255D.png?imgmax=800&quot; width=&quot;547&quot; height=&quot;772&quot;&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;If you want to share it, please go ahead&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;a href=&quot;https://twitter.com/dylanbeattie/status/743131834675015682&quot;&gt;Twitter version&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href=&quot;https://www.facebook.com/dylanbeattie/posts/10153567688351466&quot;&gt;Facebook post&lt;/a&gt;&lt;/li&gt; &lt;li&gt;You can even &lt;a href=&quot;https://dl.dropboxusercontent.com/u/7543760/eu_referendum/peace_is_more_important_than_power.pdf&quot;&gt;download a printable PDF version of it here&lt;/a&gt;.&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;Regular readers – don&apos;t go away. Normal tech blogging will resume on the 24th, one way or another. But this is a once-in-a-generation event, and it&apos;s important.&lt;/p&gt;</description>
          <pubDate>2016-06-15T17:48:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2016/06/15/why-im-voting-remain-social-media-redux.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2016/06/15/why-im-voting-remain-social-media-redux.html</guid>
        </item>
      
    
      
        <item>
          <title>Why I’m Voting to Remain in the European Union.</title>
          <description>&lt;p align=&quot;justify&quot;&gt;There&apos;s a referendum coming up. I&apos;m not going to talk about why it’s happening. I&apos;m not going to share any “facts” about politics or the economy, beyond a few historical details that are the basis for my opinion on this. But I am going to tell you why I&apos;m voting for the UK to remain in the European Union. &lt;br&gt;&lt;br&gt;I live in London. I’m British, though I grew up in Africa. I’ve never been much of a patriot, although I’ll confess to the odd surge of pride when it comes to Pink Floyd, the BBC and &lt;a href=&quot;https://en.wikipedia.org/wiki/ThrustSSC&quot;&gt;Thrust SSC&lt;/a&gt;. I’ve spent most of my adult life working in software development, and I&apos;m lucky enough to work with an outstanding team of people – some of the brightest, smartest people you&apos;ll ever meet. More than half of our development team are EU citizens who are living and working in London thanks to the UK being part of the EU. I love working with these people, and there&apos;s numerous studies supporting my own anecdotal experience that working with people you like is a massive factor in how happy you are. And, like many Londoners, my friends are a motley assortment of people from all over the world, and a lot of them are here as citizens of the EU. On a very immediate level, it would really suck if they all had to either leave the UK or go through the merry hell of applying for permanent leave to remain, and potentially have to choose between their native citizenship and becoming British citizens. But that&apos;s both a very personal and a very temporary thing. I’m sure we&apos;d all get over it eventually. &lt;br&gt;&lt;br&gt;In the last year, I&apos;ve been lucky enough to visit Lithuania, Denmark, France, Belgium, Norway and Ukraine. France and Belgium were founder members of the EEC, the predecessor to the European Union. Denmark and the UK joined in 1973. Lithuania, part of the USSR until 1991, became a member of the EU in 2004. Norway is a bit special, but it is a part of the European Economic Area and the Schengen visa area. Ukraine has gone, within my lifetime, from being part of the Soviet Union to winning the Eurovision Song Contest. Ukraine borders four EU member states (Romania, Hungary, Poland, and Slovakia), and whilst I don&apos;t think it will be admitted as a member of the European Union any time soon, the prospect of closer integration with the EU is a huge factor in Ukrainian politics. Whilst the UK was gearing up for a referendum about whether to leave the EU, two hundred thousand Ukrainians were marching through Independence Square in protest at their government&apos;s failure to sign a trade agreement that would have represented &lt;i&gt;one small step&lt;/i&gt; towards eventual EU membership. I&apos;ve visited all these countries without applying for a visa. I&apos;ve spent time in these remarkable places with friends and colleagues from all over the world. &lt;br&gt;&lt;br&gt;&lt;/p&gt; &lt;div style=&quot;text-align: center&quot; align=&quot;justify&quot;&gt;&lt;img title=&quot;The Kon-Tiki, the balsa-wood raft that Thor Heyerdahl sailed from Peru to Polynesia. Photograph &amp;copy; National Geographic&quot; alt=&quot;The Kon-Tiki, the balsa-wood raft that Thor Heyerdahl sailed from Peru to Polynesia. Photograph &amp;copy; National Geographic&quot; src=&quot;http://tvblogs.nationalgeographic.com/files/2013/04/kon-tiki_off_to_sea_big_lg.jpg&quot; width=&quot;540&quot; height=&quot;361&quot;&gt;&lt;/div&gt; &lt;p align=&quot;justify&quot;&gt;In Oslo, there is &lt;a href=&quot;http://www.kon-tiki.no/en/&quot;&gt;a museum&lt;/a&gt; dedicated to the great Norwegian explorer &lt;a href=&quot;https://en.wikipedia.org/wiki/Thor_Heyerdahl&quot;&gt;Thor Heyerdahl&lt;/a&gt;, who dedicated his life to the study of human migration, researching the astonishing stories of people who crossed Earth&apos;s vast oceans on flimsy wooden boats in search of a better life. This wanderlust, the urge to venture beyond our comfort zone, is as old as humanity. Along one wall is a quote from Heyerdahl: &lt;/p&gt; &lt;h3 align=&quot;center&quot;&gt;“Borders - I have never seen one. But I have heard they exist in the minds of some people.”&lt;/h3&gt; &lt;p align=&quot;justify&quot;&gt;He&apos;s right. Borders aren&apos;t physical things. Sure, some of them run along coastlines and rivers, but that&apos;s not the point. Borders are where, once upon a time, we drew a line in the sand and agreed that as long as your lot stayed on your side and our lot stayed on our side, it would be OK if maybe we stopped killing each other for a bit. And, slowly, as we learned to put down the pointed sticks and talk to each other instead, the world has changed. We can cross those borders now. We can live, work and study abroad. We can make friends, fall in love, start families, start businesses. Fifty years ago, someone from Birmingham falling for someone from Bucharest would have been a Pulitzer Prize-winning novel. Today it&apos;s just one more data point on Tinder (Or Grindr, depending who&apos;s playing.) &lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;And that&apos;s why this matters to me. The impact of next week’s referendum will last for generations. It isn&apos;t about the petty power struggle between David Cameron and Boris Johnson. It isn&apos;t about Jeremy Corbyn or UKIP or immigration or the &quot;war on terror&quot; or economic subsidies. It&apos;s about peace, and stability, and the opportunities that affords all of us. The author Charlie Stross captured perfectly my thoughts about this when &lt;a href=&quot;http://www.antipope.org/charlie/blog-static/2016/04/the-unavoidable-discussion.html&quot;&gt;he wrote&lt;/a&gt;: &lt;br&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p align=&quot;justify&quot;&gt;&lt;strong&gt;&quot;The EU is the current incarnation of an institution established in 1947 to ensure that never again would the nations of western Europe go to war with one another&lt;/strong&gt;.&quot;&lt;/p&gt;&lt;/blockquote&gt; &lt;p align=&quot;justify&quot;&gt;The European Union is the legacy of a group of nations who, still reeling from the horror and the bloodshed of World War II, agreed that whatever the challenges the future might hold, we would be stronger facing those challenges together. They believed, as I do, that a unified Europe represents a significant step on the road towards a better world for humankind. Next week, the UK goes to the polls to decide whether we want to step off that road or not. Whether we want to close our borders, lock our doors and go it alone. To me, this is tantamount to a decision about whether we, the citizens of the United Kingdom, believe that we deserve to prosper while others are suffering, and there&apos;s really only one way I can answer that question and still sleep at night. &lt;br&gt;&lt;br&gt;Neither side is presenting a terribly cohesive argument. Speculation, scaremongering and misinformation are absolutely rife on both sides. Nobody knows what&apos;ll happen if we leave. Nobody really knows what&apos;ll happen if we stay. I&apos;m not quite sure what &quot;vote remain&quot; are campaigning on, but from the leaflets I’ve been getting through my front door, it looks to me very much like the &quot;vote leave&quot; camp is campaigning on fear. Fear of immigration and poverty and hardship. Fear of terrorism. Fear of things which are a fact of life for billions of people around the world. I am not scared of these things, because I believe that if we work together, we can solve them, just as we brought lasting peace to the countries of western Europe after centuries of frequent, bloody conflict. Twenty years ago, most British people’s idea of a terrorist was an Irishman in a ski mask. Things can change, and they will. &lt;br&gt;&lt;br&gt;That doesn’t mean I’m not scared. I&apos;m scared that fear and misinformation are going to rule the day. I&apos;m scared for my friends and colleagues for whom a &quot;leave&quot; result on the 23rd could have a real, immediate impact on their lives and livelihoods. I&apos;m scared that, a century from now, some kid is going ask grandad how the war started, and he’ll tell them a story that starts with Brexit. If you don&apos;t think that&apos;s a possibility, consider that it&apos;s less than a hundred years since the Battle of the Somme - nearly a million British, French and German soldiers killed and wounded on the fields of northern France. No Islamic State, no al-Qaeda, no &quot;war on terror&quot; - just honest God-fearing white guys killing one another for the hell of it. Within thirty years, they&apos;d do it all over again – &lt;a href=&quot;https://en.wikipedia.org/wiki/List_of_surviving_veterans_of_World_War_II&quot;&gt;nearly a thousand veterans&lt;/a&gt; of that conflict are still alive today. &lt;br&gt;&lt;br&gt;Since the 1950s, the EEC, and later the EU, has grown from six countries to twenty-eight, and &lt;strong&gt;there has never been an armed conflict between two EU member states&lt;/strong&gt;. And if you think that EU membership is expensive, remember that World War I &lt;a href=&quot;http://www.bbc.co.uk/guides/zqhxvcw&quot;&gt;nearly bankrupted the UK&lt;/a&gt;, and World War II saw us borrow so much money from the United States that &lt;a href=&quot;https://en.wikipedia.org/wiki/Anglo-American_loan&quot;&gt;we didn&apos;t finish paying them back until 2006&lt;/a&gt;. Peace is small potatoes compared to the cost of war. &lt;br&gt;&lt;br&gt;Different people care about different things, and I respect that. Ask yourself what matters to you, research your position, and vote accordingly. Maybe even draw up a list of things you hope are going to happen as a result, so you can validate your assumptions a few years from now. But these are the things that matter to me, and I haven’t heard a single plausible argument that leaving the EU will improve any of them. &lt;br&gt;&lt;br&gt;I believe that peace is more important than politics. I believe that the desire to travel in search of new ideas and new experiences is fundamental to what makes us human. I believe that open borders and common markets give us unprecedented freedom to explore the world we live in. I believe that membership of the European Union embodies all of these principles, and I’m voting to remain. &lt;/p&gt; &lt;div style=&quot;text-align: center&quot; align=&quot;justify&quot;&gt;&lt;a href=&quot;http://www.dylanbeattie.net/2016/06/why-im-voting-remain-social-media-redux.html&quot;&gt;&lt;img alt=&quot;vote_remain_poster&quot; src=&quot;https://lh3.googleusercontent.com/-uRaT8l-jOE8/V2GU5fF8ZMI/AAAAAAAAD_E/rmdZq4syyS0/vote_remain_poster_thumb%25255B2%25255D.png?imgmax=800&quot;&gt;&lt;/a&gt;&lt;/div&gt;</description>
          <pubDate>2016-06-14T23:47:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2016/06/14/why-im-voting-to-remain-in-european_15.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2016/06/14/why-im-voting-to-remain-in-european_15.html</guid>
        </item>
      
    
      
        <item>
          <title>Join us at the Progressive.NET Tutorials 2016!</title>
          <description>&lt;p&gt;Next week sees the return of the &lt;a href=&quot;https://skillsmatter.com/conferences/7235-progressive-dot-net-tutorials-2016#program&quot;&gt;Progressive.NET Tutorials&lt;/a&gt; at SkillsMatter here in London.&lt;/p&gt; &lt;p align=&quot;center&quot;&gt;&lt;a href=&quot;https://skillsmatter.com/conferences/7235-progressive-dot-net-tutorials-2016&quot;&gt;&lt;img title=&quot;Prog dot net postcard A6 date sticker.indd&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;Prog dot net postcard A6 date sticker.indd&quot; src=&quot;https://lh3.googleusercontent.com/-c8OIeJ50Yk8/V16nxs3nn8I/AAAAAAAAD-A/mTEC5HlzEd0/Prog%252520net%252520logo%2525204x4%25255B8%25255D.jpg?imgmax=800&quot; width=&quot;500&quot; height=&quot;500&quot;&gt;&lt;/a&gt;&lt;/p&gt; &lt;p align=&quot;left&quot;&gt;&lt;strong&gt;Progressive.NET&lt;/strong&gt; is a unique event. It goes beyond the high-level slides &amp;amp; code demos of most conferences, and offers a series of hands-on, in-depth workshops with some of the best speakers and experts in the .NET world. This year we’ve got a great lineup of speakers and workshops. On Wednesday, &lt;strong&gt;Glenn Block &lt;/strong&gt;will be showing you how to run C# outside your IDE using ScriptCS, &lt;strong&gt;Ian Cooper &lt;/strong&gt;will be demonstrating high-availability patterns for distributed systems, Toby Henderson will be showing you how to get up and running with .NET Core, and I’ll be running a new workshop about asynchronous programming patterns, async/await and how you can use them to deliver more responsive apps.&lt;/p&gt; &lt;p align=&quot;center&quot;&gt;&lt;img src=&quot;https://res.cloudinary.com/skillsmatter/image/upload/c_crop,g_custom/c_scale,w_96,h_96/v1459504849/my9qhv6hlzriuohl0bpb.jpg&quot;&gt;&lt;img src=&quot;https://res.cloudinary.com/skillsmatter/image/upload/c_crop,g_custom/c_scale,w_96,h_96/v1392831565/nkhvdrsj9cqpnqj1kwh2.png&quot;&gt;&lt;img alt=&quot;Image result for ian cooper&quot; src=&quot;data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBxIQEBISEBISEBAQEBASEA8VEBAPDxAPFRIWFhUVFRUYHSggGBolGxUVITEhJSkrLi4uFx8zODMtNygtLisBCgoKDg0OFxAQFSsZFR0rLS0tLSsrLSsrKy0tLSstLTctLS0tLSsrNzctLTc3Ny0tNysrKysrLSsrKysrKysrK//AABEIAOEA4QMBIgACEQEDEQH/xAAcAAABBQEBAQAAAAAAAAAAAAAEAAECAwUGBwj/xAA/EAABAwMCAwYDBAgEBwAAAAABAAIRAwQhBTESQVEGEyJhcYEykaEUQlLBBxUjU3Kx0fAWM2KCJDRDVJLh8f/EABkBAAMBAQEAAAAAAAAAAAAAAAABAgMEBf/EACARAQEBAQADAAIDAQAAAAAAAAABEQISITEDQRMiUQT/2gAMAwEAAhEDEQA/APMb34HLDhbOpCKZ+SxiEcVff0ydJMSr1BwE4UQU7UgkAkU6eUA0pw5NCYI9Bq6K3xLXqBZOiblbFRqy6vt0fj+MWqYrCfwr2Lsa2bOj/CvIbmke9B5RC9h7Et/4Kl6J6z7bQpqQYrQ1SDEazVBimKatDFPgRoUcCkKavFOVYKaNAfgUuBEimpikjSCCkpCmixSUu6QAfdpjTRvdJu6S0A+7S7tGGmm7tGkE7tMjO7ST0PmfWT4AOrgsiFraw7DVkkJ8fGvf03Am4MqcpwVaFZCQCnKkIQFYCQCshMWpBGEpUoUQnga+hjf1Wu/dZGiECVsvHusO/ro4+Aa7chetdhWzZU/Qryx7ZK9X7Bf8mz3TifyN9rFYGJuMDdD1tSpt++MeabIYGK1lJKxqNqNDmkEEcso5lJBBmUla2kimUVey2KYBNoK1tutBlsrRSCAzhbpfZytMNCeEBl/Z0vsy04T8KCZRt/JMaC1eFMWJBk9wktXugkgPkDUckBBFiOvI7yCrvsbObuHonz6i+r/ZlFiaES9v/wBVfAnpKXNUOFFtop+4KekFhJXupwlTYCQnoU8EqTGq+u4bDEIcOjmjTGW4hHuqlkEGQeR5LIp1Y2yjBTqOGGmPklcXN/TZoXDSAeY+q6DSO0Rot4Zgch5riqNu/ZW1Ld4btKPHRZXaXnax5wHY9Qs1mo1Kp3hv3oySFyDy8bgo6x1RzCAMDmneUV6H2d14WVUQ9z6D4D2vOWHqOi9isC2q1r2mWuAIO4Xgtu6lVmS2eHqI9Cu7/R1rHcfsHPmiXeGTPduPL0KjCemspAKwBIJ08BJJJJgkkkkAkkkkAkkkkAkkkkYHx/XaC8z1yiOHipyNwUHPjd55WhYieJv4hKfJdM808T1OyVOkSYAk9E1UFryOnzhaOiMmqD0lKxU9xOhp7oy0j2WtR7M1SJ4SB1Wkao8AMZIXotxrNsyg1reHi4AIjnCl5/8A1fk74+PGNQ0RwwN+kLDv6bqTuFwh0bL0fVLpg4nmAPovLtTujWqveduIx6BVHV/z928ew9SqfdFadpz63k38Sr02zNV4B+HmuysqIaAAIATtx1/i/Hvuo6fozGAYk9StZtmIVtu3ZGsCh05MZ36uanbpw2WmKSsayFR+mV+q29Pogb3QWP5QeoXSFqi6mhN5n+PPbvTatseL4mfiH5rc7P683Y+F2M8iugrUQ4EESDuIwuO1/R+4cKtMHu3HI5NKf1z98Y+mOzd731tSeTJLBJ6kLUXjH6NO3Jp0xQq+JoHh6heoWfaK3qbVA09CYSYtdJVMuGO2c0+hCsQDpJJIBJJJIBJJJIBJJJID47iCD5lFWb+CpPsiXaaIwVSdPI2cpncXeKlq1CHh/Xc8kboTIfnPhP8ANEX4Y+3gT3gAPqQh9EmXF2MNA81XlKXjRWs1YLI5ZQzdQcImVPUSXPAGwGVQ0h1ThjEgeySe+ZfsN2irONADmSJ+UrkwJ8I3Jhdj2vpBtCmdi5xH0JWBpFrxP4uQ/mnPmjnn9NjS7Tu2gRmM+q2ramh6NPCNoBS7Z6g2iEXTQtFFMCqQ9WNKsAVbAnKrAmE+6YBRhGDSLUPdWwqMcxww4EIlsp3NSsZ9OG00GjX4TMtdB9JXplCxcWh0GDHIrldQ01pr0apwHPAd/VepaZAYGtuA3EcLmghTXPfTBo27xsSPcrWs7i5p7OdHQyQtywsiySDSqzzOCjaYweOiBH4cpJZNLXqzfjYHeexRdLtMz77HN+oRRoUXdWn5IatojXfCQUATT7QW7vvx6ghF07+k74ajD/uC5K+0N4+6SPKVh3Fs5h5j5hGh6g14OxHzTryYahXp/DUcIVrO1t0z78jzCND1SUl5f/jm48vkkjQ8YbqzOeFay/YfvD5rFICY0wi8Rr510AuGnYj5qbaoXOCl0KeXjZxU+CvN0oqDqlShp4gM9fVc6y6qDnKubqDhuPkn40eUH9qL11RlMHZrjHyIVehjw+6BvLjvGjEQj9E+H3VT1E83+zfpnCJprMq1eFsoZjK1TLTHunJrbcdNQdCOp1ZXGl11T5cQCIttceD42R5q5yX8jsJSWXZ6gHjoivtAVeKvIYCnkLCutZYw5PsEN/iIHAa4onKb+SOmDuilK5+21ok/AQtajch4kI6id1K7HE0A/ib/ADXr9ro9B1JnFSbPCMxBXk+n0DVrU2fiqNHtOV7WxsADphZ2MevrP/UdEfCC30cQlR017CS2q70JkLR4cz9FJJIEsqjfhf6gIetUaPjpub5tn8lrJQgMD7bT+5cOYej5I+qjUqVXfuK4/wBsrXubCk/42NPtlYtz2etyfDUdSPk7CWBm3tFp/wAy1c3zYfyCw720tz+8Z/E0roqnZ6sM0rsejtkDXsL9u/c1h/EPzSwnNfY6P7wfI/0TLb7q7/7en/5NSRgfP0JQrOFRIV00SEi1OnlLAhCfhUwkAqJCMIzQnS5wVdBgJzsjNMphtR0bHZDTie9adVkhUPvC3Dd1r06Uj2WZVtocYCfNa0ANdeXBsHJ4Z81ouJMhwBP1ULfTWBwfGQZjlK0RRk8REFa7MTzzf2r0hhJg4WpdUYaT5IG3dwvWjWPEIUb7bXj058taPuyT1UP1g2mYLRPoterbgZ6LNvbAVHl08M7iFrMYeNjQt7oOAlozthH2uDhA0aIdAHIQFpULeN0uh7dN2Io8d7TxhvE4+w/9r1crzHsPqtGjWDHB3eVPADjhEr0m4PhXPUdfVrSnQ1jV4m+YJBCJSSSSSSAYrH1K1Wyqq7JCVJxN3QI2JHzCzKxqDZ7x7ldfeW6yLi2SDA76t+8d8061PsySA+fiFW5EEqJaFalACXCrS0KJAT0kSEynCbhQWLLYic7Iu0d4z5LPDoKI02pLyhpx9dXY1MBXvotJWdbvWlQzuk6pP9M23CpvKnCMI58ALC1OpwmTtCrDki20lxlaY5LA0/VGgwVov1MHbIVYd75xqtcHKDrQISzuJMgLVpulOJyWGtLUAomsICrDlCq7GUVneWr2FtG1b5nEJDZcPUL2Co2RC8W0LWXWlTvGAE5EFdVa/pKH/Uox5tlY9VlebXSVK7reoXRLSfEFsWt02oJaZH1HquU/xtZVR4yWk9WuMfRCO1C1JmlchvuWf0U6m813koR92OLhBmN1ytG6c/wtuC8dA+fmtiwpRH9lGprcacKFV2FDvRCqq1UAJdFZddH3D1n13JEohJJJAfN5eokqniTlwV6tbxKPGocSjxI1K3jTcShKdMzvMpWtXhdvzUKohDlyoc3K7Cxq8S0qVWFzOmXMQtplyITkdE7GXWoMYJcfZcpq2qGpgbJai51WpjbYBEUtIx4nR7Jptt9AtLoPecCR9EdqDalMdAjLO27vDKoHlAU61q+oIc/i+Sofx3PobSdV5H5rqLW4DhIOCuTrWL6Y2meat0e+cx3C74SY9EHOrPVdi2qqq1wAMoGrcQgbu8wcpUr0up30PHME7LpLO3p1GcRDmnyK5bT7Uu4XfRdvpdp+yxjJWPVxPPV0G6yp7B5B8xKg/TDBIcDAJ2hEXVGHb5Uba5kEf6Xj6LPWk6o/sYQKp9R/Jeh06kLzHsfV/aO8i1ehNqIl9s/yz2ONZM+tIQhqKJqqmJ6tRB1ainVqIao5IH40lVxJ0tD5sLuig9xUwFW4rTFrA1wbxcLuHrBhRLui1TdAWvdgHimZjqg7Cwc+o0OloO557JhS0H+yndK66j2TpOZPevafMBYmqaUKJgP4hyxCRYx6j8ZVJKsruyoDyWkIZb1YCI+2GIWeHR8lAVUznWOk054OUZcZGViaUS4iOu/Rb11ThszIAlDbnqYArWkgkbpWTXTgmR5qVG+EK+0qhxxumewe2SIcsmuwNeeWZWz3uMrmdSuf2hKEdWDri8MRyQ4rcRAQTqkxlE2LYM/JK1nrqLCWx6Ld0+u9zdzvsudsLgjO4PIr0D9H9vTuKoBAgEuI84WPfs5WRc0fhGxPVWUrPgnGeE59l1f6ULQUrdlam0AscBA6LkbG+NUCYGDj1CjxsXOvarse79rV8o/mV39OqvOuybouKo8vzK7ulUSn0/zfRhqJu8Q/GlxK2Kb3qio5Jz1U5yRH4klXxJ0YHz5VtS3c7oYNkwtGqxznEmJPLons7SKjSYiZOVo0skhUWZA6kBdBa0wawHJrSdo54WFqFxFTwRg4A6qt1zWOcztOyeanyx6KHUwzxPY2BtIyuF7SvaXEtcD0AWZUruHxGTKEe/iMlE4GqvVTBwmc2U6tKt5UGiVMtJOyLtKOQIkz8kpQ19Jow0Ecjladep4Y8kHSAY2SSOcKdS7Dm4CsMq9fwvAGxWpo2/SQsu9Z976K7TrgYmcJhv3LCduWxXO6nbnfmtvvuIYyFReMBYcbzHkkNc9aPBMH2WzQZG+3RYooOBmIWvbXALRzOymm2abgNjjpzC6TsVr32WsHkGJgjaQd1yVjTmY+LdF25IO+5WdN6L297ZUbqk2hRl2Q55g4PRc7pVsPCcgrGtaXFVO+4ytWrcFmYmHAfNRb+lSZS7NVIvXj+Jdux64LRpbfSdiT9Qu3Y5T+1/mE8afvFRxJi5UxWOcqy5R41AuQSfEnVUpIDx4uHQLOqWjicOwjSoEwtsK7+grLPhGSZ6quu8sHicSEXUfKztVdshE0O48WRsmHko09h9VaxsZ5KlqngymJSrVCCnmSP5dVJrrennf+i0WM4AMZKHtngDI2z7qbK8u2k8gngF3VbAAHkCmpGAAoVXhrc8tlBpOSDM8uaYV3tbMJaa+D1Vd6wmOkfVStWRJHQIJqC44YxiUXWjhjlErIFXGTsUWyrJAOZgJ0BK9uWmRkcxPJDU6+cYErQujwyORws2ozxYhSbQF3G+BG+QUXaamYJEQNxufVc/Wqkug7J7Spw1J3BwU/Ea2q+t1qb/CWEcuvote01NzwC5o5E5KwKoYcgBHWRkKcLytdNY3tMVRUeeEg7bhdhZ3TKjQWODgfPK8suX4VFhqL6ZBY4gg9TBU/x6ryt+vY+JMXLmdE7UsrQyp4KhGCYDXFdCHLOywLC5QLlElRLkEnxJKuUyA8iJUXiUuicHK3FoY7qi7bxIituoEIkKAaTi2cSo/avLCvrtws+o1UeCakVBIEFV0hnzT2fMFE06YBlSSTQRk7R9URZgA5+8hjKvZVgDySw1d3VBMTthTo1Bj0hD3LeEzO6e1qRIPsU9IRdu4uGDGNk1s4A8KVQ54sKt5nhI91QXVm4x8kbbvDhBwQN1n1bvqp0KhmRsUgtr4BBn13WfUOAfNaHGSA2ROTnMhCVaG2epjkESGFiURRtxuqqXxAeaKcVpmBCmIJjmVuWohvssUCSFtUzDfZRU1Cs9AsOSi6mQs9xgpKEU6haQRuDIXofZHWzcMLKn+YzA/1N6rzdroRul37qDu8bgDopvOlbj1olRJWHoGtCuzxkB3LzC2CVhYcsTlJVpkvYeVBiiGwVJoUCF1M99mfTkFDjzRmIQj6cFOHzapuhhZ72rRu9ggnbIXprMeJFvP0QNMwUe0yihWy5PRR7yTJUXs4XR1EqRp46KQrLuKQefNSY2PVTY3oiG0gR0TkCio8iOkZVdOpz809cHYbKAdsEYBFYhzWkb8wrKGAEK7PNWk435KiO93i6HaeoVhZIkIemJA88olwACUTqFEDi2yrKjI+aa0GZPNXXDE9OVTbDK1HmAgrUeSMe/MFSXVV1DhBV2ndG1YCoq5aUH5UNmFU+s6QBtzHkrmElM6lzCrStbWn3YaAW4AiR0XWaTrQdhxwRgrz22rFufoi7W4Mywwfw8lneYnme3qPeDqPokvPf16/+wUlPhWgFig/ZJJaM/2gFU/dJJC+VFxyQ79kkkoIoCPttkklVVfiq7+NvopnZJJIoent7Iqlskkqgqh3xIOpy9UkkFEindsfRJJIUrb8giLr4QkkiJXWuwT3CSSAtsFc/wCJJJKFUKyi/wCH2SSTigdLf2KLp7FJJK/S6COVtp8TUkkp9VBiSSS0N//Z&quot; width=&quot;96&quot; height=&quot;96&quot;&gt;&lt;/p&gt; &lt;p&gt;Thursday, we’ve got &lt;strong&gt;Ashic Mahtab&lt;/strong&gt; showing you how to process millions of messages a second using Apache Kafka, we’ve got&lt;strong&gt; Mark Gray&lt;/strong&gt; talking about machine learning in F#. After lunch &lt;strong&gt;Ben Hall&lt;/strong&gt; will be showing you how to get up and running with .NET and Docker, and &lt;strong&gt;Phil Trelford&lt;/strong&gt; will be building a compiler with F#, starting with an abstract syntax tree and a parser, and ending up with a compiler that generates .NET IL code or JavaScript. Thursday wraps up with the Progressive.NET Party – food, drinks, and a chance to hang out with the Progressive.NET speakers and developers in the Space Bar at SkillsMatter’s amazing CodeNode building. &lt;/p&gt; &lt;p align=&quot;center&quot;&gt;&lt;img src=&quot;https://res.cloudinary.com/skillsmatter/image/upload/c_thumb,g_face,w_127,h_127/c_scale,w_96,h_96/v1389980454/siach27mwpvzkm7jrzkw.png&quot;&gt;&lt;img src=&quot;https://res.cloudinary.com/skillsmatter/image/upload/c_crop,g_custom/c_scale,w_96,h_96/v1444040365/bpy2uacyusrxule0wjjr.jpg&quot;&gt;&lt;img src=&quot;https://res.cloudinary.com/skillsmatter/image/upload/c_crop,g_custom/c_scale,w_96,h_96/v1465554043/zd3pn5s9efwi1lciwkqq.jpg&quot;&gt;&lt;img src=&quot;https://res.cloudinary.com/skillsmatter/image/upload/c_thumb,g_face,w_127,h_127/c_scale,w_96,h_96/v1389979939/yxgkkioabgzimplojouh.png&quot;&gt;&lt;/p&gt; &lt;p&gt;On Friday, we round out the event with a day of talks from some of the best speakers in .NET&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;Rachel Reese&lt;/strong&gt; on event-driven microservices  &lt;li&gt;&lt;strong&gt;Charalampos Karypidis&lt;/strong&gt; on isomorphic JavaScript applications and .NEt  &lt;li&gt;&lt;strong&gt;Sebastien Lambla&lt;/strong&gt; on API versioning (and why it’s evil)  &lt;li&gt;&lt;strong&gt;Rajpal Singh Wilkhu &lt;/strong&gt;on OpenID Connect and Identity Server  &lt;li&gt;&lt;strong&gt;David Whitney &lt;/strong&gt;on metaprogramming  &lt;li&gt;&lt;strong&gt;Harry Cummings &lt;/strong&gt;on Node.js for .NET developers  &lt;li&gt;&lt;strong&gt;Barbara Fusinska &lt;/strong&gt;talking about predicting the future with Azure machine learning  &lt;li&gt;&lt;strong&gt;Sam Elamin &lt;/strong&gt;talking about metrics-driven development  &lt;li&gt;&lt;strong&gt;Liam Westley &lt;/strong&gt;on App 2.0 and why the web lost  &lt;li&gt;&lt;strong&gt;Evelina Gabasova &lt;/strong&gt;on how to spice up your website with machine learning&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;&amp;nbsp;&lt;img src=&quot;https://res.cloudinary.com/skillsmatter/image/upload/c_crop,g_custom/c_scale,w_96,h_96/v1389980635/jenl2y9omtamctckqrim.png&quot;&gt;&lt;img src=&quot;https://res.cloudinary.com/skillsmatter/image/upload/c_crop,g_custom/c_scale,w_96,h_96/v1463671092/fu0qahsqfm99imknobb3.jpg&quot;&gt;&lt;img src=&quot;https://res.cloudinary.com/skillsmatter/image/upload/c_crop,g_custom/c_scale,w_96,h_96/v1389979857/xiq6t5guonkkbzqdpiai.png&quot;&gt;&lt;img src=&quot;https://res.cloudinary.com/skillsmatter/image/upload/c_crop,g_custom/c_scale,w_96,h_96/v1431427531/mwmxyodvs8diz55t306x.jpg&quot;&gt;&lt;img src=&quot;https://res.cloudinary.com/skillsmatter/image/upload/c_crop,g_custom/c_scale,w_96,h_96/v1463509093/mkp1ipdgokw6jbbxokc4.jpg&quot;&gt;&lt;img src=&quot;https://res.cloudinary.com/skillsmatter/image/upload/c_crop,g_custom/c_scale,w_96,h_96/v1464000460/jal7bhfexqrnoksl4oma.jpg&quot;&gt;&lt;img src=&quot;https://res.cloudinary.com/skillsmatter/image/upload/c_crop,g_custom/c_scale,w_96,h_96/v1464181000/lsvgdhuzkyj2onzdmysc.jpg&quot;&gt;&lt;img src=&quot;https://res.cloudinary.com/skillsmatter/image/upload/c_crop,g_custom/c_scale,w_96,h_96/v1461082340/tlwixwhl84nk68rymcyo.jpg&quot;&gt;&lt;img src=&quot;https://res.cloudinary.com/skillsmatter/image/upload/c_thumb,g_face,w_127,h_127/c_scale,w_96,h_96/v1392308988/uy7hh3k37astqgdkgmj8.png&quot;&gt;&lt;img src=&quot;https://res.cloudinary.com/skillsmatter/image/upload/c_crop,g_custom/c_scale,w_96,h_96/v1407510284/gms6jwwazogf5ik360dd.png&quot;&gt;&lt;/p&gt; &lt;p&gt;Progressive.NET has been running since 2009. It’s an excellent event, and it’s also a great way to keep up to date with everything that’s new in the .NET world. If you’ve been hearing about things like .NET Core, Docker, Identity Server, async and await, machine learning – and you’re not quite sure what they are or how they apply to you – come along to Progressive.NET. By the end of the week, you’ll have a head full of new ideas, a laptop full of working code you can refer back to, and a load of new friends who can help out when you get stuck. &lt;/p&gt; &lt;p&gt;The event is next week, 22-24th June, at the &lt;a href=&quot;https://skillsmatter.com/event-space&quot;&gt;SkillsMatter CodeNode&lt;/a&gt; here in London. Tickets are normally £875+VAT, but you can use the code &lt;strong&gt;SPECIAL_LDNUG_PROGNET &lt;/strong&gt;to get 40% off - £525 instead of £875 – so what are you waiting for? &lt;a href=&quot;https://skillsmatter.com/checkout/login?event_id=7235-progressive-dot-net-tutorials-2016&quot;&gt;Get your ticket now&lt;/a&gt; and we’ll see you there!&lt;/p&gt;</description>
          <pubDate>2016-06-13T12:32:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2016/06/13/join-us-at-progressivenet-tutorials-2016.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2016/06/13/join-us-at-progressivenet-tutorials-2016.html</guid>
        </item>
      
    
      
        <item>
          <title>The Next Big(int) Thing</title>
          <description>&lt;p align=&quot;justify&quot;&gt;One of our systems here uses a &lt;strong&gt;bigint identity&lt;/strong&gt; column as a database primary key – because we knew when we built it, back in 2010, that we were going to end up with more than 2,147,483,647 records.&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;Well, that happened at 12:02 today, and a couple of systems promptly failed – because, despite the underlying database being designed to handle 2^63 records, the POCOs that were being mapped to those classes were using a regular C# int to store the record ID, and so as soon as they got an ID from the database that&apos;s bigger than Int32.MaxValue, they blew up. Thanks to the underlying DB schema already supporting 64-bit IDs, the fix was pretty simple – just change &lt;strong&gt;int&lt;/strong&gt; to &lt;strong&gt;long&lt;/strong&gt; in a few carefully-selected places and redeploy the applications – but it&apos;s still annoying that something we knew about, and planned for, still came back to bite us. So I started thinking – how could we stop this happening? &lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;The problem is that, despite being a &lt;strong&gt;bigint&lt;/strong&gt; column, we just accepted SQL Server&apos;s default &lt;strong&gt;identity&lt;/strong&gt; setting of&lt;strong&gt; (1,1)&lt;/strong&gt; – i.e. start counting at 1, and increment by 1 each time. Which means that until you hit 2-billion-and-something records, it doesn&apos;t actually make any difference - and that takes a while. In our case, it took 5 years, 8 months and 26 days. During that time we&apos;ve made hundreds of changes to our code, and in a handful of those cases, we&apos;ve mapped that bigint ID onto a regular C# Int32 – and so inadvertently planted a little time-bomb in our production code. Tick, tick, tick…&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;So here&apos;s a nice neat solution, that I wish I&apos;d thought of five years ago. &lt;strong&gt;Anytime you create a bigint identity, seed it with (2147483648, 1)&lt;/strong&gt; – so that right from day one, it&apos;s already too big to fit in an Int32. Any system that tries to store an ID in a regular int variable will fail immediately, not in five years when someone creates that magic 2.14-billion-and-somethingth record. Even though you&apos;ve effectively thrown away 2^32 possible values, you have another (2^64 - 2^32) values to play with, so you&apos;ve lost a tiny, tiny fraction of the available keyspace in exchange for immediate feedback if any of your client apps can&apos;t cope with 64-bit ID values.&lt;/p&gt;</description>
          <pubDate>2016-05-13T14:15:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2016/05/13/the-next-bigint-thing.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2016/05/13/the-next-bigint-thing.html</guid>
        </item>
      
    
      
        <item>
          <title>We&apos;re Gonna Build a Framework!</title>
          <description>&lt;p align=&quot;left&quot;&gt;&amp;nbsp;&lt;/p&gt; &lt;p align=&quot;left&quot;&gt;Check it out! I made a thing. A musical thing. Well, Billy Joel made the musical thing (which is copyright © 1989 Columbia Records, btw) and I wrote and recorded a new set of lyrics for it, all about software frameworks and how lovely it is to have so many to choose from.&lt;/p&gt; &lt;p align=&quot;center&quot;&gt;&lt;iframe height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/Wm2h0cbvsw8&quot; frameborder=&quot;0&quot; width=&quot;420&quot; allowfullscreen&gt;&lt;/iframe&gt;&lt;/p&gt; &lt;p align=&quot;left&quot;&gt;And, for anyone who doesn&apos;t believe that every single one of those really is a software development framework, here&apos;s the Googlified lyrics.&lt;/p&gt; &lt;p align=&quot;left&quot;&gt;&lt;/p&gt; &lt;p align=&quot;center&quot;&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Handlebars+framework&quot;&gt;Handlebars&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Hibernate+framework&quot;&gt;Hibernate&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Solar+framework&quot;&gt;Solar&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Activate+framework&quot;&gt;Activate&lt;/a&gt;, &lt;br&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Phalcon+framework&quot;&gt;Phalcon&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Flask+framework&quot;&gt;Flask&lt;/a&gt; and &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=silverstripe+framework&quot;&gt;Silverstripe&lt;/a&gt; and &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Typo3+Flow+framework&quot;&gt;TYPO3 Flow&lt;/a&gt;,&lt;br&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Agavi+framework&quot;&gt;Agavi&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Pixie+framework&quot;&gt;Pixie&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=hazaar+mvc+framework&quot;&gt;Hazaar MVC&lt;/a&gt;, &lt;br&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=CodeIgniter+framework&quot;&gt;CodeIgniter&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Lithium+framework&quot;&gt;Lithium&lt;/a&gt; and &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=PRADO+framework&quot;&gt;PRADO&lt;/a&gt;. &lt;/p&gt; &lt;p align=&quot;center&quot;&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Raphael+framework&quot;&gt;Raphael&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Bobo+framework&quot;&gt;Bobo&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Bottle+framework&quot;&gt;Bottle&lt;/a&gt; and &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Tornado+framework&quot;&gt;Tornado&lt;/a&gt;, &lt;br&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Django+framework&quot;&gt;Django&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=CherryPy+framework&quot;&gt;CherryPy&lt;/a&gt; and &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=WSGI+framework&quot;&gt;WSGI&lt;/a&gt; &lt;br&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Glashammer+framework&quot;&gt;Glashammer&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=WebSphere+framework&quot;&gt;WebSphere&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=RedBean+framework&quot;&gt;RedBean&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=TurboGears+framework&quot;&gt;TurboGears&lt;/a&gt;, &lt;br&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Albatross+framework&quot;&gt;Albatross&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Aquarium+framework&quot;&gt;Aquarium&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Selenium+framework&quot;&gt;Selenium&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=web.py+framework&quot;&gt;web.py&lt;/a&gt;&lt;/p&gt; &lt;p align=&quot;center&quot;&gt;CHORUS:&lt;/p&gt; &lt;p align=&quot;center&quot;&gt;We’re gonna build a framework, &lt;br&gt;‘cos we wanna use one, but don’t wanna choose one, &lt;br&gt;We’re gonna build a framework, &lt;br&gt;we didn’t like the others, so we’ll write another…&lt;/p&gt; &lt;p align=&quot;center&quot;&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=SiteCore+framework&quot;&gt;SiteCore&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Tapestry+framework&quot;&gt;Tapestry&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Maverick+framework&quot;&gt;Maverick&lt;/a&gt; and &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=JSP+framework&quot;&gt;JSP&lt;/a&gt;, &lt;br&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Barracuda+framework&quot;&gt;Barracuda&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Caramba+framework&quot;&gt;Ay Caramba&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Groovy+on+grails+framework&quot;&gt;Groovy on Grails&lt;/a&gt;,&lt;br&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Intercal+on+interstates&quot;&gt;Intercal on Interstates&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Cascade+framework&quot;&gt;Cascade&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=NHibernate+framework&quot;&gt;NHibernate&lt;/a&gt;, &lt;br&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=JDBC+framework&quot;&gt;JDBC&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Ruby+on+Rails+framework&quot;&gt;Ruby on Rails&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Jasmine+framework&quot;&gt;Jasmine&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Doctrine+framework&quot;&gt;Doctrine&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Java+Forms+Engine+framework&quot;&gt;Java Forms Engine&lt;/a&gt;, &lt;br&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Active+Record+framework&quot;&gt;Active Record&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=D3+framework&quot;&gt;D3&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Dapper+framework&quot;&gt;Dapper&lt;/a&gt; and &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Velocity+framework&quot;&gt;Velocity&lt;/a&gt;,&lt;br&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Thymeleaf+framework&quot;&gt;Thymeleaf&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=TopLink+framework&quot;&gt;TopLink&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Pyramid+framework&quot;&gt;Pyramid&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Rethync+framework&quot;&gt;Rethync&lt;/a&gt;, &lt;br&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Aura+framework&quot;&gt;Aura&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Rico+framework&quot;&gt;Rico&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Midori+framework&quot;&gt;Midori&lt;/a&gt; and &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Mojito+framework&quot;&gt;Mojito&lt;/a&gt;, &lt;/p&gt; &lt;p align=&quot;center&quot;&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Sitemesh+framework&quot;&gt;Sitemesh&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Cymbeline+framework&quot;&gt;Cymbeline&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Enterprise+Java+Beans+framework&quot;&gt;Enterprise Java Beans&lt;/a&gt;, &lt;br&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Hug+framework&quot;&gt;Hug&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Grok+framework&quot;&gt;Grok&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Boost+framework&quot;&gt;Boost&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Click+framework&quot;&gt;Click&lt;/a&gt;, anything by &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Telerik+framework&quot;&gt;Telerik&lt;/a&gt;, &lt;br&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Rango+framework&quot;&gt;Rango&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Dojo+framework&quot;&gt;Dojo&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=LLBLGen+Pro+framework&quot;&gt;LLBLGen Pro&lt;/a&gt;, &lt;br&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Carbonado+framework&quot;&gt;Carbonado&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Seaside+framework&quot;&gt;Seaside&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Pylons+framework&quot;&gt;Pylons&lt;/a&gt; and &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Pyroxide+framework&amp;amp;nfpr=1&quot;&gt;Pyroxide&lt;/a&gt; &lt;/p&gt; &lt;p align=&quot;center&quot;&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Fusebox+framework&quot;&gt;Fusebox&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Flight+framework&quot;&gt;Flight&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Flex+framework&quot;&gt;Flex&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=ServiceStack+framework&quot;&gt;ServiceStack&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Silex+framework&quot;&gt;Silex&lt;/a&gt;, &lt;br&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Carbon+framework&quot;&gt;Carbon&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Cocoa+framework&quot;&gt;Cocoa&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Ample+framework&quot;&gt;Ample&lt;/a&gt; and &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Giotto+framework&quot;&gt;Giotto&lt;/a&gt;, &lt;br&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Banshee+framework&quot;&gt;Banshee&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Symfony+framework&quot;&gt;Symfony&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Laravel+framework&quot;&gt;Laravel&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Fat+free+framework&quot;&gt;Fat Free&lt;/a&gt;, &lt;br&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Mocha+framework&quot;&gt;Mocha&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Pecker+framework&quot;&gt;Pecker&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Hobo+framework&quot;&gt;Hobo&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Cuba+framework&quot;&gt;Cuba&lt;/a&gt; and &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Rialto+framework&quot;&gt;Rialto&lt;/a&gt; &lt;/p&gt; &lt;p align=&quot;center&quot;&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=OpenRasta+framework&quot;&gt;OpenRasta&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Nancy+framework&quot;&gt;Nancy&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=ASP+net+MVC+framework&quot;&gt;ASP net MVC&lt;/a&gt;, &lt;br&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Kendo+framework&quot;&gt;Kendo&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Zend+framework&quot;&gt;Zend&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=ODBC+framework&quot;&gt;ODBC&lt;/a&gt; and &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Tempo+framework&quot;&gt;Tempo&lt;/a&gt;, &lt;br&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Java+Server+Faces+framework&quot;&gt;Java Server Faces&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Entity+Spaces+framework&quot;&gt;Entity Spaces&lt;/a&gt;, &lt;br&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Cappuccino+framework&quot;&gt;Cappuccino&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=SpecFlow+framework&quot;&gt;SpecFlow&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Polymer+framework&quot;&gt;Polymer&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=JDO+framework&quot;&gt;JDO&lt;/a&gt;, &lt;br&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=google+web+toolkit&quot;&gt;Google Web Toolkit&lt;/a&gt;, no need to learn Javascript,&lt;br&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=JDK+framework&quot;&gt;JDK&lt;/a&gt;, code away, what else do I have to say? &lt;/p&gt; &lt;p align=&quot;center&quot;&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Prototype+framework&quot;&gt;Prototype&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Boilerplate+framework&quot;&gt;Boilerplate&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=jQuery+framework&quot;&gt;jQuery&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=animate+framework&quot;&gt;animate&lt;/a&gt;, &lt;br&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Mustache+framework&quot;&gt;Mustache&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=wijmo+framework&quot;&gt;Wijmo&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Ionic+framework&quot;&gt;Ionic&lt;/a&gt; and &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Allegro+framework&quot;&gt;Allegro&lt;/a&gt;,&lt;br&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Bootstrap+framework&quot;&gt;Bootstrap&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Backbone+framework&quot;&gt;Backbone&lt;/a&gt;, running on an iPhone; &lt;br&gt;Mobile apps, going live, built in HTML5&lt;br&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Angular+framework&quot;&gt;Angular&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Scriptaculous+framework&quot;&gt;Scriptaculous&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=react+framework&quot;&gt;react&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=redux+framework&quot;&gt;redux&lt;/a&gt;, &lt;br&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Knockout+framework&quot;&gt;Knockout&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=ember+framework&quot;&gt;ember&lt;/a&gt;, does anyone remember, &lt;br&gt;It’s just like in the browser wars, &lt;br&gt;IExplore and Netscape 4,&lt;br&gt;&lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Lodash+framework&quot;&gt;lodash&lt;/a&gt; vs &lt;a href=&quot;https://www.google.co.uk/webhp?hl=en#hl=en&amp;amp;q=Underscore+framework&quot;&gt;underscore&lt;/a&gt;, &lt;br&gt;and I can’t take it any more!&lt;/p&gt; &lt;p align=&quot;center&quot;&gt;&lt;small&gt;(yes, I know ODBC and JDBC aren&apos;t strictly speaking frameworks.)&lt;/small&gt;&lt;/p&gt; &lt;p align=&quot;center&quot;&gt;&lt;small&gt;(yes, I also know the recorded version uses Tempo twice. Relax. It&apos;s OK.)&lt;/small&gt;&lt;/p&gt;</description>
          <pubDate>2016-05-05T17:32:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2016/05/05/were-gonna-build-framework.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2016/05/05/were-gonna-build-framework.html</guid>
        </item>
      
    
      
        <item>
          <title>London.NET User Group – A Progressive.NET Special!</title>
          <description>&lt;p align=&quot;justify&quot;&gt;This July, the &lt;a href=&quot;https://skillsmatter.com/conferences/7235-progressive-dot-net-tutorials-2016&quot;&gt;Progressive.NET Tutorials&lt;/a&gt; returns to SkillsMatter – for the eighth year running, we’re bringing together some of the best speakers and experts in the .NET community for three days of in-depth workshops and hands-on tutorials. The team at SkillsMatter are working hard to finalize what promises to be a great programme, covering all the latest platforms, patterns and ideas in the world of .NET development.&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;To coincide with this, we’re running a &lt;a href=&quot;http://www.meetup.com/London-NET-User-Group/events/228252666/&quot;&gt;special London.NET User Group meetup&lt;/a&gt; at SkillsMatter the night before the conference – Tuesday 21st June.&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-gfbAO3wJDY8/VyqGIYzNNiI/AAAAAAAAD60/sWXSPvq4EJg/s1600-h/image%25255B3%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; float: right; padding-top: 0px; padding-left: 0px; border-left: 0px; margin: 10px 0px 10px 10px; display: inline; padding-right: 0px&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;https://lh3.googleusercontent.com/-E3fbDAlRW2U/VyqGIv6AJnI/AAAAAAAAD64/5UfYXQhfxpU/image_thumb%25255B1%25255D.png?imgmax=800&quot; width=&quot;128&quot; align=&quot;right&quot; height=&quot;128&quot;&gt;&lt;/a&gt;We’re going to be hosting a series of short talks and demos, around a theme of “zero footprint” development tools. Over the years, we’ve had a lot of really excellent talks covering languages, platforms, patterns, architectural styles – but quite often, we’ll be in the pub afterwards chatting about the talk we’ve just seen, and you’ll hear people saying “it looks really great; maybe I can try it out on my next project” or “I love the idea but I can’t see the rest of the team going for it.” &lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;So, for our Progressive.NET special, we want to showcase the very best tools, utilities and ideas that you can start using right away – they won’t modify your code, they won’t break your build pipeline, they won’t interfere with what the rest of your team are doing, but they might make your day just a little bit more pleasant – and we want your help!&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;Come along and give us a 10-minute demo of something you think is awesome. Maybe it’s an open source tool, maybe it’s one of your company’s products, maybe it’s just something you use daily and can’t live without – but remember: &lt;strong&gt;zero footprint.&lt;/strong&gt; Don’t break the build, don’t don’t &lt;a href=&quot;http://tvtropes.org/pmwiki/pmwiki.php/Literature/WhoMovedMyCheese&quot;&gt;move the cheese&lt;/a&gt;.&lt;/p&gt; &lt;p align=&quot;justify&quot;&gt;In the spirit of things like &lt;a href=&quot;http://www.hanselman.com/blog/ScottHanselmans2014UltimateDeveloperAndPowerUsersToolListForWindows.aspx&quot;&gt;Scott Hanselman’s 2014 Ultimate Developer and Power Users Tool List&lt;/a&gt;, we’re looking for talks about:&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;div align=&quot;justify&quot;&gt;Test runners like &lt;a href=&quot;http://www.ncrunch.net/&quot;&gt;NCrunch&lt;/a&gt; and &lt;a href=&quot;http://testdriven.net/&quot;&gt;TestDriven.NET&lt;/a&gt;&lt;/div&gt;&lt;/li&gt; &lt;li&gt; &lt;div align=&quot;justify&quot;&gt;Git clients – like &lt;a href=&quot;https://desktop.github.com/&quot;&gt;GitHub Desktop&lt;/a&gt; and &lt;a href=&quot;https://www.gitkraken.com/&quot;&gt;GitKraken&lt;/a&gt;&lt;/div&gt;&lt;/li&gt; &lt;li&gt; &lt;div align=&quot;justify&quot;&gt;Static analysis tools, like &lt;a href=&quot;http://www.ndepend.com/&quot;&gt;NDepend&lt;/a&gt; and &lt;a href=&quot;https://stylecop.codeplex.com/&quot;&gt;StyleCop&lt;/a&gt;, that help you visualise the complexities of the code you’re working on.&lt;/div&gt;&lt;/li&gt; &lt;li&gt; &lt;div align=&quot;justify&quot;&gt;Diagnostic and monitoring tools like &lt;a href=&quot;https://ngrok.com/&quot;&gt;ngrok&lt;/a&gt;, &lt;a href=&quot;https://www.runscope.com/radar/eeqbdlkx44b1&quot;&gt;RunScope&lt;/a&gt; and &lt;a href=&quot;http://www.red-gate.com/products/dotnet-development/ants-performance-profiler/&quot;&gt;ANTS Profiler&lt;/a&gt;&lt;/div&gt;&lt;/li&gt; &lt;li&gt; &lt;div align=&quot;justify&quot;&gt;Productivity tools for developers, like &lt;a href=&quot;http://www.powercmd.com/&quot;&gt;Powercmd&lt;/a&gt; and &lt;a href=&quot;http://instant-eyedropper.com/&quot;&gt;Instant Eyedropper&lt;/a&gt;&lt;/div&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p align=&quot;justify&quot;&gt;Whether you’re a first-timer speaker or a fulltime product evangelist, we’d love to have you on board, so have a think, get in touch – by &lt;a href=&quot;mailto:dylan@dylanbeattie.net+subject=London.NET+Prognet+Special&quot;&gt;email&lt;/a&gt; or find us &lt;a href=&quot;https://twitter.com/londondotnet&quot;&gt;@LondonDotNet&lt;/a&gt; on Twitter - and get involved!&lt;/p&gt;</description>
          <pubDate>2016-05-04T23:30:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2016/05/04/londonnet-user-group-progressivenet.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2016/05/04/londonnet-user-group-progressivenet.html</guid>
        </item>
      
    
      
        <item>
          <title>Hintjens</title>
          <description>&lt;p&gt; &lt;p&gt;I’m at BuildStuff Vilnius, in November 2015. It’s Thursday night. Mark Rendle and I are doing our comedy panel game quiz thing. We found out about ten minutes ago that we’re doing our show in a nightclub, with no wi-fi, hardly any microphones and… basically it’s a bit of a train crash. And we’re hustling for volunteers to help us make the train crash funny. Between Mark’s friends and mine, we rope in half-a-dozen people. One of them is this weird tall Belgian guy. He’s good; they’re all good. He gets it. He’s funny and engaging and genuinely interesting. We somehow walk away with our collective dignity intact, and people even tell us afterwards that they loved it. Mark and I swear to each other we’re never doing that particular show in a nightclub ever again. &lt;p&gt;We’re still in Vilnius, it’s Saturday, and I’ve had the day off. I’ve been sightseeing. I’m tired and hungry, and I don’t want to just head back to the hotel, so I find a café with wi-fi, I look up Vilnius on MetalTravelGuide.com, and I find this bar – Bix Baras. I go in, I chat to the staff. Their English, like their beer, is excellent; my Lithuanan barely covers “hello” and “thank you” – but I eat lunch and have a few beers, and then head back to the hotel. When I get there, the tall Belgian guy from the quiz is playing the piano – one of those wonderful grand pianos that adorn hotel bars the world over without anyone ever really playing them. I ask if I can join him; he moves over, I sit down, we play for a while – he’s doing most of the work, I’m just bouncing along on the white notes, picking out pentatonic minor melodies that fit with what he’s doing. It’s fun. It’s nice, and it feels somehow conspiratorial – for starters, we’re playing one of those pianos that you probably walk past every day of your life and assume that it’s for Other People to play, and not for you, and yet here we are. &lt;p&gt;I go upstairs, change my shirt, ask Seb if he fancies heading downtown for a beer. We need a night off from the whole conference crowd, but on a whim I ask the Belgian guy on the piano if he wants to join us. He says yes, and introduces himself as Pieter. We’ve officially met. We jump in a cab and head downtown. &lt;p&gt;Bix Baras has good beer, and great snacks, and we talk – myself and Seb and Pieter. We drink, we eat hard cheese and pigs’ ears and Lithuanian dark bread. And it’s remarkable, because there’s no small talk. We talk about ideas, and we share experiences. The conversation is disarmingly easy. I’m not used to this. Most people at conferences talk about tech – about .NET or NodeJS or Docker. We talk about life. We talk about what we do, and why we care. We talk about friendship, and failed relationships, and psychopaths, and adventures. We walk up the road to the pool hall where some of the other BuildStuff gang are having drinks. Pieter and I get talking about speaking. Within the hour, he’s challenged pretty much every idea I’ve ever had about speaking and giving talks, but it doesn’t feel adversarial – there’s something genuinely inspirational about it. We finish our drinks and wander back to the hotel, but the conversation resonates. &lt;p&gt;Sunday, we fly to Kyiv – a whole crowd of us. I’m walking next to Pieter on the tarmac as we head out to our plane, and he’s talking about how much he’s enjoying the experience – “For the first time ever I feel like I’m on the road with my gang” – and I know exactly what he means. The sense of camaraderie is wonderful – 30-odd hardcore geeks heading out to Ukraine together – yet it somehow didn’t really click until Pieter pointed it out.  &lt;p&gt;In Kyiv, we hang out. We chat. We talk about code, about community, about psychology. I watch his talk about building open source communities. From where I’m sitting, he appears to give a 50-minute talk with no notes and no slides, and solve a Rubik’s cube while he’s doing it. He confides in me afterwards that the cube was a bit of a stunt – shuffle it a couple of turns, memorise them, play them backwards on stage – but that almost doesn’t matter; the talk is brilliant, the audience are involved and engaged, and I’m sat there wondering how much of my life I’ve spent making Powerpoint slides, and why… &lt;p&gt;Eventually, Pieter turns our conversation in that bar in Vilnius into a blog post – &lt;a href=&quot;http://hintjens.com/blog:107&quot;&gt;Ten Steps to Better Public Speaking&lt;/a&gt; – which is simultaneously gratifying and terrifying. Gratifying that he thinks our conversation is interesting enough to warrant an entire blog post. Terrifying, because when you’re name-checked in a post like that, the only thing you can really do is rise to the challenge, and that means I’m gonna need to REALLY work hard on… well, on every talk I ever give again. &lt;p&gt;Months pass. I think often of our conversation in that pool hall in Vilnius and the blog post that followed. One day, I email Pieter – “Hey, remember that chat in Vilnius? Do you fancy doing a joint talk at NDC Oslo?” He says yes, I write something up, I send it over, and start worrying about the fact I’ll be sharing a stage with the great Pieter Hintjens – and about the fact I’ve signed up to give a talk that’s gonna drag me out of my comfort zone in almost every way. &lt;p&gt;By chance, I’m in Brussels in March, en route to a long weekend in Leuven with my girlfriend. I email Pieter, we arrange to meet for lunch: we talk about ideas. He’s riffing on ideas – about opening an office in Brussels for people who need a place to hack; about using mesh networking to build “smart chairs” that tell the pavement café when they need replacing; about speaking and software and people and life. He talks about his father, about euthanasia, about family. We talk briefly about our joint talk and NDC, but not too much; after all, too much rehearsal would undermine the vulnerability. And we part with a hug, and a promise to see each other in Oslo. &lt;p&gt;I watch Pieter and @jesslynnrose &lt;a href=&quot;https://twitter.com/Freerange_Inc/status/709145792565657600&quot;&gt;joking on Twitter&lt;/a&gt; about gender-swapped TV shows. Pieter posts &lt;a href=&quot;https://twitter.com/hintjens/status/709851145339072512&quot;&gt;this&lt;/a&gt;: prescient, or just meditative? Then on March 26th, following a whole lot of the kind of fallout that just doesn’t fit into 140 characters, Pieter &lt;a href=&quot;https://t.co/y6PIU2p2zV&quot;&gt;announces he’s leaving Twitter&lt;/a&gt;. I’m sad to see him go, but have no doubt I’ll have many more evenings hanging out and having my preconceptions challenged by this remarkable individual. &lt;p&gt;Then I get an email. The subject just says “NDC” It reads: &lt;blockquote&gt; &lt;p&gt;“Hi Dylan, &lt;p&gt;Seems my cancer has come back... still waiting for detailed prognosis and next steps. Looks pretty bad atm. In any case, no travel for me for the next months. &lt;p&gt;You&apos;re going to have to do the talk by yourself. Stick to the ten rules, watch my Serbian video a couple of times and you&apos;ll do fine. :) &lt;p&gt;Sorry about this.”&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;I don’t care about the talk. I’m worried about my friend – this sounds bad. I email him back. He replies. Time passes. He rejoins Twitter, because it’s a good way to connect with a lot of people who want to know what’s going on. And then he posts this: &lt;blockquote&gt; &lt;h4&gt;We will try chemotherapy. It&apos;s palliative, there is no cure for this. So, time to start saying goodbye.&lt;/h4&gt;&lt;/blockquote&gt; &lt;p&gt;And then he posts “&lt;a href=&quot;http://hintjens.com/blog:115&quot;&gt;A Protocol for Dying&lt;/a&gt;”, and it’s pretty clear that this is it. One way or another, it won’t be long before Pieter’s not around any more. And people start talking, and posting, and tweeting… and before long, a common thread emerges. It seems you really didn’t need to spend very much time with Pieter for him to leave a lasting impression. &lt;p&gt;I spent five days with Pieter late last year, and had lunch with him once, a few months ago. I’ve never visited his house, never met his family, never collaborated with him – but the time I’ve spent with him and the conversations we’ve shared have been some of the most profoundly challenging and inspiring interactions I’ve had in a very long time. And it’s not just me. There are countless comments on Pieter’s most recent blog posts from people who met him once or twice – or not at all, in the case of the people who know Pieter through email and through his code – but whom nevertheless believe that knowing him has had a profound impact on their life. &lt;p&gt;I was in a restaurant earlier tonight, with my girlfriend, Clare, and some of my cow-orkers. We ended up talking about Pieter. Clare met Pieter briefly, for about five minutes, in Bruxelles-Midi railway station back in March. At the time, Clare was feeling completely freaked out at being in an unfamiliar country where she didn’t speak the language or know how things worked, and my meeting up with this weird guy who “looked really stern” didn’t help at all. Pieter warned us (a pair of hardcore Londoners) about the risks and dangers of hanging out in the station, and then helped Clare find her train to Leuven.  &lt;p&gt;When I got that first email from Pieter, I told Clare. When I saw his Twitter post, and when he posted “A Protocol for Dying”, I told Clare – and she’d already read it. And then she said to me tonight “I want to email Pieter. I don’t know him, but I know what’s happening, and I just want him to know that I’ll remember him next time – probably every time? – that I go through Brussels, and I hope one day I’ll be a bit more badass – just like he is.” &lt;p&gt;So here’s to Pieter, and here’s hoping that long after he’s stopped coding and tweeting and blogging, he’ll still be inspiring all of us to open up, to embrace our vulnerability and “to be a bit more badass”. </description>
          <pubDate>2016-04-27T01:30:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2016/04/27/hintjens.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2016/04/27/hintjens.html</guid>
        </item>
      
    
      
        <item>
          <title>Coming Soon To A City Near You…</title>
          <description>&lt;p&gt;Following on from &quot;The Rest of REST&quot; talk that I&apos;ve given at several conferences over the last year, I&apos;ll be talking about real-world REST at several user groups over the next few months. In this talk, I&apos;ll be exploring some REST architectural patterns in more depth, and doing some hands-on demos showing how you can implement these patterns in your own applications. &lt;/p&gt; &lt;p&gt;For this demo, I&apos;ll be using NancyFX and the HAL+JSON hypermedia language – there&apos;s a beautiful synergy between the &lt;strong&gt;dynamic&lt;/strong&gt; type model in C#, the dynamic model used in NancyFX and the way HAL exploits the dynamic features of JavaScript to extend JSON into a powerful hypermedia format, and I&apos;ll be showing how you can wire all these bits together to build flexible, RESTful HTTP APIs.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;May 9th &lt;/strong&gt;I&apos;ll be at &lt;a href=&quot;http://www.meetup.com/Smart-Devs-User-Group/&quot;&gt;&lt;strong&gt;Smart Devs User Group&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt; &lt;/strong&gt;in Hereford.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;May 18th &lt;/strong&gt;I&apos;ll be at&lt;strong&gt; &lt;/strong&gt;&lt;a href=&quot;https://ti.to/the-dev-bakery/may-2016&quot;&gt;&lt;strong&gt;The Dev Bakery&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt; &lt;/strong&gt;in Altrincham.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;May 26th, &lt;/strong&gt;I&apos;ll be at the &lt;a href=&quot;http://www.meetup.com/Copenhagen-Net-User-Group/&quot;&gt;&lt;strong&gt;Copenhagen .NET User Group&lt;/strong&gt;&lt;/a&gt; in Denmark – and many thanks to the team over at &lt;a href=&quot;http://siteimprove.com/&quot;&gt;&lt;strong&gt;Siteimprove&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;&amp;nbsp;&lt;/strong&gt;for hosting and sponsoring the event.&lt;/p&gt; &lt;p&gt;&lt;a title=&quot;Siteimprove.com : Web Governance Made Easy&quot; href=&quot;http://siteimprove.com/&quot;&gt;&lt;img style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; display: block; padding-right: 0px; border-top-width: 0px; margin-right: auto&quot; border=&quot;0&quot; alt=&quot;Web Governance Tools | Siteimprove&quot; src=&quot;http://ww1.prweb.com/prfiles/2014/01/13/11680888/gI_61847_logo-inline-with-tagline_web.png&quot;&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;As always - if your user group would be interested in a session on this or one of my of other talks, &lt;a href=&quot;mailto:dylan@dylanbeattie.net?subject=Speaking+enquiry&quot;&gt;get in touch&lt;/a&gt;!&lt;/p&gt; &lt;p&gt;I&apos;ll also be appearing at these conferences throughout 2016:&lt;/p&gt; &lt;p&gt;&lt;strong&gt;8/9/10 June &lt;/strong&gt;I’ll be at&lt;strong&gt;&amp;nbsp;&lt;/strong&gt;&lt;a href=&quot;http://ndcoslo.com/speaker/dylan-beattie/&quot;&gt;NDC { Oslo }&lt;/a&gt; in Norway, and I’ll be sticking around for &lt;a href=&quot;https://pubconf.io/&quot;&gt;PubConf at NDC { Oslo }&lt;/a&gt; on 11th June – the PubConf event in London here in January was an absolute blast, so if you’re going to be in Oslo for NDC, book yourself a hotel for the weekend, see some of the city, and come to PubConf.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;22-24 June &lt;/strong&gt;I’ll be at the &lt;a href=&quot;https://skillsmatter.com/conferences/7235-progressive-dot-net-tutorials-2016#program&quot;&gt;Progressive.NET Tutorials&lt;/a&gt; at SkillsMatter here in London, running a deep-dive hands-on workshop which will probably be on async/await and asynchronous programming in C# – watch this space for further announcements as we finalise the programme.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;8-9 July &lt;/strong&gt;I’ll be at &lt;a href=&quot;http://buildstuff.com.ua/&quot;&gt;BuildStuff Odessa&lt;/a&gt; – following the successful and hugely enjoyable BuildStuff conference in Kyiv last year, I’m really excited to be going back to Ukraine for a weekend of code, games and sunshine by the sea. &lt;/p&gt; &lt;p&gt;&lt;strong&gt;13-15 July &lt;/strong&gt;I’ll be at &lt;a href=&quot;https://skillsmatter.com/conferences/7278-fullstack-2016-the-conference-on-javascript-node-and-internet-of-things&quot;&gt;FullStack 2016&lt;/a&gt; in London, the conference on “JavaScript, NodeJS and the Internet of Things” – I’ve been helping the team at SkillsMatter finalise the programme for this one, we’ve got some absolutely excellent speakers and sessions lined up, and it promises to be a great event. &lt;/p&gt; &lt;p&gt;&lt;strong&gt;3/4/5 August &lt;/strong&gt;I’m going to be at &lt;a href=&quot;http://ndcsydney.com/speaker/dylan-beattie/&quot;&gt;NDC { Sydney }&lt;/a&gt;, joining an &lt;a href=&quot;http://ndcsydney.com/speakers/&quot;&gt;amazing programme of speakers&lt;/a&gt; to bring the NDC experience to the southern hemisphere. &lt;/p&gt; &lt;p&gt;See you on the road!&lt;/p&gt; &lt;p align=&quot;center&quot;&gt;&lt;img title=&quot;&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;National Flag of Denmark&quot; src=&quot;http://www.flags.net/images/largeflags/DENM0001.GIF&quot; width=&quot;77&quot; height=&quot;64&quot;&gt;&amp;nbsp; &lt;img title=&quot;&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;Norwegian Flag&quot; src=&quot;http://www.flags.net/images/largeflags/NORW0001.GIF&quot; width=&quot;87&quot; height=&quot;64&quot;&gt;&amp;nbsp;&lt;img alt=&quot;Image of National Flag&quot; src=&quot;http://www.flags.net/images/largeflags/UKRN0001.GIF&quot; width=&quot;94&quot; height=&quot;64&quot;&gt;&amp;nbsp;&lt;img title=&quot;&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;Flag of the United Kingdom&quot; src=&quot;http://www.flags.net/images/largeflags/UNKG0001.GIF&quot; width=&quot;125&quot; height=&quot;64&quot;&gt;&amp;nbsp; &lt;img title=&quot;&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;Australian Flag&quot; src=&quot;http://www.flags.net/images/largeflags/ASTL0001.GIF&quot; width=&quot;125&quot; height=&quot;64&quot;&gt;&lt;/p&gt;</description>
          <pubDate>2016-04-19T10:47:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2016/04/19/coming-soon-to-city-near-you.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2016/04/19/coming-soon-to-city-near-you.html</guid>
        </item>
      
    
      
        <item>
          <title>The Axosoft GitKraken Songwriting Battle!</title>
          <description>&lt;p&gt;A few weeks ago, I stumbled across &lt;a href=&quot;https://www.gitkraken.com/&quot;&gt;GitKraken&lt;/a&gt; – a cross-platform GUI for Git that looks, well, lovely. ‘Cos git is a wonderful, amazing, powerful system, but it’s about time somebody added a little, well, beauty around the place. &lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-Tb2MMlvx8Cw/VxBAWggVpLI/AAAAAAAAD54/24lgUven3r4/s1600-h/image%25255B5%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;https://lh3.googleusercontent.com/-yMZ52HZabrI/VxBAXAgn7CI/AAAAAAAAD6A/CPgNKpE58FE/image_thumb%25255B3%25255D.png?imgmax=800&quot; width=&quot;560&quot; height=&quot;415&quot;&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;So I’m using GitKraken daily now, and loving it, and then I find out that they’re having a contest. &lt;a href=&quot;https://blog.axosoft.com/2016/04/04/gitkraken-version-1-launch-contest/&quot;&gt;A song-writing contest. About revision control&lt;/a&gt; systems. Now, I love music. I play instruments. I write songs. I write parody songs about tech stuff. There is absolutely no way I can see a songwriting contest about revision control and not enter it… so I did. And here’s my entry. A completely original song, written and recorded for the contest, and called, simply, Git Kraken. &lt;/p&gt;&lt;iframe height=&quot;450&quot; src=&quot;https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/259106656&amp;amp;auto_play=false&amp;amp;hide_related=false&amp;amp;show_comments=true&amp;amp;show_user=true&amp;amp;show_reposts=false&amp;amp;visual=true&quot; frameborder=&quot;no&quot; width=&quot;100%&quot; scrolling=&quot;no&quot;&gt;&lt;/iframe&gt;</description>
          <pubDate>2016-04-15T01:14:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2016/04/15/the-axosoft-gitkraken-songwriting-battle.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2016/04/15/the-axosoft-gitkraken-songwriting-battle.html</guid>
        </item>
      
    
      
        <item>
          <title>Exupérianism: Improving Things by Removing Things</title>
          <description>&lt;p&gt;Last night this popped up on Twitter:&lt;/p&gt; &lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&gt; &lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;There should be a term for when you add a new feature with net negative lines of code. &lt;a href=&quot;https://t.co/yZB0uuq3H5&quot;&gt;pic.twitter.com/yZB0uuq3H5&lt;/a&gt;&lt;/p&gt;— John Sheehan (@johnsheehan) &lt;a href=&quot;https://twitter.com/johnsheehan/status/718208807302602752&quot;&gt;April 7, 2016&lt;/a&gt;&lt;/blockquote&gt; &lt;p&gt;Last year, as part of migrating our main web stack to AWS, we created a set of conventions for things like connection strings and API endpoint addresses across our various environments, and then updated all of our legacy systems to use these conventions instead of fragile per-environment configuration. This meant deleting quite a lot of code, and reviewing pull requests with a lot more red lines than green lines in them – I once reviewed a PR which removed 600 lines of code across fifteen different files, and added nothing. No new files, no new lines - no green at all – and yet that change made one of our most complicated applications completely environment-agnostic. It was absolutely delightful.&lt;/p&gt; &lt;p&gt;When I saw John&apos;s tweet, what instantly came to mind was a quote from &lt;a href=&quot;https://en.wikipedia.org/wiki/Antoine_de_Saint-Exup%C3%A9ry&quot;&gt;Antoine de Saint-Exupéry&lt;/a&gt;:&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;&lt;em&gt;&quot;Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away.&quot;&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;So how about we adopt the term &quot;Exupérian&quot; for any change which improves something by making it smaller or simpler? The commit that removes 600 lines of unnecessary configuration. The copy-edit that turns fifteen thousand words of unstructured waffle into ten thousand words of focused, elegant writing. Maybe even that one weekend you spent going through all the clutter in your garage and finally getting rid of your unwanted lamps and old VHS tapes.&lt;/p&gt; &lt;p&gt;Saint-Exupéry was talking about designing aircraft, but I think the principle is equally applicable to software, to writing, to music, to architecture – in fact, to just about any creative process. I was submitting papers to a couple of conferences last week, and discovered that &lt;a href=&quot;http://oredev.org/&quot;&gt;Øredev&lt;/a&gt; has a 1,000-character limit for session descriptions. Turns out my session descriptions all end up around 2,000-3,000 characters, and editing those down to 1,000 characters is &lt;em&gt;really &lt;/em&gt;hard. But - it made them better. You look at every single word, you think &apos;does it still work if I remove this?&apos;, and it&apos;s surprising how often the answer is &apos;yes&apos;. &lt;/p&gt; &lt;p&gt;Go on, give it a try. Do something #exuperian today. Edit that email before you send it. Remove those two classes that you&apos;re sure aren&apos;t used any more but you&apos;re scared to delete in case they break something. Throw out the dead batteries and expired coupons from your desk drawer. Remove a pointless feature nobody really wants.&lt;/p&gt; &lt;p&gt;Maybe you even have an &lt;a href=&quot;http://www.dylanbeattie.net/2016/01/confession-time-i-implemented-eu-cookie.html&quot;&gt;EU cookie banner&lt;/a&gt; you can get rid of? :)&lt;/p&gt;</description>
          <pubDate>2016-04-08T10:53:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2016/04/08/exuperianism-improving-things-by.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2016/04/08/exuperianism-improving-things-by.html</guid>
        </item>
      
    
      
        <item>
          <title>How to *really* break the internet.</title>
          <description>&lt;p&gt;Yesterday someone broke the internet, and this time it had nothing to with Kim Kardashian. According to &lt;a href=&quot;http://www.theregister.co.uk/2016/03/23/npm_left_pad_chaos/&quot;&gt;The Register&lt;/a&gt; and various other sources, developer &lt;a href=&quot;https://medium.com/@azerbike&quot;&gt;Azer Koçulu&lt;/a&gt;&amp;nbsp;&lt;a href=&quot;https://medium.com/@azerbike/i-ve-just-liberated-my-modules-9045c06be67c#.uaiwz5q49&quot;&gt;removed over 250 of his own modules from NPM&lt;/a&gt;, the package management platform used by almost all open-source JavaScript projects. Among the projects he removed was left-pad, a tiny library that performs string padding. Turns out that literally thousand of open-source projects depend on left-pad – over 2 million downloads last month – and with left-pad removed from NPM, suddenly none of these projects would build any more. Oops.&lt;/p&gt; &lt;p&gt;It&apos;s worth remembering this is JavaScript, and one of the many things that makes JavaScript so entertaining is that it doesn&apos;t have a standard runtime library. In every other language I have ever used, string padding is either built-in, or it&apos;s part of a standard runtime that&apos;s available locally on every workstation and build server. But not in JavaScript – if you want to pad a string in JS, you either write your own function, you copy &amp;amp; paste one from StackOverflow, or you import a package to do it for you, and based on the fallout from yesterday it looks like a lot of people went for the package option.&lt;/p&gt; &lt;p&gt;&lt;a title=&quot;A typical package repository.&quot; href=&quot;http://indianajones.wikia.com/wiki/Hangar_51&quot;&gt;&lt;img title=&quot;A typical package repository.&quot; alt=&quot;A typical package repository.&quot; src=&quot;http://vignette2.wikia.nocookie.net/indianajones/images/9/9a/400.jpg/revision/latest?cb=20080523032114&quot; width=&quot;500&quot; height=&quot;216&quot;&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;Now, package management and dependency management is hard. Projects like NPM and RubyGems and NuGet appear to have made it a lot easier, but in most cases what they&apos;re actually doing is asking us to relinquish control of elements of our own projects in exchange for convenience. And they &lt;em&gt;are&lt;/em&gt; convenient - as long as you&apos;re online, and everything&apos;s working, and everybody&apos;s being friendly, it&apos;s great – but I don&apos;t think we, as an industry, have done nearly enough to understand what happens if those assumptions turn out to be false.&lt;/p&gt; &lt;p&gt;Say you&apos;re about to get on a long-haul flight; just before they call your gate, you git clone a couple of projects onto your laptop and then board the plane. Would you be able to build the project once in flight? I suspect in many cases the answer is &quot;no&quot;, because standard practice these days is to download all the required libraries as the first stage of the build process – and that means you can&apos;t build your project without being online.&lt;/p&gt; &lt;p&gt;Ok, that one&apos;s easy. This time round you shrug and watch the inflight movies instead, and next time you remember to do a full build before you go offline. Not a big deal. But what would happen if nuget.org was offline? Online services go dark for all sorts of reasons - infrastructure problems, legal action, DoS attacks. Most of the time, they come back, but not always – in 2014, &lt;a href=&quot;http://www.theregister.co.uk/2014/06/18/code_spaces_destroyed/&quot;&gt;Code Spaces was completely wiped out&lt;/a&gt; by an attacker who gained access to their AWS account. Does your build pipeline quietly rely on the fact that nuget.org isn&apos;t going to go away? And if it did, how long would it take to get things back up and running? &lt;/p&gt; &lt;blockquote lang=&quot;en&quot; class=&quot;imgur-embed-pub&quot; data-id=&quot;akk7O3a&quot;&gt;&lt;a href=&quot;//imgur.com/akk7O3a&quot;&gt;View post on imgur.com&lt;/a&gt;&lt;/blockquote&gt;&lt;script async src=&quot;//s.imgur.com/min/embed.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt; &lt;p&gt;But NuGet or NPM going down isn&apos;t even the worst case scenario. According to &lt;a title=&quot;https://www.nuget.org/stats/packages&quot; href=&quot;https://www.nuget.org/stats/packages&quot;&gt;NuGet.&lt;/a&gt;org, the most download packages over the last six weeks are:&lt;/p&gt; &lt;ol&gt; &lt;li&gt;NewtonSoft.Json (2.4M downloads)  &lt;li&gt;jQuery (1.2M downloads)  &lt;li&gt;Microsoft.AspNet.Mvc (993K downloads)  &lt;li&gt;Microsoft.AspNet.Razor (987K downloads)  &lt;li&gt;EntityFramework (974K downloads)&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;Now, let&apos;s imagine a Nefarious Super Villain breaks into James Newton-King&apos;s house one night and forces him at gunpoint to deploy a new point release of NewtonSoft.Json. A release that works perfectly, only every time you call JsonConvert.Serialize(), it sneakily posts a copy of the JSON output to an IP address in Russia. How long before you ended up running this malicious code on your own production systems? How long before you noticed something was amiss – assuming you ever noticed at all? OK, somebody would notice eventually – that&apos;s the beauty of open-source, after all – but what about closed-source libraries? If you&apos;re using EntityFramework, I&apos;d wager good money that you&apos;re using a single set of database credentials that has read/write/delete access to all your data – and trusting that the code isn&apos;t going to do anything unpleasant.&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;EDIT: &lt;a href=&quot;https://twitter.com/demisbellot/status/712637426611654656&quot;&gt;Demis Bellot pointed out on Twitter&lt;/a&gt; after I first posted this article that source code is just one of many attack vectors that exploit our faith in package repositories. Unless you&apos;re comparing checksums or building your own reference binaries, you&apos;re blindly trusting that the source you&apos;re reading on Github is the same source that was used to built the binaries that are now running on your production servers.&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;There&apos;s ways around some of this. At the very least, cache the packages used by your recent builds in case you need to run them again. Most package repositories run over HTTP and use stable URLs, so all you really need is a caching proxy between your build pipeline and your package repo servers. Here at Spotlight, we run a NuGet server on our LAN that hosts our own packages, and we&apos;ve also set up TeamCity so that following a successful build, all the .nupkg files (including the dependencies used by the build) are published to our local NuGet server. We actually started doing this by mistake – we set up a build to publish our own .nupkg files to our server and then realised afterwards it had also picked up all the package dependencies – but it actually works pretty well, and it means that if nuget.org was to go dark for a while, we could still build &amp;amp; deploy software as long as we didn&apos;t update any package dependencies.&lt;/p&gt; &lt;p&gt;As for package dependencies as an attack vector for malicious code? That one&apos;s a lot harder. We often do some ad-hoc traffic inspection as part of our review process – fire up the application with SQL Monitor and Fiddler running, take a look at the network traffic, database activity, debug logs and so on just to make sure we haven&apos;t done something stupid – but it&apos;s interesting to think how this could be turned into something more rigorous.&lt;/p&gt; &lt;p&gt;But as with so much else, it boils down to a choice – trust people, or do everything yourself. One is a calculated risk, one is a guaranteed expense, and it&apos;s managing that balance between risk and cost that makes IT – and business – so endlessly fascinating.&lt;/p&gt;</description>
          <pubDate>2016-03-23T12:08:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2016/03/23/how-to-really-break-internet.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2016/03/23/how-to-really-break-internet.html</guid>
        </item>
      
    
      
        <item>
          <title>The Mysterious Case of the Latin General: Collations in SQL Server</title>
          <description>&lt;p&gt;Computers deal in absolutes. True, false, yes, no. So when you ask a computer if two things are the same, it can’t get away with “well, sort of… it depends, really; why are you asking?”. &lt;/p&gt; &lt;p&gt;But we use computers to model reality, and reality is a horrible mess of edge cases, quirks and idiosyncracies. Today is Leap Year Day – February 29th. Which means you can do this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;var today = DateTime.Now.AddYears(1).AddYears(-1);
var yesterday = DateTime.Now.AddDays(-1);&lt;br&gt;if (today == yesterday) {
    Console.WriteLine(&quot;In your face, Einstein!&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And if you think that&apos;s messy, wait until you start looking at string comparisons. Which, by happy coincidence, was the thing on the top of my stack when I got into work this morning.&lt;/p&gt;
&lt;p&gt;OK, pop quiz time! Are these statements true or false?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&quot;foo&quot; == &quot;foo&quot; 
&lt;li&gt;&quot;FOO&quot; == &quot;foo&quot; 
&lt;li&gt;&quot;café&quot; == &quot;cafe&quot; 
&lt;li&gt;&quot;œnophile&quot; == &quot;oenophile&quot; 
&lt;li&gt;&quot;strasse&quot; = &quot;straße&quot; 
&lt;li&gt;&quot;SIGURÐAR&quot; == &quot;SIGURDAR&quot;&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;Well? How did you do? See, the answer in almost every case is that lovely old chestnut &quot;it depends&quot;. And when you&apos;re dealing with text columns in Microsoft SQL Server, the thing it depends on is called a &lt;strong&gt;collation. &lt;/strong&gt;A collation is a set of rules that defines whether or not two strings should be considered to be equal, and what &apos;alphabetical order&apos; means for ordering a given set of strings. For years, my team has run most of our databases systems with a collation called &lt;strong&gt;SQL_Latin1_General_CP1_CI_AI &lt;/strong&gt;– and the important part there is the CI_AI at the end, which indicates that this is a case-insensitive (CI), accent-insensitive (AI) collation, which means that if you search our database for &apos;CAFE&apos;, you&apos;ll find records containing &apos;café&apos;, because the case-insensitive rule says that &quot;C&quot; and &quot;c&quot; are the same, and the accent-insensitive part says &quot;ignore the accent – pretend that é is the same as e&quot;&lt;/p&gt;
&lt;p&gt;Now, in recent versions of SQL Server, Microsoft has introduced a new set of collations to choose from – which means that even though we&apos;ve already decided we want a Latin alphabet, with case-insensitive and accent-insensitive string comparisons, we still have a choice of six:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SELECT * FROM fn_helpcollations() WHERE name LIKE &apos;%Latin1_General%CI_AI&apos;&lt;/code&gt;&lt;/p&gt;
&lt;table cellspacing=&quot;0&quot; cellpadding=&quot;8&quot; width=&quot;550&quot; border=&quot;1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;214&quot;&gt;
&lt;p&gt;Latin1_General_CI_AI&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;334&quot;&gt;
&lt;p&gt;Latin1-General, case-insensitive, accent-insensitive, kanatype-insensitive, width-insensitive&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;214&quot;&gt;
&lt;p&gt;Latin1_General_100_CI_AI&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;334&quot;&gt;
&lt;p&gt;Latin1-General-100, case-insensitive, accent-insensitive, kanatype-insensitive, width-insensitive&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;214&quot;&gt;
&lt;p&gt;SQL_Latin1_General_CP1_CI_AI&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;334&quot;&gt;
&lt;p&gt;Latin1-General, case-insensitive, accent-insensitive, kanatype-insensitive, width-insensitive for Unicode Data, SQL Server Sort Order 54 on Code Page 1252 for non-Unicode Data&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;214&quot;&gt;
&lt;p&gt;SQL_Latin1_General_CP1253_CI_AI&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;334&quot;&gt;
&lt;p&gt;Latin1-General, case-insensitive, accent-insensitive, kanatype-insensitive, width-insensitive for Unicode Data, SQL Server Sort Order 124 on Code Page 1253 for non-Unicode Data&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;214&quot;&gt;
&lt;p&gt;SQL_Latin1_General_CP437_CI_AI&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;bottom&quot; width=&quot;334&quot;&gt;
&lt;p&gt;Latin1-General, case-insensitive, accent-insensitive, kanatype-insensitive, width-insensitive for Unicode Data, SQL Server Sort Order 34 on Code Page 437 for non-Unicode Data&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;214&quot;&gt;
&lt;p&gt;SQL_Latin1_General_CP850_CI_AI&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&amp;lt;strike&amp;gt;&amp;lt;/strike&amp;gt;&quot; width=&quot;334&quot;&gt;
&lt;p&gt;Latin1-General, case-insensitive, accent-insensitive, kanatype-insensitive, width-insensitive for Unicode Data, SQL Server Sort Order 44 on Code Page 850 for non-Unicode Data&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;What I&apos;m specifically interested in here – because it&apos;s my job as architect to decide this kind of thing - is why we might choose to use the new &lt;strong&gt;Latin1_General_CI_AI&lt;/strong&gt; collation instead of the tried&apos;n&apos;tested &lt;strong&gt;SQL_Latin1_General_CP1_CI_AI&lt;/strong&gt; collation. So I did a little Googling, and dug up &lt;a href=&quot;http://www.olcot.co.uk/sql-blogs/revised-difference-between-collation-sql_latin1_general_cp1_ci_as-and-latin1_general_ci_as&quot;&gt;this excellent article&lt;/a&gt;, which explains a bit about how these collations differ when it comes to Unicode character expansions. See, there are quite a lot of &apos;letters&apos; which can be written as either one or two characters, depending on your cultural conventions (and what kind of keyboard you&apos;ve got). Typesetting purists will refer to archæologists and œnophiles, whilst the rest of us have to settle for archaeologists and oenophiles because we don&apos;t know how to type &quot;æ&quot; and &quot;œ&quot;. And then there&apos;s the German &quot;sharp S&quot;, which is written as either &apos;ß&apos; or &apos;ss&apos; depending on who&apos;s writing it.&lt;/p&gt;
&lt;p&gt;Now here&apos;s the real-world scenario where this matters. Let&apos;s say we&apos;ve got a database of rock music clubs, and someone&apos;s added Roadrunner&apos;s Paradise in Berlin, and – because they&apos;re German, typing in German on a German keyboard – they&apos;re put the address in as &quot;Saarbrücker Straße 24, 10405 Berlin&quot;. Now one of our British users is on holiday, and they&apos;re in Berlin, and they&apos;re trying to find out if there are any rock clubs on Saarbrücker Straße. Except because they&apos;ve got an English guidebook and an English keyboard, they search for &quot;saarbrucker strasse&quot;, because they don&apos;t know how to type the umlaut or the sharp-S &apos;ß&apos; character.&lt;/p&gt;
&lt;p&gt;Whether their search works or not can potentially depend on several things&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The character type (varchar or nvarchar) of our StreetAddress column 
&lt;li&gt;The collation of our StreetAddress column 
&lt;li&gt;The character type specified in the query – SQL will assume string literals are ANSI strings unless you prefix them with N, so &lt;strong&gt;LIKE &apos;saarbrucker strasse&apos; &lt;/strong&gt;is different to &lt;strong&gt;LIKE N&apos;saarbrucker strasse&apos;&lt;/strong&gt; 
&lt;li&gt;Whether or not we explicitly specify a collation in our WHERE clause.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;OK, so we have two different column types (varchar vs nvarchar), we have two different collations (Latin1 vs SQL_Latin1), we have two different ways of specifying our search text (N&apos;foo&apos; vs &apos;foo&apos;), and we have three different ways to specify collation in the query (explicit Latin1, explicit SQL_Latin1, or implicit) - meaning there are 2*2*2*3 = 24 different ways to store and query the data supporting this particular scenario.&lt;/p&gt;
&lt;p&gt;So let&apos;s try them all. For SCIENCE. Code to create and run these examples is at &lt;a title=&quot;https://gist.github.com/dylanbeattie/c9705dc5c4efd82f60b8&quot; href=&quot;https://gist.github.com/dylanbeattie/c9705dc5c4efd82f60b8&quot;&gt;https://gist.github.com/dylanbeattie/c9705dc5c4efd82f60b8&lt;/a&gt;, and when you run it, it works like this:&lt;/p&gt;
&lt;table cellspacing=&quot;0&quot; cellpadding=&quot;4&quot; width=&quot;546&quot; border=&quot;1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;&lt;b&gt;Column Type&lt;/b&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;82&quot;&gt;
&lt;p&gt;&lt;b&gt;Colum collation&lt;/b&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;179&quot;&gt;
&lt;p&gt;&lt;b&gt;Search text&lt;/b&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;&lt;b&gt;Query collation&lt;/b&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;117&quot;&gt;
&lt;p&gt;&lt;b&gt;Did it work?&lt;/b&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;nvarchar&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;82&quot;&gt;
&lt;p&gt;Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;179&quot;&gt;
&lt;p&gt;&apos;%saarbrucker strasse%&apos;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;(implicit)&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;117&quot;&gt;
&lt;p&gt;Yes&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;nvarchar&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;82&quot;&gt;
&lt;p&gt;Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;179&quot;&gt;
&lt;p&gt;&apos;%saarbrucker strasse%&apos;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;117&quot;&gt;
&lt;p&gt;Yes&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;nvarchar&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;82&quot;&gt;
&lt;p&gt;Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;179&quot;&gt;
&lt;p&gt;&apos;%saarbrucker strasse%&apos;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;SQL_Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;117&quot;&gt;
&lt;p&gt;Yes&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;nvarchar&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;82&quot;&gt;
&lt;p&gt;Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;179&quot;&gt;
&lt;p&gt;N&apos;%saarbrucker strasse%&apos;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;(implicit)&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;117&quot;&gt;
&lt;p&gt;Yes&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;nvarchar&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;82&quot;&gt;
&lt;p&gt;Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;179&quot;&gt;
&lt;p&gt;N&apos;%saarbrucker strasse%&apos;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;117&quot;&gt;
&lt;p&gt;Yes&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;nvarchar&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;82&quot;&gt;
&lt;p&gt;Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;179&quot;&gt;
&lt;p&gt;N&apos;%saarbrucker strasse%&apos;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;SQL_Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;117&quot;&gt;
&lt;p&gt;Yes&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;nvarchar&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;82&quot;&gt;
&lt;p&gt;SQL_Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;179&quot;&gt;
&lt;p&gt;&apos;%saarbrucker strasse%&apos;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;(implicit)&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;117&quot;&gt;
&lt;p&gt;Yes&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;nvarchar&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;82&quot;&gt;
&lt;p&gt;SQL_Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;179&quot;&gt;
&lt;p&gt;&apos;%saarbrucker strasse%&apos;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;117&quot;&gt;
&lt;p&gt;Yes&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;nvarchar&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;82&quot;&gt;
&lt;p&gt;SQL_Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;179&quot;&gt;
&lt;p&gt;&apos;%saarbrucker strasse%&apos;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;SQL_Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;117&quot;&gt;
&lt;p&gt;Yes&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;nvarchar&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;82&quot;&gt;
&lt;p&gt;SQL_Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;179&quot;&gt;
&lt;p&gt;N&apos;%saarbrucker strasse%&apos;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;(implicit)&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;117&quot;&gt;
&lt;p&gt;Yes&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;nvarchar&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;82&quot;&gt;
&lt;p&gt;SQL_Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;179&quot;&gt;
&lt;p&gt;N&apos;%saarbrucker strasse%&apos;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;117&quot;&gt;
&lt;p&gt;Yes&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;nvarchar&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;82&quot;&gt;
&lt;p&gt;SQL_Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;179&quot;&gt;
&lt;p&gt;N&apos;%saarbrucker strasse%&apos;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;SQL_Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;117&quot;&gt;
&lt;p&gt;Yes&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;varchar&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;82&quot;&gt;
&lt;p&gt;Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;179&quot;&gt;
&lt;p&gt;&apos;%saarbrucker strasse%&apos;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;(implicit)&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;117&quot;&gt;
&lt;p&gt;Yes&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;varchar&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;82&quot;&gt;
&lt;p&gt;Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;179&quot;&gt;
&lt;p&gt;&apos;%saarbrucker strasse%&apos;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;117&quot;&gt;
&lt;p&gt;Yes&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ff9999&quot;&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;&lt;strong&gt;varchar&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;82&quot;&gt;
&lt;p&gt;&lt;strong&gt;Latin1&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;179&quot;&gt;
&lt;p&gt;&lt;strong&gt;&apos;%saarbrucker strasse%&apos;&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;&lt;strong&gt;SQL_Latin1&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;117&quot;&gt;
&lt;p&gt;&lt;strong&gt;No&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;varchar&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;82&quot;&gt;
&lt;p&gt;Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;179&quot;&gt;
&lt;p&gt;N&apos;%saarbrucker strasse%&apos;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;(implicit)&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;117&quot;&gt;
&lt;p&gt;Yes&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;varchar&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;82&quot;&gt;
&lt;p&gt;Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;179&quot;&gt;
&lt;p&gt;N&apos;%saarbrucker strasse%&apos;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;117&quot;&gt;
&lt;p&gt;Yes&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;varchar&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;82&quot;&gt;
&lt;p&gt;Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;179&quot;&gt;
&lt;p&gt;N&apos;%saarbrucker strasse%&apos;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;SQL_Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;117&quot;&gt;
&lt;p&gt;Yes&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ff9999&quot;&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;&lt;strong&gt;varchar&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;82&quot;&gt;
&lt;p&gt;&lt;strong&gt;SQL_Latin1&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;179&quot;&gt;
&lt;p&gt;&lt;strong&gt;&apos;%saarbrucker strasse%&apos;&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;&lt;strong&gt;(implicit)&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;117&quot;&gt;
&lt;p&gt;&lt;strong&gt;No&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;varchar&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;82&quot;&gt;
&lt;p&gt;SQL_Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;179&quot;&gt;
&lt;p&gt;&apos;%saarbrucker strasse%&apos;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;117&quot;&gt;
&lt;p&gt;Yes&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ff9999&quot;&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;&lt;strong&gt;varchar&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;82&quot;&gt;
&lt;p&gt;&lt;strong&gt;SQL_Latin1&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;179&quot;&gt;
&lt;p&gt;&lt;strong&gt;&apos;%saarbrucker strasse%&apos;&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;&lt;strong&gt;SQL_Latin1&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;117&quot;&gt;
&lt;p&gt;&lt;strong&gt;No&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;varchar&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;82&quot;&gt;
&lt;p&gt;SQL_Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;179&quot;&gt;
&lt;p&gt;N&apos;%saarbrucker strasse%&apos;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;(implicit)&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;117&quot;&gt;
&lt;p&gt;Yes&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;varchar&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;82&quot;&gt;
&lt;p&gt;SQL_Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;179&quot;&gt;
&lt;p&gt;N&apos;%saarbrucker strasse%&apos;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;117&quot;&gt;
&lt;p&gt;Yes&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;varchar&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;82&quot;&gt;
&lt;p&gt;SQL_Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;179&quot;&gt;
&lt;p&gt;N&apos;%saarbrucker strasse%&apos;&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;83&quot;&gt;
&lt;p&gt;SQL_Latin1&lt;/p&gt;&lt;/td&gt;
&lt;td valign=&quot;top&quot; width=&quot;117&quot;&gt;
&lt;p&gt;Yes&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;So there&apos;s the difference. If you&apos;re dealing with varchar columns (as opposed to nvarchars), and you specify your query text as an ANSI string (as opposed to a Unicode string), then you need to use the Latin1_General_CI_AI collation if you want &quot;strasse&quot; to match &quot;straße&quot;; in all other scenarios, &apos;ss&apos; is equal to &apos;ß&apos; (and apparently this is applies to all Latin collations regardless of accent sensitivity).&lt;/p&gt;
&lt;p&gt;Of course, this works both ways – by using Latin1_General_CI_AI, you create a scenario where somebody searching for &apos;%aße%&apos; will find English words like &apos;assessment&apos; and &apos;molasses&apos;, because as far as SQL Server is concerned, &apos;aße&apos; and &apos;asse&apos; are the same string. If that&apos;s a problem for your particular scenario, you&apos;ll need to do something like restrict your search inputs to the basic ASCII character set. &lt;/p&gt;
&lt;p&gt;As with almost everything in software development, modelling the real world involves making compromises. It&apos;s our job to make sure we&apos;re aware of those compromises, and that we understand how they translate back into real-world behaviour, and it&apos;s up to us to explain them to users who think our software sucks because it doesn&apos;t know what day it is and it says there&apos;s no rock bars in Saarbrucker Strasse.&lt;/p&gt;
&lt;p&gt;Happy Leap Year Day!&lt;/p&gt;</description>
          <pubDate>2016-02-29T15:08:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2016/02/29/the-mysterious-case-of-latin-general.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2016/02/29/the-mysterious-case-of-latin-general.html</guid>
        </item>
      
    
      
        <item>
          <title>Better Hypermedia Through Obfuscation</title>
          <description>Here’s a fun and completely twisted idea that came to me on the train this morning. One of the constraints of ReSTful architectures is that they use hypermedia as the engine of application state – in other words, clients shouldn’t maintain their own information about where they “are” and what resources and actions are available to them right now; they should rely on hypermedia embedded in the current resource representation they’ve retrieved from the server. A common example of this is pagination – navigating big lists of resources, using a representation something like this example:&lt;br /&gt;
&lt;blockquote&gt;
&lt;pre&gt;&lt;code&gt;

  GET /people

  &quot;_embedded&quot;: {
    &quot;people&quot;: [ &lt;span style=&quot;color: #9bbb59;&quot;&gt;/* array of 10 people */&lt;/span&gt; ]
  },
  &quot;_links&quot;: {
    &quot;first&quot;: { &quot;href&quot;: &quot;/people?page=0&quot; },
    &quot;last&quot;: { &quot;href&quot;: &quot;/people?page=12&quot; },
    &quot;next&quot;: { &quot;href&quot;: &quot;/people?page=1&quot; },
    &quot;self&quot;: { &quot;href&quot;: &quot;/people?page=0&quot; }
  },
  &quot;count&quot;: 10,
  &quot;total&quot;: 115
}&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
Now, with an API like this, it’s all too easy for the client – or rather, the person building the client – to go “ok, page number is a zero-based integer; let’s cut a few corners here” and just program something like&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;
&lt;pre&gt;&lt;code&gt;for(var i = 0; i &amp;lt; 12; i++) {
    http.get(“https://api.foo.com/people?page=”+page);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
Now, I’m a big fan of something we call the &quot;&lt;a href=&quot;http://blogs.msdn.com/b/brada/archive/2003/10/02/50420.aspx&quot;&gt;pit of success&lt;/a&gt;&quot; – the idea being that we &quot;build platforms such that […] developers just fall into doing the &apos;right thing&apos;&quot;, and more generally, the idea that the easiest way to achieve something is also the ‘correct’ way to achieve that thing. So what if we intentionally obfuscate our APIs so that hypermedia navigation becomes easier than building a for() loop? By, for example, requiring that our page number is written out longform, instead of numerically? And, just for fun, we’ll require that it’s in a language that isn’t in the regular ASCII character set. Like Ukrainian:&lt;br /&gt;
&lt;blockquote&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;first&quot;: { &quot;href&quot;: &quot;/people?page=один&quot; },
    &quot;last&quot;: { &quot;href&quot;: &quot;/people?page=дванадцять&quot; },
    &quot;prev&quot;: { &quot;href&quot;: &quot;/people?page=два&quot; },
    &quot;next&quot;: { &quot;href&quot;: &quot;/people?page=чотири&quot; },
    &quot;self&quot;: { &quot;href&quot;: &quot;/people?page=три&quot; }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
Suddenly, your ‘shortcut’ isn’t a short cut any more. For starters, you’ll probably need to install a special keyboard in order to type those characters – not to mention suddenly your source code files will need to be in UTF8, and I’ll wager that somewhere in your build pipeline there’s a tool that can’t handle UTF8 source code. And you’ll need a routine which can translate integers into Ukrainian string representations… No, the easiest thing to do now is to retrieve the first resource, parse the &lt;strong&gt;next.href &lt;/strong&gt;property, retrieve that resource, and so on until you hit a resource with no &lt;strong&gt;next&lt;/strong&gt; link. Which, of course, is exactly how hypermedia is &lt;em&gt;supposed&lt;/em&gt; to work in the first place.</description>
          <pubDate>2016-02-23T12:59:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2016/02/23/better-hypermedia-through-obfuscation.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2016/02/23/better-hypermedia-through-obfuscation.html</guid>
        </item>
      
    
      
        <item>
          <title>“How Can Software Be So Hard?”</title>
          <description>&lt;p&gt;Last night, I went to a &lt;a href=&quot;http://www.gresham.ac.uk/&quot;&gt;Gresham College&lt;/a&gt; lecture at the Museum of London, “&lt;a href=&quot;http://www.gresham.ac.uk/lectures-and-events/how-can-software-be-so-hard&quot;&gt;How Can Software Be So Hard?&lt;/a&gt;” presented by &lt;a href=&quot;http://www.gresham.ac.uk/professors-and-speakers/professor-martyn-thomas-cbe&quot;&gt;Professor Martyn Thomas CBE&lt;/a&gt;. The lecture itself was great – good content supported by solid examples. I must say there wasn’t a great deal there that I haven’t heard before – software engineering is an immature discipline; we rely too heavily on “testing” to validate and verify the systems we create; validation normally happens far too late to do anything about the problems it uncover; we’re overly reliant on modules and components that we can’t actually trust to work properly… all interesting and valid observations, but nothing particularly revolutionary. &lt;/p&gt; &lt;p&gt;Personally, I think the question posed in the lecture title is, to some extent, built on a false premise. Towards the end, he makes the observation – whilst talking about teenagers getting rich by writing iPhone games – that we only focus on the success stories, and the countless failures don’t get any attention. Which is true – but I think with software, we routinely take so much success for granted that it’s the failures which stand out. Sure, they happen – but if you think software is hard, try building a mechanical or an analog system that can send high-fidelity music from Boston to Singapore, or show the same colour photograph to a million people five minutes after it was taken. So many software projects are instigated to pursue efficiencies – to take some business process or system that used to require tens of thousands of people, and hire a few dozen programmers to replace it with a system that basically runs itself; it is any wonder it doesn’t always go smoothly? There’s a lot of things which are easy in software and close to impossible in any other domain, and I think to lose sight of that would be disadvantageous in an age where we’re trying to inspire more kids to study STEM subjects and pursue careers in science and engineering.&lt;/p&gt; &lt;p&gt;The thing that really stuck in my head, though, was a comment Professor Thomas made in response to a question after the lecture. Somebody asked him what he thought about self-driving cars, and amongst the points he raised in response, he said something like:&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;What about pedestrians? Why would you find a crossing, press the button and wait for the green man if you know all the cars on the road are programmed not to run you over?&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;Over the last few years, I’ve spent a lot of time studying the way smart devices are affecting the way we interact with the world around us – something I’ve covered at length in my talk “Are Smart Systems Making Us Stupid?”, which I presented at BuildStuff last year. I’ve looked into all sorts of models of human/machine interaction, but I’d never considered that particular angle before – and it’s fascinating. &lt;/p&gt; &lt;p&gt;&lt;img title=&quot;True fact: the Green Cross Code man is actually Darth Vader.&quot; style=&quot;float: none; margin-left: auto; display: block; margin-right: auto&quot; alt=&quot;Photograph of David Prowse as the Green Cross Code man.&quot; src=&quot;http://cdn.images.express.co.uk/img/dynamic/22/590x/RSA-green-cross-code-559740.jpg&quot; width=&quot;480&quot; height=&quot;288&quot;&gt;&lt;/p&gt; &lt;p&gt;Our basic instinct for self-preservation is reinforced from an early age – is there anybody here who DOESN’T remember being taught how to cross the street? So what happens to those behaviour patterns in a world where kids work out pretty early on that they can jump in front of Google Cars and get away with it? Do we program the cars to hit a kid once in a while – not to kill them, just give ‘em a nasty bump to teach them a lesson? How much use is a self-driving car when your journey takes longer than walking because your car slams the brakes on every time a pedestrian walks in front of it? Maybe you’re better off dumping your luggage, or your shopping, in the car, and taking the train instead?&lt;/p&gt; &lt;p&gt;It’s also an interesting perspective on a discussion that, until now, has been framed very much from the perspective of the driver – “will my self-driving car kill me to save a dozen schoolkids?” – and raises even more questions around the social implications of technology like driverless cars.&lt;/p&gt; &lt;p&gt;The next talk in the series is “&lt;a href=&quot;http://www.gresham.ac.uk/living-in-a-cyber-enabled-world&quot;&gt;Computers, People and the Real World&lt;/a&gt;” on April 5th, and if you’re interested in how our increasing dependency on machines and software is affecting the world we live in and the lives we live, I’d heartily recommend it.&lt;/p&gt; &lt;p&gt;(PS: if anyone from Gresham College is reading this – &lt;strong&gt;get somebody to introduce your speakers. &lt;/strong&gt;They’ve worked hard to prepare the material they’re presenting; help them make the best possible first impression by taking the stage to a round of applause from a crowd who already know who they are. It’s not that hard, and it makes a huge difference.)&lt;/p&gt;</description>
          <pubDate>2016-02-10T12:47:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2016/02/10/how-can-software-be-so-hard.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2016/02/10/how-can-software-be-so-hard.html</guid>
        </item>
      
    
      
        <item>
          <title>Let’s Talk About Feedback</title>
          <description>&lt;p&gt;&lt;img title=&quot;&amp;quot;That reminds me, Marty... you&apos;d better not share this post on Twitter. There&apos;s a SLIGHT possibility of overload.&amp;quot;&quot; style=&quot;float: none; margin-left: auto; display: block; margin-right: auto&quot; alt=&quot;Photo of the enormous guitar amplifier from the start of &amp;quot;Back to the Future&amp;quot;&quot; src=&quot;http://www.mwob.co.uk/gadget-news-reviews/wp-content/uploads/2010/11/Back-To-The-Future-Amplified-Speaker.jpg&quot;&gt;&lt;/p&gt;&lt;p&gt;There’s been some really interesting blog posts about speaking at conferences recently – from Todd Motto’s “&lt;a href=&quot;https://toddmotto.com/so-you-want-to-talk-at-conferences/&quot;&gt;So you want to talk at conferences?&lt;/a&gt;” piece, to Basia Fusinska’s “&lt;a href=&quot;http://barbarafusinska.com/2015/10/20/conference-talkswhere-did-we-go-wrong/&quot;&gt;Conference talks, where did we go wrong?&lt;/a&gt;” and Pieter Hintjens “&lt;a href=&quot;http://hintjens.com/blog:107&quot;&gt;Ten Steps to Better Public Speaking&lt;/a&gt;”, to Seb’s recent “&lt;a href=&quot;http://serialseb.com/blog/2016/01/15/good-talks/&quot;&gt;What’s good feedback from a talk?&lt;/a&gt;” post. There’s a lot of very useful general advice in those posts, but I absolutely believe the best way to improve as a speaker is to ask &lt;strong&gt;your&lt;/strong&gt; audience what you could be doing better, and that’s hard. &lt;/p&gt;&lt;p&gt;After most talks you’ll have a couple of people come up to you at the coffee machine or in the bar and say “hey, great talk!” – and that sort of positive feedback is great for your confidence but it doesn’t actually give you much scope to improve unless you take the initiative. Don’t turn it into an interrogation, but it’s easy to spin this into conversation – “hey, really glad you enjoyed it. Anything you think I could have done differently? Could you read the slides?” If there’s something in your talk which you’re not sure about, this is a great chance to get an anecdotal second opinion from somebody who actually saw it. &lt;/p&gt;&lt;p&gt;Twitter is another great way to get anecdotal feedback. Include your Twitter handle and hashtags in your slides – at the beginning AND at the end – and invite people to tweet at you if they have any comments or questions. In my experience, Twitter feedback tends to be positive (or neutral – I’ve been mentioned in lots of “&lt;a href=&quot;https://twitter.com/dylanbeattie&quot;&gt;@dylanbeattie&lt;/a&gt; talking about yada yada yada at #conference”-type tweets) – and again, whilst it’s good for giving your confidence a boost, it can also be a great opportunity to engage with your audience and try to get some more detailed feedback.&lt;/p&gt;&lt;p&gt;And then there’s the official feedback loops – the colored cards, the voting systems and the feedback app. I really like how&amp;nbsp; &lt;a href=&quot;http://buildstuff.lt/&quot;&gt;BuildStuff&lt;/a&gt; does this. They gather feedback on each talk through a combination of coloured-card voting and an online feedback app. Attendees who give feedback online go into a prize draw, which is a nice incentive to do so – and it makes it an easy sell for the speakers: “Thank you – and please remember to leave me feedback; you might win an iPad!” The other great thing BuildStuff does is to send you your feedback as a JPEG, which looks good and makes it really easy for speakers to share feedback and swap notes afterwards. Here’s mine from &lt;a href=&quot;http://buildstuff.lt/&quot;&gt;Vilnius&lt;/a&gt; and &lt;a href=&quot;http://buildstuff.com.ua/&quot;&gt;Kyiv&lt;/a&gt; last year:&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-AnYVe_2JmtU/VqjDi7OpGdI/AAAAAAAAD3A/FwygoNRMplY/s1600-h/Build%252520Stuff%252520Lithuania%252520ratings16%25255B6%25255D.png&quot;&gt;&lt;img title=&quot;Build Stuff Lithuania ratings16&quot; style=&quot;border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px&quot; border=&quot;0&quot; alt=&quot;Build Stuff Lithuania ratings16&quot; src=&quot;https://lh3.googleusercontent.com/-3xCuSBPx9_s/VqjDjdx2s-I/AAAAAAAAD3E/Own5tFo0-cw/Build%252520Stuff%252520Lithuania%252520ratings16_thumb%25255B4%25255D.png?imgmax=800&quot; width=&quot;224&quot; height=&quot;289&quot;&gt;&lt;/a&gt;&amp;nbsp;&lt;a href=&quot;https://lh3.googleusercontent.com/-p-n2GSmTCP0/VqjDj5GeqEI/AAAAAAAAD3M/7irYtQe9EX4/s1600-h/Build%252520Stuff%252520UA6%25255B3%25255D.png&quot;&gt;&lt;img title=&quot;Build Stuff UA6&quot; style=&quot;border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px&quot; border=&quot;0&quot; alt=&quot;Build Stuff UA6&quot; src=&quot;https://lh3.googleusercontent.com/-dfwZ8r4rKRs/VqjDkQFKDRI/AAAAAAAAD3U/OhualcbS_-I/Build%252520Stuff%252520UA6_thumb%25255B1%25255D.png?imgmax=800&quot; width=&quot;224&quot; height=&quot;289&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;p align=&quot;left&quot;&gt;Now, I’m pretty sure there were more than five people in my talk in Kyiv – so I think something might have got lost in the transcription here – but the real value here is in the anecdotal comments. &lt;/p&gt;&lt;p align=&quot;left&quot;&gt;Some conferences also run a live “leaderboard”, showing which speakers are getting the highest ratings. I’m generally not a big fan of this – I think it perpetuates a culture of celebrity that runs contrary to the approachability and openness of most conference speakers – but if you are going to do it, then &lt;em&gt;make sure it works&lt;/em&gt;. Don’t run a live leaderboard that excludes all the speakers from Room 7 because the voting machine in Room 7 was broken.&lt;/p&gt;&lt;p align=&quot;left&quot;&gt;Finally, two pieces of feedback I had from my talk about ReST at NDC London this year. The official talk rating, which I’m quite happy with but doesn’t really offer any scope for improvement:&lt;/p&gt;&lt;p align=&quot;left&quot;&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-TWvz9UN9Dh0/VqjDkvTbfkI/AAAAAAAAD3c/42h8Ht4ANtQ/s1600-h/image%25255B5%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; border-left: 0px; display: block; padding-right: 0px; margin-right: auto&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;https://lh3.googleusercontent.com/-WNXr367wA1A/VqjDlcDMb1I/AAAAAAAAD3k/DoMwRjgz7Mc/image_thumb%25255B3%25255D.png?imgmax=800&quot; width=&quot;500&quot; height=&quot;68&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;And then there’s this, thanks to Seb, who not only came along to my talk but sat in the front row scribbling away on his iPad Pro and then sent me his notes afterwards. There’s some real substance here, some good points to follow up on and things I know I could improve:&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-qlMxYVTAdIY/VqjDl4-UHtI/AAAAAAAAD3s/tYykw-PMQ0w/s1600-h/ndc_london_seb_notes%25255B5%25255D.png&quot;&gt;&lt;img title=&quot;ndc_london_seb_notes&quot; style=&quot;border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px&quot; border=&quot;0&quot; alt=&quot;ndc_london_seb_notes&quot; src=&quot;https://lh3.googleusercontent.com/-uF1JAoRMgAs/VqjDmlFucgI/AAAAAAAAD30/fP8kRKECiU8/ndc_london_seb_notes_thumb%25255B3%25255D.png?imgmax=800&quot; width=&quot;560&quot; height=&quot;740&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;This also goes to highlight one of the pitfalls of doing in-depth technical talks – your audience probably aren’t in a position to judge whether there’s flaws in your content or not, so your feedback is more likely to reflect the quality of your slides and your presentation style than the substance of your content. In other words – just because you got 45 green tickets doesn’t mean you can’t improve. Find a subject matter expert and ask if they can watch your talk and give you notes on it. Share your slides and talks online and ask the wider community for their feedback. And don’t get lazy. Even if you’ve given a talk a dozen times before, we’re in a constantly-changing industry and every audience is different.&lt;/p&gt;</description>
          <pubDate>2016-01-27T13:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2016/01/27/lets-talk-about-feedback.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2016/01/27/lets-talk-about-feedback.html</guid>
        </item>
      
    
      
        <item>
          <title>“The Face of Things to Come” from PubConf</title>
          <description>&lt;p&gt;A version of my talk from PubConf London, “The Face of Things to Come”, is now online. This isn’t a recording of the actual talk – the audio has been recorded specially, one slide has been replaced for copyright reasons, and a couple of things have been fixed in the edit – but it’s close enough.&lt;/p&gt; &lt;p align=&quot;center&quot;&gt;&lt;iframe height=&quot;281&quot; src=&quot;https://player.vimeo.com/video/152890042&quot; frameborder=&quot;0&quot; width=&quot;500&quot; allowfullscreen mozallowfullscreen webkitallowfullscreen&gt;&lt;/iframe&gt;&lt;/p&gt; &lt;p align=&quot;center&quot;&gt;&lt;a href=&quot;https://vimeo.com/152890042&quot;&gt;The Face of Things to Come&lt;/a&gt; from &lt;a href=&quot;https://vimeo.com/dylanbeattie&quot;&gt;Dylan Beattie&lt;/a&gt; on &lt;a href=&quot;https://vimeo.com&quot;&gt;Vimeo&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;As always, the only way to improve as a speaker is to listen to your audience, so I would love to hear your comments or feedback – leave a comment, &lt;a href=&quot;mailto:dylan@dylanbeattie.net&quot;&gt;email me&lt;/a&gt; or &lt;a href=&quot;https://twitter.com/dylanbeattie&quot;&gt;ping me on Twitter&lt;/a&gt;. &lt;/p&gt;</description>
          <pubDate>2016-01-24T17:58:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2016/01/24/the-face-of-things-to-come-from-pubconf.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2016/01/24/the-face-of-things-to-come-from-pubconf.html</guid>
        </item>
      
    
      
        <item>
          <title>Would you like to speak at London .NET User Group in 2016?</title>
          <description>&lt;p&gt;The &lt;a href=&quot;http://www.meetup.com/London-NET-User-Group/&quot;&gt;London .NET User Group&lt;/a&gt;, aka LDNUG – founded and run by &lt;a href=&quot;https://twitter.com/ICooper&quot;&gt;Ian Cooper&lt;/a&gt;, with help from &lt;a href=&quot;https://twitter.com/westleyl&quot;&gt;Liam Westley&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/holytshirt&quot;&gt;Toby Henderson&lt;/a&gt; and me – is now accepting speaker submissions for 2016. &lt;/p&gt; &lt;p&gt;We aim to run at least one meetup a month during 2016, with at least two speakers at each meetup. Meetups are always on weekday evenings in central London, are free, and we want to have at least two speakers at each of our meetups this year. We’re particularly keen to &lt;strong&gt;welcome &lt;/strong&gt;some &lt;strong&gt;new faces &lt;/strong&gt;and &lt;strong&gt;new ideas &lt;/strong&gt;to the London .NET community, so if you’ve ever been at a talk or a conference and thought “hey – maybe I could do that!” – this is your chance.&lt;/p&gt; &lt;p&gt;We’re going to try and introduce some variation on the format this year, so we’re inviting submissions for 45-minute talks, 15-minute short talks and 5-minute lightning talks, on any topic that’s associated with .NET, software development and the developer community. Come along and tell us about your cool new open source library, or that really big project your team’s just shipped. Tell us something we didn’t know about asynchronous programming, or distributed systems architecture. We welcome submissions from subject matter experts but we’re also keen to hear &lt;em&gt;your&lt;/em&gt; first-hand stories and experience. Never mind what the documentation said – what worked for you and your team? Why did it work? What would you do differently?&lt;/p&gt; &lt;p&gt;If you’re a new speaker and you’d like some help and support, let us know. We’d be happy to discuss your ideas for potential talks, help you write your summary, rehearse your talk, improve your slide deck. &lt;a href=&quot;mailto:dylan@dylanbeattie.net&quot;&gt;Drop me an email&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/dylanbeattie&quot;&gt;ping me on Twitter&lt;/a&gt; or come and find me after the next meetup (I’m the one in the hat!) and I’ll be happy to help.&lt;/p&gt; &lt;p&gt;So what are you waiting for? &lt;a href=&quot;https://docs.google.com/forms/d/16pBXd_KY0YSvFLoNHKvT3aYbJoiQW7Xa58_egPM0Eww/viewform&quot;&gt;Send us your ideas&lt;/a&gt;, come along to our meetups, and let’s make 2016 a great year for London.NET. &lt;/p&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt; &lt;p align=&quot;center&quot;&gt;&lt;strong&gt;&lt;font color=&quot;#ff0000&quot; size=&quot;4&quot;&gt;&lt;a href=&quot;https://docs.google.com/forms/d/16pBXd_KY0YSvFLoNHKvT3aYbJoiQW7Xa58_egPM0Eww/viewform&quot;&gt;Yes! I want to speak at London.NET in 2016!&lt;/a&gt;&lt;/font&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p align=&quot;left&quot;&gt;&lt;strong&gt;&lt;u&gt;&lt;font color=&quot;#ff0000&quot; size=&quot;4&quot;&gt;&lt;/font&gt;&lt;/u&gt;&lt;/strong&gt;&lt;/p&gt;</description>
          <pubDate>2016-01-19T18:42:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2016/01/19/would-you-like-to-speak-at-london-net.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2016/01/19/would-you-like-to-speak-at-london-net.html</guid>
        </item>
      
    
      
        <item>
          <title>Conway’s Law and the Mythical 17:00 Split</title>
          <description>&lt;p&gt;I was at &lt;a href=&quot;http://pubconf.io/&quot;&gt;PubConf&lt;/a&gt; on Saturday. It was an absolutely terrific event – fast-paced, irreverent, thought-provoking and hugely enjoyable. Huge thanks to &lt;a href=&quot;https://twitter.com/toddhgardner&quot;&gt;Todd Gardner&lt;/a&gt; for making it happen, to &lt;a href=&quot;https://twitter.com/westleyl&quot;&gt;Liam Westley&lt;/a&gt; for advanced detail-wrangling, and to &lt;a href=&quot;http://ndc-london.com/&quot;&gt;NDC London&lt;/a&gt;, &lt;a href=&quot;https://trackjs.com/&quot;&gt;Track:js&lt;/a&gt;, &lt;a href=&quot;http://www.red-gate.com/&quot;&gt;Red Gate&lt;/a&gt; and &lt;a href=&quot;http://www.zopa.com/&quot;&gt;Zopa&lt;/a&gt; for their generous sponsorship.&amp;nbsp; &lt;/p&gt; &lt;p&gt;&lt;a href=&quot;http://serialseb.com/&quot;&gt;Seb&lt;/a&gt; delivered a great talk about the Mythical 17:00 Split, which he has now &lt;a href=&quot;http://serialseb.com/blog/2016/01/19/the-mythical-1700-split/?fb_ref=Default&quot;&gt;written up on his&lt;/a&gt; blog. His talk resonated a lot with me, because I also find a lot of things about workplace culture very strange. I’m lucky enough to work somewhere where I seldom encounter these issues directly, but I know people whose managers genuinely believe that the best way to write software is to do it wearing a suit and tie, at eight’o’clock in the morning, whilst sitting in a crowded office full of hedge fund traders.&lt;/p&gt; &lt;p&gt;But &lt;a href=&quot;http://serialseb.com/blog/2016/01/19/the-mythical-1700-split/?fb_ref=Default&quot;&gt;Seb’s write-up post&lt;/a&gt; said something that really struck a chord. &lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;“Take working in teams. The best teams are made of people that like working together, and the worst teams I’ve had was when a developer had specific issues with me, to the point of causing a lot of tension”&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;Now, I’m a big fan of &lt;a href=&quot;https://en.wikipedia.org/wiki/Conway%27s_law&quot;&gt;Conway’s Law&lt;/a&gt; – over the course of my career, I’ve seen (and built) dozens of software systems that turned out to reflect the communication structures of the organizations that created them. I’ve even given a conference talk at &lt;a href=&quot;http://buildstuff.lt/&quot;&gt;BuildStuff 2015&lt;/a&gt; about Conway’s Law with Mel Conway in the audience – which was great fun, if a little nerve-wracking.&lt;/p&gt; &lt;p&gt;In a nutshell, Conway’s Law says of Seb’s observation regarding teams that if you take a bunch of people who are fundamentally incompatible, and force them to work together, you’ll end up with a system which is a bunch of incompatible components that are being forced to work together. If you want to know whether – and how – your systems are going to fail in production, look at the team dynamic of the people who are building them. If watching those people leaves you feeling calm, reassured and relaxed, then you’re gonna end up with something good. If one person is absolutely dominating the conversations, one component is going to end up dominating the architecture. If there’s two people on the team who won’t speak to each other and end up mediating all their communication through somebody else – guess what? You’re going to end up with a broker in your system that has to manage communication between two components that won’t communicate directly.&lt;/p&gt; &lt;p&gt;If your team hate each other, your product will suck – and in the entire history of humankind, there are only two documented exceptions to this rule. One is Guns’n’Roses “Appetite for Destruction” and the other is “Rumours” by Fleetwood Mac. &lt;/p&gt; &lt;p&gt;\m/&lt;/p&gt;</description>
          <pubDate>2016-01-19T15:06:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2016/01/19/conways-law-and-mythical-1700-split.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2016/01/19/conways-law-and-mythical-1700-split.html</guid>
        </item>
      
    
      
        <item>
          <title>The Rest of ReST at NDC London</title>
          <description>&lt;p&gt;A huge thank you to everyone who came along to my ReST talk here at NDC London. Links to a couple of resources you might find useful:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;The &lt;a href=&quot;https://github.com/dylanbeattie/ndc-london-2016/raw/master/The%20Rest%20of%20ReST%20-%20Dylan%20Beattie%20-%20NDC%20London%202016.pdf&quot;&gt;slide deck from my presentation&lt;/a&gt; is on Github (PDF)&lt;/li&gt; &lt;li&gt;Roy Fielding’s dissertation, ‘&lt;a href=&quot;http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm&quot;&gt;Architectural Styles and the Design of Network-based Software Architectures&lt;/a&gt;’, in which he outlines the constraints of representational state transfer (ReST). Highly recommended and remarkably readable.&lt;/li&gt; &lt;li&gt;The &lt;a href=&quot;http://stateless.co/hal_specification.html&quot;&gt;HAL – Hypertext Application Language&lt;/a&gt; site and draft specification&lt;/li&gt; &lt;li&gt;A great article from Stormpath on &lt;a href=&quot;https://stormpath.com/blog/linking-and-resource-expansion-rest-api-tips/&quot;&gt;linking and resource expansion in REST APIs&lt;/a&gt;.&lt;/li&gt; &lt;li&gt;The &lt;a href=&quot;http://oauth.net/2/&quot;&gt;OAuth2.0 site&lt;/a&gt; and specification&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;Thanks again for coming – and any comments, questions or feedback, you’ll find me on Twitter as &lt;a href=&quot;https://twitter.com/dylanbeattie&quot;&gt;@dylanbeattie&lt;/a&gt;.&lt;/p&gt;</description>
          <pubDate>2016-01-14T17:58:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2016/01/14/the-rest-of-rest-at-ndc-london.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2016/01/14/the-rest-of-rest-at-ndc-london.html</guid>
        </item>
      
    
      
        <item>
          <title>Confession Time. I Implemented the EU Cookie Banner</title>
          <description>&lt;p&gt;Troy Hunt kicked off 2016 with &lt;a href=&quot;http://www.troyhunt.com/2016/01/its-2016-already-how-are-websites-still.html&quot;&gt;a great post&lt;/a&gt; about poor user experiences online – a catalogue of common UX antipatterns that “make online life much more painful than it needs to be”. &lt;/p&gt;&lt;p&gt;One of the things he picks up on is EU Cookie Warnings – “this is just plain stupid.” And yeah, it is. Absolutely everybody I know who added an EU cookie warning to their website agrees – this is just plain stupid. But for folks outside the European Union, it might be insightful to learn just why these things started appearing all over the place.&lt;/p&gt;&lt;p&gt;First, a VERY brief primer on how the European Union works. There’s currently 28 countries in the EU. The United Kingdom, where I live and work, is one of them. One of the aims of the EU is to create a consistent legal framework that covers all citizens of all its member states. Overseeing all this is the European Parliament. They make laws. It’s then up to the governments of the individual member states to interpret and enforce those laws within their own countries. &lt;/p&gt;&lt;p&gt;So, in 2009, the European Parliament issued a directive called 2009/136/EC – OpenRightsGroup has &lt;a href=&quot;https://wiki.openrightsgroup.org/wiki/Cookie_Directive&quot;&gt;some good coverage&lt;/a&gt; of this. The kicker here is Article 5(3), which says&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;“The storing of information or the gaining of access to information already stored in the user’s equipment is only allowed on the condition that the subscriber or user concerned has given their consent, having been provided with clear and comprehensive information in accordance with Directive 95/46/EC, inter alia, about the purposes of the processing. This shall not prevent any technical storage or access for the sole purpose of carrying out the transmission of a communication over an electronic communications network, or as strictly necessary in order for the provider of an information society service explicitly requested by the subscriber or user to provide the service.”&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;In a nutshell, this means you can’t store anything (such as a cookie) on a user’s device, unless &lt;/p&gt;&lt;ol&gt;&lt;li&gt;You’ve told them what you’re doing and they’ve given their explicit consent, OR&lt;/li&gt;
&lt;li&gt;It’s absolutely necessary to provide the service they’ve asked for.&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;Directive 2009/136 goes on to state (my emphasis):&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;“Under the added Article 15a, Member States are obliged to laydown rules on penalties, including &lt;strong&gt;criminal sanctions where applicable&lt;/strong&gt; to infringements of the national provisions, which have been adopted to implement this Directive. The Member States shall also take “all measures necessary” to ensure that these are implemented. The new article further states that “the penalties provided for must be &lt;strong&gt;effective&lt;/strong&gt;, proportionate and &lt;strong&gt;dissuasive&lt;/strong&gt; and may be applied to cover the period of any breach, &lt;strong&gt;even where the breach has subsequently been rectified&lt;/strong&gt;”.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Golly! Criminal sanctions? Retrospectively applied, even for something that we already fixed? That sounds pretty ominous. &lt;/p&gt;&lt;p&gt;&lt;img style=&quot;border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; float: right; padding-top: 0px; padding-left: 0px; margin: 0px 0px 20px 20px; border-left: 0px; display: inline; padding-right: 0px&quot; border=&quot;0&quot; src=&quot;http://beyondquarterlife.com/wp-content/uploads/2014/09/Dredd.jpg&quot; width=&quot;218&quot; align=&quot;right&quot; height=&quot;240&quot;&gt;Anyway. Here’s what happens next. Directive 2009/136 means that is is now &lt;strong&gt;THE LAW &lt;/strong&gt;that you don’t store cookies without consent, and the various member states swing into action and try to work out what this means and how to enforce it. In the UK, Parliament interpreted this via something called the Privacy and Electronic Communications (EC Directive) (Amendment) Regulations 2011, which would come into effect in 2012. &lt;/p&gt;&lt;p&gt;My team and I found out in late 2011 that, when the new regulations came into force on 26 May 2012, we would be breaking the law if we put cookies on our user’s machines without their explicit consent. And nobody had the faintest idea what that actually meant, because nobody had ever broken this law yet, so nobody knew what the penalties for non-compliance would be. The arm of the UK government that deals with this kind of thing is the Information Commissioner’s Office (ICO), who have a reputation for taking data protection very seriously, and the power to exact fines up to £500,000 for non-compliance. The ICO also usually publish quite clear and reasonable guidelines on how to comply with various elements of the law – but that takes time, so in late 2011 we found ourselves with a tangle of bureacracy, a hard deadline, the possibility of severe penalties, and absolutely no guidance to work from. &lt;/p&gt;&lt;p&gt;So… we implemented it. Despite it being a pointless, stupid, ridiculous endeavour that would waste our time and piss off our users, we did it - because we didn’t want to end up in court and nobody could assure us that we wouldn’t. &lt;/p&gt;&lt;p&gt;We built a nice self-contained JavaScript library to handle displaying the banner across our various sites and pages.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-qY2TUmvZhRY/Vo6Ilb4a8pI/AAAAAAAAD1A/y0NqP5qbwo4/s1600-h/image%25255B51%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; border-left: 0px; display: block; padding-right: 0px; margin-right: auto&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;https://lh3.googleusercontent.com/-QEaYjeZWFc4/Vo6Il3RMhZI/AAAAAAAAD1E/-WLNCD6Z7ps/image_thumb%25255B47%25255D.png?imgmax=800&quot; width=&quot;285&quot; height=&quot;294&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Instead of just plastering something on every page saying “We use cookies. Deal with it”, the approach taken by most sites - we actually split our cookies into the essential ones required to make our site work, and the non-essential ones used by Boomerang, Google Analytics and other stats and analytics tools. And &lt;strong&gt;we allowed users to opt-out of the non-essential ones. &lt;/strong&gt;We went live with this on 10th May 2012. Around 30% of our users chose to opt-out of non-essential cookies – meaning they became invisible to Google Analytics and our other tracking software. Here’s our web traffic graph for April – June 2012 – see how the peaks after May 10th are suddenly a lot lower?&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://lh3.googleusercontent.com/-muKj9HxWB8I/Vo6ImIISykI/AAAAAAAAD1M/fFd5bNgvJsE/s1600-h/image%25255B46%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;https://lh3.googleusercontent.com/-djxdBmUVMHk/Vo6Imz88IcI/AAAAAAAAD1U/-S-3zuR0r4c/image_thumb%25255B44%25255D.png?imgmax=800&quot; width=&quot;545&quot; height=&quot;142&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;On 25th May 2012, ONE DAY before the new regulations became law, the ICO &lt;a href=&quot;https://www.cookielaw.org/blog/2012/5/26/new-ico-guidance-increases-support-for-implied-consent/&quot;&gt;issued some new guidance&lt;/a&gt;, which significantly relaxed the requirements around ‘consent’. “Implied consent” &lt;a href=&quot;http://www.theguardian.com/technology/2012/may/26/cookies-law-changed-implied-consent&quot;&gt;was suddenly OK&lt;/a&gt; – i.e. if your users hadn’t disabled cookies in their browser, you could interpret that as meaning they had consented to receive cookies from your site.&lt;/p&gt;&lt;p&gt;They also announced that any enforcement would be in response to user complaints about a specific site: &lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;“The end of the safe period &quot;doesn&apos;t mean the ICO is going to launch a torrent of enforcement action&quot; said the deputy commissioner and it would take serious breaches of data protection that caused &quot;significant distress&quot; to attract the maximum £0.5m non-compliance fine.” (via &lt;a href=&quot;http://www.theregister.co.uk/2012/05/18/cookie_law_ico/&quot;&gt;The Register&lt;/a&gt;)&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;So there you have it. Go to &lt;a href=&quot;http://www.spotlight.com/&quot;&gt;http://www.spotlight.com/&lt;/a&gt; and, just once, you’ll see a nice friendly banner asking if you mind us tracking your session using cookies. And if you opt out, that’s absolutely fine – our site still works and you won’t show up in any of our analytics. Couple of weeks of effort, a nice, clean, technically sound implementation… did it make the slightest bit of difference? Nah. Except now we multiply all our Analytics numbers by 1.5. And yes, we periodically review the latest guidance to see whether the EU has finally admitted the whole thing was a bit silly and maybe isn’t actually helping, but so far nada – and in the absence of any hard evidence to the contrary, it’s hard to make a business case for doing work that would make us technically non-compliant, even if the odds of any enforcement action are minimal. &lt;/p&gt;&lt;p&gt;Now, if the European Parliament really wanted to make the internet a better place, how about they &lt;a href=&quot;http://www.troyhunt.com/2016/01/its-2016-already-how-are-websites-still.html&quot;&gt;read Troy’s post&lt;/a&gt; and ban popover adverts, unnecessary pagination, linkbait headlines and restrictions on passwords? Now that’s the kind of legislation I could really get behind.&lt;/p&gt;</description>
          <pubDate>2016-01-07T17:47:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2016/01/07/confession-time-i-implemented-eu-cookie.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2016/01/07/confession-time-i-implemented-eu-cookie.html</guid>
        </item>
      
    
      
        <item>
          <title>Restival Part 6: Who Am I, Revisited</title>
          <description>&lt;p align=&quot;center&quot;&gt;&lt;strong&gt;Note: code for this instalment is in &lt;/strong&gt;&lt;a title=&quot;https://github.com/dylanbeattie/Restival/tree/v0.0.6&quot; href=&quot;https://github.com/dylanbeattie/Restival/tree/v0.0.6&quot;&gt;&lt;strong&gt;https://github.com/dylanbeattie/Restival/tree/v0.0.6&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;In &lt;a href=&quot;http://dylanbeattie.blogspot.co.uk/2015/12/restival-part-5-who-am-i.html&quot;&gt;the last instalment&lt;/a&gt;, we looked at adding HTTP Basic authentication to a simple HTTP endpoint - GET /whoami - that returns information about the authenticated user.&lt;/p&gt; &lt;p&gt;Well... I didn&apos;t like it. Both the OpenRasta and the WebAPI implementations felt really over-engineered, so I kept digging and discovered a few things that made everything much, much cleaner.&lt;/p&gt; &lt;h3&gt;Basic auth in OpenRasta - Take 2&lt;/h3&gt; &lt;p&gt;There&apos;s an HTTP Basic authentication feature baked into OpenRasta 2.5.x, but all of the classes are marked as deprecated so in my first implementation I avoided using it. After talking with Seb, the creator of OpenRasta, I understand a lot more about the rationale behind deprecating these classes - they&apos;re earmarked for migration into a standalone module, not for outright deletion, and they&apos;ll definitely remain part of the OpenRasta 2.x codebase for the foreseeable future.&lt;/p&gt; &lt;p&gt;Armed with that knowledge, and the magical compiler directive &lt;font face=&quot;Consolas&quot;&gt;&lt;strong&gt;#pragma warning disable 618&lt;/strong&gt; &lt;/font&gt;that stops Visual Studio complaining about you using deprecated code, I switched Restival back to running on the OpenRasta NuGet package instead of my forked build, and reimplemented the authentication feature - and yes, it&apos;s much, much nicer.&lt;/p&gt; &lt;p&gt;There&apos;s a RestivalAuthenticator which implements OpenRasta&apos;s IBasicAuthenticator interface - as with the other frameworks, this ends up being a really simple wrapper around the IDataStore:&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;public class RestivalAuthenticator : IBasicAuthenticator { &lt;br&gt;&amp;nbsp; private readonly IDataStore db;&lt;/font&gt;&lt;/p&gt; &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;&amp;nbsp; public RestivalAuthenticator(IDataStore db) { &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; this.db = db; &lt;br&gt;&amp;nbsp; }&lt;/font&gt;&lt;/p&gt; &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;&amp;nbsp; public AuthenticationResult Authenticate(BasicAuthRequestHeader header) { &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; var user = db.FindUserByUsername(header.Username); &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (user != null &amp;amp;&amp;amp; user.Password == header.Password) return (new AuthenticationResult.Success(user.Username, new string[] { })); &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; return (new AuthenticationResult.Failed()); &lt;br&gt;&amp;nbsp; }&lt;/font&gt;&lt;/p&gt; &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;&amp;nbsp; public string Realm { get { return (&quot;Restival.OpenRasta&quot;); } } &lt;br&gt;}&lt;/font&gt;&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;and then there&apos;s the configuration code to initialise the authentication provider. &lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;ResourceSpace.Uses.PipelineContributor&amp;lt;AuthenticationContributor&amp;gt;(); &lt;br&gt;ResourceSpace.Uses.PipelineContributor&amp;lt;AuthenticationChallengerContributor&amp;gt;(); &lt;br&gt;ResourceSpace.Uses.CustomDependency&amp;lt;IAuthenticationScheme, BasicAuthenticationScheme&amp;gt;(DependencyLifetime.Singleton); &lt;br&gt;ResourceSpace.Uses.CustomDependency&amp;lt;IBasicAuthenticator, RestivalAuthenticator&amp;gt;(DependencyLifetime.Transient);&lt;/font&gt;&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;This one stumped me for a while, until I realised that - unlike, say, Nancy, which just does everything by magic, you need to explicitly register both the AuthenticationContributor and the AuthenticationChallengerContributor. These are the OpenRasta components that handle the HTTP header parsing, decoding and the WWW-Authenticate challenge response, but if you don&apos;t explicitly wire them into your pipeline, your custom auth classes will never get called.&lt;/p&gt; &lt;h3&gt;Basic auth in WebAPI - Take 2&lt;/h3&gt; &lt;p&gt;As part of the last instalment, I wired up the &lt;a href=&quot;http://www.lightinject.net/&quot;&gt;LightInject&lt;/a&gt; IoC container to my WebAPI implementation. I love LightInject, but something I had never previously realised is that &lt;em&gt;LightInject can do property injection on your custom attributes.&lt;strong&gt; &lt;/strong&gt;&lt;/em&gt;This is a game-changer, because previously I&apos;d been following a pattern of using purely decorative attributes - i.e. with no behaviour - and then implementing a separate ActionFilter that would check for the presence of the corresponding attribute before running some custom code - and all this just so I could inject dependencies into my filter instances.&lt;/p&gt; &lt;p&gt;Well, with LightInject up and running, you don&apos;t need to do any of that - you can just put a public IService { get; set; } onto your MyCustomAttribute class, and LightInject will resolve IService at runtime and inject an instance of MyAwesomeService : IService into your attribute code. Which means the half-a-dozen classes worth of custom filters and responses from the last implementation can be ripped out in favour of a single &lt;a href=&quot;https://github.com/dylanbeattie/Restival/blob/v0.0.6/src/Restival.Api.WebApi/Security/RequireHttpBasicAuthorizationAttribute.cs&quot;&gt;RequireHttpBasicAuthorizationAttribute&lt;/a&gt; - which overrides WebAPI&apos;s built-in &lt;strong&gt;AuthorizeAttribute&lt;/strong&gt; class to provide authorization header parsing, WWW-Authenticate challenge response, and hook the authentication up to our IDataStore interface.&lt;/p&gt; &lt;p&gt;I&apos;m much happier now with all four implementations, so it raises the interesting question of how much development time is really worth. Based on the code provided here, I suspect a good developer could implement HTTP Basic auth on any of these frameworks in about fifteen minutes - but something that takes fifteen minutes to implement doesn&apos;t really count if it takes you two days to work out how to do that fifteen-minute implementation.&lt;/p&gt; &lt;p&gt;In forthcoming instalments, we&apos;re going to be adding hypermedia, HAL+JSON and resource expansion - as we move further away from basic HTTP capabilities and into more advanced REST/serialization/content negotiation, it&apos;ll be interesting to see how our four frameworks measure up.&lt;/p&gt;</description>
          <pubDate>2016-01-07T10:09:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2016/01/07/restival-part-6-who-am-i-revisited.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2016/01/07/restival-part-6-who-am-i-revisited.html</guid>
        </item>
      
    
      
        <item>
          <title>Restival Part 5: Who Am I?</title>
          <description>&lt;p align=&quot;center&quot;&gt;&lt;strong&gt;NOTE: Code for this article is at &lt;a title=&quot;https://github.com/dylanbeattie/Restival/tree/v0.0.5&quot; href=&quot;https://github.com/dylanbeattie/Restival/tree/v0.0.5&quot;&gt;https://github.com/dylanbeattie/Restival/tree/v0.0.5&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;UPDATE: The WebAPI and OpenRasta implementations described here are... inelegant. After this release, I spent a little more time and came up with something much cleaner - which you can read all about in the follow-up post &lt;a title=&quot;http://dylanbeattie.blogspot.co.uk/2015/12/restival-part-6-who-am-i-revisited.html&quot; href=&quot;http://dylanbeattie.blogspot.co.uk/2015/12/restival-part-6-who-am-i-revisited.html&quot;&gt;http://dylanbeattie.blogspot.co.uk/2015/12/restival-part-6-who-am-i-revisited.html&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;Welcome back. It&apos;s been a very busy summer that&apos;s turned into a very busy autumn - including a fantastic couple of weeks in Eastern Europe, speaking at &lt;a href=&quot;http://buildstuff.lt/&quot;&gt;BuildStuff in Lithuania&lt;/a&gt; and &lt;a href=&quot;http://buildstuff.com.ua/&quot;&gt;Ukraine&lt;/a&gt;, where I met a lot of very interesting people, discussed a lot of very interesting ideas, tried a lot of very interesting food, and generally got a nice hard kick out of my comfort zone. Which is good. &lt;/p&gt; &lt;p&gt;&lt;img title=&quot;If your name&apos;s not on the list, you&apos;re not coming in!&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; float: right; padding-top: 0px; padding-left: 0px; margin: 0px 0px 20px 20px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;If your name&apos;s not on the list, you&apos;re not coming in!&quot; src=&quot;http://40.media.tumblr.com/bc16bc661736d3ec0077b81072bebc3e/tumblr_mxi1gmcnt01ryt88so1_1280.png&quot; width=&quot;240&quot; align=&quot;right&quot; height=&quot;133&quot;&gt;Anyway. As London starts getting frosty and festive, it&apos;s time to pick up where we left off earlier in the year with my ongoing Restival project - implementing the same API in four different .NET API frameworks (ServiceStack, NancyFX, OpenRasta and WebAPI).&lt;/p&gt; &lt;p&gt;In this instalment, we&apos;re going to add two very straightforward capabilities to our API:&lt;/p&gt; &lt;ol&gt; &lt;li&gt;HTTP Basic authentication. Clients can include a username/password in an &lt;font face=&quot;Consolas&quot;&gt;HTTP Authorization&lt;/font&gt; header, and the API will verify their credentials against a simple user store. (If you&apos;re doing this in production, you&apos;d enforce HTTPS/TLS so that credentials can&apos;t be sniffed in transit, but since this is a demonstration project and I&apos;m optimising for readability, TLS is out of scope for now.)  &lt;li&gt;A /whoami endpoint, where authenticated users can GET details of their own user record. &lt;/li&gt;&lt;/ol&gt; &lt;p&gt;And that&apos;s it. Sounds simple, right? OK, first things first - let&apos;s thrash out our requirements into something a little more detailed:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;GET /whoami &lt;/strong&gt;with valid credentials should return the current users&apos; details (id, name, username) &lt;/li&gt;&lt;/ul&gt; &lt;p&gt;But remember - we&apos;re building an authentication system, so we should also be testing the negative responses:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;GET /whoami &lt;/strong&gt;with invalid credentials returns 401 Unauthorized  &lt;ul&gt; &lt;li&gt;&quot;invalid&quot; means an unsupported scheme, an unsupported header format, invalid credential encoding, or a correctly formatted header containing an unrecognised username and/or password. &lt;/li&gt;&lt;/ul&gt; &lt;li&gt;&lt;strong&gt;GET /whoami &lt;/strong&gt;without any credentials returns a 401 Unauthorized  &lt;li&gt;&lt;strong&gt;GET /whoami &lt;/strong&gt;without any credentials returns a WWW-Authenticate header indicating that we support HTTP Basic authentication. &lt;/li&gt;&lt;/ul&gt; &lt;p&gt;The WWW-Authenticate thing is partly a node to HATEOAS, and partly there to make it easier to test things from a normal web browser. I tend to use &lt;a href=&quot;https://www.getpostman.com/&quot;&gt;Postman&lt;/a&gt; for building and testing any serious HTTP services, but when it comes to quick&apos;n&apos;dirty API discovery and troubleshooting, I can&apos;t overstate the convenience of being able to paste something into a normal web browser and get a readable response.&lt;/p&gt; &lt;p&gt;The test code is in &lt;a href=&quot;https://github.com/dylanbeattie/Restival/blob/v0.0.5/src/Restival.ApiTests/WhoAmIApiTestsBase.cs&quot;&gt;WhoAmITestBase.cs&lt;/a&gt;. Notice that in this implementation, our users are stored in a fake data store - FakeDataStore.cs - and we&apos;re actually using this class as a TestCaseSource in our tests so we maintain parity between the test data and the test coverage. &lt;/p&gt; &lt;p&gt;The WhoAmI endpoint was trivial - find the current user name, look them up in the user store, and convert their user data to a WhoAmIResponse. The fun part here was the authentication. &lt;/p&gt; &lt;h3&gt;A Note on Authentication vs Authorization&lt;/h3&gt; &lt;p&gt;NOTE: Authentication and authorization are, strictly speaking, different things. Authentication is &quot;who are you?&quot;, authorization is &quot;are you allowed to do this thing?&quot; Someone trying to enter the United States with a Libyan passport is &lt;em&gt;authenticated &lt;/em&gt;- the US border service know exactly who they are - but they&apos;re not &lt;em&gt;authorized&lt;strong&gt; &lt;/strong&gt;&lt;/em&gt;because Libyan citizens can&apos;t enter the US without a valid visa.&lt;/p&gt; &lt;p&gt;In one respect, HTTP gets this exactly right - a 401 Unauthorized means &quot;we don&apos;t know who you are&quot;, a 403 Forbidden means &quot;we know who you are, but you&apos;re not allowed to do this.&quot; In another respect, it gets this badly wrong, because the Authentication header in the HTTP specification is called Authorization.&lt;/p&gt; &lt;h3&gt;Authentication - The Best Case Scenario&lt;/h3&gt; &lt;p&gt;OK, to build our secure /whoami endpoint, we need a handful of extension points in our framework. We need to:&lt;/p&gt; &lt;ol&gt; &lt;li&gt;Validate a supplied username and password against our own credential store  &lt;li&gt;Indicate that specific resources require authorization, so that unauthorized requests for those resources will be rejected  &lt;li&gt;Determine the identity of the authenticated user when responding to an authorized request &lt;/li&gt;&lt;/ol&gt; &lt;p&gt;The rest - WWW-Authenticate negotiation, decoding base64-encoded Authorization headers, returning 401 Unauthorized responses - is completely generic, so in an ideal world our framework will do all this for us; all we need to do is implement the three extension points above.&lt;/p&gt; &lt;p&gt;Let&apos;s look at Nancy first, because alphabetical is as good an order as any.&lt;/p&gt; &lt;h3&gt;NancyFX&lt;/h3&gt; &lt;p&gt;Implementing HTTP Basic auth in NancyFX proved fairly straightforward. The first head-scratching moment I hit is that - unlike the other frameworks in this project - Nancy is so lightweight that I didn&apos;t actually have any kind of bootstrapper or initialization code anywhere, so it wasn&apos;t immediately obvious where to configure the additional pipeline steps. A bit of Googling and poking around the NancyFX source led me to the &lt;a href=&quot;https://github.com/NancyFx/Nancy/tree/master/src/Nancy.Demo.Authentication.Basic&quot;&gt;Nancy.Demo.Authentication.Basic&lt;/a&gt; project, which made things a lot clearer. From this point, implementation involved:&lt;/p&gt; &lt;ol&gt; &lt;li&gt;Add an &lt;strong&gt;AuthenticationBootstrapper&lt;/strong&gt; - which Just WorkedTM without any explicit registration. I&apos;m guessing it&apos;s invoked by &lt;strike&gt;magic&lt;/strike&gt; sufficiently advanced technology, because it overrides &lt;strong&gt;DefaultNancyBootstrapper.&lt;/strong&gt;  &lt;li&gt;Implement IUserValidator to connect Nancy to my custom user store - &lt;a href=&quot;https://github.com/dylanbeattie/Restival/blob/v0.0.5/src/Restival.Api.Nancy/UserValidator.cs&quot;&gt;my implementation&lt;/a&gt; is just a really simple wrapper around my user data store. My UserValidator depends on my IDataStore interface - and, thanks to Nancy&apos;s auto-configuration, I didn&apos;t have to explicitly register either of these as dependencies.  &lt;li&gt;In this bootstrapper, call pipelines.EnableBasicAuthentication() and pass in a basic authentication configuration. &lt;/li&gt;&lt;/ol&gt; &lt;blockquote&gt; &lt;p&gt;protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines) { &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; base.ApplicationStartup(container, pipelines); &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; var authConfig = new BasicAuthenticationConfiguration(container.Resolve&amp;lt;IUserValidator&amp;gt;(), &quot;Restival.Nancy&quot;); &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; pipelines.EnableBasicAuthentication(authConfig); &lt;br&gt;}&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;Finally, the &lt;a href=&quot;https://github.com/dylanbeattie/Restival/blob/v0.0.5/src/Restival.Api.Nancy/WhoAmIModule.cs&quot;&gt;WhoAmIModule&lt;/a&gt; needs a call to &lt;strong&gt;this.RequiresAuthentication()&lt;/strong&gt;, and that&apos;s it. Clean, straightforward, no real showstoppers. &lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;UPDATE: The NancyFX documentation has now been updated with a &lt;a href=&quot;https://github.com/NancyFx/Nancy/wiki/Basic-Authentication&quot;&gt;detailed walkthrough on enabling HTTP Basic authentication&lt;/a&gt;&amp;nbsp;&lt;/p&gt;&lt;/blockquote&gt; &lt;h3&gt;OpenRasta&lt;/h3&gt; &lt;p&gt;Adding HTTP Basic auth to OpenRasta was a lot more involved - and the reasons why provide some interesting insight into the underlying development practises of the frameworks we&apos;re comparing. In 2013, there were some major changes to the request processing pipeline used by OpenRasta. As part of these changes, the basic authentication features of OpenRasta (dating back to 2008) were marked as deprecated - and until now, nobody&apos;s contributed an up-to-date implementation, I&apos;m guessing because none of the people using OpenRasta have needed one. Which left me with a choice - do I use the deprecated approach, contribute my own implementation, or disqualify OpenRasta? Well, disqualification would be no fun, and deprecated code means you get Resharper yelling at you all over the place, so I ended up implementing a pipeline-based HTTP Basic authorization module to the OpenRasta codebase.&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;Whilst writing this article, I had a long and very enlightening chat with &lt;a href=&quot;https://twitter.com/serialseb&quot;&gt;Sebastien Lambla&lt;/a&gt;, the creator of OpenRasta, about the current state of the project and specifically the authentication capabilities. It turns out the features marked as deprecated were intended to be migrated into a separate OpenRasta.Security module, thus decoupling authentication concerns from the core pipeline model - but this hasn&apos;t happened yet, and so the code is still in the OpenRasta core package. It&apos;s up to you, the implementer, whether to use the existing (&apos;deprecated&apos;) HTTP authentication provider, or to roll your own based on the new pipeline approach. &lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;The original pull request for this is &lt;a href=&quot;https://github.com/openrasta/openrasta-core/pull/89&quot;&gt;#89&lt;/a&gt;, which is based on the Digest authentication example included in the OpenRasta core codebase - but shortly after it was accepted, I found a bug with the way both the Digest and my new Basic implementation handled access to non-secured resources. The fix for this is in &lt;a href=&quot;https://github.com/openrasta/openrasta-core/pull/91&quot;&gt;#91&lt;/a&gt;, which at the time of writing is still being reviewed, so Restival&apos;s currently building against my fork of OpenRasta in order to get the authentication and WhoAmI tests passing properly.&lt;/p&gt; &lt;p&gt;Following those changes to the framework itself, the implementation was pretty easy.&lt;/p&gt; &lt;ol&gt; &lt;li&gt;Provide an &lt;a href=&quot;https://github.com/dylanbeattie/Restival/blob/v0.0.5/src/Restival.Api.OpenRasta/AuthenticationProvider.cs&quot;&gt;implementation of IAuthenticationProvider&lt;/a&gt; based on my IDataStore interface.  &lt;li&gt;Register the authentication provider and pipeline contributor in the Configuration class: &lt;br&gt;&lt;br&gt;&lt;font face=&quot;Consolas&quot;&gt;ResourceSpace.Uses.CustomDependency&amp;lt;IDataStore, FakeDataStore&amp;gt;(DependencyLifetime.Singleton); &lt;br&gt;ResourceSpace.Uses.CustomDependency&amp;lt;IAuthenticationProvider, AuthenticationProvider&amp;gt;(DependencyLifetime.Singleton); &lt;br&gt;ResourceSpace.Uses.PipelineContributor&amp;lt;BasicAuthorizerContributor&amp;gt;(); &lt;/font&gt;&lt;br&gt; &lt;li&gt;Decorate the &lt;a href=&quot;https://github.com/dylanbeattie/Restival/blob/v0.0.5/src/Restival.Api.OpenRasta/Handlers/WhoAmIHandler.cs&quot;&gt;WhoAmIHandler&lt;/a&gt; with &lt;strong&gt;[RequiresBasicAuthentication(realm)]&lt;/strong&gt; &lt;/li&gt;&lt;/ol&gt;Total research and Google time took the best part of a day - including implementing the missing pieces of the framework. Implementation time once those were in place was around an hour, although I suspect even if the necessary authorization components already existed I&apos;d have needed a couple of hours Google time to work out exactly how to plug into the pipeline.&amp;nbsp;&amp;nbsp; &lt;h3&gt;&amp;nbsp;&lt;/h3&gt; &lt;h3&gt;ServiceStack&lt;/h3&gt; &lt;p&gt;Service was really straightforward, mainly because the extension points for overriding built-in authentication behaviour are logical and &lt;a href=&quot;https://github.com/ServiceStack/ServiceStack/wiki/Authentication-and-authorization&quot;&gt;clearly documented&lt;/a&gt;. Implementation involved creating my own AuthProvider that extends the built-in BasicAuthProvider, injecting an IDataStore into this auth provider, and then registering my provider with ServiceStack in AppHost.Configure().&lt;/p&gt; &lt;p&gt;The only gotcha I encountered here was that the first implementation would response with an HTTP redirect to a login page if the request wasn&apos;t authorized - but Googling &quot;&lt;a href=&quot;https://www.google.co.uk/webhp?sourceid=chrome-instant&amp;amp;ion=1&amp;amp;espv=2&amp;amp;ie=UTF-8#q=servicestack+authentication+redirecting+to+/login&quot;&gt;servicestack authentication redirecting to /login&lt;/a&gt;&quot; brought up &lt;a href=&quot;http://stackoverflow.com/questions/13065289/when-servicestack-authentication-fails-do-not-redirect&quot;&gt;this StackOverflow post&lt;/a&gt;, which explained that (a) this only happens for HTML content-type requests (my fault for using a web browser to test my HTTP API, I guess!), and that you can disable it by specifying HtmlRedirect = null when you initialize the auth feature:&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;public class AppHost : AppHostBase { &lt;br&gt;&amp;nbsp; public AppHost() : base(&quot;Restival&quot;, typeof(HelloService).Assembly) { } &lt;br&gt;&amp;nbsp; public override void Configure(Container container) { &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; var db = new FakeDataStore(); &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; container.Register&amp;lt;IDataStore&amp;gt;(c =&amp;gt; db).ReusedWithin(ReuseScope.Container); &lt;br&gt;&lt;/font&gt;&lt;font face=&quot;Consolas&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; var auth = new AuthFeature(() =&amp;gt; new AuthUserSession(), new IAuthProvider[] { new RestivalAuthProvider(db) }) { &lt;br&gt;&amp;nbsp; &lt;font style=&quot;background-color: #ffff00&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/font&gt;&lt;strong&gt;&lt;font style=&quot;background-color: #ffff00&quot;&gt;HtmlRedirect = null &lt;br&gt;&lt;/font&gt;&lt;/strong&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }; &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Plugins.Add(auth); &lt;br&gt;&amp;nbsp; } &lt;br&gt;}&lt;/font&gt;&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;Total implementation time was about half an hour here - but bear in mind I&apos;ve worked with ServiceStack a lot, so I&apos;m familiar with the plugin architecture and the pipeline. &lt;/p&gt; &lt;h3&gt;WebAPI&lt;/h3&gt; &lt;p&gt;It took a surprisingly long time to come up with an HTTP Basic auth implementation on WebAPI. The first stumbling block here was the sheer number of posts, articles and examples demonstrating how to do it. There&apos;s a lot of excellent resources online about adding HTTP Basic authentication support to WebAPI, most of which are subtle variations on a common theme:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;a href=&quot;https://github.com/RickStrahl/WestwindToolkit/tree/master/Westwind.Web.WebApi&quot;&gt;Rick Strahl&apos;s WestwindToolkit&lt;/a&gt;  &lt;li&gt;&lt;a href=&quot;http://www.asp.net/web-api/overview/security/authentication-filters&quot;&gt;Authentication Filters in ASP.NET Web API 2&lt;/a&gt; on &lt;a href=&quot;http://www.asp.net&quot;&gt;www.asp.net&lt;/a&gt;  &lt;li&gt;Robert Muehsig&apos;s &lt;a href=&quot;http://blog.codeinside.eu/2015/04/17/basic-authentication-in-aspnet-webapi/&quot;&gt;Using Basic Authentication in ASP.NET WebAPI&lt;/a&gt; (and the associated &lt;a href=&quot;https://github.com/Code-Inside/Samples/tree/master/2015/WebApiBasicAuth&quot;&gt;code on GitHub&lt;/a&gt;)  &lt;li&gt;Jamie Kurtz&apos; &lt;a href=&quot;https://github.com/jamiekurtz/BasicAuthForWebAPI&quot;&gt;BasicAuthForWebAPI&lt;/a&gt; (based on the ASP.NET membership provider)  &lt;li&gt;&quot;&lt;a href=&quot;http://stackoverflow.com/questions/26464848/custom-authorization-in-asp-net-webapi-what-a-mess&quot;&gt;Custom Authorization in ASP.NET WebAPI - What A Mess?&lt;/a&gt;&quot; on StackOverflow &lt;/li&gt;&lt;/ul&gt; &lt;p&gt;Now, the optimal number of references in a scenario like this is &lt;em&gt;one&lt;/em&gt;. A single article, ideally written by the framework authors, saying &quot;this is how you implement this very simple thing&quot;, and identifying the available integration points. Any framework where 3+ people have written in-depth articles on how to add something as simple as HTTP Basic authentication is insufficiently opinionated. Then there&apos;s the realization that WebAPI follows the ASP.NET MVC convention of decorating controllers and actions with attributes to support features like authentication - and injecting dependencies into attributes is &lt;em&gt;fiddly,&lt;/em&gt; because of the way that attributes are instantiated by the runtime. So... fast-forward several hours of Googling and forking and compiling things and generally poking around, and I decided that Robert Muehsig&apos;s approach is the closest thing to what I&apos;m after. And since it&apos;s on GitHub under an MIT license, I borrowed it. Most of the classes in &lt;a href=&quot;https://github.com/dylanbeattie/Restival/tree/v0.0.5/src/Restival.Api.WebApi/Security&quot;&gt;Restival.Api.WebApi.Security&lt;/a&gt; are lifted directly from Robert&apos;s sample code. This produced a working implementation that passed all the tests, but I must admit I&apos;m still not happy with it. It&apos;s definitely one I plan to revisit. It&apos;s notable that this implementation explicitly decouples the attribute itself from the filter implementation - this is so I can inject dependencies into the filter at runtime whilst still using the attribute declaratively, but it&apos;s rather inelegant and I can&apos;t say I&apos;m terribly happy with it.&lt;/p&gt; &lt;p&gt;Total implementation time for this was at least a day, including research, reading examples, prototyping and digging into the WebAPI source to work out which bits to override. That&apos;s significantly more than I thought it would be, and I&apos;m rather expecting someone to comment on this post &quot;you&apos;re doing it wrong!&quot; and link me to ANOTHER blog post or web page which demonstrates a different approach that &quot;only takes five minutes&quot;. We shall see. :)&lt;/p&gt; &lt;h3&gt;Conclusions&lt;/h3&gt; &lt;p&gt;Here&apos;s how the four authentication implementations stack up. The implementation time here isn&apos;t how long it took, it&apos;s how long I think it would take to do it again now I&apos;m familiar with the various frameworks authentication/plugin patterns. &lt;/p&gt; &lt;table cellspacing=&quot;0&quot; cellpadding=&quot;8&quot; width=&quot;923&quot; border=&quot;1&quot;&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign=&quot;top&quot; width=&quot;134&quot;&gt;&lt;strong&gt;Framework&lt;/strong&gt;&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;213&quot;&gt;&lt;strong&gt;Lines of code&lt;/strong&gt;&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;226&quot;&gt;&lt;strong&gt;New Classes&lt;/strong&gt;&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;143&quot;&gt;&lt;strong&gt;Research time&lt;/strong&gt;&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;205&quot;&gt;&lt;strong&gt;Implementation Time &lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign=&quot;top&quot; width=&quot;134&quot;&gt;WebAPI&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;213&quot;&gt;~ 250&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;226&quot;&gt;6 (&lt;a href=&quot;https://github.com/dylanbeattie/Restival/tree/v0.0.5/src/Restival.Api.WebApi/Security&quot;&gt;here&lt;/a&gt;)&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;143&quot;&gt;1 day&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;205&quot;&gt;2-3 hours&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign=&quot;top&quot; width=&quot;134&quot;&gt;ServiceStack&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;213&quot;&gt;~ 20&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;226&quot;&gt;1 (RestivalAuthProvider)&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;143&quot;&gt;15 minutes&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;205&quot;&gt;15 mins&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign=&quot;top&quot; width=&quot;134&quot;&gt;NancyFX&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;213&quot;&gt;~ 25&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;226&quot;&gt;2 (UserIdentity, UserValidator)&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;143&quot;&gt;30 minutes&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;205&quot;&gt;15 mins&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign=&quot;top&quot; width=&quot;134&quot;&gt;OpenRasta&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;215&quot;&gt;~ 30 &lt;br&gt;&lt;br&gt;&lt;em&gt;plus changes to framework &lt;/em&gt;&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;233&quot;&gt;1 (AuthenticationProvider) &lt;br&gt;&lt;br&gt;&lt;em&gt;plus two new framework classes&lt;/em&gt;&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;143&quot;&gt;1 day&lt;/td&gt; &lt;td valign=&quot;top&quot; width=&quot;205&quot;&gt;1-2 hours&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;p&gt;HTTP Basic auth proved an interesting example, because whilst it&apos;s an obvious feature on any API feature &apos;checklist&apos;, any reasonably mature HTTP API will almost certainly have moved beyond basic authentication to something more sophisticated such as per-client API keys or OAuth2 tokens.&amp;nbsp; &lt;/p&gt; &lt;p&gt;When it comes to ease of implementation, I&apos;d say it&apos;s a dead heat between NancyFX and ServiceStack. I have more experience working with ServiceStack than I do with Nancy, and I suspect my familiarity with their plugin model made things a little easier, but I really don&apos;t think there&apos;s much in it - the integration points are sensible, the documentation is comprehensive (and accurate!), and I&apos;m pretty confident the implementations presented here reflect the idioms of the corresponding platforms.&lt;/p&gt; &lt;p&gt;OpenRasta and WebAPI were much less straightforward, but for very different reasons, In a nutshell, I think WebAPI is insufficiently opinionated - rather than encouraging or enforcing a particular approach, it supports a wealth of different integration patterns and extension points, and it&apos;s really not clear why any one is better than another. OpenRasta, on the other hand, has a very strong authentication pattern that makes a lot of sense once you get your head around it - but a lack of examples and up-to-date documentation means it&apos;s quite hard to work out what the pattern is without digging into the framework source code and getting your hands dirty.&lt;/p&gt; &lt;p&gt;Tune in next time, when we&apos;re going to start adding hypermedia to our /whoami endpoint response.&lt;/p&gt; &lt;p&gt;&lt;em&gt;Thanks to &lt;/em&gt;&lt;a href=&quot;https://twitter.com/jchannon&quot;&gt;&lt;em&gt;Jonathan Channon&lt;/em&gt;&lt;/a&gt;&lt;em&gt;, &lt;/em&gt;&lt;a href=&quot;https://twitter.com/grumpydev&quot;&gt;&lt;em&gt;Steven Robbins&lt;/em&gt;&lt;/a&gt;&lt;em&gt; and &lt;/em&gt;&lt;a href=&quot;https://twitter.com/serialseb&quot;&gt;&lt;em&gt;Sebastien Lambla&lt;/em&gt;&lt;/a&gt;&lt;em&gt; for their help and feedback on this instalment.&lt;/em&gt;&lt;/p&gt;</description>
          <pubDate>2016-01-07T10:08:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2016/01/07/restival-part-5-who-am-i.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2016/01/07/restival-part-5-who-am-i.html</guid>
        </item>
      
    
      
        <item>
          <title>In London next Saturday? Come to PubConf!</title>
          <description>&lt;p&gt;&lt;a href=&quot;http://pubconf.io/&quot;&gt;&lt;img style=&quot;float: right; display: inline&quot; alt=&quot;PubConf&quot;
      src=&quot;http://pubconf.io/img/logo.gif&quot; width=&quot;128&quot; align=&quot;right&quot; height=&quot;131&quot;&gt;&lt;/a&gt;Next Saturday January 16th, some
  of the best speakers in the IT industry – and me – will be appearing at PubConf, a single-track, rapid-fire
  conference. A series of five-minute Ignite-style talks, curated around three themes that we hope are relevant and
  familiar to anybody who does “computering” – managing yourself; the future of computering, and ranting about broken
  things. &lt;/p&gt;
&lt;p&gt;I’ll be talking about “The Face Of Things To Come” on the computering track – all about recent advances in computer
  vision, face detection and recognition, emotion analysis, and how I think this kind of technology is going to change
  the world. For real, this time.&lt;/p&gt;
&lt;p&gt;It promises to be a really great event – and it’s completely absolutely free. Yep, free-as-in-beer free. Read more
  about it at &lt;a href=&quot;http://pubconf.io/&quot;&gt;pubconf.io&lt;/a&gt;, and get your tickets at &lt;a
    href=&quot;https://www.eventbrite.com/e/pubconf-tickets-19880589378&quot;&gt;EventBrite&lt;/a&gt;.&lt;/p&gt;</description>
          <pubDate>2016-01-05T11:35:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2016/01/05/in-london-next-saturday-come-to-pubconf.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2016/01/05/in-london-next-saturday-come-to-pubconf.html</guid>
        </item>
      
    
      
        <item>
          <title>Restival Part 6: Who Am I, Revisited</title>
          <description>&lt;p align=&quot;center&quot;&gt;&lt;strong&gt;Note: code for this instalment is in &lt;/strong&gt;&lt;a title=&quot;https://github.com/dylanbeattie/Restival/tree/v0.0.6&quot; href=&quot;https://github.com/dylanbeattie/Restival/tree/v0.0.6&quot;&gt;&lt;strong&gt;https://github.com/dylanbeattie/Restival/tree/v0.0.6&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;In &lt;a href=&quot;http://dylanbeattie.blogspot.co.uk/2015/12/restival-part-5-who-am-i.html&quot;&gt;the last instalment&lt;/a&gt;, we looked at adding HTTP Basic authentication to a simple HTTP endpoint - GET /whoami - that returns information about the authenticated user.&lt;/p&gt;  &lt;p&gt;Well... I didn&apos;t like it. Both the OpenRasta and the WebAPI implementations felt really over-engineered, so I kept digging and discovered a few things that made everything much, much cleaner.&lt;/p&gt;  &lt;h3&gt;Basic auth in OpenRasta - Take 2&lt;/h3&gt;  &lt;p&gt;There&apos;s an HTTP Basic authentication feature baked into OpenRasta 2.5.x, but all of the classes are marked as deprecated so in my first implementation I avoided using it. After talking with Seb, the creator of OpenRasta, I understand a lot more about the rationale behind deprecating these classes - they&apos;re earmarked for migration into a standalone module, not for outright deletion, and they&apos;ll definitely remain part of the OpenRasta 2.x codebase for the foreseeable future.&lt;/p&gt;  &lt;p&gt;Armed with that knowledge, and the magical compiler directive &lt;font face=&quot;Consolas&quot;&gt;&lt;strong&gt;#pragma warning disable 618&lt;/strong&gt; &lt;/font&gt;that stops Visual Studio complaining about you using deprecated code, I switched Restival back to running on the OpenRasta NuGet package instead of my forked build, and reimplemented the authentication feature - and yes, it&apos;s much, much nicer.&lt;/p&gt;  &lt;p&gt;There&apos;s a RestivalAuthenticator which implements OpenRasta&apos;s IBasicAuthenticator interface - as with the other frameworks, this ends up being a really simple wrapper around the IDataStore:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;public class RestivalAuthenticator : IBasicAuthenticator {       &lt;br /&gt;&amp;#160; private readonly IDataStore db;&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;&amp;#160; public RestivalAuthenticator(IDataStore db) {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; this.db = db;        &lt;br /&gt;&amp;#160; }&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;&amp;#160; public AuthenticationResult Authenticate(BasicAuthRequestHeader header) {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; var user = db.FindUserByUsername(header.Username);        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; if (user != null &amp;amp;&amp;amp; user.Password == header.Password) return (new AuthenticationResult.Success(user.Username, new string[] { }));        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; return (new AuthenticationResult.Failed());        &lt;br /&gt;&amp;#160; }&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;&amp;#160; public string Realm { get { return (&amp;quot;Restival.OpenRasta&amp;quot;); } }       &lt;br /&gt;}&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;and then there&apos;s the configuration code to initialise the authentication provider. &lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;ResourceSpace.Uses.PipelineContributor&amp;lt;AuthenticationContributor&amp;gt;();       &lt;br /&gt;ResourceSpace.Uses.PipelineContributor&amp;lt;AuthenticationChallengerContributor&amp;gt;();        &lt;br /&gt;ResourceSpace.Uses.CustomDependency&amp;lt;IAuthenticationScheme, BasicAuthenticationScheme&amp;gt;(DependencyLifetime.Singleton);        &lt;br /&gt;ResourceSpace.Uses.CustomDependency&amp;lt;IBasicAuthenticator, RestivalAuthenticator&amp;gt;(DependencyLifetime.Transient);&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;This one stumped me for a while, until I realised that - unlike, say, Nancy, which just does everything by magic, you need to explicitly register both the AuthenticationContributor and the AuthenticationChallengerContributor. These are the OpenRasta components that handle the HTTP header parsing, decoding and the WWW-Authenticate challenge response, but if you don&apos;t explicitly wire them into your pipeline, your custom auth classes will never get called.&lt;/p&gt;  &lt;h3&gt;Basic auth in WebAPI - Take 2&lt;/h3&gt;  &lt;p&gt;As part of the last instalment, I wired up the &lt;a href=&quot;http://www.lightinject.net/&quot;&gt;LightInject&lt;/a&gt; IoC container to my WebAPI implementation. I love LightInject, but something I had never previously realised is that &lt;em&gt;LightInject can do property injection on your custom attributes.&lt;strong&gt; &lt;/strong&gt;&lt;/em&gt;This is a game-changer, because previously I&apos;d been following a pattern of using purely decorative attributes - i.e. with no behaviour - and then implementing a separate ActionFilter that would check for the presence of the corresponding attribute before running some custom code - and all this just so I could inject dependencies into my filter instances.&lt;/p&gt;  &lt;p&gt;Well, with LightInject up and running, you don&apos;t need to do any of that - you can just put a public IService { get; set; } onto your MyCustomAttribute class, and LightInject will resolve IService at runtime and inject an instance of MyAwesomeService : IService into your attribute code. Which means the half-a-dozen classes worth of custom filters and responses from the last implementation can be ripped out in favour of a single &lt;a href=&quot;https://github.com/dylanbeattie/Restival/blob/v0.0.6/src/Restival.Api.WebApi/Security/RequireHttpBasicAuthorizationAttribute.cs&quot;&gt;RequireHttpBasicAuthorizationAttribute&lt;/a&gt; - which overrides WebAPI&apos;s built-in &lt;strong&gt;AuthorizeAttribute&lt;/strong&gt; class to provide authorization header parsing, WWW-Authenticate challenge response, and hook the authentication up to our IDataStore interface.&lt;/p&gt;  &lt;p&gt;I&apos;m much happier now with all four implementations, so it raises the interesting question of how much development time is really worth. Based on the code provided here, I suspect a good developer could implement HTTP Basic auth on any of these frameworks in about fifteen minutes - but something that takes fifteen minutes to implement doesn&apos;t really count if it takes you two days to work out how to do that fifteen-minute implementation.&lt;/p&gt;  &lt;p&gt;In forthcoming instalments, we&apos;re going to be adding hypermedia, HAL+JSON and resource expansion - as we move further away from basic HTTP capabilities and into more advanced REST/serialization/content negotiation, it&apos;ll be interesting to see how our four frameworks measure up.&lt;/p&gt;  </description>
          <pubDate>2015-12-08T16:23:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2015/12/08/restival-part-6-who-am-i-revisited.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2015/12/08/restival-part-6-who-am-i-revisited.html</guid>
        </item>
      
    
      
        <item>
          <title>Restival Part 5: Who Am I?</title>
          <description>&lt;p align=&quot;center&quot;&gt;&lt;strong&gt;NOTE: Code for this article is at &lt;a title=&quot;https://github.com/dylanbeattie/Restival/tree/v0.0.5&quot; href=&quot;https://github.com/dylanbeattie/Restival/tree/v0.0.5&quot;&gt;https://github.com/dylanbeattie/Restival/tree/v0.0.5&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;UPDATE: The WebAPI and OpenRasta implementations described here are... inelegant. After this release, I spent a little more time and came up with something much cleaner - which you can read all about in the follow-up post &lt;a title=&quot;http://dylanbeattie.blogspot.co.uk/2015/12/restival-part-6-who-am-i-revisited.html&quot; href=&quot;http://dylanbeattie.blogspot.co.uk/2015/12/restival-part-6-who-am-i-revisited.html&quot;&gt;http://dylanbeattie.blogspot.co.uk/2015/12/restival-part-6-who-am-i-revisited.html&lt;/a&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Welcome back. It&apos;s been a very busy summer that&apos;s turned into a very busy autumn - including a fantastic couple of weeks in Eastern Europe, speaking at &lt;a href=&quot;http://buildstuff.lt/&quot;&gt;BuildStuff in Lithuania&lt;/a&gt; and &lt;a href=&quot;http://buildstuff.com.ua/&quot;&gt;Ukraine&lt;/a&gt;, where I met a lot of very interesting people, discussed a lot of very interesting ideas, tried a lot of very interesting food, and generally got a nice hard kick out of my comfort zone. Which is good. &lt;/p&gt;  &lt;p&gt;&lt;img title=&quot;If your name&amp;#39;s not on the list, you&amp;#39;re not coming in!&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; float: right; padding-top: 0px; padding-left: 0px; margin: 0px 0px 20px 20px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;If your name&amp;#39;s not on the list, you&amp;#39;re not coming in!&quot; src=&quot;http://40.media.tumblr.com/bc16bc661736d3ec0077b81072bebc3e/tumblr_mxi1gmcnt01ryt88so1_1280.png&quot; width=&quot;240&quot; align=&quot;right&quot; height=&quot;133&quot; /&gt;Anyway. As London starts getting frosty and festive, it&apos;s time to pick up where we left off earlier in the year with my ongoing Restival project - implementing the same API in four different .NET API frameworks (ServiceStack, NancyFX, OpenRasta and WebAPI).&lt;/p&gt;  &lt;p&gt;In this instalment, we&apos;re going to add two very straightforward capabilities to our API:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;HTTP Basic authentication. Clients can include a username/password in an &lt;font face=&quot;Consolas&quot;&gt;HTTP Authorization&lt;/font&gt; header, and the API will verify their credentials against a simple user store. (If you&apos;re doing this in production, you&apos;d enforce HTTPS/TLS so that credentials can&apos;t be sniffed in transit, but since this is a demonstration project and I&apos;m optimising for readability, TLS is out of scope for now.) &lt;/li&gt;    &lt;li&gt;A /whoami endpoint, where authenticated users can GET details of their own user record. &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;And that&apos;s it. Sounds simple, right? OK, first things first - let&apos;s thrash out our requirements into something a little more detailed:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;strong&gt;GET /whoami &lt;/strong&gt;with valid credentials should return the current users&apos; details (id, name, username) &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;But remember - we&apos;re building an authentication system, so we should also be testing the negative responses:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;strong&gt;GET /whoami &lt;/strong&gt;with invalid credentials returns 401 Unauthorized       &lt;ul&gt;       &lt;li&gt;&amp;quot;invalid&amp;quot; means an unsupported scheme, an unsupported header format, invalid credential encoding, or a correctly formatted header containing an unrecognised username and/or password. &lt;/li&gt;     &lt;/ul&gt;   &lt;/li&gt;    &lt;li&gt;&lt;strong&gt;GET /whoami &lt;/strong&gt;without any credentials returns a 401 Unauthorized &lt;/li&gt;    &lt;li&gt;&lt;strong&gt;GET /whoami &lt;/strong&gt;without any credentials returns a WWW-Authenticate header indicating that we support HTTP Basic authentication. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;The WWW-Authenticate thing is partly a node to HATEOAS, and partly there to make it easier to test things from a normal web browser. I tend to use &lt;a href=&quot;https://www.getpostman.com/&quot;&gt;Postman&lt;/a&gt; for building and testing any serious HTTP services, but when it comes to quick&apos;n&apos;dirty API discovery and troubleshooting, I can&apos;t overstate the convenience of being able to paste something into a normal web browser and get a readable response.&lt;/p&gt;  &lt;p&gt;The test code is in &lt;a href=&quot;https://github.com/dylanbeattie/Restival/blob/v0.0.5/src/Restival.ApiTests/WhoAmIApiTestsBase.cs&quot;&gt;WhoAmITestBase.cs&lt;/a&gt;. Notice that in this implementation, our users are stored in a fake data store - FakeDataStore.cs - and we&apos;re actually using this class as a TestCaseSource in our tests so we maintain parity between the test data and the test coverage. &lt;/p&gt;  &lt;p&gt;The WhoAmI endpoint was trivial - find the current user name, look them up in the user store, and convert their user data to a WhoAmIResponse. The fun part here was the authentication. &lt;/p&gt;  &lt;h3&gt;A Note on Authentication vs Authorization&lt;/h3&gt;  &lt;p&gt;NOTE: Authentication and authorization are, strictly speaking, different things. Authentication is &amp;quot;who are you?&amp;quot;, authorization is &amp;quot;are you allowed to do this thing?&amp;quot; Someone trying to enter the United States with a Libyan passport is &lt;em&gt;authenticated &lt;/em&gt;- the US border service know exactly who they are - but they&apos;re not &lt;em&gt;authorized&lt;strong&gt; &lt;/strong&gt;&lt;/em&gt;because Libyan citizens can&apos;t enter the US without a valid visa.&lt;/p&gt;  &lt;p&gt;In one respect, HTTP gets this exactly right - a 401 Unauthorized means &amp;quot;we don&apos;t know who you are&amp;quot;, a 403 Forbidden means &amp;quot;we know who you are, but you&apos;re not allowed to do this.&amp;quot; In another respect, it gets this badly wrong, because the Authentication header in the HTTP specification is called Authorization.&lt;/p&gt;  &lt;h3&gt;Authentication - The Best Case Scenario&lt;/h3&gt;  &lt;p&gt;OK, to build our secure /whoami endpoint, we need a handful of extension points in our framework. We need to:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Validate a supplied username and password against our own credential store &lt;/li&gt;    &lt;li&gt;Indicate that specific resources require authorization, so that unauthorized requests for those resources will be rejected &lt;/li&gt;    &lt;li&gt;Determine the identity of the authenticated user when responding to an authorized request &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;The rest - WWW-Authenticate negotiation, decoding base64-encoded Authorization headers, returning 401 Unauthorized responses - is completely generic, so in an ideal world our framework will do all this for us; all we need to do is implement the three extension points above.&lt;/p&gt;  &lt;p&gt;Let&apos;s look at Nancy first, because alphabetical is as good an order as any.&lt;/p&gt;  &lt;h3&gt;NancyFX&lt;/h3&gt;  &lt;p&gt;Implementing HTTP Basic auth in NancyFX proved fairly straightforward. The first head-scratching moment I hit is that - unlike the other frameworks in this project - Nancy is so lightweight that I didn&apos;t actually have any kind of bootstrapper or initialization code anywhere, so it wasn&apos;t immediately obvious where to configure the additional pipeline steps. A bit of Googling and poking around the NancyFX source led me to the &lt;a href=&quot;https://github.com/NancyFx/Nancy/tree/master/src/Nancy.Demo.Authentication.Basic&quot;&gt;Nancy.Demo.Authentication.Basic&lt;/a&gt; project, which made things a lot clearer. From this point, implementation involved:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Add an &lt;strong&gt;AuthenticationBootstrapper&lt;/strong&gt; - which Just WorkedTM without any explicit registration. I&apos;m guessing it&apos;s invoked by &lt;strike&gt;magic&lt;/strike&gt; sufficiently advanced technology, because it overrides &lt;strong&gt;DefaultNancyBootstrapper.&lt;/strong&gt; &lt;/li&gt;    &lt;li&gt;Implement IUserValidator to connect Nancy to my custom user store - &lt;a href=&quot;https://github.com/dylanbeattie/Restival/blob/v0.0.5/src/Restival.Api.Nancy/UserValidator.cs&quot;&gt;my implementation&lt;/a&gt; is just a really simple wrapper around my user data store. My UserValidator depends on my IDataStore interface - and, thanks to Nancy&apos;s auto-configuration, I didn&apos;t have to explicitly register either of these as dependencies. &lt;/li&gt;    &lt;li&gt;In this bootstrapper, call pipelines.EnableBasicAuthentication() and pass in a basic authentication configuration. &lt;/li&gt; &lt;/ol&gt;  &lt;blockquote&gt;   &lt;p&gt;protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines) {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; base.ApplicationStartup(container, pipelines);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; var authConfig = new BasicAuthenticationConfiguration(container.Resolve&amp;lt;IUserValidator&amp;gt;(), &amp;quot;Restival.Nancy&amp;quot;);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; pipelines.EnableBasicAuthentication(authConfig);       &lt;br /&gt;}&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Finally, the &lt;a href=&quot;https://github.com/dylanbeattie/Restival/blob/v0.0.5/src/Restival.Api.Nancy/WhoAmIModule.cs&quot;&gt;WhoAmIModule&lt;/a&gt; needs a call to &lt;strong&gt;this.RequiresAuthentication()&lt;/strong&gt;, and that&apos;s it. Clean, straightforward, no real showstoppers. &lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;UPDATE: The NancyFX documentation has now been updated with a &lt;a href=&quot;https://github.com/NancyFx/Nancy/wiki/Basic-Authentication&quot;&gt;detailed walkthrough on enabling HTTP Basic authentication&lt;/a&gt;&amp;#160;&lt;/p&gt; &lt;/blockquote&gt;  &lt;h3&gt;OpenRasta&lt;/h3&gt;  &lt;p&gt;Adding HTTP Basic auth to OpenRasta was a lot more involved - and the reasons why provide some interesting insight into the underlying development practises of the frameworks we&apos;re comparing. In 2013, there were some major changes to the request processing pipeline used by OpenRasta. As part of these changes, the basic authentication features of OpenRasta (dating back to 2008) were marked as deprecated - and until now, nobody&apos;s contributed an up-to-date implementation, I&apos;m guessing because none of the people using OpenRasta have needed one. Which left me with a choice - do I use the deprecated approach, contribute my own implementation, or disqualify OpenRasta? Well, disqualification would be no fun, and deprecated code means you get Resharper yelling at you all over the place, so I ended up implementing a pipeline-based HTTP Basic authorization module to the OpenRasta codebase.&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;Whilst writing this article, I had a long and very enlightening chat with &lt;a href=&quot;https://twitter.com/serialseb&quot;&gt;Sebastien Lambla&lt;/a&gt;, the creator of OpenRasta, about the current state of the project and specifically the authentication capabilities. It turns out the features marked as deprecated were intended to be migrated into a separate OpenRasta.Security module, thus decoupling authentication concerns from the core pipeline model - but this hasn&apos;t happened yet, and so the code is still in the OpenRasta core package. It&apos;s up to you, the implementer, whether to use the existing (&apos;deprecated&apos;) HTTP authentication provider, or to roll your own based on the new pipeline approach. &lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;The original pull request for this is &lt;a href=&quot;https://github.com/openrasta/openrasta-core/pull/89&quot;&gt;#89&lt;/a&gt;, which is based on the Digest authentication example included in the OpenRasta core codebase - but shortly after it was accepted, I found a bug with the way both the Digest and my new Basic implementation handled access to non-secured resources. The fix for this is in &lt;a href=&quot;https://github.com/openrasta/openrasta-core/pull/91&quot;&gt;#91&lt;/a&gt;, which at the time of writing is still being reviewed, so Restival&apos;s currently building against my fork of OpenRasta in order to get the authentication and WhoAmI tests passing properly.&lt;/p&gt;  &lt;p&gt;Following those changes to the framework itself, the implementation was pretty easy.&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Provide an &lt;a href=&quot;https://github.com/dylanbeattie/Restival/blob/v0.0.5/src/Restival.Api.OpenRasta/AuthenticationProvider.cs&quot;&gt;implementation of IAuthenticationProvider&lt;/a&gt; based on my IDataStore interface. &lt;/li&gt;    &lt;li&gt;Register the authentication provider and pipeline contributor in the Configuration class:      &lt;br /&gt;      &lt;br /&gt;&lt;font face=&quot;Consolas&quot;&gt;ResourceSpace.Uses.CustomDependency&amp;lt;IDataStore, FakeDataStore&amp;gt;(DependencyLifetime.Singleton);        &lt;br /&gt;ResourceSpace.Uses.CustomDependency&amp;lt;IAuthenticationProvider, AuthenticationProvider&amp;gt;(DependencyLifetime.Singleton);         &lt;br /&gt;ResourceSpace.Uses.PipelineContributor&amp;lt;BasicAuthorizerContributor&amp;gt;(); &lt;/font&gt;      &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;Decorate the &lt;a href=&quot;https://github.com/dylanbeattie/Restival/blob/v0.0.5/src/Restival.Api.OpenRasta/Handlers/WhoAmIHandler.cs&quot;&gt;WhoAmIHandler&lt;/a&gt; with &lt;strong&gt;[RequiresBasicAuthentication(realm)]&lt;/strong&gt; &lt;/li&gt; &lt;/ol&gt; Total research and Google time took the best part of a day - including implementing the missing pieces of the framework. Implementation time once those were in place was around an hour, although I suspect even if the necessary authorization components already existed I&apos;d have needed a couple of hours Google time to work out exactly how to plug into the pipeline.&amp;#160;&amp;#160; &lt;h3&gt;&amp;#160;&lt;/h3&gt;  &lt;h3&gt;ServiceStack&lt;/h3&gt;  &lt;p&gt;Service was really straightforward, mainly because the extension points for overriding built-in authentication behaviour are logical and &lt;a href=&quot;https://github.com/ServiceStack/ServiceStack/wiki/Authentication-and-authorization&quot;&gt;clearly documented&lt;/a&gt;. Implementation involved creating my own AuthProvider that extends the built-in BasicAuthProvider, injecting an IDataStore into this auth provider, and then registering my provider with ServiceStack in AppHost.Configure().&lt;/p&gt;  &lt;p&gt;The only gotcha I encountered here was that the first implementation would response with an HTTP redirect to a login page if the request wasn&apos;t authorized - but Googling &amp;quot;&lt;a href=&quot;https://www.google.co.uk/webhp?sourceid=chrome-instant&amp;amp;ion=1&amp;amp;espv=2&amp;amp;ie=UTF-8#q=servicestack+authentication+redirecting+to+/login&quot;&gt;servicestack authentication redirecting to /login&lt;/a&gt;&amp;quot; brought up &lt;a href=&quot;http://stackoverflow.com/questions/13065289/when-servicestack-authentication-fails-do-not-redirect&quot;&gt;this StackOverflow post&lt;/a&gt;, which explained that (a) this only happens for HTML content-type requests (my fault for using a web browser to test my HTTP API, I guess!), and that you can disable it by specifying HtmlRedirect = null when you initialize the auth feature:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;public class AppHost : AppHostBase {        &lt;br /&gt;&amp;#160; public AppHost() : base(&amp;quot;Restival&amp;quot;, typeof(HelloService).Assembly) { }         &lt;br /&gt;&amp;#160; public override void Configure(Container container) {         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; var db = new FakeDataStore();         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; container.Register&amp;lt;IDataStore&amp;gt;(c =&amp;gt; db).ReusedWithin(ReuseScope.Container);         &lt;br /&gt;&lt;/font&gt;&lt;font face=&quot;Consolas&quot;&gt;&amp;#160;&amp;#160;&amp;#160; var auth = new AuthFeature(() =&amp;gt; new AuthUserSession(), new IAuthProvider[] { new RestivalAuthProvider(db) }) {        &lt;br /&gt;&amp;#160; &lt;font style=&quot;background-color: #ffff00&quot;&gt;&amp;#160;&amp;#160;&amp;#160; &lt;/font&gt;&lt;strong&gt;&lt;font style=&quot;background-color: #ffff00&quot;&gt;HtmlRedirect = null            &lt;br /&gt;&lt;/font&gt;&lt;/strong&gt;&amp;#160;&amp;#160;&amp;#160; };         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Plugins.Add(auth);         &lt;br /&gt;&amp;#160; }         &lt;br /&gt;}&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Total implementation time was about half an hour here - but bear in mind I&apos;ve worked with ServiceStack a lot, so I&apos;m familiar with the plugin architecture and the pipeline. &lt;/p&gt;  &lt;h3&gt;WebAPI&lt;/h3&gt;  &lt;p&gt;It took a surprisingly long time to come up with an HTTP Basic auth implementation on WebAPI. The first stumbling block here was the sheer number of posts, articles and examples demonstrating how to do it. There&apos;s a lot of excellent resources online about adding HTTP Basic authentication support to WebAPI, most of which are subtle variations on a common theme:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;a href=&quot;https://github.com/RickStrahl/WestwindToolkit/tree/master/Westwind.Web.WebApi&quot;&gt;Rick Strahl&apos;s WestwindToolkit&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href=&quot;http://www.asp.net/web-api/overview/security/authentication-filters&quot;&gt;Authentication Filters in ASP.NET Web API 2&lt;/a&gt; on &lt;a href=&quot;http://www.asp.net&quot;&gt;www.asp.net&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;Robert Muehsig&apos;s &lt;a href=&quot;http://blog.codeinside.eu/2015/04/17/basic-authentication-in-aspnet-webapi/&quot;&gt;Using Basic Authentication in ASP.NET WebAPI&lt;/a&gt; (and the associated &lt;a href=&quot;https://github.com/Code-Inside/Samples/tree/master/2015/WebApiBasicAuth&quot;&gt;code on GitHub&lt;/a&gt;) &lt;/li&gt;    &lt;li&gt;Jamie Kurtz&apos; &lt;a href=&quot;https://github.com/jamiekurtz/BasicAuthForWebAPI&quot;&gt;BasicAuthForWebAPI&lt;/a&gt; (based on the ASP.NET membership provider) &lt;/li&gt;    &lt;li&gt;&amp;quot;&lt;a href=&quot;http://stackoverflow.com/questions/26464848/custom-authorization-in-asp-net-webapi-what-a-mess&quot;&gt;Custom Authorization in ASP.NET WebAPI - What A Mess?&lt;/a&gt;&amp;quot; on StackOverflow &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Now, the optimal number of references in a scenario like this is &lt;em&gt;one&lt;/em&gt;. A single article, ideally written by the framework authors, saying &amp;quot;this is how you implement this very simple thing&amp;quot;, and identifying the available integration points. Any framework where 3+ people have written in-depth articles on how to add something as simple as HTTP Basic authentication is insufficiently opinionated. Then there&apos;s the realization that WebAPI follows the ASP.NET MVC convention of decorating controllers and actions with attributes to support features like authentication - and injecting dependencies into attributes is &lt;em&gt;fiddly,&lt;/em&gt; because of the way that attributes are instantiated by the runtime. So... fast-forward several hours of Googling and forking and compiling things and generally poking around, and I decided that Robert Muehsig&apos;s approach is the closest thing to what I&apos;m after. And since it&apos;s on GitHub under an MIT license, I borrowed it. Most of the classes in &lt;a href=&quot;https://github.com/dylanbeattie/Restival/tree/v0.0.5/src/Restival.Api.WebApi/Security&quot;&gt;Restival.Api.WebApi.Security&lt;/a&gt; are lifted directly from Robert&apos;s sample code. This produced a working implementation that passed all the tests, but I must admit I&apos;m still not happy with it. It&apos;s definitely one I plan to revisit. It&apos;s notable that this implementation explicitly decouples the attribute itself from the filter implementation - this is so I can inject dependencies into the filter at runtime whilst still using the attribute declaratively, but it&apos;s rather inelegant and I can&apos;t say I&apos;m terribly happy with it.&lt;/p&gt;  &lt;p&gt;Total implementation time for this was at least a day, including research, reading examples, prototyping and digging into the WebAPI source to work out which bits to override. That&apos;s significantly more than I thought it would be, and I&apos;m rather expecting someone to comment on this post &amp;quot;you&apos;re doing it wrong!&amp;quot; and link me to ANOTHER blog post or web page which demonstrates a different approach that &amp;quot;only takes five minutes&amp;quot;. We shall see. :)&lt;/p&gt;  &lt;h3&gt;Conclusions&lt;/h3&gt;  &lt;p&gt;Here&apos;s how the four authentication implementations stack up. The implementation time here isn&apos;t how long it took, it&apos;s how long I think it would take to do it again now I&apos;m familiar with the various frameworks authentication/plugin patterns. &lt;/p&gt;  &lt;table cellspacing=&quot;0&quot; cellpadding=&quot;8&quot; width=&quot;923&quot; border=&quot;1&quot;&gt;&lt;tbody&gt;     &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;134&quot;&gt;&lt;strong&gt;Framework&lt;/strong&gt;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;213&quot;&gt;&lt;strong&gt;Lines of code&lt;/strong&gt;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;226&quot;&gt;&lt;strong&gt;New Classes&lt;/strong&gt;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;143&quot;&gt;&lt;strong&gt;Research time&lt;/strong&gt;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;205&quot;&gt;&lt;strong&gt;Implementation Time &lt;/strong&gt;&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;134&quot;&gt;WebAPI&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;213&quot;&gt;~ 250&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;226&quot;&gt;6 (&lt;a href=&quot;https://github.com/dylanbeattie/Restival/tree/v0.0.5/src/Restival.Api.WebApi/Security&quot;&gt;here&lt;/a&gt;)&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;143&quot;&gt;1 day&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;205&quot;&gt;2-3 hours&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;134&quot;&gt;ServiceStack&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;213&quot;&gt;~ 20&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;226&quot;&gt;1 (RestivalAuthProvider)&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;143&quot;&gt;15 minutes&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;205&quot;&gt;15 mins&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;134&quot;&gt;NancyFX&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;213&quot;&gt;~ 25&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;226&quot;&gt;2 (UserIdentity, UserValidator)&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;143&quot;&gt;30 minutes&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;205&quot;&gt;15 mins&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;134&quot;&gt;OpenRasta&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;215&quot;&gt;~ 30          &lt;br /&gt;          &lt;br /&gt;&lt;em&gt;plus changes to framework &lt;/em&gt;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;233&quot;&gt;1 (AuthenticationProvider)          &lt;br /&gt;          &lt;br /&gt;&lt;em&gt;plus two new framework classes&lt;/em&gt;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;143&quot;&gt;1 day&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;205&quot;&gt;1-2 hours&lt;/td&gt;     &lt;/tr&gt;   &lt;/tbody&gt;&lt;/table&gt;  &lt;p&gt;HTTP Basic auth proved an interesting example, because whilst it&apos;s an obvious feature on any API feature &apos;checklist&apos;, any reasonably mature HTTP API will almost certainly have moved beyond basic authentication to something more sophisticated such as per-client API keys or OAuth2 tokens.&amp;#160; &lt;/p&gt;  &lt;p&gt;When it comes to ease of implementation, I&apos;d say it&apos;s a dead heat between NancyFX and ServiceStack. I have more experience working with ServiceStack than I do with Nancy, and I suspect my familiarity with their plugin model made things a little easier, but I really don&apos;t think there&apos;s much in it - the integration points are sensible, the documentation is comprehensive (and accurate!), and I&apos;m pretty confident the implementations presented here reflect the idioms of the corresponding platforms.&lt;/p&gt;  &lt;p&gt;OpenRasta and WebAPI were much less straightforward, but for very different reasons, In a nutshell, I think WebAPI is insufficiently opinionated - rather than encouraging or enforcing a particular approach, it supports a wealth of different integration patterns and extension points, and it&apos;s really not clear why any one is better than another. OpenRasta, on the other hand, has a very strong authentication pattern that makes a lot of sense once you get your head around it - but a lack of examples and up-to-date documentation means it&apos;s quite hard to work out what the pattern is without digging into the framework source code and getting your hands dirty.&lt;/p&gt;  &lt;p&gt;Tune in next time, when we&apos;re going to start adding hypermedia to our /whoami endpoint response.&lt;/p&gt;  &lt;p&gt;&lt;em&gt;Thanks to &lt;/em&gt;&lt;a href=&quot;https://twitter.com/jchannon&quot;&gt;&lt;em&gt;Jonathan Channon&lt;/em&gt;&lt;/a&gt;&lt;em&gt;, &lt;/em&gt;&lt;a href=&quot;https://twitter.com/grumpydev&quot;&gt;&lt;em&gt;Steven Robbins&lt;/em&gt;&lt;/a&gt;&lt;em&gt; and &lt;/em&gt;&lt;a href=&quot;https://twitter.com/serialseb&quot;&gt;&lt;em&gt;Sebastien Lambla&lt;/em&gt;&lt;/a&gt;&lt;em&gt; for their help and feedback on this instalment.&lt;/em&gt;&lt;/p&gt;  </description>
          <pubDate>2015-12-07T16:25:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2015/12/07/restival-part-5-who-am-i.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2015/12/07/restival-part-5-who-am-i.html</guid>
        </item>
      
    
      
        <item>
          <title>Membership and Dynamics CRM - How&apos;s It All Going, Then?</title>
          <description>&lt;p&gt;A few months back, I blogged about how Spotlight has chosen Microsoft Dynamics CRM 2015 as the platform for delivering our new membership system, and our high-level plan for incorporating Dynamics into our existing infrastructure. Six months on, I thought it&apos;d be worth revisiting to talk about how the project is progressing and the lessons we&apos;ve learned so far. Particularly since people on Twitter keep asking me if I&apos;m regretting it yet. :)&lt;/p&gt;  &lt;p&gt;Now, I wear many hats. I&apos;m a systems architect. I sit on the strategic board at Spotlight. I&apos;ve been here long enough that I understand most of our business processes in excruciating detail. I&apos;m interested in UX and interaction design. And, sometimes, when nothing else is going on, I write code and build stuff. &lt;/p&gt;  &lt;p&gt;With my business hat on, Dynamics CRM is clearly the right solution for us. It works. It supports a business-to-consumer sales model, out of the box, which works really well for us (it surprised me how many of the other CRM systems we looked at assume that every customer is a company and every sale starts with a quote.) Dynamics CRM offers features like case management, service calendars, Outlook integration, multiple price lists and currencies, mobile device support, Active Directory integration - things that would probably never make it to the top of the backlog if we were building our own system. And that&apos;s fine - we are not in the CRM business. We build software to help people cast actors and make movies; everything else is overhead.&lt;/p&gt;  &lt;p&gt;Strategically, it makes sense. Our initial customization and integration phase will end up being less than six months, after which membership and CRM will be very much a &amp;quot;solved problem&amp;quot; as far as our development team is concerned, freeing us up to focus on other things. Using an off-the-shelf product as the backbone of our membership and business activity gives us lots more options for making improvements in future - certainly more than if we&apos;d built our own system.&lt;/p&gt;  &lt;p&gt;So I still believe Dynamics CRM was, and remains, the best solution to our business requirements.&lt;/p&gt;  &lt;p&gt;But... (you knew that was coming, didn&apos;t you?) as a software developer, it has frustrations, which can be broadly categorized as good, bad and ugly.&lt;/p&gt;  &lt;h4&gt;The Good...&lt;/h4&gt;  &lt;p&gt;It&apos;s a hugely complex product, and it has a learning curve. There&apos;s frequently several ways to achieve something - multiple email integration patterns, several different ways to implement single sign-on for user authentication. Almost anything is possible once you know how. This is frustrating when you don&apos;t know how, but once you get the hang of it, it works pretty well.&lt;/p&gt;  &lt;p&gt;You can also write custom code in C# that runs directly within Dynamics, which - again - has a hell of a learning curve but is actually a really powerful technique. With a little ingenuity, you can even build C# components that implement your business logic and rules, and then decide later where that logic should be deployed. I like that sort of flexibility.&lt;/p&gt;  &lt;h4&gt;...the Bad...&lt;/h4&gt;  &lt;p&gt;Dynamics CRM is the heart of a software ecosystem that&apos;s still a long, long way from the software-as-craft ethos which is becoming fairly common elsewhere. Partly, this is a question of tooling and process. Things like revision control, continuous deployment and unit testing become really quite hard when your &amp;quot;project&amp;quot; is a ZIP file full of proprietary XML - no branching, no merging, only the most rudimentary support for rollbacks. As a developer, this is frustrating - but it&apos;s important to remember that systems like Dynamics blur the boundary between &amp;quot;developers&amp;quot; and &amp;quot;users&amp;quot; that&apos;s prevalent in most development projects. It&apos;s a platform. The people who use it every day for the next decade WILL be making changes to it - because the whole reason we&apos;re doing it is to empower The BusinessTM to manage their own membership system - and those people aren&apos;t software developers. &lt;/p&gt;  &lt;p&gt;More worryingly, Dynamics CRM appears to be one of those products where you can make a very good living as an &amp;quot;expert&amp;quot; without actually knowing anything. The amount of misinformation and bad advice surrounding this product is astonishing. I&apos;ve read blog posts promoting the most dreadful solutions. I&apos;ve spoken to consultants - and interviewed contractors on quite generous day-rates - who are completely unaware of whole swathes of the core product&apos;s capabilities. That makes it really difficult to know who you can trust - and how much time to invest in validating your assumptions before committing to any kind of delivery.&lt;/p&gt;  &lt;p&gt;This isn&apos;t a problem with Microsoft Dynamics &lt;em&gt;per se&lt;/em&gt; - I think it&apos;s probably characteristic of any market sector where we see the dreaded &amp;quot;point and click - no development required!&amp;quot; shibboleth - but it&apos;s still a lot less fun than working on, say, Nancy or ServiceStack, where the code is open source and most of the community seem to know what they&apos;re doing. &lt;/p&gt;  &lt;h4&gt;...and the Ugly&lt;/h4&gt;  &lt;p&gt;And then there&apos;s the stuff that&apos;s just plain stupid. For example, there&apos;s an entity in CRM called a Contract. Once a contract has been &amp;quot;invoiced&amp;quot; - which you can do by clicking a single button in the UI - it is immutable. There is literally NOTHING you can do - even with all the Administrator permissions in the world - to change any detail on that contract. Ever. There&apos;s various workarounds for this. &lt;a href=&quot;https://msdn.microsoft.com/en-gb/library/gg328503.aspx&quot;&gt;Microsoft&apos;s recommendation&lt;/a&gt; is that you need to &amp;quot;clone&amp;quot; the Contract as a draft, cancel the original and replace it with the clone. Our own solution was to write a custom plugin in C# that throws an exception if you ever try to invoice a contract - this works, but it&apos;s not pleasant having to work around such an arbitrary restriction.&lt;/p&gt;  &lt;p&gt;They say there&apos;s only two hard problems in software development - cache invalidation and naming things. Well, Dynamics CRM will remind you of that on a daily basis. Almost everything in CRM needs to have a name otherwise you can&apos;t save the record. For people, companies and products, this makes sense... but, really, when you&apos;re creating an order to allow somebody to renew their membership, does it need a name? Really? So we&apos;re having to build our own code to automatically create these names all over the place, and it&apos;s a waste of time. &lt;/p&gt;  &lt;p&gt;As for cache invalidation - there&apos;s a thing called the &amp;quot;portal toolkit&amp;quot;, aka the Developer Extensions for Microsoft Dynamics CRM 2015. You can read &lt;a href=&quot;https://msdn.microsoft.com/en-us/library/gg695814.aspx&quot;&gt;all about it here&lt;/a&gt;. Pay particular attention to the sentence that says:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;The Customer Portal and Partner Relationship Management (PRM) Portal solutions for Microsoft Dynamics CRM 2015 will be available from the Microsoft Dynamics Marketplace soon.&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;I don&apos;t know when &amp;quot;soon&amp;quot; is, but at the moment, if you start using the portal toolkit, you end up with a cache that you can&apos;t invalidate - the only supported invalidation mechanism involves deploying an (unreleased) solution to your CRM server, that calls an (undocumented) DLL that you&apos;re expected to deploy to your web server. Oh, and if you&apos;re running a server farm, you&apos;ll need to work out how to configure your load balancer to forward these cache invalidation calls to all your backend servers simultaneously. &lt;/p&gt;  &lt;h4&gt;So...&lt;/h4&gt;  &lt;p&gt;Like democracy, it&apos;s probably fair to say that Dynamics CRM is the worst option apart from all of the alternatives. It certainly isn&apos;t perfect, but it is pretty damn good, and I think the long-term advantages will significantly outweigh the short-term headaches we&apos;re having. And if they don&apos;t? Well, that&apos;s why we have loosely-coupled architecture, isn&apos;t it?&lt;/p&gt;  </description>
          <pubDate>2015-09-29T15:02:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2015/09/29/membership-and-dynamics-crm-how-it-all.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2015/09/29/membership-and-dynamics-crm-how-it-all.html</guid>
        </item>
      
    
      
        <item>
          <title>Dynamics CRM Online: Any SMTP Server You Like! (as long as it&apos;s Gmail or Yahoo)</title>
          <description>&lt;p&gt;&lt;strong&gt;UPDATE: I take it all back. &lt;/strong&gt;Following a really useful call from Dynamics CRM support, it turns out there are three different mail integration scenarios, only there&apos;s no reference to the third one in the CRM Online configuration screens.&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Server-side sync using Exchange&lt;/li&gt;    &lt;li&gt;Server-side sync using POP3/SMTP - this is the one that&apos;s restricted to Yahoo/Gmail, presumably because it IS doing something very clever.&lt;/li&gt;    &lt;li&gt;Dynamics CRM Email Relay - this is a completely separate tool that you need to download and install, and use it to configure your email routing. &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;I&apos;m going through the mail relay config as we speak - will let you know how it goes. Sorry, Microsoft. :)&lt;/p&gt;  &lt;hr /&gt;  &lt;p&gt;We are doing a lot of work at the moment with Microsoft Dynamics CRM Online. It&apos;s generally very nice - as a business tool, it&apos;s excellent; the UI is great, it&apos;s a good fit for our requirements, and despite a couple of headaches caused by the restrictions of the CRM Online hosting environment, we&apos;ve now got a pretty elegant two-way data sync system up and running using EasyNetQ and webhooks.&lt;/p&gt;  &lt;p&gt;Now, one of our key requirements for CRM is being able to send email to our customers. Yeah, I know - audacious, right? CRM Online is our first foray into the cloudy world of Microsoft Office 365, and all the rest of our infrastructure (Exchange, Active Directory, etc.) is still hosted on-premise. For testing and staging systems, we use &lt;a href=&quot;http://mailtrap.io/&quot;&gt;&lt;strong&gt;mailtrap.io&lt;/strong&gt;&lt;/a&gt; - a really slick service that will accept incoming mail and store it so you can test things are relaying properly without actually sending any real emails. For production, we use &lt;a href=&quot;http://mandrillapp.com/&quot;&gt;&lt;strong&gt;Mandrill&lt;/strong&gt;&lt;/a&gt;, which is a commercial mail relay service - high availability, reputation management, excellent delivery statistics. We send about a million emails a month through Mandrill, and it works beautifully. &lt;/p&gt;  &lt;p&gt;So... this morning I log into CRM Online, go into Settings =&amp;gt; Email Configuration =&amp;gt; New POP3/SMTP Server Profile. Looks pretty straightforward, right? I enter some details, click &amp;quot;save&amp;quot; and get this:&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh3.googleusercontent.com/-2_qoxTVxfXw/Vcn2Wwtg8yI/AAAAAAAADvk/fFhIrzTZNg8/s1600-h/image%25255B4%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; display: block; padding-right: 0px; border-top-width: 0px; margin-right: auto&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh3.googleusercontent.com/-Btn5rKsPFXI/Vcn2XXXENhI/AAAAAAAADvo/DnPNWgfX1uo/image_thumb%25255B2%25255D.png?imgmax=800&quot; width=&quot;511&quot; height=&quot;128&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Weird. I don&apos;t want to set up server-side synchronization - I just want to send email. So I start poking around, Googling, that kind of thing, and find &lt;a href=&quot;https://community.dynamics.com/crm/b/dynamicscrmtipoftheday/archive/2015/03/11/tip-342-demystifying-pop3-smtp-for-crm-online&quot;&gt;this article&lt;/a&gt;, which says:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;You may have read in &lt;a href=&quot;https://technet.microsoft.com/en-us/library/dn531050.aspx&quot;&gt;the documentation&lt;/a&gt; that GMail and Yahoo are listed as supported pop3/smtp providers for Microsoft Dynamics CRM Online and&lt;/p&gt;    &lt;p&gt;&lt;strong&gt;&amp;quot;Although other POP3/SMTP systems may work with Microsoft Dynamics CRM, those systems were not been tested by Microsoft and are not supported.&amp;quot;&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;Let’s be clear about “&lt;strong&gt;not supported&lt;/strong&gt;“. In this context it means precisely “you will not be able to go past server profile screen as we will reject any pop3/smtp provider that is not GMail or Yahoo.”&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;And that is &lt;em&gt;exactly what the email relay screen does&lt;strong&gt;. &lt;/strong&gt;&lt;/em&gt;If you enter any value other than &amp;quot;smtp.gmail.com&amp;quot; or &amp;quot;smtp.mail.yahoo.com&amp;quot; in the Outgoing Server Location field, you get the &amp;quot;unsupported email server&amp;quot; message. I&apos;ve even tried modifying the configuration using the SDK instead of the UI, but get the same response - &amp;quot;the email server is unsupported&amp;quot;.&lt;/p&gt;  &lt;p&gt;There&apos;s two possibilities here:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Microsoft have worked closely with Yahoo and GMail to provide first-class business email support, with all sorts of clever features and proprietary extensions that aren&apos;t supported by any other SMTP mail servers. &lt;strong&gt;(UPDATE - yes, it appears this is more-or-less what they&apos;ve done. See above.)&lt;/strong&gt;&lt;/li&gt;    &lt;li&gt;&lt;strike&gt;Somebody has arbitrarily decided to support GMail and Yahoo and exclude all other SMTP servers. Including, by the way, Hotmail (owned by Microsoft), Outlook.com (owned by Microsoft), and our on-premise SMTP relay (powered by Microsoft Exchange)&lt;/strike&gt; &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;If I were a betting man, I know which one I&apos;d be putting money on. I&apos;m really disappointed about this. CRM Online isn&apos;t a toy. It&apos;s a seriously powerful platform, with a hefty price tag attached... and just when everything is going really nicely, something like this comes along and our beautiful product vision is completely hamstrung by the fact that if we want to email our customers, we need to do it via GMail or Yahoo - and there&apos;s absolutely no rational justification for this.&lt;/p&gt;  &lt;p&gt;Anyone else encountered with this particular restriction? Anyone have any bright ideas as to how we can work around it?&lt;/p&gt;  </description>
          <pubDate>2015-08-11T13:19:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2015/08/11/dynamics-crm-online-any-smtp-server-you.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2015/08/11/dynamics-crm-online-any-smtp-server-you.html</guid>
        </item>
      
    
      
        <item>
          <title>As a Developer, I Want to Abolish User Stories, So That I Can Ship Great Products Faster.</title>
          <description>&lt;p&gt;Once upon a time, when you programmed by the seat of your pants and the handlebars of your moustache, lots of people wrote specifications. And there was this lovely idea that you could get a Business Analyst to write you a specification, which laid out in very precise detail exactly how The Software was going to function, and then you would give the specification to a development team and they would build it for you, and Everything Would Be Lovely. Except, of course, it wasn&apos;t. Only one team ever shipped a massively successful project using this sort of specification-driven waterfall approach, and trust me - &lt;a href=&quot;https://mitpress.mit.edu/books/digital-apollo&quot;&gt;they were smarter than you, and they had a much bigger budget&lt;/a&gt;. So some bright folks started wondering why software always went wrong, and suggested a whole load of things that would probably help, and came up with things like scrum, and unit testing, and continuous integration. &lt;/p&gt;  &lt;p&gt;One of the great ideas that came out this movement was the idea of user stories. See, specifications tended to be incredibly dry and formal, and often did a really bad job of communicating exactly &lt;em&gt;why&lt;/em&gt; you were doing something. Joel Spolsky wrote a great article years ago on writing &lt;a href=&quot;http://www.joelonsoftware.com/articles/fog0000000036.html&quot;&gt;painless functional specifications&lt;/a&gt;, but user stories takes this idea even further. And, like a lot of the good ideas that came out of agile, there&apos;s a descriptive element to it and a prescriptive element to it. The descriptive part says &amp;quot;write short, simple stories, not detailed specifications&amp;quot; - and the prescriptive part suggests some &apos;templates&apos; to help you get the hang of this. There&apos;s two formats that became popular for working with user stories - &lt;em&gt;given-when-then &lt;/em&gt;and &lt;em&gt;as-a-I-want-so-that. &lt;/em&gt;&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;https://www.mountaingoatsoftware.com/agile/user-stories&quot;&gt;As-a-I-want-so-that&lt;/a&gt; is pretty good for describing, at a very high level, what you are trying to do:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;strong&gt;As a &lt;/strong&gt;marketing coordinator, &lt;strong&gt;I want &lt;/strong&gt;to send email to everyone on our mailing list, &lt;strong&gt;so that &lt;/strong&gt;I can tell them about our big summer sale.&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;And then you&apos;ll add a couple of acceptance criteria to the story:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;strong&gt;Given &lt;/strong&gt;I am not subscribed to the mailing list, &lt;strong&gt;when&lt;/strong&gt; the marketing coordinator sends an email about the summer sale, &lt;strong&gt;then&lt;/strong&gt; I should not receive the email.&lt;/p&gt;    &lt;p&gt;&lt;strong&gt;Given&lt;/strong&gt; I have received a newsletter email, &lt;strong&gt;when&lt;/strong&gt; I click the unsubscribe link, &lt;strong&gt;then&lt;/strong&gt; I should be removed from the mailing list.&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;This sort of clarity makes it easy to build the feature, easy to test the feature, and easy to understand exactly what you&apos;re doing and why. See? Simple.&lt;/p&gt;  &lt;p&gt;Right. Imagine we have a spec from the Olden Days, and Volume 6, Section 8, Subsection 14, Paragraph 9 says:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;The handset will feature a Maplin RK82D component installed on the side of the unit. The RK82D will be positioned exactly 45mm from the top of the unit. When activated, the RK82D component will cause the internal speaker to emit a square wave signal at 16Hz, at a volume of not less than 90dBA as measured from a distance of 1 metre.&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Now, let&apos;s take an old-school project manager and turn them into an agile product owner. Probably by sending them on a three-day course with a certificate and everything.&amp;#160; When they get back, they&apos;ll dig out the old spec, and they&apos;ll laugh at how dry and opaque it all is. And they&apos;ll sit down, all excited, and start writing stories that look like this:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;strong&gt;As a&lt;/strong&gt; handset user&lt;strong&gt;, I want&lt;/strong&gt; a Maplin N27QQ component installed on the side of the unit, &lt;strong&gt;so that&lt;/strong&gt; when the component is activated the device will emit a square wave signal at 16Hz at a volume of not less than 90dBA measured from a distance of 1m&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;And then they&apos;ll add acceptance criteria:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;strong&gt;Given &lt;/strong&gt;I am a handset user, &lt;strong&gt;when&lt;/strong&gt; I activate the N27QQ component, &lt;strong&gt;then&lt;/strong&gt; the frequency of the square wave signal will be 16Hz&lt;/p&gt;    &lt;p&gt;&lt;strong&gt;Given &lt;/strong&gt;I am a handset user, &lt;strong&gt;when&lt;/strong&gt; I activate the N27QQ component &lt;strong&gt;and&lt;/strong&gt; measure the signal volume at a distance of 1m, &lt;strong&gt;then&lt;/strong&gt; the volume of the square wave will be not less than 90dBA&lt;/p&gt;    &lt;p&gt;&lt;strong&gt;Given &lt;/strong&gt;I am a handset user, &lt;strong&gt;when&lt;/strong&gt; I examine the handset, &lt;strong&gt;then&lt;/strong&gt; the distance of the N27QQ component shall be 45mm from the top of the unit&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;and everything is AWESOME because we&apos;re being AGILE! The story will sit there in the icebox for a couple of weeks, until it gets bumped up the list and included in a backlog refinement meeting. At which point this conversation will happen:&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Dev: &lt;/strong&gt;&amp;quot;Er... why are we doing this? I don&apos;t understand the justification for including this feature.&amp;quot;    &lt;br /&gt;&lt;strong&gt;PM: &lt;/strong&gt;&amp;quot;Sorry, what?&amp;quot;    &lt;br /&gt;&lt;strong&gt;Dev:&lt;/strong&gt; &amp;quot;Well... do you know what a Maplin N27QQ component is?&amp;quot;    &lt;br /&gt;&lt;strong&gt;PM: &lt;/strong&gt;&amp;quot;It&apos;s the component specified in the specification&amp;quot;    &lt;br /&gt;&lt;strong&gt;Dev: &lt;/strong&gt;&amp;quot;Yes... it&apos;s also a &lt;a href=&quot;http://www.maplin.co.uk/p/hylec-apl-large-palmfoot-operated-switch-red-version-n27qq&quot;&gt;big round red plastic button the size of a softball&lt;/a&gt;&amp;quot;    &lt;br /&gt;&lt;strong&gt;PM: &lt;/strong&gt;&amp;quot;Oh. Well, it&apos;s in the specification now.&amp;quot;    &lt;br /&gt;&lt;strong&gt;Dev: &lt;/strong&gt;&amp;quot;Right. Explain to me what happens when you press it&amp;quot;    &lt;br /&gt;&lt;strong&gt;PM: &lt;/strong&gt;&amp;quot;Oh, easy. The unit emits a square wave signal at 16Hz, at a volume of...&amp;quot;    &lt;br /&gt;&lt;strong&gt;Dev: &lt;/strong&gt;&amp;quot;Yeah, yeah. Do you know what that sounds like?&amp;quot;    &lt;br /&gt;&lt;strong&gt;PM: &lt;/strong&gt;&amp;quot;Er... it&apos;s probably some sort of ring tone&amp;quot;    &lt;br /&gt;&lt;strong&gt;Dev: &lt;/strong&gt;&amp;quot;No, it&apos;s a &lt;a href=&quot;http://onlinetonegenerator.com/?waveform=square&amp;amp;freq=16&quot;&gt;fart noise as loud as a motorcycle&lt;/a&gt;&amp;quot;    &lt;br /&gt;&lt;strong&gt;PM: &lt;/strong&gt;&amp;quot;Oh. Well, can we estimate the story, please?&amp;quot;&lt;/p&gt;  &lt;p&gt;At which point the developer will start flicking through LinkedIn on their phone and wondering how long it is until they can go to the pub. &lt;/p&gt;  &lt;p&gt;You know what the story should have said? It should have said something like:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;strong&gt;As &lt;/strong&gt;Bongo the Clown, &lt;strong&gt;I want &lt;/strong&gt;my new phone handset to feature a massive red fart button, &lt;strong&gt;so that &lt;/strong&gt;I can make children laugh at parties when I answer it.&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;First of all, unless you happen to be in the circus supply business, someone&apos;s going to say &amp;quot;hang on - why are we making phone handsets for clowns? Is that a thing now?&amp;quot;&lt;/p&gt;  &lt;p&gt;Second, anybody who reads that story can immediately picture what they&apos;re building, who wants it, and why. It leads to creative discussion and suggestions. Someone might have a brilliant idea about replacing the fart noise with a trombone, or making the button light up when you press it. One of your team might remember how Dad used to get mad every time Tony Blair came on the radio, but the volume knob on Dad&apos;s stereo fell off when he tried to turn the radio off, and how &lt;em&gt;hilarious&lt;/em&gt; it was watching him chase it across the kitchen while cursing and muttering under his breath, and maybe we should make the fart button actually fall off when you press it so Bongo has to chase it around the room? Clear stories let you cut through the waffle and get straight to the important bit - what is this? How do we build it? How might we make it better?&lt;/p&gt;  &lt;p&gt;Now, compare these two sentences:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;&lt;strong&gt;As a&lt;/strong&gt; handset user&lt;strong&gt;, I want&lt;/strong&gt; a Maplin N27QQ component installed on the side of the unit, &lt;strong&gt;so that&lt;/strong&gt; when the component is activated the device will emit a square wave signal at 16Hz at a volume of not less than 90dBA measured from a distance of 1m&lt;/li&gt;    &lt;li&gt;We&apos;ll put a giant red fart button on the side of the phone, so that Bongo the Clown can use it to make kids laugh when he&apos;s doing children&apos;s parties.&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;Which one makes more sense to you, as a reader? Which one is going to lead to better decisions, better estimation and less time wasted in meetings? &lt;/p&gt;  &lt;p&gt;&lt;strong&gt;As-a-I-want-so-that &lt;/strong&gt;is not some sort of witchcraft. It doesn&apos;t magically translate your dry, meaningless specification into readable English. Like writing unit tests, it can help to keep you honest when you&apos;re breaking bad habits, and it&apos;s one more tool to keep handy when you&apos;re doing this stuff for real, but &lt;em&gt;it is not why we&apos;re here. &lt;/em&gt;It&apos;s the story that matters, not the syntax. And if you can&apos;t tell me a short, simple story about what you want and why, then maybe you don&apos;t actually understand the thing you&apos;re trying to build, and no amount of syntactic convention is going to fix that.&lt;/p&gt;  </description>
          <pubDate>2015-08-04T22:59:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2015/08/04/as-developer-i-want-to-abolish-user.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2015/08/04/as-developer-i-want-to-abolish-user.html</guid>
        </item>
      
    
      
        <item>
          <title>The Mysterious Case of the Missing Milliseconds</title>
          <description>&lt;p&gt;Strings are the &lt;em&gt;lingua franca&lt;/em&gt; of many distributed systems, and Spotlight is no different. Earlier today, we hit a weird head-scratching bug in one of our services, and - surprise, surprise - turns out it&apos;s all do with strings. To work around limitations of an old line-of-business application, we have a database trigger (no, really) that captures changes made to a particular table, serializes them into an XML message, and pushes this into a SQL Service Broker queue; we then have a Windows service that pulls messages off the queue, parses the XML, breaks it into nicely manageable chunks and publishes them all to RabbitMQ using EasyNetQ. SImple. Except, once in a while, it blows up and starts complaining about &lt;strong&gt;FormatException&lt;/strong&gt;s.&lt;/p&gt;  &lt;p&gt;Now... within the database trigger, we&apos;re doing this:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;SELECT @OccurredAtUtc = CONVERT(VARCHAR(128), GETUTCDATE(), 126)&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;which returns &lt;strong&gt;2015-07-29T20:55:21.130 &lt;/strong&gt;as you&apos;d expect.&lt;/p&gt;  &lt;p&gt;There&apos;s then a line of code in the Windows service that says:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;var format = &amp;quot;yyyy-MM-ddTHH:mm:ss.fff&amp;quot;;       &lt;br /&gt;&lt;/font&gt;&lt;font face=&quot;Consolas&quot;&gt;DateTime.ParseExact(d, format, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal);&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Now, this is the code of somebody who knows that turning datetimes into strings and back again can get a bit tricky, and so has left absolutely nothing to chance - they&apos;ve supplied an exact date format, they&apos;ve specified a culture, they&apos;ve even gone so far as to specify the DateTimeStyles. There&apos;s unit tests and integration tests, and everything looks absolutely lovely. And then it blows up. Very occasionally, &lt;/p&gt;  &lt;p&gt;Except... SQL Server does something weird.&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;DECLARE @DateTime DATETIME        &lt;br /&gt;SELECT @DateTime = &apos;2015-07-29 21:59:15:123&apos;         &lt;br /&gt;SELECT CONVERT(VARCHAR(128), @DateTime, 126) -- returns 2015-07-29T21:59:15.123 (fine!)&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;SELECT @DateTime = &apos;2015-07-29 21:59:15:000&apos;        &lt;br /&gt;SELECT CONVERT(VARCHAR(128), @DateTime, 126) -- returns 2015-07-29T21:59:15&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;SELECT @DateTime = &apos;2015-07-29 21:59:15:999&apos;        &lt;br /&gt;SELECT CONVERT(VARCHAR(128), @DateTime, 126) -- returns 2015-07-29T21:59:16&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;SELECT @DateTime = &apos;2015-07-29 21:59:15:001&apos;        &lt;br /&gt;SELECT CONVERT(VARCHAR(128), @DateTime, 126) -- returns 2015-07-29T21:59:15&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;First, SQL Server doesn&apos;t have true millisecond precision - the milliseconds part will often get rounded by +- 0.001 seconds. Second - if the milliseconds part is zero, &lt;strong&gt;it&apos;ll be omitted from the string representation&lt;/strong&gt;. Which means our incredibly specific and detailed date parsing routine will choke, because suddenly it has a date that doesn&apos;t match the format we&apos;ve specified, and DateTime.ParseExact will throw a FormatException. Unit tests don&apos;t pick it up, because why would you mock such completely bizarre (and undocumented) behaviour, when you don&apos;t even know it exists? &lt;/p&gt;  &lt;p&gt;What this means is that, since any changes done between .999 and .001 milliseconds will blow up, roughly 0.3% of all our transactions will be failing with a FormatException rather than getting synced to the rest of our systems. Which means fishing them out of the error queue and sorting them out manually - ah, the joy of distributed systems. This formatting weirdness happens on every version of SQL back as far as 2003, but there&apos;s no reference to it in &lt;a href=&quot;https://msdn.microsoft.com/en-us/library/ms187928(v=sql.110).aspx&quot;&gt;the documentation&lt;/a&gt; until SQL Server 2012. It&apos;s been &lt;a href=&quot;https://connect.microsoft.com/SQLServer/feedback/details/714388&quot;&gt;raised as a bug&lt;/a&gt; and closed as &apos;by design&apos; because &amp;quot;the ISO 8601 spec leaves the conversion semantics for fractional seconds up to the implementation&amp;quot; - which I&apos;m pretty sure didn&apos;t mean &amp;quot;go ahead and be internally inconsistent!&amp;quot; but as with so many other issues like this, fixing the bug would change behaviour that&apos;s been in place for years and could break things. I&apos;ve no idea how - or why - anyone would build a system that genuinely relies on this bizarre idiosyncrasy, but I&apos;ll bet good money somebody out there has done it.&lt;/p&gt;  &lt;p&gt;The beautiful irony, of course, is that if we&apos;d used DateTime.Parse instead of ParseExact, we&apos;d never have had a problem. :)&lt;/p&gt;  </description>
          <pubDate>2015-07-29T21:15:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2015/07/29/the-mysterious-case-of-missing.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2015/07/29/the-mysterious-case-of-missing.html</guid>
        </item>
      
    
      
        <item>
          <title>REST Workshop at Progressive.NET 2015 next week</title>
          <description>&lt;p&gt;I&apos;ll be delivering a hands-on workshop at &lt;a href=&quot;https://skillsmatter.com/conferences/6859-progressive-dotnet-2015#program&quot;&gt;Progressive.NET 2015 at SkillsMatter&lt;/a&gt; here in London next week, where I&apos;ll be talking about advanced REST architectural patterns, and actually implementing some of those patterns in .NET using several of the frameworks available for building HTTP/REST APIs on ASP.NET&lt;/p&gt;  &lt;p&gt;I&apos;ve tried quite hard to avoid any esoteric requirements, so attendees should only need&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;A laptop running Visual Studio 2013, Powershell and IIS&lt;/li&gt;    &lt;li&gt;A reasonable working knowledge of C# and HTTP&lt;/li&gt;    &lt;li&gt;A test runner capable of running NUnit tests - personally I love &lt;a href=&quot;http://www.ncrunch.net/&quot;&gt;NCrunch&lt;/a&gt; deeply but &lt;a href=&quot;https://www.jetbrains.com/resharper/&quot;&gt;ReSharper&lt;/a&gt; or plain old &lt;a href=&quot;http://www.nunit.org/&quot;&gt;NUnit&lt;/a&gt; will do just fine./&lt;/li&gt;    &lt;li&gt;Some familiarity with Git and GitHub - if you know how to fork a repo and clone it to your workstation, you should be fine.&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;The repo we&apos;ll be working from is &lt;a title=&quot;https://github.com/dylanbeattie/Restival&quot; href=&quot;https://github.com/dylanbeattie/Restival&quot;&gt;https://github.com/dylanbeattie/Restival&lt;/a&gt; - there shouldn&apos;t be a great deal of setup required, but if you want to clone the repository, check it compiles, and set up your local IIS endpoints by running &lt;a href=&quot;https://github.com/dylanbeattie/Restival/blob/master/src/Deploy.ps1&quot;&gt;deploy.ps1&lt;/a&gt; ahead of time, it&apos;ll save a little time on the day.&lt;/p&gt;  &lt;p&gt;During the workshop, we&apos;ll be discussing advanced HTTP/REST API patterns - hypermedia, pagination, resource expansion, HTTP PATCH, OAuth2 - and showing off some tools that can help us design and monitor our HTTP APIs. Alongside the discussion, we&apos;ll be implementing some of the techniques covered using your preferred choice of WebAPI, ServiceStack, OpenRasta or NancyFX - or even all four, if you&apos;re feeling productive - and then discussing the relative pros and cons of these frameworks for each of the patterns we&apos;re implementing.&lt;/p&gt;  &lt;p&gt;See you there!&lt;/p&gt;  </description>
          <pubDate>2015-06-26T16:14:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2015/06/26/rest-workshop-at-progressivenet-2015.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2015/06/26/rest-workshop-at-progressivenet-2015.html</guid>
        </item>
      
    
      
        <item>
          <title>Slides and code from NDC Oslo 2015</title>
          <description>&lt;p&gt;I’m here at the Oslo Spektrum in Norway at &lt;a href=&quot;http://www.ndcoslo.com/&quot;&gt;NDC 2015&lt;/a&gt;, where I’ve been talking about the machine code of the web, SASS, TypeScript, CoffeeScript, bundle transformations, web optimisation in ASP.NET, ReST, hypermedia, resource expansion, API versioning, OAuth2, Apiary, NGrok, RunScope – and that’s just the stuff I actually managed to cover in my two talks. It’s been a really great few days, and huge thanks to the organisers for going to such great lengths to make sure everything has gone so smoothly.&lt;/p&gt;  &lt;p&gt;A couple of non-software highlights that I really liked:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;The catering has been excellent, and having food available throughout the day is a great way to avoid the lunchtime rush. (And the free coffee at the &lt;a href=&quot;https://www.twilio.com/&quot;&gt;Twilio&lt;/a&gt; stand is excellent!)&lt;/li&gt;    &lt;li&gt;The overflow area – where you can tune into any of the 9 talks currently in progress via a wireless headset, or just sit and channel-surf – is a great idea. (But remember it’s there if you’re doing live demos with audience participation – I’m pretty sure the “winner” of my NGrok demo was one of the people in the overflow area!)&lt;/li&gt;    &lt;li&gt;If you ever get the chance to see the Norwegian band LoveShack, do it. They played the conference after-party last night, and closed their set with a note-perfect 20-minute medley which went through (I think!) Jump, Celebrate, Girls! Girls! Girls!, Welcome to the Jungle, Paradise City, the theme from Baywatch, Livin’ on a Prayer, Radio Gaga and a half-dozen more before dropping back into Jump mid-guitar-solo without skipping a beat. They’re playing the John Dee bar in Oslo this evening, and I’m almost tempted to change my flight just to stick around and see them again…&lt;/li&gt; &lt;/ul&gt;  &lt;h2&gt;Slides, Links and Code Samples&lt;/h2&gt;  &lt;p&gt;The slides and code samples for the talks I’ve given are up on GitHub: the repo is at &lt;a title=&quot;https://github.com/dylanbeattie/ndc-oslo-2015&quot; href=&quot;https://github.com/dylanbeattie/ndc-oslo-2015&quot;&gt;https://github.com/dylanbeattie/ndc-oslo-2015&lt;/a&gt; or if you want to download the slide decks directly, links are:&lt;/p&gt;  &lt;h4&gt;Front-End Fun with Sass and Coffee&lt;/h4&gt;  &lt;ul&gt;   &lt;li&gt;&lt;a href=&quot;https://github.com/dylanbeattie/ndc-oslo-2015/raw/master/Slides/Front%20End%20Fun%20-%20NDC%20Oslo%202015.pptx&quot;&gt;Front-End Fun with Sass and Coffee&lt;/a&gt; – my slide deck from NDC Oslo 2015&lt;/li&gt;    &lt;li&gt;The &lt;a href=&quot;http://sass-lang.com/&quot;&gt;SASS Language site&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;&lt;a href=&quot;http://www.typescriptlang.org/&quot;&gt;TypeScript&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;&lt;a href=&quot;http://coffeescript.org/&quot;&gt;CoffeeScript&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;&lt;a href=&quot;https://bundletransformer.codeplex.com/&quot;&gt;Bundle Transformer&lt;/a&gt; – a “modular extension for System.Web.Optimization” that is also probably my favourite NuGet package of all time, for the sheer elegance of the way it’ll give you all the power of Ruby and Sass in a way that works so harmoniously with the ASP.NET runtime&lt;/li&gt; &lt;/ul&gt;  &lt;h4&gt;The Rest of ReST&lt;/h4&gt;  &lt;ul&gt;   &lt;li&gt;&lt;a href=&quot;https://github.com/dylanbeattie/ndc-oslo-2015/raw/master/Slides/The%20Rest%20of%20REST%20-%20NDC%20Oslo%202015.pptx&quot;&gt;The Rest of REST&lt;/a&gt; – my slide deck from NDC Oslo 2015&lt;/li&gt;    &lt;li&gt;“&lt;a href=&quot;https://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm&quot;&gt;Architectural Styles and the Design of Network-based Software Architectur&lt;/a&gt;es” – Roy Fielding’s doctorate thesis, in which he outlines the constraints that define ReSTful architecture.&lt;/li&gt;    &lt;li&gt;&lt;a href=&quot;http://www.troyhunt.com/2014/02/your-api-versioning-is-wrong-which-is.html&quot;&gt;“Your API Versioning is Wrong, which is why I decided to do it three different wrong ways”&lt;/a&gt; – Troy Hunt’s blog post about API versioning. &lt;/li&gt;    &lt;li&gt;&lt;a href=&quot;https://apiary.io/&quot;&gt;apiary.io&lt;/a&gt; – rapid prototyping for HTTP+JSON APIs. Which MIGHT be ReSTful if you read Roy Fielding’s thesis before you start playing with it.&lt;/li&gt;    &lt;li&gt;&lt;a href=&quot;https://ngrok.com/&quot;&gt;NGrok&lt;/a&gt; – “secure tunnels to localhost”&lt;/li&gt;    &lt;li&gt;&lt;a href=&quot;https://www.runscope.com&quot;&gt;RunScope&lt;/a&gt; – monitoring, logging and debugging for HTTP APIs and services.&lt;/li&gt;    &lt;li&gt;&lt;a href=&quot;http://stateless.co/hal_specification.html&quot;&gt;HAL: Hypertext Application Language&lt;/a&gt; by Mike Kelly&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;I also want to follow up on one specific question somebody asked after my ReST talk this morning, which can be&amp;#160; paraphrased as “are you comfortable recommending that people use HAL, seeing as it’s basically a dead specification?” An excellent question, and one that probably slightly more detailed answer than the one I gave on the spot. To put this in context, the HAL specification was submitted to the IETF as a &lt;a href=&quot;https://tools.ietf.org/html/draft-kelly-json-hal-06&quot;&gt;draft-kelly-json-hal-06&lt;/a&gt; in October 2013; that draft expired in 2014 and hasn’t been updated or ratified since, so I can see how you &lt;em&gt;could&lt;/em&gt; argue that HAL is “dead”.&lt;/p&gt;  &lt;p&gt;First – I’d disagree with that. Although the specification itself hasn’t changed in a while, the mailing list and community is still relatively active, and I’ve no doubt would still welcome engagement and contributions from anybody who wished to participate. Second – the spec still provides a perfectly valid approach. It’s a specification, not a tool or a framework, and in terms of delivering working software, if HAL helps you solve your problem then I say go go for it. Third – and I should have made this more obvious in this morning’s talk – &lt;strong&gt;HAL is just one of several approaches for delivering hypermedia over JSON.&lt;/strong&gt; I used HAL in my examples because I think it’s the most readable, but that doesn’t mean it’s the best choice for &lt;em&gt;your&lt;/em&gt; application. (Remember, one of my requirements for a hypermedia language in this context was “looks good on Powerpoint slides”.) If you’re interested, I would recommend also looking at &lt;a href=&quot;http://jsonapi.org/format/&quot;&gt;JSONAPI&lt;/a&gt;, &lt;a href=&quot;http://json-ld.org/&quot;&gt;JSON-LD&lt;/a&gt;, &lt;a href=&quot;http://amundsen.com/media-types/collection/format/&quot;&gt;Collection+JSON&lt;/a&gt; and &lt;a href=&quot;https://github.com/kevinswiber/siren&quot;&gt;SIREN&lt;/a&gt;. There is a &lt;a href=&quot;http://sookocheff.com/posts/2014-03-11-on-choosing-a-hypermedia-format/&quot;&gt;great post by Kevin Sookocheff&lt;/a&gt;, which succintly summarises the difference between four of them – it doesn’t cover JsonAPI - and concludes “there is no clear winner. It depends on the constraints in place on your API”&lt;/p&gt;  &lt;p&gt;Right. I’m going to watch Troy Hunt making hacking child’s play for an hour, and then head to the airport. Thank you, Oslo. It’s been a blast.&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh3.googleusercontent.com/-Nj21xw2GBDM/VYQDL20iQ-I/AAAAAAAADts/u7v_73YKGQ0/s1600-h/IMG_4233%25255B13%25255D.jpg&quot;&gt;&lt;img title=&quot;IMG_4233&quot; style=&quot;border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; border-left: 0px; display: block; padding-right: 0px; margin-right: auto&quot; border=&quot;0&quot; alt=&quot;IMG_4233&quot; src=&quot;http://lh3.googleusercontent.com/-SXPeeBelxOw/VYQDMQh1sVI/AAAAAAAADtw/6qfp38Mzl-w/IMG_4233_thumb%25255B7%25255D.jpg?imgmax=800&quot; width=&quot;640&quot; height=&quot;480&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  </description>
          <pubDate>2015-06-19T11:20:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2015/06/19/slides-and-code-from-ndc-oslo-2015.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2015/06/19/slides-and-code-from-ndc-oslo-2015.html</guid>
        </item>
      
    
      
        <item>
          <title>Restival Part 4: Deployment and Fun with Case Sensitivity</title>
          <description>&lt;p&gt;Before we dive into the next phase of API development, I wanted to make it a little easier to install and run the Restival app on your own machine, so I&apos;ve added a Powershell Deploy.ps1 script to the project which will:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Create a new local IIS website called Restival, bound to &lt;a href=&quot;http://restival.local/&quot;&gt;http://restival.local/&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;Create applications for each of our API implementations&lt;/li&gt;    &lt;li&gt;Configure the whole thing to run under the ASP.NET v4.0 application pool.&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;One interesting quirk I discovered whilst doing this is that OpenRasta appears to be case-sensitive when it comes to request URLs. I&apos;d initially created the applications like this:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;a href=&quot;http://restival.local/Api.Nancy/&quot;&gt;http://restival.local/Api.Nancy/&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;&lt;a href=&quot;http://restival.local/Api.ServiceStack/&quot;&gt;http://restival.local/Api.ServiceStack/&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;&lt;a href=&quot;http://restival.local/Api.WebApi/&quot;&gt;http://restival.local/Api.WebApi/&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;&lt;a href=&quot;http://restival.local/Api.OpenRasta/&quot;&gt;http://restival.local/Api.OpenRasta/&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;The test project uses lowercase URLs - so &lt;a href=&quot;http://restival.local/api.nancy/&quot;&gt;http://restival.local/api.nancy/&lt;/a&gt; - and for some strange reason, the OpenRasta implementation just doesn&apos;t work if the IIS application name differs in case from the URL in the unit test. I&apos;ll dig into this a little further but for now, I&apos;ve just modified the deploy script to do a .ToLower() on the application name and everything&apos;s working. Code for this instalment is in &lt;a title=&quot;https://github.com/dylanbeattie/Restival/tree/v0.0.4&quot; href=&quot;https://github.com/dylanbeattie/Restival/tree/v0.0.4&quot;&gt;https://github.com/dylanbeattie/Restival/tree/v0.0.4&lt;/a&gt;&lt;/p&gt;  </description>
          <pubDate>2015-06-12T16:12:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2015/06/12/restival-part-4-deployment-and-fun-with.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2015/06/12/restival-part-4-deployment-and-fun-with.html</guid>
        </item>
      
    
      
        <item>
          <title>Restival Part 2 Revisited: Attribute Routing in WebAPI</title>
          <description>&lt;p&gt;(Code for this instalment is &lt;a href=&quot;https://github.com/dylanbeattie/Restival/tree/v0.0.3&quot;&gt;version 0.0.3 on GitHub&lt;/a&gt; if you&apos;re following along.)&lt;/p&gt;  &lt;p&gt;Mike Thomas commented on my last post, asking &amp;quot;&lt;a href=&quot;http://dylanbeattie.blogspot.co.uk/2015/05/restival-part-3-hello-world.html?showComment=1431332366041#c1696253640652788495&quot;&gt;any reason why you are not looking at attribute routing in WebAPI&lt;/a&gt;&amp;quot;? To which my answer is &amp;quot;yes - I didn&apos;t know it existed&amp;quot;, which I&apos;d argue is a pretty good reason why I hadn&apos;t looked at it! But Mike&apos;s absolutely right to bring it up - if we&apos;re comparing frameworks, it makes a lot of sense to really explore the full capabilities of those frameworks. So I&apos;ve been reading up on attribute routing, and have to say it looks rather nice - and will, I suspect, help out with a lot of the more advanced stuff that&apos;s coming up in future instalments. &lt;/p&gt;  &lt;p&gt;According to &lt;a href=&quot;http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2&quot;&gt;the documentation on www.asp.net&lt;/a&gt;:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;Web API 2 supports a new type of routing, called &lt;em&gt;attribute routing&lt;/em&gt;. As the name implies, attribute routing uses attributes to define routes. Attribute routing gives you more control over the URIs in your web API. For example, you can easily create URIs that describe hierarchies of resources.&lt;/p&gt;    &lt;p&gt;The earlier style of routing, called convention-based routing, is still fully supported. In fact, you can combine both techniques in the same project.&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Sounds good, right? So what do we need to do to make Restival&apos;s WebAPI implementation run on attribute routing instead of convention-based routing?&lt;/p&gt;  &lt;p&gt;As it turns out, not much. Well, apart from a little light &lt;a href=&quot;http://en.wiktionary.org/wiki/yak_shaving&quot;&gt;yak-shaving&lt;/a&gt;. Attribute routing is in the Microsoft.AspNet.WebApi.WebHost package - so let&apos;s install it:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;PM&amp;gt; Install-Package Microsoft.AspNet.WebApi.WebHost       &lt;br /&gt;Attempting to resolve dependency &apos;Microsoft.AspNet.WebApi.Core (≥ 5.2.3 &amp;amp;&amp;amp; &amp;lt; 5.3.0)&apos;.        &lt;br /&gt;Attempting to resolve dependency &apos;Microsoft.AspNet.WebApi.Client (≥ 5.2.3)&apos;.        &lt;br /&gt;        &lt;br /&gt;...&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font style=&quot;background-color: #ffff00&quot; face=&quot;Consolas&quot;&gt;Install failed. Rolling back...       &lt;br /&gt;&lt;/font&gt;&lt;font style=&quot;background-color: #ff0000&quot; color=&quot;#ffffff&quot; face=&quot;Consolas&quot;&gt;Install-Package : Could not install package &apos;Microsoft.AspNet.WebApi.Client 5.2.3&apos;. You are trying to install this package        &lt;br /&gt;into a project that targets &apos;.NETFramework,Version=v4.0&apos;, but the package does not contain any assembly references or content         &lt;br /&gt;files that are compatible with that framework. For more information, contact the package author.At line:1 char:1        &lt;br /&gt;+ Install-Package Microsoft.AspNet.WebApi.WebHost        &lt;br /&gt;+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; + CategoryInfo&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; : NotSpecified: (:) [Install-Package], InvalidOperationException        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; + FullyQualifiedErrorId : NuGetCmdletUnhandledException,NuGet.PowerShell.Commands.InstallPackageCommand&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;OK, no problem - we&apos;re currently targeting .NET 4.0 and it looks like WebApi.WebHost wants .NET 4.5. Right-click, properties, Target Framework to .NET Framework 4.5.1, done. Shift-Ctrl-B... what&apos;s this?&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh3.googleusercontent.com/-_hngnvnFRQc/VVuxUwHmW9I/AAAAAAAADrc/ADu7PbaKrQM/s1600-h/image%25255B12%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;display: inline&quot; alt=&quot;image&quot; src=&quot;http://lh3.googleusercontent.com/-MEUoRkPhEJo/VVuxVk6PbII/AAAAAAAADrg/BjH8BgwMe_U/image_thumb%25255B6%25255D.png?imgmax=800&quot; width=&quot;934&quot; height=&quot;227&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Oh. OK. Let&apos;s enable NuGet Package Restore so it&apos;ll reinstall packages when we compile the solution... oh dear:&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh3.googleusercontent.com/-j3e4PuRK-Fs/VVuxWTeH1fI/AAAAAAAADro/ujooNJfH2AM/s1600-h/image%25255B13%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; border-left: 0px; display: block; padding-right: 0px; margin-right: auto&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh3.googleusercontent.com/-3cgCIEwiAXo/VVuxW_41j-I/AAAAAAAADr0/OnIGv7xnARc/image_thumb%25255B7%25255D.png?imgmax=800&quot; width=&quot;498&quot; height=&quot;293&quot; /&gt;&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;Oh, joy. Right. This just stopped being fun, because now the solution has entered a sort of weird limbo-state where it&apos;s not restoring packages, but the option to enable package restore has disappeared. Time for a tried and tested troubleshooting routine:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Close Visual Studio. Completely. SHUT IT DOWN. Yes. And the other instance you&apos;ve got open. In fact, reboot the machine. DO IT.&lt;/li&gt;    &lt;li&gt;Whilst it reboots, get something to drink. Coffee if you&apos;re on the clock &lt;font size=&quot;1&quot;&gt;(did I mention Spotlight has a bean-to-cup espresso machine? We&apos;re hiring, you know...) &lt;/font&gt;- or &lt;a href=&quot;http://metal-and-wine.com/en/&quot;&gt;something a little stronger&lt;/a&gt; if you&apos;re not. &lt;/li&gt;    &lt;li&gt;Put on &amp;quot;&lt;a href=&quot;https://open.spotify.com/track/0kHQcbHlfUhpDh00G2460e&quot;&gt;Turn Up the Radio&lt;/a&gt;&amp;quot; by Autograph.&lt;/li&gt;    &lt;li&gt;Take a deep breath.&lt;/li&gt;    &lt;li&gt;Re-open Visual Studio, re-open your solution, try building it again.&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;This time, it builds. It gives a warning about assembly version conflicts, and then settles down to 0 errors and 1 warning:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;Warning: Some NuGet packages were installed using a target framework different from the current target framework and may need to be reinstalled. Visit &lt;a href=&quot;http://docs.nuget.org/docs/workflows/reinstalling-packages&quot;&gt;http://docs.nuget.org/docs/workflows/reinstalling-packages&lt;/a&gt; for more information.&amp;#160; Packages affected: EntityFramework, Microsoft.Net.Http&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Well, we&apos;re not using Entity Framework so I can just remove it. Except I can&apos;t, because Microsoft.AspNet.Providers.Core uses EntityFramework, and Microsoft.AspNet.Providers.LocalDB uses Providers.Core... but since I&apos;m not using ANY of those, we can remove LocalDB, which removes Core, which removes EntityFramework, and we&apos;re down to a single warning about Microsoft.Net.Http, which we can fix with a NuGet package reinstall. Simple.&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;&lt;strong&gt;PM&amp;gt; Update-Package -reinstall Microsoft.Net.Http&lt;/strong&gt;        &lt;br /&gt;Removing &apos;Microsoft.Net.Http 2.0.20710.0&apos; from Restival.Api.WebApi.        &lt;br /&gt;Successfully removed &apos;Microsoft.Net.Http 2.0.20710.0&apos; from Restival.Api.WebApi.        &lt;br /&gt;Removing &apos;Microsoft.Net.Http 2.0.20710.0&apos; from Restival.Api.ServiceStack.        &lt;br /&gt;Successfully removed &apos;Microsoft.Net.Http 2.0.20710.0&apos; from Restival.Api.ServiceStack.        &lt;br /&gt;Removing &apos;Microsoft.Net.Http 2.0.20710.0&apos; from Restival.Api.OpenRasta.        &lt;br /&gt;Successfully removed &apos;Microsoft.Net.Http 2.0.20710.0&apos; from Restival.Api.OpenRasta.        &lt;br /&gt;Uninstalling &apos;Microsoft.Net.Http 2.0.20710.0&apos;.        &lt;br /&gt;Successfully uninstalled &apos;Microsoft.Net.Http 2.0.20710.0&apos;.        &lt;br /&gt;Installing &apos;Microsoft.Net.Http 2.0.20710.0&apos;.        &lt;br /&gt;You are downloading Microsoft.Net.Http from Microsoft, the license agreement to which is available at &lt;/font&gt;&lt;a href=&quot;http://www.microsoft.com/web/webpi/eula/MVC_4_eula_ENU.htm&quot;&gt;&lt;font face=&quot;Consolas&quot;&gt;http://www.microsoft.com/web/webpi/eula/MVC_4_eula_ENU.htm&lt;/font&gt;&lt;/a&gt;&lt;font face=&quot;Consolas&quot;&gt;. Check the package for additional dependencies, which may come with their own license agreement(s). Your use of the package and dependencies constitutes your acceptance of their license agreements. If you do not accept the license agreement(s), then delete the relevant components from your device.       &lt;br /&gt;Successfully installed &apos;Microsoft.Net.Http 2.0.20710.0&apos;.        &lt;br /&gt;Adding &apos;Microsoft.Net.Http 2.0.20710.0&apos; to Restival.Api.WebApi.        &lt;br /&gt;&lt;font style=&quot;background-color: #ffff00&quot;&gt;Install failed. Rolling back...         &lt;br /&gt;&lt;/font&gt;&lt;font style=&quot;background-color: #ff0000&quot; color=&quot;#ffffff&quot;&gt;Update-Package : Unable to uninstall &apos;Microsoft.Net.Http 2.0.20710.0&apos; because &apos;Microsoft.AspNet.WebApi.OData 4.0.30506&apos;          &lt;br /&gt;depends on it.At line:1 char:1          &lt;br /&gt;+ Update-Package -reinstall Microsoft.Net.Http          &lt;br /&gt;+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~          &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; + CategoryInfo&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; : NotSpecified: (:) [Update-Package], Exception          &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; + FullyQualifiedErrorId : NuGetCmdletUnhandledException,NuGet.PowerShell.Commands.UpdatePackageCommand          &lt;br /&gt;&lt;/font&gt;&amp;#160; &lt;br /&gt;&apos;Microsoft.Net.Http 2.0.20710.0&apos; already installed.        &lt;br /&gt;Adding &apos;Microsoft.Net.Http 2.0.20710.0&apos; to Restival.Api.ServiceStack.        &lt;br /&gt;Successfully added &apos;Microsoft.Net.Http 2.0.20710.0&apos; to Restival.Api.ServiceStack.        &lt;br /&gt;&apos;Microsoft.Net.Http 2.0.20710.0&apos; already installed.        &lt;br /&gt;Adding &apos;Microsoft.Net.Http 2.0.20710.0&apos; to Restival.Api.OpenRasta.        &lt;br /&gt;Successfully added &apos;Microsoft.Net.Http 2.0.20710.0&apos; to Restival.Api.OpenRasta.&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;PM&amp;gt;&lt;/font&gt; &lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Hmm. I don&apos;t even know what WebApi.OData is, and I&apos;m pretty sure I&apos;m not using it... Let&apos;s remove it. Which, of course, means removing a bunch of other things, including Microsoft.Net.Http... which requires another Visual Studio restart. And this time, Update-Package fails because Microsoft.Net.Http is actually &lt;em&gt;gone&lt;/em&gt;... so let&apos;s install it:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;strong&gt;&lt;font face=&quot;Consolas&quot;&gt;PM&amp;gt; Install-Package Microsoft.Net.Http&lt;/font&gt;&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Zing! Done. Clean build, zero errors, zero warnings, it works. Now we can actually implement attribute routing.&lt;/p&gt;  &lt;p&gt;First, we&apos;re going to remove our existing Hello route and enable attribute routing:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;public static class WebApiConfig {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; public static void Register(HttpConfiguration config) {        &lt;br /&gt;&lt;strong&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; config.MapHttpAttributeRoutes();&lt;/strong&gt;        &lt;br /&gt;&lt;/font&gt;&lt;font color=&quot;#9bbb59&quot; face=&quot;Consolas&quot;&gt;&lt;font color=&quot;#008000&quot;&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; //config.Routes.MapHttpRoute(         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; //&amp;#160;&amp;#160;&amp;#160; &amp;quot;Hello&amp;quot;, // route name          &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; //&amp;#160;&amp;#160;&amp;#160; &amp;quot;hello/{name}&amp;quot;, // route template          &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; //&amp;#160;&amp;#160;&amp;#160; new { Controller = &amp;quot;Hello&amp;quot;, Name = &amp;quot;World&amp;quot; } // defaults          &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; //&amp;#160;&amp;#160;&amp;#160; );&lt;/font&gt;        &lt;br /&gt;&amp;#160;&lt;font color=&quot;#000000&quot;&gt;&amp;#160;&amp;#160; }         &lt;br /&gt;}&lt;/font&gt;&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Next, we need to decorate our HelloController with the route attribute:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;public class HelloController : ApiController {       &lt;br /&gt;&lt;strong&gt;&amp;#160;&amp;#160;&amp;#160; [Route(&amp;quot;hello/{name}&amp;quot;)]         &lt;br /&gt;&lt;/strong&gt;&amp;#160;&amp;#160;&amp;#160; public Greeting Get(string name) {        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return (new Greeting(name));        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }        &lt;br /&gt;}&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;FInally, we need to change the way we register our configuration, because &lt;a href=&quot;http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2#enable&quot;&gt;the old WebAPI 1.x convention isn&apos;t compatible with attribute routing&lt;/a&gt;:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;protected void Application_Start() {       &lt;br /&gt;&lt;font color=&quot;#008000&quot;&gt;&amp;#160;&amp;#160;&amp;#160; // Old WebAPI 1.x syntax - not compatible with attribute routing:         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; // WebApiConfig.Register(GlobalConfiguration.Configuration);          &lt;br /&gt;          &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; // New WebAPI 2.x configuration via delegate instead of direct method call&lt;/font&gt;        &lt;br /&gt;&lt;strong&gt;&amp;#160;&amp;#160;&amp;#160; GlobalConfiguration.Configure(WebApiConfig.Register);         &lt;br /&gt;&lt;/strong&gt;}&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;That works - well, everything works except our default &amp;quot;Hello, World&amp;quot; scenario - so let&apos;s add a default value to the {name} parameter in our route attribute:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;public class HelloController : ApiController {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; [Route(&amp;quot;hello/{name&lt;font style=&quot;background-color: #ffff00&quot;&gt;&lt;strong&gt;=World&lt;/strong&gt;&lt;/font&gt;}&amp;quot;)]        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; public Greeting Get(string name) {        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return (new Greeting(name));        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }        &lt;br /&gt;}&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;And there you go. Attribute routing works, all tests are passing - and a yak so impeccably shaved you could use it to sell cologne. Not bad. &lt;/p&gt;  </description>
          <pubDate>2015-05-19T21:55:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2015/05/19/restival-part-2-revisited-attribute.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2015/05/19/restival-part-2-revisited-attribute.html</guid>
        </item>
      
    
      
        <item>
          <title>Restival Part 3: Hello, World!</title>
          <description>&lt;p&gt;So far, we&apos;ve got a &lt;a href=&quot;https://github.com/dylanbeattie/Restival/tree/v0.0.1&quot;&gt;/hello service up and running in four different frameworks&lt;/a&gt; (five if you include &lt;a href=&quot;https://github.com/kolektiv/Restival&quot;&gt;Andrew &apos;kolektiv&apos; Cherry&apos;s F#/Freya implementation&lt;/a&gt;, which looks really interesting). &lt;a href=&quot;http://dylanbeattie.blogspot.co.uk/2015/05/restival-part-2-all-aboard-routemaster.html&quot;&gt;Last time&lt;/a&gt;, we looked at how our frameworks handle routing; in this instalment, we&apos;re going to look at adding default values to our route parameters. Specifically, if you just call /hello, the service should return &amp;quot;Hello, World!&amp;quot;&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;strong&gt;GET /hello&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;should return&lt;/p&gt;    &lt;p&gt;&lt;strong&gt;200 OK&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;&lt;strong&gt;{ &amp;quot;Message&amp;quot; : &amp;quot;Hello, World!&amp;quot; }&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;OK, so how we do this? There&apos;s three places we can potentially do it - the routing, the handlers, or the underlying Greeting object itself - but not all our frameworks support all three options. &lt;/p&gt;  &lt;p&gt;Only WebAPI supports explicit defaults in the routing configuration, using this syntax:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;config.Routes.MapHttpRoute(       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &amp;quot;Hello&amp;quot;, // route name        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &amp;quot;hello/{name}&amp;quot;, // route template        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; new { Controller = &amp;quot;Hello&amp;quot;, &lt;font style=&quot;background-color: #ffff00&quot;&gt;Name = &amp;quot;World&amp;quot;&lt;/font&gt; } // defaults        &lt;br /&gt;);&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;ServiceStack doesn&apos;t support routing defaults &lt;em&gt;per se&lt;/em&gt;, but because the request is decoupled from the rest of our implementation, we can specify the default inside our request DTO - and thanks to the single responsibility principle, we know this isn&apos;t going to affect any other details of our implementation&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;[Route(&amp;quot;/hello&amp;quot;)]       &lt;br /&gt;[Route(&amp;quot;/hello/{name}&amp;quot;)]        &lt;br /&gt;public class Hello {        &lt;br /&gt;&lt;font style=&quot;background-color: #ffff00&quot;&gt;&amp;#160;&amp;#160;&amp;#160; private string name = &amp;quot;World&amp;quot;;&lt;/font&gt;        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; public string Name {        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; get { return name; }        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; set { name = value; }        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }        &lt;br /&gt;}&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;OpenRasta&apos;s routing maps our requests directly onto the Get method of our HelloHandler, which makes it really easy to use .NET&apos;s optional parameter syntax to supply a default value to the handler method itself:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;public class HelloHandler {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; public object Get(string name&lt;font style=&quot;background-color: #ffff00&quot;&gt; = &amp;quot;World&amp;quot;&lt;/font&gt;) {        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return (new Greeting(name));        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }        &lt;br /&gt;}&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Finally, NancyFX has such sparse routing that there&apos;s only really one line of code we &lt;em&gt;could &lt;/em&gt;modify - after all, our entire API&apos;s only two lines of code, and the other one&apos;s doing our /hello/{name} implementation.&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;public class HelloModule : NancyModule {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; public HelloModule() {        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Get[&amp;quot;/hello&amp;quot;] = _ =&amp;gt; new Greeting(&lt;font style=&quot;background-color: #ffff00&quot;&gt;&amp;quot;World&amp;quot;&lt;/font&gt;);        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Get[&amp;quot;/hello/{name}&amp;quot;] = parameters =&amp;gt; new Greeting(parameters.name);        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }        &lt;br /&gt;}&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Astute readers will be going &amp;quot;but hang on - you&apos;ve just defined the same default in four different places! That&apos;s not very SOLID! What if it changes?&amp;quot; - and yes, you&apos;re exactly right. Because this is a demo application, I&apos;m favouring readability over abstraction; if you had to define this kind of default behaviour in four different places in a real app, you&apos;d do it using constants, or implement it on the Greeting object itself so the code is reused between all four endpoints.&lt;/p&gt;  &lt;p&gt;Code for this post is on &lt;a href=&quot;https://github.com/dylanbeattie/Restival/releases/tag/v0.0.2&quot;&gt;GitHub as v0.0.2&lt;/a&gt;. &lt;/p&gt;  &lt;p&gt;Now we&apos;ve got a basic skeleton API up and running, it&apos;s probably time to share my to-do list - things we&apos;ll be implementing, in no particular order&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;List resources, and retrieving multiple objects&lt;/li&gt;    &lt;li&gt;Support for PUT and DELETE (easy), POST (interesting) and PATCH (&lt;em&gt;really&lt;/em&gt; interesting)&lt;/li&gt;    &lt;li&gt;Hypermedia links using &lt;a href=&quot;http://stateless.co/hal_specification.html&quot;&gt;HAL&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;Pagination and resource expansion, as described in &lt;a href=&quot;https://stormpath.com/blog/linking-and-resource-expansion-rest-api-tips/&quot;&gt;this excellent post on the Stormpath blog&lt;/a&gt; and &lt;a href=&quot;http://venkat.io/posts/expanding-your-rest-api/&quot;&gt;this blog post from Etsy&apos;s Venkata Mahalingam&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;API versioning - because, sooner or later, we&apos;re going to break something and need to maintain backward compatibility for older clients. Check out &lt;a href=&quot;http://www.troyhunt.com/2014/02/your-api-versioning-is-wrong-which-is.html&quot;&gt;Troy Hunt&apos;s great post on API versioning&lt;/a&gt; for a preview of how we&apos;ll be getting this wrong in three different ways.&lt;/li&gt;    &lt;li&gt;OAuth2 and bearer authentication - this one&apos;s going to take some work, because before you can build an OAuth2 API, you need an OAuth2 authentication server, but I&apos;ve wanted to build a really lightweight .NET OAuth2 server for a while so I&apos;m quite looking forward to it.&lt;/li&gt;    &lt;li&gt;API documentation using tools like &lt;a href=&quot;http://swagger.io/&quot;&gt;Swagger&lt;/a&gt;, which generates documentation based on metadata exposed by your API itself. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;See you next time.&lt;/p&gt;  </description>
          <pubDate>2015-05-07T21:50:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2015/05/07/restival-part-3-hello-world.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2015/05/07/restival-part-3-hello-world.html</guid>
        </item>
      
    
      
        <item>
          <title>Restival Part 2: All Aboard The Routemaster</title>
          <description>&lt;p&gt;Hello, and welcome to the second instalment of Restival: The Great .NET ReST Showdown &lt;a href=&quot;http://dylanbeattie.blogspot.co.uk/2015/04/one-api-four-frameworks-great-net-rest.html&quot;&gt;(part 1 is here if you missed it&lt;/a&gt;)&amp;#160; So far, our API defines a single very simple method – “hello”. Making a GET request to /hello will return &lt;font face=&quot;Consolas&quot;&gt;&lt;strong&gt;{ &amp;quot;Message&amp;quot; : &amp;quot;Hello, World!&amp;quot; }&lt;/strong&gt;&lt;/font&gt;, and making a GET request to /hello/chris will return &lt;font face=&quot;Consolas&quot;&gt;&lt;strong&gt;{ &amp;quot;Message&amp;quot; : &amp;quot;Hello, Chris!&amp;quot; }&lt;/strong&gt;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;The code we&apos;re discussing here is &lt;a href=&quot;https://github.com/dylanbeattie/Restival/releases/tag/v0.0.1&quot;&gt;on GitHub as release v0.0.1&lt;/a&gt;. This release supports &lt;strong&gt;/hello/{name}&lt;/strong&gt;, which demonstrates routing and parameter binding. I&apos;ve deliberately not implemented &amp;quot;Hello, World&amp;quot; at &lt;strong&gt;/hello&lt;/strong&gt; yet,&amp;#160;&amp;#160; because I want to do that by using the various frameworks&apos; conventions for specifying default parameter values and that logically can&apos;t happen until you&apos;ve defined your routes. Even at this incredibly early stage, there&apos;s some interesting design decisions going on. &lt;/p&gt;  &lt;h4&gt;Routing and Route Binding&lt;/h4&gt;  &lt;p&gt;Routing controls how your app will map incoming HTTP requests to methods - it&apos;s the set of rules that say &amp;quot;when you get a request that looks like X, run method Y on class Z&amp;quot;&lt;/p&gt;  &lt;p&gt;Nancy has a really lightweight routing syntax inspired by &lt;a href=&quot;http://www.sinatrarb.com/&quot;&gt;Sinatra&lt;/a&gt; - by inheriting from NancyModule, you get access to a RouteBuilder, a sort of dictionary that maps routes to anonymous methods, for each supported HTTP verb (&lt;code&gt;DELETE&lt;/code&gt;, &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;HEAD&lt;/code&gt;, &lt;code&gt;OPTIONS&lt;/code&gt;, &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt; and &lt;code&gt;PATCH&lt;/code&gt;) - to add a route, you supply the pattern to match and the method implementation:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;public class HelloModule : NancyModule {     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; public HelloModule() {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Get[&amp;quot;/hello/{name}&amp;quot;] = parameters =&amp;gt; new Greeting(parameters.name);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;}&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Note the Nancy convention whereby we use an underscore to indicate &amp;quot;we&apos;re not using this variable for anything&amp;quot; in handlers that don&apos;t actually use their parameter dictionary. It&apos;s also worth noting that Nancy&apos;s lightweight syntax won&apos;t stop you defining multiple handlers for the same route - but this &lt;a href=&quot;https://github.com/NancyFx/Nancy/wiki/Defining-routes#the-secret-for-selecting-the-right-route-to-invoke&quot;&gt;can lead to non-deterministic behaviour&lt;/a&gt;, so don&apos;t do it :)&lt;/p&gt;  &lt;p&gt;WebAPI uses an explicit routing table that&apos;s configured during application startup - in WebAPI, there&apos;s a call to &lt;font face=&quot;Consolas&quot;&gt;WebApiConfig.Register(GlobalConfiguration.Configuration) &lt;/font&gt;in Application_Start, and routes are mapped by specifying the name, the URL pattern and the defaults to use for that route. (If you&apos;re familiar with routing in ASP.NET MVC, WebAPI uses a very similar routing configuration, but with the &apos;action&apos; mapped to the HTTP verb instead of to a path segment.)&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;config.Routes.MapHttpRoute(     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &amp;quot;Hello&amp;quot;,&amp;#160;&amp;#160; // route name      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &amp;quot;hello/{name}&amp;quot;, // route template      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; new { Controller = &amp;quot;Hello&amp;quot; } // route defaults      &lt;br /&gt;);&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;OpenRasta and ServiceStack are both far more explicit about the relationship between resources, routes and handlers. OpenRasta uses a fluent configuration interface to declare your &lt;strong&gt;resources&lt;/strong&gt; (i.e. the bits of data we&apos;re interested in), your URIs (routes), handlers (the bits of code that actually do the work), and contracts (which handle things like serialization and content types)&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;public class Configuration : IConfigurationSource {     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; public void Configure() {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; using (OpenRastaConfiguration.Manual) {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ResourceSpace.Has.ResourcesOfType&amp;lt;Greeting&amp;gt;()      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; AtUri(&amp;quot;/hello/{name}&amp;quot;)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; .HandledBy&amp;lt;HelloHandler&amp;gt;()      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; .AsJsonDataContract();      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;}&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Finally, ServiceStack requires you to explicitly define &lt;strong&gt;requests &lt;/strong&gt;(DTOs representing the incoming request data)&lt;strong&gt;, services (&lt;/strong&gt;analogous to handlers in our other frameworks) and &lt;strong&gt;responses&lt;/strong&gt;. This is far more verbose than the other frameworks, but providing these abstraction layers between every aspect of your ReST API and your underlying codebase gives you more flexibility to evolve your API independently of the underlying business logic. You map your routes to your request DTOs using the &lt;strong&gt;Route&lt;/strong&gt; attribute, and inherit from &lt;strong&gt;ServiceStack.Service&lt;/strong&gt; when implementing your handlers. ServiceStack maps HTTP verbs onto service method names - &lt;font face=&quot;Consolas&quot;&gt;HelloService.Get(Hello dto), HelloService.Post(Hello dto)&lt;/font&gt;, etc. - but also supports a catch-all Any() method which will map incoming requests regardless of the request verb.&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;[Route(&amp;quot;/hello&amp;quot;)]     &lt;br /&gt;[Route(&amp;quot;/hello/{name}&amp;quot;)]      &lt;br /&gt;public class Hello {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; public string Name { get; set; }      &lt;br /&gt;}&lt;/p&gt;    &lt;p&gt;public class HelloResponse {     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; public string Message { get; set; }      &lt;br /&gt;}&lt;/p&gt;    &lt;p&gt;public class HelloService : Service {     &lt;br /&gt;&amp;#160; public HelloResponse Any(Hello dto) {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; var greeting = new Greeting(dto.Name);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; var response = new HelloResponse() { Message = greeting.Message };      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; return (response);      &lt;br /&gt;&amp;#160; }      &lt;br /&gt;}&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;So there you go. &lt;strong&gt;/hello/{name}&lt;/strong&gt; takes one line in NancyFX, a couple of lines in OpenRasta and WebAPI, and three entire classes in ServiceStack. Before you draw any conclusions, though, try pointing a browser at the root URL of each API implementation. &lt;/p&gt;  &lt;p&gt;Nancy gives you this rather splendid 404 page - complete with &lt;a href=&quot;http://theoatmeal.com/comics/state_web_summer#tumblr&quot;&gt;Tumbeast&lt;/a&gt;:&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh3.googleusercontent.com/-rhJ2mrWMcNg/VUfjgjuI2DI/AAAAAAAADqQ/jzQv3xhqZVU/s1600-h/image%25255B4%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; border-left: 0px; display: block; padding-right: 0px; margin-right: auto&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh3.googleusercontent.com/-cLrtXlIKCW8/VUfjhPn40JI/AAAAAAAADqU/D0f-nVsnp-k/image_thumb%25255B2%25255D.png?imgmax=800&quot; width=&quot;640&quot; height=&quot;423&quot; /&gt;&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;Running under IIS, WebAPI and OpenRasta both interpret GET / as a directory browse request, and give you the all-too-familiar IIS 7.5 HTTP error screen:&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh3.googleusercontent.com/-_cDqXY6-wxo/VUfjhs8ti3I/AAAAAAAADqg/BwohqHSRArY/s1600-h/image%25255B9%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; border-left: 0px; display: block; padding-right: 0px; margin-right: auto&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh3.googleusercontent.com/-2YtSPRQHUDM/VUfjii3B_xI/AAAAAAAADqk/_kBxcHDbJJI/image_thumb%25255B5%25255D.png?imgmax=800&quot; width=&quot;640&quot; height=&quot;423&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;But the pay-off for the extra boilerplate required by ServiceStack is this rather nice API documentation page, describing the services and encoding formats supported by the API and providing WSDL files for adding our API as a service endpoint. Now, we&apos;re not actually &lt;em&gt;using &lt;/em&gt;any of that yet... but as our API grows, it&apos;s going to be interesting to see how much extra work the other frameworks require to do things that ServiceStack provides for free. (Or for &lt;a href=&quot;https://servicestack.net/pricing&quot;&gt;$800 per developer&lt;/a&gt;, depending on what you&apos;re doing with it.)&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh3.googleusercontent.com/-8opeyU6wAEk/VUfjjIqL4SI/AAAAAAAADqw/E00Lv0nmBmM/s1600-h/image%25255B14%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; border-left: 0px; display: block; padding-right: 0px; margin-right: auto&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh3.googleusercontent.com/-3Xwe5WmcjPQ/VUfjj8RUldI/AAAAAAAADq0/zZs8jSn9SXA/image_thumb%25255B8%25255D.png?imgmax=800&quot; width=&quot;587&quot; height=&quot;480&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Now, it&apos;s important to remember that we&apos;re trying to reflect the &lt;strong&gt;conventions and idioms&lt;/strong&gt; of our chosen frameworks here. You could, without too much difficulty, implement the request/service/response pattern favoured by ServiceStack on any of the other frameworks, or to get your ServiceStack services to return raw entities instead of mapping them into Response objects - but if you&apos;re trying to make framework A behave like framework B, you might as well just switch to framework B and be done with it.&lt;/p&gt;  &lt;p&gt;In the next episode, we&apos;re going to make &lt;font face=&quot;Consolas&quot;&gt;&lt;strong&gt;GET /hello&lt;/strong&gt;&lt;/font&gt; return &amp;quot;Hello, World!&amp;quot;, and in the process look at how to define default values for our route parameters in each of our frameworks. Until then, happy hacking!&lt;/p&gt;  </description>
          <pubDate>2015-05-04T21:24:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2015/05/04/restival-part-2-all-aboard-routemaster.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2015/05/04/restival-part-2-all-aboard-routemaster.html</guid>
        </item>
      
    
      
        <item>
          <title>One API, Four Frameworks: The Great .NET ReST Showdown</title>
          <description>&lt;p&gt;There’s only two hard problems in software: cache invalidation, naming things, off-by-one errors, null-terminated lists, and &lt;a href=&quot;http://en.wikipedia.org/wiki/Overchoice&quot;&gt;choice overload&lt;/a&gt;. Back when I started building data-driven websites, classic ASP gave you Request, Response, Application, Session and Server, and you built the rest yourself - &lt;em&gt;and &lt;/em&gt;it was uphill both ways! These days, we’re spoiled for choice. Whatever you’re trying to achieve, you can bet good money that somebody’s been there before you. If you’re lucky, they’ve shared some of their code – and if you’re really lucky, they’ve built a framework or a library you can use. &lt;/p&gt;  &lt;p&gt;Since the heady days of classic ASP, I’ve built quite a few systems that have to expose data or services to other systems – from standalone HTTP endpoints that just return true or false, to full-featured ReST APIs. I’ve come across several frameworks that are designed to help developers create ReSTful APIs using Microsoft .NET, each of which doubtless has its own strengths and weaknesses – and now I find myself facing the aforementioned choice overload, because if I was starting a new API project right now, I honestly don’t know which framework I’d use. Whilst I’ve got hands-on experience with most of them, I’ve never had the opportunity to try implementing the &lt;em&gt;same API spec &lt;/em&gt;in multiple frameworks to compare and contrast their capabilities on a like-for-like basis.&lt;/p&gt;  &lt;p&gt;So here goes. Over the next few months, I’m going to be developing the same API in four different frameworks, side-by-side. The APIs have to use the same back-end code – same data access, same business logic – and have to pass the same integration test suite. I’m going to start out really simple = “hello, world!” simple – and gradually introduce features like resource expansion, pagination, OAuth2, content negotiation. The idea is that some of these features will actually break the interface, so I’ll also be looking at how to handle API versioning in each of my chosen frameworks. I’m also going to try and respect the idioms and conventions of each of the frameworks I’m working with – good frameworks tend to be opinionated, and if you don’t agree with their opinions you’re probably not going to find them particularly helpful.&amp;#160; &lt;/p&gt;  &lt;h3&gt;The Frameworks&lt;/h3&gt;  &lt;h4&gt;Microsoft ASP.NET WebAPI (&lt;a title=&quot;http://www.asp.net/web-api&quot; href=&quot;http://www.asp.net/web-api&quot;&gt;http://www.asp.net/web-api&lt;/a&gt;)&lt;/h4&gt;  &lt;p&gt;Microsoft’s out-of-the-box solution for building HTTP-driven APIs. Superficially similar to ASP.NET MVC but I suspect there’s much more to it than that. I’ve built a couple of small standalone APIs in WebAPI but not used it for anything too substantial.&lt;/p&gt;  &lt;h4&gt;ServiceStack (&lt;a title=&quot;https://servicestack.net/&quot; href=&quot;https://servicestack.net/&quot;&gt;https://servicestack.net/&lt;/a&gt;)&lt;/h4&gt;  &lt;p&gt;For a long while I was completely smitten with ServiceStack. Then it hit v4.0 and went from free-as-in-beer to expensive-as-in-$999-per-developer for any reasonable-sized project – there is a free usage tier, but it’s pretty restrictive. That said, it’s still a really powerful, flexible framework. Version 3 is still on NuGet, is still available under a BSD license, and there’s at least &lt;a href=&quot;http://www.nservicekit.com/&quot;&gt;one active project&lt;/a&gt; based on a fork of the ServiceStack v3 codebase.&amp;#160; I like ServiceStack’s conventions and idioms; working with it taught me a &lt;em&gt;lot &lt;/em&gt;about ReST; it has great support for things like SwaggerUI, and I suspect as I start implementing various API features ServiceStack is going to deliver additional value and capabilities which the other frameworks can’t match. Will it add enough value to justify $999 per developer? We’ll see :)&lt;/p&gt;  &lt;h4&gt;OpenRasta (&lt;a title=&quot;http://openrasta.org/&quot; href=&quot;http://openrasta.org/&quot;&gt;http://openrasta.org/&lt;/a&gt;)&lt;/h4&gt;  &lt;p&gt;I’ve played with OpenRasta on-and-off over the years, though I’ve never used it on anything substantial, but I know the folks over at Huddle are using it in a big way and having great results with it, so I’m really curious to see how it stacks up against the others. (I should probably disclose a slight bias here in that &lt;a href=&quot;https://twitter.com/serialseb&quot;&gt;Sebastien Lambla&lt;/a&gt;, the creator of OpenRasta, is a friend of mine; it was Seb who first got me thinking about ReST via London .NET User Group and prolonged conversations in the pub afterwards.)&lt;/p&gt;  &lt;h4&gt;NancyFX (&lt;a title=&quot;http://nancyfx.org/&quot; href=&quot;http://nancyfx.org/&quot;&gt;http://nancyfx.org/&lt;/a&gt;)&lt;/h4&gt;  &lt;p&gt;This one is completely new to me – until last week, I’d never even looked at it. But so far, it looks really nice – minimalist, elegant and expressive, and I’m looking forward to seeing what it can do.&lt;/p&gt;  &lt;h4&gt;Other Candidates&lt;/h4&gt;  &lt;p&gt;It would be interesting to get some F# into the mix – mainly because I’ve never used F# and I’m curious. I’ve heard interesting things about &lt;a href=&quot;http://websharper.com/&quot;&gt;WebSharper&lt;/a&gt; and &lt;a href=&quot;https://github.com/freya-fs/freya&quot;&gt;Freya&lt;/a&gt; – and, of course, if anyone wants to add an F# implementation and send me a pull request, go for it!&lt;/p&gt;  &lt;h3&gt;&lt;/h3&gt;  &lt;h3&gt;The API&lt;/h3&gt;  &lt;p&gt;I’m using Apiary.IO to design the API itself – you can check it out at &lt;a title=&quot;http://docs.restival.apiary.io/&quot; href=&quot;http://docs.restival.apiary.io/&quot;&gt;http://docs.restival.apiary.io/&lt;/a&gt;&lt;/p&gt;  &lt;h3&gt;The Code&lt;/h3&gt;  &lt;p&gt;The code is on GitHub - &lt;a title=&quot;https://github.com/dylanbeattie/Restival&quot; href=&quot;https://github.com/dylanbeattie/Restival&quot;&gt;https://github.com/dylanbeattie/Restival&lt;/a&gt;. &lt;/p&gt;  &lt;p&gt;If you want to run it, you’ll need to set up IIS applications for each of the four framework implementations – I use a hosts file hack to point restival.local at 127.0.0.1, and I’ve set up &lt;a href=&quot;http://restival.local/api.webapi&quot;&gt;http://restival.local/api.webapi&lt;/a&gt;, &lt;a href=&quot;http://restival.local/api.nancy&quot;&gt;http://restival.local/api.nancy&lt;/a&gt;, &lt;a href=&quot;http://restival.local/api.servicestack&quot;&gt;http://restival.local/api.servicestack&lt;/a&gt; and &lt;a href=&quot;http://restival.local/api.openrasta&quot;&gt;http://restival.local/api.openrasta&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;The test suite is NUnit, uses RestSharp to make real live HTTP requests, and all the actual test logic is in the abstract base class. There’s four concrete implementations, and the only difference is the URL of the API endpoint under test.&lt;/p&gt;  &lt;h3&gt;The Backlog&lt;/h3&gt;  &lt;p&gt;Features I want to implement include, but are probably not restricted to…&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Pagination. What’s a good way to handle huge result sets without causing timeouts and performance problems?&lt;/li&gt;    &lt;li&gt;Resource expansion. How do you request a customer, and all their orders, and the invoices linked to those orders, in a single API call?&lt;/li&gt;    &lt;li&gt;API versioning – using &lt;a href=&quot;http://www.troyhunt.com/2014/02/your-api-versioning-is-wrong-which-is.html&quot;&gt;all three different ways of doing it wrong&lt;/a&gt;&lt;/li&gt;    &lt;ul&gt;     &lt;li&gt;Version numbers in the URL (api.foo.com/v1/)&lt;/li&gt;      &lt;li&gt;Version numbers in a custom HTTP header (X-Restival-Version: 1.0)&lt;/li&gt;      &lt;li&gt;Content negotation based on MIME types (Accept: application/vnd.restival.v1.0+json)&lt;/li&gt;   &lt;/ul&gt;    &lt;li&gt;OAuth2 and bearer token authentication. (You’ll like this one because I’m not using DotNetOpenAuth)&lt;/li&gt;    &lt;li&gt;API documentation – probably by seeing how easily I can add &lt;a href=&quot;http://swagger.io/&quot;&gt;Swagger&lt;/a&gt; support to each implementation&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;In every case, this is stuff I’ve already implemented in at least one project over the last couple of years, so it’s going to be interesting seeing how readily those implementations translate across to the other frameworks I’m using. &lt;/p&gt;  &lt;p&gt;Sound like fun? You bet it does. Tune in and see how it unfolds. Or come to &lt;a href=&quot;http://www.ndcoslo.com/&quot;&gt;NDC Oslo in June&lt;/a&gt; or &lt;a href=&quot;https://skillsmatter.com/conferences/6859-progressive-dotnet-2015&quot;&gt;Progressive.NET in London in July&lt;/a&gt;, where you not only get to listen to me talk about ReST, you get several days of talks and workshops from some of the best speakers in the industry. &lt;/p&gt;  </description>
          <pubDate>2015-04-27T23:26:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2015/04/27/one-api-four-frameworks-great-net-rest.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2015/04/27/one-api-four-frameworks-great-net-rest.html</guid>
        </item>
      
    
      
        <item>
          <title>Spotlight, Dynamics CRM, and the age-old question of “build vs buy”</title>
          <description>&lt;p&gt;We’re in the early stages of creating a new membership system built on Microsoft Dynamics CRM 2015. This isn’t a decision we’ve taken lightly – the decision to buy vs. build is always complex where software is concerned, as &lt;a href=&quot;http://mikehadlow.blogspot.co.uk/2009/09/why-is-buy-not-always-better-than-build.html&quot;&gt;Mike Hadlow explains in this excellent blog post&lt;/a&gt;. In our case, though, there’s two good reasons why we’ve decided to integrate an off-the-shelf solution.&lt;/p&gt;  &lt;p&gt;First, we are willing to change our own business process to suit the software. We’ve been printing books since 1927, and our business model is tightly coupled to our publishing process. As part of this project, we’re going to decouple those two areas of activity. Publishing is a &lt;a href=&quot;http://www.businessdictionary.com/definition/differentiators.html&quot;&gt;differentiator&lt;/a&gt; for us, and always will be, but membership is not – whilst it’s important &lt;em&gt;that&lt;/em&gt; it works, it’s not hugely important &lt;em&gt;how &lt;/em&gt;it works. If CRM can offer us a “pit of success”, we’ll happily do what’s necessary to fall in it.&lt;/p&gt;  &lt;p&gt;Second – we really understand what it costs to write our own software. We’ve got a solid, mature agile process, which links in to our time-tracking system. One of the high-level metrics that we track is “cost per point” – how much, in pounds, does it cost us to get a single story point into production? It’s easy for businesses to think of off-the-shelf software as “expensive”, because it has a price tag, and bespoke software as ‘free’ because you’re already paying developers’ salaries, but that’s a pretty naive way to look at it. When you factor in the opportunity cost of all the things we &lt;em&gt;could &lt;/em&gt;have been doing instead of reinventing the CRM wheel, the off-the-shelf option starts to look a lot more attractive even when it carries a hefty up-front price tag.&lt;/p&gt;  &lt;p&gt;That’s two good reasons why we’re going with CRM2015. Now for two good reasons why CRM projects fail, and what we’re doing to mitigate them.&lt;/p&gt;  &lt;p&gt;First – scope creep. CRM vendors will happily sell it CRM as the solution to all your problems, and then they’ll start showing off marketing campaigns and case management and Outlook integration and web portals and everybody’s eyes light up like this is the most amazing thing they’ve ever seen… and next thing you know you’ve got a 300-page “requirements” document and everyone’s got so carried away by what’s possible that they’ve forgotten what they were trying to fix in the first place. I’ve seen this happen first-hand, and it doesn’t work, and the reason it doesn’t work is that the project isn’t being driven by &lt;strong&gt;prioritised requirements&lt;/strong&gt;, it’s being driven by &lt;strong&gt;wish-lists.&lt;/strong&gt; &lt;/p&gt;  &lt;p&gt;So… start with a problem. Any successful business probably has dozens of things that could be done better – so list them, analyze them, identify dependencies. Work out which one to solve first, and &lt;strong&gt;focus.&lt;/strong&gt; CRM isn’t fundamentally different to any other software project. Identify your milestones. Be absolutely brutal about the MVP – what is the simplest possible thing that’s better than what we’ve got right now? Build that. Ship that. Get people using it. In our case, CRM’s first outing is going to be as a replacement for GroupMail, our email-merge tool, &lt;em&gt;and that’s it&lt;strong&gt;.&lt;/strong&gt;&lt;/em&gt;&lt;strong&gt; &lt;/strong&gt;We’ll integrate just enough data that CRM can send personalised email to a specific group of customers, we’ll ship it, and we’ll use it – and then we’ll iterate based on feedback and lessons learned. We already have a pretty good idea what we’ll do after that, but we’re not going to worry about it until we’ve delivered that first MVP release.&lt;/p&gt;  &lt;p&gt;I think the second reason CRM projects fail is over-extension. Dynamics CRM is a really powerful, flexible platform, and with enough &lt;strike&gt;consultants&lt;/strike&gt; effort you can probably get it to do just about anything. But that doesn’t mean it’s the best solution. Sure, there’s going to be cases where it makes sense to customise CRM by adding a new field or some validation rules. Spotlight holds the same core “business” data&amp;#160; as any other company – what’s your name? Where do you live? what’s your email address? Is your account up-to-date? Off-the-shelf CRM is very, very good at managing this sort of information – and once you’ve got this core information in CRM, there’s dozens of off-the-shelf marketing tools available to help you use it more effectively.&lt;/p&gt;  &lt;p&gt;But Spotlight stores a lot more than that. We also store all the information that appears on your professional acting CV – height, weight, eye colour, hairstyle, skills, credits. We store details of almost all the productions being cast in the UK, we track tens of thousands of CV submissions every day, and millions of job notification emails each week. We manage terabytes of photography and video clips.&amp;#160; You probably &lt;strong&gt;could&lt;/strong&gt; get CRM to manage all this information. But hey, you can &lt;a href=&quot;http://www.wikihow.com/Open-a-Beer-Bottle-with-a-Dollar-Bill&quot;&gt;open a beer bottle with a dollar bill&lt;/a&gt; – doesn’t mean it’s a good idea, though. &lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh3.googleusercontent.com/-_4C_n3_il48/VTpvEYB7gZI/AAAAAAAADpQ/kNcttNI9Js0/s1600-h/image%25255B17%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; float: none; padding-top: 0px; padding-left: 0px; margin: 20px auto; display: block; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh3.googleusercontent.com/-rNsiVO_4J5Y/VTpvExTKalI/AAAAAAAADpY/1wJGBXXaX9I/image_thumb%25255B9%25255D.png?imgmax=800&quot; width=&quot;688&quot; height=&quot;403&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;The overlap – the green bit – is where CRM solves one of our problems. The blue bit is things CRM does that aren’t really relevant to us – not at the moment, anyway. The red bit is the stuff that we’re going to keep out of CRM. And that dark area on the border… that’s representation, which is a tremendously complicated tangle of operational data, business data and publishing data that we’re going to have to work out as part of our service roadmap. Fun.&lt;/p&gt;  &lt;p&gt;Now, different people – and systems! – have different expectations about what “CRM” and “membership” mean. &lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;To our &lt;strong&gt;customers&lt;/strong&gt;, good CRM means it’s easy to join Spotlight, it’s easy to manage your account, it’s easy to talk to us and get answers to your questions. You know how you hate phoning the electric company because every time you get through you talk to a different person who has no idea what’s going on, and your “my account” page on their website says one thing and your bill says something else and the person on the phone doesn’t agree with either of them? Yeah. Imagine the complete opposite of that. &lt;/li&gt;    &lt;li&gt;To our &lt;strong&gt;marketing team&lt;/strong&gt;, good CRM is about accurate data, effective marketing campaigns, happy customers – and, yes, revenue. It’s about helping us work out what we’re doing right and what we’re doing wrong, giving us the intelligence we need to make decisions about new products and initiatives. &lt;/li&gt;    &lt;li&gt;To our &lt;strong&gt;software team&lt;/strong&gt;,&amp;#160; good CRM means easy access to the data and processes you’ll need to build great products. Responsive systems, logical data structures, simple integration patterns and intuitive API endpoints. In other words, if you want to build an awesome online tool that’s only available to customers with a current, paid-up Spotlight Actors membership, it should be trivial to work out whether the current user can see it or not. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Same system, same data, three radically different use cases. So here’s how we’re proposing to make it work:&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh3.googleusercontent.com/-qcjONLTwM_Q/VTpvFkoD5ZI/AAAAAAAADpk/r-jIqB1H4rg/s1600-h/image%25255B22%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; margin: 20px 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh3.googleusercontent.com/--_X8lEPEGXM/VTpvGS_RoII/AAAAAAAADpo/eg9iILgs73M/image_thumb%25255B12%25255D.png?imgmax=800&quot; width=&quot;944&quot; height=&quot;450&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Astute readers will notice a slim black box marked “abstraction layer”. That’s how we’re going to fool the rest of our stack into thinking that Dynamics CRM 2015 is a ReSTful microservice, so tune in over the next couple of weeks to find out how it works, what sort of patterns and techniques we’re using, and how we’re going to test and monitor it.&lt;/p&gt;  &lt;p&gt;&lt;font size=&quot;2&quot;&gt;(Astute readers may also notice a resemblance between Spotlight’s customer base and the cast of Game of Thrones… well, that’s because Spotlight’s customers &lt;em&gt;are&lt;/em&gt; the cast of Game of Thrones. I told you working here was awesome.)&lt;/font&gt;&lt;/p&gt;  </description>
          <pubDate>2015-04-24T16:39:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2015/04/24/spotlight-dynamics-crm-and-age-old_24.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2015/04/24/spotlight-dynamics-crm-and-age-old_24.html</guid>
        </item>
      
    
      
        <item>
          <title>Spotlight, Dynamics CRM, and the age-old question of “build vs buy”</title>
          <description>&lt;p&gt;We’re in the early stages of creating a new membership “engine” built on Microsoft Dynamics CRM 2015. This isn’t a decision we’ve taken lightly – the decision to buy vs. build is always complex where software is concerned, as &lt;a href=&quot;http://mikehadlow.blogspot.co.uk/2009/09/why-is-buy-not-always-better-than-build.html&quot;&gt;Mike Hadlow explains in this excellent blog post&lt;/a&gt;. In our case, though, there’s two good reasons why we’ve decided to integrate an off-the-shelf solution.&lt;/p&gt;  &lt;p&gt;First, we are willing to change our own business process to suit the software. We’ve been printing books since 1927, and our business model is tightly coupled to our publishing process. As part of this project, we’re going to decouple those two areas of activity. Publishing is a &lt;a href=&quot;http://www.businessdictionary.com/definition/differentiators.html&quot;&gt;differentiator&lt;/a&gt; for us, and always will be, but membership is not – whilst it’s important &lt;em&gt;that&lt;/em&gt; it works, it’s not hugely important &lt;em&gt;how &lt;/em&gt;it works. If CRM can offer us a “pit of success”, we’ll happily do what’s necessary to fall in it.&lt;/p&gt;  &lt;p&gt;Second – we really understand what it costs to write our own software. We’ve got a solid, mature agile process, which links in to our time-tracking system. One of the high-level metrics that we track is “cost per point” – how much, in pounds, does it cost us to get a single story point into production? It’s easy for businesses to think of off-the-shelf software as “expensive”, because it has a price tag, and bespoke software as ‘free’ because you’re already paying developers’ salaries, but that’s a pretty naive way to look at it. When you factor in the opportunity cost of all the things we &lt;em&gt;could &lt;/em&gt;have been doing instead of reinventing the CRM wheel, the off-the-shelf option starts to look a lot more attractive even when it carries a hefty up-front price tag.&lt;/p&gt;  &lt;p&gt;That’s two good reasons why we’re going with CRM2015. Now for two good reasons why CRM projects fail, and what we’re doing to mitigate them.&lt;/p&gt;  &lt;p&gt;First – scope creep. CRM vendors will happily sell it CRM as the solution to all your problems, and then they’ll start showing off marketing campaigns and case management and Outlook integration and web portals and everybody’s eyes light up like this is the most amazing thing they’ve ever seen… and next thing you know you’ve got a 300-page “requirements” document and everyone’s got so carried away by what’s possible that they’ve forgotten what they were trying to fix in the first place. I’ve seen this happen first-hand, and it doesn’t work, and the reason it doesn’t work is that the project isn’t being driven by &lt;strong&gt;prioritised requirements&lt;/strong&gt;, it’s being driven by &lt;strong&gt;wish-lists.&lt;/strong&gt; &lt;/p&gt;  &lt;p&gt;So… start with a problem. Any successful business probably has dozens of things that could be done better – so list them, analyze them, identify dependencies. Work out which one to solve first, and &lt;strong&gt;focus.&lt;/strong&gt; CRM isn’t fundamentally different to any other software project. Identify your milestones. Be absolutely brutal about the MVP – what is the simplest possible thing that’s better than what we’ve got right now? Build that. Ship that. Get people using it. In our case, CRM’s first outing is going to be as a replacement for GroupMail, our email-merge tool, &lt;em&gt;and that’s it&lt;strong&gt;.&lt;/strong&gt;&lt;/em&gt;&lt;strong&gt; &lt;/strong&gt;We’ll integrate just enough data that CRM can send personalised email to a specific group of customers, we’ll ship it, and we’ll use it – and then we’ll iterate based on feedback and lessons learned. We already have a pretty good idea what we’ll do after that, but we’re not going to worry about it until we’ve delivered that first MVP release.&lt;/p&gt;  &lt;p&gt;I think the second reason CRM projects fail is over-extension. Dynamics CRM is a really powerful, flexible platform, and with enough &lt;strike&gt;consultants&lt;/strike&gt; effort you can probably get it to do just about anything. But that doesn’t mean it’s the best solution. Sure, there’s going to be cases where it makes sense to customise CRM by adding a new field or some validation rules. Spotlight holds the same core “business” data&amp;#160; as any other company – what’s your name? Where do you live? what’s your email address? Is your account up-to-date? Off-the-shelf CRM is very, very good at managing this sort of information – and once you’ve got this core information in CRM, there’s dozens of off-the-shelf marketing tools available to help you use it more effectively.&lt;/p&gt;  &lt;p&gt;But Spotlight stores a lot more than that. We also store all the information that appears on your professional acting CV – height, weight, eye colour, hairstyle, skills, credits. We store details of almost all the productions being cast in the UK, we track tens of thousands of CV submissions every day, and millions of job notification emails each week. We manage terabytes of photography and video clips.&amp;#160; You probably &lt;strong&gt;could&lt;/strong&gt; get CRM to manage all this information. But hey, you can &lt;a href=&quot;http://www.wikihow.com/Open-a-Beer-Bottle-with-a-Dollar-Bill&quot;&gt;open a beer bottle with a dollar bill&lt;/a&gt; – doesn’t mean it’s a good idea, though. &lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh3.googleusercontent.com/-_4C_n3_il48/VTpvEYB7gZI/AAAAAAAADpQ/kNcttNI9Js0/s1600-h/image%25255B17%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; float: none; padding-top: 0px; padding-left: 0px; margin: 20px auto; border-left: 0px; display: block; padding-right: 0px&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh3.googleusercontent.com/-rNsiVO_4J5Y/VTpvExTKalI/AAAAAAAADpY/1wJGBXXaX9I/image_thumb%25255B9%25255D.png?imgmax=800&quot; width=&quot;688&quot; height=&quot;403&quot; /&gt;&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;The overlap – the green bit – is where CRM solves one of our problems. The blue bit is things CRM does that aren’t really relevant to us – not at the moment, anyway. The red bit is the stuff that we’re going to keep out of CRM. And that dark area on the border… that’s representation, which is a tremendously complicated tangle of operational data, business data and publishing data that we’re going to have to work out as part of our service roadmap. Fun.&lt;/p&gt;  &lt;p&gt;Now, different people – and systems! – have different expectations about what “CRM” and “membership” mean. &lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;To our &lt;strong&gt;customers&lt;/strong&gt;, good CRM means it’s easy to join Spotlight, it’s easy to manage your account, it’s easy to talk to us and get answers to your questions. You know how you hate phoning the electric company because every time you get through you talk to a different person who has no idea what’s going on, and your “my account” page on their website says one thing and your bill says something else and the person on the phone doesn’t agree with either of them? Yeah. Imagine the complete opposite of that. &lt;/li&gt;    &lt;li&gt;To our &lt;strong&gt;marketing team&lt;/strong&gt;, good CRM is about accurate data, effective marketing campaigns, happy customers – and, yes, revenue. It’s about helping us work out what we’re doing right and what we’re doing wrong, giving us the intelligence we need to make decisions about new products and initiatives.&lt;/li&gt;    &lt;li&gt;To our &lt;strong&gt;software team&lt;/strong&gt;,&amp;#160; good CRM means easy access to the data and processes you’ll need to build great products. Responsive systems, logical data structures, simple integration patterns and intuitive API endpoints. In other words, if you want to build an awesome online tool that’s only available to customers with a current, paid-up Spotlight Actors membership, it should be trivial to work out whether the current user can see it or not. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Same system, same data, three radically different use cases. So here’s how we’re proposing to make it work:&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh3.googleusercontent.com/-qcjONLTwM_Q/VTpvFkoD5ZI/AAAAAAAADpk/r-jIqB1H4rg/s1600-h/image%25255B22%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 20px 0px; border-left: 0px; display: inline; padding-right: 0px&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh3.googleusercontent.com/--_X8lEPEGXM/VTpvGS_RoII/AAAAAAAADpo/eg9iILgs73M/image_thumb%25255B12%25255D.png?imgmax=800&quot; width=&quot;944&quot; height=&quot;450&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Astute readers will notice a slim black box marked “abstraction layer”. That’s how we’re going to fool the rest of our stack into thinking that Dynamics CRM 2015 is a ReSTful microservice, so tune in over the next couple of weeks to find out how it works, what sort of patterns and techniques we’re using, and how we’re going to test and monitor it.&lt;/p&gt;  </description>
          <pubDate>2015-04-24T16:28:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2015/04/24/spotlight-dynamics-crm-and-age-old.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2015/04/24/spotlight-dynamics-crm-and-age-old.html</guid>
        </item>
      
    
      
        <item>
          <title>NSBCon 2014 – All About NServiceBus</title>
          <description>I&apos;m excited to say I&apos;ll be speaking at &lt;a href=&quot;https://skillsmatter.com/conferences/6198-nsbcon#program&quot;&gt;NSBCon&lt;/a&gt; here in London in June. NServiceBus is the most popular open-source service bus for .NET platforms, and I&apos;ll be talking about how we use NServiceBus at &lt;a href=&quot;http://http%3B//www.spotlight.com/&quot; target=&quot;_blank&quot;&gt;Spotlight &lt;/a&gt;to deliver online photo and multimedia publishing systems aimed at professional actors, casting directors and production professionals.&lt;br /&gt;
I&apos;ll be talking about two distinct systems. The first is our proprietary system for managing online photo publication. Spotlight has been publishing photographs in one form or another since 1927, and so our current system is the latest in a long, long series of incremental developments - from acid-etched brass photograph plates, to lithographic bromide machines, to industrial film scanners and ImageMagick. Photography is an absolutely fundamental part of the creative casting process, and we reached a point a few years ago where our web servers were spending 60%-70% of their CPU time rendering photographs. Working within the limitations of our legacy systems, we developed a distributed thumbnailing and caching system that delivers the same results with a fraction of the processing overhead. I&apos;ll discuss how we used NServiceBus to work around those limitations, the architectural and operation challenges we faced building and running this system, and some of the lessons we learned when, a year after deploying it into production, we had to migrate this system from onsite hosting to a private cloud.&lt;br /&gt;
The second is an online audio/video upload and publishing system. Working with online video offers a unique set of challenges - dozens of incompatible file formats, massive file uploads, unpredictable analysis and encoding jobs. We built and deployed a system that satisfied these requirements whilst offering an intuitive, responsive user experience, and in this talk I&apos;ll cover the high-level architectural approach that enabled this. We&apos;ll look at how we used NServiceBus to decouple the &apos;heavy lifting&apos; components of the application from the customer-facing user interface components, and some of the lessons we learned deploying a distributed NServiceBus application in an Amazon EC2 hosting environment.&lt;br /&gt;
NSBCon is at &lt;a href=&quot;https://skillsmatter.com/&quot; target=&quot;_blank&quot;&gt;SkillsMatter &lt;/a&gt;here in London, on the 26th and 27th of June 2014. I&apos;ll be speaking alongside a great panel of .NET and distributed systems experts, including Udi Dahan, Ayende, Greg Young, Andreas Ohlund and many others. Follow &lt;a href=&quot;https://twitter.com/NSBCon&quot; target=&quot;_blank&quot;&gt;@NSBCon&lt;/a&gt; on Twitter for more updates, and &lt;a href=&quot;https://skillsmatter.com/conferences/6198-nsbcon&quot; target=&quot;_blank&quot;&gt;sign up at SkillsMatter&lt;/a&gt; if you&apos;re interested.</description>
          <pubDate>2014-05-22T12:03:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2014/05/22/nsbcon-2014-all-about-nservicebus.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2014/05/22/nsbcon-2014-all-about-nservicebus.html</guid>
        </item>
      
    
      
        <item>
          <title>How to read FormsAuthentication.Timeout in .NET 3.5</title>
          <description>&lt;p&gt;Forms authentication in .NET has a timeout property, controlled via the web.config section like so:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;&amp;lt;system.web&amp;gt;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &amp;lt;authentication&amp;gt;        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &amp;lt;forms name=&amp;quot;foo&amp;quot; loginUrl=&amp;quot;~/Account/Login&amp;quot; timeout=&amp;quot;30&amp;quot; /&amp;gt;        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &amp;lt;/authentication&amp;gt;        &lt;br /&gt;&lt;/font&gt;&lt;font face=&quot;Consolas&quot;&gt;&amp;lt;/system.web&amp;gt;&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;This setting has existed since .NET 1.0, I believe, but only in .NET 4 did we get a corresponding property on the FormsAuthentication object. To access the Timeout property in .NET 4+, you just call&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;FormsAuthentication.Timeout&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;To do the same in .NET 3.5, you need to do this:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;var defaultTimeout = TimeSpan.FromMinutes(30);       &lt;br /&gt;var xml = new XmlDocument();        &lt;br /&gt;var webConfigFilePath = Path.Combine(HttpRuntime.AppDomainAppPath, &amp;quot;web.config&amp;quot;);        &lt;br /&gt;xml.Load(webConfigFilePath);        &lt;br /&gt;var node = xml.SelectSingleNode(&amp;quot;/configuration/system.web/authentication/forms&amp;quot;);        &lt;br /&gt;if (node == null || node.Attributes == null) return (defaultTimeout);        &lt;br /&gt;var attribute = node.Attributes[&amp;quot;timeout&amp;quot;];        &lt;br /&gt;if (attribute == null) return (defaultTimeout);        &lt;br /&gt;int minutes;        &lt;br /&gt;if (Int32.TryParse(attribute.Value, out minutes)) return(TimeSpan.FromMinutes(minutes));        &lt;br /&gt;&lt;/font&gt;&lt;font face=&quot;Consolas&quot;&gt;return(defaultTimeout);&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;I&apos;ll file this under &amp;quot;Things that wrong with Forms Authentication, #217&amp;quot;&lt;/p&gt;  </description>
          <pubDate>2014-01-31T16:56:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2014/01/31/how-to-read-formsauthenticationtimeout.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2014/01/31/how-to-read-formsauthenticationtimeout.html</guid>
        </item>
      
    
      
        <item>
          <title>Friday Puzzle Time! (aka EU Roaming Data Charges)</title>
          <description>&lt;p&gt;I’m going to France. I want to be able to use my phone for data when I’m out there, so I’m investigating the cheapest way to do this. My phone is on Orange, who are now owned by EE, so I wander over to &lt;a href=&quot;http://explore.ee.co.uk/roaming/orange/france&quot;&gt;http://explore.ee.co.uk/roaming/orange/france&lt;/a&gt; and have a look. &lt;/p&gt;  &lt;p&gt;&lt;em&gt;(I should mention here that I’m generally really happy with Orange. The reception’s good. Their network doesn’t randomly drop calls or send me text messages 12 hours late, and Orange Wednesdays are awesome.)&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;Anyway. Here’s their data options:&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh5.ggpht.com/--L2Ja-5ge_I/UtmQ9fxOv4I/AAAAAAAAAV4/PyHri6FkO7k/s1600-h/image%25255B16%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; float: none; padding-top: 0px; padding-left: 0px; margin: 10px auto; display: block; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh3.ggpht.com/-tFl5txOsjMw/UtmQ-G0pRjI/AAAAAAAAAV8/NSIDKkxDlsQ/image_thumb%25255B12%25255D.png?imgmax=800&quot; width=&quot;409&quot; height=&quot;500&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Before we even get into the chaos of daily limits and terms and conditions, notice how the &lt;strong&gt;EU 100MB&lt;/strong&gt; daily bundle gives you 100MB for £3, whilst the &lt;strong&gt;EU Data 500MB&lt;/strong&gt; bundle gives you 500Mb for a hundred and fifty pounds? Same data but the bundle offers “&lt;strong&gt;93% saving on standard rates&lt;/strong&gt;” – which is such a huge discount as to be really quite unsettling. If someone tried to sell you a MacBook for £50, you’d be a bit suspicious, right? &lt;/p&gt;  &lt;p&gt;So I tried to work out what that catch might be – using &lt;a href=&quot;http://explore.ee.co.uk/roaming/orange/france&quot;&gt;EE’s website&lt;/a&gt;; Orange’s &lt;a href=&quot;https://explore-orange-live-orangedigital.s3.amazonaws.com/ORANGE_-_EU_Mobile_Internet_Daily_Bundles_July_2013.pdf&quot;&gt;EU Mobile Internet Daily Bundles T&amp;amp;Cs&lt;/a&gt;, and a phone call to Orange customer services. Here’s what I found.&lt;/p&gt;  &lt;table style=&quot;border-top: #000 1px solid; border-right: #000 1px solid; border-collapse: collapse; border-bottom: #000 1px solid; border-left: #000 1px solid&quot; cellspacing=&quot;0&quot; cellpadding=&quot;8&quot; width=&quot;100%&quot; border=&quot;1&quot;&gt;&lt;tbody&gt;     &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;155&quot;&gt;My Question&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;264&quot;&gt;Website&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;280&quot;&gt;T&amp;amp;Cs&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;155&quot;&gt;Customer Services (referring to an internal&amp;#160; T&amp;amp;C document dated Dec 15th 2013)&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;155&quot;&gt;What happens if I go over 100MB in a single day?&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;264&quot;&gt;&lt;strong&gt;“This bundle reoccurs up to 10 times until midnight of that same day (local time), each time the bundle reoccurs you will be charged an additional bundle fee and recieve additional bundle allowance. &lt;strong&gt;If you exceed the reoccuring bundle limit you’ll be charged 45.9p/MB.”&lt;/strong&gt;”             &lt;br /&gt;&lt;/strong&gt;&lt;em&gt;           &lt;br /&gt;(Does this mean I get ten bundles per day? Or one bundle per day, for up to ten days?)&lt;/em&gt;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;280&quot;&gt;&lt;strong&gt;“Any data used above the amount in your bundle, will be charged at our standard data roaming rate of 45.9p per MB. So, for example, if you have the £1 per day bundle and use more than your 20MB you will be charged an additional £1 for every additional 20MB you use up to 200MB (as the bundle reoccurs ten times). Any data usage in excess of 200MB will then be charged at our standard data roaming rate of 45.9p per MB.”           &lt;br /&gt;            &lt;br /&gt;&lt;/strong&gt;&lt;em&gt;(that sounds to me like you get ten bundles per day – otherwise it would just say “if you use more than 20Mb you’ll pay standard rates for the rest of the day” – no?)&lt;/em&gt;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;155&quot;&gt;Customer services advised me that bundles only work once per day – so when you reach 100MB, you’ll pay normal roaming charges until midnight, and you’ll get another 100MB bundle the next day.&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;155&quot;&gt;How many times can I use the bundle in a single day?&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;264&quot;&gt;&lt;strong&gt;See above.&lt;/strong&gt;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;280&quot;&gt;&lt;strong&gt;“A daily bundle will last until midnight (local time of the country you purchased the bundle in) on the day you made the&amp;#160; purchase, or until you have used up your data allowance, whichever comes first in time. Once&amp;#160; you’ve purchased a bundle, you will be opted in for the bundle to reoccur up to ten times”&lt;/strong&gt;           &lt;br /&gt;          &lt;br /&gt;&lt;em&gt;Doesn’t say if that’s ten times per day, or ten times per trip, or per billing period, or what. &lt;/em&gt;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;155&quot;&gt;Just once – see above. You can’t use two daily bundles in the same day.&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;155&quot;&gt;How many days does it last for?&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;264&quot;&gt;Really unclear whether it’s ten bundles per day – for as many days as you like – or whether it’s one bundle per day, for up to ten days. And no indication of whether that ten day limit is per trip or per billing period or what.&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;280&quot;&gt;See above.&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;155&quot;&gt;Customer services said there’s no limit – if I went to France for fifty days, I’d get fifty bundles. One per day, for the duration of my trip.&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;155&quot;&gt;How much does it cost once I exceed my bundle limit?&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;264&quot;&gt;&lt;strong&gt;“If you exceed the reoccuring bundle limit you’ll be charged 45.9p/MB.”&lt;/strong&gt;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;280&quot;&gt;“Any data used above the amount in your bundle, will be charged at our standard data roaming rate of          &lt;br /&gt;45.9p per MB.”&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;155&quot;&gt;£3.70 per MB. Though I was advised that information &lt;em&gt;might&lt;/em&gt; be out of date.&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;155&quot;&gt;What does the “ten bundle” limit actually mean?&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;264&quot;&gt;Unclear&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;280&quot;&gt;Unclear&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;155&quot;&gt;No idea. Neither of the advisors I spoke to could tell me what the “up to 10 times” limit actually meant.&lt;/td&gt;     &lt;/tr&gt;   &lt;/tbody&gt;&lt;/table&gt;  &lt;p&gt;So, let’s spin those various answers into some possible phone bills, bearing in mind nobody can actually give me a definitive answer. Imagine we’re going to France from Feb 1st to Feb 16th, and we’re going to use 25Mb/day most days on e-mail and Facebook and the like, and 250Mb on Fridays, ‘cos we’re watching snowboarding videos on YouTube.&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;With 1 EU100 bundle per day, unlimited days – that’d cost us &lt;strong&gt;£185.70&lt;/strong&gt; &lt;/li&gt;    &lt;li&gt;At 1 EU100 bundle per day, up to 10 days – would cost us &lt;strong&gt;£270.98&lt;/strong&gt; &lt;/li&gt;    &lt;li&gt;The EU500 plan? Given our 500MB quota runs out halfway through our trip, we’d pay &lt;strong&gt;£322.13&lt;/strong&gt; &lt;/li&gt;    &lt;li&gt;If the chap on the phone was right about £3.70/MB, we’d be looking at £1,110 in excess data charges for our two nights of YouTube, and a total bill of &lt;strong&gt;£1,158.&lt;/strong&gt; &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;And on top of all that, whoever wrote their (legally binding?) terms &amp;amp; conditions cannot spell ‘receive’ or ‘recurring’. &lt;/p&gt;  &lt;p&gt;No wonder I have a headache.&lt;/p&gt;  &lt;p&gt;UPDATE: So when I texted EU100 to 2088 to activate the bundle, it failed… I rang them (again), and apparently there’s already a bundle on there. It’s been on there since February 2013. Will it run out? “No, sir”. Is there any kind of limit on how many days I get? “No, sir.”&lt;/p&gt;  &lt;p&gt;So that’s an hour of research and phone calls to work out that nobody knows for sure what the bundle is, but it’s probably safe to assume it *doesn’t* run out because I’ve already got one and it’s been active for nearly a year.&lt;/p&gt;  &lt;p&gt;O_o&lt;/p&gt;  </description>
          <pubDate>2014-01-17T20:22:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2014/01/17/friday-puzzle-time-aka-eu-roaming-data.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2014/01/17/friday-puzzle-time-aka-eu-roaming-data.html</guid>
        </item>
      
    
      
        <item>
          <title>The Joy of GUIDs</title>
          <description>&lt;p&gt;Like many developers, my first experience of primary keys was the Autonumber field in MS Access. At the time, this looked like a pretty neat idea. When I outgrew Access and moved on to using SQL Server, I adopted the convention of an identity(1,1) field as a primary key - a pattern that&apos;s used extensively both within my own code and throughout the industry.&lt;/p&gt;  &lt;p&gt;Recently, though, I&apos;ve been working on big distributed systems where the identity field really doesn&apos;t work too well. The biggest drawback is that if you create a new entity in any of your data stores, you need to wait for the response from the database before you can find it again. For RESTful systems where your Create method might just return an HTTP 202 &amp;quot;Accepted for processing&amp;quot; and leave you to work out whether your request eventually succeeded or not, this approach just plain doesn&apos;t work. Enter the GUID - globally unique identifier. The idea is that every GUID ever created is completely unique - it uses various system/hardware IDs, the system clock and a degree of randomness to create unique 128-bit values. The lovely thing about working with GUIDs is that when you create your new customer, or order, or invoice, YOU can assign the ID that will identify the object for the rest of its life. &lt;/p&gt;  &lt;p&gt;Thing is, working with GUIDs is very, very different to working with ints. For starters, a lot of security practises around integers just don&apos;t apply.&lt;/p&gt;  &lt;p&gt;A great example is URL-hacking. If you&apos;ve ever seen an address in your browser&apos;s address bar that looks like:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;a href=&quot;http://www.myshop.com/basket.asp?basketid=2789&quot;&gt;http://www.myshop.com/basket.asp?basketid=2789&lt;/a&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Try editing it. Try changing the query string to be basketid=2788 - on many (poorly written!) systems, that&apos;ll show you what&apos;s in someone else&apos;s basket. It doesn&apos;t take a lot of work to write a little script that&apos;ll GET all the basket IDs from 0-999999 and voila! you&apos;ve got a folder full of other people&apos;s shopping baskets. Even if the developers do something terribly clever - like using identity(27897,17) instead of identity(1,1) - if you can do ten HTTP requests per second, you can still sweep the entire ID range from 0-1,000,000 in just over 24 hours. Sure, only 1 in 17 of your requests will succeed - but who cares? That&apos;s still 58,823 compromised shopping baskets.&lt;/p&gt;  &lt;p&gt;Now, imagine instead of sequental integers, you use a GUID: &lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;a href=&quot;http://www.myshop.com/basket.asp?basketid=fa8f6423-e7d6-ff40-7afc-ae874c755c00&quot;&gt;http://www.myshop.com/basket.asp?basketid=fa8f6423-e7d6-ff40-7afc-ae874c755c00&lt;/a&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Try hacking it. It won&apos;t work. There are 3.4×10&lt;sup&gt;38 &lt;/sup&gt;possible GUIDs, and roughly seven billion people on earth. If every single person on the planet used your website every day, at the end of a year you&apos;d have 10,000,000,000 * 365 shopping baskets in your system. Let&apos;s see how long it would take an attacker to steal those baskets by URL-hacking:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;Number of possible basket IDs &lt;em&gt;&lt;strong&gt;B&lt;/strong&gt;&lt;sub&gt;p&lt;/sub&gt;&lt;/em&gt; = 3.4x10^38&lt;/p&gt;    &lt;p&gt;Number of actual basket IDs &lt;em&gt;&lt;strong&gt;B&lt;/strong&gt;&lt;sub&gt;a&lt;/sub&gt;&lt;/em&gt; = 3.65x10^10&lt;/p&gt;    &lt;p&gt;Likelihood of a randomly chosen GUID finding a shopping basket = &lt;em&gt;B&lt;sub&gt;p&lt;/sub&gt;/B&lt;sub&gt;a&lt;/sub&gt;&lt;/em&gt; = 1.0x10&lt;sup&gt;-28&lt;/sup&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Let&apos;s assume the website we&apos;re attacking is running on some serious distributed hardware so it&apos;ll remain responsive throughout our attack, and you have a massive botnet so you can check baskets until the server starts to struggle under the load. Facebook manages 6 million page-views per minute, or 100,000 requests per second. At 100,000 requests per second, it will take you (on average) 2,953,788,842,323,276 years to find &lt;strong&gt;a single shopping basket&lt;/strong&gt; by URL-hacking.&amp;#160; Given those odds, a lot of security concerns around vulnerabilities like URL-hacking just disappear. &lt;/p&gt;  &lt;p&gt;I&apos;ve sat through numerous conversations that get onto &amp;quot;what&apos;ll happen if we get a GUID collision?&amp;quot;&lt;/p&gt;  &lt;p&gt;There&apos;s really only one sensible answer to this: You&apos;ll find the place in your code where you&apos;re accidentally re-using the same GUIDs, and fix the bug. GUIDs don&apos;t collide - you&apos;re doing it wrong. If you&apos;re using Guid.NewGuid() or NEWID(), you won&apos;t get collisions. It just won&apos;t happen. There isn&apos;t even any meaningful analogy with daily human experience that can convey just how unlikely a GUID collision is. &lt;strong&gt;Things with a probability of 1.0x10&lt;sup&gt;-28 &lt;/sup&gt;just don&apos;t happen. &lt;/strong&gt;&lt;/p&gt;  </description>
          <pubDate>2013-12-17T13:17:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2013/12/17/the-joy-of-guids.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2013/12/17/the-joy-of-guids.html</guid>
        </item>
      
    
      
        <item>
          <title>Automatic Semantic Versioning with GitHub and TeamCity</title>
          <description>&lt;p style=&quot;line-height: normal; margin: 0cm 0cm 0pt; background: white; text-autospace: ; mso-layout-grid-align: none&quot; class=&quot;MsoNormal&quot; align=&quot;left&quot;&gt;&lt;/p&gt;  &lt;p style=&quot;line-height: 13pt; margin: 0cm 0cm 10pt&quot; class=&quot;MsoNormal&quot; align=&quot;left&quot;&gt;&lt;font face=&quot;Calibri&quot;&gt;&lt;font style=&quot;font-size: 11pt&quot; color=&quot;#000000&quot;&gt;&lt;/font&gt;&lt;/font&gt;&lt;/p&gt; You’ve quite possibly come across the idea of &lt;a href=&quot;http://semver.org/spec/v2.0.0.html&quot;&gt;semantic versioning&lt;/a&gt;. It&apos;s a set of rules for versioning releases of software, designed to minimise disruption to people who are relying on your APIs not to change. In a nutshell, every release of your software has a three-part version number.    &lt;blockquote&gt;   &lt;p&gt;&lt;strong&gt;Given a version number MAJOR.MINOR.PATCH, increment the:&lt;/strong&gt;&lt;/p&gt;    &lt;ol&gt;     &lt;li&gt;&lt;strong&gt;MAJOR version when you make incompatible API changes, &lt;/strong&gt;&lt;/li&gt;      &lt;li&gt;&lt;strong&gt;MINOR version when you add functionality in a backwards-compatible manner, and &lt;/strong&gt;&lt;/li&gt;      &lt;li&gt;&lt;strong&gt;PATCH version when you make backwards-compatible bug fixes.&lt;/strong&gt; &lt;/li&gt;   &lt;/ol&gt;    &lt;p&gt;&lt;strong&gt;Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format.&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Pretty simple, huh? In other words, 2.3.4 should offer the same feature set as 2.3.1 but with extra bug fixes. 2.4.0 is backwards-compatible with 2.3.2, and with 2.2.*, and with 2.1.*, and when you go for the ‘big rewrite’ and break everything, that’s version 3.0.0 which isn’t compatible with any previous versions.&lt;/p&gt;  &lt;p&gt;Our main development pipeline is now based on TeamCity 8 and GitHub – both of which are absolutely wonderful once you get the hang of using them properly – and I wanted to automate the versioning of our builds according to semantic versioning principles. Here’s the workflow I settled on:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Every build has a MAJOR, MINOR, PATCH, and BUILD version, and an optional SUFFIX &lt;/li&gt;    &lt;li&gt;Each part of the version number is managed as a TeamCity build parameter. &lt;/li&gt;    &lt;li&gt;MAJOR and MINOR are managed manually. TeamCity can’t tell whether your commit is a feature or a bugfix, so for now we’ve just settled for manually changing the relevant parameters when you release a major or minor release. &lt;/li&gt;    &lt;li&gt;All development is done on feature branches and submitted via pull requests. &lt;/li&gt;    &lt;li&gt;Any pull request is assumed to be a PATCH release unless a developer changes the major or minor numbers when they accept it. &lt;/li&gt;    &lt;li&gt;The SUFFIX is used to denote a pre-release package, and should indicate which branch’s features are included in that prerelease      &lt;ul&gt;       &lt;li&gt;e.g. Dylan.Web-1.2.3.0-branch7 should be the code you’d get if you accepted branch7 into master 1.2.3 &lt;/li&gt;     &lt;/ul&gt;   &lt;/li&gt;    &lt;li&gt;Anything built off &lt;strong&gt;master&lt;/strong&gt; should be releasable, and tagged as such &lt;/li&gt;    &lt;li&gt;Finally, whenever a production release is built, we’ll tag the GitHub repository with the version of that release – for tagging, we ignore the build number, so the tag will be something like &lt;strong&gt;v0.0.0&lt;/strong&gt; &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;This lot actually distils to quite a simple guideline: &lt;strong&gt;The patch number is the number of pull requests accepted since the last tagged minor release. &lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;Now &lt;em&gt;that &lt;/em&gt;sounds like something you could automate… so with Powershell on one screen and the Github API documentation on the other, I hacked together the following script, which is now happily running as a build step on our TeamCity server.&lt;/p&gt;  &lt;blockquote&gt;   &lt;p align=&quot;left&quot;&gt;&lt;font face=&quot;Consolas&quot;&gt;$MajorVersion = %MajorVersion% # The major version of your current build        &lt;br /&gt;$MinorVersion = %MinorVersion% # The minor version of your current build         &lt;br /&gt;$RepoUrl = &amp;quot;%vcsroot.url%&amp;quot; # The HTTPS path to your repo – &lt;a href=&quot;https://github.com/MyCompany/MyRepository.git&quot;&gt;https://github.com/MyCompany/MyRepository.git&lt;/a&gt;         &lt;br /&gt;$Token = &amp;quot;%GitHubAccessToken%&amp;quot; # Your GitHub access token, from &lt;a href=&quot;https://github.com/settings/applications&quot;&gt;https://github.com/settings/applications&lt;/a&gt;&lt;/font&gt;&lt;/p&gt;    &lt;p align=&quot;left&quot;&gt;&lt;font face=&quot;Consolas&quot;&gt;try {        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; # Parse the supplied Git repo URL to determine the repo name and owner         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; $RepoUrl -match &amp;quot;&lt;/font&gt;&lt;a href=&quot;https://github.com/([^/]+)/(.+)\.git&quot;&gt;&lt;font face=&quot;Consolas&quot;&gt;https://github.com/([^/]+)/(.+)\.git&lt;/font&gt;&lt;/a&gt;&lt;font face=&quot;Consolas&quot;&gt;$&amp;quot;        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; $repoOwner = $matches[1]         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; $repoName = $matches[2]         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Write-Host &amp;quot;Reading repo $repoName owned by $repoOwner&amp;quot;         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; # Find the tag resource matching the baseline of the current major/minor version         &lt;br /&gt;&lt;/font&gt;&lt;font face=&quot;Consolas&quot;&gt;&amp;#160;&amp;#160;&amp;#160; # Remember, we’re not looking for X.Y.Z, we’re looking for X.Y.0 since patch numbers        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; # should increment until the next minor release.         &lt;br /&gt;&lt;/font&gt;&lt;font face=&quot;Consolas&quot;&gt;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; $uri = &amp;quot;&lt;/font&gt;&lt;a href=&quot;https://api.github.com/repos/&quot;&gt;&lt;font face=&quot;Consolas&quot;&gt;https://api.github.com/repos/&lt;/font&gt;&lt;/a&gt;&lt;font face=&quot;Consolas&quot;&gt;$repoOwner/$repoName/git/refs/tags/v$MajorVersion.$MinorVersion.0?access_token=$Token&amp;quot;        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Write-Output &amp;quot;Looking for tag v$MajorVersion.$MinorVersion.0 at $uri&amp;quot;         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; $tag = Invoke-RestMethod -Uri &amp;quot;$uri&amp;quot;&lt;/font&gt;&lt;/p&gt;    &lt;p align=&quot;left&quot;&gt;&lt;font face=&quot;Consolas&quot;&gt;&amp;#160;&amp;#160;&amp;#160; # $tag.object.url will now give us a more detailed tag resource, including the commit that was tagged        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; $uri = $tag.object.url         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Write-Output &amp;quot;Getting tag info from $uri`?access_token=$Token&amp;quot;         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; $tag = Invoke-RestMethod -Uri &amp;quot;$uri`?access_token=$Token&amp;quot;         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; # $tag.object.url is now a link to the tagged commit         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; $uri = $tag.object.url         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; $tag = Invoke-RestMethod -Uri &amp;quot;$uri`?access_token=$Token&amp;quot;&lt;/font&gt;&lt;/p&gt;    &lt;p align=&quot;left&quot;&gt;&lt;font face=&quot;Consolas&quot;&gt;&amp;#160;&amp;#160;&amp;#160; # now we can dig into the commit itself and find out WHEN the baseline release was tagged...        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; $since = $tag.committer.date&lt;/font&gt;&lt;/p&gt;    &lt;p align=&quot;left&quot;&gt;&lt;font face=&quot;Consolas&quot;&gt;&amp;#160;&amp;#160;&amp;#160; # Now we can retrieve all the commits in this repo SINCE that date        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; $commitsUri = &amp;quot;&lt;/font&gt;&lt;a href=&quot;https://api.github.com/repos/&quot;&gt;&lt;font face=&quot;Consolas&quot;&gt;https://api.github.com/repos/&lt;/font&gt;&lt;/a&gt;&lt;font face=&quot;Consolas&quot;&gt;$repoOwner/$repoName/commits?since=$since&amp;amp;access_token=$Token&amp;quot;        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Write-Host &amp;quot;Retrieving commit log from $commitsUri&amp;quot;         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; $commits = Invoke-RestMethod -Uri &amp;quot;$commitsUri&amp;quot;         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; $merges = @($commits | Where-Object { $_.commit.message -match &amp;quot;^Merge pull request&amp;quot; })         &lt;br /&gt;        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; # Reversing the merges just means they show up in TeamCity’s build log in chronological order.         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; # Which is nice.         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; [Array]::Reverse($merges)         &lt;br /&gt;        &lt;br /&gt;&lt;/font&gt;&lt;font face=&quot;Consolas&quot;&gt;&amp;#160;&amp;#160;&amp;#160; $mergeCount = $merges.Count        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Write-Host &amp;quot;Found $mergeCount merges since last release tag&amp;quot;         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; for($i = 0; $i -lt $merges.Count; $i++) {         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; $merge_number = $i+1         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; $merge_message = $merges[$i].commit.message.split(&amp;quot;`r`n&amp;quot;,[StringSplitOptions]&amp;quot;RemoveEmptyEntries&amp;quot;) -join &amp;quot;`r`n&amp;#160;&amp;#160;&amp;#160; &amp;quot;         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; $merge_sha = $merges[$i].sha         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; $merge_web_url = $RepoUrl.Replace(&amp;quot;.git&amp;quot;, &amp;quot;/commit/$merge_sha&amp;quot;)         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &amp;quot;`r`n&amp;#160; Merge #$merge_number`: $merge_web_url&amp;quot;         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &amp;quot;&amp;#160;&amp;#160;&amp;#160; $merge_message&amp;quot;         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Write-Host &amp;quot;`r`n&amp;quot;         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Write-Output &amp;quot;##teamcity[setParameter name=&apos;PatchVersion&apos; value=&apos;$mergeCount&apos;]&amp;quot;         &lt;br /&gt;} catch [System.Exception] {         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Write-Host &amp;quot;Exception trying to determine patch number from Github API&amp;quot;         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Write-Host $_         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Write-Host &amp;quot;Using default patch number 0&amp;quot;         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Write-Output &amp;quot;##teamcity[setParameter name=&apos;PatchVersion&apos; value=&apos;0&apos;]&amp;quot;         &lt;br /&gt;}&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;A couple of fun details to look out for:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;If we’re doing the first build of a new minor version, there won’t &lt;strong&gt;be&lt;/strong&gt; any ‘baseline’ tagged version yet – which is fine; we just set the patch number to zero and off we go. &lt;/li&gt;    &lt;li&gt;You can control TeamCity build parameters by outputting specially-formatted messages in your build log – that’s what those lines      &lt;br /&gt;      &lt;br /&gt;&lt;font size=&quot;2&quot; face=&quot;Consolas&quot;&gt;Write-Output &amp;quot;##teamcity[setParameters name=&apos;SomeParameter&apos; value=&apos;NewValue&apos;]&amp;quot;&lt;/font&gt;       &lt;br /&gt;      &lt;br /&gt;are doing. See &lt;a href=&quot;http://confluence.jetbrains.com/display/TCD7/Build+Script+Interaction+with+TeamCity#BuildScriptInteractionwithTeamCity-AddingorChangingaBuildParameter&quot;&gt;Build Script Interaction with TeamCity&lt;/a&gt; for more info. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Finally, if you&apos;d like the commit/merge hyperlinks in your TeamCity build log to be clickable, check out this awesome tip from Christian Rodemeyer about &lt;a href=&quot;http://atombrenner.blogspot.co.uk/2012/09/embed-url-links-in-teamcity-build-logs.html&quot;&gt;how to embed clickable URL links in TeamCity build logs&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;Happy versioning!&lt;/p&gt;  </description>
          <pubDate>2013-10-11T19:20:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2013/10/11/automatic-semantic-versioning-with.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2013/10/11/automatic-semantic-versioning-with.html</guid>
        </item>
      
    
      
        <item>
          <title>Building a ServiceStack-based OAuth2 resource server using DotNetOpenAuth</title>
          <description>&lt;p&gt;Last week, I asked on StackOverflow if &lt;a href=&quot;http://stackoverflow.com/questions/18257753&quot;&gt;anyone had used DotNetOpenAuth to secure a ServiceStack API using OAuth2&lt;/a&gt;. Well, it would appear not… so alongside a week of band rehearsals and a big old party last weekend, I’ve put a proof of concept up onGithub that demonstrates how to use ServiceStack and DotNetOpenAuth together to create a REST API that’s secured using OAuth2. The app I’m working on is aimed specifically at existing customers, who will already have a login account on our site, so I don’t need to use OpenID at all. In fact, I spent a good few days puzzling over this before I realised my mistake. After digging through the various specs and innumerable blog posts, I decided to use the &lt;a href=&quot;http://tools.ietf.org/html/rfc6749&quot;&gt;OAuth2 authorization framework&lt;/a&gt; – specifically, a feature of that framework called the &lt;a href=&quot;http://tools.ietf.org/html/rfc6749#section-4.3&quot;&gt;resource owner password credentials grant&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;The DotNetOpenAuth project includes a couple of great OAuth2 samples, including an authorization server (the bit that does the customer login), but they’re built primarily around ASP.NET MVC or WCF. I’ve worked with both of these in the past, but recently I’ve switched to using the &lt;a href=&quot;http://www.servicestack.net/&quot;&gt;ServiceStack&lt;/a&gt; library to build ReST APIs, and I absolutely love it. It’s easy to use, easy to extend, and wonderfully lightweight, so I wanted to use ServiceStack to build the data API that our app is going to use. I assumed it wouldn’t be too hard to build our own authorisation server, and then use a standard protocol like OAuth to control access to the resources exposed by this server.&lt;/p&gt;  &lt;p&gt;There’s a working prototype / proof of concept up on GitHub now, at &lt;a href=&quot;https://github.com/dylanbeattie/OAuthStack&quot;&gt;https://github.com/dylanbeattie/OAuthStack&lt;/a&gt; – complete with commentary on how the various integration points worked. OAuth2 is actually conceptually quite simple, but getting the two libraries to work together proved a little fiddly; both ServiceStack and OpenAuthDotNet use their own abstractions over the ASP.NET request/response objects, making me really wish Microsoft hadn’t made the .NET HTTP framework quite so inflexible back in the day… but after a week or so of tinkering, testing, poring over RFCs and bouncing around in the Visual Studio debugger, I’ve got something that seems pretty lightweight, doesn’t reinvent the wheel, and seems to satisfy the requirements. &lt;/p&gt;  &lt;p&gt;Suffice to say that if DotNetOpenAuth and ServiceStack weren’t open source this kind of integration would have been practically impossible, so I want to say a huge thank you to the authors and maintainers of both of those projects. Thanks, guys. You rock :)&lt;/p&gt;  </description>
          <pubDate>2013-08-19T21:07:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2013/08/19/building-servicestack-based-oauth2.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2013/08/19/building-servicestack-based-oauth2.html</guid>
        </item>
      
    
      
        <item>
          <title>Linking Subversion Commits to Pivotal Tracker on Windows using Powershell</title>
          <description>&lt;p&gt;My team at work now keep our entire lives in &lt;a href=&quot;https://www.pivotaltracker.com/&quot;&gt;PivotalTracker&lt;/a&gt;, and rather like it. We recently linked it to our GitHub repository using the built-in PivotalTracker integration that’s provided by GitHub, which worked really nicely. Thing is, we’re still in the process of migrating our codebase to GitHub – there’s lots of older projects with TeamCity jobs still linked to our in-house Subversion repo – and I wanted to add a similar facility for our projects that are still in Subversion.&lt;/p&gt;  &lt;p&gt;This is all based on the &lt;a href=&quot;https://www.pivotaltracker.com/help/api?version=v3#scm_post_commit&quot;&gt;Source Control Management Post-Commit Hook Integration&lt;/a&gt; feature in the Pivotal Tracker API, and getting it working required two scripts.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;post-commit.bat&lt;/strong&gt; – this is the standard batch file used by Subversion on Windows to run code after a successful commit. Subversion runs this automatically, and passes in two command-line arguments – the (local) path of the repository that accepted the commit, and the revision number created by that commit. Ours now looks like this:&lt;/p&gt;  &lt;blockquote&gt;   &lt;pre&gt;@echo off&lt;br /&gt;$ps = %SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe&lt;br /&gt;$ps Set-ExecutionPolicy unrestricted
$ps -command &amp;quot;D:\svn\data\repositories\projects\hooks\post-commit.ps1&amp;quot; -repopath %1 -revision %2&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;A couple of things to notice. First, we’re running the powershell Set-ExecutionPolicy as part of the script. This is brute force and there’s probably a nicer way of doing it, but since the Powershell script runs as the user who owns the Subversion server process, and that user doesn’t have interactive logon rights, I couldn’t see a quicker way of getting that script to run reliably. Ah, Powershell security, how we love you.&lt;/p&gt;

&lt;p&gt;Second – we’re passing the two command-line arguments into the powershell script as named parameters. Subversion will call the batch file as:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;post-commit.bat D:\svn\data\repositories\projects 12345&lt;/font&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and we’re translating that into&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;powershell.exe –command post-commit.ps1 –repopath D:\svn\data\repositories\projects –revision 12345&lt;/font&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The bit that actually does the heavy lifting is &lt;strong&gt;post-commit.ps1&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;param([string]$repopath, [string]$revision)&amp;#160;&amp;#160;&amp;#160; &lt;/font&gt;&lt;/p&gt;

  &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;$svnlook = &amp;quot;C:\Program Files\Subversion\bin\svnlook.exe&amp;quot;&lt;/font&gt;&lt;/p&gt;

  &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;$message = &amp;amp; &amp;quot;$svnlook&amp;quot; log $repopath -r $revision | Out-String&lt;/font&gt;&lt;/p&gt;

  &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;if ($message -match &amp;quot;#\d\d\d\d\d\d\d\d&amp;quot;) {
      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; $reponame = Split-Path $repopath –leaf

      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; $author = &amp;amp; &amp;quot;$svnlook&amp;quot; author $repopath -r $revision | Out-String

      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; # Grab a reference to System.Web so we can HtmlEncode things.

      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Add-Type -AssemblyName System.Web

      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; $author = [System.Web.HttpUtility]::HtmlEncode($author)

      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; $message = [System.Web.HttpUtility]::HtmlEncode($message)

      &lt;br /&gt;

      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; # We run ViewCV on our Subversion server to browse code via the web. Tweak this to suit,

      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; # or remove the &amp;lt;url&amp;gt;&amp;lt;/url&amp;gt; element from the XML completely.&lt;/font&gt;&lt;font face=&quot;Consolas&quot;&gt;
      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; $url = &amp;quot;&lt;/font&gt;&lt;a href=&quot;http://subversion.mycompany.com/viewvc/&quot;&gt;&lt;font face=&quot;Consolas&quot;&gt;http://subversion.mycompany.com/viewvc/&lt;/font&gt;&lt;/a&gt;&lt;font face=&quot;Consolas&quot;&gt;$reponame`?view=revision&amp;amp;revision=$revision&amp;quot;
      &lt;br /&gt;

      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; $headers = @{&amp;quot;X-TrackerToken&amp;quot; = &amp;quot;&lt;font style=&quot;background-color: #ffff00&quot;&gt;&lt;strong&gt;put your Pivotal Tracker API Token Here&lt;/strong&gt;&lt;/font&gt;&amp;quot;}

      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; # This is Powershell&apos;s &amp;quot;here-string&amp;quot; syntax for multiline string literals.

      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; $body = @&amp;quot;&lt;/font&gt;&lt;/p&gt;

  &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;&amp;lt;source_commit&amp;gt;
      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &amp;lt;message&amp;gt;$message&amp;lt;/message&amp;gt;

      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &amp;lt;author&amp;gt;$author&amp;lt;/author&amp;gt;

      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &amp;lt;commit_id&amp;gt;$revision&amp;lt;/commit_id&amp;gt;

      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &amp;lt;url&amp;gt;$url&amp;lt;/url&amp;gt;

      &lt;br /&gt;&amp;lt;/source_commit&amp;gt;

      &lt;br /&gt;&amp;quot;@&lt;/font&gt;&lt;/p&gt;

  &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;&amp;#160;&amp;#160;&amp;#160; $r = Invoke-WebRequest -Uri &lt;/font&gt;&lt;a href=&quot;http://www.pivotaltracker.com/services/v3/source_commits&quot;&gt;&lt;font face=&quot;Consolas&quot;&gt;http://www.pivotaltracker.com/services/v3/source_commits&lt;/font&gt;&lt;/a&gt;&lt;font face=&quot;Consolas&quot;&gt; -ContentType &amp;quot;application/xml&amp;quot; -Method POST -Headers $headers -Body $body&lt;/font&gt;&lt;/p&gt;

  &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;&amp;#160;&amp;#160;&amp;#160; Write-Host $r
      &lt;br /&gt;} else {

      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; # if nothing in the commit message matched the #12345678 syntax, then don’t bother calling the API.

      &lt;br /&gt;}&lt;/font&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p align=&quot;left&quot;&gt;To test it, first run the Powershell script directly from your command line. Say you’ve just committed revision 12345 to the “projects” repository, remembering to include [#12345678] in your commit comment where 12345678 is the PivotalTracker story ID.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p align=&quot;left&quot;&gt;&lt;strong&gt;PS &lt;/strong&gt;D:\svn\data\repositories\projects\hooks&amp;gt; &lt;strong&gt;./post-commit.ps1 –repopath D:\svn\data\repositories\projects –revision 12345&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p align=&quot;left&quot;&gt;Your commit comment should appear in the story history within a few seconds. &lt;/p&gt;

&lt;p align=&quot;left&quot;&gt;Now check the batch file harness can run your Powershell properly – see the note below about revisions:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p align=&quot;left&quot;&gt;D:\svn\data\repositories\projects\hooks&amp;gt; &lt;strong&gt;post-commit.bat D:\svn\data\repositories\projects 12346&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p align=&quot;left&quot;&gt;If that works, the whole kaboodle should work. A couple of caveats to watch out for:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;div align=&quot;left&quot;&gt;If your post-commit hook fails, your Subversion client will give you something cryptic about “Commit failed: MERGE … 200 OK” – which is not particularly transparent. Best to get things working on the command line first before testing via Subversion itself.&lt;/div&gt;
  &lt;/li&gt;

  &lt;li&gt;
    &lt;div align=&quot;left&quot;&gt;I couldn’t get the same commit to produce multiple Pivotal tickets – when I was testing it, I had to create a fresh commit for each test run of the integration hook. This is possibly just Pivotal automatically de-duplicating identical stories – but worth knowing for test purposes&lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;  </description>
          <pubDate>2013-06-24T12:03:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2013/06/24/linking-subversion-commits-to-pivotal.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2013/06/24/linking-subversion-commits-to-pivotal.html</guid>
        </item>
      
    
      
        <item>
          <title>Adventures in Querying with RavenDB</title>
          <description>&lt;p&gt;For the last couple of months, we&apos;ve been working on a project that uses &lt;a href=&quot;http://ravendb.net/&quot;&gt;RavenDB&lt;/a&gt; as the main data store. We went with Raven for several reasons - it looked like a pretty good fit for what we were doing, we were using NServiceBus which now includes Raven as its default persistence store, and we were keen to see what it could do. Generally, it&apos;s been really, really positive. However, RavenDB is the first document database I&apos;ve ever used, and so once in a while the learning curve just leaves me scratching my head in frustration...&lt;/p&gt;  &lt;p&gt;So, last week, a request comes in for a straightforward one-off report. We&apos;re using Raven to store details about media clips - video and audio files - that are linked to our customers&apos; online CVs, and the product owner wanted a list of customers along with the total duration of the media clips linked to those customers&apos; CVs.&lt;/p&gt;  &lt;p&gt;In SQL, this would be &lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;SELECT Customer, SUM(Duration) as TotalDuration FROM MediaClips GROUP BY Customer&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;The two developers who actually built the system weren&apos;t in the office last week, so this request ended up on my desk. &amp;quot;Ah, no problem&amp;quot;, thinks me. After all - it&apos;s a database; how hard can it be? I&apos;d spent a bit of time building custom indexes to drive an SLA report we built last week, and with quite a lot of Googling, managed to get some really impressive results out of Raven. &lt;/p&gt;  &lt;p&gt;Thing is - I&apos;m not building a feature here, I just need to copy &amp;amp; paste a bunch of numbers into a spreadsheet and get on with my life. And I have no idea how you do that in Raven. I spend an hour or so trying to get Ronnie Overby&apos;s &lt;a href=&quot;https://github.com/ronnieoverby/RavenDB-Linqpad-Driver&quot;&gt;RavenDB provider for LinqPAD to install&lt;/a&gt;. This is a third-party library that isn&apos;t part of RavenDB, but I like LinqPAD, I&apos;m pretty comfortable with Linq query syntax, and this looked like a good place to start. Half an hour later, after downloading the source, compiling it, creating a certificate, signing it and getting LinqPAD to recognise and install the driver, I gave up because I just couldn&apos;t work out how to get any data out of the damn thing. &lt;/p&gt;  &lt;p&gt;Instead, I throw together a .NET console app and start playing around with indexes, working from &amp;quot;&lt;a href=&quot;http://ravendb.net/docs/2.0/client-api/querying/using-linq-to-query-ravendb&quot;&gt;Using Linq to query RavenDB indexes&lt;/a&gt;&amp;quot;. I get as far as creating a named map/reduce index that&apos;s pulling out the data I need - this takes a good hour or two of trial and error. The eureka moment is when I work out that you can define Raven indexes as raw LINQ code - they&apos;re not even embedded strings, they&apos;re actually strongly-typed, compiled Linq expressions that are passed in to the query definition. I get as far as this:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;class Total {       &lt;br /&gt;&amp;#160; public string CustomerId { get; set; }        &lt;br /&gt;&amp;#160; public int Duration { get; set; }        &lt;br /&gt;}&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;public void BuildIndex() {       &lt;br /&gt;&amp;#160; var store = new DocumentStore() { ConnectionStringName = &amp;quot;raven&amp;quot; };        &lt;br /&gt;&amp;#160; store.Initialize();        &lt;br /&gt;&amp;#160; store.DatabaseCommands.PutIndex(&amp;quot;ClipInfos/TotalDurations&amp;quot;, new IndexDefinitionBuilder&amp;lt;ClipInfo, Total&amp;gt; {&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;&amp;#160;&amp;#160;&amp;#160; Map = clips =&amp;gt; from clip in clips select new {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Customer = clip.CustomerId,        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Duration = clip.DurationInSeconds        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; },&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;&amp;#160;&amp;#160;&amp;#160; Reduce = results =&amp;gt; from result in results group result by result.Customer into g select new {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Customer = g.Key,        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Duration = g.Sum(x =&amp;gt; x.Duration)        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }        &lt;br /&gt;&amp;#160; });        &lt;br /&gt;}&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Now, Raven encourages &amp;quot;safe&amp;quot; query patterns. Which means it&apos;ll only give you 128 records per query by default, and if you run more than 30 queries in a single session it&apos;ll fail with an exception telling you you&apos;re doing something dangerous. The only way I can find to actually retrieve all the data I need is to circumvent these defaults to increase the batch size from 128 to 1024, and then repeated use .Skip(1024*batch).Take(1024) to pull records back 1,024 at a time and add them to my result set.&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;using (var session = raven.OpenSession()) {       &lt;br /&gt;&amp;#160; session.Advanced.MaxNumberOfRequestsPerSession = 512;        &lt;br /&gt;&amp;#160; while (true) {        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; var totals = session.Query&amp;lt;Total&amp;gt;(&amp;quot;ClipInfos/TotalDurations&amp;quot;).Skip(1024 * batches).Take(1024).ToList();         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; foreach (var total in totals)&amp;#160; {         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; // Do something useful.         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; if (totals.Count() &amp;lt; 1024) break;        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; batches++;        &lt;br /&gt;&amp;#160; }        &lt;br /&gt;}&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;At this point, I&apos;m thinking one of two things:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;This is fine. It&apos;s an ad-hoc requirement, and if we end up doing this a *lot* I should read up on the &lt;a href=&quot;http://ravendb.net/docs/server/bundles/index-replication&quot;&gt;index replication bundle&lt;/a&gt; - a plug-in that, I gather, will export indexed data into a relational DB for easier querying.&lt;/li&gt;    &lt;li&gt;This is completely wrong. It should not take three hours and a dedicated console application to run a simple ad-hoc query against a set of Raven data. I am missing something obvious...&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;As somebody who&apos;s been writing SQL for nearly 20 years, it&apos;s really frustrating to hit a brick wall like this... and it&apos;s been a long day, and I&apos;m getting fed up, and I wander over to Twitter to have a bit of a rant and @ayende pops up:&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh4.ggpht.com/-iJA22STRzxk/UU3f6uT4BbI/AAAAAAAAAP0/2FxXwWwVqfc/s1600-h/image%25255B3%25255D.png&quot;&gt;&lt;img style=&quot;background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh3.ggpht.com/-Tzz8bMqTKSo/UU3f7qszg4I/AAAAAAAAAP4/xmlSXpJx-Tk/image_thumb%25255B1%25255D.png?imgmax=800&quot; width=&quot;518&quot; height=&quot;617&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Ah. Dynamic Queries. That looks interesting. So I head over to Raven Studio and sure enough, there&apos;s a &amp;quot;Dynamic Queries&amp;quot; facility. So I paste in a simple query and hit &amp;quot;Execute&amp;quot;:&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh6.ggpht.com/-K2Z1lDTgIo4/UU3f8S3HM4I/AAAAAAAAAQA/13trudD3ff4/s1600-h/image%25255B20%25255D.png&quot;&gt;&lt;img style=&quot;background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh5.ggpht.com/-5CXj5TBm9j0/UU3f9UMuDwI/AAAAAAAAAQM/az2aslYpW1g/image_thumb%25255B10%25255D.png?imgmax=800&quot; width=&quot;891&quot; height=&quot;671&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;No results? Huh? But there&apos;s tens of thousands of documents in there! At this point I give up and go to the pub.&lt;/p&gt;  &lt;p&gt;This morning, I go back to it and notice the little &amp;quot;i&amp;quot; icon next to the Query header, and see this...&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh5.ggpht.com/-Crp8FRF0o8E/UU3f-ZY2LTI/AAAAAAAAAQU/BXnR_siKMpk/s1600-h/image%25255B12%25255D.png&quot;&gt;&lt;img style=&quot;background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; margin-left: auto; border-top: 0px; margin-right: auto; border-right: 0px; padding-top: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh5.ggpht.com/-I95FIJbl7A0/UU3f_R9uMKI/AAAAAAAAAQY/F24GpP4j5lM/image_thumb%25255B6%25255D.png?imgmax=800&quot; width=&quot;419&quot; height=&quot;236&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Ah. I have no idea what Lucene syntax is... but that probably explains why none of my dynamic queries worked.&lt;/p&gt;  &lt;p&gt;Raven is really impressive. We&apos;ve managed to get it to do some very cool things; it&apos;s fast, and most of the time it Just Works. And there is an inevitable learning curve associated with adopting a new technology - particularly one that represents a paradigm shift in approach. It&apos;s kinda like learning Powershell and treating it as another CLR/.NET language... which is really, really frustrating, until you realize it&apos;s not &amp;quot;scriptable .NET&amp;quot;, it&apos;s more like command.com on steroids. The shift from relational to document databases is much the same. That said - it would be really, really useful if the dynamic query tool in Raven Studio would distinguish between &amp;quot;I understand that query but could find no matching records&amp;quot; and &amp;quot;I have no idea what you are trying to do&amp;quot; - I have no idea how strict the Lucene query language or parser is, but knowing whether your query is syntactically valid or not would be a big help when you&apos;re trying to work out why it didn&apos;t return anything.&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh6.ggpht.com/-eR482ATG8gU/UU3gATwT08I/AAAAAAAAAQk/Zt-2dYTNnv8/s1600-h/image%25255B24%25255D.png&quot;&gt;&lt;img style=&quot;background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; margin-left: auto; border-top: 0px; margin-right: auto; border-right: 0px; padding-top: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh6.ggpht.com/-wyPakOtZt-w/UU3gBDYcnVI/AAAAAAAAAQs/BUYWQH11OoA/image_thumb%25255B12%25255D.png?imgmax=800&quot; width=&quot;891&quot; height=&quot;671&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  </description>
          <pubDate>2013-03-23T17:01:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2013/03/23/adventures-in-querying-with-ravendb.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2013/03/23/adventures-in-querying-with-ravendb.html</guid>
        </item>
      
    
      
        <item>
          <title>Adventures with Powershell and Amazon EC2 (part 1)</title>
          <description>&lt;p&gt;Managing servers isn’t fun. Setting up servers isn’t fun. I don’t want to do it any more, ever – it’s tedious, repetitive and error-prone, and, dammit, we have &lt;em&gt;machines&lt;/em&gt; to do those kinds of jobs these days. &lt;/p&gt;  &lt;p&gt;So, here’s what I’m trying to do. I want to be able to spin up a blank Windows instance on Amazon EC2 and deploy our apps onto it without ever logging into the box directly. This means no remote desktop sessions, no VNC, no SSH – nothing. These blog posts are as much a diary of work in progress as anything else… I’ll probably make mistakes and get things wrong along the way, but hopefully I’ll end up with something usable.&lt;/p&gt;  &lt;h3&gt;Step 1: Hello, World!&lt;/h3&gt;  &lt;p&gt;Prerequisites:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;An Amazon EC2 security group with firewall rules accepting all connections from your local network.&lt;/li&gt;    &lt;li&gt;A fresh Windows instance running in that security group, bound to an Amazon elastic IP&lt;/li&gt;    &lt;li&gt;The Windows Administrator username and password for that instance.&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;Create this script on your local workstation:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font size=&quot;2&quot; face=&quot;Consolas&quot;&gt;$remoteUsername = &amp;quot;Administrator&amp;quot;       &lt;br /&gt;$remotePassword = &amp;quot;&lt;font color=&quot;#a5a5a5&quot;&gt;&lt;em&gt;your Amazon EC2 instance Administrator password&lt;/em&gt;&lt;/font&gt;&amp;quot;        &lt;br /&gt;$remoteHostname = &amp;quot;&lt;em&gt;&lt;font color=&quot;#a5a5a5&quot;&gt;the elastic IP bound to your Amazon EC2 box&lt;/font&gt;&lt;/em&gt;&amp;quot;&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font size=&quot;2&quot; face=&quot;Consolas&quot;&gt;$securePassword = ConvertTo-SecureString -AsPlainText -Force $remotePassword       &lt;br /&gt;$cred = New-Object System.Management.Automation.PSCredential $remoteUsername, $securePassword&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font size=&quot;2&quot; face=&quot;Consolas&quot;&gt;Invoke-Command -ComputerName $remoteHostname -Credential $cred -ScriptBlock {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Write-Host &amp;quot;Hello, World (from $env:COMPUTERNAME)&amp;quot;&lt;/font&gt;      &lt;br /&gt;}&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Run it. You should get:&lt;/p&gt;  &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;Invoking command on Remote host &lt;font color=&quot;#a5a5a5&quot;&gt;&lt;em&gt;X.X.X.X         &lt;br /&gt;&lt;/em&gt;&lt;/font&gt;Hello, World (from AMAZONA-???????)&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;If that works, you’ve got Powershell remoting working against your Amazon EC2 instance. Hurrah!&lt;/p&gt;  </description>
          <pubDate>2012-12-03T13:18:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2012/12/03/adventures-with-powershell-and-amazon.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2012/12/03/adventures-with-powershell-and-amazon.html</guid>
        </item>
      
    
      
        <item>
          <title>The Joy of Forms Authentication, IIS7 and Device Profiles</title>
          <description>&lt;p&gt;We’re setting up a thing called &lt;a href=&quot;http://www.paessler.com/prtg&quot;&gt;PRTG Network Monitor&lt;/a&gt; to add monitoring and logging to one of our new MVC3 web applications, and I spent this morning scratching my head and wondering why an app that works just fine when you view it in a browser was consistently failing when I tried to attach an HTTP monitor to it.&lt;/p&gt;  &lt;p&gt;A little digging with &lt;a href=&quot;http://www.wireshark.org/&quot;&gt;Wireshark&lt;/a&gt; later, and it turns out that IIS/ASP.NET isn’t sending the FormsAuthentication cookie in the response to requests made via the PRTG monitoring software. Which is… odd. We haven’t explicitly configured cookieless authentication or anything – although we’re doing some funky stuff with cross-domain auth cookies, the PRTG monitor is as simple as it gets – POST credentials to the login handler, get a response, check the response includes a cookie. Not rocket surgery.&lt;/p&gt;  &lt;p&gt;After a fun hour of comparing TCP traces and HTTP headers, it turns out it’s the &lt;a href=&quot;http://en.wikipedia.org/wiki/User_agent&quot;&gt;HTTP User Agent&lt;/a&gt; that’s controlling this behaviour – if ASP.NET running under IIS7 sees particular user agents, it just doesn’t set any cookies. And this is where it gets interesting. Here’s some real-world HTTP user agents that work just fine:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font size=&quot;2&quot;&gt;&lt;strong&gt;Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1&lt;/strong&gt;&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font size=&quot;2&quot;&gt;&lt;strong&gt;Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0);&lt;/strong&gt;&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font size=&quot;2&quot;&gt;&lt;strong&gt;Opera/9.80 (Windows NT 6.1; WOW64; U; en) Presto/2.10.289 Version/12.02&lt;/strong&gt;&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Here’s the PRTG monitoring software’s default user agent. Now this looks to me like a completely sensible user agent for a piece of monitoring software – but IIS7 clearly believes this to be a client that can’t handle HTTP cookies, and so won’t set any:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font size=&quot;2&quot;&gt;&lt;strong&gt;Mozilla/5.0 (compatible; PRTG Network Monitor (www.paessler.com); Windows)&lt;/strong&gt;&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;But the &lt;em&gt;really&lt;/em&gt; fun part – here’s a bunch of user agents that work absolutely fine:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;strong&gt;Bozilla/5.0 (compatible; PRTG Network Monitor (www.paessler.com); Windows)&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;&lt;strong&gt;Batman/1.2 (compatible; spiderman; vigorous jazz-hands)&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;&lt;strong&gt;CAKE CAKE CAKE CAKE CAKE I LIKE CAKE&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;&lt;strong&gt;CookieHater1.2 (incompatible; does not accept cookies; no cookies; cookies are evil)&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;This doesn’t work:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;strong&gt;Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML)&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;But these do:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;strong&gt;Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like a BOSS)&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;&lt;strong&gt;Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like anyone cares)&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;In short - it looks like any string that starts with “Mozilla” triggers some sort of complicated parsing algorithm that looks for magic values in the user-agent string and decides that means they’re cookie-capable. I have this horrible, horrible feeling that somewhere in the bowels of the IIS team there’s somebody who still thinks &lt;a href=&quot;https://browsers.garykeith.com/&quot;&gt;browscap.ini&lt;/a&gt; was a good idea – and I’m &lt;em&gt;amazed &lt;/em&gt;that something as fragile and idiosyncratic as determining device capabilities based on user agents would be the default behaviour in IIS7 and ASP.NET 4.&lt;/p&gt;  &lt;p&gt;The good news is that this bizarre behaviour is easily fixed – just add &lt;strong&gt;cookieless=&amp;quot;useCookies&amp;quot;&lt;/strong&gt; to the &lt;strong&gt;system.web/authentication/forms &lt;/strong&gt;element of your web.config file. The default is apparently “useDeviceProfile”, and there’s more details in the &lt;a href=&quot;http://technet.microsoft.com/en-us/library/cc732830(v=ws.10).aspx&quot;&gt;IIS documentation on Microsoft Technet&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;This is also another classic example of how easily you can lose an entire day of developer time to something that &lt;em&gt;should &lt;/em&gt;have taken about ten minutes. We have an app that uses HTTP and out-of-the-box cookie-based authentication. We’ve got something approaching 500 tests verifying the behaviour of this app under every scenario we could think of. We have a mature, stable monitoring platform that hundreds, if not thousands, of people are using to monitor HTTP applications every day. And yet BANG! you connect them together, everything falls apart and next thing you know you’re up to your elbows in network packets and wondering if it’s too early in the week to start drinking…&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;UPDATE:&lt;/strong&gt; Thanks to &lt;a href=&quot;http://twitter.com/duncansmart&quot;&gt;@duncansmart&lt;/a&gt; for pointing me at &lt;a href=&quot;http://www.hanselman.com/blog/FormsAuthenticationOnASPNETSitesWithTheGoogleChromeBrowserOnIOS.aspx&quot;&gt;this article on Scott Hanselman’s blog&lt;/a&gt; where he discusses this very problem. &lt;/p&gt;  </description>
          <pubDate>2012-10-30T20:08:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2012/10/30/the-joy-of-forms-authentication-iis7.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2012/10/30/the-joy-of-forms-authentication-iis7.html</guid>
        </item>
      
    
      
        <item>
          <title>Rocksmith for Guitar Players</title>
          <description>&lt;p&gt;OK, having finally &lt;a href=&quot;http://dylanbeattie.blogspot.co.uk/2012/10/ripping-off-paying-customers-ubisoft-way.html&quot;&gt;convinced those necrotic turbinates at Ubisoft that I&apos;m not a dirty filthy pirate&lt;/a&gt; (yarrr!) I have an install key for Rocksmith. And I&apos;ve installed it. And I&apos;ve played it. Now, I&apos;m not a gamer. I&apos;ve played around with Guitar Hero and gone &amp;quot;meh&amp;quot;... But I &lt;em&gt;have&lt;/em&gt; been playing guitar for 25 years and wasted more hours than I care to recall plugging assorted guitars into various kinds of computer gear to try and make them do interesting things, so I was really curious to see how Rocksmith did it.&lt;/p&gt;  &lt;h4&gt;First impressions? &lt;/h4&gt;  &lt;p&gt;It looks fantastic. Lovely atmospheric visuals, stylistically quite beautiful. Second impression? &lt;em&gt;Lag&lt;/em&gt;. Seriously. The folks telling you you won&apos;t notice 30ms of latency aren&apos;t musicians - they&apos;re marketing shills who work for the computer games industry. Having 30 milliseconds of delay between your fingers and your ears is &lt;em&gt;horrible - &lt;/em&gt;after ten minutes, it was actually making me feel ill. Kinda like when you&apos;re on a roller-coaster and your eyes are disagreeing with your inner ear about which way is up - the same kind of slightly nauseating sensory disconnect. Yuck. &lt;/p&gt;  &lt;h4&gt;Eliminating Latency&lt;/h4&gt;  &lt;p&gt;So... just when I was about to give the whole thing up as a failed experiment, I had a daft idea. I plugged in an electro-acoustic and turned the guitar volume in the game right down - I figured the game can play the backing track and the guitar can look after itself, sonically speaking. The experience was instantly transformed - the game engine itself doesn&apos;t seem to suffer from any kind of latency, and suddenly I was jamming along to the Stones with a big stupid grin on my face. So I started thinking - maybe there&apos;s some way I could link up my existing guitar gear so I can get the same zero-latency experience but with an electric guitar and all the tones and presets in my trusty &lt;a href=&quot;http://line6.com/legacy/podxt&quot;&gt;Line6 PodXT&lt;/a&gt;... easy. All you need is something to split the signal as it leaves the guitar. Now although I&apos;ve not tried, I suspect an ordinary jack splitter would do the trick - plug it into your guitar, then plug the Rocksmith jack-USB cable into one output, and connect the other to your amp or effects rig using a normal guitar lead.&lt;/p&gt;  &lt;p&gt;&lt;img style=&quot;display: block; float: none; margin-left: auto; margin-right: auto&quot; src=&quot;http://cachepe.samedaymusic.com/media/quality,85/3fc5b012bc918e68ac6150ab043e9221-7beeb2fe0d28c9c0812d91fc3f5f972f.jpg&quot; width=&quot;464&quot; height=&quot;234&quot; /&gt;&lt;/p&gt;  &lt;p&gt;Problem is, I didn&apos;t have anything as simple as a splitter amongst my boxes of audio gear... so I ended up doing this instead:&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh4.ggpht.com/-A70jVbACdyQ/UIXbrr9MtBI/AAAAAAAAAO4/Uu2spq6jdtk/s1600-h/image%25255B6%25255D.png&quot;&gt;&lt;img style=&quot;background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto; padding-top: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh5.ggpht.com/-pG5W27H0Ij8/UIXbshWt5EI/AAAAAAAAAO8/2UNY0xYClHo/image_thumb%25255B4%25255D.png?imgmax=800&quot; width=&quot;660&quot; height=&quot;314&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;The UA25EX is a really good USB audio adapter, but here I&apos;m just using it as an active splitter, piping one output into the PC via the Rocksmith lead and then running the other one into the PodXT; the Pod&apos;s outputs are wired to the PC line-in, and then I&apos;ve tweaked the Windows volume control so that the line-in is mixed back into the playback signal. Finally, remember to turn the guitar volume in Rocksmith down to zero, and &lt;em&gt;voila&lt;/em&gt; - Rocksmith does the backing, Pod does the guitar sound with zero latency, and I&apos;m back to grinning like an idiot. (Yes, I realise I&apos;m using about £1500 worth of gear as a glorified gamepad... but hey, it&apos;s not like I bought this gear just to play computer games like &lt;a href=&quot;http://www.codinghorror.com/blog/2008/01/my-racing-simulation-rig.html&quot;&gt;Jeff Atwood over at CodingHorror&lt;/a&gt; did...) One more thing I changed - under Options -&amp;gt;&amp;#160; Game Settings -&amp;gt; String Layout - set this to &lt;strong&gt;inverted&lt;/strong&gt;. It wasn&apos;t obvious at first, but the standard setting mimics the physical layout of guitar strings (i.e. treble at the bottom, bass at the top) while guitar tab and normal notation are written the other way up - things suddenly got a lot more fun once I tweaked this.&lt;/p&gt;  &lt;h4&gt;So... latency solved, is the game any good? &lt;/h4&gt;  &lt;p&gt;Well, yeah. It is. And I&apos;m really impressed at how well it equates improvement in guitar technique with in-game progress. Using a combination of training exercises, arcade mini-games and full band backing tracks, it&apos;ll take you from tuning up, to playing simple one-note riffs, to slides, bends and hammer-ons. If you can already play, you&apos;ll be amazed at how quickly it exposes your weak spots and bad habits - and if you can&apos;t, I suspect you&apos;ll be really surprised at how quickly you progress. The techniques are clearly demonstrated on-screen, there&apos;s a built-in tuner that pops up at regular intervals to make sure your guitar&apos;s still in tune, and even following the on-screen color-coded notation isn&apos;t too hard - as a guitarist who never learned to sight-read, I was surprised how quickly I started to recognise patterns and phrases as they flew down the screen towards me at 140bpm. It also has distinct guitar and bass modes - including an &amp;quot;emulated bass&amp;quot; mode if you want to learn to play basslines but you&apos;ve only got a six-string guitar. No five-string bass support, though... here&apos;s hoping we get that in Rocksmith 2. &lt;/p&gt;  &lt;p&gt;The Guitarcade mini-games deserve a special mention, if only for the stupid amount of time I&apos;ve spent playing Super Slider. Coloured blocks fall down the grid... you move them left and right by playing slide notes on the guitar. It is stupidly addictive and - because it requires you to change strings and positions without taking your eyes off the screen - it&apos;s also great for improving fretboard technique. &lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh4.ggpht.com/-7R4ehNMhXC4/UIXbtveYfmI/AAAAAAAAAPE/d2y9KzFBtI4/s1600-h/image%25255B10%25255D.png&quot;&gt;&lt;img style=&quot;background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto; padding-top: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh4.ggpht.com/-FGo-bJ79e78/UIXbulIQBOI/AAAAAAAAAPQ/3I1ZTAYjON4/image_thumb%25255B6%25255D.png?imgmax=800&quot; width=&quot;625&quot; height=&quot;484&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;My one frustration is that the game constantly adjusts the difficulty, so there&apos;s no way - that I&apos;ve found, anyway - to just bring up a song on &amp;quot;accurate&amp;quot; difficulty setting and keep practising it until you get it right. A couple of times I actually managed to move up a level just by noodling around and throwing in notes that weren&apos;t on the screen - turns out I was getting it right (?) and so the difficulty increased accordingly. I can imagine this being great fun if you already know the tune and you&apos;re working on your guitar technique, but it&apos;d be nice if you could just switch it off and dive in at the deep end, so to speak.&lt;/p&gt;  &lt;p&gt;In conclusion: great visuals, great gameplay, great implementation. And it actually *works* - once I&apos;d solved the lag problem, I never once felt like I&apos;d hit a note and the game had missed it, which is &lt;em&gt;really&lt;/em&gt; impressive when you consider it&apos;s analysing the ragged mess of waveforms coming off a six-string guitar in real-time. Sure, the lag sucks and the missing CD keys and crappy attitude &lt;em&gt;really &lt;/em&gt;sucks, but neither of those problems is insurmountable with a little patience and ingenuity. And once you get up and running, it&apos;s great fun. I can&apos;t wait to hear what happens when a generation of kids who grew up on this move onto GarageBand and start recording demos. &lt;/p&gt;  </description>
          <pubDate>2012-10-22T23:50:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2012/10/22/rocksmith-gaming-fun-for-guitar-players.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2012/10/22/rocksmith-gaming-fun-for-guitar-players.html</guid>
        </item>
      
    
      
        <item>
          <title>Ripping Off Paying Customers the Ubisoft Way</title>
          <description>&lt;p&gt;I got into work this morning to find Dave excitedly waving a box containing something called Rocksmith - which, it turns out, is a &amp;quot;Guitar Hero&amp;quot;-type game, but you play it using a &lt;em&gt;real guitar. &lt;/em&gt;Now, whilst I&apos;m not a serious gamer, I am a tech nerd, and a guitar geek, and this sounded like the best thing ever. If nothing else, for the first time in 15 years, it might actually be a computer game I could beat my brother at... So, this lunchtime, I left the office, wandered to HMV on Piccadilly, and bought it. No download, no Steam, no piracy or dubious torrenting, just good, old-fashioned law-abiding retail. I walked into a physical store, picked up a boxed product, paid £39.99 for it, and left. &lt;/p&gt;  &lt;p&gt;Now, here&apos;s what the guy in HMV did NOT say when I bought it. He didn&apos;t say &amp;quot;Keep the receipt because you&apos;ll need to send a copy of it to Ubisoft in order to install your game&amp;quot;. He DID offer to sell me &amp;quot;game protection&amp;quot; for a pound, which sounded like (a) a way of charging me money for my own statutory rights, and (b) the biggest rip-off I&apos;d ever heard - ha, maybe he knew something I didn&apos;t. But no, after I&apos;d paid for my game, he wished me a pleasant weekend and off I went. The receipt went in the bag, along with the game, and, by the time I returned to work, some samosas and a Chinese pork bun. When I got back to the office, I put the game in my rucksack, the samosas and the pork bun in my belly, and stuck the carrier bag in a drawer. Why am I mentioning this? Ah, just wait and see...&lt;/p&gt;  &lt;p&gt;So I get home around nine this evening and open it - &lt;em&gt;look at all the things! &lt;/em&gt;It even comes with a jack-USB cable to plug your guitar into your PC - that&apos;s cool! OK, and a sheet of rather lame-looking fret stickers. I shudder at the thought of someone sticking these on a &apos;57 Les Paul Standard that they&apos;ve only dragged out from under the bed because they think Rocksmith might FINALLY get them playing it...&lt;/p&gt;  &lt;p&gt;Anyway, I digress. I go to install it, and I&apos;m asked to enter a CD key. This is pretty normal.&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh4.ggpht.com/-_2bh_w5ACJw/UIHKTyMrOqI/AAAAAAAAANY/OzbN3o7rqgs/s1600-h/image%25255B3%25255D.png&quot;&gt;&lt;img style=&quot;background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto; padding-top: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh4.ggpht.com/-e9xhYKyhnxI/UIHKUn1sBQI/AAAAAAAAANg/MtRdSMT6cVI/image_thumb%25255B1%25255D.png?imgmax=800&quot; width=&quot;480&quot; height=&quot;596&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;OK... on the jewel case? No. Manual cover? No. Disc insert? Under the disc? No. Oh dear. This is invariably where things get unpleasant. I Google &lt;a href=&quot;http://www.google.co.uk/search?q=rocksmith+CD+key&quot;&gt;rocksmith CD key&lt;/a&gt; and - wow, it looks like I&apos;m not the only one. The top link is to a Ubisoft support article published 4 days ago:&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh4.ggpht.com/-SkXZEllYXOQ/UIHKVVbNW5I/AAAAAAAAANk/8dAMHpPHnC4/s1600-h/image%25255B8%25255D.png&quot;&gt;&lt;img style=&quot;background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto; padding-top: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh6.ggpht.com/-uXD8Lgmlb3U/UIHKWLUUMWI/AAAAAAAAANw/P2iezJc46xA/image_thumb%25255B4%25255D.png?imgmax=800&quot; width=&quot;549&quot; height=&quot;105&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Which links to &lt;a href=&quot;http://ubisoft-en.custhelp.com/app/answers/detail/a_id/17223/~/missing-cd-key-for-rocksmith-pc&quot;&gt;this article&lt;/a&gt;, which says:&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh4.ggpht.com/-4Z2yp0nXvqQ/UIHKW_7rz_I/AAAAAAAAAN4/p2laXaR4yz4/s1600-h/image%25255B14%25255D.png&quot;&gt;&lt;img style=&quot;background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh5.ggpht.com/-Hy0ni9cRGgI/UIHKX_kEWmI/AAAAAAAAAN8/R-vZzCQ8zYw/image_thumb%25255B8%25255D.png?imgmax=800&quot; width=&quot;637&quot; height=&quot;500&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;I try giving them a call, but &amp;quot;unfortunately [their] offices are now closed&amp;quot;. So it looks like that support article is all I&apos;ve got until Monday morning. I love the &amp;quot;encountering problems during installation&amp;quot;. No, it&apos;s not a &amp;quot;problem during installation&amp;quot;, it&apos;s that the manufacturer screwed up and omitted to include an essential part of the product in the retail packaging. And they&apos;d like a copy of the receipt. Which, as I&apos;ve mentioned, is in a bag eight miles away.&lt;/p&gt;  &lt;p&gt;So, leaving aside that little detail, let&apos;s see how far we get. First I have to submit a request. I can&apos;t do this without signing in, I can&apos;t sign in without creating an account. Of course, this doesn&apos;t work first time because their sign-up/login system has &apos;issues&apos;. Eventually I get as far as submitting a support request:&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh5.ggpht.com/-5ksT5PifrRE/UIHKYmDNKHI/AAAAAAAAAOE/ZImlJEbwM8g/s1600-h/image%25255B21%25255D.png&quot;&gt;&lt;img style=&quot;background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto; padding-top: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh5.ggpht.com/-1TkxOLMPQIY/UIHKZYjOnrI/AAAAAAAAAOQ/5LA7iHmugr8/image_thumb%25255B11%25255D.png?imgmax=800&quot; width=&quot;539&quot; height=&quot;484&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;OK, question submitted at 21:52 UK time. And now, I guess, I wait and see what happens. They aim to respond to me &amp;quot;within 24-48 hours, although response times may be longer.&amp;quot; Looks like I won&apos;t be playing Rocksmith this weekend.&lt;/p&gt;  &lt;p&gt;Let&apos;s just recap. So far, I&apos;ve gone out in the rain to purchase a product because it looks like fun. I&apos;ve spent £39.99. I&apos;ve spent ten minutes searching through packaging for a non-existent key. I&apos;ve had to create an account in order to politely request that the manufacturer allow me to use the product. Ubisoft have &lt;strike&gt;effectively accused me of having stolen the game&lt;/strike&gt; assumed I&apos;m not a legitimate customer and I therefore can&apos;t install it until I prove otherwise. This isn&apos;t a minor manufacturing defect affecting a single unit, like a cracked picture frame or a corked bottle of wine - this &lt;a href=&quot;http://www.amazon.co.uk/review/RWOUR7ZJSGRAT&quot;&gt;looks&lt;/a&gt; &lt;a href=&quot;http://www.youtube.com/watch?v=Gw1nPMr4ttc&quot;&gt;like&lt;/a&gt; &lt;a href=&quot;https://de.twitter.com/search?q=rocksmith%20cd%20key&amp;amp;src=typd&quot;&gt;they&apos;ve&lt;/a&gt; just &lt;a href=&quot;http://forums.ubi.com/showthread.php/718398-My-retail-copy-of-Rocksmith-PC-does-not-have-a-product-registration-code-Forums&quot;&gt;deliberately excluded&lt;/a&gt; the installation key from boxed retail copies of the game. &lt;/p&gt;  &lt;p&gt;What&apos;s completely ridiculous is that this is one of the few games in recent history that requires a physical component - the aforementioned USB-guitar cable. They could so easily have encoded a unique key in the cable and used that as a copy protection dongle - after all, you can&apos;t download a cable from the Pirate Bay. But no, instead they go for a CD key and don&apos;t bother to include one. I appreciate game development is a hugely involved and expensive exercise, and that piracy has the potential to seriously undermine the commercial viability of game development - but treating your customers like criminals to compensate for your own short-sightedness and lack of imagination is not the answer. &lt;/p&gt;  &lt;p&gt;Next time I want to spend £40 on something fun to do over a rainy weekend, I&apos;ll forgo the delights of proving my innocence to the games industry and just buy some more Lego. At least I&apos;ve never had to grovel to the manufacturer for permission to start building a Lego set.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;UPDATE:&lt;/strong&gt; E-mail from Ubisoft on Saturday afternoon saying they can&apos;t/won&apos;t help until I send them the original purchase receipt:&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh6.ggpht.com/-L1IhOK3_7dQ/UIPq6TRN9wI/AAAAAAAAAOg/guvWbw_QOD0/s1600-h/image%25255B4%25255D.png&quot;&gt;&lt;img style=&quot;background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto; padding-top: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh5.ggpht.com/-EIF1LNji-Nc/UIPq7EetvaI/AAAAAAAAAOo/E1SytnPZUkE/image_thumb%25255B1%25255D.png?imgmax=800&quot; width=&quot;538&quot; height=&quot;373&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Oh well. &lt;/p&gt;  &lt;p&gt;&lt;strong&gt;UPDATE 2: &lt;/strong&gt;I e-mailed them a photo of the receipt on Monday morning at 10am; they e-mailed me a CD key at noon. I wonder whether they actually verify the proof-of-purchase details against the retailers’ records or are just under instructions to give out CD keys to anyone with a convincing-looking receipt… &lt;/p&gt;  </description>
          <pubDate>2012-10-19T21:47:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2012/10/19/ripping-off-paying-customers-ubisoft-way.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2012/10/19/ripping-off-paying-customers-ubisoft-way.html</guid>
        </item>
      
    
      
        <item>
          <title>SECURITY_DENIED_BY_MIMEMAP Hosting WCF Services in IIS 7 Classic Mode</title>
          <description>&lt;p&gt;Another one of those gotchas where the error message doesn&apos;t actually have anything to do with the problem...&lt;/p&gt;  &lt;p&gt;One of our apps includes a WCF service endpoint that&apos;s called by some of our other systems. This works perfectly in production, but when I fired it up locally this morning to fix a bug, I just couldn&apos;t get the thing to run properly - the right handlers were mapped, the file permissions were OK, but every time I pointed a browser at http://www.mysite.com.local/some_app/endpoint.svc , I got an HTTP 403 Forbidden.&lt;/p&gt;  &lt;p&gt;Turning on IIS failed request tracing gave me one of those incredibly detailed XML dump files:&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh5.ggpht.com/-P1dGJHUSTcU/UC90gIIxzeI/AAAAAAAAALs/ipvWBucTdb0/s1600-h/image%25255B3%25255D.png&quot;&gt;&lt;img style=&quot;background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh6.ggpht.com/-TcAgrRN8tH8/UC90g0crRfI/AAAAAAAAALw/vTUC5l1Wqv4/image_thumb%25255B1%25255D.png?imgmax=800&quot; width=&quot;644&quot; height=&quot;304&quot; /&gt;&lt;/a&gt;So... SECURITY_DENIED_BY_MIMEMAP. Which is a little odd, because I was pretty sure MIME mapping only applied to static content - but sure enough, I tried setting up a MIME mapping for .svc files - mapped to application/xml. This didn&apos;t work - IIS just sent me the .svc definition file instead of actually executing it...&lt;/p&gt;  &lt;p&gt;Turns out that the problem is our production servers are 32-bit and our development boxes are x64 - and I hadn&apos;t gone into the IIS Application Pool for this app and enabled 32-bit applications. &lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh5.ggpht.com/-HtHp5HqBOgQ/UC90hlBDT6I/AAAAAAAAAL8/VjkesbSkIkg/s1600-h/image%25255B12%25255D.png&quot;&gt;&lt;img style=&quot;background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; margin-left: auto; border-top: 0px; margin-right: auto; border-right: 0px; padding-top: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh6.ggpht.com/-5ki9_UDRCIA/UC90iQcphII/AAAAAAAAAME/QDpmUeApA-k/image_thumb%25255B6%25255D.png?imgmax=800&quot; width=&quot;454&quot; height=&quot;554&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Quite why this results in the misleading MIME type error is one of those little mysteries that makes IIS such fun to work with, but that solved it for me.&lt;/p&gt;  </description>
          <pubDate>2012-08-18T10:55:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2012/08/18/securitydeniedbymimemap-hosting-wcf.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2012/08/18/securitydeniedbymimemap-hosting-wcf.html</guid>
        </item>
      
    
      
        <item>
          <title>Planning Poker… with LOLCATS</title>
          <description>&lt;p&gt;So… there’s this thing called &lt;a href=&quot;http://en.wikipedia.org/wiki/Planning_poker&quot;&gt;planning poker&lt;/a&gt;, which although it looks a little odd at first, is actually a really effective way to flush out uncertainty during software planning meetings. It’s played with a special deck of cards, and for the last year or two we’ve been using a couple of decks that I got at a &lt;a href=&quot;http://skillsmatter.com/&quot;&gt;SkillsMatter&lt;/a&gt; event.&lt;/p&gt;  &lt;p&gt;Now, in &lt;strong&gt;normal&lt;/strong&gt; poker, all the cards need to be from the same deck – otherwise you could read the card backs. Obvious. But with planning poker, only the cards in your own hand need to be the same – there’s no reason why &lt;strong&gt;your&lt;/strong&gt; deck needs to be the same as the other people in the planning session – so there’s nothing stopping you making your own planning poker cards. So, thanks to a bit of interwebbing, a little bit of &lt;a href=&quot;http://www.imagemagick.org/script/index.php&quot;&gt;magick&lt;/a&gt; and those wonderful people at MOO.com, I give you my one-of-a-kind LOLcat planning poker deck.&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh5.ggpht.com/-sDyaVrYaGzg/UBvF2XL0k-I/AAAAAAAAALU/jT8iZBPBOiE/s1600-h/image%25255B6%25255D.png&quot;&gt;&lt;img style=&quot;background-image: none; border-bottom: 0px; border-left: 0px; margin: 10px auto; padding-left: 0px; padding-right: 0px; display: block; float: none; border-top: 0px; border-right: 0px; padding-top: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh5.ggpht.com/-S2sShwP-zWw/UBvF4W_km3I/AAAAAAAAALc/-kuh1jCnbBc/image_thumb%25255B4%25255D.png?imgmax=800&quot; width=&quot;559&quot; height=&quot;500&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Whilst I’m here, I want to say a public thanks to MOO for their outstanding customer service… I got this e-mail from them on Tuesday:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font size=&quot;2&quot;&gt;&lt;em&gt;Dear Dylan,&lt;/em&gt;&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font size=&quot;2&quot;&gt;&lt;em&gt;I&apos;m afraid we have been experiencing some problems rendering your order. I think I have managed to fix it now, but we had to enlist the help of our developers to ascertain the cause of the problems. As I can see these are Agile estimation cards, I thought I&apos;d share the more complex development solution to the problem :-)&lt;/em&gt;&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font size=&quot;2&quot;&gt;&lt;em&gt;Basically, the PNGs are a bit odd: all but the first two have offsets that position our canvas outside of the actual image. Our cropping implementation obeys the offsets, and finds that cropping produces a 0 width image. This is bad. &lt;/em&gt;&lt;/font&gt;&lt;font size=&quot;2&quot;&gt;&lt;em&gt;You&apos;ll find that if you open the images with GIMP or, probably, Photoshop it&apos;ll report that something a bit odd is going on. Unfortunately some software (browsers, the flash) either helpfully ignore the duff offsets, or just don&apos;t support offsets at all (so mask the problem.)&lt;/em&gt;&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font size=&quot;2&quot;&gt;&lt;em&gt;Now for some guess work, the offsets in the PNGs suggest that they were originally all part of one large (2080x2340) image. Did you perhaps use an automated process to extract the images, to make them suitable for uploading to Moo? &lt;/em&gt;&lt;/font&gt;&lt;font size=&quot;2&quot;&gt;&lt;em&gt;The obvious way to achieve this using ImageMagick is to use the -extract command, but with pngs this has an odd side effect: it gives you an output image of the size you asked for, but sets the canvas to the width and height of the original image, with the offset as specified in the extract command. This is not very helpful.&lt;/em&gt;&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font size=&quot;2&quot;&gt;&lt;em&gt;I hope that this all makes sense and helps to avoid this problem in the future. I have also upgraded your order to rush printing, and they will be shipped tomorrow.&lt;/em&gt;&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font size=&quot;2&quot;&gt;&lt;em&gt;If you have any questions, please do not hesitate to get in touch by replying to this email.&lt;/em&gt;&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;They are absolutely spot-on – the way I made the cards was to start off with a 2080x2340 PNG file, use ImageMagick to crop it into 4x3 cards, and then upload the cropped tiles to MOO for printing. So, if you fancy getting your own deck printed, here’s what you’ll need to do:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Download this &lt;a href=&quot;https://dl.dropbox.com/u/7543760/template.png&quot;&gt;template.png&lt;/a&gt; – it’s a huge PNG with transparent windows for you to arrange your images of choice behind the frame on each card. (No, you can’t have my LOLCAT pictures. Mainly because I don’t own the copyright, but also ‘cos choosing your own pictures is half the fun)&lt;/li&gt;    &lt;li&gt;Put photographs in it, tweak it to suit, flatten it, and save it as agile_cards.png – this should be a single 2080x2340 PNG file&lt;/li&gt;    &lt;li&gt;Download ImageMagick if you don’t have it already.&lt;/li&gt;    &lt;li&gt;Use ImageMagick’s convert.exe to slice your file into cards:     &lt;br /&gt;      &lt;br /&gt;&lt;font face=&quot;Consolas&quot;&gt;D:\lolcards&amp;gt;C:\Program Files\ImageMagick\convert.exe –crop 520x780 agile_cards.png card%02d.png       &lt;br /&gt;&lt;/font&gt;&lt;/li&gt;    &lt;li&gt;Use ImageMagick to strip the offset from the individual card files – this is the bit that’ll stop you having the same problem I had above:     &lt;br /&gt;      &lt;br /&gt;&lt;font face=&quot;Consolas&quot;&gt;D:\lolcards&amp;gt;C:\Program Files\ImageMagick\mogrify.exe +repage card*.png       &lt;br /&gt;&lt;/font&gt;&lt;/li&gt;    &lt;li&gt;Design an image for your card back&lt;/li&gt;    &lt;li&gt;Head over to &lt;a href=&quot;http://www.moo.com&quot;&gt;MOO.com&lt;/a&gt;, and use your agile card pictures to order a pack of rounded-corner business cards. I only used ten of the card images, so a pack of 50 cards includes five full sets of cards – with their classic paper and rounded corners, my deck was £13.49 + postage and VAT.&lt;/li&gt;    &lt;li&gt;Wait for the postman&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;Who knows… maybe personalized planning decks will become the software developer equivalent of personalised guitar picks or golf balls… ? Or maybe they’re just fun. Mine certainly brought a few smiles to people’s faces this morning. &lt;/p&gt;  </description>
          <pubDate>2012-08-03T12:36:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2012/08/03/planning-poker-with-lolcats.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2012/08/03/planning-poker-with-lolcats.html</guid>
        </item>
      
    
      
        <item>
          <title>Cross-Domain Forms Authentication with ASP.NET</title>
          <description>&lt;p&gt;I&apos;m looking into splitting bits of a web application across multiple domains, so that - for example - all logins, password changes, etc. are handled at &lt;a href=&quot;http://security.example.com/&quot;&gt;http://security.example.com/&lt;/a&gt;, payments and order history are handled at &lt;a href=&quot;http://accounts.example.com/&quot;&gt;http://accounts.example.com/&lt;/a&gt; and so on. Just to keep things interesting, some of these apps are in .NET 2.0 and some are greenfield apps we&apos;ll be building in .NET 4.0.&lt;/p&gt;  &lt;p&gt;Good news is - it&apos;s completely possible. And with IIS 7, it&apos;s apparently even &lt;a href=&quot;http://learn.iis.net/page.aspx/244/how-to-take-advantage-of-the-iis-integrated-pipeline/#htmlrewrite_content_rewrite&quot;&gt;possible to secure static pages, classic ASP and other non-.NET pages&lt;/a&gt; using the same authentication mechanism.&lt;/p&gt;  &lt;h4&gt;Step 1: Make sure your machineKey values are identical&lt;/h4&gt;  &lt;p&gt;Each application that&apos;s going to participate in cross-domain authentication needs an identical machineKey element - this defines the encryption keys that are used to encrypt and decrypt the .ASPXAUTH cookie that contains the authenticated user data.&lt;/p&gt;  &lt;p&gt;In my solution, I generated a machineKey using &lt;a href=&quot;http://aspnetresources.com/tools/machineKey&quot;&gt;http://aspnetresources.com/tools/machineKey&lt;/a&gt; and then added this to the web.config files for each application individually - it goes under &amp;lt;system.web&amp;gt; like:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;&amp;lt;system.web&amp;gt;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &amp;lt;machineKey validationKey=&amp;quot;&lt;em&gt;&lt;font color=&quot;#cccccc&quot;&gt;yada yada yada&lt;/font&gt;&lt;/em&gt;&amp;quot; decryptionKey=&amp;quot;&lt;font color=&quot;#cccccc&quot;&gt;&lt;em&gt;yada yada yada&lt;/em&gt;&lt;/font&gt;&amp;quot; validation=&amp;quot;SHA1&amp;quot; decryption=&amp;quot;AES&amp;quot; /&amp;gt;        &lt;br /&gt;&amp;lt;/system.web&amp;gt;&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;In theory, you could also define this in your machine.config or machine-level web.config file - remembering that you may have up to four of these (x86 .NET 20, x86 .NET 4.0, x64 .NET 2.0 and x64 .NET 4.0) - but I haven&apos;t tested this approach.&lt;/p&gt;  &lt;h4&gt;Step 2: Configure your FormsAuthentication attributes&lt;/h4&gt;  &lt;p&gt;Add this to the &amp;lt;system.web&amp;gt; section of the applications that are going to participate in cross-domain authentication:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;&amp;lt;authentication mode=&amp;quot;Forms&amp;quot;&amp;gt;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &amp;lt;forms loginUrl=&amp;quot;&lt;/font&gt;&lt;a href=&quot;https://security.example.com/login&amp;quot;&quot;&gt;&lt;font face=&quot;Consolas&quot;&gt;https://security.example.com/login&amp;quot;&lt;/font&gt;&lt;/a&gt;&lt;font face=&quot;Consolas&quot;&gt; timeout=&amp;quot;2880&amp;quot; domain=&amp;quot;.example.com&amp;quot; path=&amp;quot;/&amp;quot; /&amp;gt;       &lt;br /&gt;&amp;lt;/authentication&amp;gt;&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Key points to note here:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;The loginUrl is an absolute reference to the site that hosts your security app - so if you try to view a secure page, it&apos;ll redirect you to this website to log in.&lt;/li&gt;    &lt;li&gt;The cookie domain is set to &amp;quot;.example.com&amp;quot; = &lt;em&gt;notice the leading dot&lt;strong&gt;. &lt;/strong&gt;&lt;/em&gt;That&apos;s important - it means the same cookie will be sent to example.com, something.example.com, another.url.at.example.com, and so on.&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;You&apos;ll need to make sure you&apos;re calling FormsAuthentication.SetAuthCookie(username) on your login server, but once that&apos;s done, you should be able to retrieve HttpContext.Current.User.Identity.Name from anywhere on any participating server. It is still up to you to verify that the username is valid and authorized - but this should save you a bit of plumbing when it comes to getting things working.&lt;/p&gt;  </description>
          <pubDate>2012-03-30T12:55:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2012/03/30/cross-domain-forms-authentication-with.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2012/03/30/cross-domain-forms-authentication-with.html</guid>
        </item>
      
    
      
        <item>
          <title>Snowcode 2012</title>
          <description>&lt;p&gt;&lt;a title=&quot;Snowcode 2012 on meetup.com&quot; href=&quot;http://www.camdug.com/events/49752952/&quot;&gt;&lt;img style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; float: right; padding-top: 0px; padding-left: 0px; margin: 0px 0px 10px 10px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; src=&quot;http://photos2.meetupstatic.com/photos/event/7/7/b/2/global_88650642.jpeg&quot; align=&quot;right&quot;&gt;&lt;/a&gt;Snowcode is an awesome combination of skiing holiday, unconference and hack camp. Snowcode was started in 2010 as an experiment, organized by &lt;a href=&quot;http://uk.linkedin.com/in/goblinfactory&quot;&gt;Alan Hemmings&lt;/a&gt; whom you may know from the &lt;a href=&quot;http://www.camdug.com/&quot;&gt;Cambridge .NET User Group&lt;/a&gt;. We did it again in 2011, and in the spirit of continuous improvement, we&apos;re making a couple of changes to the format this year. We have a new venue - &lt;a href=&quot;http://www.rudechalets.com/winter-holidays/morzine/chalet-joseph/&quot;&gt;Chalet Joseph&lt;/a&gt; in Morzine - and we&apos;re going to try a more focused approach to the &quot;code&quot; part of Snowcode. Previously, we&apos;ve used an &quot;anything goes&quot; unconference format, which has led to workshops on OpenRasta and REST frameworks, WaTIR, ASP.NET MVC, MongoDB, automated acceptance testing, and lots of good code and great ideas. &lt;/p&gt; &lt;p&gt;This year, it&apos;s JavaScript all the way - from deep-diving into the obscure corners of pure JavaScript to the latest JS frameworks and tools such as &lt;a href=&quot;http://nodejs.org/&quot;&gt;node.js&lt;/a&gt;, &lt;a href=&quot;http://coffeescript.org/&quot;&gt;CoffeeScript&lt;/a&gt;, &lt;a href=&quot;http://backbonejs.org/&quot;&gt;Backbone&lt;/a&gt;, &lt;a href=&quot;http://knockoutjs.com/&quot;&gt;Knockout&lt;/a&gt; - and, of course, &lt;a href=&quot;http://jquery.com/&quot;&gt;jQuery&lt;/a&gt;. We&apos;ll discuss them, we&apos;ll play with them, and we&apos;ll work out fun ways to glue them together to build apps, websites, services and games. &lt;/p&gt; &lt;p&gt;One of the things we&apos;ve struggled with on previous Snowcode events is sustaining the &lt;a href=&quot;http://en.wikipedia.org/wiki/Unconference&quot;&gt;unconference&lt;/a&gt; format over an entire week, and so I&apos;m really excited to announce that Alan has invited &lt;a href=&quot;http://uk.linkedin.com/in/petermounce&quot;&gt;Pete Mounce&lt;/a&gt; and I to coordinate the &quot;code&quot; part of Snowcode 2012. Pete and I are preparing a programme that will provide a little more structure to the week, but still be modular enough that people can dip in and out, participating in the bits that interest them. If you&apos;re exhausted from a day on the snow and want to skip a session, that&apos;s fine - and if we find ourselves going off on interesting tangents - no problem. We&apos;re here to have fun and to learn. (And to ski, of course.)&lt;/p&gt; &lt;p&gt;&lt;a style=&quot;overflow: hidden; height: 140px; text-align: center; display: block&quot; href=&quot;http://lh5.ggpht.com/-28uXBeHSLDU/Tz0t3ek2QHI/AAAAAAAAAJ8/vJ0T_IDBoX4/s1600-h/Untitled-112.png&quot;&gt;&lt;img title=&quot;Untitled-1&quot; style=&quot;border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px&quot; border=&quot;0&quot; alt=&quot;Untitled-1&quot; src=&quot;http://lh5.ggpht.com/-m0PvDy2q3ok/Tz0t4cpSj4I/AAAAAAAAAKE/byilvHVeQLA/Untitled-1_thumb4.png?imgmax=800&quot; width=&quot;800&quot; height=&quot;140&quot;&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;Every Snowcode is different, but a typical day will go a little something like this:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;Wake up &lt;/strong&gt;(a mild hangover is optional but not unlikely)  &lt;li&gt;&lt;strong&gt;Breakfast &lt;/strong&gt;- coffee, fresh bread, croissants, fresh juice... and a dozen or so people sat around laptops doing a quick bit of coding or catching up on Facebook or giggling at the pictures from dinner last night.  &lt;li&gt;&lt;strong&gt;Hit the slopes &lt;/strong&gt;- if that&apos;s your thing. Morzine is in the Portes du Soleil ski area, with access to over 600km of stunning runs and spectacular scenery. The sun is shining, there&apos;s fresh powder on the ground, and your hangover&apos;s gone by the time you hit the bottom of the first run.  &lt;li&gt;&lt;strong&gt;Break for lunch &lt;/strong&gt;- normally an opportunity to rendezvous with other Snowcoders and swap notes on the mornings&apos; ski runs  &lt;li&gt;&lt;strong&gt;Ski some more&lt;/strong&gt; - or snowboard, or head back to the chalet for a lazy afternoon&apos;s coding, reading or napping...  &lt;li&gt;&lt;strong&gt;Coding &amp;amp; &quot;geeking out&quot; &lt;/strong&gt;- get back to the chalet, have a shower, open a beer and a laptop, and spend a couple of hours hacking, learning, coding, talking and generally working out cool ways to build cool stuff.  &lt;li&gt;&lt;strong&gt;Dinner&lt;/strong&gt; - three courses, wine, stories, the inevitable jokes at Alan&apos;s expense, that kind of thing...  &lt;li&gt;&lt;strong&gt;...and then whatever you like! &lt;/strong&gt;Wander into town and go clubbing; watch movies, play X-box, more hacking, or just crash out early and get ready to do it all over again. A jaunt into Morzine is well worth it - it&apos;s a fantastic town with some really good bars, and the Mutzig beer is practically a Snowcode rite of passage. &lt;/li&gt;&lt;/ul&gt; &lt;p&gt;You should come. No, really, you should. It&apos;ll be a fantastic week, and we&apos;d love to see you there. If you ski or snowboard, you&apos;ll love it. If you code, you&apos;ll love it. And if you&apos;re not a skier or a coder, you&apos;re still more than welcome - it&apos;s a great opportunity to learn to do either (or both!)&lt;/p&gt; &lt;p&gt;Full details of the event are over on the &lt;a href=&quot;http://www.camdug.com/events/49752952/&quot;&gt;SNOWCODE 2012 page on meetup.com&lt;/a&gt; - but in a nutshell:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;It&apos;s in Morzine, France, from 18-25 March 2012.  &lt;li&gt;Book your place via the &lt;a href=&quot;http://www.camdug.com/events/49752952/&quot;&gt;SNOWCODE 2012 page on meetup.com&lt;/a&gt; - Snowcode costs £569 per person including half-board accommodation and airport transfers.  &lt;li&gt;Get a cheap flight to Geneva with Easyjet (don&apos;t forget to book your skis as extra baggage)  &lt;li&gt;Don&apos;t forget your laptop... &lt;/li&gt;&lt;/ul&gt; &lt;hr&gt;  &lt;p align=&quot;center&quot;&gt;&lt;font color=&quot;#a6cee1&quot; face=&quot;Constantia&quot;&gt;&lt;font size=&quot;6&quot; face=&quot;Times New Roman&quot;&gt;&lt;font color=&quot;#3a89af&quot;&gt;&quot;&lt;/font&gt; &lt;/font&gt;when&lt;font color=&quot;#529fc5&quot;&gt; I&lt;/font&gt; &lt;font color=&quot;#3a89af&quot; size=&quot;5&quot;&gt;&lt;strong&gt;ski, &lt;/strong&gt;&lt;/font&gt;&amp;nbsp;&lt;font color=&quot;#529fc5&quot;&gt;I&lt;/font&gt; &lt;strong&gt;&lt;font color=&quot;#3a89af&quot; size=&quot;4&quot;&gt;&lt;font size=&quot;5&quot;&gt;live&lt;/font&gt;... &lt;/font&gt;&lt;/strong&gt;the &lt;font size=&quot;4&quot;&gt;&lt;font color=&quot;#529fc5&quot;&gt;rest is just &lt;font size=&quot;5&quot;&gt;waiting&lt;/font&gt;&lt;/font&gt; &lt;/font&gt;&lt;font color=&quot;#3a89af&quot; size=&quot;6&quot; face=&quot;Times New Roman&quot;&gt;&quot;&lt;/font&gt;&lt;/font&gt;&lt;/p&gt; &lt;hr&gt;  &lt;div id=&quot;scid:66721397-FF69-4ca6-AEC4-17E6B3208830:1787d2d8-11bb-4f82-b141-bc0107249fe4&quot; class=&quot;wlWriterSmartContent&quot; style=&quot;width: 417px; float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px auto; display: block; padding-right: 0px&quot;&gt; &lt;table style=&quot;border-top-style: none; border-left-style: none; width: 417px; border-collapse: collapse; border-bottom-style: none; padding-bottom: 0px; padding-top: 0px; border-right-style: none; outline-style: none; padding-left: 0px; margin: 0px; padding-right: 0px&quot; cellspacing=&quot;0&quot; cellpadding=&quot;0&quot; border=&quot;0&quot;&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td style=&quot;border-top-style: none; border-left-style: none; width: auto; border-bottom-style: none; padding-bottom: 0px; padding-top: 0px; border-right-style: none; outline-style: none; padding-left: 0px; margin: 0px; padding-right: 0px&quot;&gt;&lt;a style=&quot;border-top-style: none; border-left-style: none; border-bottom-style: none; padding-bottom: 0px; padding-top: 0px; border-right-style: none; outline-style: none; padding-left: 0px; margin: 0px; padding-right: 0px&quot; href=&quot;https://skydrive.live.com/redir.aspx?cid=111f53f7296891f3&amp;amp;page=play&amp;amp;resid=111F53F7296891F3!109&amp;amp;type=5&amp;amp;authkey=!AGrYocoFolP04SI&amp;amp;Bsrc=Photomail&amp;amp;Bpub=SDX.Photos&quot; target=&quot;_blank&quot;&gt;&lt;img title=&quot;View album&quot; style=&quot;border-top: 0px; border-right: 0px; vertical-align: bottom; background: none transparent scroll repeat 0% 0%; border-bottom: 0px; padding-bottom: 0px; padding-top: 0px; outline-style: none; padding-left: 0px; border-left: 0px; margin: 0px; padding-right: 0px&quot; alt=&quot;View album&quot; src=&quot;http://lh4.ggpht.com/-vxM20KJswFc/Tz0t5Clu2PI/AAAAAAAAAKI/RidamPgzqqo/Snowcode%2525202011%25255B20%25255D.jpg?imgmax=800&quot;&gt;&lt;/a&gt; &lt;div style=&quot;overflow: visible; width: 417px; padding-bottom: 0px; text-align: center; padding-top: 0px; padding-left: 0px; margin: 0px; padding-right: 0px&quot;&gt; &lt;div style=&quot;overflow: visible; width: 417px&quot;&gt;&lt;a style=&quot;text-decoration: none&quot; href=&quot;https://skydrive.live.com/redir.aspx?cid=111f53f7296891f3&amp;amp;page=browse&amp;amp;resid=111F53F7296891F3!109&amp;amp;type=5&amp;amp;authkey=!AGrYocoFolP04SI&amp;amp;Bsrc=Photomail&amp;amp;Bpub=SDX.Photos&quot; target=&quot;_blank&quot;&gt;&lt;span style=&quot;font-size: 26pt; font-family: &apos;Segoe UI&apos;, helvetica, arial, sans-serif; width: 417px; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; line-height: 1.26em; padding-right: 0px&quot; defaulttext=&quot;Enter album name here&quot;&gt;Snowcode 2010&lt;/span&gt;&lt;/a&gt;&lt;/div&gt; &lt;div style=&quot;font-size: 8pt; font-family: &apos;Segoe UI&apos;, helvetica, arial, sans-serif; padding-bottom: 0px; text-align: center; padding-top: 9px; padding-left: 0px; margin: 0px; padding-right: 0px&quot;&gt; &lt;table style=&quot;border-top-style: none; border-left-style: none; width: auto; border-collapse: collapse; border-bottom-style: none; padding-bottom: 0px; text-align: center; padding-top: 0px; border-right-style: none; outline-style: none; padding-left: 0px; margin-left: auto; padding-right: 0px; margin-right: auto&quot; cellspacing=&quot;0&quot; cellpadding=&quot;0&quot; border=&quot;0&quot;&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td style=&quot;border-top-style: none; border-left-style: none; vertical-align: top; border-bottom-style: none; padding-bottom: 6px; padding-top: 6px; border-right-style: none; outline-style: none; padding-left: 0px; margin: 0px; padding-right: 12px&quot;&gt;&lt;a style=&quot;border-top-style: none; font-size: 8pt; text-decoration: none; border-left-style: none; font-family: &apos;Segoe UI&apos;, helvetica, arial, sans-serif; border-bottom-style: none; padding-bottom: 0px; padding-top: 0px; border-right-style: none; outline-style: none; padding-left: 0px; margin: 0px; padding-right: 0px&quot; href=&quot;https://skydrive.live.com/redir.aspx?cid=111f53f7296891f3&amp;amp;page=play&amp;amp;resid=111F53F7296891F3!109&amp;amp;type=5&amp;amp;authkey=!AGrYocoFolP04SI&amp;amp;Bsrc=Photomail&amp;amp;Bpub=SDX.Photos&quot; target=&quot;_blank&quot; border=&quot;0&quot;&gt;VIEW SLIDE SHOW&lt;/a&gt;&lt;/td&gt; &lt;td style=&quot;border-top-style: none; border-left-style: none; vertical-align: top; border-bottom-style: none; padding-bottom: 6px; padding-top: 6px; border-right-style: none; outline-style: none; padding-left: 0px; margin: 0px; padding-right: 0px&quot;&gt;&lt;a style=&quot;border-top-style: none; font-size: 8pt; text-decoration: none; border-left-style: none; font-family: &apos;Segoe UI&apos;, helvetica, arial, sans-serif; border-bottom-style: none; padding-bottom: 0px; padding-top: 0px; border-right-style: none; outline-style: none; padding-left: 0px; margin: 0px; padding-right: 0px&quot; href=&quot;https://skydrive.live.com/redir.aspx?cid=111f53f7296891f3&amp;amp;page=downloadphotos&amp;amp;resid=111F53F7296891F3!109&amp;amp;type=5&amp;amp;Bsrc=Photomail&amp;amp;Bpub=SDX.Photos&amp;amp;authkey=!AGrYocoFolP04SI&quot; target=&quot;_blank&quot; border=&quot;0&quot;&gt;DOWNLOAD ALL&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;</description>
          <pubDate>2012-02-16T16:25:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2012/02/16/snowcode-2012.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2012/02/16/snowcode-2012.html</guid>
        </item>
      
    
      
        <item>
          <title>GiveCamp UK 2011 – A Retrospective</title>
          <description>&lt;p&gt;I spent this last weekend at UCL’s Bloomsbury campus in London, with a hundred or so charitable geeks, at the first UK GiveCamp. I came away from it amazed – at the generosity of the volunteers and sponsors alike; at the sophistication of some of the solutions that were delivered within a single weekend, and at the reactions from the charities involved when we presented our projects on Sunday afternoon. It was a wonderful experience, and one I’d happily do again, but in the spirit of agile and continuous improvement, here’s my own personal retrospective on GiveCamp UK 2011.&lt;/p&gt;  &lt;h3&gt;What went well?&lt;/h3&gt;  &lt;ul&gt;   &lt;li&gt;The project. I was lucky enough to be working with a team who were helping &lt;a href=&quot;http://www.sceneandheard.org/&quot;&gt;Scene and Heard&lt;/a&gt;, a mentoring project based in north London that arranges for children to work with volunteer theatre professionals to write and perform plays. Simma , their Head of Development, showed up on Friday with a great pitch, obvious enthusiasm, a wealth of knowledge about the domain, and a “wish list” of projects and ideas for us to investigate. Top of the list was a ticket booking system – and I was immensely heartened when our entire team of coders – you know, people who like to &lt;em&gt;build&lt;/em&gt; stuff – unanimously agreed that the pragmatic solution would be to hook them up with &lt;a href=&quot;http://www.eventbrite.com/&quot;&gt;EventBrite&lt;/a&gt;. By 8pm on Friday, we’d drawn up a backlog of requirements, made sure EventBrite would actually deliver what they needed, and a couple of the team were already working on the integration. This left the rest of us free to start looking at some of the more blue-sky ideas on Simma’s list… and somehow by Saturday afternoon we were building a full relational database, MVC web front-end, and a set of data migration and normalisation routines to import their data from a collection of Access and Excel sheets into SQL Server. It’s not quite live yet – we’re having some problems getting the Entity Framework config code to work with &lt;a href=&quot;http://www.appharbor.com/&quot;&gt;AppHarbor&lt;/a&gt;’s SQL Server databases – but by the end of the week we hope to have it online, hosted, and handed over to them, for immediate use, or as a solid platform for future development. &lt;/li&gt;    &lt;li&gt;The venue. The power worked. There was ample desk space and seating. The wi-fi was pretty solid – for most of the weekend we were actually using their wi-fi network for all our database access and development, as well as simple web browsing &amp;amp; e-mail, and other than an occasional IP address change, it worked. &lt;/li&gt;    &lt;li&gt;The catering. The food was excellent, there were ample supplies of drinks, snacks, awesome tea thanks to &lt;a href=&quot;http://www.teapigs.co.uk/&quot;&gt;Teapigs&lt;/a&gt;, fresh fruit and enough Haribo to keep you wired and coding well into the small hours. Result. &lt;/li&gt;    &lt;li&gt;The wrap-up. Without Paul &amp;amp; Phil imposing a strict code cut-off, we’d have happily coded until they threw us out – but in retrospect, having a hard deadline with plenty of notice really focused our efforts towards the end of the project. &lt;/li&gt;    &lt;li&gt;The platforms. We used &lt;a href=&quot;http://www.github.com/&quot;&gt;Github&lt;/a&gt; for hosting, &lt;a href=&quot;https://trello.com/&quot;&gt;Trello&lt;/a&gt; for organising our backlogs and a whole lot more besides; EventBrite solved the ticketing problem, and we’re in the process of deploying to AppHarbor. All fantastic, powerful tools – and all completely free. &lt;/li&gt;    &lt;li&gt;The swag. 80Gb SSDs for all the attendees? Best. Swag. EVER. Plus a hugely generous range of licenses, software, books and ebooks. Check out the list of sponsors at &lt;a href=&quot;http://www.givecamp.org.uk/sponsors&quot;&gt;http://www.givecamp.org.uk/sponsors&lt;/a&gt; – I’m hugely grateful to every single one of them for making this amazing event a reality. &lt;/li&gt;    &lt;li&gt;The people. Everyone was helpful, cooperative, collaborative and enthusiastic, and I hope everyone learned as much working with each other as I did working with them. &lt;/li&gt;    &lt;li&gt;The demos. I was really, really impressed at what the teams delivered. From discovery to implementation to – in some cases – deployment onto a live website, in under 48 hours… as Ben Hall put it, it “makes you wonder what we normally &lt;em&gt;do&lt;/em&gt; all day…” I am also not at all biased by how genuinely delighted Simma and Jasmine looked when we showed them our work. Honest. &lt;/li&gt; &lt;/ul&gt;  &lt;h3&gt;What could have gone better?&lt;/h3&gt;  &lt;ul&gt;   &lt;li&gt;I thought the Friday start was too early. Lots of people – including several of the charities and one of the organisers – were held up in transit and missed the opening pitches and kick-off. The only downside of a venue as wonderfully accessible as UCL Bloomsbury is that at 5pm on a Friday it’s smack in the middle of the worst rush hour in the country. Just an idea – but I’d suggest next time a preliminary session, maybe earlier in the week, with the team leaders and the charity reps? A solid couple of hours capturing requirements and understanding the domain. We’d have been up to speed much quicker if one or two of us had had the chance to think things over ahead of time. &lt;/li&gt;    &lt;li&gt;That’s actually the only criticism I have of the event itself. The rest are memos to myself and the team for next time:      &lt;ul&gt;       &lt;li&gt;Bring an ergonomic keyboard! By Sunday afternoon my arms were starting to seize up… I’m not a laptop coder by choice, and having one of my trusty MS Natural 4000s would have made a *huge* difference. Plus a proper mouse. And a second monitor. &lt;/li&gt;        &lt;li&gt;Stick to what you know. This is not an event for trying out new technology – unless someone on your team knows it well, don’t go near it. We spent a good half-day investigating Visual Studio Lightswitch before concluding that we just didn’t have the expertise to tailor it to our requirements… it was gone 3pm on Saturday when we finally settled on ASP.NET MVC 3 with Entity Framework, and an hour later we were absolutely &lt;em&gt;flying&lt;/em&gt;. &lt;/li&gt;        &lt;li&gt;Get a Skype chat or IRC channel set up ASAP. You’re going to be sharing lots of addresses, API keys, URLs – stuff that’s time-consuming to read out loud or write on paper. &lt;/li&gt;     &lt;/ul&gt;   &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;In conclusion – wonderful event, I’m really pleased to have been part of it, and I hope the rest of our team will be at the next one because I’d love to work with them again some time. And huge thanks to Paul Stack, Rachel Hawley, Phil Winstanley, Kendal Miller and Dave Sussman, who all looked absolutely worn out by Sunday evening – it wouldn’t have happened without you!&lt;/p&gt;  </description>
          <pubDate>2011-10-25T23:52:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2011/10/25/givecamp-uk-2011-retrospective.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2011/10/25/givecamp-uk-2011-retrospective.html</guid>
        </item>
      
    
      
        <item>
          <title>Moleskine + Kindle = ... Moleskindle?</title>
          <description>&lt;p&gt;Faced with the harrowing prospect of trying to fit the entire &lt;a href=&quot;http://rcm-uk.amazon.co.uk/e/cm?lt1=_blank&amp;amp;bc1=000000&amp;amp;IS2=1&amp;amp;bg1=FFFFFF&amp;amp;fc1=000000&amp;amp;lc1=0000FF&amp;amp;t=dylanbeattieb-21&amp;amp;o=2&amp;amp;p=8&amp;amp;l=as4&amp;amp;m=amazon&amp;amp;f=ifr&amp;amp;ref=ss_til&amp;amp;asins=B0050AV5MW&quot;&gt;Song of Ice and Fire&lt;/a&gt; into hand luggage on my next holiday, I bought a &lt;a href=&quot;http://www.amazon.co.uk/gp/product/B002Y27P46/ref=as_li_ss_tl&quot;&gt;Kindle&lt;/a&gt;. It&apos;s quite magic. It won&apos;t switch off, it doesn&apos;t light up, it looks utterly fake - like some sort of plastic prop tablet device where they&apos;ve used a printed cardboard screen... and it&apos;s absolutely lovely. My paperback books tend to end up rather battered from being slung around in bags all the time, and I wanted a case to keep the Kindle safe from knocks and scratches. Rather than spend money on one of the ridiculously overpriced cases you can get for it, I wanted to try something a bit different. You remember reading spy books as a kid where people would hide stuff inside hollowed-out books? &lt;/p&gt;  &lt;p&gt;I found an old Moleskine notebook that was just the right size for it, and started hacking away - a couple of happy hours playing with craft-knives and glue, and here it is: the Moleskindle&lt;/p&gt;  &lt;p align=&quot;center&quot;&gt;&lt;a href=&quot;http://lh6.ggpht.com/-NFLyraErxTM/TnS6Bn_LG3I/AAAAAAAAAI0/U4jsSmqWbIg/s1600-h/IMG_8804%25255B8%25255D.jpg&quot;&gt;&lt;img style=&quot;background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px&quot; title=&quot;IMG_8804&quot; border=&quot;0&quot; alt=&quot;IMG_8804&quot; src=&quot;http://lh3.ggpht.com/-7jdlNZyuwf0/TnS6CIxmoaI/AAAAAAAAAI4/RViSVeYzd-Y/IMG_8804_thumb%25255B5%25255D.jpg?imgmax=800&quot; width=&quot;180&quot; height=&quot;140&quot; /&gt;&lt;/a&gt;&amp;#160;&lt;a href=&quot;http://lh6.ggpht.com/-qTz1rOd6WQU/TnS6DFPR1sI/AAAAAAAAAI8/rPq-eMm18rE/s1600-h/IMG_8810%25255B5%25255D.jpg&quot;&gt;&lt;img style=&quot;background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px&quot; title=&quot;IMG_8810&quot; border=&quot;0&quot; alt=&quot;IMG_8810&quot; src=&quot;http://lh3.ggpht.com/-fgZnccBBEB8/TnS6DimQUdI/AAAAAAAAAJA/l7jDQLbYxS4/IMG_8810_thumb%25255B2%25255D.jpg?imgmax=800&quot; width=&quot;180&quot; height=&quot;140&quot; /&gt;&lt;/a&gt;&amp;#160; &lt;br /&gt;&lt;a href=&quot;http://lh6.ggpht.com/-Ih18VYmloZs/TnS6EOBpI-I/AAAAAAAAAJE/3VmHqDlH1u8/s1600-h/IMG_8815%25255B5%25255D.jpg&quot;&gt;&lt;img style=&quot;background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px&quot; title=&quot;IMG_8815&quot; border=&quot;0&quot; alt=&quot;IMG_8815&quot; src=&quot;http://lh6.ggpht.com/-Bl57D-uqjlE/TnS6EZj8ZmI/AAAAAAAAAJI/8ndfiEFYgrc/IMG_8815_thumb%25255B2%25255D.jpg?imgmax=800&quot; width=&quot;180&quot; height=&quot;140&quot; /&gt;&lt;/a&gt;&amp;#160;&lt;a href=&quot;http://lh6.ggpht.com/-BnIeCMPQYDA/TnS6FFWRmHI/AAAAAAAAAJM/v3qkcZdA-SE/s1600-h/IMG_8814%25255B5%25255D.jpg&quot;&gt;&lt;img style=&quot;background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px&quot; title=&quot;IMG_8814&quot; border=&quot;0&quot; alt=&quot;IMG_8814&quot; src=&quot;http://lh3.ggpht.com/-aEkXjPrYb-I/TnS6FXsZzLI/AAAAAAAAAJQ/EFilRV4nOLA/IMG_8814_thumb%25255B2%25255D.jpg?imgmax=800&quot; width=&quot;180&quot; height=&quot;140&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;It&apos;s a bit fiddly - and messy - getting the cutouts just the right shape; I found wood glue worked just fine - and once it&apos;s dried, the compacted glued paper is quite easy to carve &amp;amp; trim using a sharp craft knife. I cut a notch in the right-hand side so I can reach the page-turn buttons whilst it&apos;s in the case, but you need to pop it out to reach the power button or recharge it. Still, I think it looks pretty cool, it&apos;ll stop the Kindle getting knocked and scratched, and you can fool people on the Tube into thinking you&apos;re reading something incredibly intellectual that&apos;s been hand-written in a Moleskine notebook when you&apos;re secretly reading &lt;a href=&quot;http://www.amazon.co.uk/This-Gonna-Hurt-Nikki-Sixx/dp/0062061879/ref=pd_sim_b4&quot;&gt;rock star autobiographies&lt;/a&gt;.&lt;/p&gt;  </description>
          <pubDate>2011-09-17T15:10:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2011/09/17/moleskine-kindle-moleskindle.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2011/09/17/moleskine-kindle-moleskindle.html</guid>
        </item>
      
    
      
        <item>
          <title>Software Development - HORSE-style</title>
          <description>&lt;p&gt;There&apos;s as many ways to lose at poker as there&apos;s ways to fail at delivering software, but one variation I have yet to experience is a game called &lt;a href=&quot;http://www.horsepoker.net/&quot;&gt;HORSE&lt;/a&gt;. In HORSE, each hand follows a different set of rules - you&apos;ll play a hand of Hold&apos;Em, a hand of Omaha, a hand of Razz, a hand of Stud, and a hand of Stud Hi-Lo; then you go back to the beginning and do it all over again, until my brother has all my chips.&lt;/p&gt;  &lt;p&gt;Inspired by this, I&apos;ve devised the following brilliant software methodology for all those teams who can&apos;t quite settle on a system that works for them. It&apos;s called WALKS, and you work in two-week sprints, using a different methodology for each sprint to ensure you get the maximum efficiency from all these wonderful processes and systems:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;strong&gt;Weeks 1-2: Waterfall       &lt;br /&gt;&lt;/strong&gt;You spend the first two weeks making bold, ambitious, big-design-up-front plans, and not actually writing any code or shipping any features.&lt;/p&gt; &lt;/blockquote&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;strong&gt;Weeks 3-4: Agile       &lt;br /&gt;&lt;/strong&gt;You spend the next two weeks trying desperately to get *something* built and releasable.&lt;/p&gt; &lt;/blockquote&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;strong&gt;Weeks 5-6: Lean       &lt;br /&gt;&lt;/strong&gt;Realizing that your &amp;quot;big design&amp;quot; is probably killing your attempts to be agile, you start hacking out unnecessary features and trying to pare the design back to something you might actually be able to build.&lt;/p&gt; &lt;/blockquote&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;strong&gt;Weeks 7-8: Kanban       &lt;br /&gt;&lt;/strong&gt;You still don&apos;t know what you&apos;re doing, so you decide to write everything on Post-It notes and stick them to a board, figuring that if you start pulling jobs off the queue, you might at least get *something* done.&lt;/p&gt; &lt;/blockquote&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;strong&gt;Weeks 9-10: Scrum        &lt;br /&gt;&lt;/strong&gt;You have two weeks of daily stand-up meetings, in a desperate attempt to try and get a handle on things. Finally, you sit down on Friday afternoon, have a two-hour timeboxed retrospective, and decide that what you &lt;em&gt;really &lt;/em&gt;need is a full set of requirements and a definitive spec.&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Then you take a weekend off, come in on Monday, and start at the top again.&lt;/p&gt;  &lt;p&gt;If that sounds familiar, it&apos;s OK - you&apos;re not hopelessly lost, confused or unproductive; you&apos;re just taking a structured approach to being multi-disciplinary...&lt;/p&gt;  &lt;p&gt;&lt;font size=&quot;1&quot;&gt;(This is a joke post. Please don&apos;t use WALKS to build software. Ever. The world has enough problems as it is...)&lt;/font&gt;&lt;/p&gt;  </description>
          <pubDate>2011-09-11T21:31:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2011/09/11/software-development-horse-style.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2011/09/11/software-development-horse-style.html</guid>
        </item>
      
    
      
        <item>
          <title>SkillsMatter Progressive.NET Tutorials 2011</title>
          <description>&lt;p&gt;Some of you may have heard about - or attended - &lt;a href=&quot;http://skillsmatter.com/event/open-source-dot-net/progressive-dot-net-tutorials-2011/wd-2388&quot;&gt;SkillsMatter&apos;s Progressive.NET Tutorials&lt;/a&gt; over the last couple of years. This is a three-day program of in-depth workshops covering the latest languages, frameworks and techniques in .NET development. The great thing about the workshop format is that it provides enough time to actually get some hands-on experience; instead of the rapid-fire 45-minute lectures you&apos;ll find at most conferences, you can actually try things out, ask questions, work through examples, and really get to grips with the techniques or frameworks you&apos;re exploring.&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://skillsmatter.com/event/open-source-dot-net/progressive-dot-net-tutorials-2011/wd-2388&quot;&gt;&lt;img style=&quot;display: block; float: none; margin-left: auto; margin-right: auto&quot; src=&quot;http://skillsmatter.com/nl/prognet/prognet11_banner.gif&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;This year, &lt;a href=&quot;http://codebetter.com/iancooper/&quot;&gt;Ian Cooper&lt;/a&gt; - whom you may know from the &lt;a href=&quot;http://www.dnug.org.uk/&quot;&gt;London .NET User Group&lt;/a&gt; - has put together a great programme of speakers and topics, and I&apos;m really excited to say that, for the first time, I&apos;ll be there as a speaker instead of an attendee. &lt;/p&gt;  &lt;p&gt;My workshop is called &amp;quot;&lt;a href=&quot;http://skillsmatter.com/podcast/open-source-dot-net/front-end-tips-for-back-end-devs&quot;&gt;Front-End Tips for Back-End Devs&lt;/a&gt;&amp;quot;. I&apos;ll be looking at how many of the techniques we take for granted in back-end development - including DRY, abstractions, packaging and dependency management -&amp;#160; can be applied to your page layouts, stylesheets and scripts. We&apos;ll cover semantic markup, we&apos;ll take a whirlwind tour of all the wonderful new tags introduced by HTML5, we&apos;ll look at CSS sprites and media queries, and we&apos;ll see how you can keep your web UI as clean, elegant and maintainable as the rest of your codebase.&lt;/p&gt;  &lt;p&gt;There&apos;s also &lt;a href=&quot;http://skillsmatter.com/expert-profile/ajax-ria/damjan-vujnovic&quot;&gt;Damjan Vujnovic&lt;/a&gt; talking about TDD in JavaScript; I went along to one of Damjan&apos;s talks earlier this year and was really impressed, so I&apos;m looking forward to the opportunity to go over his ideas in a little more depth. &lt;a href=&quot;http://stackoverflow.com/users/22656/jon-skeet&quot;&gt;Jon Skeet&lt;/a&gt; will be talking about &lt;a href=&quot;http://msmvps.com/blogs/jon_skeet/archive/2011/07/28/speaking-engagement-progressive-net-london-september-7th.aspx&quot;&gt;async/await programming on C# 5&lt;/a&gt; (no word yet on whether Tony the Pony will be there). There&apos;s workshops on continuous deployment, on web development in F# using the WebSharper framework, on packaging and dependency management, on REST, on Nancy, on SimpleData - in fact, if you&apos;ve heard the .NET community buzzing about anything in the last year or so, chances are there&apos;s a workshop here that will show you what they&apos;re all so excited about. &lt;/p&gt;  &lt;p&gt;It&apos;s at the &lt;a href=&quot;http://skillsmatter.com/location-details/open-source-dot-net/849/96&quot;&gt;SkillsMatter eXchange&lt;/a&gt; in London, from 5-7th September 2011. The cost is £425 (yes, it&apos;s not free, but you do get three days of top-notch content without giving up your evenings or weekends) - and &lt;strong&gt;you can use the promo code PROGNET50 to get £50 off.&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://skillsmatter.com/event/open-source-dot-net/progressive-dot-net-tutorials-2011&quot;&gt;Sign up online here&lt;/a&gt;. To keep up with event news, follow the hashtag &lt;a href=&quot;http://twitter.com/#!/search/%23prognet&quot;&gt;#prognet&lt;/a&gt; on Twitter, and it&apos;d be great to see you there.&lt;/p&gt;  </description>
          <pubDate>2011-08-01T11:59:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2011/08/01/skillsmatter-progressivenet-tutorials.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2011/08/01/skillsmatter-progressivenet-tutorials.html</guid>
        </item>
      
    
      
        <item>
          <title>Just Do It: Command-Query Segregation, Nike-Style</title>
          <description>&lt;p&gt;OK, CQRS is a hugely mis-applied and mis-understood architectural style. The insight I&apos;m sharing here is based on my attending Udi Dahan&apos;s Advanced Distributed Systems Architecture course, then applying what I&apos;d learned to a project we were building for 3-4 months, then having one of my colleagues go on the same course, and then have him come back and point out everything we&apos;d done wrong. Let&apos;s assume you are already using CQRS. Maybe you&apos;re using it appropriately; maybe it&apos;s over-engineering; maybe it&apos;s completely misapplied. Doesn&apos;t matter, for what I have to say here; there are smarter folks than I who can tell you whether you should be doing it in the first place. No, I&apos;m here to share a particular insight about CQRS with you.&lt;/p&gt;  &lt;p align=&quot;center&quot;&gt;&lt;strong&gt;Your commands should be like a psychotic drill sergeant screaming orders.&lt;/strong&gt;&lt;/p&gt;  &lt;p align=&quot;center&quot;&gt;&lt;a href=&quot;http://lh5.ggpht.com/-Tc6aCXoOOIc/TguG6I0JozI/AAAAAAAAAGg/U6XZf5RdPYE/s1600-h/image%25255B4%25255D.png&quot;&gt;&lt;img style=&quot;background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh4.ggpht.com/-9aO3mKNTyQc/TguG7qxOalI/AAAAAAAAAGk/EbIYzjQsOwM/image_thumb%25255B2%25255D.png?imgmax=800&quot; width=&quot;644&quot; height=&quot;364&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;When you issue a command, &lt;strong&gt;your work is done. &lt;/strong&gt;End of story. Maybe it happens immediately. Maybe there&apos;s a delay, and someone has to wait a few seconds. Maybe it doesn&apos;t go according to plan, and somebody else notices afterwards, and they call someone else, and it gets fixed up. Maybe it fails spectacularly. You don&apos;t care. (Hell, you&apos;re probably dead by now. Nobody would ever have won any wars if everyone threw an exception when they found the sarge face-down in a fox-hole with a bullet-hole in his head.)&lt;/p&gt;  &lt;p&gt;You&apos;re the sarge. You are IN COMMAND. You order someone to destroy the ammo dump in North Camp, then you get on with your life. Tomorrow morning, you&apos;ll get a fresh intel report. Maybe it&apos;ll say that ammo dump in North Camp has been destroyed - maybe it won&apos;t. You&apos;ll review the fresh intelligence, decide what to next, issue a fresh batch of orders - and &lt;strong&gt;get on with your life. &lt;/strong&gt;That&apos;s CQRS. Your data is stale, your word is LAW, and you have better things to do than hang around wondering if you maybe did the wrong thing. If you give a command, and it doesn&apos;t get obeyed, there&apos;s exactly two potential outcomes:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Nobody notices&lt;/li&gt;    &lt;li&gt;Somebody notices&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;&lt;strong&gt;Nobody notices? &lt;/strong&gt;Cool. No problem. &lt;strong&gt;Somebody notices? &lt;/strong&gt;Well - that&apos;s where you hope it&apos;s one of your guys (i.e. alerts, logging, infrastructure, monitoring) instead of one of THEIR guys (i.e&amp;#160; customers/clients) That&apos;s how you do CQRS. Get your intelligence - your queries. The freshest data you can get, but don&apos;t bust a gut if it&apos;s a little out of date. Give your orders. Trust your intelligence. Get on with your life. Rinse. Repeat.&lt;/p&gt;  </description>
          <pubDate>2011-06-29T20:10:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2011/06/29/just-do-it-command-query-segregation.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2011/06/29/just-do-it-command-query-segregation.html</guid>
        </item>
      
    
      
        <item>
          <title>How to install &quot;Active Directory Users and Computers&quot; on your Windows 7 Workstation</title>
          <description>&lt;p&gt;This one stumped me until I hit upon the magical combination that makes it work.&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Install Windows 7 Service Pack 1.&lt;/li&gt;    &lt;li&gt;Download the &lt;a href=&quot;http://www.microsoft.com/download/en/details.aspx?displaylang=en&amp;amp;id=7887&quot;&gt;Remote Server Administration Tools for Windows® 7 with SP1&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;Install them.&lt;/li&gt;    &lt;li&gt;Reboot&lt;/li&gt;    &lt;li&gt;Go into Start -&amp;gt; Control Panel -&amp;gt; Programs and Features, and go to &amp;quot;Turn Windows features on or off&amp;quot; - because the installer will download and install the admin tools, but won&apos;t actually switch them on. Helpful.&lt;/li&gt; &lt;/ol&gt; &lt;a href=&quot;http://lh5.ggpht.com/-fOHA9UiZnGI/TfN35TdM4-I/AAAAAAAAAGY/_WMm5eSE-Yc/s1600-h/image%25255B4%25255D.png&quot;&gt;&lt;img style=&quot;display: block; float: none; margin-left: auto; margin-right: auto&quot; title=&quot;image&quot; alt=&quot;image&quot; src=&quot;http://lh6.ggpht.com/-jLGdJvPd3HM/TfN35-OveaI/AAAAAAAAAGc/b3SDApHZXe4/image_thumb%25255B2%25255D.png?imgmax=800&quot; width=&quot;811&quot; height=&quot;362&quot; /&gt;&lt;/a&gt;  &lt;p&gt;What makes it fun is that it just fails silently until you get it right. No error messages, no warnings... installer didn&apos;t even tell me I was missing SP1 - which I thought I already had.&lt;/p&gt;  </description>
          <pubDate>2011-06-11T14:12:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2011/06/11/how-to-install-directory-users-and.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2011/06/11/how-to-install-directory-users-and.html</guid>
        </item>
      
    
      
        <item>
          <title>Why Cloning Classic Games in Javascript Makes for a Great Hack Day</title>
          <description>&lt;p&gt;&lt;img style=&quot;background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px 0px 0px 20px; padding-left: 0px; padding-right: 0px; display: inline; float: right; border-top: 0px; border-right: 0px; padding-top: 0px&quot; title=&quot;When I needed a picture to go with &amp;quot;friction&amp;quot;, nothing else even came close... (Far Side © Gary Larson)&quot; border=&quot;0&quot; alt=&quot;Gary Larson / Far Side&quot; align=&quot;right&quot; src=&quot;http://www.johnstons.org/roy/comics/farside/fs24.gif&quot; /&gt;Hack days should be about code. Anything that stops you writing code (or talking about / editing / refactoring code) is friction.&lt;/p&gt;  &lt;p&gt;There&apos;s two things in any software project that tend to cause huge amounts of friction - certainly during the early stages. One is tooling. Installing compilers takes time. Installing libraries takes time. Configuration takes time. I remember a day at Snowcode last year when we spent literally five hours installing Ruby, various build tools, make files, modules, browser automation components, plug-ins... I don&apos;t think I wrote a single line of code that day. It was interesting, and educational, but a hack day should be about &lt;strong&gt;building&lt;/strong&gt; stuff.&lt;/p&gt;  &lt;p&gt;The other huge source of friction in software development? Debates. There&apos;s X ways of doing something, and you can&apos;t make any progress until you&apos;ve chosen one. Should we allow HTML in the comments? How big do we make the gallery thumbnails? &lt;a href=&quot;http://en.wikipedia.org/wiki/Parkinson&apos;s_Law_of_Triviality&quot;&gt;What colour should we paint the bike-shed&lt;/a&gt;? On a &amp;quot;real&amp;quot; project, these decisions are made by the product owner - but for something like a hack-day, if you one person in charge of all the design, decision-making and prioritisation, they&apos;ll rapidly become a rather frustrated bottleneck.&lt;/p&gt;  &lt;p&gt;So - pick a problem that&apos;s clearly-defined and well-understood, and solve it using a language that everyone&apos;s already got, that doesn&apos;t need a compiler, linker or build environment, and that everyone can run just by opening a web browser.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;In other words - clone a classic game in JavaScript. &lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;Choose something everyone&apos;s played. Bomberman. Tetris. Lemmings. Asteroids. Pac-man. Have a copy of the game on hand - on a laptop, or an emulator, or bring a console, or whatever - so if anyone asks questions, you can just refer to your definitive reference implementation and get back to work. That&apos;ll eliminate debate without handing anyone the poisoned chalice of product ownership on a volunteer-based project.&lt;/p&gt;  &lt;p&gt;And embrace the awesome lightweight expressiveness of JavaScript - the only language that you can write &lt;strong&gt;and run&lt;/strong&gt;, out of the box, on every single computer since Windows 98. There&apos;s no compiler. There&apos;s no IDE, no build chain, no runtime or virtual machine or standard libraries to install. People can use vi, Visual Studio, TextMate, Notepad - whatever they like. (Personally, &lt;a href=&quot;http://www.jetbrains.com/webstorm/&quot;&gt;WebStorm&lt;/a&gt; is rocking my world right now - JavaScript intellisense and refactoring with built-in Git support... it&apos;s fantastic. Just remember that &lt;a href=&quot;http://youtrack.jetbrains.net/issue/WI-6561&quot;&gt;Ctrl-Y doesn&apos;t redo by default&lt;/a&gt; and you&apos;ll love it.) &lt;/p&gt;  &lt;p&gt;OK, this weekend we were using NodeJS, so the build/run/test cycle involved restarting the node server (which is Ctrl-C, up, enter) - but the guys working on the renderer didn&apos;t even need a server. They built a client-side test harness (index.html), and their build and deployment cycle was Ctrl-S, Alt-Tab, F5. &lt;/p&gt;  &lt;p&gt;That&apos;s low friction. That&apos;s walking in off the street, opening up your laptop, pulling the code, and starting to build stuff straight away. And I like that.&lt;/p&gt;  </description>
          <pubDate>2011-06-08T23:12:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2011/06/08/why-cloning-classic-games-in-javascript.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2011/06/08/why-cloning-classic-games-in-javascript.html</guid>
        </item>
      
    
      
        <item>
          <title>Notes from the KaboomJS! LonDev Hack-Day</title>
          <description>&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/dylanbeattie/5799010387/sizes/m/in/set-72157626763870203/&quot;&gt;&lt;img style=&quot;background-image: none; border-right-width: 0px; margin: 0px 0px 20px 20px; padding-left: 0px; padding-right: 0px; display: inline; float: right; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px&quot; border=&quot;0&quot; align=&quot;right&quot; src=&quot;http://farm3.static.flickr.com/2740/5799010387_a491d3417e_d.jpg&quot; width=&quot;240&quot; height=&quot;181&quot; /&gt;&lt;/a&gt;A couple of weeks back, I had this crazy idea to build Bomberman, in Javascript, in a day. I floated the idea on Twitter and got a pretty enthusiastic response, and so I set up the first LonDev hack day. 12 people, in a room, for one day, working together to build and ship a working game.&lt;/p&gt;  &lt;p&gt;Did we do it? You&apos;ll have to read all about it on the &lt;a href=&quot;http://www.londev.org/&quot;&gt;new LonDev wiki&lt;/a&gt;, but personally, I&apos;m really pleased at how it went, and really&amp;#160; excited at the idea of organising the next one.&lt;/p&gt;  &lt;p&gt;What&apos;s really encouraging is the mix of people and expertise who contributed. We had a couple of .NET coders, some Ruby/Rails guys, some JS web hackers who&apos;d not done node/socket stuff before, and &lt;a href=&quot;http://twitter.com/#!/palfrey&quot;&gt;@palfrey&lt;/a&gt; who, as far as I can tell, spends his days switching between Erlang and PHP to stop himself getting bored.&lt;/p&gt;  &lt;p&gt;A fun day. Some really solid code. Some really interesting lessons learned. And there&apos;s a couple of us hanging out in #kaboomjs on Freenode over the next few days to get it finished off and up and running.&lt;/p&gt;  </description>
          <pubDate>2011-06-05T15:01:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2011/06/05/notes-from-kaboomjs-londev-hack-day.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2011/06/05/notes-from-kaboomjs-londev-hack-day.html</guid>
        </item>
      
    
      
        <item>
          <title>Want to work for Spotlight?</title>
          <description>&lt;p&gt;&lt;a href=&quot;http://www.spotlight.com/&quot;&gt;Spotlight&lt;/a&gt; are hiring! We&apos;re looking for someone to join our software team full-time, in a senior development position. An experienced scrum master, who knows how to work with business and technical people to make things happen. Somebody who understands how to create great software. From database optimization to SOLID principles to TDD to user experience and accessibility, you understand what makes software great - great to use, great to maintain, great to extend.&lt;/p&gt;  &lt;p&gt;We’re at a turning point. Five years ago, we were an award-winning publishing company who maintained our own website. Five years from now, we’re going to be a software company who publish award-winning directories. It’s a great place to work, and it’s a really exciting time to be here. It’s full-time, permanent job, working at our office just off Leicester Square. We’re upstairs from the Prince Charles Cinema – London home of Sing-along-a-Sound-of-Music and The Room – and surrounded by excellent bars, restaurants and theatres.&lt;/p&gt;  &lt;p&gt;If you understand 90% of the postings on my blog, you’re probably in the right ball-park in terms of technology – but if you want buzzwords, it’s C#, .NET, agile, scrum, MVC, Castle Windsor, NHibernate, NServiceBus, jQuery, IIS, SQL Server, NUnit, SOA, TeamCity, FinalBuilder, msdeploy, and various other bits that are occasionally referred to as the “alt net stack”. &lt;/p&gt;  &lt;p&gt;Interested? Read the full job spec, and details of how to apply, at &lt;a href=&quot;http://www.spotlight.com/jobs/developer.html&quot;&gt;www.spotlight.com/jobs/developer.html&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;NO AGENCIES. &lt;font size=&quot;1&quot;&gt;Seriously. If we want to deal with agencies, we’ll call you. If you call me, I will put my phone handset in a drawer, close the drawer, and let you talk to my stationery while I wander off and make some coffee. If you’re lucky, it’ll only waste 90 seconds of your time. If you’re unlucky, your phone system still uses analogue-switched PSTN and you’ll find you can’t hang up. It’s hard to earn commission when you can’t use your phone, and you’d be surprised how long it takes to make a really good cup of coffee.&lt;/font&gt;&lt;/p&gt;  </description>
          <pubDate>2011-04-26T12:55:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2011/04/26/want-to-work-for-spotlight.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2011/04/26/want-to-work-for-spotlight.html</guid>
        </item>
      
    
      
        <item>
          <title>Slides and Notes from “So You Think You Know JavaScript”</title>
          <description>&lt;p&gt;&lt;a href=&quot;http://lh5.ggpht.com/_de7B8BtZZIw/TaIMTb7NsoI/AAAAAAAAAGQ/HPZgew2umjQ/s1600-h/image%5B3%5D.png&quot;&gt;&lt;img style=&quot;background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px 0px 0px 20px; padding-left: 0px; padding-right: 0px; display: inline; float: right; border-top: 0px; border-right: 0px; padding-top: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; align=&quot;right&quot; src=&quot;http://lh4.ggpht.com/_de7B8BtZZIw/TaIMT3nC8_I/AAAAAAAAAGU/ES69E2BEN3g/image_thumb%5B1%5D.png?imgmax=800&quot; width=&quot;240&quot; height=&quot;150&quot; /&gt;&lt;/a&gt;The slides, notes and references from my JavaScript talk are now online, at     &lt;br /&gt;    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &lt;a href=&quot;http://www.dylanbeattie.net/javascript/&quot;&gt;&lt;strong&gt;http://www.dylanbeattie.net/javascript/&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt; &lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;A huge thanks to everyone who came along, to &lt;a href=&quot;http://iancooper.brinkster.net/Frontpage.aspx&quot;&gt;Ian Cooper and the LDNUG User Group&lt;/a&gt; for organising, and to &lt;a href=&quot;http://skillsmatter.com/&quot;&gt;SkillsMatte&lt;/a&gt;r for the venue, the projector, the publicity, the video and the ginger tea. A &lt;a href=&quot;http://skillsmatter.com/podcast/ajax-ria/javascript-dotnet&quot;&gt;full video of the talk&lt;/a&gt; is also available on the SkillsMatter website – and you’ll be pleased to hear that their awesome new video processing rig means you can now see my grinning face AND read the code samples on the slides.&lt;/p&gt;  &lt;p&gt;The NodeJS demo code is open source and is online at &lt;a href=&quot;https://github.com/dylanbeattie/BomberJS&quot;&gt;https://github.com/dylanbeattie/BomberJS&lt;/a&gt; – fork it, pull it, do whatever you like with it. No warranties as to whether it’s any good or not… but it’s there and it works.&lt;/p&gt;  &lt;p&gt;A couple of people asked afterwards about running Node on Windows, as I was doing in the demos. I was using a compiled binary from &lt;a href=&quot;http://node-js.prcn.co.cc/&quot;&gt;http://node-js.prcn.co.cc/&lt;/a&gt;, which worked absolutely fine for little demo apps with 5-6 concurrent client connections. I&apos;ve no idea how it scales, but the general consensus seems to be that you should stick to Linux / MacOS for hosting any significant Node applications.&lt;/p&gt;  </description>
          <pubDate>2011-04-10T20:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2011/04/10/slides-and-notes-from-so-you-think-you.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2011/04/10/slides-and-notes-from-so-you-think-you.html</guid>
        </item>
      
    
      
        <item>
          <title>Churn-down Charts</title>
          <description>&lt;p&gt;Our team have just finished a sprint on a project that’s using loads of new technology – MSMQ, NServiceBus, WCF – that we’ve not worked with before, and it’s played havoc somewhat with our estimates of how long everything was going to take. We hit our deadline, but only thanks to the product owner shifting a group of features into the next sprint, and at the retrospective everyone agreed that the process worked just fine but we didn’t have any really good way of visualising it. We have a &lt;a href=&quot;http://en.wikipedia.org/wiki/Burn_down_chart&quot;&gt;burn-down chart&lt;/a&gt; – actually, we have two, ‘cos there’s one drawn on the whiteboard and there’s one in FogBugz as well – and what we’ve been doing is at the end of every day, we’ll just mark the number of hours left. On days when we discover more problems than we ship features, this looks like we’re moving backwards… which is true in terms of monitoring progress and planning, but isn’t great for morale, and it doesn’t really explain what’s going on. &lt;/p&gt;  &lt;p&gt;So, I’ve come up with this, as a way of tracking estimation accuracy and churn as well as straightforward progress. I’ve no idea if it’s original or not, I don’t know whether it has a name, but I’ve called mine a &lt;strong&gt;churn-down chart&lt;/strong&gt;. I’ve annotated this example to show you what happens over the course of the project – click for a bigger version.&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh6.ggpht.com/_de7B8BtZZIw/TaHVYFA0yjI/AAAAAAAAAGI/5ug0p0-ac-4/s1600-h/churn_chart%5B5%5D.png&quot;&gt;&lt;img style=&quot;background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; margin-left: auto; border-top: 0px; margin-right: auto; border-right: 0px; padding-top: 0px&quot; title=&quot;Churn-down Chart&quot; border=&quot;0&quot; alt=&quot;Churn-down Chart&quot; src=&quot;http://lh3.ggpht.com/_de7B8BtZZIw/TaHVYjX7hKI/AAAAAAAAAGM/cMDpdepZc_I/churn_chart_thumb%5B3%5D.png?imgmax=800&quot; width=&quot;481&quot; height=&quot;480&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Basically – when the green line hits the red line, you’re done. The product owner controls the red line, by adding and removing features from the sprint. Yes, I know you’re not allowed to add stories to a sprint that’s in progress - I think the chart actually demonstrates why. The little blue tails were inspired by &lt;a href=&quot;http://www.nytimes.com/interactive/2010/02/02/us/politics/20100201-budget-porcupine-graphic.html&quot;&gt;this fantastic visualisation of budget forecasts compared with reality&lt;/a&gt; (which I found via &lt;a href=&quot;http://chartporn.org/&quot;&gt;Chart Porn&lt;/a&gt;), and they track how many hours of features we actually delivered that day, as opposed to how many hours we have left at the end of the day. This clearly shows the difference between days when we were productive but discovered lots of unplanned work, as opposed to days when we were just stuck.&lt;/p&gt;  &lt;p&gt;I like the way it empowers the product owner to actually work with the team to hit the deadline – you’re not working towards a fixed target, you’re both dealing with shifting requirements as you find bugs and have ideas, and you can see at a glance whether you’re on target or not, and if not, why not.&lt;/p&gt;  </description>
          <pubDate>2011-04-10T16:05:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2011/04/10/churn-down-charts.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2011/04/10/churn-down-charts.html</guid>
        </item>
      
    
      
        <item>
          <title>Development Methodologies</title>
          <description>&lt;p&gt;By now, everyone’s seen test-driven development (TDD), behaviour-driven development (BDD) and domain-driven design (DDD) – but there’s some other, so far development paradigms that haven’t got nearly the attention that they deserve.&lt;/p&gt;  &lt;h3&gt;Attention Deficit Disorder Driven Design (ADDDD)&lt;/h3&gt;  &lt;p&gt;Most commonly seen in open-source projects. You begin by implementing a core feature. After a couple of days, when either it gets boring or you’ve coded yourself into a corner and can’t work out how to get out, you pick a new feature and start implementing that one instead. Advantages of this approach are that you can tick “in development” on the feature comparison charts when evaluating your solution against the alternatives. Disadvantages are that it leads to crappy software that doesn’t work.&lt;/p&gt;  &lt;h3&gt;Attention Deficit Hyperactive Disorder Driven Development (ADHDDD)&lt;/h3&gt;  &lt;p&gt;Just like ADDDD, but features are only ever added in brief caffeine-fuelled bursts of manic coding, usually around 4am, accompanied by dozens of tweets, blog posts and Facebook status updates.&lt;/p&gt;  &lt;h3&gt;Developer Developer Developer Driven Development (DDDDD)&lt;/h3&gt;  &lt;p&gt;Projects are started twice a year, normally the week immediately after the popular DDD community event at Microsoft headquarters, and generally involves building something really ground-breaking like a wiki or a blog engine, just to “get your head around” all the amazing new stuff you’ve seen at DDD. You’ll generally lose interest about two days after you put the code up on Github as a “pre-alpha technology demo”, and then six months later you’ll do the whole thing all over again.&lt;/p&gt;  &lt;h3&gt;Advanced Dungeons &amp;amp; Dragons Driven Development (AD&amp;amp;DDD)&lt;/h3&gt;  &lt;p&gt;Everyone sits around drinking Red Bull, eating Doritos, boasting about their accomplishments and pretending to be some sort of tenth-level software architect when deep down they’re still not quite sure what a pointer is. A “dungeon master” (also known as a “project manager”) occasionally rolls some dice or reads a Gartner report, and then tells them that their project has died. Then they do it all over again, once every couple of months, sometimes continuing well into middle age.&lt;/p&gt;  &lt;h3&gt;Acronym Driven Development (ADD)&lt;/h3&gt;  &lt;p&gt;The &lt;a href=&quot;http://www.horsepoker.net/&quot;&gt;HORSE&lt;/a&gt; of development methodologies; you consistently blame the failure of your last project on the fact that you picked the wrong methodology, and resolve to try something different on your next project. The conventional approach is to go test-driven, then behaviour-driven, then domain-driven, then extreme, then back to domain-driven. It’s a very educational way of wasting your employer’s time and money, and there’s normally someone in a back room happily coding away who doesn’t have the faintest idea what the rest of you are doing, but is probably shipping enough features to keep your company afloat.&lt;/p&gt;  </description>
          <pubDate>2011-04-04T08:48:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2011/04/04/development-methodologies.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2011/04/04/development-methodologies.html</guid>
        </item>
      
    
      
        <item>
          <title>I’ll be talking about JavaScript at Skills Matter on April 5th</title>
          <description>&lt;p&gt;On April 5th I’ll be giving a talk on JavaScript at SkillsMatter here in London. It’s being organized by the London .NET User Group, but it’s not a .NET talk. Instead, it’ll cover a range of topics related to JavaScript’s history, the current state of the language, and the future of this widely-used and widely-misunderstood language.&amp;#160; &lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://skillsmatter.com/podcast/home/javascript-dotnet/js-1532&quot;&gt;&lt;img style=&quot;background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px 20px 20px 0px; padding-left: 0px; padding-right: 0px; display: inline; float: left; border-top: 0px; border-right: 0px; padding-top: 0px&quot; border=&quot;0&quot; align=&quot;left&quot; src=&quot;http://skillsmatter.com/custom/images/skills-matter_150x60_logo_2010.gif&quot; /&gt;&lt;/a&gt;JavaScript is fifteen years old, and the principles that influenced JavaScript’s architecture go back to the very dawn of computer science. It’s a powerful, expressive, dynamic language, that’s now being used to deliver some of the biggest and &lt;a href=&quot;http://www.gmail.com&quot;&gt;most popular&lt;/a&gt; software application &lt;a href=&quot;http://maps.google.co.uk/&quot;&gt;in the world&lt;/a&gt; – and yet a whole generation of developers still thinks of JavaScript as being a scripting language that’s barely good enough to make pop-up windows appear on a web page.&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh6.ggpht.com/_de7B8BtZZIw/TYjfS5PKgzI/AAAAAAAAAGA/4Q5p38X66Gg/s1600-h/image%5B4%5D.png&quot;&gt;&lt;img style=&quot;background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px 0px 0px 20px; padding-left: 0px; padding-right: 0px; display: inline; float: right; border-top: 0px; border-right: 0px; padding-top: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; align=&quot;right&quot; src=&quot;http://lh5.ggpht.com/_de7B8BtZZIw/TYjfTfi75zI/AAAAAAAAAGE/j08d5t7jAVg/image_thumb%5B2%5D.png?imgmax=800&quot; width=&quot;128&quot; height=&quot;149&quot; /&gt;&lt;/a&gt;There’s a lot of very cool stuff going on in the JavaScript world right now. With HTML5’s offline storage, you can use JavaScript to write client applications that you can install to your phone or your laptop and run them even when you’re offline. With &lt;a href=&quot;http://www.commonjs.org/&quot;&gt;CommonJS&lt;/a&gt;, there’s finally a unified effort to create a standard runtime library for JavaScript so we can write JS programs that support file systems, networking, loadable modules and unit tests. With &lt;a href=&quot;http://nodejs.org/&quot;&gt;NodeJS&lt;/a&gt;, there’s a fast, scalable&amp;#160; framework for writing HTTP servers as collections of discrete JavaScript components. With frameworks like &lt;a href=&quot;http://knockoutjs.com/&quot;&gt;KnockoutJS&lt;/a&gt;, there’s declarative support for building rich web user interfaces in JavaScript - and it’s still pretty good at doing pop-up windows as well.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;So you think you know Javascript? &lt;/strong&gt;&lt;a href=&quot;http://skillsmatter.com/podcast/home/javascript-dotnet/js-1532&quot;&gt;Sign up, come along&lt;/a&gt; and find out. I’ll bet you a pint there’s something in there you’ve never seen before.&lt;/p&gt;  </description>
          <pubDate>2011-03-22T17:41:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2011/03/22/ill-be-talking-about-javascript-at.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2011/03/22/ill-be-talking-about-javascript-at.html</guid>
        </item>
      
    
      
        <item>
          <title>True Names, and Other Dangers: What Dr. Seuss Can Teach Us About SoA</title>
          <description>&lt;p align=&quot;center&quot;&gt;&lt;em&gt;Did I ever tell you that Mrs. McCave, Had twenty-three sons, and she named them all Dave? &lt;/em&gt;&lt;/p&gt;  &lt;p align=&quot;center&quot;&gt;&lt;em&gt;Well, she did. And that wasn&apos;t a smart thing to do; You see, when she wants one, and calls out &amp;quot;Yoo-Hoo!      &lt;br /&gt;Come into the house, Dave!&amp;quot; she doesn&apos;t get one; All twenty-three Daves of hers come on the run!&lt;/em&gt;&lt;/p&gt;  &lt;p align=&quot;center&quot;&gt;&lt;em&gt;- from “&lt;a href=&quot;http://www.freondream.com/ice/daves.html&quot;&gt;Too Many Daves&lt;/a&gt;” by Dr. Seuss&lt;/em&gt;&lt;/p&gt;  &lt;p align=&quot;justify&quot;&gt;They say there’s only two hard problems in software – cache invalidation, naming things, and off-by-one errors. Cache invalidation’s hard because it’s difficult to clarify requirements. Off-by-one errors are hard because the joke wouldn’t work without them. But naming things? How hard can that be?&lt;/p&gt;  &lt;p align=&quot;justify&quot;&gt;&lt;img style=&quot;background-image: none; border-right-width: 0px; margin: 0px 0px 16px 16px; padding-left: 0px; padding-right: 0px; display: inline; float: right; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px&quot; title=&quot;Sentences you never hear: &quot; border=&quot;0&quot; alt=&quot;The &quot; align=&quot;right&quot; src=&quot;http://www.cyberpunkreview.com/images/terminator06.jpg&quot; width=&quot;240&quot; height=&quot;136&quot; movie.?=&quot;movie.?&quot; Terminator=&quot;Terminator&quot; The=&quot;The&quot; from=&quot;from&quot; listing=&quot;listing&quot; phonebook=&quot;phonebook&quot; Connor?=&quot;Connor?&quot; Sarah=&quot;Sarah&quot; Schwarzenegger??=&quot;Schwarzenegger??&quot; *other*=&quot;*other*&quot; the=&quot;The&quot; him,=&quot;him,&quot; not=&quot;not&quot; No,=&quot;No,&quot; Schwarzenegger.=&quot;Schwarzenegger.&quot; /&gt;If you’ve ever worked in IT support, you’ll have had calls saying “the system is down”. Sometimes, a more enlightened caller will helpfully tell you that it’s ‘the network’ or ‘the database’ that’s broken. I once started at a job where everybody referred to everything as “sequel.” There had been a big database migration a few years earlier, resulting in a new website and new desktop software, and the whole process had been referred to as “upgrading to SQL Server”. Everyone kept hearing the techies talk about “upgrading to sequel”, and so when they got something new on their desktops, they conclude “Ah – this must be that sequel thing that everyone’s been talking about!”. Two days later, they call you up and say there’s a problem with ‘sequel’ – and in this context, ‘sequel’ could refer to just about anything. The name was overloaded to the point of uselessness.&lt;/p&gt;  &lt;div align=&quot;justify&quot;&gt;   &lt;div style=&quot;text-align: center; padding-bottom: 16px; padding-left: 16px; width: 200px; padding-right: 16px; float: left; margin-right: 8px; border-right: #000 1px solid; padding-top: 16px&quot;&gt;&lt;strong&gt;“Ah yes - but it’s       &lt;br /&gt; &lt;font size=&quot;3&quot;&gt;services&lt;/font&gt;         &lt;br /&gt;&lt;font size=&quot;4&quot;&gt;all the way down&lt;/font&gt;!”&lt;/strong&gt;&lt;/div&gt; &lt;/div&gt;  &lt;p align=&quot;justify&quot;&gt;What’s scary is that this happens all over the industry. People talk about “Software as a Service”, when what they’re actually dealing with is an XML web service, that’s connecting to a WCF service hosted in a Windows service to provide a business service. Like dear old Mrs. McCave, we’re finding out that names are great if they’re unique, but when different things start laying claim to the same names, you’re going to end up cross-eyed.&lt;/p&gt;  &lt;p align=&quot;justify&quot;&gt;So, as my team start teasing apart our proverbial big ball of mud, I’m trying out a new naming policy for the new components and modules we’re building. Pick a word that sounds nice and doesn’t mean anything within our business. &lt;/p&gt;  &lt;p align=&quot;justify&quot;&gt;The last three projects I worked on were called Rosemary, Tarragon and Kamogelo. No namespace conflicts, no semantic overloading and no clashing with reserved keywords. Rosemary’s almost like an employee – it has an event log, and a mailbox, and a sufficiently clear sense of identity that people seem to get it. When they say there’s a problem with Rosemary, they’re right; it doesn’t take 15 minutes to work out what they mean, and that’s a good thing. It also encourages clear separation of concerns, and facilitates good discussion thereof – lots of “does this feature belong in Rosemary or Tarragon?” instead of just adding another class to the legacy codebase. &lt;/p&gt;  &lt;p align=&quot;justify&quot;&gt;So it’s goodbye, “customer e-mail service” and “accounts system” and “web shop”, and hello to Sundance, Monolith and Aquarius. And before too long, Moonface and PuttPutt and Shadrack, in recognition of dear old Mrs. McCave.&lt;/p&gt;  </description>
          <pubDate>2011-02-26T23:23:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2011/02/26/true-names-and-other-dangers.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2011/02/26/true-names-and-other-dangers.html</guid>
        </item>
      
    
      
        <item>
          <title>Making HttpContext.Current Available Within a WCF Service</title>
          <description>&lt;p&gt;I needed to add a quick’n’dirty WCF service to an ASP.NET MVC web application, so I could call a handful of methods from a different application.&lt;/p&gt;  &lt;p&gt;The MVC app in question is using Windsor, NHibernate and the repository pattern, so we’ve got a fairly standard pattern where we spin up a ManagedWebSessionContext in the Application_BeginRequest handler (in global.asax.cs) and then flush and close the session in Application_EndRequest(). I used the Windsor WCF facility to inject a bunch of dependencies into a little WCF service, but I was finding that SessionFactory.GetCurrentSession() was always returning null – because when you’re using the ManagedWebSessionContext, your NHibernate session is bound to your HttpContext.Current, and by default you don’t have one of these inside a WCF service. &lt;/p&gt;  &lt;p&gt;However - if you can live with tight coupling between your WCF service and IIS hosting, there’s a couple of little config things you’ll need to do to get this working. What doesn’t help is that until you’ve got all this just right, you’ll get a really helpful “Failed to Execute URL” error from IIS that’ll tell you absolutely nothing about what’s wrong.&lt;/p&gt;  &lt;p&gt;First, make sure WCF HTTP activation is installed on your server – in Windows 2008, it’s under Server Manager –&amp;gt; Features:&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;a href=&quot;http://lh6.ggpht.com/_de7B8BtZZIw/TWZrJ6M0n4I/AAAAAAAAAFk/aSui0JLh5uI/s1600-h/image%5B3%5D.png&quot;&gt;&lt;img style=&quot;background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh4.ggpht.com/_de7B8BtZZIw/TWZrKMGJMkI/AAAAAAAAAFo/jQgFzTDa8Co/image_thumb%5B1%5D.png?imgmax=800&quot; width=&quot;525&quot; height=&quot;484&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Next, make sure you’ve registered the WCF service model with IIS, by running:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font color=&quot;#a5a5a5&quot;&gt;C:\WINDOWS\Microsoft.NET\Framework\v3.0\Windows Communication Foundation\&amp;gt;&lt;/font&gt; ServiceModelReg.exe –i&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Next, make sure your web service is running in ASP.NET compatibility mode. First, check you’ve got this:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font color=&quot;#a5a5a5&quot;&gt;&amp;lt;system.serviceModel&amp;gt;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &amp;lt;serviceHostingEnvironment &lt;font color=&quot;#000000&quot;&gt;&lt;strong&gt;aspNetCompatibilityEnabled=&amp;quot;true&amp;quot;&lt;/strong&gt;&lt;/font&gt; /&amp;gt;        &lt;br /&gt;&amp;lt;/system.serviceModel&amp;gt;&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;in your web.config file, and then decorate your service implementation with the AspNetCompatibilityRequirements attribute:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;[AspNetCompatibilityRequirements(RequirementsMode= AspNetCompatibilityRequirementsMode.Required)]     &lt;br /&gt;&lt;font color=&quot;#a5a5a5&quot;&gt;public class WcfMagicService : IMagicService {       &lt;br /&gt;&amp;#160;&amp;#160; . . .        &lt;br /&gt;}&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;The last thing I had to do was necessitated by WCF not supporting multiple host headers; I had to hard-wire the WCF endpoint to listen on a specific hostname. In this case, this involved tweaking the serviceHostingEnvironment section of web.config, which now looks like this:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font color=&quot;#a5a5a5&quot;&gt;&amp;lt;serviceHostingEnvironment aspNetCompatibilityEnabled=&amp;quot;true&amp;quot;&amp;gt;       &lt;br /&gt;&lt;/font&gt;&amp;#160;&amp;#160;&amp;#160; &lt;strong&gt;&amp;lt;baseAddressPrefixFilters&amp;gt;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &amp;lt;add prefix=http://services.mydomain.com” /&amp;gt;        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &amp;lt;/baseAddressPrefixFilters&amp;gt;        &lt;br /&gt;&lt;/strong&gt;&lt;font color=&quot;#a5a5a5&quot;&gt;&amp;lt;/serviceHostingEnvironment&amp;gt;&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;And then adding another attribute to the service implementation class:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;strong&gt;[ServiceBehavior(AddressFilterMode=AddressFilterMode.Any)]       &lt;br /&gt;&lt;/strong&gt;&lt;font color=&quot;#a5a5a5&quot;&gt;[AspNetCompatibilityRequirements(RequirementsMode= AspNetCompatibilityRequirementsMode.Required)]       &lt;br /&gt;public class WcfMagicService : IMagicService {        &lt;br /&gt;}&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Once that’s done, you’ll have an instantiated HttpContext.Current inside your service methods, so your code – and useful things like NHibernate’s ManagedWebSessionContext – will behave just as they do in normal MVC controllers or WebForms code-behind.&lt;/p&gt;  </description>
          <pubDate>2011-02-24T14:28:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2011/02/24/making-httpcontextcurrent-available.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2011/02/24/making-httpcontextcurrent-available.html</guid>
        </item>
      
    
      
        <item>
          <title>Check Out smtp4dev if You Build Mail-Enabled Software on Windows</title>
          <description>&lt;p&gt;&lt;a href=&quot;http://twitter.com/giantdave&quot;&gt;One of my cow-orkers&lt;/a&gt; pointed me at a great little utility a while back called &lt;a href=&quot;http://smtp4dev.codeplex.com/&quot;&gt;smtp4dev&lt;/a&gt;. It’s an SMTP server that listens on your local machine, and instead of relaying e-mail, it’ll capture them and store them in a queue so you can review and open them.&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh6.ggpht.com/_de7B8BtZZIw/TWL5cJ2neAI/AAAAAAAAAFc/T3n46KwdwaE/s1600-h/image%5B3%5D.png&quot;&gt;&lt;img style=&quot;background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh4.ggpht.com/_de7B8BtZZIw/TWL5csD7XmI/AAAAAAAAAFg/Pjuf_5EW8nU/image_thumb%5B1%5D.png?imgmax=800&quot; width=&quot;534&quot; height=&quot;393&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;It’s brilliant – simple and elegant and incredibly easy to use. Just configure your application (website, debugger, logging framework – whatever it is you’re building) to send mail on localhost:25, fire up smtp4dev, and watch the messages pile up. I’ve been building SMTP appenders for log4net this evening, and it’s been really, really useful.&lt;/p&gt;  &lt;p&gt;Binaries and source are at &lt;a href=&quot;http://smtp4dev.codeplex.com/&quot;&gt;smtp4dev.codeplex.com&lt;/a&gt; – well worth a look if you ever write software that sends e-mail.&lt;/p&gt;  </description>
          <pubDate>2011-02-21T23:46:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2011/02/21/check-out-smtp4dev-if-you-build-mail.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2011/02/21/check-out-smtp4dev-if-you-build-mail.html</guid>
        </item>
      
    
      
        <item>
          <title>How to host Git in the same Apache server that comes with CollabNet Subversion</title>
          <description>&lt;p align=&quot;justify&quot;&gt;&lt;a href=&quot;http://lh4.ggpht.com/_de7B8BtZZIw/TVCACwdo4_I/AAAAAAAAAFU/MybNY-C16R0/s1600-h/IMG_6726%5B7%5D.jpg&quot;&gt;&lt;img style=&quot;background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px 0px 20px 20px; padding-left: 0px; padding-right: 0px; display: inline; float: right; border-top: 0px; border-right: 0px; padding-top: 0px&quot; title=&quot;This is the moon rising over the Costa Smeralda, Sardinia. It has nothing to do with revision control.&quot; border=&quot;0&quot; alt=&quot;This is the moon rising over the Costa Smeralda, Sardinia. It has nothing to do with revision control.&quot; align=&quot;right&quot; src=&quot;http://lh5.ggpht.com/_de7B8BtZZIw/TVCADJNcToI/AAAAAAAAAFY/3Gmsu7VpLFU/IMG_6726_thumb%5B4%5D.jpg?imgmax=800&quot; width=&quot;320&quot; height=&quot;240&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.collab.net/products/subversion/whatsnew.html&quot;&gt;CollabNet Subversion Edge&lt;/a&gt; is a great Subversion distro that includes Apache 2.2 and the ViewVC web-based repo browser, and makes it really, really easy to get up and running with Subversion and WebDAV. I’m setting up a project server to host something we’re working on, and it’s been generally decided that whilst Subversion is all very well for keeping Word documents in, we’d quite like something a touch more… &lt;em&gt;distributed&lt;/em&gt; for the actual source code repo. And when &lt;a href=&quot;http://jagregory.com/&quot;&gt;James Gregory&lt;/a&gt; mentioned on Twitter that git would mean “&lt;a href=&quot;http://twitter.com/#!/jagregory/status/34710468748779520&quot;&gt;no more tree conflicts&lt;/a&gt; ”, I may have actually started salivating… ahem. &lt;/p&gt;  &lt;p align=&quot;justify&quot;&gt;Anyway, yes. Apparently Git is quite good. &lt;/p&gt;  &lt;p align=&quot;justify&quot;&gt;&lt;a href=&quot;http://www.jeremyskinner.co.uk/2010/07/31/hosting-a-git-server-under-apache-on-windows/&quot;&gt;Jeremy Skinner has some fantastic notes&lt;/a&gt; on how to get git up and running with Apache 2.2. on a Windows server – I followed these pretty much to the letter to get my first incarnation up and running, but had to comment out a bunch of the Collabnet/Subversion settings in the Apache config files to get the Git server running properly. A bit of tinkering, though, and I’m pretty much there. What makes this interesting is that CollabNet includes a web-based admin console, which makes configuring the built-in modules very straightforward, but it does mean several of the config files have this rather ominous warning at the top:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p align=&quot;justify&quot;&gt;&lt;strong&gt;#        &lt;br /&gt;# DO NOT EDIT THIS FILE IT WILL BE REGENERATED AUTOMATICALLY BY COLLABNET SUBVERSION&amp;#160; &lt;br /&gt;#&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p align=&quot;justify&quot;&gt;so – any changes we make in there will be just peachy until someone touches the web interface, at which point BOOM! they’ll spontaneously stop working. So whatever we’re going to do, we need to do it without touching any of those files. I wasn’t sure at first whether this would be possible, but it seems to be up and running now and hanging together quite nicely. &lt;/p&gt;  &lt;p align=&quot;justify&quot;&gt;Fire up the Apache httpd.conf file in &lt;a href=&quot;http://code.kliu.org/misc/notepad2/&quot;&gt;your favourite editor&lt;/a&gt; – by default it’ll be in C:\Program Files\Subversion\data\conf\ – and add the following lines at the end:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p align=&quot;justify&quot;&gt;&lt;em&gt;&lt;font color=&quot;#a5a5a5&quot;&gt;# Configure Apache to listen for named virtual hosts on port 80         &lt;br /&gt;&lt;/font&gt;&lt;/em&gt;&lt;strong&gt;NameVirtualHost *:80&lt;/strong&gt;&lt;/p&gt;    &lt;p align=&quot;justify&quot;&gt;&lt;em&gt;&lt;font color=&quot;#a5a5a5&quot;&gt;# Include the configuration file for our git http hosting&lt;/font&gt;        &lt;br /&gt;&lt;/em&gt;&lt;strong&gt;Include &amp;quot;C:\Program Files\Subversion\data\conf\git_httpd.conf&amp;quot;&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p align=&quot;justify&quot;&gt;Now create a new document called – yep - &lt;strong&gt;C:\Program Files\Subversion\data\conf\git_httpd.conf&lt;/strong&gt; – and make it look something like this:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p align=&quot;justify&quot;&gt;&lt;font color=&quot;#a5a5a5&quot;&gt;# HTTP settings for using Apache with MSysGit on Windows       &lt;br /&gt;# Based on Jeremy Skinner&apos;s notes at &lt;/font&gt;&lt;a href=&quot;http://www.jeremyskinner.co.uk/2010/07/31/hosting-a-git-server-under-apache-on-windows/&quot;&gt;&lt;font color=&quot;#a5a5a5&quot;&gt;http://www.jeremyskinner.co.uk/2010/07/31/hosting-a-git-server-under-apache-on-windows/&lt;/font&gt;&lt;/a&gt;&lt;/p&gt;    &lt;p align=&quot;justify&quot;&gt;&amp;lt;VirtualHost *:80&amp;gt;&lt;/p&gt;    &lt;p align=&quot;justify&quot;&gt;&lt;font color=&quot;#a5a5a5&quot;&gt;&amp;#160;&amp;#160;&amp;#160; # Set this to the root folder containing your Git repositories.       &lt;br /&gt;&lt;/font&gt;&amp;#160;&amp;#160;&amp;#160; SetEnv GIT_PROJECT_ROOT D:/Git/      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &lt;br /&gt;&lt;font color=&quot;#a5a5a5&quot;&gt;&amp;#160;&amp;#160;&amp;#160; # Set this to export all projects by default (by default,        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; # git will only publish those repositories that contain a        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; # file named “git-daemon-export-ok”&lt;/font&gt;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; SetEnv GIT_HTTP_EXPORT_ALL      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &lt;font color=&quot;#a5a5a5&quot;&gt;# Route specific URLS matching this regular expression to the git http server.       &lt;br /&gt;&lt;/font&gt;&amp;#160;&amp;#160;&amp;#160; ScriptAliasMatch &amp;quot;(?x)^/git/(.*/(HEAD | info/refs | \      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; objects/(info/[^/]+ | [0-9a-f]{2}/[0-9a-f]{38} | pack/pack-[0-9a-f]{40}\.(pack|idx)) | \      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; git-(upload|receive)-pack))$&amp;quot; \       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &amp;quot;C:/Program Files (x86)/git/libexec/git-core/git-http-backend.exe/$1&amp;quot;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &lt;br /&gt;&lt;font color=&quot;#a5a5a5&quot;&gt;&amp;#160;&amp;#160;&amp;#160; # The canonical DNS hostname that you want to use for your git server&lt;/font&gt;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; ServerName my_git_server      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;br /&gt;&lt;font color=&quot;#a5a5a5&quot;&gt;&amp;#160;&amp;#160;&amp;#160; # Any other DNS aliases that point to your git server&lt;/font&gt;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; ServerAlias my_git_server my_git_server.mydomain.com my_git_server.my_intranet.local      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;br /&gt;&lt;font color=&quot;#a5a5a5&quot;&gt;&amp;#160;&amp;#160;&amp;#160; # The root folder for non-GIT-hosted documents (e.g. phpgit or some other Web front end)&lt;/font&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; DocumentRoot &amp;quot;D:\gitserver\htdocs\&amp;quot;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &amp;lt;Location /&amp;gt;      &lt;br /&gt;&lt;font color=&quot;#a5a5a5&quot;&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; # This section is duplicated from the Collabnet SVN LDAP authentication        &lt;br /&gt;&lt;/font&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; AuthType Basic      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; AuthName &amp;quot;Spotlight GIT Repository&amp;quot;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; AuthBasicProvider csvn-file-users ldap-users      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Require valid-user      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &amp;lt;/Location&amp;gt;      &lt;br /&gt;&amp;lt;/VirtualHost&amp;gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p align=&quot;justify&quot;&gt;Check your configuration by running httpd.exe from the command line, like so:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p align=&quot;justify&quot;&gt;&lt;font color=&quot;#a5a5a5&quot;&gt;C:\Program Files\Subversion\bin&amp;gt;&lt;/font&gt;&lt;strong&gt;httpd.exe -f &amp;quot;c:\program files\Subversion\data\conf\httpd.conf&amp;quot; –t       &lt;br /&gt;&lt;/strong&gt;Syntax OK&lt;/p&gt; &lt;/blockquote&gt;  &lt;p align=&quot;justify&quot;&gt;and if all looks good, go into services.msc and restart the CollabNetSubversionServer service (which is actually Apache)&lt;/p&gt;  &lt;p align=&quot;justify&quot;&gt;Finally, I followed Jeremy’s instructions to get GitPhp running, but then replaced it with a different project – also called GitPhp – from &lt;a href=&quot;http://www.xiphux.com/programming/gitphp/&quot;&gt;http://www.xiphux.com/programming/gitphp/&lt;/a&gt;, which provides a full repository browser, revision history, etc.&lt;/p&gt;  &lt;p align=&quot;justify&quot;&gt;All I had to do to get GitPHP running was to copy the gitphp.conf.php.example file to gitphp.conf.php, and then tweak the following settings:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p align=&quot;justify&quot;&gt;&lt;font color=&quot;#a5a5a5&quot;&gt;/* The root folder of my Git repositories */&lt;/font&gt;      &lt;br /&gt;$gitphp_conf[&apos;projectroot&apos;] = &apos;D:\git&apos;;&lt;/p&gt;    &lt;p align=&quot;justify&quot;&gt;&lt;font color=&quot;#a5a5a5&quot;&gt;/* On 64-bit Windows, C:\Program Files (x86) ends up as C:\Progra~2\ so these need to be configured manually */&lt;/font&gt;      &lt;br /&gt;$gitphp_conf[&apos;gitbin&apos;]&amp;#160; = &amp;quot;C:\Progra~2\Git\bin\git.exe&amp;quot;;      &lt;br /&gt;$gitphp_conf[&apos;diffbin&apos;]&amp;#160; = &amp;quot;C:\Progra~2\Git\bin\diff.exe&amp;quot;;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p align=&quot;justify&quot;&gt;Job done. I now have:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;     &lt;div align=&quot;justify&quot;&gt;&lt;a href=&quot;http://my_svn_server/&quot;&gt;http://my_svn_server/&lt;/a&gt; returning regular HTML pages hosted by Apache&lt;/div&gt;   &lt;/li&gt;    &lt;li&gt;     &lt;div align=&quot;justify&quot;&gt;&lt;a href=&quot;http://my_svn_server/svn/&quot;&gt;http://my_svn_server/svn/&lt;/a&gt; giving me a WebDAV browser view on my raw Subversion repositories&lt;/div&gt;   &lt;/li&gt;    &lt;li&gt;     &lt;div align=&quot;justify&quot;&gt;&lt;a href=&quot;http://my_svn_server/viewvc/&quot;&gt;http://my_svn_server/viewvc/&lt;/a&gt; giving me ViewCV web-browsable revision historys and diffs on my Subversion repos&lt;/div&gt;   &lt;/li&gt;    &lt;li&gt;     &lt;div align=&quot;justify&quot;&gt;&lt;a href=&quot;http://my_git_server/&quot;&gt;http://my_git_server/&lt;/a&gt; giving me GitPHP browsing of my Git repositories&lt;/div&gt;   &lt;/li&gt;    &lt;li&gt;     &lt;div align=&quot;justify&quot;&gt;&lt;a href=&quot;http://my_git_server/git/&quot;&gt;http://my_git_server/git/&lt;/a&gt;&amp;#160; exposing URLs for GIT cloning, fetching and pushing&lt;/div&gt;   &lt;/li&gt;    &lt;li&gt;     &lt;div align=&quot;justify&quot;&gt;&lt;a href=&quot;http://my_iis_server/&quot;&gt;http://my_iis_server/&lt;/a&gt; is still running IIS 7 with .NET 4 and MVC 3&lt;/div&gt;   &lt;/li&gt; &lt;/ul&gt;  &lt;p align=&quot;justify&quot;&gt;and that’s all on one box, with two IP addresses, with the svn and git servers sharing an instance of Apache on one address, the IIS server running on the other, and DNS records pointing svn and git at the first address and IIS at the second.&lt;/p&gt;  </description>
          <pubDate>2011-02-07T23:28:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2011/02/07/how-to-host-git-in-same-apache-server.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2011/02/07/how-to-host-git-in-same-apache-server.html</guid>
        </item>
      
    
      
        <item>
          <title>Running IIS and Apache on the same Windows 2008 R2 Server</title>
          <description>I’m trying to get a Composite C1 site and the Apache WebDAV front-end to Subversion running on the same Windows 2008 R2 server, and doing so requires a bit of trickery with IP address bindings and such, and I thought I’d share it – partly ‘cos it’s useful, and partly because I’m bound to have to do this again in three months time and there’s no way I’ll remember how I did it. First off, make sure your box has (at least) two IP addresses – I’ve bound mine to 192.168.0.13 and 192.168.0.14&lt;br /&gt;
To get IIS to listen on ONLY 192.168.0.13, you’ll need to run the netsh.exe utility. &lt;br /&gt;
&lt;blockquote&gt;
C:\Users\dylan.beattie&amp;gt;netsh&lt;br /&gt;
netsh&amp;gt;http add iplisten ipaddress=192.168.0.13&lt;br /&gt;
IP address successfully added&lt;br /&gt;
netsh&amp;gt;http show iplisten&lt;br /&gt;
IP addresses present in the IP listen list: &lt;br /&gt;-------------------------------------------&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp; 192.168.0.13&lt;br /&gt;
netsh&amp;gt;exit&lt;/blockquote&gt;
(note that netsh.exe is a Windows 2008 utility – if you’re running Windows 2003 or earlier, look up the docs on using httpcfg.exe to achieve the same thing)&lt;br /&gt;
If you now fire up a web browser and go to &lt;a href=&quot;http://192.168.0.13/&quot;&gt;http://192.168.0.13/&lt;/a&gt;, you should get the default IIS7 “Welcome” screen, and &lt;a href=&quot;http://192.168.0.14/&quot;&gt;http://192.168.0.14/&lt;/a&gt; shouldn’t return anything at all. Now to get Apache listening on 192.168.0.14. Find your httpd.conf file – if you’ve just installed CollabNet Subversion (like I have) it’ll be in the \data\conf folder of wherever you put your SVN install.&lt;br /&gt;
You’ll need to find the Listen directive in httpd.conf, and modify it to say:&lt;br /&gt;
&lt;blockquote&gt;
Listen 192.168.0.14:80&lt;/blockquote&gt;
That’s all. Next time – to get Git running on the same Apache installation… until then, happy hacking.&lt;br /&gt;
&lt;hr /&gt;
UPDATE: After running this for several years, I&apos;ve found that occasionally, following an unscheduled shutdown or power outage, IIS won&apos;t come back up properly after the box is restarted. Sites will respond on &lt;a href=&quot;http://localhost/&quot;&gt;http://localhost/&lt;/a&gt; but trying to access them via hostname gives an ERR_CONNECTION_RESET message. &lt;br /&gt;
This can be fixed by removing and re-adding the HTTP binding:&lt;br /&gt;
&lt;blockquote&gt;
&lt;span style=&quot;font-family: Consolas;&quot;&gt;C:\&amp;gt;&lt;strong&gt;netsh&lt;/strong&gt; &lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Consolas;&quot;&gt;netsh&amp;gt; &lt;strong&gt;http&lt;/strong&gt;&lt;br /&gt;netsh http&amp;gt; &lt;strong&gt;delete iplisten 192.168.0.13&lt;/strong&gt;netsh http&amp;gt; &lt;strong&gt;add iplisten 192.168.0.13&lt;/strong&gt;netsh http&amp;gt; &lt;strong&gt;exit&lt;/strong&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Consolas;&quot;&gt;C:\&amp;gt;&lt;strong&gt;iisreset&lt;/strong&gt;&lt;/span&gt;&lt;/blockquote&gt;
</description>
          <pubDate>2011-02-07T19:18:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2011/02/07/running-iis-and-apache-on-same-windows.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2011/02/07/running-iis-and-apache-on-same-windows.html</guid>
        </item>
      
    
      
        <item>
          <title>“Choose Life” For DBAs</title>
          <description>&lt;p&gt;I’m really really sorry. Someone tagged &lt;a href=&quot;http://search.twitter.com/search?q=%23sqlmoviequotes&quot;&gt;#sqlmoviequotes&lt;/a&gt; on Twitter and I got carried away…&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;SELECT TOP(1) * FROM LIFE.&lt;/p&gt;    &lt;p&gt;SELECT TOP(1) * FROM JOB.&lt;/p&gt;    &lt;p&gt;SELECT TOP(1) * FROM CAREER.&lt;/p&gt;    &lt;p&gt;SELECT TOP(1) * FROM FAMILY.&lt;/p&gt;    &lt;p&gt;SELECT TOP(1) * FROM television ORDER BY SIZE DESC&lt;/p&gt;    &lt;p&gt;SELECT * FROM washing_machine CROSS JOIN car CROSS JOIN compact_disc_player CROSS JOIN electrical_tin_opener&lt;/p&gt;    &lt;p&gt;SELECT * FROM health, cholestorol, dental_insurance      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; WHERE health.status = &apos;good&apos; and cholestorol.level &amp;lt; 5      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &lt;br /&gt;SELECT * FROM mortgage WHERE interest_rate = &apos;fixed&apos;&lt;/p&gt;    &lt;p&gt;SELECT TOP(1) * FROM home WHERE TYPE = &apos;starter&apos;&lt;/p&gt;    &lt;p&gt;SELECT * FROM person     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; INNER JOIN friendship ON person.id = friendship.person_id and friendship.friend_id = &apos;ME&apos;&lt;/p&gt;    &lt;p&gt;SELECT * FROM leisurewear      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; INNER JOIN luggage ON leisurewear.color_scheme = luggage.color_scheme      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &lt;br /&gt;SELECT TOP(3) * FROM lounge_furniture WHERE payment_plan = &apos;hire purchase&apos;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; AND range_id IN (SELECT range_id FROM fabric_option GROUP BY range_id HAVING COUNT(*) &amp;gt; @RANGE_SIZE)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &lt;br /&gt;SELECT &apos;diy&apos;, CURRENT_USER FROM activity WHERE DATEPART(dw, activity_date) = 1 AND DATEPART(hh, activity_date) &amp;lt; 12&lt;/p&gt;    &lt;p&gt;DECLARE @your_mouth INT     &lt;br /&gt;DECLARE junk_food CURSOR FOR SELECT * FROM FOOD WHERE TYPE = &apos;junk&apos;      &lt;br /&gt;WHILE @@CURRENT_SHOW IN (SELECT * FROM SHOW WHERE keyword IN (&apos;mind_numbing&apos;, &apos;spirit-crushing&apos;)) BEGIN      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; FETCH NEXT FROM junk_food INTO @your_mouth      &lt;br /&gt;END&lt;/p&gt;    &lt;p&gt;BEGIN&lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; sp_start_job &apos;brat&apos;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; sp_start_job &apos;brat&apos;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; sp_start_job &apos;brat&apos;&lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; KILL @@SPID     &lt;br /&gt;END&lt;/p&gt;    &lt;p&gt;SELECT * FROM events WHERE EventDate &amp;gt; GETDATE()&lt;/p&gt;    &lt;p&gt;SELECT TOP(1) * FROM LIFE&lt;/p&gt;&lt;/blockquote&gt;  </description>
          <pubDate>2011-01-17T23:11:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2011/01/17/choose-life-for-dbas.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2011/01/17/choose-life-for-dbas.html</guid>
        </item>
      
    
      
        <item>
          <title>Mapping a Drive Letter to a Subversion Repository with CollabNet, WebDrive and WebDAV</title>
          <description>&lt;p&gt;This is quite neat. As part of a business-wide agile initiative, I’m looking into solutions for storing and collaborating on documents – something that gives *me* the history and auditing capabilities of something like Subversion, but gives the rest of the team something clean and easy that fits well with current working practises.&lt;/p&gt;  &lt;p&gt;So… mapped drive letters. Everyone knows about drive letters – “just stick it on the R: drive” is nice and easy, and as long as everyone’s R: drive points to the same place and the fileserver behind it’s getting backed up, you’re sorted. Unless you want to revert a document that’s been corrupted, or accidentally deleted, or you just want to get back an earlier revision because you realize you’ve done something dumb. Then you need to mess around with tapes and stuff, and that’s just no fun at all.&lt;/p&gt;  &lt;p&gt;Plus, of course, we have a wiki, which isn’t much fun to edit because the constant round-tripping from WYSIWYG-&amp;gt;markup-&amp;gt;HTML-&amp;gt;WYSIWYG tends to clobber newlines and formatting, but it *is* a great place to keep stuff because it doesn’t get lost.&lt;/p&gt;  &lt;p&gt;So, requirements for document storage:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;As easy to use as a drive letter. &lt;/li&gt;    &lt;li&gt;Security. Windows / LDAP authentication to control who can read and who can write. &lt;/li&gt;    &lt;li&gt;Revision history – just a record of who made changes, and when. &lt;/li&gt;    &lt;li&gt;Ability to revert changes to an earlier revision &lt;/li&gt;    &lt;li&gt;HTTP accessible so you can read stuff with just a web browser. &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;There’s two things you can do with a list of requirements like that… speak to vendors, or hack something together yourself. You wanna speak to vendors, you go ahead; I shan’t stop you. Still here? Good. Let’s hack.&lt;/p&gt;  &lt;h4&gt;1. Install Subversion on the server. &lt;/h4&gt;  &lt;p&gt;For this, I’m using the &lt;a href=&quot;http://www.collab.net/products/subversion/whatsnew.html&quot;&gt;CollabNet Subversion Edge&lt;/a&gt; stack – a single installer combining Subversion, Apache and the ViewVC web front-end. It’s very, very neat, and (having done this the hard way) &lt;em&gt;much&lt;/em&gt; easier than setting up mod_dav_svn yourself. Once it’s up, use the web interface (linked from the Start menu on your server) to set up a new repo – call it doc_repo – and then verify that if you browse to &lt;a href=&quot;http://myserver/viewvc/doc_repo/&quot;&gt;http://myserver/viewvc/doc_repo/&lt;/a&gt; you get the ViewVC web front-end view of your new, empty repository.&lt;/p&gt;  &lt;p&gt;If you’re after Windows/LDAP authentication, that’s also configurable from the Subversion Edge web interface – and CollabNet has &lt;a href=&quot;http://blogs.open.collab.net/svn/2009/03/subversion-with-apache-and-ldap-updated.html&quot;&gt;detailed notes on how this works&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh4.ggpht.com/_de7B8BtZZIw/TStdb3ntWII/AAAAAAAAAEo/JHxonYR9BpQ/s1600-h/image%5B4%5D.png&quot;&gt;&lt;img style=&quot;background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; margin-left: auto; border-top: 0px; margin-right: auto; border-right: 0px; padding-top: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh6.ggpht.com/_de7B8BtZZIw/TStdcb8ZV8I/AAAAAAAAAEs/Qxj4cKZDaF4/image_thumb%5B2%5D.png?imgmax=800&quot; width=&quot;573&quot; height=&quot;741&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;h4&gt;2. Install &lt;strong&gt;&lt;a href=&quot;http://www.webdrive.com/products/webdrive/index.html&quot;&gt;WebDrive&lt;/a&gt;&lt;/strong&gt; on your workstation.&lt;/h4&gt;  &lt;p&gt;This is a commercial package that’ll map a Windows drive letter to a WebDAV share. This is supposedly something that Windows is capable of doing natively, but I have never, ever got this to work, not even once. I would be glad to hear recommendations for free / open-source alternatives for this, since it’s currently the only bit of this set-up that costs money. There’s apparently also a &lt;a href=&quot;http://www.novell.com/coolsolutions/qna/999.html&quot;&gt;netdrive.exe floating around&lt;/a&gt; but licensing for NetDrive seems to be a little confused.&lt;/p&gt;  &lt;h4&gt;3. Hack the Subversion config file. &lt;/h4&gt;  &lt;p&gt;On the server, open up &lt;font face=&quot;Consolas&quot;&gt;C:\Program Files\Subversion\data\conf\svn_viewvc_httpd.conf&lt;/font&gt;. We’re not going to edit this file – you’ll need to find the bit that looks like:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&amp;lt;Location /svn/&amp;gt;      &lt;br /&gt;&amp;#160;&amp;#160; DAV svn       &lt;br /&gt;&amp;#160;&amp;#160; SVNParentPath &amp;quot;C:\Program Files\Subversion\data\repositories&amp;quot;       &lt;br /&gt;&amp;#160;&amp;#160; SVNReposName &amp;quot;CollabNet Subversion Repository&amp;quot;       &lt;br /&gt;&amp;#160; AuthzSVNAccessFile &amp;quot;C:\Program Files\Subversion\data/conf/svn_access_file&amp;quot;       &lt;br /&gt;&amp;#160; SVNListParentPath On       &lt;br /&gt;&amp;#160; Allow from all       &lt;br /&gt;&amp;#160; AuthType Basic       &lt;br /&gt;&amp;#160; AuthName &amp;quot;CollabNet Subversion Repository&amp;quot;       &lt;br /&gt;&amp;#160; AuthBasicProvider csvn-file-users ldap-users       &lt;br /&gt;&amp;#160; Require valid-user       &lt;br /&gt;&amp;lt;/Location&amp;gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;and copy it. Then open up &lt;font face=&quot;Consolas&quot;&gt;C:\Program Files\Subversion\data\conf\httpd.conf&lt;/font&gt; – which is the regular Apache configuration file – and paste the copied section right at the end, and make the following changes:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font color=&quot;#a5a5a5&quot;&gt;# Change Location to be the URL path of your WebDAV repo – I’ve used &lt;em&gt;webdrive here         &lt;br /&gt;&lt;/em&gt;&lt;/font&gt;&amp;lt;Location&lt;font color=&quot;#4bacc6&quot;&gt;&lt;strong&gt; /webdrive/&lt;/strong&gt;&lt;/font&gt;&amp;gt;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; DAV svn       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; SVNParentPath &amp;quot;C:\Program Files\Subversion\data\repositories&amp;quot;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; SVNReposName &amp;quot;Subversion WebDAV&amp;quot;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; AuthzSVNAccessFile &amp;quot;C:\Program Files\Subversion\data/conf/svn_access_file&amp;quot;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; SVNListParentPath On       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Allow from all       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; AuthType Basic       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; AuthName &amp;quot;Document Repository&amp;quot;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; AuthBasicProvider csvn-file-users ldap-users       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Require valid-user       &lt;br /&gt;&lt;font color=&quot;#a5a5a5&quot;&gt;# Add the two lines below &lt;/font&gt;      &lt;br /&gt;&lt;font color=&quot;#4bacc6&quot;&gt;&lt;strong&gt;&amp;#160;&amp;#160;&amp;#160; ModMimeUsePathInfo on          &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; SVNAutoversioning on           &lt;br /&gt;&lt;/strong&gt;&lt;/font&gt;&amp;lt;/Location&amp;gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Now restart the web server (using ApacheMonitor.exe from &lt;font face=&quot;Consolas&quot;&gt;C:\Program Files\Subversion\bin\ &lt;/font&gt;on your server) and check that you can see &lt;a href=&quot;http://myserver/webdrive/&quot;&gt;http://myserver/webdrive/&lt;/a&gt; in a normal Web browser – the screen should say “Collection of Repositories” with your doc_repo repository listed underneath.&lt;/p&gt;  &lt;h4&gt;4. Connect WebDrive to your new WebDEV-Enabled Repository&lt;/h4&gt;  &lt;p&gt;Nearly there. Finally, fire up WebDrive on your workstation and create a new connection. Enter the Site Address/URL as &lt;a href=&quot;http://myserver/webdrive/doc_repo/&quot;&gt;http://myserver/webdrive/doc_repo/&lt;/a&gt; – note that you &lt;strong&gt;must put the repo name in the URL otherwise WebDrive will complain with an error something like:&lt;/strong&gt;&lt;/p&gt;  &lt;blockquote&gt;   &lt;pre&gt;&lt;code&gt;Unable to connect to server, error information below

Error: Socket receive failure (4507)
Operation: Connecting to server
Winsock Error: WSAECONNRESET (10054)&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;



&lt;p&gt;Correct settings will look something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://lh6.ggpht.com/_de7B8BtZZIw/TStdcmWs2pI/AAAAAAAAAEw/iHMCiXNNDqE/s1600-h/image%5B10%5D.png&quot;&gt;&lt;img style=&quot;background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; margin-left: auto; border-top: 0px; margin-right: auto; border-right: 0px; padding-top: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh6.ggpht.com/_de7B8BtZZIw/TStdc24_UMI/AAAAAAAAAE0/XgIpgoUC4Fg/image_thumb%5B6%5D.png?imgmax=800&quot; width=&quot;644&quot; height=&quot;442&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hit Connect, and Windows explorer will fire up a new N: drive window pointing at your repo.&lt;/p&gt;

&lt;h4&gt;5. Witness the Awesome Power of Autoversioning!&lt;/h4&gt;

&lt;p&gt;Create an empty folder, create a text file in it, then browse to &lt;a href=&quot;http://myserver/viewvc/doc_repo/&quot;&gt;http://myserver/viewvc/doc_repo/&lt;/a&gt; and you should see your new folder, and file, along with the Subversion history recording who created the file and when:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://lh4.ggpht.com/_de7B8BtZZIw/TStdddljLwI/AAAAAAAAAE4/KwwcWACaGr0/s1600-h/image%5B14%5D.png&quot;&gt;&lt;img style=&quot;background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh6.ggpht.com/_de7B8BtZZIw/TStddx7f64I/AAAAAAAAAE8/KtAeE9nk3UA/image_thumb%5B8%5D.png?imgmax=800&quot; width=&quot;869&quot; height=&quot;553&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I like that a lot.&lt;/p&gt;  </description>
          <pubDate>2011-01-10T19:26:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2011/01/10/mapping-drive-letter-to-subversion.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2011/01/10/mapping-drive-letter-to-subversion.html</guid>
        </item>
      
    
      
        <item>
          <title>Christmas, Crisis and the Cloud</title>
          <description>&lt;p&gt;This Christmas, I volunteered at &lt;a href=&quot;http://www.crisis.org.uk/&quot;&gt;Crisis&lt;/a&gt;, the UK charity that provides food, shelter and help for homeless people and rough sleepers over Christmas. It’s the first time I’d done anything like this, and – amongst stints of washing-up, manning doors, looking after showers, mopping and playing Articulate – I spent quite a lot of time helping with the suite of PCs that were provided for our guests to use. &lt;/p&gt;  &lt;p&gt;&lt;a title=&quot;Crisis IT Zone photo © Bob Johns&quot; href=&quot;http://www.crisis.org.uk/gallery.php/24/crisis-at-christmas-2010#/gallery-image-large/24/_RJ_8987_1.jpg&quot;&gt;&lt;img style=&quot;background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px 0px 20px 20px; padding-left: 0px; padding-right: 0px; display: inline; float: right; border-top: 0px; border-right: 0px; padding-top: 0px&quot; border=&quot;0&quot; align=&quot;right&quot; src=&quot;http://www.crisis.org.uk/gallery-image-large/24/_RJ_8987_1.jpg&quot; width=&quot;188&quot; height=&quot;280&quot; /&gt;&lt;/a&gt;It was a profound and enlightening experience. I use Gmail and Google Docs because they’re convenient – I regularly use 3-4 different PCs and online services are just a convenient way of keeping things accessible and in sync. It never really occurred to me that for someone who doesn’t have a computer of their own, or any regular access to one via a university or workplace, something as simple as a webmail account can be the difference between staying in touch and disappearing completely. People with no regular income, no phone and no fixed address were using Yahoo Mail to keep in touch with family and friends, apply for jobs and look for accommodation. It was quite amazing. I don’t know whether the folks at Yahoo, Gmail, Hotmail &lt;em&gt;et al&lt;/em&gt; realize just what a profound difference this makes – but if any of them are reading, well done. You rock, and I never realized quite how much until this week.&lt;/p&gt;  &lt;p&gt;Another interesting observation – Microsoft’s market saturation is so complete that even people who store their worldly possessions in a plastic shopping-bag insist on Microsoft Word. Their CVs are in Word format, they know the Word menus and commands – many of them are remarkably accomplished Word users. This is sad, because Word means Windows, and – even with the awesome job the people at the &lt;a href=&quot;http://www.theaimarfoundation.org/&quot;&gt;Aimar Foundation&lt;/a&gt; had done setting up a locked-down Citrix system -&amp;#160; some machines became infected with a malware product called “Security Shield”. Security Shield masquerades as an anti-virus package,&amp;#160; makes a lot of bad noise about (non-existent) viruses and then asks the user for a credit card number to “activate the virus protection”. Well, Security Shield, this Christmas you &lt;strong&gt;actually asked the homeless for money&lt;/strong&gt;. Well done. You scared the hell out of frightened, vulnerable people who thought they’d broken the computer. You scared and upset the volunteers who were giving up their Christmas holiday to help those same people. I really hope you take all the money you’ve made from your little scam and spend it on something that’ll bring you joy in this life, because karma’s a bitch and your next life is going to suck.&lt;/p&gt;  &lt;p&gt;Anyway. Karmic retribution aside, it really got me thinking. For every iPad-toting hipster raving about how they keep all their stuff “in the cloud” because it’s the Next Big Thing, there’s a rough sleeper out there who genuinely doesn’t have anywhere else to keep it. There’s web mail, web docs, online tools for retouching photos, online games (spidersolitaire.com proved very popular with some of our guests), online video – you have no &lt;em&gt;idea&lt;/em&gt; how cool YouTube is to someone who doesn’t own a PC or a television - and I started wondering what else technology could offer.&lt;/p&gt;  &lt;p&gt;What about a complete online tool set and sandbox for people looking to learn about software development? Tutorials, exercises, a lab, workshop, CV and portfolio, accessible anytime, anywhere, for the cost of a virtual machine and a few gigs of disk space (i.e. practically nothing). A full development environment where they can learn to code, store projects, compile, test and deploy applications and websites – but accessible entirely within the sort of stripped-down web browser you&apos;d find in most internet cafés. There’s no material reason why someone couldn’t learn to build software, establish an online presence, contribute to open-source projects, develop a reputation on Q&amp;amp;A sites like &lt;a href=&quot;http://www.stackoverflow.com/&quot;&gt;StackOverflow&lt;/a&gt;, and generally become a &lt;em&gt;demonstrably employable&lt;/em&gt; developer, entirely without any investment in physical resources. The days of needing expensive computer time or equipment to become a coder are gone. The financial barrier to entry in our industry is effectively zero. We should be shouting this from the rooftops, doing everything possible to put information and resources in the hands of anyone who cares to take advantage of it, and celebrating this amazing consequence of the openness that’s sustained our industry for so long.&lt;/p&gt;  &lt;p&gt;Anyway. Crisis was awesome, you should &lt;em&gt;all&lt;/em&gt; do it next year, and here’s to a 2011 filled with compassion, kindness, enthusiasm and excitement. &lt;/p&gt;  &lt;p&gt;Happy New Year.&lt;/p&gt;  </description>
          <pubDate>2010-12-29T00:20:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2010/12/29/christmas-crisis-and-cloud.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2010/12/29/christmas-crisis-and-cloud.html</guid>
        </item>
      
    
      
        <item>
          <title>Git Logos and Icons</title>
          <description>&lt;p&gt;&lt;a href=&quot;http://lh5.ggpht.com/_de7B8BtZZIw/TPKsn3uX1yI/AAAAAAAAAEQ/CEBmbI-7pxA/s1600-h/image%5B27%5D.png&quot;&gt;&lt;img style=&quot;background-image: none; border-right-width: 0px; margin: 0px 0px 1px 20px; padding-left: 0px; padding-right: 0px; display: inline; float: right; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; align=&quot;right&quot; src=&quot;http://lh6.ggpht.com/_de7B8BtZZIw/TPKsoanKwPI/AAAAAAAAAEU/-kw3USmgb2c/image_thumb%5B13%5D.png?imgmax=800&quot; width=&quot;260&quot; height=&quot;152&quot; /&gt;&lt;/a&gt;I’ve recently started working on a couple of projects that are hosted on Github, most notably the &lt;a href=&quot;https://github.com/Kurejito/Kurejito&quot;&gt;Kurejito&lt;/a&gt; payment gateway project for which I’m a member of the core team on an OS project for the first time (yeah, scary…) &lt;/p&gt;  &lt;p&gt;Git is pretty cool. A bit of a learning curve but I suspect once I get the hang of frequent commits and local branching and work out how stop shooting myself in the foot it’s going to be quite hard going back to Subversion.&lt;/p&gt;  &lt;p&gt;Thing is, I really don’t like the Git icon and logo that are shipping with Mingw32 Git for Windows. I mean, I &lt;em&gt;really&lt;/em&gt; don’t like them. I don’t like them so much that every time I fire up git bash, I get completely distracted by how much I don’t like them and end up wanting to redesign them instead of doing whatever it was I was supposed to be doing… so I did.&lt;/p&gt;  &lt;p&gt;This started out as me just wanting a decent Windows icon for the link to Git Bash in my Start menu, I got a bit carried away, and ended up with this. And since Git appears to use a &lt;a href=&quot;https://git.wiki.kernel.org/index.php/GitRelatedLogos&quot;&gt;collaboratively-edited Wiki page&lt;/a&gt; as the closest thing it has to brand and logo guidelines, if you want to use any of these logos or icons for your Git-related shortcuts, projects or pages, go ahead. I’m not going to stop you, and I suspect they won’t, either.&lt;/p&gt;  &lt;p align=&quot;center&quot;&gt;&lt;/p&gt;  &lt;p align=&quot;left&quot;&gt;If you’re interested, they’re released under a &lt;a href=&quot;http://creativecommons.org/licenses/by-sa/3.0/&quot;&gt;Creative Commons Sharealike Attribution License&lt;/a&gt; – and there’s downloads (including Windows and Mac icon formats) and original artwork files at &lt;a href=&quot;http://www.dylanbeattie.net/git_logo/&quot;&gt;http://www.dylanbeattie.net/git_logo/&lt;/a&gt; &lt;/p&gt;  &lt;p align=&quot;left&quot;&gt;&lt;strong&gt;Direct download links:&lt;/strong&gt;&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;     &lt;div align=&quot;left&quot;&gt;&lt;a href=&quot;http://lh3.ggpht.com/_de7B8BtZZIw/TPKspL3geOI/AAAAAAAAAEY/zNP0bZ8c92g/s1600-h/image%5B25%5D.png&quot;&gt;&lt;img style=&quot;background-image: none; border-right-width: 0px; margin: 0px 0px 20px; padding-left: 0px; padding-right: 0px; display: inline; float: right; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; align=&quot;right&quot; src=&quot;http://lh3.ggpht.com/_de7B8BtZZIw/TPKspmkSwxI/AAAAAAAAAEc/E5fl6pbMTpc/image_thumb%5B11%5D.png?imgmax=800&quot; width=&quot;409&quot; height=&quot;181&quot; /&gt;&lt;/a&gt;Git Icon – Windows .ICO Format – &lt;a href=&quot;http://www.dylanbeattie.net/git_logo/git_icon.ico&quot;&gt;git_icon.ico&lt;/a&gt;&lt;/div&gt;   &lt;/li&gt;    &lt;li&gt;     &lt;div align=&quot;left&quot;&gt;Git icon in MacOS .icns format – &lt;a href=&quot;http://www.dylanbeattie.net/git_logo/git_icon.icns&quot;&gt;git_icon.icns&lt;/a&gt;&lt;/div&gt;   &lt;/li&gt;    &lt;li&gt;     &lt;div align=&quot;left&quot;&gt;Git icon original artwork – &lt;a href=&quot;http://www.dylanbeattie.net/git_logo/git_icon.ai&quot;&gt;git_icon.ai&lt;/a&gt; or &lt;a href=&quot;http://www.dylanbeattie.net/git_logo/git_icon.png&quot;&gt;git_icon.png&lt;/a&gt;&lt;/div&gt;   &lt;/li&gt;    &lt;li&gt;     &lt;div align=&quot;left&quot;&gt;Git logo in PNG format – &lt;a href=&quot;http://www.dylanbeattie.net/git_logo/git_icon.png&quot;&gt;git_logo.png&lt;/a&gt;&lt;/div&gt;   &lt;/li&gt;    &lt;li&gt;     &lt;div align=&quot;left&quot;&gt;Git logo in Adobe Illustrator format – &lt;a href=&quot;http://www.dylanbeattie.net/git_logo/git_logo.ai&quot;&gt;git_logo.ai&lt;/a&gt;&lt;/div&gt;   &lt;/li&gt;    &lt;li&gt;     &lt;div align=&quot;left&quot;&gt;Git logo in SVG format – &lt;a href=&quot;http://www.dylanbeattie.net/git_logo/git_logo.svg&quot;&gt;git_logo.svg&lt;/a&gt;&lt;/div&gt;   &lt;/li&gt; &lt;/ul&gt;  &lt;p align=&quot;left&quot;&gt;or grab the whole package as &lt;a href=&quot;http://www.dylanbeattie.net/git_logo/git_icon_and_logo.zip&quot;&gt;git_icon_and_logo.zip&lt;/a&gt;&lt;/p&gt;  </description>
          <pubDate>2010-11-28T19:25:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2010/11/28/git-logos-and-icons.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2010/11/28/git-logos-and-icons.html</guid>
        </item>
      
    
      
        <item>
          <title>Sending Templated E-mail using the Spark View Engine</title>
          <description>&lt;p&gt;&lt;a title=&quot;Spark photograph © SCholewiak via Flickr&quot; href=&quot;http://www.flickr.com/photos/scholewiak/145096304/sizes/m/in/photostream/&quot;&gt;&lt;img style=&quot;background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px 0px 20px 20px; padding-left: 0px; padding-right: 0px; display: inline; float: right; border-top: 0px; border-right: 0px; padding-top: 0px&quot; title=&quot;&quot; border=&quot;0&quot; alt=&quot;Spark photo © SCholewiak via Flickr&quot; align=&quot;right&quot; src=&quot;http://farm1.static.flickr.com/47/145096304_0ff99eec39.jpg&quot; width=&quot;320&quot; height=&quot;241&quot; /&gt;&lt;/a&gt;We have a couple of systems that send personalized e-mail notifications to our users, and for a while I’ve been looking for a nice way to use a proper MVC-style view engine to populate the templates for personalizing these e-mails. The problem is, most of the ASP.NET view engines are so tightly bound to System.Web and things like HttpContext.Current and the VirtualPathProvider that running them in a standalone console application is really quite unpleasant.&lt;/p&gt;  &lt;p&gt;Well, with a bit of hacking around and some help from &lt;a href=&quot;http://twitter.com/#!/RobertTheGrey&quot;&gt;@RobertTheGrey&lt;/a&gt;, I’ve finally got the awesome &lt;a href=&quot;http://sparkviewengine.com/&quot;&gt;Spark view engine&lt;/a&gt; running within a console app. No fake VirtualPathProviders, no mocking or spoofing HttpContext.Current – it just works.&lt;/p&gt;  &lt;p&gt;The secret is this little snippet of code here:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;var templateFolder = @&amp;quot;D:\Templates\&amp;quot;;        &lt;br /&gt;var viewFolderParameters = new Dictionary&amp;lt;string, string&amp;gt; {         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {&amp;quot;basePath&amp;quot;, templateFolder}         &lt;br /&gt;};         &lt;br /&gt;var settings = new SparkSettings();         &lt;br /&gt;settings.SetPageBaseType(typeof(TemplateBase));         &lt;br /&gt;settings.AddViewFolder(ViewFolderType.FileSystem, viewFolderParameters);         &lt;br /&gt;engine = new SparkViewEngine(settings);&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;which spins up a fresh SparkSettings configuration object, tells it to use your own TemplateBase class and the templates folder you’ve specified. The method that actually does the population looks like this:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;public string Populate(string templateFileName, object data) {&amp;#160; &lt;br /&gt;&amp;#160; var writer = new StringWriter();&amp;#160; &lt;br /&gt;&amp;#160; var descriptor = new SparkViewDescriptor();&amp;#160; &lt;br /&gt;&amp;#160; descriptor.AddTemplate(templateFileName);&amp;#160; &lt;br /&gt;&amp;#160; var view = (TemplateBase)engine.CreateInstance(descriptor);&amp;#160; &lt;br /&gt;&amp;#160; try {&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; view.ViewData = new ViewDataDictionary(data);&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; view.RenderView(writer);&amp;#160; &lt;br /&gt;&amp;#160; } finally {&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; engine.ReleaseInstance(view);&amp;#160; &lt;br /&gt;&amp;#160; }&amp;#160; &lt;br /&gt;&amp;#160; return (writer.ToString());&amp;#160; &lt;br /&gt;}&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;so you end up with little snippets like this:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;foreach(var user in userRepository.RetrieveUsersWhoShouldGetWelcomeEmails()) {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; var htmlBody = templater.Populate(&amp;quot;welcome_html.spark”, user):        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; var textBody = templater.Populate(“welcome_text.spark”, user):        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; mailServer.SendMail(&lt;/font&gt;&lt;a href=&quot;mailto:&amp;ldquo;me@mysite.com&quot;&gt;&lt;font face=&quot;Consolas&quot;&gt;“me@mysite.com&lt;/font&gt;&lt;/a&gt;&lt;font face=&quot;Consolas&quot;&gt;”, user.Email, “Welcome!”, textBody, htmlBody);       &lt;br /&gt;}&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;There’s a &lt;a href=&quot;https://github.com/loudej/spark/tree/master/src/Samples/DirectUsage/SparkEmailMerge&quot;&gt;full working example in the Spark repository on GitHub&lt;/a&gt; if you’re interested.&lt;/p&gt;  &lt;p&gt;&lt;font size=&quot;1&quot;&gt;Spark photo © &lt;a href=&quot;http://www.flickr.com/photos/scholewiak/145096304/sizes/m/in/photostream/&quot;&gt;SCholewiak via Flickr&lt;/a&gt; – used under Creative Commons attribution license.&lt;/font&gt;&lt;/p&gt;  </description>
          <pubDate>2010-11-26T11:46:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2010/11/26/sending-templated-e-mail-using-spark.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2010/11/26/sending-templated-e-mail-using-spark.html</guid>
        </item>
      
    
      
        <item>
          <title>Should “Cancel” cancel the cancellation, or just cancel the cancellation cancellation?</title>
          <description>&lt;p&gt;This is from SagePay’s new MySagePay admin system, which has brought us all so much joy these past weeks and will go down in history as my second favourite piece of web design ever, after &lt;a href=&quot;http://www.kibo.com/webtv/webtv.html&quot;&gt;Kibo’s Optimised for WebTV! page&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;Today’s happy little discovery. You click to view a transaction. You think “Oops, wrong transaction.” Years of Windows experience means you instinctively hit &lt;strong&gt;Cancel &lt;/strong&gt;– the button marked Cancel, that’s at the bottom-right of the window, just like it is on every other dialog box you’ve seen in the last 25 years. But guess what? In this case, the &lt;strong&gt;Cancel&lt;/strong&gt; button actually &lt;em&gt;cancels the transaction&lt;strong&gt;, &lt;/strong&gt;&lt;/em&gt;rather than closing the window and backing out of the operation.&lt;/p&gt;  &lt;p&gt;Of course, it wouldn’t do anything that drastic without a confirmation dialog… guess what &lt;strong&gt;that&lt;/strong&gt; dialog looks like?&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh4.ggpht.com/_de7B8BtZZIw/TNGxy31rbQI/AAAAAAAAADw/45KcT3c-l20/s1600-h/image%5B7%5D.png&quot;&gt;&lt;img style=&quot;background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; margin-left: auto; border-top: 0px; margin-right: auto; border-right: 0px; padding-top: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh6.ggpht.com/_de7B8BtZZIw/TNGxzVxTJQI/AAAAAAAAAD0/Q5hbKVlp-NU/image_thumb%5B5%5D.png?imgmax=800&quot; width=&quot;804&quot; height=&quot;492&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;“Cancel” means exactly the same thing in practically every application, window manager and operating system that’s been released for the last 25 years. The idea that anyone might think that using it for a different purpose in a different context is… just unbelievable. &lt;/p&gt;  &lt;p&gt;Moral of the story: if your ubiquitous language contains a word or phrase that everyone &lt;em&gt;knows&lt;/em&gt; means something else – change the ubiquitous language. Think of a better word. Here’s some to get you started… Unauthenticate. Withdraw. Retract. Deauthenticate. Discard. Abandon. Reject. Decline. Even “Cancel Transaction” would have been better than this.&lt;/p&gt;  &lt;p&gt;So – to cancel a transaction, you hit Cancel, then Cancel again. To cancel the cancellation, you hit Cancel, but DON’T press Cancel, press X and then X.&lt;/p&gt;  &lt;p&gt;(and yeah, to cancel the &lt;em&gt;dialog&lt;/em&gt; as opposed to the transaction, you click the groovy little X in the top corner, because this is Web 2.0, dontcha know?)&lt;/p&gt;  &lt;p&gt;Obvious, really.&lt;/p&gt;</description>
          <pubDate>2010-11-03T19:02:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2010/11/03/should-cancel-cancel-cancellation-or.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2010/11/03/should-cancel-cancel-cancellation-or.html</guid>
        </item>
      
    
      
        <item>
          <title>“If It Ain’t Broke…” – Why Good Code Doesn’t Guarantee Happy Users</title>
          <description>&lt;p&gt;For years, my sites have processed online payments using a firm called Protx. Protx was acquired by Sage a few years back, rebranded as &lt;a href=&quot;http://www.sagepay.com/&quot;&gt;SagePay&lt;/a&gt;, and since then they’ve been gradually making changes to their system. Nothing too drastic, and always with plenty of warning that changes were forthcoming. Their payment API is generally rock-solid, powerful, flexible, and has allowed us to cope with all sorts of complicated payment scenarios without too much fuss. So far, so good.&lt;/p&gt;  &lt;p&gt;About two months ago, SagePay announced that a preview of their new administration system was running on their test servers, and they invited everyone to go and have a look. Their old system was kinda clunky, but it worked, and our accounts team had been using it quite happily for years. So I went. I looked. The new system had lots of shiny Ajax – OK, fair enough, this is 2010 after all – and horizontal scrolling. No, really. Check this out:&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh5.ggpht.com/_de7B8BtZZIw/TMWYRsMxWHI/AAAAAAAAADg/jCmZCnP_W0k/s1600-h/image%5B6%5D.png&quot;&gt;&lt;img style=&quot;background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto; padding-top: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh6.ggpht.com/_de7B8BtZZIw/TMWYR8F5AxI/AAAAAAAAADk/BmtEcSFPysw/image_thumb%5B4%5D.png?imgmax=800&quot; width=&quot;335&quot; height=&quot;258&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;I have a 1600x1200 screen because information is nice  and I like being able to see lots of it at once. SagePay have decided, for some reason, that our payment transactions are best presented in a fixed-size 800x300px window. Fine, though – that’s what technical previews are for; lots of people reported this and were &lt;a href=&quot;https://support.sagepay.com/forum/Topic11656-26-2.aspx#bm11685&quot;&gt;assured it would be sorted out&lt;/a&gt;. &lt;/p&gt;  &lt;p&gt;Well, the new system launched over the weekend. The old one’s gone, they new one’s live, and things like the horizontal scrolling have &lt;em&gt;not&lt;/em&gt; been sorted out, and customers’ reactions to the new system is, well, a bit negative. A thread &lt;a href=&quot;https://support.sagepay.com/forum/Topic11656-26-2.aspx&quot;&gt;on their support forums&lt;/a&gt; is full of gems like:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;“A complete crock. Should be rolled back immediately.” (&lt;a href=&quot;https://support.sagepay.com/forum/FindPost11697.aspx&quot;&gt;here&lt;/a&gt;)&lt;/p&gt;    &lt;p&gt;“this is a complete and utter disaster. This system needs rolling back as soon as possible.” (&lt;a href=&quot;https://support.sagepay.com/forum/FindPost11699.aspx&quot;&gt;here&lt;/a&gt;)&lt;/p&gt;    &lt;p&gt;“The system is rejecting the order because of the delivery address line 2 which contained 58-62 xxxxx Road. Your system is not allowing the - in the address (This address is the formatted address that is provided by Royal Mail). This needs to be fixed quickly.” (&lt;a href=&quot;https://support.sagepay.com/forum/FindPost11707.aspx&quot;&gt;here&lt;/a&gt;)&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;OK, fine. Big company rolls out shiny upgrade, upsets customers, breaks APIs, whatever. Big companies do this a lot. &lt;/p&gt;  &lt;p&gt;But - I was expecting something different here, because last week, Mat – SagePay’s “Chief Nerd” – published &lt;a href=&quot;http://sagepay.wordpress.com/2010/10/18/if-it-aint-broke/&quot;&gt;this blog post&lt;/a&gt;, in which he talks quite rationally and eloquently about the forthcoming improvements. And it sounds like quite good stuff, too. Stuff like:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;“To accommodate this we’ve changed the way we develop software, not only at a language level, but also at the process level.  We’ve embraced Test Driven Development, Agile methodologies, Continuous Integration and parallel automated testing.”&lt;/p&gt;    &lt;p&gt;“By writing tests first and only then writing code that passes those tests, we know that our software does what it is supposed to.”&lt;/p&gt;    &lt;p&gt;“This demonstrable code quality gives our developers much more confidence in their code, frees them to refactor software that behaves sub-optimally, and ensures the test team’s time isn’t wasted on trivial bugs.”&lt;/p&gt;    &lt;p align=&quot;right&quot;&gt; (quotes from &lt;a title=&quot;http://sagepay.wordpress.com/2010/10/18/if-it-aint-broke/&quot; href=&quot;http://sagepay.wordpress.com/2010/10/18/if-it-aint-broke/&quot;&gt;http://sagepay.wordpress.com/2010/10/18/if-it-aint-broke/&lt;/a&gt;)&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;OK. Mat, I believe you. If your comments about improved capacity and security are correct, this actually represents a huge achievement for the SagePay technical team. But I have to wonder… if the chief nerd is doing everything right, why are the customers so upset?&lt;/p&gt;  &lt;div style=&quot;padding-bottom: 8px; line-height: 2em; padding-left: 8px; width: 240px; padding-right: 8px; float: left; font-size: large; margin-right: 8px; border-right: #999999 1px solid; padding-top: 8px&quot;&gt;   &lt;p align=&quot;center&quot;&gt;&lt;span &gt;&lt;span &gt;“&lt;/span&gt;&lt;strong&gt;&lt;span &gt;…if the&lt;/span&gt;          
CHIEF NERD          
&lt;span &gt;is doing&lt;/span&gt; &lt;span &gt;EVERYTHING&lt;/span&gt; RIGHT,          
&lt;span &gt;why are the           
&lt;/span&gt;&lt;span &gt;CUSTOMERS &lt;/span&gt;&lt;span &gt;so&lt;/span&gt; UPSET?&lt;/strong&gt;&lt;/span&gt;&lt;span &gt;”&lt;/span&gt;&lt;/p&gt; &lt;/div&gt;  &lt;p&gt;Well, this is what I think probably happened. First, they’ve been working on this since June 2009; the first real customer preview was in September 2010, and it’s now live, just over a month later. That’s not a series of incremental improvements, delivering discrete chunks of business value every few weeks or months. That’s the &lt;a href=&quot;http://www.joelonsoftware.com/articles/fog0000000069.html&quot;&gt;big&lt;/a&gt; &lt;a href=&quot;http://chadfowler.com/2006/12/27/the-big-rewrite&quot;&gt;rewrite&lt;/a&gt; wearing a not-very-convincing Agile disguise. &lt;/p&gt;  &lt;p&gt;Second, one of the core tenets of the Agile Manifesto is “&lt;strong&gt;customer collaboration&lt;/strong&gt; over contract negotiation” – and to get that right, you have to know who your customers really are, and &lt;em&gt;collaborate&lt;/em&gt; with them. This is tricky, because to the developers, the ‘customer’ is probably the product owner – but in reality, the customer should be the person who’s going to use the product. Now a firm like SagePay probably can’t call their customers in off the street to collaborate on a big project like this – but they &lt;em&gt;can&lt;/em&gt; talk to them.  &lt;/p&gt;  &lt;p&gt;I think it’s really easy – particularly in big companies – to get this wrong. I have made this mistake many times. You launch version 1.0, you spend a couple of years babysitting it and fielding the support calls, and you end up thinking you know exactly what all your customers want… except you don’t, because &lt;em&gt;happy users never call support&lt;/em&gt;. They just use the product and get on with their lives. You can find out what your users &lt;em&gt;don’t&lt;/em&gt; like from support calls and complaints, but to find out what they &lt;em&gt;do&lt;/em&gt; like, you need to get out there and talk to them, and it sounds like many of the features in the new SagePay system were based on feature requests and complaints from a vocal minority who weren’t really representative of the user base as a whole. It feels like you’re doing exactly the right thing, but you’re not actually &lt;em&gt;collaborating&lt;/em&gt; with your customer.&lt;/p&gt;  &lt;div style=&quot;padding-bottom: 8px; line-height: 2em; padding-left: 8px; width: 240px; padding-right: 8px; float: right; font-size: large; padding-top: 8px&quot;&gt;   &lt;p align=&quot;center&quot;&gt;&lt;span &gt;“&lt;strong&gt;REAL USERS &lt;/strong&gt;&lt;/span&gt; 
don&apos;t care about      
&lt;span &gt;&lt;strong&gt;TEST DATA&lt;/strong&gt;”&lt;/span&gt;&lt;/p&gt; &lt;/div&gt;  &lt;p&gt;Second – technical previews are all very well, but &lt;strong&gt;real users don’t care about test data&lt;/strong&gt;. When I tried the new MySagePay tech preview last month, it had about a dozen transactions in it, from 2006, when we were testing an upgrade to our own payment system. That’s not a realistic test of the system, because there’s no incentive to actually do anything with it. The only way to get &lt;strong&gt;real feedback &lt;/strong&gt;is to get &lt;strong&gt;real people &lt;/strong&gt;to use the software to do &lt;strong&gt;real work &lt;/strong&gt;– and &lt;strong&gt;you can’t do real work with test data&lt;/strong&gt;. &lt;/p&gt;  &lt;p&gt;In a nutshell, I think they missed the distinction between &lt;strong&gt;have we built the right thing?&lt;/strong&gt; and &lt;strong&gt;have we built it right?&lt;/strong&gt; &lt;/p&gt;  &lt;p&gt;TDD, Continuous Integration, refactoring – these will all help you &lt;strong&gt;build it right, &lt;/strong&gt;but agile’s also about making sure you’re building the &lt;strong&gt;right thing, &lt;/strong&gt;and I think SagePay dropped the ball on this one.&lt;/p&gt;  &lt;p&gt;Mat even says (my emphasis)&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;“…any change introduces risk. … We might produce new software that performs identically to the old for a given payment protocol only to find that two thousand customers are &lt;strong&gt;using a non-documented “feature” of the old system that we’ve now written out.&quot;&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Well, that’s &lt;em&gt;exactly&lt;/em&gt; what they did. On Friday, &lt;a title=&quot;https://live.sagepay.com/txstatus/txstatus.asp&quot; href=&quot;https://live.sagepay.com/txstatus/txstatus.asp&quot;&gt;https://live.sagepay.com/txstatus/txstatus.asp&lt;/a&gt; was returning real-time status for previous payment transactions – and now it’s gone. Vanished. What used to take 500 milliseconds in an automated script now takes a real person 90 seconds or so – including all that lovely horizontal scrolling they have to do.&lt;/p&gt;  &lt;p&gt;Still, all is not lost. If SagePay really do have a clean, new architecture, and full test coverage, and a decent agile process in place, then it should be straightforward to respond to this customer feedback.&lt;strong&gt; Respond to change, instead of following a plan.&lt;/strong&gt; Some tweaks at the presentation layer, maybe a couple of new properties on various view models and controllers, a handful of new methods on the supporting services, and it shouldn’t take long to deliver a product that combines the scalability and security of the new system with a UI that does exactly what the customers need. &lt;/p&gt;  &lt;p&gt;Mat, if you’re reading this, I’d be really interested to hear what you guys did today whilst your support team were running around putting out fires. I’d love to see what your product backlog looks like right now, and how you’re condensing the torrent of feedback into user stories and work items. There’s not enough sharing in this industry, and if you and your team can be as open about things today as you were this time last week, you’ll probably help a whole bunch of people – including me – next time we find ourselves babysitting a tricky launch.&lt;/p&gt;</description>
          <pubDate>2010-10-26T01:23:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2010/10/26/if-it-aint-broke-why-good-code-doesnt.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2010/10/26/if-it-aint-broke-why-good-code-doesnt.html</guid>
        </item>
      
    
      
        <item>
          <title>Making iPhone Ringtones using Free Software</title>
          <description>&lt;p&gt;iPhone ringtones are audio files encoded as MPEG-4 audio and then renamed to use the .m4r file extension. If you don’t fancy paying £0.99 for twelve seconds of Van Halen, it’s pretty easy to roll your own iPhone ringtones, and you don’t even need to buy any software to do it. You’ll need:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;&lt;a href=&quot;http://ffmpeg.org/&quot;&gt;&lt;img style=&quot;border-bottom: 0px; border-left: 0px; margin: 0px 0px 0px 20px; display: inline; border-top: 0px; border-right: 0px&quot; border=&quot;0&quot; align=&quot;right&quot; src=&quot;http://ffmpeg.org/ffmpeg-logo.png&quot; width=&quot;90&quot; height=&quot;30&quot; /&gt;&lt;/a&gt;An MP3 or WAV file of the song or noise that you want to use as a ringtone. Legally speaking, this could constitute copyright infringement, so please stick to original recordings or your own compositions. &lt;/li&gt;    &lt;li&gt;&lt;a href=&quot;http://audacity.sourceforge.net/&quot;&gt;&lt;img style=&quot;border-bottom: 0px; border-left: 0px; margin: 0px 0px 0px 20px; display: inline; border-top: 0px; border-right: 0px&quot; border=&quot;0&quot; align=&quot;right&quot; src=&quot;http://audacity.sourceforge.net/images/Audacity-logo-r_50pct.jpg&quot; width=&quot;90&quot; height=&quot;39&quot; /&gt;Audacity&lt;/a&gt;, an open-source audio editor. &lt;/li&gt;    &lt;li&gt;&lt;a href=&quot;http://ffmpeg.org/&quot;&gt;ffmpeg&lt;/a&gt;, the most awesomely powerful audio/video encoder ever. You’ll want to grab one of the Windows binary builds from &lt;a href=&quot;http://ffmpeg.arrozcru.org/autobuilds/&quot;&gt;http://ffmpeg.arrozcru.org/autobuilds/&lt;/a&gt; &lt;/li&gt; &lt;/ol&gt;  &lt;blockquote&gt;   &lt;p&gt;Aside: I normally have Cygwin, a set of command-line Unix-a-like tools for Windows, installed into c:\windows\cygwin\, and then I add C:\Windows\cygwin\bin\ to my system path. Having done this, it makes sense to install ffmpeg into c:\windows\cygwin\ – it uses the same bin/doc/lib structure as most *nix ports and means I don’t have to add another path to the system PATH variable.&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Import your source track into Audacity. Highlight the section you want, copy it, Ctrl-N to get a new file, and paste it. &lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh6.ggpht.com/_de7B8BtZZIw/TLOewYotX5I/AAAAAAAAADQ/GleZBzpDVm0/s1600-h/image%5B9%5D.png&quot;&gt;&lt;img style=&quot;border-right-width: 0px; margin: 10px auto; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh3.ggpht.com/_de7B8BtZZIw/TLOe_0HcebI/AAAAAAAAADU/mqXgPDz0ZLA/image_thumb%5B5%5D.png?imgmax=800&quot; width=&quot;640&quot; height=&quot;449&quot; /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;At this point, playing around with effects might be worthwhile – especially selecting the first 1-2 seconds of the clip and using Effect –&amp;gt; Fade In, and applying a corresponding Fade out to the last second or two of your clip. When you’re happy with it, File –&amp;gt; Export as WAV…, and put it somewhere useful – D:\Ringtones\ works for me.&lt;/p&gt;  &lt;p&gt;Then you’ll want to turn your WAV into an m4a file:&lt;/p&gt;  &lt;blockquote&gt;   &lt;pre&gt;&lt;strong&gt;D:\ringtones&amp;gt;ffmpeg –i ringtone.wav ringtone.m4a&lt;/strong&gt;

Input #0, wav, from &apos;ringtone.wav&apos;:
  Duration: 00:00:40.30, bitrate: 1411 kb/s
    Stream #0.0: Audio: pcm_s16le, 44100 Hz, 2 channels, s16, 1411 kb/s
Output #0, ipod, to &apos;ringtone.m4a&apos;:
  Metadata:
    encoder         : Lavf52.80.0
    Stream #0.0: Audio: aac, 44100 Hz, 2 channels, s16, 64 kb/s
Stream mapping:
  Stream #0.0 -&amp;gt; #0.0
Press [q] to stop encoding
size=     427kB time=40.31 bitrate=  86.8kbits/s
video:0kB audio:413kB global headers:0kB muxing overhead 3.447648%&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once you’re done, either via Windows Explorer or the rename command, rename ringtone.m4a to &lt;strong&gt;ringtone.m4r&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Finally, fire up iTunes, go to File –&amp;gt; Add File to Library… and browse to your new ringtone.m4r file. After a moment’s processing, it should show up in the Ringtones library. Right-click, Get Info… and you can replace the name, title, artist and so on, and then you just need to make sure it’s selected under the Ringtones heading on your iPhone sync menu.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://lh4.ggpht.com/_de7B8BtZZIw/TLOfIqcuEKI/AAAAAAAAADY/60WAgw8DNq0/s1600-h/image%5B7%5D.png&quot;&gt;&lt;img style=&quot;border-right-width: 0px; margin: 10px auto; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh4.ggpht.com/_de7B8BtZZIw/TLOfREkwukI/AAAAAAAAADc/Nn6iiRO3X-0/image_thumb%5B3%5D.png?imgmax=800&quot; width=&quot;640&quot; height=&quot;364&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Happy ringtoning.&lt;/p&gt;  </description>
          <pubDate>2010-10-11T23:35:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2010/10/11/making-iphone-ringtones-using-free.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2010/10/11/making-iphone-ringtones-using-free.html</guid>
        </item>
      
    
      
        <item>
          <title>/usr/bin/tawgo: Instant Geek Cred for Movies</title>
          <description>&lt;p&gt;I had this idea &lt;a href=&quot;http://it.slashdot.org/comments.pl?sid=184239&amp;amp;cid=15212286&quot;&gt;a while back&lt;/a&gt;. You know how computers in movies are always some sort of interactive brightly-coloured touch-screen kind of deal? (“It’s a UNIX system… I know this!”) And you remember how excited all us techno-geeks got when Trinity used sshnuke to crack the security mainframe system in The Matrix Reloaded?&lt;/p&gt;  &lt;p&gt;There should be a utility /usr/bin/tawgo, that’s standard on all Linux distros, that combines the audience appeal of brightly coloured cheese with the geek cred of technical accuracy.&lt;/p&gt;  &lt;pre&gt;[root@fortress]$ cd /home/dr.evil/
[root@fortress]$ tawgo &amp;quot;PREPARING TO COPY SECRET FILES...&amp;quot;
[root@fortress]$ cp -Rf * /mnt/floppy
[root@fortress]$ tawgo &amp;quot;SECRET FILES COPIED&amp;quot;

[root@fortress]$ tawgo --help

tawgo: Tell Audience What&apos;s Going On

Usage: tawgo [option] MESSAGE

Displays MESSAGE in big bright coloured letters, probably in some sort of futuristic animated dialog box.

-a --animation Show cheesy animation
-w --warning Use yellow &amp;amp; black warning stripes
-s --self-destruct Initiate fake countdown sequence
-v --voice Reads MESSAGE in a Female Computer Voice

Use -v -s if you need Female Computer Voice counting down the seconds to our hero&apos;s impending destruction.

[root@fortress]$ tawgo &amp;quot;INITIATING SATELLITE ALIGNMENT&amp;quot;
[root@fortress]$ /usr/sbin/comsatctl -a --lat=53.47.6 --lon=1.29.2
[root@fortress]$ tawgo &amp;quot;SATELLITE ALIGNED.&amp;quot;
[root@fortress]$ tawgo &amp;quot;BEGINNING FIRING SEQUENCE&amp;quot;
[root@fortress]$ /usr/sbin/comsatctl --target 01 --fire
[root@fortress]$ tawgo -v -s &amp;quot;FIRING SEQUENCE INITIATED&amp;quot;&lt;/pre&gt;

&lt;p&gt;They’d save a fortune on expensive UI mockups, too – all you’d need is a laptop running Ubuntu and you’ve got your snazzy whizz-bang computer hacker Unix system all ready to go.&lt;/p&gt;  </description>
          <pubDate>2010-10-07T22:27:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2010/10/07/usrbintawgo-instant-geek-cred-for.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2010/10/07/usrbintawgo-instant-geek-cred-for.html</guid>
        </item>
      
    
      
        <item>
          <title>Stap Me Vitals! Scanning Domain Servers using System.Management</title>
          <description>&lt;p&gt;In preparation for an overhaul of our hosting &amp;amp; network infrastructure, I wanted to gather stats on exactly what was running on all of our remote servers – stuff like how much physical memory is installed in the servers, what’s their Dell service tag, what sort of disk interface they’re running, what version of Windows, that kind of thing.&lt;/p&gt;  &lt;p&gt;There’s a command-line tool &lt;a href=&quot;http://support.microsoft.com/kb/290216&quot;&gt;wmic.exe&lt;/a&gt; that does some neat stuff by hooking into the Windows Management Instrumentation interface – little snippets like:&lt;/p&gt;  &lt;div id=&quot;codeSnippetWrapper&quot;&gt;   &lt;pre style=&quot;border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Consolas&amp;#39;, &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px&quot; id=&quot;codeSnippet&quot;&gt;&lt;p&gt;&lt;strong&gt;C:\&amp;gt;&lt;/strong&gt;wmic /node:myserver cpu get description&lt;br /&gt;Description&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Intel(R) Xeon(TM) CPU 3.00GHz&lt;br /&gt;Intel(R) Xeon(TM) CPU 3.00GHz&lt;br /&gt;Intel(R) Xeon(TM) CPU 3.00GHz&lt;br /&gt;Intel(R) Xeon(TM) CPU 3.00GHz&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;C:\&amp;gt;&lt;/p&gt;&lt;/pre&gt;

  &lt;br /&gt;&lt;/div&gt;

&lt;p&gt;- hey, looks like myserver has a quad-core Xeon CPU. Nice. Anyway, doing this across a whole lot of properties on a whole lot of servers would clearly be a bit time-consuming, so here’s how you do it using C# and the System.Management namespaces. The username/password specified in ConnectionOptions will need admin rights on all the servers you’re connecting to, and I’m sure you can reformat the output to look a little nicer, but in a nutshell, this’ll scan all the servers you specify and dump their vitals into D:\servers.txt&lt;/p&gt;

&lt;p&gt;Simple - especially once you work out that the documentation for the cryptic magic strings inside the WMI classes is at &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/aa394084.aspx&quot;&gt;http://msdn.microsoft.com/en-us/library/aa394084.aspx&lt;/a&gt;&lt;/p&gt;

&lt;div id=&quot;codeSnippetWrapper&quot;&gt;
  &lt;pre style=&quot;border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Consolas&amp;#39;, &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px&quot; id=&quot;codeSnippet&quot;&gt;&lt;p&gt;&lt;span style=&quot;color: #0000ff&quot;&gt;using&lt;/span&gt; System;&lt;br /&gt;&lt;span style=&quot;color: #0000ff&quot;&gt;using&lt;/span&gt; System.Collections.Generic;&lt;br /&gt;&lt;span style=&quot;color: #0000ff&quot;&gt;using&lt;/span&gt; System.IO;&lt;br /&gt;&lt;span style=&quot;color: #0000ff&quot;&gt;using&lt;/span&gt; System.Linq;&lt;br /&gt;&lt;span style=&quot;color: #0000ff&quot;&gt;using&lt;/span&gt; System.Management;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #0000ff&quot;&gt;namespace&lt;/span&gt; ServerScan {&lt;br /&gt;    &lt;span style=&quot;color: #0000ff&quot;&gt;class&lt;/span&gt; Program {&lt;br /&gt;&lt;br /&gt;        &lt;span style=&quot;color: #0000ff&quot;&gt;static&lt;/span&gt; &lt;span style=&quot;color: #0000ff&quot;&gt;string&lt;/span&gt;[] servers = &lt;span style=&quot;color: #0000ff&quot;&gt;new&lt;/span&gt; &lt;span style=&quot;color: #0000ff&quot;&gt;string&lt;/span&gt;[] { &lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;hugh&amp;quot;&lt;/span&gt;, &lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;pugh&amp;quot;&lt;/span&gt;, &lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;barney&amp;quot;&lt;/span&gt;, &lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;mcgrew&amp;quot;&lt;/span&gt;, &lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;cuthbert&amp;quot;&lt;/span&gt;, &lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;dibble&amp;quot;&lt;/span&gt;, &lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;grubb&amp;quot;&lt;/span&gt;};&lt;/p&gt;&lt;p&gt;&lt;font color=&quot;#00ff00&quot;&gt; &lt;/font&gt;&lt;font color=&quot;#008000&quot;&gt;       // see notes on Win32 instrumentation classes at &lt;/font&gt;&lt;a href=&quot;http://msdn.microsoft.com/en-us/library/aa394084.aspx&quot;&gt;&lt;font color=&quot;#008000&quot;&gt;http://msdn.microsoft.com/en-us/library/aa394084.aspx&lt;/font&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span style=&quot;color: #0000ff&quot;&gt;static&lt;/span&gt; Dictionary&amp;lt;&lt;span style=&quot;color: #0000ff&quot;&gt;string&lt;/span&gt;, &lt;span style=&quot;color: #0000ff&quot;&gt;string&lt;/span&gt;[]&amp;gt; wmiClasses = &lt;span style=&quot;color: #0000ff&quot;&gt;new&lt;/span&gt; Dictionary&amp;lt;&lt;span style=&quot;color: #0000ff&quot;&gt;string&lt;/span&gt;, &lt;span style=&quot;color: #0000ff&quot;&gt;string&lt;/span&gt;[]&amp;gt; {&lt;br /&gt;            { &lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;Win32_DiskDrive&amp;quot;&lt;/span&gt;, &lt;span style=&quot;color: #0000ff&quot;&gt;new&lt;/span&gt; &lt;span style=&quot;color: #0000ff&quot;&gt;string&lt;/span&gt;[] { &lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;Caption&amp;quot;&lt;/span&gt;, &lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;InterfaceType&amp;quot;&lt;/span&gt;, &lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;MediaType&amp;quot;&lt;/span&gt;, &lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;Name&amp;quot;&lt;/span&gt;,&lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;Size&amp;quot;&lt;/span&gt; }},&lt;br /&gt;            { &lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;Win32_PhysicalMedia&amp;quot;&lt;/span&gt;, &lt;span style=&quot;color: #0000ff&quot;&gt;new&lt;/span&gt; &lt;span style=&quot;color: #0000ff&quot;&gt;string&lt;/span&gt;[] { &lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;Tag&amp;quot;&lt;/span&gt;, &lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;SerialNumber&amp;quot;&lt;/span&gt; }},&lt;br /&gt;            { &lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;Win32_Processor&amp;quot;&lt;/span&gt;, &lt;span style=&quot;color: #0000ff&quot;&gt;new&lt;/span&gt; &lt;span style=&quot;color: #0000ff&quot;&gt;string&lt;/span&gt;[] { &lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;Name&amp;quot;&lt;/span&gt;, &lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;ExtClock&amp;quot;&lt;/span&gt;, &lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;CurrentClockSpeed&amp;quot;&lt;/span&gt;, &lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;DeviceID&amp;quot;&lt;/span&gt; }},&lt;br /&gt;            { &lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;Win32_OperatingSystem&amp;quot;&lt;/span&gt;, &lt;span style=&quot;color: #0000ff&quot;&gt;new&lt;/span&gt; &lt;span style=&quot;color: #0000ff&quot;&gt;string&lt;/span&gt;[] { &lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;Name&amp;quot;&lt;/span&gt;, &lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;CSDVersion&amp;quot;&lt;/span&gt;, &lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;Description&amp;quot;&lt;/span&gt;, &lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;TotalVisibleMemorySize&amp;quot;&lt;/span&gt; }},&lt;br /&gt;            { &lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;Win32_SCSIController&amp;quot;&lt;/span&gt;, &lt;span style=&quot;color: #0000ff&quot;&gt;new&lt;/span&gt; &lt;span style=&quot;color: #0000ff&quot;&gt;string&lt;/span&gt;[] { &lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;Caption&amp;quot;&lt;/span&gt; }},&lt;br /&gt;            { &lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;Win32_SystemEnclosure&amp;quot;&lt;/span&gt;, &lt;span style=&quot;color: #0000ff&quot;&gt;new&lt;/span&gt; &lt;span style=&quot;color: #0000ff&quot;&gt;string&lt;/span&gt;[] { &lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;SerialNumber&amp;quot;&lt;/span&gt; }}&lt;br /&gt;        };&lt;br /&gt;&lt;br /&gt;        &lt;span style=&quot;color: #0000ff&quot;&gt;static&lt;/span&gt; &lt;span style=&quot;color: #0000ff&quot;&gt;void&lt;/span&gt; Main(&lt;span style=&quot;color: #0000ff&quot;&gt;string&lt;/span&gt;[] args) {&lt;br /&gt;            var output = File.CreateText(&lt;span style=&quot;color: #006080&quot;&gt;@&amp;quot;D:\servers.csv&amp;quot;&lt;/span&gt;);&lt;br /&gt;            ConnectionOptions options = &lt;span style=&quot;color: #0000ff&quot;&gt;new&lt;/span&gt; ConnectionOptions();&lt;br /&gt;            options.Username = &lt;span style=&quot;color: #006080&quot;&gt;@&amp;quot;MYDOMAIN\big.boss&amp;quot;&lt;/span&gt;;&lt;br /&gt;            options.Password = &lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;password&amp;quot;&lt;/span&gt;;&lt;br /&gt;            &lt;span style=&quot;color: #0000ff&quot;&gt;foreach&lt;/span&gt; (var server &lt;span style=&quot;color: #0000ff&quot;&gt;in&lt;/span&gt; servers) {&lt;br /&gt;                ManagementScope scope = &lt;span style=&quot;color: #0000ff&quot;&gt;new&lt;/span&gt; ManagementScope(String.Format(&lt;span style=&quot;color: #006080&quot;&gt;@&amp;quot;\\{0}\root\cimv2&amp;quot;&lt;/span&gt;, server), options);&lt;br /&gt;                &lt;span style=&quot;color: #0000ff&quot;&gt;try&lt;/span&gt; {&lt;br /&gt;                    scope.Connect();&lt;br /&gt;                    &lt;span style=&quot;color: #0000ff&quot;&gt;foreach&lt;/span&gt; (var wmiClass &lt;span style=&quot;color: #0000ff&quot;&gt;in&lt;/span&gt; wmiClasses.Keys) {&lt;br /&gt;                        ObjectQuery query = &lt;span style=&quot;color: #0000ff&quot;&gt;new&lt;/span&gt; ObjectQuery(&lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;SELECT * FROM &amp;quot;&lt;/span&gt; + wmiClass);&lt;br /&gt;                        ManagementObjectSearcher searcher = &lt;span style=&quot;color: #0000ff&quot;&gt;new&lt;/span&gt; ManagementObjectSearcher(scope, query);&lt;br /&gt;                        Console.WriteLine(&lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;Searching {0} / {1}&amp;quot;&lt;/span&gt;, server, wmiClass);&lt;br /&gt;                        var results = searcher.Get();&lt;br /&gt;                        &lt;span style=&quot;color: #0000ff&quot;&gt;foreach&lt;/span&gt; (var result &lt;span style=&quot;color: #0000ff&quot;&gt;in&lt;/span&gt; results) {&lt;br /&gt;                            &lt;span style=&quot;color: #0000ff&quot;&gt;foreach&lt;/span&gt; (var thing &lt;span style=&quot;color: #0000ff&quot;&gt;in&lt;/span&gt; result.Properties) {&lt;br /&gt;                                &lt;span style=&quot;color: #0000ff&quot;&gt;if&lt;/span&gt; (!wmiClasses[wmiClass].Contains(thing.Name)) &lt;span style=&quot;color: #0000ff&quot;&gt;continue&lt;/span&gt;;&lt;br /&gt;                                output.Write(String.Format(&lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;{0}\t{1}\t{2}\t{3}\r\n&amp;quot;&lt;/span&gt;, server, wmiClass, thing.Name, thing.Value));&lt;br /&gt;                            }&lt;br /&gt;                        }&lt;br /&gt;                    }&lt;br /&gt;                } &lt;span style=&quot;color: #0000ff&quot;&gt;catch&lt;/span&gt; (Exception ex) {&lt;br /&gt;                    output.Write(String.Format(&lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;{0}\tFAIL\tFAIL\t{1}\r\n&amp;quot;&lt;/span&gt;, server, ex.Message));&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;            output.Close();&lt;br /&gt;            Console.Write(&lt;span style=&quot;color: #006080&quot;&gt;&amp;quot;All Done!&amp;quot;&lt;/span&gt;);&lt;br /&gt;            Console.ReadKey(&lt;span style=&quot;color: #0000ff&quot;&gt;false&lt;/span&gt;);&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/p&gt;&lt;/pre&gt;

  &lt;br /&gt;&lt;/div&gt;  </description>
          <pubDate>2010-10-04T17:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2010/10/04/stap-me-vitals-scanning-domain-servers.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2010/10/04/stap-me-vitals-scanning-domain-servers.html</guid>
        </item>
      
    
      
        <item>
          <title>Five Things I Wish My iPhone Did</title>
          <description>&lt;h4&gt;Wake Me Up When I Get To Bristol&lt;/h4&gt;  &lt;p&gt;For falling asleep on the train – or on the last bus home – this would be a killer feature; an alarm that goes off when you get within 1km of your destination, waking you up so you don’t miss your stop. For bonus points, it could calculate your average speed and last known distance from your destination, so if you&apos;ve lost GPS signal, it’ll wake you up early just to be on the safe side.&lt;/p&gt;  &lt;h4&gt;Geographical Task Reminders&lt;/h4&gt;  &lt;p&gt;Every night I get home and realize that I’ve left my headphones in my desk drawer at work – &lt;em&gt;again&lt;/em&gt;. It’d be cool to set a reminder so that next time I’m &lt;em&gt;at&lt;/em&gt; work, it’ll remind me to put my headphones in my bag so I don’t forget them.&lt;/p&gt;  &lt;h4&gt;Charging Reminders Based On Wi-Fi Signals&lt;/h4&gt;  &lt;p&gt;I sometimes forget to charge my phone at work, and since it won’t last a full day &amp;amp; night without a top-up, this means it dies sometime during the evening, leaving me out &amp;amp; about with no music, no movies, no Twitter, no e-mail – oh, yeah, and no phone. &lt;/p&gt;  &lt;p&gt;If my phone’s connected to my office wi-fi network, then I’m at work – so if I haven’t plugged it in to charge after 10-15 minutes, it should beep at me “hey, I know you’re at work – plug me in!”&lt;/p&gt;  &lt;h4&gt;Don’t Ask for a Unlock Code When On A Known Wi-Fi Network&lt;/h4&gt;  &lt;p&gt;Just like above – if my phone’s on my office or my home wi-fi network, then it’s hopefully not been stolen, and it’s probably safe to use it without entering the unlock code first. &lt;/p&gt;  &lt;h4&gt;Make Phone Calls and Send Text Messages Reliably&lt;/h4&gt;  &lt;p&gt;I know… wishful thinking. The iPhone is a lovely internet gadget, music player, movie player, sat-nav and all-round geek toy. But for actually communicating with other people, I still carry a battered old Nokia. It makes calls, it sends texts, and the battery lasts well over a week. &lt;/p&gt;</description>
          <pubDate>2010-09-30T10:15:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2010/09/30/five-things-i-wish-my-iphone-did.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2010/09/30/five-things-i-wish-my-iphone-did.html</guid>
        </item>
      
    
      
        <item>
          <title>Fun with Powershell, SQL Backup and Automated Restores</title>
          <description>&lt;p&gt;Our database backup system is based on taking a full backup of every database every night, and transaction log backups every 20 minutes. Backups are shipped offsite to a backup server sitting in a data-centre across town, and what I was trying to do was to automate the process of restoring the latest full backup and all subsequent transaction logs on the remote server.&lt;/p&gt;  &lt;p&gt;So: ingredients. I have :&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;a href=&quot;http://www.red-gate.com/products/SQL_Backup/&quot;&gt;Red Gate SQL Backup&lt;/a&gt; (and the SqlBackupC.exe command-line client) &lt;/li&gt;    &lt;li&gt;&lt;a href=&quot;http://technet.microsoft.com/en-gb/scriptcenter/powershell.aspx&quot;&gt;Windows Powershell&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;A list of databases in a text file (“databases.txt”) &lt;/li&gt;    &lt;li&gt;A&amp;#160; folder full of database backups, and a folder full of transaction log backups. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;The backup filenames look something like:&lt;/p&gt;  &lt;pre&gt;&lt;blockquote&gt;&lt;p&gt;D:\backup\&lt;br /&gt;&amp;#160; data\&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; FULL_myserver_Animals_20100921_183021.sqb&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; FULL_myserver_Animals_20100922_183021.sqb&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; FULL_myserver_Animals_20100923_183021.sqb&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; FULL_myserver_Northwind_20100921_183021.sqb&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; FULL_myserver_Northwind_20100922_183021.sqb&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; FULL_myserver_Northwind_20100923_183021.sqb&lt;br /&gt;&amp;#160; logs\&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; LOG_myserver_Animals_20100921_190002.sqb&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; LOG_myserver_Animals_20100921_200004.sqb&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; LOG_myserver_Animals_20100921_210003.sqb&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; LOG_myserver_Animals_20100921_220002.sqb&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; LOG_myserver_Animals_20100921_230005.sqb&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; ...&lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; ...&lt;/p&gt;&lt;/blockquote&gt;&lt;/pre&gt;

&lt;p&gt;&lt;a title=&quot;Abalone shell (c) Rojer via Flickr&quot; href=&quot;http://www.flickr.com/photos/rojer/4552977683/sizes/m/in/photostream/&quot;&gt;&lt;img style=&quot;border-right-width: 0px; margin: 0px 0px 20px 20px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px&quot; border=&quot;0&quot; align=&quot;right&quot; src=&quot;http://farm3.static.flickr.com/2538/4552977683_f725d7ea1e.jpg&quot; width=&quot;280&quot; height=&quot;211&quot; /&gt;&lt;/a&gt;What I’m trying to do is, for each backup listed in databases.txt, I want to find the most up-to-date full backup, restore it, and then restore, in chronological order, every transaction log that’s been created since that full backup was taken.&lt;/p&gt;

&lt;p&gt;So. Powershell… time to see what all the fuss is about. &lt;/p&gt;

&lt;p&gt;Let’s start with a couple of things that threw me until I got my head around them. Powershell is a &lt;strong&gt;&lt;em&gt;shell&lt;/em&gt;.&lt;/strong&gt; Don’t think of it like C# or Java – think of it like a batch file on steroids. With objects. If you want to run a program from a Powershell script, you &lt;strong&gt;just put its name in the script.&lt;/strong&gt; For example – this is a valid Powershell script that’ll open itself in Notepad:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;C:\Windows\System32\Notepad.exe myscript.ps1&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Copy that line into notepad, save it as as myscript.ps1, fire up Powershell.exe, navigate to the folder where you saved it, and type .\myscript.ps1 – bingo.&lt;/p&gt;

&lt;p&gt;Secondly, Powershell is designed so that &lt;strong&gt;if something has a certain meaning in DOS, it’ll mean the same thing in Powershell&lt;/strong&gt;. &amp;lt;, | and &amp;gt; will redirect and pipe output – just like in DOS – which means Powershell can’t use &amp;gt; as the greater-than operator – so they’ve used –ge instead. (Mmm. Perly.) Backslashes are path separators – just like in DOS – so Powershell uses the backtick (&lt;strong&gt;`&lt;/strong&gt;) as an escape character. &lt;/p&gt;

&lt;p&gt;Thirdly, Powershell supports Perl-style string interpolation.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;$foo = 10 
    &lt;br /&gt;echo “I have $foo fingers”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;will output &lt;strong&gt;“I have 10 fingers”&lt;/strong&gt;. To avoid this behaviour, use single-quotes instead:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;$foo = 10 
    &lt;br /&gt;echo &apos;I have $foo fingers&apos;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;will print &quot;&lt;strong&gt;I have $foo fingers” &lt;/strong&gt;Be careful, though – underscores are valid in variable names, so &lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;echo “LOG_$server_$catalog_$timestamp”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;is actually going to try and find variables called $server_ , $catalog_ and $timestamp. To work around this behaviour, enclose the variable name in braces:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;echo “LOG_${server}_${catalog}_${timestamp}”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Finally – Powershell supports aliases, which means most commands have more than one name. For example, there’s a built-in command &lt;strong&gt;Get-ChildItem – &lt;/strong&gt;and &lt;strong&gt;dir&lt;/strong&gt;, &lt;strong&gt;gci&lt;/strong&gt;, and &lt;strong&gt;ls&lt;/strong&gt; are all shortcuts for this command, so when you bring up a Powershell command and want to list the contents of the current directory, it doesn’t care whether you type dir, ls, gci, or Get-ChildItem – they all do exactly the same thing.&lt;/p&gt;

&lt;p&gt;OK. Time for scripty fun. Here’s the script – you’ll need to supply your own databases.txt and credentials, and probably tweak the &lt;/p&gt;

&lt;blockquote&gt;
  &lt;pre&gt;&lt;span style=&quot;color: #999&quot;&gt;# Read the contents of databases.txt into a collection called $databases&lt;/span&gt;
$databases= Get-Content &lt;font color=&quot;#ff0000&quot;&gt;&amp;quot;databases.txt&amp;quot;
&lt;/font&gt;
&lt;span style=&quot;color: #999&quot;&gt;# Path to the SQL Backup command-line client.&lt;/span&gt;
$exe = &lt;font color=&quot;#ff0000&quot;&gt;&amp;quot;C:\Program Files (x86)\Red Gate\SQL Backup 6\SqlBackupC.exe&amp;quot;&lt;/font&gt;

&lt;span style=&quot;color: #999&quot;&gt;# Credentials for connection to SQL Server.&lt;/span&gt; &lt;br /&gt;$username = &apos;sa&apos;
$password = &lt;a href=&quot;mailto:&apos;p@ssw0rd&apos;&quot;&gt;&apos;p@ssw0rd&apos;&lt;/a&gt;&lt;/pre&gt;

  &lt;pre&gt;&lt;span style=&quot;color: #999&quot;&gt;# Database backup encryption password (you *do* encrypt your backups, don’t you… ?)&lt;br /&gt;&lt;/span&gt;$dbpw = &apos;T0P$3CR3T&apos;&lt;/pre&gt;

  &lt;pre&gt;foreach($database in $databases) {

  &lt;span style=&quot;color: #999&quot;&gt;# Get a collection of ALL backup files in the backup folder.&lt;/span&gt;
  $allSqbFiles = &lt;a href=&quot;http://technet.microsoft.com/en-us/library/ee176841.aspx&quot;&gt;Get-ChildItem&lt;/a&gt; D:\backup\data\*.sqb 
  
  &lt;span style=&quot;color: #999&quot;&gt;# Create a regular expression that&apos;ll match filenames&lt;/span&gt;
  &lt;span style=&quot;color: #999&quot;&gt;# against the current database name&lt;/span&gt;
  $regex = &lt;font color=&quot;#ff0000&quot;&gt;&amp;quot;FULL_\(local\)_${database}_\d+_\d+\.sqb&amp;quot;&lt;/font&gt;
  
  &lt;span style=&quot;color: #999&quot;&gt;# Filter the list of backup files to find those for the current DB&lt;/span&gt;
  $backups = $allSqbFiles | &lt;a href=&quot;http://technet.microsoft.com/en-us/library/ee177028.aspx&quot;&gt;Where-Object&lt;/a&gt; { $_.Name -match $regex }
  
  &lt;span style=&quot;color: #999&quot;&gt;# Sort the backups by LastWriteTime...&lt;/span&gt;
  $backups = $backups | &lt;a href=&quot;http://technet.microsoft.com/en-us/library/ee176968.aspx&quot;&gt;Sort-Object&lt;/a&gt; LastWriteTime

  &lt;span style=&quot;color: #999&quot;&gt;# and extract the most recent one&lt;/span&gt;
  $latestBackup = $backups | &lt;a href=&quot;http://technet.microsoft.com/en-us/library/ee176955.aspx&quot;&gt;Select-Object&lt;/a&gt; -last 1

  &lt;span style=&quot;color: #999&quot;&gt;# Capture the LastWriteTime of the most recent backup  &lt;/span&gt;
  $t = $latestBackup.LastWriteTime

  &lt;span style=&quot;color: #999&quot;&gt;# Extract the full name of the database backup file  &lt;/span&gt;
  $backup = $latestBackup.FullName
  
  &lt;span style=&quot;color: #999&quot;&gt;# Construct the SQL statement to pass to SqlBackupC.exe - note the &lt;/span&gt;
  &lt;span style=&quot;color: #999&quot;&gt;# doubled double-quotes used to include a double-quote in the result.&lt;/span&gt;
  
  $sql = &lt;font color=&quot;#ff0000&quot;&gt;&amp;quot;&amp;quot;&amp;quot;RESTORE DATABASE [$database] FROM DISK = &apos;$backup&apos; WITH PASSWORD = &apos;$dbpw&apos;, NORECOVERY, REPLACE&amp;quot;&amp;quot;&amp;quot;
&lt;/font&gt;  
  &lt;span style=&quot;color: #999&quot;&gt;# This next bit is what actually does the database restore, and &lt;/span&gt;
  &lt;span style=&quot;color: #999&quot;&gt;# it&apos;s a bit fiddly.&lt;/span&gt;
  &lt;span style=&quot;color: #999&quot;&gt;# &amp;amp; (the ampersand) is known as the &amp;quot;call&amp;quot; operator, and &lt;/span&gt;
  &lt;span style=&quot;color: #999&quot;&gt;# basically says &amp;quot;hey, run this command&amp;quot; Note that you CAN&apos;T just&lt;/span&gt;
  &lt;span style=&quot;color: #999&quot;&gt;# give it a big fat string containing the whole command, arguments&lt;/span&gt;
  &lt;span style=&quot;color: #999&quot;&gt;# and everything. That&apos;s not how it works.&lt;/span&gt;
  &lt;span style=&quot;color: #999&quot;&gt;# Second, the arguments beginning with a hyphen need to be&lt;/span&gt;
  &lt;span style=&quot;color: #999&quot;&gt;# backtick-escaped so Powershell knows they&apos;re not operators.&lt;/span&gt;
  &lt;span style=&quot;color: #999&quot;&gt;# Finally, note how we&apos;ve included the $exe in quotes,&lt;/span&gt;
  &lt;span style=&quot;color: #999&quot;&gt;# because the path to the SqlBackupC.exe has spaces in it. &lt;/span&gt;

  &amp;amp; &amp;quot;$exe&amp;quot; `-U $username `-P $password `-SQL $sql
  
  &lt;span style=&quot;color: #999&quot;&gt;# Use another regex to grab all the LOG files for the current database.&lt;/span&gt;
  $regex = &amp;quot;LOG_\(local\)_${database}_\d+_\d+\.sqb&amp;quot;;
  $logFiles = Get-ChildItem D:\backup\logs\*.sqb
  $logFiles = $logFiles | &lt;a href=&quot;http://technet.microsoft.com/en-us/library/ee177028.aspx&quot;&gt;Where-Object&lt;/a&gt; { $_.Name -match $regex }
  
  &lt;span style=&quot;color: #999&quot;&gt;# Then we filter this list to return only those that are more&lt;/span&gt;
  &lt;span style=&quot;color: #999&quot;&gt;# recent than the full backup (which we captured earlier)&lt;/span&gt;
  $logFilesToRestore = $logFiles | &lt;a href=&quot;http://technet.microsoft.com/en-us/library/ee177028.aspx&quot;&gt;Where-Object&lt;/a&gt; {$_.CreationTime -ge $t } | &lt;a href=&quot;http://technet.microsoft.com/en-us/library/ee176968.aspx&quot;&gt;Sort-Object&lt;/a&gt; CreationTime
  
  $logFileCount = $logFilesToRestore.Length
  
  &lt;span style=&quot;color: #999&quot;&gt;# If there&apos;s no log files, we break out of the loop and move&lt;/span&gt;
  &lt;span style=&quot;color: #999&quot;&gt;# onto the next database.&lt;/span&gt;
  if ($logFileCount -le 0) { continue }
  
  &lt;span style=&quot;color: #999&quot;&gt;# Now, the LAST transaction log needs a slightly different SQL &lt;/span&gt;
  &lt;span style=&quot;color: #999&quot;&gt;# command, because we want the last restore to leave the database&lt;/span&gt;
  &lt;span style=&quot;color: #999&quot;&gt;# in a usable state, so we need to split the logs into the final&lt;/span&gt;
  &lt;span style=&quot;color: #999&quot;&gt;# one, and all the others. &lt;/span&gt;
  $splitAtIndex = $logFileCount - 2
  
  &lt;span style=&quot;color: #999&quot;&gt;# Powershell lets you slice arrays by saying $MyArray[x..y]&lt;/span&gt;
  $logFilesExceptFinaltOne = $logFilesToRestore[0..$splitAtIndex]
  
  $finalFogFile = $logFilesToRestore | Select-Object -last 1
  
  foreach($log in $logFilesToRestore) {
    $logFileName = $log.FullName
  
    &lt;span style=&quot;color: #999&quot;&gt;# Construct the SQL statement to restore the transaction log&lt;/span&gt;
    &lt;span style=&quot;color: #999&quot;&gt;# leaving the database ready to accept further log restores:&lt;/span&gt;
    $sql = &lt;font color=&quot;#ff0000&quot;&gt;&amp;quot;&amp;quot;&amp;quot; RESTORE LOG [${database}] FROM DISK = &apos;${logFileName}&apos; WITH PASSWORD = &apos;${dbpw}&apos;, NORECOVERY&amp;quot;&amp;quot; &amp;quot;&lt;/font&gt;
   
    &lt;span style=&quot;color: #999&quot;&gt;# and run SqlBackupC with that SQL statement:&lt;/span&gt;
    &amp;amp; &amp;quot;$exe&amp;quot; `-U $username `-P $password `-SQL $sql&lt;br /&gt;  }
   
  $logFileName = $finalFogFile.FullName
   
  &lt;span style=&quot;color: #999&quot;&gt;# Construct the SQL statement to restore the last &lt;/span&gt;
  &lt;span style=&quot;color: #999&quot;&gt;# transaction log and leave everything ready for use&lt;/span&gt;
  $sql = &lt;font color=&quot;#ff0000&quot;&gt;&amp;quot;&amp;quot;&amp;quot;RESTORE LOG [${database}] FROM DISK = &apos;$logFileName&apos; WITH PASSWORD = &apos;${dbpw}&apos;,&lt;br /&gt;            RECOVERY, ORPHAN_CHECK &amp;quot;&amp;quot; &amp;quot;&lt;/font&gt;
   
   &lt;span style=&quot;color: #999&quot;&gt;# and run it!&lt;/span&gt;
   &amp;amp; &amp;quot;$exe&amp;quot; `-U $username `-P $password `-SQL $sql
 }&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;Having never really used Powershell before, this whole thing took about three hours to put together – and I spent the first two hours cursing Powershell for being so damned awkward, and then it just &lt;em&gt;clicked &lt;/em&gt;and suddenly everything made a lot more sense. Hopefully the tips above will save you a bit of frustration if you’re taking your first steps with it… and I’ve a feeling I’m going to be doing a lot more Powershelling before too long. For stuff like this, it’s so much more powerful than batch files, and so much more maintainable than writing little command-line utilities in C#.&lt;/p&gt;  </description>
          <pubDate>2010-09-24T21:46:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2010/09/24/fun-with-powershell-sql-backup-and.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2010/09/24/fun-with-powershell-sql-backup-and.html</guid>
        </item>
      
    
      
        <item>
          <title>Want a Free Red Gate Tool to Support Your Open-Source .NET Project?</title>
          <description>&lt;p&gt;Earlier in the year, I helped Red Gate software out with some customer focus group stuff, and in return they’ve offered me a free license to any single Red Gate software tool &lt;/p&gt;  &lt;p&gt;Since I’m already a happily-licensed user of Red Gate’s SQL Toolbelt and most of their .NET tools, I thought I’d pass this generous gesture on to the .NET community, so here’s the deal. &lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;strong&gt;If you maintain an open-source .NET project, and you’d like a free license for any single Red Gate tool, drop me a short e-mail, tell me about your project, tell me which tool you’d like, and why it would be useful.&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;&lt;a href=&quot;http://www.red-gate.com/&quot;&gt;&lt;img style=&quot;border-right-width: 0px; margin: 0px 0px 20px 20px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; align=&quot;right&quot; src=&quot;http://lh4.ggpht.com/_de7B8BtZZIw/TJeO8bWnLPI/AAAAAAAAADM/8bCORYvw3bo/image%5B5%5D.png?imgmax=800&quot; width=&quot;240&quot; height=&quot;89&quot; /&gt;&lt;/a&gt;There is a single license up for grabs, for any of the tools listed on Red Gate’s products page at &lt;a href=&quot;http://www.red-gate.com/products/index.htm&quot;&gt;http://www.red-gate.com/products/index.htm&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;I’m not looking for ten-page essays – if your project is well-known, just tell me which tool you’d like and how it will help. E-mail your entry to &lt;a href=&quot;mailto:dylan@dylanbeattie.net&quot;&gt;dylan@dylanbeattie.net&lt;/a&gt; – please put “Red Gate Giveaway” in the subject line so they don’t end up in the spam folder, and remember to include your full name &amp;amp; e-mail so I can let you know if you’ve won. I’ll accept entries until this Friday, October 1st, at 10:00am (GMT), after which I’ll pick a winner and send their details across to Red Gate. &lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/Wil_Wheaton#Wheaton.27s_Law&quot;&gt;Wheaton’s Law&lt;/a&gt; applies; my decision is final, no correspondence will be entered into, etc, etc. Good luck!&lt;/p&gt;  </description>
          <pubDate>2010-09-20T16:42:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2010/09/20/want-free-red-gate-tool-to-support-your.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2010/09/20/want-free-red-gate-tool-to-support-your.html</guid>
        </item>
      
    
      
        <item>
          <title>.NET String Formatting Cheat Sheet</title>
          <description>&lt;p&gt;This morning I saw &lt;a href=&quot;http://twitter.com/jhollingworth/status/24554006296&quot;&gt;this tweet&lt;/a&gt; from &lt;a href=&quot;http://twitter.com/jhollingworth&quot;&gt;@jhollingworth&lt;/a&gt;:&lt;a href=&quot;http://twitter.com/jhollingworth/status/24554006296&quot;&gt;&lt;img style=&quot;border-right-width: 0px; margin: 10px 0px 10px 20px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; align=&quot;left&quot; src=&quot;http://lh4.ggpht.com/_de7B8BtZZIw/TJECSaXZeII/AAAAAAAAADA/qvXSQt4e4oc/image%5B21%5D.png?imgmax=800&quot; width=&quot;542&quot; height=&quot;63&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p style=&quot;clear: both&quot;&gt;which linked to &lt;a href=&quot;http://blog.stevex.net/string-formatting-in-csharp/&quot;&gt;this excellent guide to .NET string formats&lt;/a&gt; by &lt;a href=&quot;http://blog.stevex.net/&quot;&gt;Steve Tibbett&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Sometimes, something is just so incredibly useful that even bookmarking it isn’t convenient enough – so I’ve hacked Steve’s guide and examples into a one-page A4 cheat sheet and stuck it on the wall next to my desk.&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh4.ggpht.com/_de7B8BtZZIw/TJECSgjUkfI/AAAAAAAAADE/EK8h8-DxBVw/s1600-h/image%5B20%5D.png&quot;&gt;&lt;img style=&quot;border-right-width: 0px; margin: 10px auto; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh3.ggpht.com/_de7B8BtZZIw/TJECTK-d9KI/AAAAAAAAADI/vTaiSdzBAg4/image_thumb%5B12%5D.png?imgmax=800&quot; width=&quot;322&quot; height=&quot;227&quot; /&gt;&lt;/a&gt; It’s up on my site as a &lt;a href=&quot;http://www.dylanbeattie.net/cheatsheets/dot_net_string_format_cheat_sheet.pdf&quot;&gt;PDF&lt;/a&gt; or as a &lt;a href=&quot;http://www.dylanbeattie.net/cheatsheets/dot_net_string_format_cheat_sheet.doc&quot;&gt;Word document&lt;/a&gt; – download it, print it out, stick it on the wall, and you’ll never find yourself Googling {0:n} vs {0:c} again.&lt;/p&gt;  &lt;p&gt;&lt;small&gt;All credit to Steve Tibbett for this – all I did was make it fit on one printed page. Happy formatting.&lt;/small&gt;&lt;/p&gt;  </description>
          <pubDate>2010-09-15T17:28:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2010/09/15/net-string-formatting-cheat-sheet.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2010/09/15/net-string-formatting-cheat-sheet.html</guid>
        </item>
      
    
      
        <item>
          <title>Automating Secure FTP Backups to a Windows 2008 Server</title>
          <description>&lt;p&gt;I’ve been looking into solutions for shipping database backups and log-files to a remote server as part of our backup strategy. Requirements are pretty simple:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;It’s got to be completely automated&lt;/li&gt;    &lt;li&gt;A daily run has to take less than 6 hours so it’ll finish overnight whilst our connection is quiet&lt;/li&gt;    &lt;li&gt;Transfers need to be encrypted to stop people sniffing our packets&lt;/li&gt;    &lt;li&gt;I don’t want to spend a lot of money&lt;/li&gt;    &lt;li&gt;If I can get away without lots of complicated (read: &lt;em&gt;fragile&lt;/em&gt;) command-line switches, then that would be a bonus&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Now, there’s four common protocols for doing secure file transfers - &lt;a href=&quot;http://en.wikipedia.org/wiki/SSH_file_transfer_protocol&quot;&gt;SFTP&lt;/a&gt;, &lt;a href=&quot;http://en.wikipedia.org/wiki/FTPS&quot;&gt;FTPS&lt;/a&gt;, &lt;a href=&quot;http://en.wikipedia.org/wiki/FTP_over_SSH#FTP_over_SSH_.28not_SFTP.29&quot;&gt;FTP+SSH&lt;/a&gt;, and &lt;a href=&quot;http://en.wikipedia.org/wiki/Secure_copy&quot;&gt;SCP&lt;/a&gt;. The specifics aren’t important – what matters is that they’re incompatible, so your client and server have to be using the same protocol, otherwise nothing will work. There’s also various dedicated tools such as rsync, robocopy, xcopy, SyncBack, DeltaCopy… the list goes on. Setting up rsync on Windows is a bit fiddly – not to mention the dozens of subtle and nefarious &lt;a href=&quot;http://www.samba.org/ftp/rsync/rsync.html&quot;&gt;command-line switches&lt;/a&gt; available to rsync.exe – and whilst I love a bit of cygwin hacking as much as anyone else, I’d really rather use something a little more intuitive here. I did try DeltaCopy, a GUI wrapper around rsync, but I quickly ran into security problems with SSH keys and username/password combinations. Since I wanted to avoid open folder shares on the remote system, tools like &lt;a href=&quot;http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/xcopy.mspx?mfr=true&quot;&gt;xcopy&lt;/a&gt; and &lt;a href=&quot;http://technet.microsoft.com/en-us/library/cc733145(WS.10).aspx&quot;&gt;robocopy&lt;/a&gt; were out. &lt;/p&gt;  &lt;p&gt;In the past, I’ve had great results with &lt;a href=&quot;http://www.vandyke.com/&quot;&gt;Vandyke Software&lt;/a&gt;’s &lt;a href=&quot;http://www.vandyke.com/products/vshell/&quot;&gt;VShell&lt;/a&gt;, a commercial Windows SSH server, but whilst putting this lot together, I discovered that in Windows Server 2008, you can set up an FTP server to encrypt connections via SSL (it’ll even use the same certificate as your website). That’s good, by the way – I think things are always cheaper &amp;amp; easier when Windows does them out of the box.&lt;/p&gt;  &lt;p&gt;So – assuming you’ve already got a Windows 2008 Server running IIS, and you’ve already got a certificate installed, then log onto the server, fire up IIS, create a new FTP site, switch on SSL, bingo. &lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh6.ggpht.com/_de7B8BtZZIw/TI5rQ1rEUiI/AAAAAAAAACg/aGtVFfAzl6g/s1600-h/image%5B37%5D.png&quot;&gt;&lt;img style=&quot;border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh3.ggpht.com/_de7B8BtZZIw/TI5rRXhDHvI/AAAAAAAAACk/0lyQkBc2XAc/image_thumb%5B21%5D.png?imgmax=800&quot; width=&quot;662&quot; height=&quot;576&quot; /&gt;&lt;/a&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;Just to sanity-check that everything’s working, fire up &lt;a title=&quot;WinSCP is an open source free SFTP client and FTP client for Windows.&quot; href=&quot;http://winscp.net/eng/index.php&quot;&gt;WinSCP&lt;/a&gt;, create a new session using FTP with SSL Explicit Encryption, and you should be able to connect to your new secure server.&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh4.ggpht.com/_de7B8BtZZIw/TI5rR76WkHI/AAAAAAAAACo/xhShRQ1Kd_M/s1600-h/image%5B30%5D.png&quot;&gt;&lt;img style=&quot;border-right-width: 0px; margin: 10px auto; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh5.ggpht.com/_de7B8BtZZIw/TI5rSkc4BfI/AAAAAAAAACs/jOPqs7VqMME/image_thumb%5B18%5D.png?imgmax=800&quot; width=&quot;529&quot; height=&quot;374&quot; /&gt;&lt;/a&gt; OK – so far, so good. We can connect, we can see a directory listing. Now what I want to do is to automate the client side of things, and for this bit, I’m going to use a superb bit of software called &lt;a href=&quot;http://www.superflexible.com/&quot;&gt;Super Flexible File Synchronizer&lt;/a&gt;. This is something I’ve only discovered recently, but so far, I’m delighted with it. It’s got native support for FTP. SFTP, WebDAV and HTTP, as well as Google Docs, Amazon S3 and Azure (!), plus a built-in scheduler, mail notification facility… as I said, it’s looking very promising.&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://http://www.superflexible.com/&quot;&gt;&lt;img style=&quot;border-bottom: 0px; border-left: 0px; margin: 0px 0px 20px 20px; display: inline; border-top: 0px; border-right: 0px&quot; border=&quot;0&quot; align=&quot;right&quot; src=&quot;http://www.superflexible.com/images/SiteName.png&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Right, time for some actual computer science – complete with experimental data and graphs and everything. I want to know how fast this thing is going to go, and I want to know how much the encryption’s going to slow things down, so I’ve put together 72Mb of assorted database backups, TIFF files and documents, and transferred them up to the remote server using various settings. Results here are the average of five runs for each combination.&lt;/p&gt;  &lt;p&gt;There’s a slightly odd setting in Super Flexible File Synchroniser – when you’re setting up an Internet target, if you pick FTP, it’ll give you a choice of libraries – cryptically called 1, 2 and 3. &lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh4.ggpht.com/_de7B8BtZZIw/TI5rT3CS-8I/AAAAAAAAACw/s-sJSKB0knc/s1600-h/image%5B43%5D.png&quot;&gt;&lt;img style=&quot;border-right-width: 0px; margin: 10px auto; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh4.ggpht.com/_de7B8BtZZIw/TI5rUXgs2pI/AAAAAAAAAC0/bVLpD28C6rU/image_thumb%5B25%5D.png?imgmax=800&quot; width=&quot;392&quot; height=&quot;394&quot; /&gt;&lt;/a&gt; I’m guessing that under the hood, there’s three different off-the-shelf FTP libraries in there, and the option lets you pick another one if the default doesn’t play nicely with your particular server. (Yeah, like anyone’s ever going to need &lt;em&gt;that…)&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;So, numbers. I ran five different settings – network share (i.e. copying straight to &lt;a href=&quot;file://\\myserver\backups&quot;&gt;\\myserver\backups&lt;/a&gt; using Windows file sharing), “open” FTP (no SSL), and then each of the three FTP libraries with the SSL option switched on.&lt;/p&gt;  &lt;table style=&quot;margin: 10px auto&quot; border=&quot;1&quot; cellspacing=&quot;0&quot; cellpadding=&quot;2&quot;&gt;&lt;tbody&gt;     &lt;tr&gt;       &lt;td&gt;&amp;#160;&lt;/td&gt;        &lt;td rowspan=&quot;2&quot;&gt;         &lt;p align=&quot;center&quot;&gt;Total Time&lt;/p&gt;       &lt;/td&gt;        &lt;td colspan=&quot;3&quot;&gt;         &lt;p align=&quot;center&quot;&gt;Time per File&lt;/p&gt;       &lt;/td&gt;        &lt;td colspan=&quot;3&quot;&gt;         &lt;p align=&quot;center&quot;&gt;Transfer Speed&lt;/p&gt;       &lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td&gt;         &lt;p align=&quot;center&quot;&gt;&amp;#160;&lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;center&quot;&gt;Average&lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;center&quot;&gt;Fastest&lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;center&quot;&gt;Slowest&lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;center&quot;&gt;Average&lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;center&quot;&gt;Slowest&lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;center&quot;&gt;Fastest&lt;/p&gt;       &lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td&gt;         &lt;p align=&quot;left&quot;&gt;&lt;strong&gt;FTP&lt;/strong&gt;&lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;right&quot;&gt;00:00:46&lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;right&quot;&gt;2 &lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;right&quot;&gt;0 &lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;right&quot;&gt;16 &lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;right&quot;&gt;1,944 &lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;right&quot;&gt;146 &lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;right&quot;&gt;2,274 &lt;/p&gt;       &lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td&gt;         &lt;p align=&quot;left&quot;&gt;&lt;strong&gt;SMB&lt;/strong&gt;&lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;right&quot;&gt;00:00:45&lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;right&quot;&gt;2 &lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;right&quot;&gt;0 &lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;right&quot;&gt;16 &lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;right&quot;&gt;1,976 &lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;right&quot;&gt;86 &lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;right&quot;&gt;2,451 &lt;/p&gt;       &lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td&gt;         &lt;p align=&quot;left&quot;&gt;&lt;strong&gt;FTP+SSL (Library 1)&lt;/strong&gt;&lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;right&quot;&gt;00:08:22&lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;right&quot;&gt;22 &lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;right&quot;&gt;0 &lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;right&quot;&gt;41 &lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;right&quot;&gt;148 &lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;right&quot;&gt;1 &lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;right&quot;&gt;870 &lt;/p&gt;       &lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td&gt;         &lt;p align=&quot;left&quot;&gt;&lt;strong&gt;FTP+SSL (Library 2)&lt;/strong&gt;&lt;/p&gt;       &lt;/td&gt;        &lt;td colspan=&quot;7&quot;&gt;         &lt;p align=&quot;center&quot;&gt;&lt;font color=&quot;#ff0000&quot;&gt;&lt;strong&gt;FAIL&lt;/strong&gt;&lt;/font&gt;&lt;/p&gt;       &lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td&gt;         &lt;p align=&quot;left&quot;&gt;&lt;strong&gt;FTP+SSL (Library 3)&lt;/strong&gt;&lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;right&quot;&gt;00:00:40&lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;right&quot;&gt;2 &lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;right&quot;&gt;0 &lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;right&quot;&gt;14 &lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;right&quot;&gt;2,161 &lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;right&quot;&gt;129 &lt;/p&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;p align=&quot;right&quot;&gt;2,521 &lt;/p&gt;       &lt;/td&gt;     &lt;/tr&gt;   &lt;/tbody&gt;&lt;/table&gt;  &lt;p align=&quot;center&quot;&gt;&lt;a href=&quot;http://lh6.ggpht.com/_de7B8BtZZIw/TI5rVGYf0II/AAAAAAAAAC4/QhCKEUk04bg/s1600-h/image%5B62%5D.png&quot;&gt;&lt;img style=&quot;border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh6.ggpht.com/_de7B8BtZZIw/TI5rVoMNuPI/AAAAAAAAAC8/4snssgth2Ng/image_thumb%5B36%5D.png?imgmax=800&quot; width=&quot;751&quot; height=&quot;344&quot; /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;A couple of things to note:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;FTP Library 2 just didn’t work – it wouldn’t even connect to the remote server to retrieve a directory listing.&lt;/li&gt;    &lt;li&gt;FTP Library 1 clearly had some issues – a typical run took well over eight minutes, and the subsequent logs were littered with timeouts and stuff like this:&lt;/li&gt;    &lt;blockquote&gt;     &lt;p&gt;&lt;font face=&quot;Consolas&quot;&gt;COPY L-&amp;gt;R d:\FileDemo\F00090820-0103.tif&amp;#160; (889.7kB)         &lt;br /&gt;FTP Exception (5): EclSocketError Timeout error occured @ 009283D0          &lt;br /&gt;COPY L-&amp;gt;R d:\FileDemo\F00090820-0104.tif&amp;#160; (851.6kB)          &lt;br /&gt;FTP Exception (5): EclSocketError Timeout error occured @ 009283D0&lt;/font&gt;&lt;/p&gt;   &lt;/blockquote&gt; &lt;/ol&gt;  &lt;p&gt;Fortunately – FTP Library #3 worked perfectly, and gave transfer speeds that were actually &lt;em&gt;faster&lt;/em&gt; than the raw FTP connection. I’m thinking that’s probably down to variations in test conditions (time of day, traffic. and so on) but even allowing for a little experimental error, it’s definitely fast enough to work with.&lt;/p&gt;  &lt;p&gt;We’re looking at transferring about 20Gb worth of database backups and photographs a day. Based on the stats above, we can do 75Mb in 45 seconds, which equates rather neatly to 100Mb / min, which means our 20Gb backups are going to take just over three hours. $60 for a copy of SuperSynchroFlexofile O’Maticalyzer (and I’ll throw in a case of beer if you can come up with a snappier name for the next edition), SSL all the way, and a nice GUI to set it all up.&lt;/p&gt;  &lt;p&gt;Isn’t it nice when everything just… &lt;em&gt;works?&lt;/em&gt;&lt;/p&gt;  </description>
          <pubDate>2010-09-13T13:36:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2010/09/13/automating-secure-ftp-backups-to.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2010/09/13/automating-secure-ftp-backups-to.html</guid>
        </item>
      
    
      
        <item>
          <title>Heisenbug of the Day: IIS 7.0 Discarding POST Data From Firefox 3 when using Custom 404 Handlers</title>
          <description>&lt;p&gt;Our site uses IIS custom error handlers, so that when you request /not/a/real/page.html, it’ll actually run /errors/404.asp – there’s a &lt;a href=&quot;http://www.4guysfromrolla.com/webtech/123000-1.shtml&quot;&gt;nice article on 4guysfromrolla&lt;/a&gt; about how you do this in classic ASP.&lt;/p&gt;  &lt;p&gt;In theory, this works for both GET and POST requests, but last week &lt;a href=&quot;http://stackoverflow.com/questions/3510680/firefox-empty-post-with-ajax&quot;&gt;we hit a snag&lt;/a&gt; – some of our jQuery Ajax code wasn’t working properly in Firefox 3. More specifically – it worked fine locally, it worked fine in all other browsers, but when we deployed the code to any of our test or live servers, it wouldn’t work in Firefox. IE, Opera, Safari, Chrome – all fine; it seems like only Firefox was affected.&lt;/p&gt;  &lt;table border=&quot;0&quot; cellspacing=&quot;0&quot; cellpadding=&quot;2&quot; width=&quot;499&quot;&gt;&lt;tbody&gt;     &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;100&quot;&gt;&lt;strong&gt;Method&lt;/strong&gt;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;172&quot;&gt;&lt;strong&gt;Server&lt;/strong&gt;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;152&quot;&gt;&lt;strong&gt;Request URL&lt;/strong&gt;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;73&quot;&gt;         &lt;p align=&quot;center&quot;&gt;&lt;strong&gt;Result&lt;/strong&gt;&lt;/p&gt;       &lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;100&quot;&gt;GET&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;172&quot;&gt;IIS 7.5 (Windows 7)&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;152&quot;&gt;/errors/404.asp&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;73&quot;&gt;         &lt;p align=&quot;center&quot;&gt;&lt;font color=&quot;#00e600&quot;&gt;&lt;strong&gt;OK&lt;/strong&gt;&lt;/font&gt;&lt;/p&gt;       &lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;100&quot;&gt;POST&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;172&quot;&gt;IIS 7.5 (Windows 7)&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;152&quot;&gt;/errors/404.asp&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;73&quot;&gt;         &lt;p align=&quot;center&quot;&gt;&lt;font color=&quot;#00e600&quot;&gt;&lt;strong&gt;OK&lt;/strong&gt;&lt;/font&gt;&lt;/p&gt;       &lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;100&quot;&gt;GET&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;172&quot;&gt;IIS 7.5 (Windows 7)&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;152&quot;&gt;(404 handler)&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;73&quot;&gt;         &lt;p align=&quot;center&quot;&gt;&lt;font color=&quot;#00e600&quot;&gt;&lt;strong&gt;OK&lt;/strong&gt;&lt;/font&gt;&lt;/p&gt;       &lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;100&quot;&gt;POST&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;172&quot;&gt;IIS 7.5 (Windows 7)&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;152&quot;&gt;(404 handler)&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;73&quot;&gt;         &lt;p align=&quot;center&quot;&gt;&lt;font color=&quot;#00e600&quot;&gt;&lt;strong&gt;OK&lt;/strong&gt;&lt;/font&gt;&lt;/p&gt;       &lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;100&quot;&gt;GET&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;172&quot;&gt;IIS 7.0 (Windows 2008)&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;152&quot;&gt;/errors/404.asp&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;73&quot;&gt;         &lt;p align=&quot;center&quot;&gt;&lt;font color=&quot;#00e600&quot;&gt;&lt;strong&gt;OK&lt;/strong&gt;&lt;/font&gt;&lt;/p&gt;       &lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;100&quot;&gt;POST&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;172&quot;&gt;IIS 7.0 (Windows 2008)&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;152&quot;&gt;/errors/404.asp&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;73&quot;&gt;         &lt;p align=&quot;center&quot;&gt;&lt;font color=&quot;#00e600&quot;&gt;&lt;strong&gt;OK&lt;/strong&gt;&lt;/font&gt;&lt;/p&gt;       &lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;100&quot;&gt;GET&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;172&quot;&gt;IIS 7.0 (Windows 2008)&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;152&quot;&gt;(404 handler)&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;73&quot;&gt;         &lt;p align=&quot;center&quot;&gt;&lt;font color=&quot;#00e600&quot;&gt;&lt;strong&gt;OK&lt;/strong&gt;&lt;/font&gt;&lt;/p&gt;       &lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;100&quot;&gt;POST&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;172&quot;&gt;IIS 7.0 (Windows 2008)&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;152&quot;&gt;(404 handler)&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;73&quot;&gt;         &lt;p align=&quot;center&quot;&gt;&lt;strong&gt;&lt;font color=&quot;#dd0000&quot;&gt;FAIL&lt;/font&gt;&lt;/strong&gt;&lt;/p&gt;       &lt;/td&gt;     &lt;/tr&gt;   &lt;/tbody&gt;&lt;/table&gt;  &lt;p&gt;Firebug didn’t show up anything unusual, so we fired up &lt;a href=&quot;http://www.fiddler2.com/fiddler2/&quot;&gt;Fiddler&lt;/a&gt;, a web debugging proxy that’ll show you what’s actually being passed between the client and the server. At least, that’s the idea… what &lt;em&gt;actually&lt;/em&gt; happened is that when we started running Fiddler, the bug went away. Yep… we had ourselves a real live Heisenbug:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;strong&gt;Heisenbug: &lt;/strong&gt;“…a computer bug that disappears … when an attempt is made to study it.” [via &lt;a href=&quot;http://en.wikipedia.org/wiki/Unusual_software_bug#Heisenbug&quot;&gt;Wikipedia&lt;/a&gt;]&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Fiddler runs as an HTTP-level proxy – in other words, it understands the HTTP protocol, and sits between your web browser and your web server, and – in theory – transparently forwards information between them, whilst recording all the bits that fly backwards and forwards so that you can dissect them and see what’s going on. I’d guess that, somehow, Firefox was sending dodgy requests, and Fiddler was cleaning up these requests as part of the proxying process – hence why the problem disappeared when Fiddler was running.&lt;/p&gt;  &lt;p&gt;Time to dig a little deeper. &lt;a href=&quot;http://www.wireshark.org/&quot;&gt;Wireshark&lt;/a&gt; is a deep-level network protocol analyser that’ll sniff your network traffic right down to the frame level. What I did next was to load up Wireshark, set up a filter [1] to show only HTTP traffic to/from our build server, and then submit the same request from a couple of different browsers – including Firefox.&lt;/p&gt;  &lt;p align=&quot;center&quot;&gt;&lt;a href=&quot;http://lh4.ggpht.com/_de7B8BtZZIw/THK_SoQrU3I/AAAAAAAAACA/p-HxIeSwqNU/s1600-h/image%5B4%5D.png&quot;&gt;&lt;img style=&quot;border-right-width: 0px; margin: 10px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh6.ggpht.com/_de7B8BtZZIw/THK_THx7txI/AAAAAAAAACE/SoTzmvPtSVw/image_thumb%5B2%5D.png?imgmax=800&quot; width=&quot;240&quot; height=&quot;278&quot; /&gt;&lt;/a&gt; &lt;a href=&quot;http://lh5.ggpht.com/_de7B8BtZZIw/THK_TT1tkMI/AAAAAAAAACI/-aPjsb7xYww/s1600-h/image%5B9%5D.png&quot;&gt;&lt;img style=&quot;border-right-width: 0px; margin: 10px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh6.ggpht.com/_de7B8BtZZIw/THK_T_sUoLI/AAAAAAAAACM/24V8LuKIA7M/image_thumb%5B5%5D.png?imgmax=800&quot; width=&quot;240&quot; height=&quot;278&quot; /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;The first grab there is what’s travelling over the wire when you POST that form using Google Chrome; the second is the same POST submitted using Firefox. Remember – at this point, we’re &lt;em&gt;totally lost&lt;/em&gt; and so looking for absolutely anything that’s different. If you look closely, you’ll see the Chrome trace includes an extra line - [Reassembled TCP Segments (680 bytes)] – that isn’t in the Firefox trace. They’re otherwise identical other than known differences like the User-Agent string and so on. Curious. A bit of experimentation verifies that Safari and IE are doing the same thing as Chrome – submitting two frames of data for each POST – where Firefox is only submitting one.&lt;/p&gt;  &lt;p&gt;It turns out this triggers a bug in IIS 7.0 when you’re using custom 404 handlers.&lt;/p&gt;  &lt;h3&gt;Bad Analogy Time…&lt;/h3&gt;  &lt;p&gt;&lt;a href=&quot;http://lh5.ggpht.com/_de7B8BtZZIw/THK_UuW4T-I/AAAAAAAAACQ/xtVPTb1DjdY/s1600-h/image%5B28%5D.png&quot;&gt;&lt;img style=&quot;border-bottom: 0px; border-left: 0px; margin: 0px 0px 20px 20px; display: inline; border-top: 0px; border-right: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; align=&quot;right&quot; src=&quot;http://lh3.ggpht.com/_de7B8BtZZIw/THK_U9wJKNI/AAAAAAAAACU/54MxFCFxugg/image_thumb%5B15%5D.png?imgmax=800&quot; width=&quot;136&quot; height=&quot;240&quot; /&gt;&lt;/a&gt; Imagine it’s your birthday. You’ve got a load of packages to open - you open the first one, and there’s a card inside saying “Happy Birthday! Enjoy the Lego! Love Granny xxx” &lt;/p&gt;  &lt;p&gt;Now – at this point, you’re expecting some &lt;a href=&quot;http://www.flickr.com/groups/1214232@N20/&quot;&gt;Lego&lt;/a&gt;, right? Well, if Granny is Chrome, IE or Safari, she’s been sensible – she’s sent the card in its own envelope, and put the Lego in the &lt;em&gt;next &lt;/em&gt;parcel. But, if Granny is Firefox, then Granny has done something foolish, and has crammed as many of the Lego bricks as she can into the same envelope as the birthday card. If the Lego set is only tiny, then she can fit all the bricks into the envelope – and so won’t bother sending the now-empty Lego box.&lt;/p&gt;  &lt;p&gt;So… imagine the envelopes/parcels are TCP frames, the birthday card is your HTTP request, and the Lego is the associated POST data. The card (headers) say “hey, there’s more stuff coming” – and then somewhere close behind, there’s another package with that “stuff” in it.&lt;/p&gt;  &lt;p&gt;Now, onto the IIS 7.0 bug. Under normal circumstances, IIS 7 copes just fine with POST data being in the same frame as the actual request. (That’s why this bug doesn’t affect every Firefox user who visits an IIS7 site.) &lt;/p&gt;  &lt;p&gt;Thing is - when a request is processed by a custom 404 handler, IIS 7.0 is opening the envelope, finding the birthday card, going “whooopeee! Lego!” – and then throwing the envelope away &lt;em&gt;without checking to see if there’s any Lego in it, &lt;/em&gt;before looking around excitedly to see where the next parcel is.&lt;/p&gt;  &lt;p&gt;For very small POSTs, this results in the Request.Form being empty (because all the Lego has been thrown away with the envelope). If you deliberately pad your POST with a couple of really long fields - &lt;strong&gt;&amp;lt;input name=”padding” value=”xxxxxxxx … xxx” /&amp;gt;&lt;/strong&gt; for 2000 characters or so – then you’ll see that even Firefox now has to split the request over more than one frame, and that any POST values that end up in the second frame are now accessible to IIS via Request.Form in the usual way. Kinda like Granny sending you a really big Lego set, and putting the first 20 or so bricks in the envelope with the birthday card, and the rest in a separate parcel or two – throw away the envelope, and you’ve still got *most* of the bricks, but many of them have gone missing.&lt;/p&gt;  &lt;p&gt;So… workarounds. Firefox patch – no good. Too many installed users. Upgrade all our web servers to IIS 7.5? Er, not right now, thanks. IIS hotfix? Lovely – if you’ve got one, send it over.&lt;/p&gt;  &lt;p&gt;In the meantime, the best option we could find was to stick two hidden fields at the top of the affected form, something like:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&amp;lt;input type=”hidden” name=”ff_frame” value=”xxxxx . . . &lt;em&gt;1460 Xs here&amp;#160; . &lt;/em&gt;. . xxxx” /&amp;gt;       &lt;br /&gt;&amp;lt;input type=”hidden” name=”ff_split” value=”1” /&amp;gt;&lt;/p&gt;    &lt;p&gt;&lt;font color=&quot;#c0c0c0&quot;&gt;&amp;lt;!—everything after this point will show up intact in Request.Form --&amp;gt;&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&amp;lt;input type=”hidden” name=”real” value=”some_data” /&amp;gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;The big string of X’s pads the first frame to make sure all your real data ends up in the second one, and the ff_split value ensures that this padding doesn’t mess up IIS’ parsing of subsequent POST values. Yes, this is disgusting - and it adds 1Kb+ to every POST - but it’s only required in a handful of places, and we’re looking to isolate it inside the jQuery code we’re using so it’ll be dynamically inserted into POSTs where necessary.&lt;/p&gt;  &lt;p&gt;&lt;small&gt;[1] ((http.request || http.response) &amp;amp;&amp;amp; (http.host contains &amp;quot;build&amp;quot;)) &amp;amp;&amp;amp; !(http.request.uri == &amp;quot;/favicon.ico&amp;quot;)&lt;/small&gt;&lt;/p&gt;  </description>
          <pubDate>2010-08-23T12:17:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2010/08/23/heisenbug-of-day-iis-70-discarding-post.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2010/08/23/heisenbug-of-day-iis-70-discarding-post.html</guid>
        </item>
      
    
      
        <item>
          <title>Uncle Bob’s Bowling Kata in Functional C#</title>
          <description>&lt;p&gt;During the session on functional programming at &lt;a href=&quot;http://openvolcano10.eventbrite.com/&quot;&gt;OpenVolcano10&lt;/a&gt; this week, &lt;a href=&quot;http://www.objectmentor.com/omTeam/martin_r.html&quot;&gt;Uncle Bob Martin&lt;/a&gt; spoke of a particularly elegant functional solution to his &lt;a href=&quot;http://butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata&quot;&gt;bowling game kata&lt;/a&gt;. Proposed by Stuart Halloway, &lt;a href=&quot;http://github.com/stuarthalloway/clojure-bowling&quot;&gt;the solution&lt;/a&gt; regards the bowling game itself as a (potentially infinite) sequence of ‘rolls’ (balls), and then uses some nicely elegant list manipulation to extract ten valid scoring frames from the sequence of balls and calculate the score of the game.&lt;/p&gt;  &lt;p&gt;C# and IEnumerable&amp;lt;T&amp;gt; allow you to do some very similar things in .NET – basically, you can define operations in terms of mapping one infinite list to another infinite list, and you don’t have to worry about stack overflows or out of memory errors because, thanks to the wonder of lazy evaluation, nothing is actually allocated or returned until you start asking for the resulting values.&lt;/p&gt;  &lt;p&gt;Take a look at this code snippet:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;static IEnumerable&amp;lt;Int64&amp;gt; Integers {     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; get {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; for (var i = 1; true; i++) yield return (i);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;}&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;If you’re thinking “but that’s just an infinite loop!” – you’re partly right. Yes, it’s infinite, but no, it’s not a loop. The magical &lt;strong&gt;yield return&lt;/strong&gt; operator there actually breaks the loop, and allows you go to infinity &lt;em&gt;(&lt;a href=&quot;http://www.imdb.com/title/tt0114709/&quot;&gt;and beyond!&lt;/a&gt;)&lt;/em&gt; – &lt;strong&gt;one step at a time&lt;/strong&gt;. Using the LINQ Take() method, you can easily slice’n’dice this infinite list into useful pieces:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;// Calculate the sum of the first 20 positive integers:     &lt;br /&gt;var sum = Integers.Take(20).Sum(); &lt;/p&gt;    &lt;p&gt;// Calculate the product of the first 10 positive integers:     &lt;br /&gt;var product = Integers.Take(10).Aggregate((a, b) =&amp;gt; (a * b));&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;By throwing some extensions methods onto Int64, you can use the same approach to filter the list:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;public static class ExtensionMethods {     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; public static bool IsPrime(this Int64 candidate) {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if ((candidate &amp;amp; 1) == 0) return (candidate == 2);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; var num = (int)Math.Sqrt((double)candidate);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; for (var i = 3; i &amp;lt;= num; i += 2) {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if ((candidate % i) == 0) return false;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return true;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;}&lt;/p&gt;    &lt;p&gt;// now we’ve defined myInt64.IsPrime(), we can do this:     &lt;br /&gt;var primes = Integers.Where(i =&amp;gt; i.IsPrime()); &lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;We now have a C# variable – &lt;strong&gt;primes&lt;/strong&gt; – that, for all intents and purposes, contains &lt;strong&gt;all the prime numbers&lt;/strong&gt;. If we try to print the entire list, our program will never terminate – but that’s what we’d expect, because the list of prime numbers is infinite. We can, however, do things like printing a list of the first 100 primes:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;foreach(var prime in primes.Take(100)) Console.WriteLine(prime);&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;or calculating the product of the first 50 primes:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;var product = primes.Take(50).Aggregate((a,b) =&amp;gt; (a*b));&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;So, what’s all this got to do with bowling? Well – using this approach, we can store the progress of a bowling game as a (potentially infinite?) list of rolls, and by using a couple of useful extension methods on IEnumerable&amp;lt;int&amp;gt;, we can calculate the resulting ten-pin bowling score in a single LINQ statement:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;var score = rolls.ToFrames().Take(10).Sum(frame =&amp;gt; frame.Sum());&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Here’s the supporting extension methods&amp;#160; - which basically capture the scoring quirks of spares, strikes, frames and the other idiosyncracies of ten-pin-bowling. The ToFrames() method is particularly interesting – it’s translated from the &lt;strong&gt;cons &lt;/strong&gt;operator in Lisp/Clojure, and effectively returns a list (the rolls that count towards the current frame), followed by a list of lists (where each list represents a subsequent frame in the game):&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;public static class BowlingRules {     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; public static IEnumerable&amp;lt;IEnumerable&amp;lt;int&amp;gt;&amp;gt; ToFrames(this IEnumerable&amp;lt;int&amp;gt; rolls) {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; yield return (rolls.Take(rolls.BallsToScore()));      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; foreach(var frame in (rolls.Skip(rolls.FrameAdvance()).ToFrames())) {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; yield return(frame);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; } &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; private static int FrameAdvance(this IEnumerable&amp;lt;int&amp;gt; rolls) {     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return (rolls.IsStrike() ? 1 : 2);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; } &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; private static int BallsToScore(this IEnumerable&amp;lt;int&amp;gt; rolls) {     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return (rolls.IsBonus() ? 3 : 2);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; } &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; private static bool IsStrike(this IEnumerable&amp;lt;int&amp;gt; rolls) {     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return (rolls.Take(1).Sum().Equals(10));      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; } &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; private static bool IsSpare(this IEnumerable&amp;lt;int&amp;gt; rolls) {     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return (rolls.Take(2).Sum().Equals(10));      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; } &lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; private static bool IsBonus(this IEnumerable&amp;lt;int&amp;gt; rolls) {     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return (rolls.IsSpare() || rolls.IsStrike());      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;} &lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;The project – including the unit test from Uncle Bob’s kata translated to NUnit / C# – is up on &lt;a href=&quot;http://code.google.com/p/dylanhax/source/browse/#svn/trunk/BowlingKata/BowlingKata.Game&quot;&gt;Google Code&lt;/a&gt; if you want to take a look. &lt;/p&gt;  </description>
          <pubDate>2010-04-24T18:20:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2010/04/24/uncle-bobs-bowling-kata-in-functional-c.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2010/04/24/uncle-bobs-bowling-kata-in-functional-c.html</guid>
        </item>
      
    
      
        <item>
          <title>Dynamic CSS with .less at the TechDays Open Source Evening</title>
          <description>&lt;p&gt;&lt;a href=&quot;http://lh5.ggpht.com/_de7B8BtZZIw/S8bzWW1ZCJI/AAAAAAAAAB4/hEOQg2U_U3M/s1600-h/image%5B12%5D.png&quot;&gt;&lt;img style=&quot;border-bottom: 0px; border-left: 0px; margin: 0px 0px 25px 20px; display: inline; border-top: 0px; border-right: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; align=&quot;right&quot; src=&quot;http://lh5.ggpht.com/_de7B8BtZZIw/S8bzXNQ6f2I/AAAAAAAAAB8/NOk4GZD9-uU/image_thumb%5B8%5D.png?imgmax=800&quot; width=&quot;240&quot; height=&quot;152&quot; /&gt;&lt;/a&gt; Thanks to all of you who came along to last night’s Open Source .NET evening (and extra thanks to &lt;a href=&quot;http://twitter.com/serialseb&quot;&gt;@serialseb&lt;/a&gt; for putting the whole thing together!) I think we managed to cover a really great programme of topics – &lt;a href=&quot;http://en.wikipedia.org/wiki/OpenRasta&quot;&gt;OpenRasta&lt;/a&gt;, &lt;a href=&quot;http://blog.benhall.me.uk/&quot;&gt;Ben Hall&lt;/a&gt; using Ruby to test ASP.NET projects, &lt;a href=&quot;http://mikehadlow.blogspot.com/&quot;&gt;Mike Hadlow&lt;/a&gt; talking about the &lt;a href=&quot;http://www.castleproject.org/container/&quot;&gt;Windsor IoC&lt;/a&gt; container, &lt;a href=&quot;http://www.jeremyskinner.co.uk/2010/04/01/fluentvalidation-presentation-london-wed-14-april/&quot;&gt;Jeremy Skinner&lt;/a&gt; presenting his &lt;a href=&quot;http://fluentvalidation.codeplex.com/&quot;&gt;FluentValidation&lt;/a&gt; library (and a late apology from Neil Robbins, whose CouchDB talk had to be cancelled after his laptop went into ‘permanent hibernation’ at the last minute… I guess the long cold winter affects laptops as well as people)&lt;/p&gt;  &lt;p&gt;I should make one very important clarification: &lt;strong&gt;dotLess is NOT my project!&lt;/strong&gt; The original Ruby project was created by &lt;a href=&quot;http://twitter.com/cloudhead&quot;&gt;Alexis Sellier&lt;/a&gt; and &lt;a href=&quot;http://twitter.com/usabilitypost&quot;&gt;Dmitry Fadeyev&lt;/a&gt;, and it was ported to .NET by &lt;a href=&quot;http://enginechris.wordpress.com/&quot;&gt;Christopher Owen&lt;/a&gt;, &lt;a href=&quot;http://blog.smoothfriction.nl/&quot;&gt;Erik van Brakel&lt;/a&gt; and &lt;a href=&quot;http://www.tigraine.at/&quot;&gt;Daniel Hoelbling&lt;/a&gt;. Apologies if this wasn’t clear from the presentation – from the questions afterwards, I appear to have inadvertently given the impression I was one of the project contributors. I’m not; I just think it’s an awesome library and wanted to give it a bit of exposure.&lt;/p&gt;  &lt;p&gt;You can download the demo code from last night’s talk here: &lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; &lt;a href=&quot;http://www.dylanbeattie.net/dot_less_demo.zip&quot;&gt;http://www.dylanbeattie.net/dot_less_demo.zip&lt;/a&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;You should just be able to unzip the whole lot into a folder, bring up LessDemo.sln in Visual Studio and hit “Run” – but let me know if you’re having any problems with it.&lt;/p&gt;  &lt;p&gt;The other bits I spoke about in the talk are:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Project Homepage and Downloads: &lt;a href=&quot;http://www.dotlesscss.com/&quot;&gt;http://www.dotlesscss.com/&lt;/a&gt;      &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;Ruby LESS Library: &lt;a href=&quot;http://lesscss.org/&quot;&gt;http://lesscss.org/&lt;/a&gt;      &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;Phil Haack’s T4 Template (.less to .css at build time):     &lt;br /&gt;&amp;#160;&lt;a href=&quot;http://haacked.com/archive/2009/12/02/t4-template-for-less-css.aspx&quot;&gt;http://haacked.com/archive/2009/12/02/t4-template-for-less-css.aspx&lt;/a&gt;      &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;Opening .less files in Visual Studio 2010’s CSS editor     &lt;br /&gt;&lt;a href=&quot;http://stackoverflow.com/questions/2346243/&quot;&gt;http://stackoverflow.com/questions/2346243/&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Finally, I’m very excited to see that for the next version of LESS they’re migrating away from server-side Ruby and porting the entire library to Javascript – so you can run it client-side or server-side depending on your setup. Nothing’s officially released yet, but you can get download the latest source from the &lt;a href=&quot;http://github.com/cloudhead/less.js/tree/&quot;&gt;less.js project on GitHub&lt;/a&gt; – definitely a project worth keeping an eye on.&lt;/p&gt;  </description>
          <pubDate>2010-04-14T22:45:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2010/04/14/dynamic-css-with-less-at-techdays-open.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2010/04/14/dynamic-css-with-less-at-techdays-open.html</guid>
        </item>
      
    
      
        <item>
          <title>A Manager’s Perspective - Why Attend Progressive.NET Tutorials?</title>
          <description>&lt;p&gt;&lt;a href=&quot;http://skillsmatter.com/&quot;&gt;SkillsMatter&lt;/a&gt; are organizing another series of their &lt;a href=&quot;http://skillsmatter.com/event/open-source-dot-net/progressive-dotnet-tutorials-2010&quot;&gt;Progressive.NET&lt;/a&gt; tutorials this May. I’ve heard from several people who would love to attend but are struggling to convince their managers that it’s worth the money (and to justify the time out of the office) – so I’d like to take this opportunity to go on the record, as a software manager, to endorse these tutorials.&lt;/p&gt;  &lt;p&gt;If you’re in a hurry, here’s the quote:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;I’m &lt;a href=&quot;http://stackoverflow.com/users/5017/dylan-beattie&quot;&gt;Dylan Beattie&lt;/a&gt;. I’ve been programming professionally for over 10 years, I’ve run a software team for 5 years. I believe that the SkillsMatter Progressive.NET tutorials offer more knowledgeable speakers, more relevant content, and better value for money than any other paid training course I have ever attended – and I believe that the skills learned at these tutorials will almost immediately result in improved productivity and better-quality software.&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Progressive.NET is not a dry textbook lecture delivered by some professional trainer who doesn’t care what happens after you leave the room. It’s enthusiastic, passionate experts, who hate wasting time and money as much as you (and your boss) do, sharing lessons they’ve learned the hard way, and demonstrating tools that will help you build better software faster.&lt;/p&gt;  &lt;p&gt;I manage a team of Web developers, who build software on Microsoft platforms – Windows, IIS, SQL Server, ASP, ASP.NET, MVC, and every kind of data access framework from good old ADO through ADO.NET, Linq to SQL, Castle ActiveRecord and Fluent NHibernate. Between us we’ve built and shipped software using almost all these platforms. We’ve spent painful hours and days tracking down obscure bugs in seven-year-old VBScript code, and we’ve seen how smoothly everything happens once you have unit tests, continuous integration and a decent development environment in place.&lt;/p&gt;  &lt;p&gt;Over the years, my team &amp;amp; I have been on various “proper” developer training courses, including a five-day Microsoft-certified ASP.NET course that cost thousands of pounds and was basically a complete waste of time. &lt;/p&gt;  &lt;p&gt;Three of us attended the SkillsMatter Progressive.NET tutorials last year, and found the event to be very, very good. The hardest thing about adopting a new technology like NHibernate or Castle is working out where it can help, and what it can do – and the best way I’ve found of getting this “guided tour” of a project’s capabilities is to spend a few hours with an expert like &lt;a href=&quot;http://ayende.com/Blog/default.aspx&quot;&gt;Ayende&lt;/a&gt; or Hammett showing you how it works and how it all fits together. One of my team also said that it was the first time he’d really spent time out of the office &lt;strong&gt;thinking&lt;/strong&gt; about how to write better software – and he’s right; it’s hard to find the time when you’re at your desk day in, day out, to really take a step back and think “could I be doing this better?” &lt;/p&gt;  &lt;p&gt;From a team lead / management perspective, the lessons learned and insights gleaned from the sessions at last year’s tutorials have made it much easier to base new projects on tools like NHibernate – and this has resulted in genuine time savings. We’re delivering &lt;strong&gt;better code&lt;/strong&gt;, we’re delivering it &lt;strong&gt;faster&lt;/strong&gt;, we’re not wasting time writing stored procedures and SELECT statements. We have less bugs. We have better release processes. &lt;strong&gt;We are more productive because we are inspired by learning new techniques, we’re using the right tools for the job, and we feel like part of a larger community.&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;More importantly, having sat in a room full of people sharing the same interest in software and software development, we &lt;strong&gt;don’t feel like we’re going off on some crazy hippy open-source tangent&lt;/strong&gt;. We’ve talked IoC and ORM with people who write e-commerce systems, insurance software, finance software and major enterprise systems for healthcare organisations. It’s a radically different perspective from trying NHibernate because you read about on some guy’s blog. It’s given me, my team, and our business stakeholders confidence in these tools, and access to a rich network of fellow users and experts who are happy to advise and help out when we get a bit lost.&lt;/p&gt;  </description>
          <pubDate>2010-03-02T14:35:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2010/03/02/managers-perspective-why-attend.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2010/03/02/managers-perspective-why-attend.html</guid>
        </item>
      
    
      
        <item>
          <title>Fun and Games with NHibernate and String Keys</title>
          <description>&lt;p&gt;One of the team spent much of yesterday banging his head against the wall. We had a query that took 18ms to run in Query Analyzer, and 1800ms to run from NHibernate. &lt;a href=&quot;http://nhprof.com/&quot;&gt;NHibernate Profiler&lt;/a&gt; would report 1800-2200 ms – and copying’n’pasting the &lt;em&gt;exact same query statement&lt;strong&gt; &lt;/strong&gt;&lt;/em&gt;into Query Analyzer would run it in &amp;lt; 20 ms. Running the same SQL statement via ADO.NET, from the same block of code, proved it wasn’t a network latency issue or anything - ADO.NET came back in 20ms, NHibernate still took over 2 seconds. &lt;/p&gt;  &lt;p&gt;Finally, thanks to SQL Profiler, we managed to work out what was going on. The table in question had a primary key which was a varchar(9); in this instance, we were filtering on a particular primary key value – and &lt;strong&gt;NHibernate was specifying the value as an nvarchar(6), &lt;/strong&gt; because NHibernate treats strings as unicode (nvarchar) by default. This C# code&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;currentSession     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; .CreateCriteria&amp;lt;UserFeedItem&amp;gt;()      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; .Add(Expression.Eq(&amp;quot;User.UserCode&amp;quot;, “abc123”)) // is a varchar(9) in the database for historical reasons      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; .AddOrder(Order.Asc(&amp;quot;Received&amp;quot;))      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; .SetFirstResult(firstResult)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; .SetMaxResults(resultCount)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; .List&amp;lt;UserFeedItem&amp;gt;()      &lt;br /&gt;); &lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;was resulting in the following SQL statement &lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;exec sp_executesql      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; N&apos;SELECT TOP 50 FeedItemId16_0_, FeedUser16_0_, MailSent16_0_, ItemSeen16_0_, Deleted16_0_, Received16_0_       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; FROM (      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; SELECT this_.FeedItemId as FeedItemId16_0_, this_.FeedUser as FeedUser16_0_, this_.MailSent as MailSent16_0_, this_.ItemSeen as ItemSeen16_0_, this_.Deleted as Deleted16_0_, this_.Received as Received16_0_, ROW_NUMBER() OVER(ORDER BY this_.Received) as __hibernate_sort_row FROM Spotweb.dbo.[FeedUserFeedItem] this_ WHERE this_.FeedUser = @p0) as query WHERE query.__hibernate_sort_row &amp;gt; 50 ORDER BY query.__hibernate_sort_row&apos;,      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; N&apos;@p0 nvarchar(6)&apos;,      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; @p0=N&apos;abc123&apos;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;This means SQL Server has to do an implicit conversion when comparing your parameter value to the table’s primary key value – instead of using indexes, it has to retrieve every row, cast the primary key to an nvarchar(6), and then see whether the result is equal to your parameter. As you can imagine, this slows things down a little – especially on a table with several million records.&lt;/p&gt;  &lt;p&gt;The immediate solution for this was to rewrite the code as follows:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;currentSession     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; .CreateQuery(&amp;quot;from UserFeedItem where User.UserCode = :code&amp;quot;)      &lt;br /&gt;&lt;strong&gt;&amp;#160;&amp;#160;&amp;#160; .SetParameter(&amp;quot;code&amp;quot;, “abc123”, TypeFactory.GetAnsiStringType(9))&lt;/strong&gt;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; .SetFirstResult(firstResult)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; .SetMaxResults(resultCount)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; .List&amp;lt;UserFeedItem&amp;gt;()      &lt;br /&gt;);&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;- notice how on the third line we’re explicitly specifying TypeFactory.GetAnsiStringType(9) to force NHibernate to specify the parameter as a varchar(9). A better solution is to explicitly specify the mapping type for the column – we’re using Fluent NHibernate, so the mapping override code looks like this:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;public class SiteUserOverrides : IAutoMappingOverride&amp;lt;SiteUser&amp;gt; {     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; public void Override(AutoMapping&amp;lt;SiteUser&amp;gt; map) {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; map.Id(u =&amp;gt; u.UserCode).CustomType(“AnsiString”);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;}&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;and now any reference in any query to a SiteUser.UserCode will be properly mapped as varchar instead of nvarchar.&lt;/p&gt;  &lt;p&gt;Moral of the story: be careful with varchar and nvarchar keys when using NHibernate; make sure there aren’t implicit type conversions happening all over the place, because they can seriously mess up your performance.&lt;/p&gt;  &lt;p&gt;It’s also worth noting that even the excellent NHibernate Profiler couldn’t shed any light on what was going on here – SQL Profiler (included with SQL Server) is still an incredibly powerful and under-used tool, and it’s well worth spending a couple of hours getting to grips with it.&lt;/p&gt;  </description>
          <pubDate>2010-02-17T11:33:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2010/02/17/fun-and-games-with-nhibernate-and.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2010/02/17/fun-and-games-with-nhibernate-and.html</guid>
        </item>
      
    
      
        <item>
          <title>Fun with msdeploy, Extended Protection and Windows Authentication</title>
          <description>&lt;p&gt;Trying to push a change to our live servers today, we got this wonderful message from msdeploy:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;Error: (09/12/2009 16:10:21) An error occurred when the request was processed on the remote computer.&lt;/p&gt;    &lt;p&gt;Error: Child object &apos;extendedProtection&apos; cannot be added to object &apos;windowsAuthentication&apos;. The &apos;windowsAuthentication&apos; provider may not support this deployment.&lt;/p&gt;    &lt;p&gt;Error count: 1.&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;We do this several times a day… it worked yesterday, nobody changed anything, and suddenly today it doesn’t work. (Don’t you just &lt;em&gt;love&lt;/em&gt; it when that happens?)&lt;/p&gt;  &lt;p&gt;Now, msdeploy is wonderful, but has practically no documentation – which means when an error like this happens, you’re on your own.&lt;/p&gt;  &lt;p&gt;A bit of Googling and a couple of lucky guesses later, and we worked out what was causing it. “Extended Protection” is apparently some of new fangled security framework that’s included in recent Windows Updates. Our internal servers install Windows updates automatically; our live servers don’t.&lt;/p&gt;  &lt;p&gt;In other words – our staging server had quietly upgraded itself in the night to support extended authentication, and was now trying to push that configuration to the live server, which had absolutely no idea what was going on.&lt;/p&gt;  &lt;p&gt;Logging on to the live server and installing the outstanding Windows security updates seems to have fixed it.&lt;/p&gt;  </description>
          <pubDate>2009-12-09T16:53:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/12/09/fun-with-msdeploy-extended-protection.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/12/09/fun-with-msdeploy-extended-protection.html</guid>
        </item>
      
    
      
        <item>
          <title>Axure RP: Lego For Software Designers</title>
          <description>&lt;p&gt;Someone asked me on Twitter a little while back:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;@&lt;a href=&quot;http://twitter.com/dylanbeattie&quot;&gt;dylanbeattie&lt;/a&gt; Do u currently use Axure? If so, could u pls tell what size team it&apos;s effective with? And, is it really better than pen-paper?       &lt;br /&gt;&lt;small&gt;&lt;a href=&quot;http://twitter.com/goblinfactory/status/5504405400&quot;&gt;11:38 AM Nov 7th&lt;/a&gt; from web &lt;a href=&quot;http://twitter.com/dylanbeattie/status/5445442251&quot;&gt;in reply to dylanbeattie&lt;/a&gt;&lt;/small&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;&lt;a href=&quot;http://www.axure.com/&quot;&gt;&lt;img style=&quot;border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; margin-left: 0px; border-left-width: 0px; margin-right: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; align=&quot;right&quot; src=&quot;http://lh5.ggpht.com/_LV_l8kYLOwo/Sxme8AqrChI/AAAAAAAAASo/_znbB2ih2qY/image%5B23%5D.png?imgmax=800&quot; width=&quot;185&quot; height=&quot;101&quot; /&gt;&lt;/a&gt;The short Twitter answers are “yes”, “we’ve used it effectively with up to 4 people”, and “yes, because I can’t draw” – in that order.&lt;/p&gt;  &lt;p&gt;But a slightly more detailed response would probably help.&lt;/p&gt;  &lt;p&gt;Once upon a time, prototyping web apps was easy. You’d draw every page, and then use a site map to demonstrate which links went where. Every page was static; nothing moved, there was no Ajax, no infinite scrolling, no drag’n’drop, and most websites were actually about as interactive as a Choose Your Own Adventure novel. Well, those days are gone. People expect more – richer UIs, better responsiveness, less postbacks and waiting around for pages to load – and with libraries like jQuery, there’s really no excuse for not delivering code that satisfies those expectations. &lt;/p&gt;  &lt;p&gt;Question is – how do you prototype a rich user interface? How do you draw a picture of something that won’t sit still? For me, that’s where &lt;a title=&quot;Axure RP - www.axure.com&quot; href=&quot;http://www.axure.com/&quot;&gt;Axure RP&lt;/a&gt; comes in. Axure is a “tool for rapidly creating wireframes, prototypes and specifications for applications and web sites”. It’s a commercial product, so it is, alas, not free (although to put it into perspective, it costs less than hiring a .NET developer for &lt;em&gt;one day) &lt;/em&gt;– but it is a uniquely powerful and expressive piece of software that I find myself firing up on an almost daily basis.&lt;/p&gt;  &lt;p&gt;In everyday use, it’s like a weird cross between Balsamiq, Visual Basic, and Lego.&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;strong&gt;Balsamiq&lt;/strong&gt;, because it’s easy to mock up static user interfaces by dragging buttons, inputs and form elements on to your page. &lt;/li&gt;    &lt;li&gt;&lt;strong&gt;Visual Basic&lt;/strong&gt;, because it’s easy to add behaviour to those elements using click handlers, events and dynamic controls. &lt;/li&gt;    &lt;li&gt;&lt;strong&gt;Lego&lt;/strong&gt;, because it’s intuitive, and it’s fun, and &lt;em&gt;there is no way anyone is going to look at what you’ve done and think the project is finished&lt;/em&gt;. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;The game Populous was &lt;a href=&quot;http://en.wikipedia.org/wiki/Populous#Development&quot;&gt;designed using Lego&lt;/a&gt;. I grew up with Lego*. From a very early age, I learned to use Lego bricks to express ideas. I knew every single brick I owned. I could demonstrate an idea I’d had for a car, or a spaceship, or a robot, by assembling these reusable components into a prototype with spinning wheels and moving parts and a sense of scale and colour. Working entirely in plastic bricks actually become very liberating, because it stops you worrying about materials and finishes, and allows you to focus entirely on expressing &lt;em&gt;ideas&lt;/em&gt;.&lt;/p&gt;  &lt;p&gt;&lt;a title=&quot;&quot; href=&quot;http://www.brickartist.com/lego-art/infinity.html&quot; brickartist.com?=&quot;brickartist.com?&quot; Sawaya=&quot;Sawaya&quot; Nathan=&quot;Nathan&quot; &amp;#169;=&quot;&amp;amp;#169;&quot; Infinity?=&quot;Infinity?&quot;&gt;&lt;img style=&quot;border-right-width: 0px; margin: 0px 0px 20px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px&quot; title=&quot;&amp;quot;Infinity&amp;quot; © Nathan Sawaya / brickartist.com&quot; border=&quot;0&quot; alt=&quot;&amp;quot;Infinity&amp;quot; © Nathan Sawaya / brickartist.com&quot; align=&quot;right&quot; src=&quot;http://lh5.ggpht.com/_LV_l8kYLOwo/Sxme810QVeI/AAAAAAAAASs/69Gu4bDOIqE/image%5B25%5D.png?imgmax=800&quot; width=&quot;225&quot; height=&quot;240&quot; /&gt;&lt;/a&gt;Have you ever showed someone a Lego house and had them say “Hey, that looks great! When can we move in?” No. People know a Lego house is not a real house. They appreciate that the point of a Lego - or cardboard, or clay - model is to demonstrate what you’re &lt;em&gt;planning&lt;/em&gt; to do, not show off what you’ve already done.&lt;/p&gt;  &lt;p&gt;Have you ever showed anyone an HTML mockup of a web app and had them say “Hey, that looks great! When do we launch?” – and then they look &lt;em&gt;horrified&lt;/em&gt;&lt;strong&gt; &lt;/strong&gt;when you explain that you haven’t actually started the build yet?&lt;/p&gt;  &lt;p&gt;People don’t grok the difference between HTML mockups and completed web apps the way they grok the difference between Lego houses and real ones. I can’t say I blame them. HTML is HTML – whether it was hacked together late last night in Notepad or generated in the cloud by your domain-driven MVC application framework. The difference doesn’t become apparent until they actually start clicking things – by which point it’s too late; you’ve made your first impression (“wow, the new app is done!”) and it’s all downhill from there.&lt;/p&gt;  &lt;p&gt;I think the hardest questions in software are &lt;strong&gt;“what are we doing?”&lt;/strong&gt; and &lt;strong&gt;“are we done yet?”&lt;/strong&gt;. I think good prototypes are absolutely instrumental in answering those questions, and any tool that can help us refine those prototypes without falling into the trap of “well, it &lt;em&gt;looks&lt;/em&gt; finished” has to be a Good Thing. &lt;/p&gt;  &lt;p&gt;&lt;small&gt;* Some people look at how much Lego I still have and conclude that I never grew up at all…&lt;/small&gt;&lt;/p&gt;  </description>
          <pubDate>2009-11-07T13:36:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/11/07/axure-rp-lego-for-software-designers.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/11/07/axure-rp-lego-for-software-designers.html</guid>
        </item>
      
    
      
        <item>
          <title>A (Slightly Faster) URL Resolver Module for ASP.NET MVC</title>
          <description>&lt;p&gt;Yesterday, I posted some code I’d hacked together as part of an MVC2 demo that would resolve ASP.NET virtual path URLs on the fly as pages were written to the ASP.NET response stream.&lt;/p&gt;  &lt;p&gt;Having run some tests on this code in isolation – it’s actually quite nasty. For running ad-hoc demos on your workstation, it’s fine, but the performance hit of decoding the byte array, doing the regex transform and re-encoding it is something like &lt;strong&gt;two hundred times&lt;/strong&gt; slower than a direct stream copy. Not good. There is now a modified version &lt;a href=&quot;http://code.google.com/p/tagalong/source/browse/trunk/DylanHax/Web/UrlResolverModule.cs&quot;&gt;online at Google Code&lt;/a&gt; which is quite a bit faster, but there’s still huge scope for improvement. In particular, although it’s using byte comparisons now to work out where the &lt;strong&gt;~/ &lt;/strong&gt;combination occurs, it’s still falling back to string comparisons every time it finds a tilde to decide whether that tilde needs replacing or not.&lt;/p&gt;  &lt;p&gt;These stats were created using a loop that spins up HTML pages of various sizes – one version full of ASP.NET-style tilde paths, one containing no tildes -&amp;#160; and then writes them 100 times to both a normal MemoryStream and a UrlResolverStream in order to calculate the average rendering time. If a page doesn’t contain any tildes at all, performance is 5-6 times slower than the equivalent direct memory copy – i.e. 21ms instead of 4ms. For pages with &lt;em&gt;lots&lt;/em&gt; of tildes, the additional string processing hits quite hard and you’re looking at a slowdown factor of around 30-35x. &lt;/p&gt;  &lt;table border=&quot;1&quot; cellspacing=&quot;0&quot; cellpadding=&quot;4&quot; width=&quot;640&quot;&gt;&lt;tbody&gt;     &lt;tr&gt;       &lt;td width=&quot;81&quot;&gt;&lt;strong&gt;Tildes?&lt;/strong&gt;&lt;/td&gt;        &lt;td width=&quot;80&quot;&gt;&lt;strong&gt;Page Size&lt;/strong&gt;&lt;/td&gt;        &lt;td width=&quot;184&quot;&gt;&lt;strong&gt;MemoryStream copy (ms)&lt;/strong&gt;&lt;/td&gt;        &lt;td width=&quot;204&quot;&gt;&lt;strong&gt;UrlResolverStream copy (ms)&lt;/strong&gt;&lt;/td&gt;        &lt;td width=&quot;89&quot;&gt;&lt;strong&gt;Ratio&lt;/strong&gt;&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td width=&quot;84&quot;&gt;Yes&lt;/td&gt;        &lt;td width=&quot;84&quot;&gt;50Kb&lt;/td&gt;        &lt;td width=&quot;182&quot;&gt;0.05&lt;/td&gt;        &lt;td width=&quot;203&quot;&gt;0.38&lt;/td&gt;        &lt;td width=&quot;88&quot;&gt;7&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td width=&quot;86&quot;&gt;Yes&lt;/td&gt;        &lt;td width=&quot;87&quot;&gt;101Kb&lt;/td&gt;        &lt;td width=&quot;181&quot;&gt;0.17&lt;/td&gt;        &lt;td width=&quot;202&quot;&gt;2.51&lt;/td&gt;        &lt;td width=&quot;87&quot;&gt;14&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td width=&quot;87&quot;&gt;Yes&lt;/td&gt;        &lt;td width=&quot;89&quot;&gt;202Kb&lt;/td&gt;        &lt;td width=&quot;180&quot;&gt;0.19&lt;/td&gt;        &lt;td width=&quot;201&quot;&gt;6.05&lt;/td&gt;        &lt;td width=&quot;87&quot;&gt;31&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td width=&quot;87&quot;&gt;Yes&lt;/td&gt;        &lt;td width=&quot;90&quot;&gt;405Kb&lt;/td&gt;        &lt;td width=&quot;180&quot;&gt;0.37&lt;/td&gt;        &lt;td width=&quot;201&quot;&gt;13.88&lt;/td&gt;        &lt;td width=&quot;87&quot;&gt;37&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td width=&quot;87&quot;&gt;Yes&lt;/td&gt;        &lt;td width=&quot;91&quot;&gt;809Kb&lt;/td&gt;        &lt;td width=&quot;180&quot;&gt;0.88&lt;/td&gt;        &lt;td width=&quot;200&quot;&gt;29.47&lt;/td&gt;        &lt;td width=&quot;87&quot;&gt;33&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td width=&quot;87&quot;&gt;Yes&lt;/td&gt;        &lt;td width=&quot;91&quot;&gt;1,619Kb&lt;/td&gt;        &lt;td width=&quot;180&quot;&gt;1.62&lt;/td&gt;        &lt;td width=&quot;200&quot;&gt;60.55&lt;/td&gt;        &lt;td width=&quot;87&quot;&gt;37&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td width=&quot;87&quot;&gt;Yes&lt;/td&gt;        &lt;td width=&quot;91&quot;&gt;&lt;strong&gt;3,238Kb&lt;/strong&gt;&lt;/td&gt;        &lt;td width=&quot;180&quot;&gt;&lt;strong&gt;3.45&lt;/strong&gt;&lt;/td&gt;        &lt;td width=&quot;200&quot;&gt;&lt;strong&gt;123.40&lt;/strong&gt;&lt;/td&gt;        &lt;td width=&quot;87&quot;&gt;&lt;strong&gt;35&lt;/strong&gt;&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td width=&quot;87&quot;&gt;No&lt;/td&gt;        &lt;td width=&quot;91&quot;&gt;50Kb&lt;/td&gt;        &lt;td width=&quot;180&quot;&gt;0.02&lt;/td&gt;        &lt;td width=&quot;200&quot;&gt;0.34&lt;/td&gt;        &lt;td width=&quot;87&quot;&gt;17&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td width=&quot;87&quot;&gt;No&lt;/td&gt;        &lt;td width=&quot;91&quot;&gt;101Kb&lt;/td&gt;        &lt;td width=&quot;180&quot;&gt;0.07&lt;/td&gt;        &lt;td width=&quot;200&quot;&gt;0.66&lt;/td&gt;        &lt;td width=&quot;87&quot;&gt;9&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td width=&quot;87&quot;&gt;No&lt;/td&gt;        &lt;td width=&quot;91&quot;&gt;202Kb&lt;/td&gt;        &lt;td width=&quot;180&quot;&gt;0.15&lt;/td&gt;        &lt;td width=&quot;200&quot;&gt;1.28&lt;/td&gt;        &lt;td width=&quot;87&quot;&gt;8&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td width=&quot;87&quot;&gt;No&lt;/td&gt;        &lt;td width=&quot;91&quot;&gt;405Kb&lt;/td&gt;        &lt;td width=&quot;180&quot;&gt;0.48&lt;/td&gt;        &lt;td width=&quot;200&quot;&gt;2.62&lt;/td&gt;        &lt;td width=&quot;87&quot;&gt;5&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td width=&quot;87&quot;&gt;No&lt;/td&gt;        &lt;td width=&quot;91&quot;&gt;809Kb&lt;/td&gt;        &lt;td width=&quot;180&quot;&gt;0.83&lt;/td&gt;        &lt;td width=&quot;200&quot;&gt;5.26&lt;/td&gt;        &lt;td width=&quot;87&quot;&gt;6&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td width=&quot;87&quot;&gt;No&lt;/td&gt;        &lt;td width=&quot;91&quot;&gt;1,619Kb&lt;/td&gt;        &lt;td width=&quot;180&quot;&gt;1.66&lt;/td&gt;        &lt;td width=&quot;200&quot;&gt;10.57&lt;/td&gt;        &lt;td width=&quot;87&quot;&gt;6&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td width=&quot;87&quot;&gt;&lt;strong&gt;No&lt;/strong&gt;&lt;/td&gt;        &lt;td width=&quot;91&quot;&gt;&lt;strong&gt;3,237Kb&lt;/strong&gt;&lt;/td&gt;        &lt;td width=&quot;180&quot;&gt;&lt;strong&gt;3.53&lt;/strong&gt;&lt;/td&gt;        &lt;td width=&quot;202&quot;&gt;&lt;strong&gt;21.17&lt;/strong&gt;&lt;/td&gt;        &lt;td width=&quot;89&quot;&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/td&gt;     &lt;/tr&gt;   &lt;/tbody&gt;&lt;/table&gt;  &lt;p&gt;It should be possible to make this considerably faster still; since the code basically scans byte arrays, this is one of those areas where using pointer arithmetic could make a huge difference. I’ll dig out my unsafe hat and my pointer-gloves this weekend and see what I can do to it. In the meantime – play with it, experiment with it, but it’s probably a good idea not to let it within a hundred miles of your live servers :)&lt;/p&gt;  </description>
          <pubDate>2009-11-05T00:57:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/11/05/slightly-faster-url-resolver-module-for.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/11/05/slightly-faster-url-resolver-module-for.html</guid>
        </item>
      
    
      
        <item>
          <title>A URL Resolver Module for ASP.NET MVC</title>
          <description>&lt;blockquote&gt;   &lt;p&gt;Update: An improved version of this module, along with some performance stats, &lt;a href=&quot;http://dylanbeattie.blogspot.com/2009/11/slightly-faster-url-resolver-module-for.html&quot;&gt;is available here&lt;/a&gt;. The original version posted here was very, very slow. Probably not a good idea to use it for anything. Ever.&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;One of the few things I actually &lt;em&gt;liked&lt;/em&gt; about ASP.NET WebForms was that you could do things like &lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&amp;lt;a href=”~/my/account.aspx” runat=”server”&amp;gt;My Account&amp;lt;/a&amp;gt; &lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;and ASP.NET would magically turn the tilde character (~) into the current relative application root – so you could debug your apps on &lt;a href=&quot;http://localhost:4567/&quot;&gt;http://localhost:4567/&lt;/a&gt; and then deploy them to &lt;a href=&quot;http://www.myserver.com/some/app/&quot;&gt;http://www.myserver.com/some/app/&lt;/a&gt;, and your links wouldn’t break.&lt;/p&gt;  &lt;p&gt;ASP.NET MVC doesn’t like things that are runat=”server” – and with good reason, I think – but this does mean you can end up with rather a lot of calls to ResolveUrl() sprinked throughout your code.&lt;/p&gt;  &lt;p&gt;To get around this, I’ve hacked together an HTTP module that basically rewrites the output stream on the fly. It wraps the HTTP output stream (the thing you&apos;re writing to when you Response.Write stuff) in a &apos;smart&apos; stream wrapper, and the &lt;del&gt;magic&lt;/del&gt; &lt;ins&gt;naively optimistic&lt;/ins&gt; part looks like this:&lt;/p&gt;  &lt;blockquote&gt;   &lt;pre&gt;public override void Write(byte[] buffer, int offset, int count) {
  if (HttpContext.Current.Handler is System.Web.Mvc.MvcHandler) {
    HttpContext.Current.Trace.Warn(&amp;quot;Resolving URLs in output stream...&amp;quot;);
    byte[] data = new byte[count];
    Buffer.BlockCopy(buffer, offset, data, 0, count);
    string html = Encoding.ASCII.GetString(data);

    // Don&apos;t try and use Regex transformations on your 
    // entire output stream. It is slow. Like, really, really slow.
    // Take a look at &lt;a href=&quot;http://dylanbeattie.blogspot.com/2009/11/slightly-faster-url-resolver-module-for.html&quot;&gt;this updated version&lt;/a&gt; instead.

    var re = new Regex(&amp;quot;(?&lt;attr&gt;src|href|action)=\&amp;quot;~/&amp;quot;, RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.ExplicitCapture);
    html = re.Replace(html, &amp;quot;${attr}=\&amp;quot;&amp;quot; + VirtualPathUtility.ToAbsolute(&amp;quot;~/&amp;quot;));
    data = Encoding.ASCII.GetBytes(html);
    sink.Write(data, 0, html.Length);
    HttpContext.Current.Trace.Warn(&amp;quot;Resolved URLs in output stream.&amp;quot;);
  } else {
    sink.Write(buffer, offset, count);
  }
}&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;Basically, it looks for HTML SRC, ACTION and HREF attributes whose value begins with ~/, and replaces the ~ with the application’s virtual path on the fly. &lt;del&gt;I haven’t tested this code for performance, so I don’t know what kind of impact it’ll have on your page response times,&lt;/del&gt; &lt;em&gt;This code is something like 200 times slower than a straight stream copy&lt;/em&gt;, but it’s running in a couple of demo apps I’m working on and it seems to work &lt;del&gt;pretty nicely&lt;/del&gt;.&lt;/p&gt;

&lt;p&gt;The full implementation is over on &lt;a href=&quot;http://code.google.com/p/tagalong/source/browse/trunk/DylanHax/Web/UrlResolverModule.cs&quot;&gt;Google Code&lt;/a&gt; if you’re interested. &lt;/p&gt;  </description>
          <pubDate>2009-11-04T01:42:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/11/04/url-resolver-module-for-aspnet-mvc.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/11/04/url-resolver-module-for-aspnet-mvc.html</guid>
        </item>
      
    
      
        <item>
          <title>Code from my HTML5 / MVC2 Talk at SkillsMatter</title>
          <description>&lt;p&gt;A big thank-you to everyone who came along to my &lt;a href=&quot;http://skillsmatter.com/podcast/open-source-dot-net/html-5-and-aspdot-net&quot;&gt;HTML5 and MVC2&lt;/a&gt; talk at &lt;a href=&quot;http://skillsmatter.com/&quot;&gt;SkillsMatter&lt;/a&gt; on Monday – and thanks also to SkillsMatter for hosting us! Whilst I’ve done plenty of talking at &lt;a href=&quot;http://www.altnetuk.com/2009-08-02.en.html&quot;&gt;unconferences&lt;/a&gt; and events like &lt;a href=&quot;http://www.barcamplondon.org/&quot;&gt;BarCamp&lt;/a&gt;, this was the first proper full-length technical talk I’ve given, so I’d really appreciate any feedback – especially since we might be doing a re-run in a couple of weeks.&lt;a href=&quot;http://lh5.ggpht.com/_LV_l8kYLOwo/SvDUjmzCUkI/AAAAAAAAASg/djUikiQb0TU/s1600-h/image%5B21%5D.png&quot;&gt;&lt;img style=&quot;border-bottom: 0px; border-left: 0px; display: block; float: none; margin-left: auto; border-top: 0px; margin-right: auto; border-right: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh6.ggpht.com/_LV_l8kYLOwo/SvDUkFkB_wI/AAAAAAAAASk/YGm2tlzLxyA/image_thumb%5B13%5D.png?imgmax=800&quot; width=&quot;400&quot; height=&quot;120&quot; /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;During the talk, I demo’ed a tiny web app – &lt;a href=&quot;http://code.google.com/p/tagalong&quot;&gt;TagAlong&lt;/a&gt; - that I’ve built to showcase some of the new features in HTML 5 and ASP.NET MVC preview 2. This is by no means production code – if nothing else, I’m using static List&amp;lt;T&amp;gt;’s instead of having an actual database, so your changes will disappear every time you restart the app – but it should be pretty easy to get it up and running and poke around.&lt;/p&gt;  &lt;p&gt;If you’re interested, the code is online at &lt;a title=&quot;http://code.google.com/p/tagalong&quot; href=&quot;http://code.google.com/p/tagalong&quot;&gt;http://code.google.com/p/tagalong&lt;/a&gt; – you’ll need &lt;a href=&quot;http://www.microsoft.com/downloads/details.aspx?familyid=D3F06BB9-5F5F-4F46-91E9-813B3FCE2DB1&amp;amp;displaylang=en&quot;&gt;MVC 2 Preview 2&lt;/a&gt; installed to run it, but everything else is included.&lt;/p&gt;  &lt;p&gt;A couple of other interesting links that I mentioned during the talk:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;The page structure diagrams are taken from &lt;a href=&quot;http://www.alistapart.com/articles/previewofhtml5&quot;&gt;A List Apart&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;Dean Edward’s work-in-progress Javascript library to emulate HTML5 in downlevel browsers is online at &lt;a href=&quot;http://html5-now.appspot.com/&quot;&gt;html5-now.appspot.com&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;Kevin Roast created the &lt;a href=&quot;http://www.kevs3d.co.uk/dev/asteroids/&quot;&gt;Asteroids demo&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;The &lt;a href=&quot;http://www.chromeexperiments.com/detail/js-fireworks/&quot;&gt;Fireworks demo&lt;/a&gt; was created by &lt;a href=&quot;http://kenneth.kufluk.com/blog/&quot;&gt;Kenneth Kufluk&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;&lt;a href=&quot;http://lh5.ggpht.com/_LV_l8kYLOwo/SvDUIfVTfgI/AAAAAAAAASY/x7XHq0HYu_o/s1600-h/image%5B10%5D.png&quot;&gt;&amp;#160;&lt;/a&gt;&lt;/p&gt;  </description>
          <pubDate>2009-11-04T01:08:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/11/04/code-from-my-html5-mvc2-talk-at.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/11/04/code-from-my-html5-mvc2-talk-at.html</guid>
        </item>
      
    
      
        <item>
          <title>You Forgot to Say the Magic Word…</title>
          <description>&lt;p&gt;In Microsoft SQL Server, this query won’t work:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;SELECT * FROM ( SELECT * FROM Customer UNION SELECT * FROM Supplier) ORDER BY CompanyName&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;But – if you ask &lt;em&gt;nicely&lt;/em&gt;, it does exactly what you’d expect:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;SELECT * FROM ( SELECT * FROM Customer UNION SELECT * FROM Supplier) &lt;strong&gt;&lt;font color=&quot;#ff0000&quot;&gt;PLEASE&lt;/font&gt; &lt;/strong&gt;ORDER BY CompanyName&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;You won’t believe the look on your colleague’s faces when you solve their problem using simple good manners.&lt;/p&gt;  &lt;p&gt;&lt;small&gt;(Of course, it actually works because PLEASE in that context just acts as a table-name alias for result of the UNION sub-select, and sub-selects in SQL Server need to have a name. But don&apos;t let that stop you using it for fun and profit.)&lt;/small&gt;  </description>
          <pubDate>2009-10-20T18:00:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/10/20/you-forgot-to-say-magic-word.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/10/20/you-forgot-to-say-magic-word.html</guid>
        </item>
      
    
      
        <item>
          <title>Is doctype.com a License Too Far for Stack Overflow?</title>
          <description>&lt;p&gt;&lt;strong&gt;Short answer: &lt;/strong&gt;&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;No, because doctype.com doesn’t use technology licensed from Stack Overflow. Sorry. I got this one completely, completely wrong. D’oh. &lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;&lt;strong&gt;Long answer:&lt;/strong&gt;&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;This post was originally inspired by doctype.com. I now understand, thanks to an extremely informative &lt;a href=&quot;https://www.blogger.com/comment.g?blogID=7295454224203070190&amp;amp;postID=7433653809826624030&quot;&gt;comment from one of the doctype.com developers&lt;/a&gt;, that doctype.com doesn’t actually run on Stack Exchange. It looks and feels very similar, but is in fact a completely separate codebase built by the guys at doctype.com using Ruby on Rails.       &lt;br /&gt;      &lt;br /&gt;This post is therefore based on completely incorrect assumptions. I’ve struck-out the bits that are actually factually incorrect, although my concerns about fragmenting the user based remain valid – even more so since I discovered that ask.sqlteam.com and ask.sqlservercentral.com are both Stack Exchange sites - but clearly doctype.com has nothing to do with it, and in fact, their platform offers a lot of design-centric tools that Stack Overflow doesn’t.       &lt;br /&gt;      &lt;br /&gt;There’s also &lt;a href=&quot;http://meta.stackoverflow.com/questions/25558/stack-exchange-is-splintering-off-portions-of-so-is-that-a-problem&quot;&gt;this disucussion&lt;/a&gt; at meta.stackoverflow.com that addresses a lot of the same concerns. &lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;&lt;a title=&quot;The Coney Island Parachute Drop at sunset, October 2006.&quot; href=&quot;http://www.flickr.com/photos/dylanbeattie/2565249473/in/set-72157605523397810/&quot;&gt;&lt;img style=&quot;border-right-width: 0px; margin: 0px 0px 20px 20px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px&quot; title=&quot;The derelict Parachute Drop ride at Coney Island.&quot; border=&quot;0&quot; alt=&quot;The derelict Parachute Drop ride at Coney Island.&quot; align=&quot;right&quot; src=&quot;http://farm4.static.flickr.com/3080/2565249473_c2e29de1cd.jpg&quot; width=&quot;320&quot; height=&quot;240&quot; /&gt;&lt;/a&gt;(Note: In this post, where I say Stack Overflow I’m referring to the website, and where I say StackOverflow LLC, I’m talking about the company&amp;#160; behind it.)&lt;/p&gt;  &lt;p&gt;I’ve been &lt;a href=&quot;http://stackoverflow.com/users/5017&quot;&gt;using stackoverflow.com&lt;/a&gt; since it was in beta, and I love it. I ask questions. I answer questions. I hang out and read and comment and vote and generally find the whole thing a hugely rewarding experience. I think it works for two reasons. &lt;/p&gt;  &lt;p&gt;First, the technology platform (now available as Stack Exchange – more on this in a moment) is innovative, usable and packed with great ideas. &lt;/p&gt;  &lt;p&gt;Second, by &lt;a href=&quot;http://blog.stackoverflow.com/&quot;&gt;actively engaging&lt;/a&gt; with people who followed &lt;a href=&quot;http://www.codinghorror.com/blog/&quot;&gt;Jeff Atwood&lt;/a&gt; and &lt;a href=&quot;http://www.joelonsoftware.com/&quot;&gt;Joel Spolsky&lt;/a&gt;’s blogs, they gathered exactly the right audience to breathe life into their product. Stack Overflow launched with a committed, dedicated community of experts already in place. They created a forum where people like &lt;a href=&quot;http://www.amazon.co.uk/exec/obidos/search-handle-url?_encoding=UTF8&amp;amp;search-type=ss&amp;amp;index=books-uk&amp;amp;field-author=Jon%20Skeet&quot;&gt;Jon Skeet&lt;/a&gt; will donate endless hours of their time for nothing more than kudos and &lt;a href=&quot;http://stackoverflow.com/badges&quot;&gt;badges&lt;/a&gt;. (I bet Jon’s employers are wishing they’d thought of that…) &lt;/p&gt;  &lt;p&gt;Here’s a few choice quotes from &lt;a href=&quot;http://www.inc.com/magazine/20081101/how-hard-could-it-be-the-unproven-path.html&quot;&gt;Joel Spolsky’s Inc.com column&lt;/a&gt; I’m referring to (my emphasis)&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;“Between our two blogs, we felt we could generate the &lt;strong&gt;critical mass it would take to make the site work.&lt;/strong&gt;”&lt;/p&gt;    &lt;p&gt;“I started a business with the objective of building a big audience, which we would &lt;strong&gt;figure out how to monetize later&lt;/strong&gt;.”&lt;/p&gt;    &lt;p&gt;“we promised the audience that the site would &lt;strong&gt;always be free&lt;/strong&gt; and open to the public, and that we would never add flashing punch-the-monkey ads or pop-up windows.”&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Now this is the web, where “monetize” usually means “slap advertising all over everything.” – but when Stack Overflow introduced advertising, they were sympathetic and responsive to users’ feedback, and quickly evolved an advertising model that’s elegant, unobtrusive and complements the ethos of the site. The &lt;a href=&quot;http://stackoverflow.com/badges/71/woot-enthusiast&quot;&gt;Woot! badge&lt;/a&gt; was clever. The tiny Adobe logo on tags like &lt;a href=&quot;http://stackoverflow.com/questions/tagged/flex&quot;&gt;flex&lt;/a&gt; and &lt;a href=&quot;http://stackoverflow.com/questions/tagged/actionscript&quot;&gt;actionscript&lt;/a&gt; was &lt;em&gt;really&lt;/em&gt; clever – possibly the best use of targeted advertising I’ve seen. &lt;/p&gt;  &lt;p&gt;Before long, non-programmers were asking how they could get a slice of the Stack Overflow goodness, and so &lt;a href=&quot;http://serverfault.com/&quot;&gt;serverfault.com&lt;/a&gt; – for systems admin questions – and &lt;a href=&quot;http://superuser.com/&quot;&gt;superuser.com&lt;/a&gt; – for general IT enthusiasts – were born. That clearly worked, so they set up &lt;a href=&quot;http://stackexchange.com/&quot;&gt;Stack Exchange&lt;/a&gt;, to license the platform to third parties, and soon there was &lt;a href=&quot;http://moms4mom.com/&quot;&gt;moms4mom.com&lt;/a&gt; (questions about parenthood), &lt;a href=&quot;http://epicadvice.com/&quot;&gt;Epic Advice&lt;/a&gt; (questions about World of Warcraft), &lt;a href=&quot;http://ask.recipelabs.com/?src=stackexchangesites&quot;&gt;Ask Recipe Labs&lt;/a&gt; (cooking and food), &lt;a href=&quot;http://mathoverflow.net/&quot;&gt;Math Overflow&lt;/a&gt; (for mathematicians), and various other Stack Exchange sites covering video, photography, car maintenance – all sorts.&lt;/p&gt;  &lt;p&gt;A few days ago, I stumbled across &lt;a href=&quot;http://doctype.com/&quot;&gt;doctype.com&lt;/a&gt; – a &lt;del&gt;Stack Exchange&lt;/del&gt; site for HTML/CSS questions, web design and e-mail design – and some unsettling questions popped into my head. &lt;/p&gt;  &lt;h3&gt;1. Where am I supposed to ask my jQuery questions now?&lt;/h3&gt;  &lt;p&gt;I work on everything from T-SQL to a very occasional bit of Photoshop. There is a huge amount of crossover between HTML, CSS, Javascript, AJAX, and web server platforms and their various view/markup engines. Here’s the all-time most popular 20 tags on Stack Overflow, as of 20th October 2009:&lt;/p&gt;  &lt;div align=&quot;center&quot;&gt;   &lt;table style=&quot;border-collapse: collapse&quot; border=&quot;1&quot; cellpadding=&quot;4&quot; width=&quot;336&quot;&gt;&lt;tbody&gt;       &lt;tr&gt;         &lt;th width=&quot;57&quot;&gt;1&lt;/th&gt;          &lt;td width=&quot;203&quot;&gt;c#&lt;/td&gt;          &lt;td width=&quot;74&quot;&gt;43,860&lt;/td&gt;       &lt;/tr&gt;        &lt;tr&gt;         &lt;th width=&quot;57&quot;&gt;2&lt;/th&gt;          &lt;td width=&quot;203&quot;&gt;.net&lt;/td&gt;          &lt;td width=&quot;74&quot;&gt;24,590&lt;/td&gt;       &lt;/tr&gt;        &lt;tr&gt;         &lt;th width=&quot;57&quot;&gt;3&lt;/th&gt;          &lt;td width=&quot;203&quot;&gt;java&lt;/td&gt;          &lt;td width=&quot;74&quot;&gt;22,924&lt;/td&gt;       &lt;/tr&gt;        &lt;tr&gt;         &lt;th width=&quot;57&quot;&gt;4&lt;/th&gt;          &lt;td width=&quot;203&quot;&gt;asp.net&lt;/td&gt;          &lt;td width=&quot;74&quot;&gt;20,678&lt;/td&gt;       &lt;/tr&gt;        &lt;tr&gt;         &lt;th width=&quot;57&quot;&gt;5&lt;/th&gt;          &lt;td width=&quot;203&quot;&gt;php&lt;/td&gt;          &lt;td width=&quot;74&quot;&gt;16,797&lt;/td&gt;       &lt;/tr&gt;        &lt;tr style=&quot;background-color: #cccccc&quot;&gt;         &lt;th width=&quot;57&quot;&gt;6&lt;/th&gt;          &lt;td width=&quot;203&quot;&gt;javascript&lt;/td&gt;          &lt;td width=&quot;74&quot;&gt;16,363&lt;/td&gt;       &lt;/tr&gt;        &lt;tr&gt;         &lt;th width=&quot;57&quot;&gt;7&lt;/th&gt;          &lt;td width=&quot;203&quot;&gt;c++&lt;/td&gt;          &lt;td width=&quot;74&quot;&gt;15,462&lt;/td&gt;       &lt;/tr&gt;        &lt;tr&gt;         &lt;th width=&quot;57&quot;&gt;8&lt;/th&gt;          &lt;td width=&quot;203&quot;&gt;python&lt;/td&gt;          &lt;td width=&quot;74&quot;&gt;11,639&lt;/td&gt;       &lt;/tr&gt;        &lt;tr style=&quot;background-color: #cccccc&quot;&gt;         &lt;th width=&quot;57&quot;&gt;9&lt;/th&gt;          &lt;td width=&quot;203&quot;&gt;jquery&lt;/td&gt;          &lt;td width=&quot;74&quot;&gt;11,287&lt;/td&gt;       &lt;/tr&gt;        &lt;tr&gt;         &lt;th width=&quot;57&quot;&gt;10&lt;/th&gt;          &lt;td width=&quot;203&quot;&gt;sql&lt;/td&gt;          &lt;td width=&quot;74&quot;&gt;10,910&lt;/td&gt;       &lt;/tr&gt;        &lt;tr&gt;         &lt;th width=&quot;57&quot;&gt;11&lt;/th&gt;          &lt;td width=&quot;203&quot;&gt;iphone&lt;/td&gt;          &lt;td width=&quot;74&quot;&gt;9,686&lt;/td&gt;       &lt;/tr&gt;        &lt;tr&gt;         &lt;th width=&quot;57&quot;&gt;12&lt;/th&gt;          &lt;td width=&quot;203&quot;&gt;sql-server&lt;/td&gt;          &lt;td width=&quot;74&quot;&gt;9,165&lt;/td&gt;       &lt;/tr&gt;        &lt;tr style=&quot;background-color: #cccccc&quot;&gt;         &lt;th width=&quot;57&quot;&gt;13&lt;/th&gt;          &lt;td width=&quot;203&quot;&gt;html&lt;/td&gt;          &lt;td width=&quot;74&quot;&gt;7,932&lt;/td&gt;       &lt;/tr&gt;        &lt;tr&gt;         &lt;th width=&quot;57&quot;&gt;14&lt;/th&gt;          &lt;td width=&quot;203&quot;&gt;mysql&lt;/td&gt;          &lt;td width=&quot;74&quot;&gt;7,794&lt;/td&gt;       &lt;/tr&gt;        &lt;tr&gt;         &lt;th width=&quot;57&quot;&gt;15&lt;/th&gt;          &lt;td width=&quot;203&quot;&gt;asp.net-mvc&lt;/td&gt;          &lt;td width=&quot;74&quot;&gt;6,532&lt;/td&gt;       &lt;/tr&gt;        &lt;tr&gt;         &lt;th width=&quot;57&quot;&gt;16&lt;/th&gt;          &lt;td width=&quot;203&quot;&gt;windows&lt;/td&gt;          &lt;td width=&quot;74&quot;&gt;6,425&lt;/td&gt;       &lt;/tr&gt;        &lt;tr&gt;         &lt;th width=&quot;57&quot;&gt;17&lt;/th&gt;          &lt;td width=&quot;203&quot;&gt;wpf&lt;/td&gt;          &lt;td width=&quot;74&quot;&gt;6,370&lt;/td&gt;       &lt;/tr&gt;        &lt;tr&gt;         &lt;th width=&quot;57&quot;&gt;18&lt;/th&gt;          &lt;td width=&quot;203&quot;&gt;ruby-on-rails&lt;/td&gt;          &lt;td width=&quot;74&quot;&gt;6,095&lt;/td&gt;       &lt;/tr&gt;        &lt;tr&gt;         &lt;th width=&quot;57&quot;&gt;19&lt;/th&gt;          &lt;td width=&quot;203&quot;&gt;c&lt;/td&gt;          &lt;td width=&quot;74&quot;&gt;6,071&lt;/td&gt;       &lt;/tr&gt;        &lt;tr style=&quot;background-color: #cccccc&quot;&gt;         &lt;th width=&quot;57&quot;&gt;20&lt;/th&gt;          &lt;td width=&quot;203&quot;&gt;css&lt;/td&gt;          &lt;td width=&quot;74&quot;&gt;5,849&lt;/td&gt;       &lt;/tr&gt;     &lt;/tbody&gt;&lt;/table&gt; &lt;/div&gt;  &lt;div align=&quot;center&quot;&gt;&amp;#160;&lt;/div&gt;  &lt;p&gt;The highlighted rows are &lt;strong&gt;all Web client technologies – &lt;/strong&gt;and that ignores all the questions that get tagged as PHP, ASP.NET or Ruby on Rails but actually turn out to involve HTML, CSS or jQuery once the experts have had a look at them. There’s clearly a thriving community of web designers and developers already signed up to Stack Overflow. &lt;strong&gt;Should we now all be asking CSS questions on doctype.com instead of using Stack Overflow?&lt;/strong&gt; I have no idea!&amp;#160; &lt;/p&gt;  &lt;p&gt;&lt;del&gt;I realize there are HTML / CSS gurus out there who aren’t currently using Stack Overflow because they think it’s just for programmers – but wouldn’t it be better if Stack Overflow was looking at ways to attract that expertise, rather than renting them a walled garden of their own?&amp;#160; Getting designers and coders to communicate is hard enough at the best of times, and giving them their own “definitive” knowledge-exchange resources isn’t going to help.&lt;/del&gt; &lt;/p&gt;  &lt;h3&gt;2. What Does This Mean For The Stack Overflow Community?&lt;/h3&gt;  &lt;p&gt;Shortly after discovering doctype.com, I tweeted something daft about “&lt;a href=&quot;http://twitter.com/dylanbeattie/status/4975040906&quot;&gt;stackoverflow failed as a business&lt;/a&gt;”, which elicited &lt;a href=&quot;http://twitter.com/michaelfogcreek/status/4981014161&quot;&gt;this response&lt;/a&gt; from one of the guys at Fog Creek… he’s absolutely right, of course. StackOverflow LLC is clearly doing just fine – their product is being enthusiastically received, and I’m thoroughly looking forward to &lt;a href=&quot;http://stackoverflow.carsonified.com/&quot;&gt;their DevDays event&lt;/a&gt; later this month. &lt;/p&gt;  &lt;p&gt;However, I think the success of StackOverflow LLC is potentially coming at a cost to stackoverflow.com – the site and the community that surrounds it – and in &lt;em&gt;that&lt;/em&gt; respect, I believe that the single, definitive, free site that they originally launched last year has failed to fulfil its potential as a revenue stream.&lt;/p&gt;  &lt;p&gt;&lt;a title=&quot;That famous bridge in Central Park where they always meet at the end of the movie.&quot; href=&quot;http://www.flickr.com/photos/dylanbeattie/2566073220/in/set-72157605523397810/&quot;&gt;&lt;img style=&quot;border-right-width: 0px; margin: 10px 20px 20px 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px&quot; title=&quot;The bridge in Central Park.&quot; border=&quot;0&quot; alt=&quot;The bridge in Central Park.&quot; align=&quot;left&quot; src=&quot;http://farm4.static.flickr.com/3255/2566073220_529abe16e6.jpg&quot; width=&quot;320&quot; height=&quot;240&quot; /&gt;&lt;/a&gt;The decision to license Stack Exchange to sites who are directly competing for mindshare with Stack Overflow’s “critical mass” worries me, because it suggests that StackOverflow LLC is now calling the shots instead of stackoverflow.com, and making decisions that are financially astute but potentially deleterious to the existing user base. &lt;/p&gt;  &lt;p&gt;They are entitled to do this, of course. It’s their site, and I’m extremely grateful that I get to use it for free.&lt;/p&gt;  &lt;p&gt;&lt;del&gt;What’s ironic is that the worst case scenario here - for me, for stackoverflow.com, and for the developer community at large - is that doctype.com is wildly successful, becomes the &lt;em&gt;de facto&lt;/em&gt; resource for HTML/CSS questions on the internet, generates a healthy revenue stream of its own, and StackOverflow LLC does quite nicely out of the deal. The format is copied by other technology sites, and soon there’s a site for SQL, a site for Java, a site for WinForms, a site for PHP… stackoverflow.com is no longer the definitive resource for programming questions, and we, the users, are back to using Google to trawl a dozen different forum sites looking for answers, and cross-posting our questions to half-a-dozen different sites in the hope that &lt;em&gt;one&lt;/em&gt; of them might elicit a response. It’ll be just like 2006 all over again.&lt;/del&gt;&lt;/p&gt;  &lt;h3&gt;OK, So What Would I Have Done Instead?&lt;/h3&gt;  &lt;p&gt;&lt;img style=&quot;margin: 0px 0px 20px 20px; display: inline&quot; title=&quot;Fortitude, the stone lion outside the New York Public Library.&quot; alt=&quot;Fortitude, the stone lion outside the New York Public Library.&quot; align=&quot;right&quot; src=&quot;http://farm4.static.flickr.com/3184/2565251187_ef99f4bca2.jpg&quot; width=&quot;240&quot; height=&quot;320&quot; /&gt;&lt;del&gt;doctype.com is trying to compete with an established market leader, by licensing that leader’s technology, in a market where the leader has a year’s head start &lt;em&gt;and &lt;/em&gt;controls the technology platform. That’s like trying to open a BMW dealership in a town where there’s already a BMW factory outlet, run by two guys everyone knows and loves, whose reputation for service and maintenance is second to none. It &lt;em&gt;has &lt;/em&gt;to fail… right?&lt;/del&gt; &lt;sup&gt;[1]&lt;/sup&gt;&lt;/p&gt;  &lt;p&gt;But – I can appreciate what they’re trying to do. I appreciate that StackOverflow LLC is not a charity, and I appreciate why the folks behind doctype.com think there’s a niche for an SO-style site focusing on designers. &lt;/p&gt;  &lt;p&gt;The key to Stack Overflow’s success isn’t the catchy domain name, or that fetching orange branding. The key is the &lt;strong&gt;information&lt;/strong&gt; and the &lt;strong&gt;people &lt;/strong&gt;- I see no technical reason why something like doctype.com couldn’t be licensed as a front-end product that’s integrated with the same database and the same user community as Stack Overflow. Modify the back-end code so that users who sign up at doctype.com get certain filters applied. Use a different web address, a different design, maybe just include questions tagged with html, css, jquery and javascript to start with, so new users see content that’s more immediately relevant to their interests - but when they search or ask a question, they’re getting the full benefit of Stack Overflow’s huge community of loyal experts – not to mention the tens of thousands of accepted answers already in the Stack Overflow database.&lt;/p&gt;  &lt;p&gt;How about it? doctype.stackoverflow.com, javaguru.stackoverflow.com, aspnetmvc.stackoverflow.com… each a finely-tuned filtered view onto a single, authoritative information resource for programming questions, from assembler up to CSS sprites. That has to be better than the gradual ghettoization and eventual fragmentation of a thriving community, yes?&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Stop Press: &lt;/strong&gt;Someone just pointed me at &lt;a href=&quot;http://ask.sqlservercentral.com/&quot;&gt;ask.sqlservercentral.com&lt;/a&gt;. That’s – yep, you guessed it – a Stack Exchange site for SQL questions. As if having to choose between stackoverflow.com and serverfault.com wasn’t bad enough. Does anyone else think this is getting a bit silly?&lt;/p&gt;  &lt;p&gt;&lt;small&gt;&lt;del&gt;[1] Of course, it’s entirely possible that Joel &amp;amp; the gang &lt;em&gt;know&lt;/em&gt; this, and are quite happy to take $129 a month off the folks at doctype.com whilst they work this out for themselves... &lt;/del&gt;&lt;/small&gt;&lt;/p&gt;  </description>
          <pubDate>2009-10-20T13:27:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/10/20/is-doctypecom-license-too-far-for-stack.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/10/20/is-doctypecom-license-too-far-for-stack.html</guid>
        </item>
      
    
      
        <item>
          <title>Is There Such A Thing As Test-Driven Maintenance?</title>
          <description>&lt;p&gt;One of the great strengths of test-driven development is that systems that are built one tiny test at a time tend to be… well, &lt;em&gt;better&lt;/em&gt;. Fewer bugs. Cleaner architecture. Better separation of concerns. The characteristics that make code hard to &lt;em&gt;modify&lt;/em&gt; are the same characteristics that make it hard to &lt;em&gt;test&lt;/em&gt;, so by incorporating testing into your development cycle, you find – and fix – these pain points early, whilst development is cheap, instead of discovering them three months after you’ve shipped and spending the next two years death-marching your way to version 1.1.&lt;/p&gt;&lt;p&gt;However, there’s a flip-side to this. Not a disadvantage of TDD &lt;em&gt;per se&lt;/em&gt;, but something that I think is an unavoidable side-effect of placing so much emphasis on TDD as applied to green-field projects. The “test-driven” part and the “development” part are so tightly coupled that it&apos;s easy to assume that &lt;strong&gt;automated testing was only applicable to new systems.&lt;/strong&gt;&amp;#160;&lt;/p&gt;&lt;p&gt;I can’t be the only one using Moq and NUnit on new projects, whilst the rest of the time grimly hacking away on legacy code, dancing the Alt-Tab-F5 fandango and spending hours manually testing new features before they go live. And I can’t be the only one who thinks this is just &lt;em&gt;not right&lt;/em&gt; – after all, the big legacy apps are the ones with the thousands of paying customers; surely if we’re running automated tests on anything, it should be those? &lt;/p&gt;&lt;p&gt;&lt;a title=&quot;I love TeamCity so much, I want to go to the park and carve &amp;quot;DB 4 TC 4 EVER&amp;quot; into a tree.&quot; href=&quot;http://www.jetbrains.com/teamcity/&quot;&gt;&lt;img style=&quot;border-bottom: 0px; border-left: 0px; margin: 0px 0px 20px 20px; display: inline; border-top: 0px; border-right: 0px&quot; title=&quot;I love TeamCity so much, I want to go to the park and carve &amp;quot;DB 4 TC 4 EVER&amp;quot; into a tree.&quot; border=&quot;0&quot; alt=&quot;I love TeamCity so much, I want to go to the park and carve &amp;quot;DB 4 TC 4 EVER&amp;quot; into a tree.&quot; align=&quot;right&quot; src=&quot;http://lh6.ggpht.com/_LV_l8kYLOwo/Ste7y-lvlBI/AAAAAAAAAQY/XjlOTaJFVUA/teamcity512.png?imgmax=800&quot; width=&quot;120&quot; height=&quot;120&quot; /&gt;&lt;/a&gt;Last week, two things happened. One was a happy afternoon spent setting up TeamCity to build and man age most of our web projects. The other was a botched deployment of an update to a legacy site – the &lt;em&gt;new&lt;/em&gt; code worked fine, but a config change deployed as part of the update actually broke half-a-dozen other web apps running on the same host. Broke, as in they&amp;#160; disappeared and were replaced by a Yellow Screen of Death, because the root web.config was loading an HttpModule from a new assembly, and the other web apps were picking up the root’s config settings but didn’t have the necessary DLL. Easily fixed, but rather embarrassing. &lt;/p&gt;&lt;h4&gt;If It Runs, You Can Test It&lt;/h4&gt;&lt;p&gt;This was a stupid mistake on my part, easily avoided, and it suddenly occurred to me, &lt;strong&gt;screamingly easy to detect&lt;/strong&gt;. We may not have any controller methods or IoC containers to enable granular unit tests, but we can certainly make sure that the site is actually up and responding to HTTP requests.&lt;/p&gt;&lt;p&gt;One of the team put together a very quick NUnit project, which just sent an HTTP GET to the default page of each web app, and asserted that it returned a 200 OK and some valid-looking HTML. Suddenly, after five years of tedious and error-prone manual testing, we had &lt;font color=&quot;#008000&quot;&gt;&lt;strong&gt;green lights&lt;/strong&gt;&lt;/font&gt; that meant our websites were OK. It took another ten minutes or so to add the new tests to TeamCity, and &lt;em&gt;voila&lt;/em&gt; – suddenly we’ve got legacy code being automatically pushed to the test server, and then a way of firing HTTP requests at the server and making sure something comes back.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;http://lh4.ggpht.com/_LV_l8kYLOwo/Ste7zUzXBgI/AAAAAAAAAQQ/RLN7ICKHags/s1600-h/image4.png&quot;&gt;&lt;img style=&quot;border-right-width: 0px; margin: 0px 20px 20px 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; align=&quot;left&quot; src=&quot;http://lh3.ggpht.com/_LV_l8kYLOwo/Ste7z-9Q5rI/AAAAAAAAAQU/-j6VUpjLrw4/image_thumb2.png?imgmax=800&quot; width=&quot;255&quot; height=&quot;192&quot; /&gt;&lt;/a&gt; You can do this. You can do this right now. TeamCity is free, Subversion is free, NUnit is free, and &lt;em&gt;it doesn’t matter what your web apps are running.&lt;/em&gt;&lt;strong&gt; &lt;/strong&gt;Because the ‘API’ we’re testing against is plain simple HTTP request/response, you can test ASP, ASP.NET, PHP, ColdFusion, Java – even static HTML.&lt;/p&gt;&lt;p&gt;What’s beautiful is that, once the test project’s part of your continuous-integration setup, it becomes really easy to &lt;strong&gt;add new tests&lt;/strong&gt;… and that’s where things start getting interesting. Retro-fitting unit tests to a legacy app is hard, but when you need to modify a piece of the legacy app &lt;em&gt;anyway&lt;/em&gt;, to fix a bug or add a feature, it’s not that hard to put together a couple of tests for your new code at the same time. Test-first, or code-first – doesn’t matter; just make sure they make it into the test suite. If you’re coupled to legacy data models and payment services and ASP session variables, you’re probably going to struggle to set up the required preconditions. But, most of the time, you’ll find &lt;strong&gt;something&lt;/strong&gt; you can test automatically, which means it’s one less feature you need to worry about every time you make a change or deploy a build.&lt;/p&gt;&lt;p&gt;We now have 19 tests covering over 50,000 lines of code. Yeah, I know - that’s not a lot. But it’s a start, and the lines that &lt;em&gt;are&lt;/em&gt; getting hit are absolutely critical. They’re the lines that initialize the application, verify the database is accessible, make sure the server’s configuration is sane, and make sure our homepage is returning something that starts with &amp;lt;html&amp;gt; and has the word “Welcome” in it – because I figure if we’re going to start somewhere, it might as well be at the beginning.&lt;/p&gt;</description>
          <pubDate>2009-10-16T00:21:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/10/16/is-there-such-thing-as-test-driven.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/10/16/is-there-such-thing-as-test-driven.html</guid>
        </item>
      
    
      
        <item>
          <title>There Can Be Only One. Or Two. Or Three, but Never Four.</title>
          <description>&lt;p&gt;A quick but very simple technique to limit the number of instances of a .NET app that will execute at once:&lt;/p&gt;  &lt;blockquote&gt;   &lt;pre&gt;using System;
using System.Linq;
using System.Diagnostics;

namespace ConsoleApplication1 {
  public class Program {
    static void Main(string[] args) {

      var MAX_PERMITTED_INSTANCES = 3;

      var myProcessName = Process.GetCurrentProcess().ProcessName;
      Process[] processes = Process.GetProcesses();
      var howManyOfMe = processes.Where(p =&amp;gt; p.ProcessName == myProcessName).Count();
      if (howManyOfMe &amp;gt; MAX_PERMITTED_INSTANCES) {
        Console.WriteLine(&amp;quot;Too many instances - exiting!&amp;quot;);
      } else {
        Console.WriteLine(&amp;quot;I&apos;m process #{0} and I&apos;m good to go!&amp;quot;, howManyOfMe);
        /* do heavy lifting here! */
      }
      Console.ReadKey(false);
    }
  }
}&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;Very handy if – like we do – you’re firing off potentially expensive encoding jobs every few minutes via a scheduled task, and you’re happy for 3-4 of them to be running at any one time – hey, that’s what multicore CPUs are for, right? - but you’d rather not end up with 37 instances of encoder.exe all fighting for your CPU cycles like cats fighting for the last bit of bacon.&lt;/p&gt;

&lt;p&gt;I’m still sometimes really, really impressed at how easy stuff like this is in .NET… I thought this would end up being hours of horrible extern/Win32 API calls, but no. It’s that easy. Very nice.&lt;/p&gt;  </description>
          <pubDate>2009-10-14T23:32:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/10/14/there-can-be-only-one-or-two-or-three.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/10/14/there-can-be-only-one-or-two-or-three.html</guid>
        </item>
      
    
      
        <item>
          <title>Hey… My World Wide Web Went Weird!</title>
          <description>&lt;p&gt;About a week ago, my world wide web went weird. There’s no other way to describe it. Well, OK, there’s probably lots of ways to describe it, but I like the alliteration. Anyway – what happened was, lots of websites just suddenly started looking horrible, for no readily apparent reason. Like the “spot the difference” screenshot below. &lt;br&gt;&amp;nbsp;&lt;a href=&quot;https://lh3.googleusercontent.com/-Kkt69USKgz8/V3ztNnEjaVI/AAAAAAAAEAE/MornjqvZGbM/s1600-h/image%25255B4%25255D.png&quot;&gt;&lt;img title=&quot;image&quot; style=&quot;border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; border-left: 0px; display: block; padding-right: 0px; margin-right: auto&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;https://lh3.googleusercontent.com/-FyrrsRHfuxo/V3ztN-zTXDI/AAAAAAAAEAI/B2qVzekLZ-c/image_thumb%25255B10%25255D.png?imgmax=800&quot; width=&quot;546&quot; height=&quot;258&quot;&gt;&lt;/a&gt;&lt;br&gt;See how the snapshot on the left looks really rather unpleasant, while the one on the right is nice and crisp and readable?&lt;/p&gt; &lt;p&gt;First time I saw it, I assumed it was some ill-inspired redesign of a single site. Second time, I thought it must be some new design trend. Then I noticed it happening on some of our own sites - including our wiki and our FogBugz install – and since I definitely hadn’t messed around with them, that ruled out the possibility of it being something happening server-side. &lt;em&gt;Some&lt;/em&gt; sites were still working and looking just fine, so it probably wasn’t a browser issue… but, thanks to a bit of lucky exploration and the awesome power of &lt;a href=&quot;http://getfirebug.com/&quot;&gt;Firebug&lt;/a&gt;, I just worked out what’s going on.&lt;/p&gt; &lt;p&gt;All the affected sites use the same CSS font specification:&lt;/p&gt; &lt;blockquote&gt;&lt;pre&gt;body { font-family: Helvetica, Arial, sans-serif; }&lt;/pre&gt;&lt;/blockquote&gt;
&lt;p&gt;Of course, Helvetica isn’t a standard Windows typeface, so on most Windows PCs, the browser will skip over Helvetica and render the document using Arial instead. Last week, whilst working on something for our editorial team, I installed some additional fonts on my workstation, which includes – you guessed it – the Helvetica typeface shown above. &lt;/p&gt;
&lt;p&gt;Arial, and most other Windows fonts like Calibri, Verdana, Trebuchet, use a neat trick called &lt;a href=&quot;http://en.wikipedia.org/wiki/Font_hinting&quot;&gt;font hinting&lt;/a&gt; which ensures that when they’re rendered at small sizes, the shape of the individual glyphs lines up nicely with the pixels of your display – so you get nice, crispy fonts. The particular Helvetica flavour I’d installed obviously doesn’t do this – hence the spidery nastiness in the left-hand screenshot.&lt;/p&gt;
&lt;p&gt;I’m guessing either the designers who built most of these sites had a hinted version of Helvetica (possibly ‘cos they’re Mac-based?), or that they just never tested that particular CSS rule on a Windows system with a print-optimised Helvetica typeface installed.&lt;/p&gt;
&lt;p&gt;I guess the moral of the story is that if you want to annoy somebody in a &lt;em&gt;really&lt;/em&gt; subtle way, install the nastiest Helvetica font you can find on their Windows PC. I’m pretty sure that if I hadn’t stumbled across the solution, sooner or later I’d actually have reinstalled in despair just to get things looking crispy again.&lt;/p&gt;</description>
          <pubDate>2009-10-13T12:52:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/10/13/hey-my-world-wide-web-went-weird.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/10/13/hey-my-world-wide-web-went-weird.html</guid>
        </item>
      
    
      
        <item>
          <title>RESTful Routing in ASP.NET MVC 2 Preview 2</title>
          <description>&lt;p&gt;Microsoft recently &lt;a href=&quot;http://haacked.com/archive/2009/10/01/asp.net-mvc-preview-2-released.aspx&quot;&gt;released Preview 2&lt;/a&gt; of the next version of their &lt;a href=&quot;http://www.asp.net/mvc/&quot;&gt;ASP.NET MVC framework&lt;/a&gt;. There’s a couple of things in this release that are designed to allow your controls to expose &lt;a href=&quot;http://en.wikipedia.org/wiki/Representational_State_Transfer&quot;&gt;RESTful APIs&lt;/a&gt;, and – more interestingly, I think – to let you build your own Web pages and applications on top of the same controllers and routing set-up that provides this RESTful API. In other words, you can build one RESTful API exposing your business logic and domain methods, and then your own UI layer – your views and pages – can be implemented on top of this same API that you’re exposing for developers and third parties.&lt;/p&gt;  &lt;p&gt;Thing is… I think they way they’ve implemented it in preview&amp;#160; doesn’t really work. Don’t get me wrong; there are some good ideas in there – among them an HTML helper method, &lt;strong&gt;Html.HttpMethodOverride,&lt;/strong&gt; that works with the MVC routing and controller back-end to “simulate” unsupported HTTP verbs on browsers that don’t support them (which was &lt;em&gt;all &lt;/em&gt;of them, last time I looked). You write your form code like this:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&amp;lt;form action=”/products/1234” method=”post”&amp;gt;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &amp;lt;%= Html.HttpMethodOverride(HttpVerbs.Delete) %&amp;gt;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &amp;lt;input type=”submit” name=”whatever” value=”Delete This Product” /&amp;gt;       &lt;br /&gt;&amp;lt;/form&amp;gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;and then in your controller code, you implement a method something like:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;[AcceptVerbs(HttpVerbs.Delete)]      &lt;br /&gt;public ActionResult Delete(int id) {       &lt;br /&gt;&amp;#160; /* delete product here */       &lt;br /&gt;&amp;#160;&amp;#160; return(Index());       &lt;br /&gt;}&amp;#160;&amp;#160; &lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;&lt;a title=&quot;The London Eye and Houses of Parliament by night&quot; href=&quot;http://www.flickr.com/photos/dylanbeattie/3402938268/in/set-72157605523122852/&quot;&gt;&lt;img style=&quot;border-bottom: 0px; border-left: 0px; margin: 0px 0px 20px 20px; display: inline; border-top: 0px; border-right: 0px&quot; title=&quot;The London Eye and Houses of Parliament by night. Very restful.&quot; border=&quot;0&quot; alt=&quot;The London Eye and Houses of Parliament by night. Very restful.&quot; align=&quot;right&quot; src=&quot;http://lh5.ggpht.com/_LV_l8kYLOwo/StPFTj0bNhI/AAAAAAAAAP4/yTV0kbdKdf4/3402938268_1038dbd5f7%5B6%5D.jpg?imgmax=800&quot; width=&quot;282&quot; height=&quot;212&quot; /&gt;&lt;/a&gt;The HTML helper injects a hidden form element called &lt;strong&gt;X-HTTP-Method-Override&lt;/strong&gt; into your POST submission, and then the framework examines that hidden field when deciding whether your request should pass the AcceptVerbs attribute filter on a particular method. &lt;/p&gt;  &lt;p&gt;Now, most MVC routing examples – and the default behaviour you get from the Visual Studio MVC file helpers – will give you a bunch of URLs mapped to different controller methods using a {controller}/{action}/{id} convention – so your application will expose URLs that look like this:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;/products/&lt;strong&gt;view&lt;/strong&gt;/1234 &lt;/li&gt;    &lt;li&gt;/products/&lt;strong&gt;edit&lt;/strong&gt;/1234 &lt;/li&gt;    &lt;li&gt;/products/&lt;strong&gt;delete&lt;/strong&gt;/1234 &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Since web browsers only support GET and POST, we end up having to express our intentions through the URI like this, and so the URI doesn’t really identify a resource, it identifies the &lt;strong&gt;act of doing something to a resource&lt;/strong&gt;. That’s all very well if you subscribe to the &lt;a href=&quot;http://en.wikipedia.org/wiki/Nathan_Lee_Chasing_His_Horse&quot;&gt;Nathan Lee Chasing His Horse&lt;/a&gt; school of nomenclature, but one of the key tenets of REST is that you can apply a &lt;strong&gt;different verb&lt;/strong&gt; to the &lt;strong&gt;same resource identifier – &lt;/strong&gt;i.e. the same URI – in order to perform different operations. Assuming we’re using the product ID as part of our resource identification system, then:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;strong&gt;PUT /products/1234 – &lt;/strong&gt;will create a new product with ID 1234 &lt;/li&gt;    &lt;li&gt;&lt;strong&gt;POST /products/1234 &lt;/strong&gt;– will update product #1234&lt;/li&gt;    &lt;li&gt;&lt;strong&gt;GET /products/1234 &lt;/strong&gt;– will retrieve a representation of product #1234&lt;/li&gt;    &lt;li&gt;&lt;strong&gt;DELETE /products/1234 – &lt;/strong&gt;will remove product #1234 &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;One approach would be to map all these URIs to the same controller method – say &lt;strong&gt;ProductController.DoTheRightThing(int id)&lt;/strong&gt; – and then inspect the &lt;strong&gt;Request.HttpMethod&lt;/strong&gt; inside this method to see whether we’re &lt;strong&gt;PUT&lt;/strong&gt;ing, &lt;strong&gt;POST&lt;/strong&gt;ing, or what. &lt;/p&gt;  &lt;p&gt;This won’t work, though, because Request.HttpMethod hasn’t been through the ‘unsupported verb translator’ that’s included with MVC 2; the Request.HttpMethod will still be “POST” even if the request is a pseudo-DELETE created via the HttpMethodOverride technique shown above. &lt;/p&gt;  &lt;p&gt;Now, MVC v1 supports something called &lt;a href=&quot;http://www.asp.net/learn/mvc/tutorial-24-cs.aspx&quot;&gt;route constraints&lt;/a&gt;. Stephen Walther has a &lt;a href=&quot;http://stephenwalther.com/blog/archive/2008/08/07/asp-net-mvc-tip-30-create-custom-route-constraints.aspx&quot;&gt;great post about these&lt;/a&gt;; basically they’ll let you say that a certain route only applies to GET requests or POST requests. &lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;routes.MapRoute(      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &amp;quot;Product&amp;quot;,&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &amp;quot;Product/Insert&amp;quot;,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; new { controller = &amp;quot;Product&amp;quot;, action = &amp;quot;Insert&amp;quot;},       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; new { httpMethod = new HttpMethodConstraint(&amp;quot;POST&amp;quot;) }       &lt;br /&gt;); &lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;That last line there? That’s the key – you can map a request for &lt;strong&gt;/Product/1234 &lt;/strong&gt;to your controller’s Details() method if the request is a GET request, and map the same URL - &lt;strong&gt;/Product/1234&lt;/strong&gt; – to your controller’s Update() method if the request is a POST request. Very nice, and very RESTful.&lt;/p&gt;  &lt;p&gt;But – yes, you guessed it; it doesn’t work with PUT and DELETE, because it’s still inspecting the untranslated Request.HttpMethod, which will always be GET or POST with today’s browsers.&lt;/p&gt;  &lt;p&gt;However, thanks to the ASP.NET MVC’s rich extensibility, it’s actually very simple to add the support we need alongside the features built in to preview 2. (So simple that this started out as a post complaining that MVC2 couldn’t do it, until I realized I could probably implement what was missing in less time than it would take to describe the problem)&lt;/p&gt;  &lt;p&gt;You’ll need to brew yourself up one of these:&lt;/p&gt;  &lt;blockquote&gt;   &lt;pre&gt;/// &lt;summary&gt;Allows you to define which HTTP verbs are permitted when determining 
/// whether an HTTP request matches a route. This implementation supports both 
/// native HTTP verbs and the X-HTTP-Method-Override hidden element
/// submitted as part of an HTTP POST&lt;/summary&gt;
public class HttpVerbConstraint : IRouteConstraint {

  private HttpVerbs verbs;

  public HttpVerbConstraint(HttpVerbs routeVerbs) {
    this.verbs = routeVerbs;
  }

  public bool Match(&lt;br /&gt;    HttpContextBase httpContext, &lt;br /&gt;    Route route, string parameterName, 
    RouteValueDictionary values, &lt;br /&gt;    RouteDirection routeDirection&lt;br /&gt;  ) {
    switch (httpContext.Request.HttpMethod) {
      case &amp;quot;DELETE&amp;quot;:
        return ((verbs &amp;amp; HttpVerbs.Delete) == HttpVerbs.Delete);
      case &amp;quot;PUT&amp;quot;:
        return ((verbs &amp;amp; HttpVerbs.Put) == HttpVerbs.Put);
      case &amp;quot;GET&amp;quot;:
        return ((verbs &amp;amp; HttpVerbs.Get) == HttpVerbs.Get);
      case &amp;quot;HEAD&amp;quot;:
        return ((verbs &amp;amp; HttpVerbs.Head) == HttpVerbs.Head);
      case &amp;quot;POST&amp;quot;:

        // First, check whether it&apos;s a real post.
        if ((verbs &amp;amp; HttpVerbs.Post) == HttpVerbs.Post) return (true);

        // If not, check for special magic HttpMethodOverride hidden fields.
        switch (httpContext.Request.Form[&amp;quot;X-HTTP-Method-Override&amp;quot;]) {
          case &amp;quot;DELETE&amp;quot;:
            return ((verbs &amp;amp; HttpVerbs.Delete) == HttpVerbs.Delete);
          case &amp;quot;PUT&amp;quot;:
            return ((verbs &amp;amp; HttpVerbs.Put) == HttpVerbs.Put);
        }
        break;
    }
    return (false);
  }
}&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;This just implements the IRouteConstraint interface (part of MVC) with a Match() method that will check for the hidden form field when deciding whether to treat a POST request as a pseudo-DELETE or pseudo-PUT. Once you’ve added this to your project, you can set up your MVC routes like so:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;pre&gt;routes.MapRoute(
  // Route name - anything you like but must be unique.
  &amp;quot;DeleteProduct&amp;quot;,				 
  
  // The URL pattern to match
  &amp;quot;Products/{guid}&amp;quot;, 
  
  // The controller and method that should handle requests matching this route 
  new { controller = &amp;quot;Products&amp;quot;, action = &amp;quot;Delete&amp;quot;, id = &amp;quot;&amp;quot; },   
  
  // The HTTP verbs required for a request to match this route.
  new { httpVerbs = new HttpVerbConstraint(HttpVerbs.Delete) }
);

routes.MapRoute(
  &amp;quot;CreateProduct&amp;quot;,
  &amp;quot;Products/{id}&amp;quot;,
  new { controller = &amp;quot;Products&amp;quot;, action = &amp;quot;Create&amp;quot;, id = &amp;quot;&amp;quot; },
  new { httpVerbs = new HttpVerbConstraint(HttpVerbs.Put) }
);

routes.MapRoute(
  &amp;quot;DisplayProduct&amp;quot;,
  &amp;quot;Products/{id}&amp;quot;,
  new { controller = &amp;quot;Products&amp;quot;, action = &amp;quot;Details&amp;quot;, id = &amp;quot;&amp;quot; },
  new { httpVerbs = new HttpVerbConstraint(HttpVerbs.Get) }
);&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;and finally, just implement your controller methods something along these lines:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;public class ProductsController {
    &lt;br /&gt;&amp;#160; public ViewResult Details(int id) { /* implementation */ }

    &lt;br /&gt;&amp;#160; public ViewResult Create(int id) { /* implementation */ }

    &lt;br /&gt;&amp;#160; public ViewResult Delete(int id) { /* implementation */ }

    &lt;br /&gt;}&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You don’t need the AcceptVerbs attribute at all. I think you’re better off mapping each resource/verb combination to sensibly-named method on your controller, and leaving it at that. Let proper REST clients send requests using whichever verb they like; let normal browsers submit POSTs with hidden X-HTTP-Method-Override fields, trust the routing engine and route constraints to sort that lot out before it hits your controller code, and you’ll find that you can completely decouple your resource identification strategy from your controller/action naming conventions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BLATANT PLUG:&lt;/strong&gt; If you’re into this kind of thing, you should come along to &lt;a href=&quot;http://skillsmatter.com/event/open-source-dot-net/the-future-of-web-development-html-5-and-aspdot-net-httpaspdot-net-mvc-2&quot;&gt;Skills Matter in London on November 2nd&lt;/a&gt;, where I’ll be talking about the future of web development - HTML 5, MVC 2, REST, jQuery, semantic markup, web standards, and… well, you’ll have to come along and find out. If you’re interested, &lt;a href=&quot;http://skillsmatter.com/event/open-source-dot-net/the-future-of-web-development-html-5-and-aspdot-net-httpaspdot-net-mvc-2&quot;&gt;register here&lt;/a&gt; and see you on the 2nd.)&lt;/p&gt;  </description>
          <pubDate>2009-10-13T00:09:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/10/13/restful-routing-in-aspnet-mvc-2-preview.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/10/13/restful-routing-in-aspnet-mvc-2-preview.html</guid>
        </item>
      
    
      
        <item>
          <title>Coordinating Web Development with IIS7, DNS and the Web Deployment Tool</title>
          <description>&lt;blockquote&gt;   &lt;p&gt;DISCLAIMER: There’s some stuff in here which could cause all sorts of chaos if it doesn’t sit happily alongside your individual setup – in particular, hacking your internal DNS records is a really bad idea unless you know what’s already in there, and you understand how DNS resolution works within your organisation. Be careful, and if you’re not responsible for your DNS setup, probably best to discuss this with whoever &lt;em&gt;is&lt;/em&gt; responsible for it first.&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;I’ve been setting up a continuous integration system for our main software products. We host 20+ web sites and applications across four different domain names, ranging from ancient legacy ASP applications based on VBScript and recordsets, to ASP.NET MVC apps built with TDD, Windsor, NHibernate and “alt-net” stack.&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh6.ggpht.com/_LV_l8kYLOwo/StJUk5K0laI/AAAAAAAAAPw/gzGr1vnpeT4/s1600-h/IMG_6835%5B8%5D.jpg&quot;&gt;&lt;img style=&quot;border-bottom: 0px; border-left: 0px; margin: 0px 0px 20px 20px; display: inline; border-top: 0px; border-right: 0px&quot; title=&quot;The City of London skyline, from the South Bank at low tide.&quot; border=&quot;0&quot; alt=&quot;The City of London skyline, from the South Bank at low tide.&quot; align=&quot;right&quot; src=&quot;http://lh4.ggpht.com/_LV_l8kYLOwo/StJUlRaYqvI/AAAAAAAAAP0/r7OOM-PIKJA/IMG_6835_thumb%5B6%5D.jpg?imgmax=800&quot; width=&quot;182&quot; height=&quot;242&quot; /&gt;&lt;/a&gt;Here’s a couple of things we’ve come up with that make the whole process run a little more smoothly. Let’s imagine our developers are Alice, Bob and myself, and we’re running a three-stage deployment process. Here’s how it works.&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Alice implements a new feature, working on code on her local workstation. She has a full local copy of every site, under Subversion control, which she can view and test at &lt;a href=&quot;http://www.website.com.local&quot;&gt;www.website.com.local&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;Once the feature’s done, Alice commits her code. TeamCity – the continuous integration server – will pull the change from Subversion, build it, and deploy the results to &lt;a href=&quot;http://www.website.com.build&quot;&gt;www.website.com.build&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;We run tests – both automated and manual – against this build site. If everything looks OK, we send this .build URL to the stakeholders and testers to get their feedback on the new feature. &lt;/li&gt;    &lt;li&gt;Once the tests are green and the stakeholders are happy, the feature is ready for launch. We’ll now use msdeploy to push the &lt;strong&gt;entire modified website&lt;/strong&gt; onto the test server - &lt;a href=&quot;http://www.website.com.test&quot;&gt;www.website.com.test&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;We run integration tests that hit&amp;#160; &lt;a href=&quot;http://www.website.com.test&quot;&gt;www.website.com.test&lt;/a&gt; – and also &lt;a href=&quot;http://www.website.com.test/some_app&quot;&gt;www.website.com.test/some_app&lt;/a&gt;, &lt;a href=&quot;http://www.website2.co.uk.test&quot;&gt;www.website2.co.uk.test&lt;/a&gt;, &lt;a href=&quot;http://www.another-site.com.test&quot;&gt;www.another-site.com.test&lt;/a&gt; – basically, they verify that not only do the individual apps and sites all work, but that they’re co-existing happily on the same server. &lt;/li&gt;    &lt;li&gt;Finally, we have a couple of msdeploy tasks set up in TeamCity, that will deploy the &lt;strong&gt;entire server configuration&lt;/strong&gt; from the test server to the public-facing servers. &lt;/li&gt; &lt;/ol&gt;  &lt;h3&gt;Setting up Developer Workstations&lt;/h3&gt;  &lt;p&gt;Most of our developer machines are running Windows 7, which includes IIS7, which supports multiple websites (this used to be a huge limitation of XP Professional, which would only run a single local website). We have a standard setup across workstations, build, test and live servers – they all have a 100Gb+ D: drive dedicated for web hosting, which means we can use msdeploy.exe to clone the test server onto new workstations (or just to reset your own configuration if things get messed up), and to handle the deployment from build to test to live.&lt;/p&gt;  &lt;p&gt;Note that this &lt;strong&gt;doesn’t&lt;/strong&gt; mean we’re hard-coding paths to D:\ drives – the apps and sites will happily run from any location on the server, since they use virtual directories and Server.MapPath() to handle any filesystem access. However, it does make life much easier to set up configuration once, and then clone this config across the various systems.&lt;/p&gt;  &lt;p&gt;Finally, note that our workstations are 64-bit and the servers are 32-bit, which works fine with one caveat – you can sync and deploy configuration &lt;strong&gt;from&lt;/strong&gt; the servers &lt;strong&gt;to &lt;/strong&gt;the workstations, but not vice versa. In practise, this is actually quite useful – anything being pushed onto the servers should be getting there via Subversion and TeamCity anyway &lt;/p&gt;  &lt;h3&gt;Using DNS to manage .local, .build and .test zones&lt;/h3&gt;  &lt;p&gt;Unless you want to maintain a &lt;em&gt;lot &lt;/em&gt;of /etc/hosts files, you’ll need your own local DNS servers for this part – but if your organisation is using Active Directory, you’re sorted because your domain controllers are all local DNS servers anyway. The trick here is to create “fake” locally-accessible DNS zones containing wildcard records. We have a zone called &lt;strong&gt;local&lt;/strong&gt;, which contains a single DNS CNAME record that points&amp;#160; &lt;strong&gt;*&lt;/strong&gt; at 127.0.0.1. This means that anything.you.like.local will resolve to 127.0.0.1 – so developers can access their local copies of every site by using something like &lt;a href=&quot;http://www.sitename.com.local&quot;&gt;www.sitename.com.local&lt;/a&gt;. &lt;/p&gt;  &lt;p&gt;There’s a DNS zone called &lt;strong&gt;build&lt;/strong&gt;, which contains an ALIAS record pointing * at build-server.mydomain.com, and another one called test, which has an ALIAS record pointing * at test-server.mydomain.com. We’ve also set up *.dylan as an alias for my workstation, and *.alice as an alias for Alice’s PC, and *.bob as an alias for Bob’s PC, and so on. &lt;/p&gt;  &lt;p&gt;This seems simple but it’ll actually give you some very neat capabilities:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Every developer can see their own projects at &lt;a href=&quot;http://www.website.com.local&quot;&gt;www.website.com.local&lt;/a&gt; and &lt;a href=&quot;http://www.othersite.co.uk.local&quot;&gt;www.othersite.co.uk.local&lt;/a&gt;&amp;#160; &lt;/li&gt;    &lt;li&gt;Everyone in the organization can see a particular developer’s work in progress, by going to &lt;a href=&quot;http://www.website.com.alice&quot;&gt;www.website.com.alice&lt;/a&gt; or &lt;a href=&quot;http://www.othersite.co.uk.bob&quot;&gt;www.othersite.co.uk.bob&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;Everyone in the organization can see the build server – &lt;a href=&quot;http://www.website.com.build&quot;&gt;www.website.com.build&lt;/a&gt; – and the test server, &lt;a href=&quot;http://www.website.com.test&quot;&gt;www.website.com.test&lt;/a&gt; &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Of course, this doesn’t work unless there’s a web server on the other end that’s listening for those requests, so our common IIS configuration has the following bindings set up for every site:&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh5.ggpht.com/_LV_l8kYLOwo/Ss7-GPdCStI/AAAAAAAAAPo/oKpIFCgrGD4/s1600-h/image3.png&quot;&gt;&lt;img style=&quot;border-right-width: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh4.ggpht.com/_LV_l8kYLOwo/Ss7-Gku_gHI/AAAAAAAAAPs/MFVElgEPATA/image_thumb1.png?imgmax=800&quot; width=&quot;607&quot; height=&quot;306&quot; /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;This looks like a lot of work to maintain, but because developer workstations are set up by using msdeploy to clone the test server’s configuration, these mappings only need to be created once, manually, on the test server, and they’ll be transferred across along with everything else.&lt;/p&gt;  &lt;p&gt;I’d be interested to hear from anyone who’s using a similar setup – or who’s found an alternative approach to the same problem. Leave a comment here or drop me a line – or better still, blog your own set-up and send me a link, and I’ll add it here.&lt;/p&gt;  </description>
          <pubDate>2009-10-11T21:59:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/10/11/coordinating-web-development-with-iis7.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/10/11/coordinating-web-development-with-iis7.html</guid>
        </item>
      
    
      
        <item>
          <title>A Neat Trick using Switch in JavaScript</title>
          <description>You ever see code blocks that look like this?&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;if (someCondition) {
    doSomeThing();
} else if (someOtherCondition) {
    doSomeOtherThing();
} else if (someThirdCondition) {
    doSomeThirdThing();
} else {
    doUsualThing();
}&lt;/pre&gt;&lt;br /&gt;
Turns out in Javascript - and, I suspect, in other dynamically-typed languages that support switch statements - that you can do this:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;switch(true) {
    case someCondition:
        doSomeThing();
        break;
    case someOtherCondition:
        doSomeOtherThing();
        break;
    case someThirdCondition:
        doSomeThirdThing();
        break;
    default:
        doUsualThing();
        break;
    }&lt;/pre&gt;&lt;br /&gt;
Of course, by omitting the break keyword you could wreak all sorts of havoc – but I can think of a couple of scenarios where you want to apply one or more enhancements (e.g. adding one or more CSS classes to a piece of mark-up) and this would fit very nicely.</description>
          <pubDate>2009-10-11T14:46:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/10/11/neat-trick-using-switch-in-javascript.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/10/11/neat-trick-using-switch-in-javascript.html</guid>
        </item>
      
    
      
        <item>
          <title>I love this photograph.</title>
          <description>&lt;div style=&quot;float: right; margin-left: 10px; margin-bottom: 10px;&quot;&gt;&lt;a href=&quot;http://www.flickr.com/photos/dylanbeattie/3955804069/&quot; title=&quot;photo sharing&quot;&gt;&lt;img src=&quot;http://farm3.static.flickr.com/2574/3955804069_776d14d9ef_m.jpg&quot; alt=&quot;&quot; style=&quot;border: solid 2px #000000;&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;span style=&quot;font-size: 0.9em; margin-top: 0px;&quot;&gt;&lt;a href=&quot;http://www.flickr.com/photos/dylanbeattie/3955804069/&quot;&gt;Robin Red-breast Looking Quizzical&lt;/a&gt;&lt;br /&gt;Originally uploaded by &lt;a href=&quot;http://www.flickr.com/people/dylanbeattie/&quot;&gt;Dylan Beattie&lt;/a&gt;&lt;/span&gt;&lt;/div&gt;We have an olive tree growing in a huge pot in our garden, which ended up horribly waterlogged and looking rather unhappy, so this afternoon I set about the back-breaking task of digging it out, emptying the (disgusting) waterlogged soil from the bottom of the pot, and generally sorting the whole thing out.&lt;br /&gt;&lt;br /&gt;Turns out you get quite a lot of earthworms in 500 litres of waterlogged soil - and whether he was attracted by the worms, or just curious, this little robin showed up in the garden. I ran inside to grab a camera, hoping he&apos;d still be there when I got back - and he was. He stayed around for most of the afternoon, perching on the tree, the washing line, the shovel, chasing spiders around - at one point he was sitting literally two feet away from me, and actually started singing.  It was absolutely mesmerising.&lt;br /&gt;&lt;br /&gt;The photo is complete luck; he was hopping around so much I didn&apos;t have time to frame or set up a shot, so I just snapped him whenever he stopped for a moment. I was thrilled that this one came out so well.&lt;br clear=&quot;all&quot; /&gt;</description>
          <pubDate>2009-09-26T21:31:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/09/26/i-love-this-photograph.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/09/26/i-love-this-photograph.html</guid>
        </item>
      
    
      
        <item>
          <title>A Better Example of Command-Query Separation</title>
          <description>&lt;p&gt;&lt;a href=&quot;http://twitter.com/Daneel3001&quot;&gt;Daneel3001&lt;/a&gt; just &lt;a href=&quot;http://twitter.com/Daneel3001/status/4055024776&quot;&gt;posted&lt;/a&gt; this little quartet on Twitter:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;@udidahan decisions. And I guess the command will either succeed, or fail by raising a compensating action (booking couldn&apos;t succeed).&lt;/p&gt;    &lt;p&gt;@udidahan Yet, there are some cases where the command will not enable the domain, cinema room here, won&apos;t have the freedom to make much.&lt;/p&gt;    &lt;p&gt;@udidahan You were rightly stating that grid like views of data as having negative effect on understanding intent of user actions.&lt;/p&gt;    &lt;p&gt;@udidahan Maybe instead of using the hotel booking analogy for explaining CQS you could have used the cinema seat booking. &lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;- and that reminded me of one of my all-time frustrations with the internet – booking theatre tickets – which, coincidentally, dovetails very neatly with something Udi Dahan was describing last night.&lt;/p&gt;  &lt;p&gt;I work in Leicester Square. I walk past a dozen theatres every day, so if I want to see a show, it really doesn’t matter &lt;em&gt;when&lt;/em&gt; I go and see it. I can go any time – and yet, every single theatre and ticket website starts the search process by saying “which day do you want to go?” – and then this happens:&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Me&lt;/strong&gt;: Friday.     &lt;br /&gt;&lt;strong&gt;Computer&lt;/strong&gt;: Sorry, sold out.     &lt;br /&gt;&lt;strong&gt;Me&lt;/strong&gt;: OK, Wednesday.     &lt;br /&gt;&lt;strong&gt;Computer&lt;/strong&gt;: Sorry, sold out.     &lt;br /&gt;&lt;strong&gt;Me: &lt;/strong&gt;OK, Tuesday.     &lt;br /&gt;&lt;strong&gt;Computer&lt;/strong&gt;: Great! Tuesday! We can do that! Now, choose a seating section:     &lt;br /&gt;&lt;strong&gt;Me&lt;/strong&gt;: Dress Circle, please.     &lt;br /&gt;&lt;strong&gt;Computer&lt;/strong&gt;: Sorry – sold out.&lt;/p&gt;  &lt;p&gt;What I &lt;em&gt;really&lt;/em&gt; want is a to be able to say “I want to see Mamma Mia. I want to sit anywhere in the front ten rows, anytime between now and Christmas, and I can’t make Sep 23rd or any Saturday” – and, as Udi put it, &lt;em&gt;let the computer do the busy-work&lt;/em&gt;. I &lt;strong&gt;know&lt;/strong&gt; that the data is in there. I &lt;strong&gt;know&lt;/strong&gt; that there are algorithms capable of fulfilling that request. Why the hell am I sitting here brute-forcing a solution and whacking the back button like I’m playing Track &amp;amp; Field 2 all over again?&lt;/p&gt;  &lt;p&gt;Most current ticket-booking websites will reduce your request to “seats H24, H25, H26 and H27 for Chicago at 19:00 on Saturday 25th” – but that request is just too detailed, and far too prone to failure, and doesn’t reflect AT ALL what I’m actually trying to achieve. Maybe I’m only in town for Saturday night and don’t care what show I see. Maybe I desperately want to see Chicago but I’m prepared to go on any night. Maybe I’d be happy with two pairs of tickets instead of four together. I’d love a website that actually asked me what I wanted &lt;strong&gt;on my terms&lt;/strong&gt; instead of presenting me with a bunch of data and expecting me to do the grunt work.&lt;/p&gt;  &lt;p&gt;Command-query separation would appear to be the first step towards this – but it seems that what’s &lt;em&gt;really&lt;/em&gt; important here is that your command model reflects the &lt;strong&gt;intention of your users&lt;/strong&gt;, rather than the shape of your data. &lt;/p&gt;  </description>
          <pubDate>2009-09-17T15:23:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/09/17/better-example-of-command-query.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/09/17/better-example-of-command-query.html</guid>
        </item>
      
    
      
        <item>
          <title>AltNet Beers – Command/Query Separation with Udi Dahan</title>
          <description>&lt;p&gt;Tonight was the twelfth of &lt;a href=&quot;http://serialseb.blogspot.com/&quot;&gt;SerialSeb&lt;/a&gt;’s &lt;a href=&quot;http://www.altnetgroup.com/&quot;&gt;alternative network&lt;/a&gt; beers events, and this evening we were lucky enough to have &lt;a href=&quot;http://www.udidahan.com/&quot;&gt;Udi Dahan&lt;/a&gt; joining us. Clearly a lot of people in the room were very interested in hearing what Udi had to say because for the first time in history (I think?) the usual altnet musical chairs didn’t happen – the speakers hardly changed for the entire hour. That said, I feel like tonight raised a whole lot of fascinating questions for me, and I’m thoroughly grateful to Udi for taking the time to share his insight, to Seb for organizing the whole thing, and to Tequila\ and Thoughtworks for beer and pizza – thanks!&lt;/p&gt;  &lt;p&gt;Command/query separation is a relatively new concept to me, and I’m sure I’ve got the wrong end of the stick here, but I’m going to share my reflections on this evening anyway so I can look back on this in a year or so and laugh at myself. Feel free to laugh now if you’ve already got your head around all this.&lt;/p&gt;  &lt;p&gt;Anyway. The underlying principle of CQS seems to be that &lt;strong&gt;reading&lt;/strong&gt; data and &lt;strong&gt;changing&lt;/strong&gt; data are actually fundamentally different operations in any business system, and that trying to use the same architectural style for both of these operations can lead to all sorts of chaos.&lt;/p&gt;  &lt;p&gt;It also seems pretty obvious that CQS is a topic with a lot of potential “false peaks”. Maybe you’ve refactored your Customer object to use a ChangeName() method instead of exposing property setters for Forenames and Surname. Maybe you’ve exposed a bunch of denormalized data based on optimised stored procedures for your common query scenarios, and you’re still using a rich domain model to do your inserts and updates. In each case, you probably &lt;em&gt;think&lt;/em&gt; you’re doing command/query separation – but there’s more to it than that. Until tonight, I thought CQS just meant having some special methods on your data access code for returning big lists of stuff in a hurry. Now, I’m pretty sure I don’t really know what it is at all.&lt;/p&gt;  &lt;p&gt;A couple of great highlights from Udi’s contribution to the discussion tonight:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Users are always working with stale data. The information on their screen could potentially be stale by the time they actually see it. In any multi-user system, people are always making decisions and requests based on stale data. (“This is obvious, it’s physics – you can’t fight it. Well, you can, but you’ll lose”)&lt;/li&gt;    &lt;li&gt;Separating queries from commands allows the commands to model the user’s &lt;strong&gt;intention&lt;/strong&gt; more clearly – which in turn allows the software to deal gracefully with conflicts and failures (“Sorry, room 133 is taken – would you like room 155?”), where a more granular system might just throw an exception because the data is no longer valid. (“Booking failed – not in the database!”)&lt;/li&gt;    &lt;li&gt;The reason we create domain models is really just that we need &lt;em&gt;somewhere&lt;/em&gt; to store all our complex business rules, but it’s easy for elements of business rules to leak into the controllers or presentation layers when we’re manipulating domain objects directly.&lt;/li&gt;    &lt;li&gt;The ideal CQS approach is that &lt;em&gt;every&lt;/em&gt; business operation involves exactly three things: &lt;/li&gt;    &lt;ol&gt;     &lt;li&gt;Find a domain entity&lt;/li&gt;      &lt;li&gt;Execute &lt;strong&gt;one method&lt;/strong&gt; on that entity&lt;/li&gt;      &lt;li&gt;Commit the transaction.&lt;/li&gt;   &lt;/ol&gt;    &lt;li&gt;With this approach, it’s impossible for any business logic to ‘leak’ into the presentation or controller layers – because they’re not making any decisions. Every business operation, complete with all the validation and processing and rules associated with that operation, has to be exposed as a &lt;strong&gt;single entry point&lt;/strong&gt; to the domain model.&lt;/li&gt;    &lt;li&gt;The domain entity that exposes the method will probably behave as an aggregate root for the purpose of that operation – but different entities will act as aggregate roots for different operations. Again, this was a bit of an eye-opener for me; talking about DDD gave me the impression that an aggregate root was a fixture of your business model, not something you could chop and change based on what makes sense for a particular operation.&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;Finally, an analogy of my own that came to me on the way home, that might help, or might be horribly naive and misguided, but which I rather like and which I’ll share here in the hope of provoking some conversation. ‘Traditional’ domain modelling is like home baking; your data store is a supermarket, where the various products on offer are your objects. They’re all there, laid out for you to search through and count and process. To do anything complicated – like making a soufflé – you need to acquire all the various objects required for that operation, then manipulate and combine them in all sorts of complicated ways to achieve the result you’re after. If anything goes wrong – you forget the butter, or you over-cook the eggs – boom! No soufflé for you. Transaction aborted.&lt;/p&gt;  &lt;p&gt;CQS seems far more like eating at a fine restaurant. You don’t choose your meal from an array of component products; instead, you get given a menu – a read-only representation of the domain that’s optimised for rapid retrieval. Based on the information on the menu, you then execute a command – you tell the waiter what you’d like to eat – but the structure of that command expresses your &lt;em&gt;intention&lt;/em&gt; far more explicitly than the complex series of interactions involved in doing it yourself. If the data that informed your decision is stale - say they’ve just run out of haddock -the command carries enough context that the waiter can offer you the sea bream instead, or perhaps the mackerel, and the entire dining transaction isn’t abandoned.&lt;/p&gt;  &lt;p&gt;I guess the question is, do you want your users to feel like they’re making a soufflé, or dining in a Michelin-starred restaurant?&lt;/p&gt;  </description>
          <pubDate>2009-09-17T00:47:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/09/17/altnet-beers-commandquery-separation.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/09/17/altnet-beers-commandquery-separation.html</guid>
        </item>
      
    
      
        <item>
          <title>Determining FluentNH Schema Mappings based on Entity Namespaces</title>
          <description>&lt;p align=&quot;justify&quot;&gt;&lt;a title=&quot;Sunrise over Sardinia (otherwise this post would be all code and no pictures, and where&amp;#39;s the fun in that?)&quot; href=&quot;http://www.flickr.com/photos/dylanbeattie/3908231290/in/set-72157622332767804/&quot;&gt;&lt;img style=&quot;border-bottom: 0px; border-left: 0px; margin: 0px 0px 20px 20px; display: inline; border-top: 0px; border-right: 0px&quot; title=&quot;&quot; border=&quot;0&quot; alt=&quot;Sardinia Sunrise by you.&quot; align=&quot;right&quot; src=&quot;http://farm3.static.flickr.com/2445/3908231290_cf2e85b876.jpg&quot; width=&quot;300&quot; height=&quot;225&quot; /&gt;&lt;/a&gt;I’m setting up some Fluent NHibernate mappings for a rewrite of some of our legacy code, and one of the issues I’ve hit is that we make extensive use of cross-database views and joins – the data supporting our app is split across three separate SQL Server databases (which, thankfully, are all hosted by the same SQL Server instance).&lt;/p&gt;  &lt;p&gt;Turns out this is pretty easy to do – Mike Hadlow has a &lt;a href=&quot;http://mikehadlow.blogspot.com/2008/10/mapping-entities-to-multiple-databases.html&quot;&gt;great post here&lt;/a&gt; which covers the fundamentals.&lt;/p&gt;  &lt;p align=&quot;justify&quot;&gt;I’ve extended this principal a bit, using the Conventions facility provided by Fluent NHibernate, so that you can determine the SQL database for each entity based on your entities’ namespaces, so I have a model that looks (very) roughly like this. Let&apos;s imagine that my core web data is in a database called WebStuff, my accounts system is in CashDesk and my CRM database is in DylanCrm. Each mapped entity is declared in a sub-namespace of my common Dylan.Entities namespace, with these sub-namespaces named to reflect the database they’re mapping:&lt;/p&gt;  &lt;blockquote&gt;   &lt;pre&gt;
namespace Dylan.Entities.WebStuff {
	public class WebUser {
		public int Id { get; set; }
		public Customer AssociatedCustomer { get; set; }
	}
}

namespace Dylan.Entities.CashDesk {
	public class Invoice {
		public int Id { get; set; }
		public Customer Customer { get; set; }
	}
}

namespace Dylan.Entities.DylanCrm {
	public class Customer {
		public int Id { get; set; }
		public IList&lt;invoice&gt; Invoices { get; set; }
	}
}&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;NHibernate will quite happily retrieve and update data across multiple databases, by prepending the schema name to the table names - so you end up running SQL statements like &lt;code&gt;SELECT ... FROM CashDesk.dbo.Invoice WHERE ...&lt;/code&gt;. If you&apos;re only mapping a handful of tables, it&apos;s easy to specify the schema for each table/object as in Mike&apos;s example - but you can also use &lt;code&gt;FluentNHibernate.Conventions&lt;/code&gt; to achieve the same thing.&lt;/p&gt;

&lt;p&gt;First off, you&apos;ll need to add a new class which implements &lt;code&gt;IClassConvention&lt;/code&gt; and modifies the &lt;code&gt;Schema&lt;/code&gt; property of each class mapping:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;pre&gt;public class SchemaPrefixConvention : IClassConvention {

	private string ExtractDatabaseName(string entityNamespace) {
		return (entityNamespace.Substring(entityNamespace.LastIndexOf(&apos;.&apos;) + 1));
	}

	public void Apply(IClassInstance instance) {
		instance.Schema(ExtractDatabaseName(instance.EntityType.Namespace) + &amp;quot;.dbo&amp;quot;);
	}
}&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once you&apos;ve done that, you just need to reference this convention when you set up your mappings; if you&apos;re using the auto-mapping facility, it looks like this:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;pre&gt;mappings.AutoMappings.Add(
	AutoMap
		.AssemblyOf&amp;lt;Invoice&amp;gt;()
		.Where(t =&amp;gt; t.Namespace == &amp;quot;Dylan.Entities.CashDesk&amp;quot;)
		&lt;strong&gt;.Conventions.Add&amp;lt;SchemaPrefixConvention&amp;gt;()&lt;/strong&gt;
	);

mappings.AutoMappings.Add(
	AutoMap
		.AssemblyOf&amp;lt;Customer&amp;gt;()
		.Where(t =&amp;gt; t.Namespace == &amp;quot;Dylan.Entities.DylanCrm&amp;quot;)
		&lt;strong&gt;.Conventions.Add&amp;lt;SchemaPrefixConvention&amp;gt;()&lt;/strong&gt;
	);

mappings.AutoMappings.Add(
	AutoMap
		.AssemblyOf&amp;lt;WebUser&amp;gt;()
		.Where(t =&amp;gt; t.Namespace == &amp;quot;Dylan.Entities.WebStuff&amp;quot;)
		&lt;strong&gt;.Conventions.Add&amp;lt;SchemaPrefixConvention&amp;gt;()&lt;/strong&gt;
	);&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fluent NH will run your Apply() method to each mapped class in each of these three mappings, which means the resulting configuration will qualify each table name with a schema name derived from the mapped class’ namespace – and once that’s in place, you can query, retrieve, update, join and generally hack your objects about at will, and completely ignore the fact that under the hood they&apos;re actually being persisted across multiple data stores.&lt;/p&gt;

&lt;p&gt;I think that&apos;s quite neat.&lt;/p&gt;  </description>
          <pubDate>2009-09-14T22:40:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/09/14/determining-fluentnh-schema-mappings.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/09/14/determining-fluentnh-schema-mappings.html</guid>
        </item>
      
    
      
        <item>
          <title>The Problem with Stack Overflow</title>
          <description>&lt;p&gt;I love &lt;a href=&quot;http://www.stackoverflow.com/&quot;&gt;Stack Overflow&lt;/a&gt;. I think it’s a fantastic resource, not to mention a beautifully-engineered social community. But sometimes mixing knowledge resources with social networking can be… distracting. Let’s say you’re, I don’t know, trying to choose an open source blog engine to add to a .NET site you’re working on.&lt;/p&gt;  &lt;p&gt;It starts like this:&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh4.ggpht.com/_LV_l8kYLOwo/SoqIGAed4JI/AAAAAAAAAPM/5gKS9SmOixo/s1600-h/image%5B4%5D.png&quot;&gt;&lt;img style=&quot;border-right-width: 0px; margin: 20px auto; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh6.ggpht.com/_LV_l8kYLOwo/SoqIGu2NJWI/AAAAAAAAAPQ/5OqSCvJbEPU/image_thumb%5B2%5D.png?imgmax=800&quot; width=&quot;503&quot; height=&quot;210&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Then this:&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh6.ggpht.com/_LV_l8kYLOwo/SoqIG7cI0EI/AAAAAAAAAPU/uyPWCOOQf_0/s1600-h/image%5B10%5D.png&quot;&gt;&lt;img style=&quot;border-right-width: 0px; margin: 20px auto; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh3.ggpht.com/_LV_l8kYLOwo/SoqIHX1327I/AAAAAAAAAPY/8HD2MaC58F8/image_thumb%5B6%5D.png?imgmax=800&quot; width=&quot;567&quot; height=&quot;80&quot; /&gt;&lt;/a&gt; Ah-ha… *click* - &lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh3.ggpht.com/_LV_l8kYLOwo/SoxxRFCSCRI/AAAAAAAAAPc/MGeC7vntYx4/s1600-h/image%5B21%5D.png&quot;&gt;&lt;img style=&quot;border-right-width: 0px; margin: 0px auto 20px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh6.ggpht.com/_LV_l8kYLOwo/SoxxRSW4lAI/AAAAAAAAAPg/I3i2MHo2rkU/image_thumb%5B11%5D.png?imgmax=800&quot; width=&quot;496&quot; height=&quot;82&quot; /&gt;&lt;/a&gt; Oooh – how exciting! A comment! *click*&lt;/p&gt;  &lt;p&gt;.&lt;/p&gt;  &lt;p&gt;.&lt;/p&gt;  &lt;p&gt;.&lt;/p&gt;  &lt;p&gt;Some time later:&lt;/p&gt;  &lt;p&gt;&lt;a title=&quot;What do you want me to do? LEAVE? Then they&amp;#39;ll keep being wrong!&quot; href=&quot;http://xkcd.com/386/&quot;&gt;&lt;img style=&quot;border-bottom: 0px; border-left: 0px; display: block; float: none; margin-left: auto; border-top: 0px; margin-right: auto; border-right: 0px&quot; title=&quot;What do you want me to do? LEAVE? Then they&amp;#39;ll keep being wrong!&quot; border=&quot;0&quot; alt=&quot;What do you want me to do? LEAVE? Then they&amp;#39;ll keep being wrong!&quot; src=&quot;http://lh4.ggpht.com/_LV_l8kYLOwo/SoxxR_9cmCI/AAAAAAAAAPk/lvvK4r0YFKU/duty_calls%5B1%5D%5B6%5D.png?imgmax=800&quot; width=&quot;300&quot; height=&quot;330&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;&lt;small&gt;Thanks to &lt;a href=&quot;http://xkcd.com/&quot;&gt;xkcd&lt;/a&gt; for the cartoon, and for many, many more I have loved over the years.&lt;/small&gt;&lt;/p&gt;  &lt;p&gt;&lt;small&gt;Inspired by the last comment to my accepted answer for &lt;a href=&quot;http://stackoverflow.com/questions/220020/how-to-handle-checkboxes-in-asp-net-mvc-forms/220073#220073&quot;&gt;this&lt;/a&gt;. Where do I even begin?&lt;/small&gt;&lt;/p&gt;  &lt;p&gt;&lt;small&gt;Also - you have any idea how hard it is to get a screenshot of StackOverflow&apos;s squawk bar? Once you&apos;ve seen it once, it goes away and never comes back. And you can&apos;t spoof it by setting up another user account to post a temporary comment on your own thread, because your second user account can&apos;t comment until it has reputation... I&apos;m actually really quite impressed now at how hard it would be to game the system.&lt;/small&gt;&lt;/p&gt;  </description>
          <pubDate>2009-08-18T10:53:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/08/18/problem-with-stack-overflow.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/08/18/problem-with-stack-overflow.html</guid>
        </item>
      
    
      
        <item>
          <title>Alt.Net UK Conf – Unit Testing WinForms UIs</title>
          <description>&lt;p&gt;A couple of links and the code we hacked together during the session on unit-testing desktop UIs during the Alt.Net UK Conference on Sunday:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;a href=&quot;http://blog.benhall.me.uk/2008/02/project-white-automated-ui-testing.html&quot;&gt;Ben Hall’s Blog Post&lt;/a&gt; on Project White and automated UI testing&lt;/li&gt;    &lt;li&gt;&lt;a href=&quot;http://white.codeplex.com/&quot;&gt;Project White&lt;/a&gt; on Codeplex&lt;/li&gt;    &lt;li&gt;The “Hello World” sample app we put together during the session is &lt;a href=&quot;http://openspacecode.googlecode.com/svn/trunk/src/2009-08-01%20London/ProjectWhite/WhiteDemo.zip&quot;&gt;here&lt;/a&gt; – you’ll need the Project White download as well, and you’ll need to edit the hard-coded EXE path files.&lt;/li&gt; &lt;/ul&gt;  </description>
          <pubDate>2009-08-02T15:14:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/08/02/altnet-uk-conf-unit-testing-winforms.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/08/02/altnet-uk-conf-unit-testing-winforms.html</guid>
        </item>
      
    
      
        <item>
          <title>Tool Time</title>
          <description>&lt;p&gt;So.. having installed Windows 7 beta twice and the release candidate three times, it feels like I’m turning bare Windows boxes into working developer workstations about once a week at the moment, so here’s the low-down of what I put onto a bare Windows install to get things &lt;a title=&quot;KENTUCKY (adv.) Fitting exactly and satisfyingly. The cardboard box that slides neatly into an exact space in a garage, or the last book which exactly fills a bookshelf, is said to fit &amp;#39;real nice and kentucky&amp;#39;.&quot; href=&quot;http://folk.uio.no/alied/TMoL.html#anchorK&quot;&gt;real nice‘n’kentucky&lt;/a&gt;. Partly because it’s nice to share, but more because I’ll need a list to work from when Windows goes gold in October and I end up doing this all over again.&lt;/p&gt;  &lt;h3&gt;Microsoft Office 2007&lt;/h3&gt;  &lt;p&gt;Office 2007, because everyone else in the world uses it, and because Exchange calendaring is actually pretty good. I use completely separate accounts for work and personal mail – my work e-mail is all in our Exchange server at work, which means if need be I can share my work mailbox with my co-workers without sharing any personal stuff. &lt;/p&gt;  &lt;h3&gt;Visual Studio 2008 and SQL Server 2008&lt;/h3&gt;  &lt;p&gt;VS2008 and SQL2008 are kinda obvious – I can’t really imagine building .NET business apps without them. Unless you’re some sort of C# ninja who codes against MySQL and ProgreSQL libraries using vim.exe, in which case send me a picture – I’ll put you on a T-shirt.&lt;/p&gt;  &lt;p&gt;Although I’m not a fan of Resharper, Coderush or any of the other ‘heavyweight’ refactoring add-ins, I do use a couple of little VS2008 utilities. &lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;a href=&quot;http://code.msdn.microsoft.com/PowerCommands&quot;&gt;PowerCommands for Visual Studio 2008&lt;/a&gt; – worth it just to be able to right-click and copy/paste project and assembly references within a solution. &lt;/li&gt;    &lt;li&gt;&lt;a href=&quot;http://code.msdn.microsoft.com/KB958502/Release/ProjectReleases.aspx?ReleaseId=1736&quot;&gt;JScript Editor Support for “-vsdoc.js” IntelliSense documentation files&lt;/a&gt; – to get magic jQuery intellisense in .ASPX views and controls. &lt;/li&gt;    &lt;li&gt;&lt;a href=&quot;http://www.axialis.com/download/iwlite.html&quot;&gt;Axialis IconWorkshop Lite for Visual Studio 2008&lt;/a&gt; – a free cut-down version of Axialis wonderful IconWorkshop icon editor, that’ll install &amp;amp; run absolutely free as long as you’re running a fully-licensed version of VS2008 Professional or Standard. &lt;/li&gt; &lt;/ul&gt;  &lt;h2&gt;Show Me The Money&lt;/h2&gt;  &lt;p&gt;As well as the full Microsoft / MSDN licensing bundle, there’s a couple of high-end commercial apps that I absolutely swear by. They’re not open source – you can’t share, modify, hack or fork them – and when there’s so many great free apps around, paying hundreds of pounds for an application can be a bit of a shock, but they’re powerful, flexible, beautifully-crafted tools, and they are worth every single penny. &lt;/p&gt;  &lt;h3&gt;&lt;a href=&quot;http://www.red-gate.com/products/SQL_Professional_Toolbelt/index.htm&quot;&gt;&lt;/a&gt;&lt;a href=&quot;http://www.red-gate.com/products/SQL_Professional_Toolbelt/index.htm&quot;&gt;&lt;img style=&quot;border-bottom: 0px; border-left: 0px; margin: 0px 10px 0px 0px; display: inline; border-top: 0px; border-right: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; align=&quot;left&quot; src=&quot;http://lh4.ggpht.com/_LV_l8kYLOwo/Smoq9FQJDWI/AAAAAAAAAOc/mJmq--Tv7mE/image%5B62%5D.png?imgmax=800&quot; width=&quot;50&quot; height=&quot;50&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.red-gate.com/products/SQL_Professional_Toolbelt/index.htm&quot;&gt;Red Gate SQL Toolbelt&lt;/a&gt;&amp;#160;&lt;/a&gt;&lt;/h3&gt;  &lt;p&gt;Red Gate’s database tools are fantastic. Awesomely powerful, intuitive, rock-solid, and &lt;em&gt;polished&lt;/em&gt; – if you do anything at all with SQL Server databases, you need these tools. The SQL Toolbelt includes the whole lot for just under a grand (i.e. roughly the same as hiring a decent contract DBA for three days) and once you’ve used them, you’ll never want to build a project without them again. &lt;/p&gt;  &lt;h3&gt;&lt;a href=&quot;http://www.axure.com/&quot;&gt;&lt;img style=&quot;border-bottom: 0px; border-left: 0px; margin: 0px 10px 0px 0px; display: inline; border-top: 0px; border-right: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; align=&quot;left&quot; src=&quot;http://lh4.ggpht.com/_LV_l8kYLOwo/SmovyXfkTUI/AAAAAAAAAOk/_6qxn8ZMhMU/image%5B63%5D.png?imgmax=800&quot; width=&quot;46&quot; height=&quot;50&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.axure.com/&quot;&gt;Axure RP Pro&lt;/a&gt;&lt;/h3&gt;  &lt;p&gt;There are great tools out there for writing code, editing photos, writing documents and creating databases and debugging CSS, but for &lt;strong&gt;designing software&lt;/strong&gt;, Axure RP blows everything else out of the water. It’s expressive, it’s intuitive, and the resulting interactive prototypes show people &lt;em&gt;exactly&lt;/em&gt; what you’re planning to deliver - which is great, because you find out what you’ve got wrong after three hours instead of three weeks. &lt;/p&gt;  &lt;h3&gt;&lt;a href=&quot;http://www.techsmith.com/screen-capture.asp&quot;&gt;&lt;img style=&quot;border-bottom: 0px; border-left: 0px; margin: 0px 10px 0px 0px; display: inline; border-top: 0px; border-right: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; align=&quot;left&quot; src=&quot;http://lh5.ggpht.com/_LV_l8kYLOwo/Smoq-yLrRbI/AAAAAAAAAOo/P0CZIIz2VF4/image%5B64%5D.png?imgmax=800&quot; width=&quot;36&quot; height=&quot;38&quot; /&gt;&lt;/a&gt; &lt;a href=&quot;http://www.techsmith.com/screen-capture.asp&quot;&gt;SnagIt&lt;/a&gt;&lt;/h3&gt;  &lt;p&gt;Capture your screen, annotate it, scribble on it, move things around, snip and cut and paste and shuffle and reorganize – SnagIt is intuitive, powerful, and works extremely well. The latest version even supports basic video capture – and if you need more advanced video capture, &lt;a href=&quot;http://www.techsmith.com/camtasia.asp&quot;&gt;Camtasia Studio&lt;/a&gt; from the same people, &lt;a href=&quot;http://www.techsmith.com/&quot;&gt;TechSmith&lt;/a&gt;,&amp;#160; is well worth a look.&lt;/p&gt;  &lt;h3&gt;&lt;a href=&quot;http://www.scootersoftware.com/index.php&quot;&gt;&lt;img style=&quot;border-bottom: 0px; border-left: 0px; margin: 0px 10px 0px 0px; display: inline; border-top: 0px; border-right: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; align=&quot;left&quot; src=&quot;http://lh5.ggpht.com/_LV_l8kYLOwo/SmorAEFOyKI/AAAAAAAAAOs/YeK7LVcXJFY/image%5B65%5D.png?imgmax=800&quot; width=&quot;52&quot; height=&quot;52&quot; /&gt;&lt;/a&gt; &lt;a href=&quot;http://www.scootersoftware.com/index.php&quot;&gt;Beyond Compare&lt;/a&gt;&lt;/h3&gt;  &lt;p&gt;For when Subversion’s built-in diff doesn’t really cut it. Beyond Compare is the best file-compare utility out there, bar none. One little touch I really like – their 30-day trial license only counts days that you actually run the software, so if you only use it once a week, it’ll be a good six months before the trial expires. More software should do this. &lt;/p&gt;  &lt;h2&gt;The Best Things In Life Are Free&lt;/h2&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;h3&gt;&lt;a href=&quot;http://www.google.co.uk/chrome&quot;&gt;Chrome&lt;/a&gt;, &lt;a href=&quot;http://www.mozilla-europe.org/en/firefox/&quot;&gt;Firefox&lt;/a&gt;, &lt;a href=&quot;http://www.apple.com/safari/&quot;&gt;Safari&lt;/a&gt; and &lt;a href=&quot;http://www.opera.com/&quot;&gt;Opera&lt;/a&gt;&lt;/h3&gt;  &lt;p&gt;&lt;a href=&quot;http://www.google.co.uk/chrome&quot;&gt;&lt;/a&gt;&lt;a href=&quot;http://lh6.ggpht.com/_LV_l8kYLOwo/Smoq7xWw25I/AAAAAAAAANY/_hHFtpwJysQ/s1600-h/image%5B47%5D.png&quot;&gt;&lt;img style=&quot;border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; margin-left: 0px; border-left-width: 0px; margin-right: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; align=&quot;right&quot; src=&quot;http://lh5.ggpht.com/_LV_l8kYLOwo/Smoq8aCFqoI/AAAAAAAAANc/x0jXCEglI0w/image_thumb%5B27%5D.png?imgmax=800&quot; width=&quot;45&quot; height=&quot;41&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://lh4.ggpht.com/_LV_l8kYLOwo/Smoq7Xec48I/AAAAAAAAANQ/BPS02-5WCak/s1600-h/image%5B45%5D.png&quot;&gt;&lt;img style=&quot;border-right-width: 0px; margin: 0px 0px 0px 10px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; align=&quot;right&quot; src=&quot;http://lh4.ggpht.com/_LV_l8kYLOwo/Smoq7uAHTWI/AAAAAAAAANU/ouDvm506IUU/image_thumb%5B25%5D.png?imgmax=800&quot; width=&quot;52&quot; height=&quot;49&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://lh4.ggpht.com/_LV_l8kYLOwo/Smoq58S6njI/AAAAAAAAANA/sRLPI7jTweA/s1600-h/image%5B46%5D.png&quot;&gt;&lt;img style=&quot;border-right-width: 0px; margin: 0px 0px 0px 10px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; align=&quot;right&quot; src=&quot;http://lh5.ggpht.com/_LV_l8kYLOwo/Smoq6ea9mdI/AAAAAAAAANE/yzePwzTjA18/image_thumb%5B26%5D.png?imgmax=800&quot; width=&quot;49&quot; height=&quot;48&quot; /&gt;&lt;/a&gt;&lt;/a&gt;&lt;a href=&quot;http://lh5.ggpht.com/_LV_l8kYLOwo/Smoq6iPoBKI/AAAAAAAAANI/a77qjU6kIYU/s1600-h/image%5B44%5D.png&quot;&gt;&lt;img style=&quot;border-right-width: 0px; margin: 0px 0px 0px 10px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; align=&quot;right&quot; src=&quot;http://lh4.ggpht.com/_LV_l8kYLOwo/Smoq69-W14I/AAAAAAAAANM/viDRieZ69N8/image_thumb%5B24%5D.png?imgmax=800&quot; width=&quot;51&quot; height=&quot;49&quot; /&gt;&lt;/a&gt;Latest versions of these (plus Internet Explorer, of course) are pretty much essential for testing final release web apps. I have a slightly odd set-up – I use IE for “work browsing” (MSDN, FogBugz, our wiki), I use Firefox for GMail, and I use Chrome for pretty much everything else. On Windows 7, you might want to try the &lt;a href=&quot;http://dev.chromium.org/getting-involved/dev-channel&quot;&gt;Chrome Channel Changer&lt;/a&gt; which will pull updates from Google’s weekly alpha builds instead – might be a bit wobbly but generally works a lot better on Windows 7 than the mainstream build.&lt;/p&gt;  &lt;h3&gt;&lt;img style=&quot;border-right-width: 0px; margin: 0px 10px 0px 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px&quot; border=&quot;0&quot; align=&quot;left&quot; src=&quot;https://addons.mozilla.org/en-US/firefox/images/addon_icon/1843/1247742647&quot; /&gt;&lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/1843&quot;&gt;Firebug&lt;/a&gt;&lt;/h3&gt;  &lt;p&gt;The only Firefox add-in I ever use; Firebug gives you full, detailed, debug views into your CSS, HTML, HTTP, and Javascript, all at your fingertips. Building web pages without Firebug is like playing the piano in boxing gloves.&lt;/p&gt;  &lt;h3&gt;&lt;a href=&quot;http://www.7-zip.org/&quot;&gt;7-Zip&lt;/a&gt;&lt;/h3&gt;  &lt;p&gt;The best archiver and archive manager out there, bar none. 32-bit and 64-bit versions; supports every archiving format you can think of, with a decent GUI on the top. Open source. Free. Fast. Bye-bye Winzip. It’s been… emotional. &lt;/p&gt;  &lt;h3&gt;&lt;a href=&quot;http://www.digsby.com/&quot;&gt;&lt;/a&gt;&lt;a href=&quot;http://www.digsby.com/&quot;&gt;&lt;img style=&quot;border-right-width: 0px; margin: 0px 10px 0px 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; align=&quot;left&quot; src=&quot;http://lh3.ggpht.com/_LV_l8kYLOwo/Smoq9zZFfhI/AAAAAAAAAOU/cvM2inTfvAE/image%5B61%5D.png?imgmax=800&quot; width=&quot;48&quot; height=&quot;52&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.digsby.com/&quot;&gt;Digsby&lt;/a&gt;&lt;/a&gt;&lt;/h3&gt;  &lt;p&gt;&amp;#160; Twitter, Facebook, Linkedin, MSN Messenger, AIM, ICQ and Jabber (Google Talk) in one client. Just watch out for all the &lt;a href=&quot;http://www.downloadsquad.com/2008/11/24/new-digsby-installer-loaded-with-bloat-and-adverts/&quot;&gt;heinous bloatware&lt;/a&gt; in their installer – don’t say yes or accept anything except the first screen, and you should be fine.&lt;/p&gt;  &lt;h3&gt;&lt;a href=&quot;http://code.kliu.org/misc/notepad2/&quot;&gt;NotePad2&lt;/a&gt;, &lt;a href=&quot;http://www.textpad.com/&quot;&gt;TextPad&lt;/a&gt; and A. N. Other Editor…&lt;/h3&gt;  &lt;p&gt;&lt;strong&gt;Notepad2&lt;/strong&gt; is fast, free, lightweight, and lovely. You’ll want to download &lt;a href=&quot;http://code.kliu.org/misc/notepad2/&quot;&gt;Kai Liu’s installer that replaces Windows notepad.exe&lt;/a&gt;. &lt;/p&gt;  &lt;p&gt;For a long time I swore by TextPad, which back in the day was a truly impressive editor, but recent releases have felt to me like they’re treading the water a bit; some subtle UI changes between 4.x and 5.x meant it didn’t really feel like the upgrade I’d been waiting for, and since I now do most of my actual coding in Visual Studio, switching text editors isn’t the life-changing transition it would have been a couple of years ago. I’ve been playing around with &lt;a href=&quot;http://notepad-plus.sourceforge.net/uk/site.htm&quot;&gt;Notepad++&lt;/a&gt;, &lt;a href=&quot;http://www.editpadpro.com/&quot;&gt;EditPadPro&lt;/a&gt;, &lt;a href=&quot;http://www.ultraedit.com/products/ultraedit.html&quot;&gt;UltraEdit&lt;/a&gt;, and probably end up installing all of them at some point, but I don’t really have a favourite editor right now.&lt;/p&gt;  &lt;h3&gt;&lt;a href=&quot;http://www.cygwin.com/&quot;&gt;Cygwin&lt;/a&gt;&lt;/h3&gt;  &lt;p&gt;Cygwin provides a huge collection of Unix command line utilities – sed, grep, bash, tar, that kind of thing – that are just useful to have around. I install Cygwin at C:\Windows\Cygwin\ - which keeps it neatly out of the way - and then add C:\Windows\Cygwin\bin\ to the system path, and then forget about it, because grepping for stuff just &lt;em&gt;works &lt;/em&gt;and that’s the whole point. &lt;/p&gt;  &lt;p&gt;If you want to use cygwin’s git client, you’ll need to add the optional git and openssh packages, because you’ll need ssh-keygen.exe to set things up, and then git.exe to wrangle your repositories.&lt;/p&gt;  &lt;h3&gt;&lt;a href=&quot;http://www.sliksvn.com/en/download&quot;&gt;&lt;/a&gt;&lt;a href=&quot;http://tortoisesvn.tigris.org/&quot;&gt;&lt;img style=&quot;border-bottom: 0px; border-left: 0px; margin: 0px 10px 0px 0px; display: inline; border-top: 0px; border-right: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; align=&quot;left&quot; src=&quot;http://lh3.ggpht.com/_LV_l8kYLOwo/Smoq_rhO3gI/AAAAAAAAAOw/G015totFujQ/image%5B66%5D.png?imgmax=800&quot; width=&quot;53&quot; height=&quot;45&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.sliksvn.com/en/download&quot;&gt;SlikSvn&lt;/a&gt; and &lt;a href=&quot;http://tortoisesvn.tigris.org/&quot;&gt;TortoiseSvn&lt;/a&gt;&lt;/h3&gt;  &lt;p&gt;TortoiseSvn is the wonderfully smooth and polished Windows shell extension that gives you right-click version control menus in Windows. It’s not just a great revision-control system; it’s a wonderful example of how you can seamlessly integrate your software into the OS instead of needing lots of clunky great windows and forms all over the place. SlikSvn is the command-line Windows binaries; although Tortoise does 99% of the day-to-day stuff, once in a while it’s useful being able to call svn from batch files and scripts, and that’s where SlikSvn comes in.&lt;/p&gt;  &lt;h3&gt;&lt;a href=&quot;http://www.nunit.org/index.php&quot;&gt;NUnit&lt;/a&gt;, &lt;a href=&quot;http://code.google.com/p/moq/&quot;&gt;Moq&lt;/a&gt; and &lt;a href=&quot;http://testdriven.net/&quot;&gt;TestDriven.NET&lt;/a&gt; &lt;/h3&gt;  &lt;p&gt;I like the simplicity of NUnit; I like Moq’s Linq-driven syntax, and I like the way TestDriven.net gives you all this from a right-click anywhere in your project. I particularly like that once these are all in place, they become the easiest way to run a chunk of experimental code, so your successful experiments often end up as unit tests without even trying. I like that. &lt;/p&gt;  &lt;h3&gt;&lt;a href=&quot;http://www.sliver.com/dotnet/SnippetCompiler/&quot;&gt;&lt;img style=&quot;border-right-width: 0px; margin: 0px 10px 0px 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px&quot; border=&quot;0&quot; align=&quot;left&quot; src=&quot;http://www.sliver.com/dotnet/SnippetCompiler/SnippetCompilerIcon.png&quot; /&gt;Snippet Compiler&lt;/a&gt;&lt;/h3&gt;  &lt;p&gt;Snippet Compiler compiles snippets. It’s like the notepad.exe of .NET IDEs, and it’s wonderful for just hacking together tiny programs to automate ad-hoc tasks or try out an idea. &lt;/p&gt;  &lt;h3&gt;&lt;a href=&quot;http://www.getpaint.net/index.html&quot;&gt;&lt;img style=&quot;border-bottom: 0px; border-left: 0px; margin: 0px 10px 0px 0px; display: inline; border-top: 0px; border-right: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; align=&quot;left&quot; src=&quot;http://lh3.ggpht.com/_LV_l8kYLOwo/SmorA0d1RNI/AAAAAAAAAPA/_T5sm2fgawU/image%5B67%5D.png?imgmax=800&quot; width=&quot;49&quot; height=&quot;50&quot; /&gt;&lt;/a&gt; &lt;a href=&quot;http://www.getpaint.net/index.html&quot;&gt;Paint.NET&lt;/a&gt;&lt;/h3&gt;  &lt;p&gt;It’s free, it’s open-source, it works, and it’s powerful. If you’re used to Photoshop it can take a bit of getting used to, but otherwise it’s a great application to have around.&lt;/p&gt;  </description>
          <pubDate>2009-07-24T22:05:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/07/24/tool-time.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/07/24/tool-time.html</guid>
        </item>
      
    
      
        <item>
          <title>Managing Loosely-Ordered Collections : Lopsided Fun with NHibernate</title>
          <description>&lt;p&gt;NHibernate’s &lt;a href=&quot;https://www.hibernate.org/hib_docs/nhibernate/1.2/reference/en/html/collections.html#collections-onetomany&quot;&gt;One-To-Many&lt;/a&gt; mapping allows you to map database one-to-many relationships into various kinds of .NET collections, but I recently hit a bit of a snag regarding the ordering of the collections. For example, consider a simple content management system for a music store’s website. This table:&lt;/p&gt;  &lt;table border=&quot;1&quot; cellspacing=&quot;0&quot; cellpadding=&quot;4&quot; width=&quot;430&quot;&gt;&lt;tbody&gt;     &lt;tr&gt;       &lt;th valign=&quot;top&quot; width=&quot;93&quot;&gt;Id&lt;/td&gt; &lt;/th&gt;        &lt;th valign=&quot;top&quot; width=&quot;138&quot;&gt;Name&lt;/td&gt; &lt;/th&gt;        &lt;th valign=&quot;top&quot; width=&quot;107&quot;&gt;ParentPageId&lt;/td&gt; &lt;/th&gt;        &lt;th valign=&quot;top&quot; width=&quot;90&quot;&gt;Position&lt;/td&gt; &lt;/tr&gt;&lt;/th&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;89&quot;&gt;1&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;134&quot;&gt;Home&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;113&quot;&gt;null&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;92&quot;&gt;0&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;87&quot;&gt;2&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;132&quot;&gt;Products&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;117&quot;&gt;1&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;93&quot;&gt;0&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;85&quot;&gt;3&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;131&quot;&gt;Guitars&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;120&quot;&gt;2&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;93&quot;&gt;0&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;84&quot;&gt;4&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;130&quot;&gt;Basses&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;122&quot;&gt;2&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;93&quot;&gt;1&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;84&quot;&gt;5&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;129&quot;&gt;Keyboards&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;123&quot;&gt;2&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;93&quot;&gt;3&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;84&quot;&gt;6&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;129&quot;&gt;Drums&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;124&quot;&gt;2&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;93&quot;&gt;4&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;84&quot;&gt;7&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;129&quot;&gt;Services&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;124&quot;&gt;1&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;93&quot;&gt;0&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;84&quot;&gt;8&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;129&quot;&gt;Guitar Tuition&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;124&quot;&gt;7&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;93&quot;&gt;0&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;84&quot;&gt;9&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;129&quot;&gt;Guitar Repairs&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;125&quot;&gt;7&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;93&quot;&gt;1&lt;/td&gt;     &lt;/tr&gt;   &lt;/tbody&gt;&lt;/table&gt;  &lt;p&gt;represents a simple page-tree structure that looks like this:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;strong&gt;Home        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; +&amp;#160; Products         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; + Guitars         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; + Basses         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; + Keyboards         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; + Drums         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; + Services         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; + Guitar Tuition         &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; + Guitar Repairs&lt;/strong&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;The key here is that the &lt;strong&gt;order of the elements is controlled by the Position column, &lt;/strong&gt;so I can change the order of any page’s child pages. NHibernate can cope with this just fine by using an indexed collection – but this only works &lt;strong&gt;as long as the Position column is always populated with unique, sequential, non-null values. &lt;/strong&gt;Null Position values cause it to blow up. It’ll also leave gaps in the list if there’s gaps in your sequence – five records with Position values 1,2,4,5,8 will be mapped into a nine-element list with NULL entries at indices 0,3,6,7, which turns your foreach() loops into little baby minefields and requires liberal use of guard clauses.&lt;/p&gt;  &lt;p&gt;That’s not quite what I’m after here. All I want to do is &lt;strong&gt;preserve the order of elements if an order has been defined&lt;/strong&gt;. There’s other apps talking to this database that aren’t using NH, which respect ordering by Position when retrieving records but don’t necessarily use sequential zero-based indices when saving changes. The actual data I’m dealing with is often going to look more like this:&lt;/p&gt;  &lt;table border=&quot;1&quot; cellspacing=&quot;0&quot; cellpadding=&quot;4&quot; width=&quot;430&quot;&gt;&lt;tbody&gt;     &lt;tr&gt;       &lt;th valign=&quot;top&quot; width=&quot;93&quot;&gt;Id&lt;/td&gt; &lt;/th&gt;        &lt;th valign=&quot;top&quot; width=&quot;138&quot;&gt;Name&lt;/td&gt; &lt;/th&gt;        &lt;th valign=&quot;top&quot; width=&quot;107&quot;&gt;ParentPageId&lt;/td&gt; &lt;/th&gt;        &lt;th valign=&quot;top&quot; width=&quot;90&quot;&gt;Position&lt;/td&gt; &lt;/tr&gt;&lt;/th&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;89&quot;&gt;1&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;134&quot;&gt;Home&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;113&quot;&gt;null&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;92&quot;&gt;0&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;87&quot;&gt;2&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;132&quot;&gt;Products&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;117&quot;&gt;1&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;93&quot;&gt;0&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;85&quot;&gt;3&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;131&quot;&gt;Guitars&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;120&quot;&gt;2&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;93&quot;&gt;2&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;84&quot;&gt;4&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;130&quot;&gt;Basses&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;122&quot;&gt;2&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;93&quot;&gt;null&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;84&quot;&gt;5&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;129&quot;&gt;Keyboards&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;123&quot;&gt;2&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;93&quot;&gt;5&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;84&quot;&gt;6&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;129&quot;&gt;Drums&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;124&quot;&gt;2&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;93&quot;&gt;6&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;84&quot;&gt;7&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;129&quot;&gt;Services&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;124&quot;&gt;1&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;93&quot;&gt;7&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;84&quot;&gt;8&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;129&quot;&gt;Guitar Tuition&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;124&quot;&gt;7&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;93&quot;&gt;null&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;84&quot;&gt;9&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;129&quot;&gt;Guitar Repairs&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;125&quot;&gt;7&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;93&quot;&gt;4&lt;/td&gt;     &lt;/tr&gt;   &lt;/tbody&gt;&lt;/table&gt;  &lt;p&gt;All I’m after is that when I retrieve elements, they are ordered by the Position column (using whatever ORDER BY semantics are in use on the database server), and that if I move things around in the list and then save it, the order of my list is preserved when saving. I don’t care about null values – if there’s no explicit positions defined, just stick whatever you’ve got in a list in any old order and give it back to me. Likewise duplicate values are OK, and if there’s missing values, don’t give me NULLs, just skip to the next element.&lt;/p&gt;  &lt;p&gt;One possible solution I’ve come up with looks like this. First, define a Position property on the entity. The getter returns the item’s current index in the parent’s children collection; and the setter is private and does nothing (but NHibernate won’t let you leave it out)&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;public virtual int Position {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; // Getter returns the current index of this element in its parent&apos;s children collection.       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; get {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (this.Parent == null) return (0);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (this.Parent.Children == null) return (0);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return (this.Parent.Children.IndexOf(this));       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; // Setter does nothing - order is determined by the &amp;quot;order-by&amp;quot; attribute in NHibernate mappings,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; // but NH requires that the setter exists.       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; private set { }       &lt;br /&gt;}&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Then there’s the actual mapping. We want to map this Position property to a DB column, so when doing insert/update operations, the value is persisted to the Position column, and we want to add an order-by attribute to the NHibernate mapping so that when we retrieve the collection, it comes back in the right order. If you’re using the lovely functional goodness that is &lt;a href=&quot;http://fluentnhibernate.org/&quot;&gt;Fluent NHibernate&lt;/a&gt;, the mapping looks like this:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;public class CmsPageMap : ClassMap&amp;lt;CmsPage&amp;gt; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; public CmsPageMap() {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Map(page =&amp;gt; page.Position);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; HasMany&amp;lt;CmsPage&amp;gt;(p =&amp;gt; p.Children)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; .KeyColumnNames.Add(&amp;quot;ParentPageId&amp;quot;)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; .WithForeignKeyConstraintName(&amp;quot;Page_Parent_Children&amp;quot;)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; .Inverse()       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; .Cascade.AllDeleteOrphan()       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; // Using SetAttribute() here because Fluent NHibernate doesn&apos;t support order-by yet.       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; .SetAttribute(&amp;quot;order-by&amp;quot;, &amp;quot;Position&amp;quot;);&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;and if you’re mapping it using XML files, you’ll need something like this:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&amp;lt;property name=&amp;quot;Position&amp;quot; type=&amp;quot;Int32&amp;quot;&amp;gt;      &lt;br /&gt;&amp;#160; &amp;lt;column name=&amp;quot;Position&amp;quot; /&amp;gt;       &lt;br /&gt;&amp;lt;/property&amp;gt;       &lt;br /&gt;&amp;lt;many-to-one name=&amp;quot;Parent&amp;quot; column=&amp;quot;ParentPageId&amp;quot; /&amp;gt;       &lt;br /&gt;&amp;lt;bag name=&amp;quot;Children&amp;quot; inverse=&amp;quot;true&amp;quot; cascade=&amp;quot;all-delete-orphan&amp;quot; order-by=&amp;quot;Position&amp;quot;&amp;gt;       &lt;br /&gt;&amp;#160; &amp;lt;key foreign-key=&amp;quot;Page_Parent_Children&amp;quot; column=&amp;quot;ParentPageId&amp;quot; /&amp;gt;       &lt;br /&gt;&amp;#160; &amp;lt;one-to-many class=&amp;quot;NinjaCms.Shared.Model.CmsPage, NinjaCms.Shared&amp;quot; /&amp;gt;       &lt;br /&gt;&amp;lt;/bag&amp;gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;The asymmetry of having a property that’s an order-by on the way out and a column mapping on the way back is slightly weird, but it works.&lt;/p&gt;  </description>
          <pubDate>2009-06-14T22:34:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/06/14/managing-loosely-ordered-collections.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/06/14/managing-loosely-ordered-collections.html</guid>
        </item>
      
    
      
        <item>
          <title>What If They Ran The Trains For Free?</title>
          <description>&lt;p&gt;&lt;a href=&quot;http://lh3.ggpht.com/_LV_l8kYLOwo/Si6wxYf_m_I/AAAAAAAAAM0/kxPrBsbDOJA/s1600-h/image%5B9%5D.png&quot;&gt;&lt;img style=&quot;border-bottom: 0px; border-left: 0px; margin: 0px 0px 20px 20px; display: inline; border-top: 0px; border-right: 0px&quot; title=&quot;Sexy Dynamite is on the Bakerloo line, but it&amp;#39;s in Zone 漢 字. (Just kidding. It&amp;#39;s actually a shop in Tokyo.)&quot; border=&quot;0&quot; alt=&quot;Sexy Dynamite is on the Bakerloo line, but it&amp;#39;s in Zone 漢 字. (Just kidding. It&amp;#39;s actually a shop in Tokyo.)&quot; align=&quot;right&quot; src=&quot;http://lh3.ggpht.com/_LV_l8kYLOwo/Si6wychHGoI/AAAAAAAAAM8/3SAcZwykHh8/image_thumb%5B4%5D.png?imgmax=800&quot; width=&quot;180&quot; height=&quot;240&quot; /&gt;&lt;/a&gt;London is gearing up for a two-day Tube strike – industrial action by London Underground staff means tomorrow morning, three million people who’d normally take the tube will be walking, cycling, packing themselves into crowded buses or overground trains, or just pretending they’re sick and staying at home. Papers say the strike is going to cost £100m in lost productivity. The net is a-twitter with harassed Londoners who want to kick Bob Crow in the bollocks. &lt;/p&gt;  &lt;p&gt;Now, I recall once hearing a story, which I cannot now find, so the details are somewhat hazy, but I’ll tell it like I remember it and you can tell me if I’m wrong.&amp;#160; It’s about a railway strike. I believe it happened in Japan sometime after the war, but the details aren’t really important. It started out like most industrial disputes – the railway staff wanted something, and the Powers That Be didn’t want to give it to them.&lt;/p&gt;  &lt;p&gt;So the staff resorted to industrial action. They didn’t strike, picket, protest, or stay at home. They went to work early, they cleaned the trains, fuelled them, and ran them as usual – but they opened all the barriers, shut off all the ticket machines, and &lt;strong&gt;let everybody travel for free. &lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;Imagine if the RMT tried this. There’d be trains full of happy, supportive passengers right now. By Thursday, every commuter, employer and tourist in London would be cheering the drivers, encouraging the staff, showing their support and praising Bob Crow as a public hero. Meanwhile, London Underground are panicking, watching their lost revenue shoot into the tens of millions, and probably hammering out a deal pretty damn sharpish so they can get the ticket barriers switched back on.&lt;/p&gt;  &lt;p&gt;Surely that’s a nicer way of making your point than pissing off three million people?&lt;/p&gt;  </description>
          <pubDate>2009-06-09T09:57:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/06/09/what-if-they-ran-trains-for-free.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/06/09/what-if-they-ran-trains-for-free.html</guid>
        </item>
      
    
      
        <item>
          <title>White Noise and Chaos</title>
          <description>&lt;p&gt;&lt;a title=&quot;Photo by ooOJasonOoo via Flickr&quot; href=&quot;http://www.flickr.com/photos/restlessglobetrotter/434218278/&quot;&gt;&lt;img style=&quot;border-right-width: 0px; margin: 0px 0px 20px 20px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px&quot; border=&quot;0&quot; align=&quot;right&quot; src=&quot;http://farm1.static.flickr.com/188/434218278_ddcbd80dd7_m.jpg&quot; /&gt;&lt;/a&gt;It’s been observed, both in &lt;a href=&quot;http://arxiv.org/abs/cond-mat/9907500&quot;&gt;science fact&lt;/a&gt; and in &lt;a href=&quot;http://www.amazon.co.uk/Wheelers-Ian-Stewart/dp/0743429028&quot;&gt;science fiction&lt;/a&gt;, that the radio transmissions of any sufficiently advanced civilization are indistinguishable from random noise. Think about this for one second. The most efficient way of utilising available bandwidth is to encrypt the stuff that needs to stay secret, and compress the hell out of everything else. End result – every repeating pattern in the signal becomes obliterated, either because it’s been deliberately hidden to prevent the signal being decrypted, or because compression algorithms have exploited – and thus eliminated – the redundancy in the repeating patterns. If E.T. is out there, he’s probably using gzip and PGP, and so even if we could pick up his transmissions they’d look exactly like random noise coming from every other corner of the universe.&lt;/p&gt;  &lt;p&gt;Does the same thing apply to software development teams? What happens once you’ve established a solid system for code reuse, a culture of refining and refactoring, implemented continuous integration for&amp;#160; builds, testing and deployment, and generally automated or eliminated all the routine, repetitive manual processes you can find? When you reach a point where every predictable aspect of the build and release process has been automated, you’ve effectively guaranteed that the only bits left for the &lt;em&gt;people&lt;/em&gt; to do are the creative, unpredictable, subjective, &lt;em&gt;random&lt;/em&gt; aspects of the process.&lt;/p&gt;  &lt;p&gt;Does that mean that, to an outside observer, the behaviour of any sufficiently advanced software team is indistinguishable from chaos?&lt;/p&gt;  &lt;p&gt;(And if is is… what does mean for new hires trying to learn your culture and processes?)&lt;/p&gt;  &lt;p&gt;&lt;small&gt;Photo from &lt;a href=&quot;http://www.flickr.com/photos/restlessglobetrotter/434218278/&quot;&gt;oooJasonOoo&lt;/a&gt; via Flickr, used by permission under Creative Commons. Thanks.&lt;/small&gt;&lt;/p&gt;  </description>
          <pubDate>2009-06-07T14:59:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/06/07/white-noise-and-chaos.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/06/07/white-noise-and-chaos.html</guid>
        </item>
      
    
      
        <item>
          <title>NHibernate / Castle ByteCode provider – are you running the right number of bits?</title>
          <description>&lt;p&gt;Just had one of those head-scratching moments… checked out a known working project including NHibernate and ActiveRecord DLLs, built the whole thing – which built without errors - and then got this lovely message:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;Unable to load type &apos;NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle&apos; during configuration of proxy factory class.     &lt;br /&gt;Possible causes are:      &lt;br /&gt;- The NHibernate.Bytecode provider assembly was not deployed.      &lt;br /&gt;- The typeName used to initialize the &apos;proxyfactory.factory_class&apos; property of the session-factory section is not well formed. &lt;/p&gt;    &lt;p&gt;Solution:     &lt;br /&gt;Confirm that your deployment folder contains one of the following assemblies:      &lt;br /&gt;NHibernate.ByteCode.LinFu.dll      &lt;br /&gt;NHibernate.ByteCode.Castle.dll&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;I’ve seen that message quite a lot recently, but thought we’d well and truly sorted it… well, it turns out there’s another possible cause – you’re building for “Any CPU” on a 64-bit machine and your copy of NHibernate.ByteCode.Castle.dll was built on a colleague’s 32-bit machine. Because this DLL is loaded at runtime there’s no compile-time checking that it’s the right bitness – so it’ll build fine, and then blow up.&lt;/p&gt;  &lt;p&gt;Setting VS2008’s platform target to “x86” has solved it in this case – I guess doing a 64-bit build of the relevant DLL would also work.&lt;/p&gt;  </description>
          <pubDate>2009-06-05T16:16:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/06/05/nhibernate-castle-bytecode-provider-are.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/06/05/nhibernate-castle-bytecode-provider-are.html</guid>
        </item>
      
    
      
        <item>
          <title>Just for Fun: The Webcam Teleprompter Shroud Experiment</title>
          <description>&lt;p&gt;My first experience of video conferencing was on a £10,000 dual ISDN Pentium system back in 1997, and it was horrible
    – low quality, unresponsive, and this weird problem where the person on the other end never actually looked AT you,
    they always looked slightly off to one side or above your head. Nowadays, of course, the video and audio quality are
    massively improved, and even a £300 netbook has a pretty good built-in webcam – but they still haven’t solved the
    problem of the camera and the screen not being in the same place – so if we’re chatting on Skype, and I’m looking at
    &lt;em&gt;you&lt;/em&gt;, I’m not looking at the camera – so to you, I appear to be looking slightly above your head, or off to
    one side, or whatever...
&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/2009/skype-shot.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;TV &lt;a href=&quot;https://en.wikipedia.org/wiki/Teleprompter&quot;&gt;teleprompters&lt;/a&gt; have solved this problem – they use a piece
    of glass, tilted at 45°, inside a darkened “shroud” ; the camera lens is hidden behind the darkened glass, so the
    newsreader sees the reflection of the hidden screen, and the camera doesn’t see the screen at all – it sees straight
    through the darkened glass to where the newsreader is sitting. Yes, this is a bit magic (or maybe it’s just &lt;a
        href=&quot;https://en.wikipedia.org/wiki/Clarke&apos;s_Law&quot;&gt;sufficiently advanced&lt;/a&gt;?) It’s worth remembering that a
    sheet
    of glass which is lit on one side and dark on the other actually behaves like a mirror – notice how you see your
    reflection when you look out of your house windows after dark, but you never see your reflection when it’s bright
    outside?&lt;/p&gt;
&lt;p&gt;Point is, teleprompters work on some pretty basic optics. I’ve wondered for a long time whether the same principle
    could solve the video-conferencing problem, so I decided to hack together a prototype to see if it would work… and
    although the image quality is pretty rough, it does actually work. I was quite surprised. In this screenshot, I’m
    looking at Skype&amp;#160; - NOT at the webcam, which is flipped 90° and pointing straight down from inside the
    cardboard gizmo on top of the screen – and yet the video feed shows me looking straight at the camera. &lt;/p&gt;
&lt;p&gt;&lt;img style=&quot;border-bottom: 0px; border-left: 0px; display: block; float: none; margin-left: auto; border-top: 0px; margin-right: auto; border-right: 0px&quot;
        title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;/images/posts/2009/teleprompter-skype-screenshot.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Optically, here’s what’s going on. Once Skype’s running, just drag the remote video window so it lines up behind the
    glass exactly (my webcam has a tiny light next to the lens that shows up in the reflection, which makes this part
    pretty easy), and then when you look at the person you’re talking to, you’ll actually make eye contact instead of
    staring over their head or down at their chest.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/2009/teleprompter-optics-diagram.png&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;and here’s the actual contraption itself; the glass is from one of those cheap postcard picture frames, and it’s just
    held in place with blu-tak inside a home-made cardboard “shroud”. The webcam’s a Logitech Communicator Deluxe, which
    has a neat flexible base/clamp/grip thing that makes it pretty easy to flip, and the last thing I had to do was
    tweak the webcam settings in Skype to flip the video 90° vertically.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/2009/teleprompter-housing-with-blutak.jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/2009/teleprompter-housing-2.jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/2009/teleprompter-housing-3.jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If anyone else fancies hacking together one of these, drop me a line – I’d be curious to see how well it works when
    you’ve got eye contact and line of sight on both ends of the video feed.&lt;/p&gt;</description>
          <pubDate>2009-06-03T23:16:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/06/03/just-for-fun-webcam-teleprompter-shroud.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/06/03/just-for-fun-webcam-teleprompter-shroud.html</guid>
        </item>
      
    
      
        <item>
          <title>Speaking at SkillsMatter Open Source .NET Exchange III</title>
          <description>&lt;p&gt;&lt;a title=&quot;Skills Matter Open Source .NET Training&quot; href=&quot;http://skillsmatter.com/go/open-source-dot-net&quot;&gt;&lt;img style=&quot;border-bottom: 0px; border-left: 0px; margin: 0px 0px 20px 20px; display: inline; border-top: 0px; border-right: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; align=&quot;right&quot; src=&quot;http://lh6.ggpht.com/_LV_l8kYLOwo/SiKgiJ9P_JI/AAAAAAAAAL8/_e6koLLfh9g/image%5B24%5D.png?imgmax=800&quot; width=&quot;149&quot; height=&quot;154&quot; /&gt;&lt;/a&gt; I’ll be doing a short spot about the &lt;a href=&quot;http://www.microsoft.com/Web/downloads/platform.aspx&quot;&gt;Web Platform Installer&lt;/a&gt; and &lt;a href=&quot;http://blogs.iis.net/msdeploy/&quot;&gt;msdeploy&lt;/a&gt; at the forthcoming &lt;a href=&quot;http://skillsmatter.com/event/open-source-dot-net/open-source-dot-net-exchange-iii&quot;&gt;SkillsMatter Open Source .NET Exchange&lt;/a&gt; evening here in London on July 16th. It’s a kind of “bonsai conference” where we get through six or seven fifteen-minute topics in one evening, giving a sort of whirlwind tour of what’s new and what’s cool in the open source .NET development world.&lt;/p&gt;  &lt;p&gt;The Web Platform Installer (below) is a slick GUI installer that’ll download and configure everything you’ll need to get started building and hosting web apps on Microsoft systems – if you’re just getting started with web development, or you want to check out one of the popular open-source frameworks like Subtext, Drupal or DotNetNuke, it’ll set up everything you need in a couple of clicks, including Visual Studio Express, SQL Server express, PHP, and whatever else you’ll need to run your chosen apps and frameworks. And yes, it really is as quick and easy as the screenshot makes it look.&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh6.ggpht.com/_LV_l8kYLOwo/SiKgjSS6hzI/AAAAAAAAAMA/LeAksuYkR6w/s1600-h/image%5B8%5D.png&quot;&gt;&lt;img style=&quot;border-bottom: 0px; border-left: 0px; margin: 20px auto; display: block; float: none; border-top: 0px; border-right: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh3.ggpht.com/_LV_l8kYLOwo/SiKgke2XKGI/AAAAAAAAAME/lg5NqC2ghw8/image_thumb%5B6%5D.png?imgmax=800&quot; width=&quot;600&quot; height=&quot;445&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;msdeploy is the command-line packaging and deployment engine used internally by the Web Platform Installer, but you can also use msdeploy directly to update, package, publish and manage your own servers. It’ll let you publish updated files, configurations, virtual directories, script handlers – basically your entire web application, set up and ready to run –between servers. We’re using it to publish changes from our staging server to our live servers, and it’s an absolute delight to work with.&lt;/p&gt;  &lt;p&gt;We’ve also got &lt;a href=&quot;http://skillsmatter.com/podcast/open-source-dot-net/a-first-look-a-boo&quot;&gt;Ian Cooper talking about Boo&lt;/a&gt;, &lt;a href=&quot;http://skillsmatter.com/podcast/open-source-dot-net/spark-view-engine&quot;&gt;Scott Cowan talking about the Spark view engine&lt;/a&gt;, &lt;a href=&quot;http://skillsmatter.com/podcast/open-source-dot-net/introduction-to-mpidot-net&quot;&gt;David Ross on MPI.NET&lt;/a&gt;, Gojko talking about &lt;a href=&quot;http://skillsmatter.com/podcast/open-source-dot-net/acceptance-testing-in-english-with-concordion-dot-net&quot;&gt;acceptance testing with&amp;#160; Concordion.NET&lt;/a&gt;, &lt;a href=&quot;http://skillsmatter.com/podcast/open-source-dot-net/f-units-of-measure&quot;&gt;Phil Trelford showing off units of measure in F#,&lt;/a&gt; and &lt;a href=&quot;http://skillsmatter.com/podcast/open-source-dot-net/what-openrasta-does-other-frameworks-cant&quot;&gt;Seb talking about his rapidly-evolving REST framework, OpenRasta&lt;/a&gt;. It should be a good evening with plenty of time for food, beer and chat with like-minded developers. The last one was extremely popular so if you want to come along, &lt;a href=&quot;http://skillsmatter.com/event/open-source-dot-net/open-source-dot-net-exchange-iii&quot;&gt;please register&lt;/a&gt; and we’ll see you there.&lt;/p&gt;  </description>
          <pubDate>2009-05-31T15:21:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/05/31/speaking-at-skillsmatter-open-source.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/05/31/speaking-at-skillsmatter-open-source.html</guid>
        </item>
      
    
      
        <item>
          <title>Room Service Oriented Architecture</title>
          <description>&lt;p align=&quot;justify&quot;&gt;This is a story about breakfasts.&lt;a title=&quot;Sunrise on the Chobe River channel (c) bjmccray via Flickr&quot; href=&quot;http://www.flickr.com/photos/bjmccray/2792588400/&quot;&gt;&lt;img style=&quot;border-bottom: 0px; border-left: 0px; margin: 0px 0px 20px 20px; display: inline; border-top: 0px; border-right: 0px&quot; title=&quot;Sunrise on the Chobe River channel (c) bjmccray via Flickr&quot; border=&quot;0&quot; alt=&quot;Sunrise on the Chobe River channel (c) bjmccray via Flickr&quot; align=&quot;right&quot; src=&quot;http://lh6.ggpht.com/_LV_l8kYLOwo/SiG-35cN7QI/AAAAAAAAAL0/TAz-ZjNtFG8/image%5B10%5D.png?imgmax=800&quot; width=&quot;360&quot; height=&quot;240&quot; /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p align=&quot;justify&quot;&gt;Well, OK, it’s actually a story about hotels and software architecture. But, like anything worth doing, it starts with two breakfasts. (You all know about &lt;a href=&quot;http://www.imdb.com/title/tt0120737/quotes&quot;&gt;second breakfast&lt;/a&gt;, right?)&lt;/p&gt;  &lt;p align=&quot;justify&quot;&gt;The first was the breakfast I ate on my ninth birthday, cooked by my dad on the banks of the &lt;a href=&quot;http://images.google.co.uk/images?q=chobe+river&quot;&gt;Chobe river in Botswana&lt;/a&gt;. We were on a camping trip, at a remote spot in the middle of nowhere – no roads, no phones, no shops, no electricity - so to cook this particular breakfast, my folks had brought eggs, bacon, sausages, bread, butter, cooking oil – you name it, they had it, along with a gas-powered fridge to keep it all fresh, and a big tank of water to wash up afterwards, and a gas stove to cook it on, and matches to light the stove, and countless other necessities that were all written down on a big long list taped to the inside of Dad’s camping trailer. This single breakfast, like every meal we’d eat out out there in the bush, involved weeks of planning, preparation, equipment, checklists – did we have enough food? Would it stay fresh long enough? Had we brought enough gas bottles to run the fridge and still have enough left to cook with? Was it safe to wash up with boiled river-water or did we need to use bottled stuff? When it’s just you and your tent in the middle of nowhere, you have to cover every single detail – and if you pack two weeks worth of bacon and then wake up one day and fancy porridge, you’re screwed. It’s bacon or nothing.&lt;/p&gt;  &lt;p align=&quot;justify&quot;&gt;&lt;img style=&quot;border-bottom: 0px; border-left: 0px; margin: 10px 20px 20px 0px; display: inline; border-top: 0px; border-right: 0px&quot; title=&quot;They apparently have wildlife in the Big Apple as well.&quot; border=&quot;0&quot; alt=&quot;They apparently have wildlife in the Big Apple as well.&quot; align=&quot;left&quot; src=&quot;http://lh4.ggpht.com/_LV_l8kYLOwo/SiG-5rbIpeI/AAAAAAAAAL4/NwaQ4sAFcvc/image%5B19%5D.png?imgmax=800&quot; width=&quot;240&quot; height=&quot;319&quot; /&gt; The second breakfast was eaten on the 44th floor of a Manhattan hotel, sometime in 2004. Organising this breakfast involved picking up a phone and saying to Steph in room service &amp;quot;Hey, can you send up some pancakes and coffee?” and then going back to sleep until someone knocked at the door with – you guessed it - pancakes and coffee. Planning involved? Zero. Which was just as well; as I’d discovered the previous night, New York does a pretty good line in cocktails as well, and if I’d needed to plan anything whatsoever that morning, I’d probably have given up and gone hungry.&lt;/p&gt;  &lt;p align=&quot;justify&quot;&gt;So, let’s compare the two scenarios. In both cases, someone’s making a request for breakfast, and someone is then responding to that request – ask for breakfast, wait a while, get breakfast. Simple. Thing is, you can clearly see that for my dad, responding to this particular request involves an awful lot of work, whereas Steph probably doesn’t even need to stand up. And the irony is that for all his hard work and planning, the only thing Dad can actually respond with is bacon and eggs, whereas Steph – who probably can’t even &lt;em&gt;cook&lt;/em&gt; – can do bacon, eggs, orange juice, pastries, espresso, fresh fruit and even those famous silver-dollar pancakes covered in sugar. And martinis. And probably arrange a shoe-shine, a haircut, a massage and get your tux dry-cleaned while she’s about it.&lt;/p&gt;  &lt;p align=&quot;justify&quot;&gt;If you’re only here to hear about breakfast, you can leave now; it’s about to get a bit geeky. Still here? Cool, OK, here we go. &lt;/p&gt;  &lt;p align=&quot;justify&quot;&gt;If you’ve built web applications for any length of time, at some point in your career you probably wrote something like an ASP page that connects to a database, reads some files, sends an e-mail or two, updates a database, does some validation, resizes an image, saves it to disk, generates a bunch of HTML and returns it to the client – &lt;em&gt;all in one file&lt;strong&gt;. &lt;/strong&gt;&lt;/em&gt;It’s OK, you can admit it; we’re all friends here. For quite a long time, I wrote most web apps this way. At the time it all seemed terribly clever, and if you stuck at it long enough, you normally ended up with something that did the job, paid the bills and kept the client reasonably happy. Problem is, this is normally the client who thinks he has millions of potential customers, but actually the load on his Web 1.0 B2B portal is him, his business partner, and about four guys in Russia who they persuaded to sign up. The wonderful thing about scaling issues is that when you get them in your software, it normally means your business is doing OK. Assuming, of course, you have a business model in the first place…&lt;/p&gt;  &lt;p align=&quot;justify&quot;&gt;Anyway. Point is, trying to apply this everything-in-one-page approach in any large-scale app is like using my dad’s catering strategy to make breakfast for 1,200 people. Last time I looked, the only people who set off into the middle of nowhere with fifty tons of bacon and eggs were the Army - and whatever your thoughts on the armed forces, I’m sure you’ll agree that if we’re trying to learn about minimalist efficiency and cost-effectiveness, we’re probably better off looking elsewhere.&lt;/p&gt;  &lt;p align=&quot;justify&quot;&gt;So, how does Steph arrange a couple of hundred breakfasts a day using just a phone – and what can we learn from her about designing scalable web apps?&lt;/p&gt;  &lt;p align=&quot;justify&quot;&gt;What’s happening is that Steph’s acting as a &lt;strong&gt;front controller&lt;/strong&gt; in a &lt;strong&gt;service-oriented architecture&lt;/strong&gt;. The front controller’s job is to grab the incoming request, call whichever services are necessary to fulfil that request, and tell them what to do with the result.&lt;/p&gt;  &lt;p align=&quot;justify&quot;&gt;Steph has a single responsibility – she converts customer requests (imagine Peter from Family Guy ordering breakfast: “yeah, uh, do you have, like… ah… hang on… er… like, pancakes? With syrup? And some tea? No, wait, coffee… no, tea… no coffee. Yeah. Coffee. Did I get pancakes?”) into short, snappy, precise kitchen orders (“Mikey, two pancakes, two coffee for forty-four-oh-seven”). Mikey is the breakfast chef, and he’s just one of the services that Steph can delegate to. Someone orders a martini, she can delegate to Raoul, the bartender service. Someone orders a shoeshine, she can delegate to Chip the shoe-shine guy. &lt;/p&gt;  &lt;p align=&quot;justify&quot;&gt;Like Steph, Mikey has a single responsibility. He turns short, snappy kitchen orders into plates full of food, and he does it quickly. He doesn’t buy the ingredients, he doesn’t serve the food – he delegates those responsibilities to other services. Like Tony, the delivery service, who turns up at 4am every day with a truck full of fresh supplies. Or Chet, the bellhop service, who takes the plates, puts them on one of those funny little trolley-tables with some cutlery, and takes them up to 4407 where I’m waiting for my breakfast.&lt;/p&gt;  &lt;p align=&quot;justify&quot;&gt;Hotels can teach us a lot about scaling, because they’re good at it – they’ve been in the hotel business a lot longer than we’ve been in the software business, after all.&lt;/p&gt;  &lt;h3&gt;Make Your Controllers Delegate Heavy Lifting to Services&lt;/h3&gt;  &lt;p align=&quot;justify&quot;&gt;Think about a typical hotel; there’s probably only four or five points of customer interaction – say the front desk, the concierge, the room service desk, and the bar. There’s probably only one or two people at each desk, but because they’re working as part of a service-oriented architecture, they can fulfil a huge number of incredibly diverse requests, whilst keeping their own responsibilities simple, well-defined and manageable. Think of your controllers like the customer service desks in a hotel – clean, friendly, and completely dependent on supporting services to actually get anything done. That’s how it should be.&lt;/p&gt;  &lt;h3&gt;Cache Whatever You Can (Within Reason)&lt;/h3&gt;  &lt;p align=&quot;justify&quot;&gt;For a hotel, this is the early-morning deliveries of food, booze, fresh linen, theater tickets – stuff that you’re &lt;em&gt;probably&lt;/em&gt; going to need sometime today, and you don’t want clients sat around waiting whilst you have to run out and fetch it. For web apps, this means setting up local caches of images, persistent data, resource files. But don’t go crazy. Just like hotels don’t order a ten-year supply of lamb chops, you’ll need to consider how much space your cache consumes, and how long it’ll stay fresh before you need to reload it.&lt;/p&gt;  &lt;h3&gt;Acknowledge Now; Deliver Later&lt;/h3&gt;  &lt;p align=&quot;justify&quot;&gt;The room-service desk doesn’t stay on the line yakking with you until your pancakes arrive. They tell you it’ll be twenty minutes, and then hang up so they can take the next call – if they didn’t, they’d need fifty room service operators instead of one. You can use asynchronous processing, message queues and Ajax callbacks to achieve the same thing in web applications – when you get a request that’s going to take a while, respond immediately saying it’ll take a while, and let your users get on with something else whilst it’s processing. And be careful with the Refresh button. If I call down to see where my pancakes have got to, it doesn’t mean I want to order &lt;em&gt;more&lt;/em&gt; pancakes; I’m just chasing up the first batch.&lt;/p&gt;  </description>
          <pubDate>2009-05-30T23:19:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/05/30/room-service-oriented-architecture.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/05/30/room-service-oriented-architecture.html</guid>
        </item>
      
    
      
        <item>
          <title>What If SQL Had Abstract Tables And Composite Types?</title>
          <description>&lt;p&gt;There are well-documented mismatches between the object-oriented paradigm and the relational database model. Despite the early adopters telling us we should all be using F# running against CouchDB, I’m going to go out on a limb and say that object-oriented languages and relational databases aren’t going away any time soon.&lt;/p&gt;  &lt;p&gt;The more I see and learn about ORMs, the more I get this feeling like there’s a real “code vs data” mindset going on. To an object-oriented application developer, the database is a necessarily evil, the DBA is a pain-in-the-ass Luddite who won’t let you write code the way you want to, tables are an inadequate and inferior persistence mechanism for your beautiful object hierarchies, and – well, if someone could wave a magic wand and just make the whole database thing go away, you’d all be very happy. Even in the most wonderfully agile, test-driven greenfield project, there’s still this perception that the more we can abstract the project &lt;em&gt;away&lt;/em&gt; from the database, the better off we’ll be. It’s like the Utopian ideal of ORMs is not to embrace the power of the database – they’d rather make the DB completely irrelevant.&lt;/p&gt;  &lt;p&gt;I sit on both sides of this particular fence. As a developer, I find persistence as frustrating and time-consuming as everyone else – but as a business stakeholder, I want my data stored in normal, sensibly-named tables that I can get at using SQL-92 queries. Not because that’s what I’m doing now, but because I really have no idea what I’ll be doing with that data in 5-10 years, and I believe the relational model has stood the test of time.&amp;#160; &lt;/p&gt;  &lt;p&gt;I’ve spent today at&amp;#160; Ayende’s NHibernate workshops at the Skillsmatter Progressive .NET event, and he’s shown us all sorts of NHibernate magic, including various strategies for modelling inheritance and class hierarchies with polymorphic associations, sparse tables, and the like. There’s been some discussion of stuff like composite keys, searching, indexes, unique constraints – and it’s pretty clear that when it comes to object-relational mapping, it’s the object folks that are doing all the work, and the relational side of things is really not doing anything much to make their lives easier.&lt;/p&gt;  &lt;p&gt;There’s two things that stand out as being particular painful when it comes to object-relational mapping – composite keys and inheritance. Inheritance because it has no analogue in the relational model so you have to somehow “fake” it – which makes no difference to the code, but it can leave the DB in a bit of a mess. Composite keys because they have built-in limitations regarding nested queries (WHERE foo IN (SELECT…)) – and complex equality and value semantics, which means most ORMs strongly advise against using composite keys if you can help it.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;(From this point forward I’m making stuff up as a sort of thought experiment. Don’t take any of it too literally :))&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;What if we added ABSTRACT and EXTENDS keywords to SQL? What if you could do this in your database?&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;CREATE &lt;font color=&quot;#008000&quot;&gt;&lt;strong&gt;ABSTRACT&lt;/strong&gt;&lt;/font&gt; TABLE Customer (       &lt;br /&gt;&amp;#160; ID int identity(1,1) primary key       &lt;br /&gt;)&lt;/p&gt;    &lt;p&gt;CREATE TABLE Company &lt;font color=&quot;#008000&quot;&gt;&lt;strong&gt;EXTENDS&lt;/strong&gt;&lt;/font&gt; Customer (       &lt;br /&gt;&amp;#160; CompanyName varchar(256)       &lt;br /&gt;)&lt;/p&gt;    &lt;p&gt;CREATE TABLE Person &lt;strong&gt;&lt;font color=&quot;#008000&quot;&gt;EXTENDS&lt;/font&gt;&lt;/strong&gt; Customer (       &lt;br /&gt;&amp;#160;&amp;#160; Forenames varchar(256),       &lt;br /&gt;&amp;#160;&amp;#160; Surname varchar(256)       &lt;br /&gt;)&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Here’s the rules:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Tables must be abstract or concrete. &lt;/li&gt;    &lt;li&gt;You can’t insert, update or delete records from an abstract table &lt;/li&gt;    &lt;li&gt;Both abstract and concrete tables can participate in foreign key relationships &lt;/li&gt;    &lt;li&gt;One abstract record must have exactly one concrete record (i.e. you can’t insert a Company whose ID already represents a Person)&lt;/li&gt;    &lt;li&gt;Concrete tables behave like the union of their own columns and their abstract base table’s columns&lt;/li&gt; &lt;/ul&gt;  &lt;blockquote&gt;   &lt;p&gt;SELECT * FROM Customer&lt;/p&gt;    &lt;table cellspacing=&quot;0&quot; cellpadding=&quot;2&quot; width=&quot;20&quot; border=&quot;1&quot;&gt;&lt;tbody&gt;       &lt;tr&gt;         &lt;td valign=&quot;top&quot; width=&quot;20&quot;&gt;           &lt;p align=&quot;right&quot;&gt;&lt;strong&gt;Id&lt;/strong&gt;&lt;/p&gt;         &lt;/td&gt;       &lt;/tr&gt;        &lt;tr&gt;         &lt;td valign=&quot;top&quot; width=&quot;20&quot;&gt;1&lt;/td&gt;       &lt;/tr&gt;        &lt;tr&gt;         &lt;td valign=&quot;top&quot; width=&quot;20&quot;&gt;2&lt;/td&gt;       &lt;/tr&gt;        &lt;tr&gt;         &lt;td valign=&quot;top&quot; width=&quot;20&quot;&gt;3&lt;/td&gt;       &lt;/tr&gt;     &lt;/tbody&gt;&lt;/table&gt;    &lt;p&gt;SELECT * FROM Company&lt;/p&gt;    &lt;table cellspacing=&quot;0&quot; cellpadding=&quot;2&quot; width=&quot;232&quot; border=&quot;1&quot;&gt;&lt;tbody&gt;       &lt;tr&gt;         &lt;td valign=&quot;top&quot; width=&quot;39&quot;&gt;&lt;strong&gt;Id&lt;/strong&gt;&lt;/td&gt;          &lt;td valign=&quot;top&quot; width=&quot;191&quot;&gt;&lt;strong&gt;CompanyName&lt;/strong&gt;&lt;/td&gt;       &lt;/tr&gt;        &lt;tr&gt;         &lt;td valign=&quot;top&quot; width=&quot;39&quot;&gt;1&lt;/td&gt;          &lt;td valign=&quot;top&quot; width=&quot;191&quot;&gt;Monkey Butlers Ltd&lt;/td&gt;       &lt;/tr&gt;     &lt;/tbody&gt;&lt;/table&gt;    &lt;p&gt;SELECT * FROM Person&lt;/p&gt;    &lt;table cellspacing=&quot;0&quot; cellpadding=&quot;2&quot; width=&quot;230&quot; border=&quot;1&quot;&gt;&lt;tbody&gt;       &lt;tr&gt;         &lt;td valign=&quot;top&quot; width=&quot;32&quot;&gt;&lt;strong&gt;Id&lt;/strong&gt;&lt;/td&gt;          &lt;td valign=&quot;top&quot; width=&quot;115&quot;&gt;&lt;strong&gt;Forenames&lt;/strong&gt;&lt;/td&gt;          &lt;td valign=&quot;top&quot; width=&quot;81&quot;&gt;&lt;strong&gt;Surname&lt;/strong&gt;&lt;/td&gt;       &lt;/tr&gt;        &lt;tr&gt;         &lt;td valign=&quot;top&quot; width=&quot;34&quot;&gt;2&lt;/td&gt;          &lt;td valign=&quot;top&quot; width=&quot;114&quot;&gt;Eddie&lt;/td&gt;          &lt;td valign=&quot;top&quot; width=&quot;81&quot;&gt;Van Halen&lt;/td&gt;       &lt;/tr&gt;        &lt;tr&gt;         &lt;td valign=&quot;top&quot; width=&quot;35&quot;&gt;3&lt;/td&gt;          &lt;td valign=&quot;top&quot; width=&quot;114&quot;&gt;Jack&lt;/td&gt;          &lt;td valign=&quot;top&quot; width=&quot;82&quot;&gt;Sparrow&lt;/td&gt;       &lt;/tr&gt;     &lt;/tbody&gt;&lt;/table&gt;    &lt;p&gt;&amp;#160;&lt;/p&gt;    &lt;p&gt;INSERT INTO Person(Id, Forenames, Surname) VALUES(4, “Ayende”, “Rahien”)&lt;/p&gt;    &lt;p&gt;-- will work as expected&lt;/p&gt;    &lt;p&gt;INSERT INTO Company(Id, Name) VALUES(4, “Rhino Ltd”)&lt;/p&gt;    &lt;p&gt;-- will fail with “Distributed key violation” or some such thing, because ID4 is already taken by a Person and so can’t be used for a Company.&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;You can, e.g. associate Customer with Address (so every address is ‘owned’ by exactly one customer, regardless of whether the customer in question is a person or a company) – but you can also associate Employee with Company directly, so that your data schema enforces the business requirement that a Person cannot have Employees. As a database concept, it doesn’t really add anything – but with a suitably turbocharged ORM, think of what you could do. A simple Customer.ListAll() could return an array of Customer objects – each of which is ACTUALLY a strongly-typed Person or Company. You could do things like Customer.ListAll(typeof(Company)); you could switch on the type of the objects – and all without the compromise of sparse tables or child table inheritance.&lt;/p&gt;  &lt;p&gt;Ok, what about composite keys? Imagine we’re booking flights for an airline. There is a real-world business constraint that the same person cannot be on the same flight twice, and composite keys are tailor-made for modelling this sort of “unique combination” scenario:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;CREATE TABLE SeatReservation (     &lt;br /&gt;&amp;#160;&amp;#160; Key &lt;font color=&quot;#008000&quot;&gt;&lt;strong&gt;COMPOSITE&lt;/strong&gt;&lt;/font&gt; (      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; PassengerId int,      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; FlightNumber varchar(5)      &lt;br /&gt;&amp;#160;&amp;#160; ) primary key,      &lt;br /&gt;&amp;#160;&amp;#160; SeatRow char,      &lt;br /&gt;&amp;#160;&amp;#160; SeatNumber int      &lt;br /&gt;)&lt;/p&gt;    &lt;p&gt;CREATE TABLE SpecialMeal (&lt;/p&gt;    &lt;p&gt;&amp;#160; -- Notice that the “type” of this column is a SeatReservation.Key – i.e. a reference     &lt;br /&gt;&amp;#160; -- to the composite type defined as Key in the SeatReservation table.      &lt;br /&gt;&amp;#160;&amp;#160; SeatReservation.Key SeatReservation primary key,      &lt;br /&gt;&amp;#160;&amp;#160; IsVegetarian bit,      &lt;br /&gt;&amp;#160;&amp;#160; IsVegan bit,      &lt;br /&gt;&amp;#160;&amp;#160; IsHalal bit      &lt;br /&gt;) &lt;/p&gt;    &lt;p&gt;SELECT * FROM SeatReservation WHERE Key.PassengerId = 12&lt;/p&gt;    &lt;p&gt;SELECT * FROM SeatReservation WHERE Key IN&amp;#160; (SELECT SeatReservationKey FROM SpecialMeal)&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Think of this like adding a struct type to SQL, where COMPOSITE defines a collection of columns just as in C# a struct defines a collection of fields. Composites carry field-value semantics for comparison and equality; if all the columns are equal, the struct is equal. Composite literals are defined inline like:&lt;/p&gt;  &lt;p&gt;select * from SpecialMeal WHERE SeatReservation.Key = (178189, ‘VS207’)&lt;/p&gt;  &lt;p&gt;- using similar syntax to SQL’s familiar INSERT INTO (Column1, Column2) VALUES(Value1, Value2)&lt;/p&gt;  &lt;p&gt;This would allow ORMs such as NHibernate to explicitly map a composite key as a struct, benefiting from intrinsic value-object semantics. The resulting database queries could use composite keys in exactly the same way as primary keys – because the database explicitly allows a composite ‘type’ to be used wherever a primitive type is currently supported – and, most importantly, the &lt;strong&gt;&lt;em&gt;business meaning of a composite key&lt;/em&gt;&lt;/strong&gt; is explicit in both the database schema and the object model. &lt;/p&gt;  </description>
          <pubDate>2009-05-12T16:18:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/05/12/what-if-sql-had-abstract-tables-and.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/05/12/what-if-sql-had-abstract-tables-and.html</guid>
        </item>
      
    
      
        <item>
          <title>Offsite Backup Recommendations for Really, Really Big Files?</title>
          <description>&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/spadgy/313252221/&quot;&gt;&lt;img style=&quot;margin: 0px 0px 20px 20px; display: inline&quot; title=&quot;Old media 2 by john_a_ward. via Flickr.&quot; alt=&quot;Old media 2 by john_a_ward. via Flickr.&quot; align=&quot;right&quot; src=&quot;http://farm1.static.flickr.com/103/313252221_cf49d277a3.jpg?v=0&quot; width=&quot;240&quot; height=&quot;214&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;When I’m not coding and being all nerdy, I play guitar and mess around with digital audio recording. Uncompressed digital audio is stored in the same format as old-fashioned audio CDs – 74 mins of raw audio takes up 740Mb or thereabouts. This means that a good afternoon’s recording can easily create 2-3Gb of files – and they’re not like MP3s or movies that are easy enough to replace; these are often the only copy in the world of that one killer solo or that bass riff that I’ve been practising for weeks and just happened to &lt;em&gt;nail&lt;/em&gt; it this afternoon. &lt;/p&gt;  &lt;p&gt;Stuff like Carbonite or Mozy is all well and good for documents and holiday snaps, but given I have consumer ADSL (512Kb upstream), is there even any point thinking about offsite backup for my audio projects, or am I better off just copying stuff to a USB hard drive and maybe taking a drive into work every couple of months? What do people do with video / post-processing stuff? What’s your backup strategy when the actual work you’re doing is creating gigabytes of files on a daily basis? &lt;/p&gt;  &lt;p&gt;&lt;small&gt;[photo from &lt;a href=&quot;http://www.flickr.com/photos/spadgy/313252221/&quot;&gt;john_a_ward via flickr&lt;/a&gt; – thanks for sharing, John.]&lt;/small&gt;&lt;/p&gt;  </description>
          <pubDate>2009-05-09T18:55:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/05/09/offsite-backup-recommendations-for.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/05/09/offsite-backup-recommendations-for.html</guid>
        </item>
      
    
      
        <item>
          <title>Unsubscribe (aka ‘the Internet Way of Saying “It’s Not You, It’s Me.”’)</title>
          <description>&lt;p&gt;My personal inbox is full of opt-in marketing… I receive newsletters about hotels, concerts, travel insurance, car insurance, DVDs, cheap flights… seems you can’t buy &lt;em&gt;anything&lt;/em&gt; these days without ending up on their list. Spam is one thing, but most of this is stuff I have actually signed up for – albeit unwittingly – over the years. You know the deal - “by purchasing this motor insurance you agree to receive blah blah blah from time to time” and you never get around to opting-out? &lt;/p&gt;  &lt;p&gt;So I spent a happy Saturday morning unsubscribing from every piece of legitimate-but-unwanted marketing material in my inbox… and it’s remarkable how diverse something as simple as the unsubscribe experience can get. &lt;strong&gt;Here is how unsubscribe should work.&lt;/strong&gt; I want to click an &lt;strong&gt;unsubscribe link&lt;/strong&gt;. I do not want to log in and update my preferences. I want to click &lt;strong&gt;one link. &lt;/strong&gt; I want to get a page that’s looks like it’s somehow connected to the organisation who are sending me this stuff – even if it’s just a logo at the top of the page. I want a message informing me that &lt;strong&gt;my e-mail address&lt;/strong&gt; has been removed. I see that, I feel all happy, like you care about &lt;strong&gt;me&lt;/strong&gt;, and you care about &lt;strong&gt;your brand&lt;/strong&gt;, and I’ll come back and do business with you again, because I know &lt;strong&gt;you’re happy to let me walk away&lt;/strong&gt; if I want to.&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh6.ggpht.com/_LV_l8kYLOwo/SgXOnHOMu7I/AAAAAAAAALs/YQT90uPjkxo/s1600-h/image%5B15%5D.png&quot;&gt;&lt;img style=&quot;border-bottom: 0px; border-left: 0px; display: block; float: none; margin-left: auto; border-top: 0px; margin-right: auto; border-right: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh5.ggpht.com/_LV_l8kYLOwo/SgXOn2iYJ3I/AAAAAAAAALw/JsD3VW5PvNI/image_thumb%5B9%5D.png?imgmax=800&quot; width=&quot;602&quot; height=&quot;480&quot; /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;It’s amazing how many unsubscribe links go to a page called unsubscribe.htm – htm! - on unrelated domain (some third-party mailing list provider) with no branding or connection to the company you’re dealing with, and give you some completely impersonal “Your request has been acknowledged” message. Don’t acknowledge my request – take me off the damn list already. “Acknowledging my request” is right up there with my call being important to you, your being an equal opportunities employer, and your product helping weight loss as part of a calorie controlled diet. &lt;/p&gt;  &lt;p&gt;Oh – and when I click the same link again, I’d love a message saying “you’re already unsubscribed”. Unsubscribe should not be &lt;a href=&quot;http://en.wikipedia.org/wiki/Idempotent&quot;&gt;idempotent&lt;/a&gt;. If they &lt;em&gt;really&lt;/em&gt; removed you from the list the first time, what exactly are they doing the second time?&lt;/p&gt;  &lt;p&gt;PS: Whilst we’re talking usability and logging in and stuff, how many times has this happened to you?&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Go to website login form&lt;/li&gt;    &lt;li&gt;Enter your e-mail address and password… “sorry, incorrect password”&lt;/li&gt;    &lt;li&gt;Click “Forgotten password”…&lt;/li&gt;    &lt;li&gt;…and have to enter your e-mail address again?&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;I just &lt;em&gt;&lt;strong&gt;told&lt;/strong&gt; &lt;/em&gt;you my e-mail address… is it too much to ask that you remember it for fifteen seconds?&lt;/p&gt;  </description>
          <pubDate>2009-05-09T09:42:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/05/09/unsubscribe-aka-internet-way-of-saying.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/05/09/unsubscribe-aka-internet-way-of-saying.html</guid>
        </item>
      
    
      
        <item>
          <title>A Weird Little Quirk with Windows 7, Outlook 2007 and NormalEmail.dotm</title>
          <description>&lt;p&gt;One of those “get this fix into Google” blog posts… running Outlook 2007 on Windows 7 Beta, whenever i close Outlook I get this error message:&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh6.ggpht.com/_LV_l8kYLOwo/SfiTSFJ1MPI/AAAAAAAAALk/Jyi8CagwVX8/s1600-h/image%5B4%5D.png&quot;&gt;&lt;img style=&quot;border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px&quot; title=&quot;image&quot; border=&quot;0&quot; alt=&quot;image&quot; src=&quot;http://lh5.ggpht.com/_LV_l8kYLOwo/SfiTSRRbM2I/AAAAAAAAALo/67XoGcZcebU/image_thumb%5B2%5D.png?imgmax=800&quot; width=&quot;588&quot; height=&quot;127&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;Outlook cannot save or create this file. Make sure that the disk you want to save the file on is not full, write-protected, or damaged. (C:\NormalEmail.dotm)&lt;/p&gt; &lt;/blockquote&gt;  &lt;p align=&quot;left&quot;&gt;It’s &lt;strong&gt;actually &lt;/strong&gt;trying to create NormalEmail.dotm under &lt;/p&gt;  &lt;p align=&quot;left&quot;&gt;&lt;strong&gt;C:\Users\user.name\AppData\Roaming\Microsoft\Templates\&lt;/strong&gt;&lt;/p&gt;  &lt;p align=&quot;left&quot;&gt;but for some reason I don’t understand, after installing Office 2007 on Windows 7, there’s a weird 100Kb file called simply “Templates” – no extension - in &lt;strong&gt;C:\Users\user.name\AppData\Roaming\Microsoft\&lt;/strong&gt;. It doesn’t do anything as far as I can tell, but it means Outlook can’t create the &lt;strong&gt;Templates\ &lt;/strong&gt;folder because there’s already a file by that name in the AppData\Roaming\Microsoft\ folder. Deleting this file doesn’t appear to hurt anything, and once it’s gone, Outlook happily shuts down without complaint.&lt;/p&gt;  &lt;p align=&quot;left&quot;&gt;That’s been bugging me for weeks, so I’ll bet you it’s fixed in yesterday’s Office 2007 Service Pack 2, or the Windows 7 Release Candidate that’s due out tomorrow… but if it’s not, then this is how I fixed it :)&lt;/p&gt;  </description>
          <pubDate>2009-04-29T17:50:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/04/29/weird-little-quirk-with-windows-7.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/04/29/weird-little-quirk-with-windows-7.html</guid>
        </item>
      
    
      
        <item>
          <title>Hey, Apple. DVD + iPod - Make It Happen. Here’s How.</title>
          <description>&lt;p&gt;&lt;img title=&quot;Jed Bartlett would have abolished copyright by now. You know he would.&quot; style=&quot;border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; margin: 0px 0px 20px 20px; border-right-width: 0px&quot; alt=&quot;Jed Bartlett would have abolished copyright by now. You know he would.&quot; src=&quot;http://ecx.images-amazon.com/images/I/51mldHLdMFL._SL500_AA240_.jpg&quot; align=&quot;right&quot; border=&quot;0&quot; /&gt;Two good things happened this Easter weekend. One – I joined the gym. Two – I bought the &lt;a href=&quot;http://en.wikipedia.org/wiki/The_West_Wing_(TV_series)&quot;&gt;West Wing&lt;/a&gt; complete boxed set. The West Wing is good because it’s intelligent, gripping, well-written, brilliantly acted television. The gym is good because it will probably stop me dying of a heart attack aged 35. If I can work out some way of combining these two awesome positive forces, it’ll probably improve my life in a whole lot of ways.&lt;/p&gt;  &lt;p&gt;I don’t currently own a portable video player. Yeah, I know. Luddite freak. It’s ok. Point, stare, laugh if you must. What I &lt;em&gt;do &lt;/em&gt;have is a cupboard full of things (ankle weights, rollerblades, squash gear, yoga mats, that kind of thing) acquired through thinking &lt;strong&gt;“hey, if I buy that, it’ll make me fit”. &lt;/strong&gt;Since I’ve now run out of cupboard space, I have a rule that I must actually &lt;em&gt;try&lt;/em&gt; these things before I spend any money. So, before running off to Tottenham Court Road to drool over portable video players, I decided I had to try out this idea to see if it worked. My better half has a 5G video iPod that she offered to lend me for this experiment, so I figured all I had to do was import a couple of West Wing episodes into iTunes, sync them onto her iPod, take it to the gym, and see how it goes.&lt;/p&gt;  &lt;p&gt;Oh, if only it was that easy. iTunes is blissfully ignorant of DVDs. Presumably because they’d much rather you paid for a second copy of everything from the iTunes video store. I should mention that they do now have a thing called the &lt;a href=&quot;http://www.apple.com/pr/library/2008/01/15fox.html&quot;&gt;iTunes Digital Copy&lt;/a&gt; – where you get a DVD, and a second disc with a digital copy of the same movie in MP4 format that you can transfer &lt;em&gt;once&lt;/em&gt; onto one iTunes computer. Which is frankly completely stupid. There’s already a perfectly good digital copy of the movie on the first disc – &lt;em&gt;that’s what DVD is&lt;/em&gt;. Next thing you know, they’ll be trying to sell you &lt;a href=&quot;http://en.wikipedia.org/wiki/Something_Wall-Mart_This_Way_Comes&quot;&gt;three copies of Timecop for $18&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;I eventually managed to transfer one disc’s worth of shows, via a convoluted process of trial-and-error involving VLC, Handbrake and lots of tedious waiting around to see if &lt;em&gt;this&lt;/em&gt; time it would actually work. Long story short. Yes, it’s possible, (and yes, watching West Wing in the gym is frikkin’ awesome). No, it’s not easy. On Mac OS X, &lt;a href=&quot;http://handbrake.fr/&quot;&gt;Handbrake&lt;/a&gt; does a reasonable job. On a PC, it’s even worse because the PC version of &lt;a href=&quot;http://handbrake.fr/&quot;&gt;Handbrake&lt;/a&gt; won’t read DVDs directly. There are many extremely dodgy-looking DVD-to-iPod convertors on Google; there’s also some wonderfully powerful open-source tools around that can read, write and transcode the various formats, but none of them are particularly intuitive. Encoding a DVD file for the iPhone using ffmpeg is apparently as easy as:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;ffmpeg -threads 4 -i %1 -r 29.97 -vcodec libx264 -s 480x272 -flags +loop -cmp +chroma -deblockalpha 0 -deblockbeta 0 -crf 24 -bt 256k -refs 1 -coder 0 -me umh -me_range 16 -subq 5 -partitions +parti4x4+parti8x8+partp8x8 -g 250 -keyint_min 25 -level 30 -qmin 10 -qmax 51 -trellis 2 -sc_threshold 40 -i_qfactor 0.71 -acodec libfaac -ab 128k -ar 48000 -ac 2 %~n1.mp4&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;- see? It’s that easy! Now, I love ffmpeg. For doing &lt;a href=&quot;http://www.dylanbeattie.net/misc/monsters.jpg&quot;&gt;weird and wonderful things&lt;/a&gt; with video, it’s unsurpassed – but this? This should be easy. I bought a DVD in a store, and I want to watch it on a consumer video playback appliance. I’m not stealing anything, sharing anything, or doing anything even remotely anarchist – I’m just being a good little consumer who spent a lot of money on shiny things and wants them to play nicely together. &lt;/p&gt;  &lt;h3&gt;Let me say right now that I will give a huge amount of love and cash to the first company who can come up with a nice way of making video as convenient as MP3 is right now.&lt;/h3&gt;  &lt;p&gt;I can buy a CD in a store, rip it at work, listen to the MP3s on the way home, copy them off my MP3 player onto my media server and listen to them again while I’m making dinner. I can buy MP3s online from pretty much anywhere, play them on anything, and even burn them back onto CD to listen to in the car. (And for the record, I’m quite happy paying more for high-quality MP3s than I do for physical CDs, because you save me the hassle of ripping it myself.) &lt;/p&gt;  &lt;p&gt;Well, I want the same with video. I’ve already spent a fortune on DVDs. I’m not going to buy those movies and TV shows again – there is nothing wrong with the quality or content of my existing purchases, and I know it’s &lt;em&gt;technically &lt;/em&gt;possible to do what I want, because those catch little ffmpeg incantations like the one above seem to do a pretty good job. And yeah, I know it’s probably not going to happen because of copyright, piracy, the &lt;a href=&quot;http://en.wikipedia.org/wiki/Digital_Millennium_Copyright_Act&quot;&gt;DMCA&lt;/a&gt;, and the usual bureaucratic reasons that only really matter to people who don’t have time to circumvent them.&lt;/p&gt;  &lt;h3&gt;But until that happens - Apple, how about this, just for starters? &lt;/h3&gt;  &lt;p&gt;Allow each iTunes Music Store account to make a single DRM-encoded copy of a particular DVD. I can download The&amp;#160; Dark Knight from the iTunes Music Store, and watch it on my five authorized devices, and neither Apple nor the MPAA nor Warner Bros seem to have a problem with this. They get their money, I get my movie, end of story. So why not just provide a way of doing the same thing, but instead of downloading it from you, I can buy The Dark Knight in HMV and rip it myself? The file’s still encrypted, still DRM-protected, and if nothing else, it’ll save you 8Gb worth of bandwidth.&amp;#160; &lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh6.ggpht.com/_LV_l8kYLOwo/SePQWaVdhpI/AAAAAAAAALc/bm3i7-pFi1A/s1600-h/archos7_reflet%5B6%5D.jpg&quot;&gt;&lt;img title=&quot;Mmm. Shiny. Open source. Linux. Want.&quot; style=&quot;border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; margin: 0px 20px 20px 0px; border-right-width: 0px&quot; height=&quot;248&quot; alt=&quot;Mmm. Shiny. Open source. Linux. Want.&quot; src=&quot;http://lh6.ggpht.com/_LV_l8kYLOwo/SePQW8ozJwI/AAAAAAAAALg/UzckuTgSCUw/archos7_reflet_thumb%5B4%5D.jpg?imgmax=800&quot; width=&quot;400&quot; align=&quot;left&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;Apple have proved with iTunes that most consumers don’t actually care about DRM, ideology, freedom or any of that stuff, because they’d rather just play with the shiny things. They have proved this astonishingly well, and created such very lovely shiny things into the bargain, that I’m verging on just ditching any pretence of principled consumerism and buying an iPod Touch. They’re pretty, and they work, and apparently they now play Scrabble as well. Bonus.&lt;/p&gt;  &lt;p&gt;But – it looks like if I want to watch West Wing in the gym, I’m resigned to using DVD rippers, ffmpeg.exe and command-line encoding hacks &lt;em&gt;anyway&lt;/em&gt;. So I’m thinking I should sod Apple and buy an Archos, since they have bigger drives, bigger screens, Linux under the hood, and no DRM.&amp;#160; If I end up spending a couple of hours a week hacking ffmpeg instead of working out, then that’s what I was doing last week anyway. &lt;em&gt;Plus ça change, plus c&apos;est la même chose&lt;/em&gt;&lt;/p&gt;  </description>
          <pubDate>2009-04-13T23:52:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/04/13/hey-apple-dvd-ipod-make-it-happen-heres.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/04/13/hey-apple-dvd-ipod-make-it-happen-heres.html</guid>
        </item>
      
    
      
        <item>
          <title>Exifacto – a Nicer EXIF Library for .NET</title>
          <description>&lt;p&gt;I’ve been looking at reading and parsing &lt;a href=&quot;http://en.wikipedia.org/wiki/Exchangeable_image_file_format&quot;&gt;EXIF data&lt;/a&gt; for a project I’m working on; the project itself is still a long way off, but – as is so often the case in software - the EXIF code I’m working on is starting to show some real promise, so I’ve cleaned up the (very basic!) implementation I’ve currently got and put it up on &lt;a href=&quot;http://code.google.com/p/exifacto/source/checkout&quot;&gt;Google Code&lt;/a&gt; for the world to play with.&lt;/p&gt;  &lt;p&gt;The System.Drawing classes in .NET expose  EXIF data as key/value pairs, but it’s about as unfriendly an API as I’ve ever seen. Each EXIF tag is exposed as a numeric ID (which is one of about a hundred magic numbers defined in the EXIF spec), and an array of bytes – it’s up to you to work out which item is which, extract, parse and decode the byte arrays, and make sense of the result. Just to make things interesting, most numeric data in EXIF is stored as ratios – pairs of signed or unsigned ints representing the numerator/denominator of a rational number. Which makes perfect sense if you’re a photographer - always talking in F-stops and hundredths of a second and fractions of an inch – but makes things just a bit harder if you’re trying to get this stuff out into your .NET application.&lt;/p&gt;  &lt;p&gt;For example - to get the date a photograph was taken, using  .NET code, you need to do this:&lt;/p&gt;  &lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;DateTime myImageTaken = DateTime.MinValue;

Bitmap bitmap = new Bitmap(@&quot;image001.jpg&quot;);
foreach(PropertyItem item in bitmap.PropertyItems) {
   if (item.Id == 0x9003) {
       string exifDate = Encoding.ASCII.GetString(item.Value);
       if (DateTime.TryParseExact(exifDate,
           exifDateFormats,
           CultureInfo.InvariantCulture,
           DateTimeStyles.AllowInnerWhite,
           out myImageTaken)
       ) {
           break;
       }
   }
}
Console.WriteLine(&quot;Image taken at: &quot; + myImageTaken.ToString());&lt;/pre&gt;&lt;p&gt;&lt;a href=&quot;http://lh4.ggpht.com/_LV_l8kYLOwo/ScV6LSL5TGI/AAAAAAAAAK8/fEuH_JYLoLs/s1600-h/exifacto%5B5%5D.png&quot;&gt;&lt;img title=&quot;exifacto&quot; style=&quot;border-right: 0px; border-top: 0px; display: inline; margin: 0px 0px 0px 20px; border-left: 0px; border-bottom: 0px&quot; height=&quot;215&quot; alt=&quot;exifacto&quot; src=&quot;http://lh6.ggpht.com/_LV_l8kYLOwo/ScV6Liu62II/AAAAAAAAALA/PNqjKiF1Q2E/exifacto_thumb%5B3%5D.png?imgmax=800&quot; width=&quot;385&quot; align=&quot;right&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;With Exifacto, you do this:&lt;/p&gt;&lt;pre class=&quot;c#&quot; name=&quot;code&quot;&gt;ExifData exifData = new ExifData(“image001.jpg”);
Console.WriteLine(“Image taken at: “ + exifData.DateTimePhotoCreated)&lt;/pre&gt;&lt;p&gt;Right now, it’s got support for the basic string and DateTime information, and a couple of the lookup properties wrapped in .NET enumerations; next thing is parsing and arithmetic for signed and unsigned rational numbers. The actual implementation isn’t really the point, though - the idea is to get as much descriptive documentation into the Intellisense comments as possible, ending up with a discoverable API that takes a lot of the confusion out of working with EXIF data.&lt;/p&gt;&lt;p&gt;It’s being hosted at &lt;a href=&quot;http://code.google.com/p/exifacto/&quot;&gt;http://code.google.com/p/exifacto/&lt;/a&gt; – Subversion only for now, but source code &amp;amp; DLLs to follow once I’ve tied up a few loose ends.&lt;/p&gt;</description>
          <pubDate>2009-03-21T23:37:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/03/21/exifacto-nicer-exif-library-for-net.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/03/21/exifacto-nicer-exif-library-for-net.html</guid>
        </item>
      
    
      
        <item>
          <title>Just One More Reason to Love Red Gate Software</title>
          <description>&lt;p&gt;&lt;a href=&quot;http://www.red-gate.com/&quot;&gt;Red Gate&lt;/a&gt; make really great software – if you work with SQL Server or .NET and haven’t checked out Red Gate’s offerings, I highly recommend them.&amp;#160; They also sponsor the UK Alt.Net conferences, they’re frequently voted one of the top ten workplaces in the UK – and I just stumbled across this, on the 11th page of their (legally binding) &lt;a href=&quot;http://www.red-gate.com/purchase/license.pdf&quot;&gt;license agreements&lt;/a&gt;:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p align=&quot;center&quot;&gt;&lt;strong&gt;SCHEDULE 3       &lt;br /&gt;BEERWARE        &lt;br /&gt;&lt;/strong&gt;The following Beerware is licensed on a free basis:*&lt;/p&gt;    &lt;p&gt;DTS Package Compare     &lt;br /&gt;SQL Log Rescue&lt;/p&gt;    &lt;p&gt;* subject to a non-binding obligation on the Licensee (or their representative) to buy a beer for     &lt;br /&gt;a representative of the Licensor at any trade conference at which the Licensee (or their      &lt;br /&gt;representative) and the Licensor&apos;s representative are both present, provided, however, that      &lt;br /&gt;the Licensee (or their representative) should not attempt to purchase a beer where to do so      &lt;br /&gt;would be unlawful in the relevant jurisdiction.&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Even their &lt;em&gt;lawyers&lt;/em&gt; are awesome. I love Red Gate.&lt;/p&gt;  </description>
          <pubDate>2009-03-05T15:43:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/03/05/just-one-more-reason-to-love-red-gate.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/03/05/just-one-more-reason-to-love-red-gate.html</guid>
        </item>
      
    
      
        <item>
          <title>Critical Mass</title>
          <description>&lt;p&gt;I’m pretty much a control-freak nerd with antisocial tendencies and an aversion to physical exercise. &lt;a href=&quot;http://www.criticalmasslondon.org.uk/main.html&quot; target=&quot;_blank&quot;&gt;Critical Mass&lt;/a&gt; involves joining 200 total strangers on a bicycle ride around London with no plan, no fixed route and no organisers… so I really have no idea why I enjoy it so much. But I do.&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/dylanbeattie/sets/72157614438813175/&quot; target=&quot;_blank&quot;&gt;&lt;img style=&quot;border-right: 0px; border-top: 0px; display: inline; margin: 0px 0px 20px 20px; border-left: 0px; border-bottom: 0px&quot; src=&quot;http://farm4.static.flickr.com/3609/3314411529_59da56fe6d_m.jpg&quot; align=&quot;right&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;It might be that whole snow-day thing, of witnessing a phenomenon that you couldn’t recreate no matter how hard you tried so you really, really try to absorb and remember as much detail as you can. Or it might be the sheer joy of cycling up Whitehall and down Oxford Street without wondering if you’re going to die horribly every time you go near a bus. It’s the last Friday of every month, a whole crowd of cyclists appear under Waterloo Bridge by the BFI and cycle around London. It’s not a protest, it’s not organised, there’s no destination or agenda; it’s just a bunch of people who love cycling and happen to be following one another. Thing is, when the group of cyclists reaches a certain size (the “critical mass” they’re named after), the normal rules of cycling in London are turned upside down, and the traffic has to get out of &lt;em&gt;your&lt;/em&gt; way for a change.&lt;/p&gt;  &lt;p&gt;The London rides have been happening since 1994, but I only found out about them last year. I’d been following the &lt;a href=&quot;http://news.bbc.co.uk/1/hi/magazine/7667183.stm&quot; target=&quot;_blank&quot;&gt;coverage of the legal dispute&lt;/a&gt; between the police and the cyclists about Critical Mass, which was eventually resolved &lt;a href=&quot;http://news.bbc.co.uk/1/hi/england/london/7750004.stm&quot; target=&quot;_blank&quot;&gt;in favour of the cyclists&lt;/a&gt;&amp;#160; late last November. Two days later, I was in the café at Foyles bookstore on Charing Cross Road and saw the Critical Mass procession going past – two bikes with sound systems, a couple of unicyclists, a penny-farthing or two, and several hundred regular cyclists – and I just thought “yeah, I want to do that!” I went along to the next ride – which was Boxing Day, and absolutely &lt;em&gt;freezing&lt;/em&gt;, but really good fun nonetheless&lt;em&gt; – &lt;/em&gt;and tonight was my second ride. 15 miles or so, from &lt;a href=&quot;http://maps.google.co.uk/maps/ms?msa=0&amp;amp;msid=104938853509746000381.000463ee2569ed0d61a7b&amp;amp;ie=UTF8&amp;amp;z=13&quot; target=&quot;_blank&quot;&gt;Waterloo to Kings Cross, Euston, then along Hyde Park into Kensington and back through Knightsbridge&lt;/a&gt;. &lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/dylanbeattie/3314418885/&quot; target=&quot;_blank&quot;&gt;&lt;img title=&quot;&quot; style=&quot;border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; border-left: 0px; margin-right: auto; border-bottom: 0px&quot; height=&quot;383&quot; alt=&quot;Critical Mass London, February 2009 by you.&quot; src=&quot;http://farm4.static.flickr.com/3529/3314408987_235bd11585.jpg?v=0&quot; width=&quot;508&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Every day, I try to ask myself “why will I remember today? What makes today stand out from all the others?” Well, today’s was cycling down Oxford Street, cyclists as far as the eye can see, shoppers applauding and cheering, AC/DC blasting out from the bike next to me…&amp;#160; awesome. But don’t take my word for it – grab your bike and &lt;a href=&quot;http://www.critical-mass.org/europe.html&quot; target=&quot;_blank&quot;&gt;join us for the next one&lt;/a&gt;, wherever you are. &lt;/p&gt;  </description>
          <pubDate>2009-02-28T00:50:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/02/28/critical-mass.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/02/28/critical-mass.html</guid>
        </item>
      
    
      
        <item>
          <title>Blog woes…</title>
          <description>&lt;p&gt;Windows Live Writer on Windows 7 is a little… unstable. &amp;quot;The “Save” button crashes it. So I was playing around with a couple of other blog clients today… two non-starters and one that helpfully ignored the “post as draft” button. So ignore the post about nulls being null, ‘cos it’s not finished, and bear with me until I get this thing sorted. (Other than the Live Writer problems, Windows 7 is really looking quite nice. Like Vista but quicker and less… opinionated. I like it.)&lt;/p&gt;  </description>
          <pubDate>2009-02-27T22:58:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/02/27/blog-woes.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/02/27/blog-woes.html</guid>
        </item>
      
    
      
        <item>
          <title>A Post About Nothing, or, How I Learned To Stop Worrying And Love Nullable&lt;T&gt;</title>
          <description>&lt;p&gt;&lt;a href=&quot;http://tech-nous.blogspot.com/&quot;&gt;Iain Holder&lt;/a&gt; has an &lt;a href=&quot;http://tech-nous.blogspot.com/2009/02/nullable.html&quot;&gt;interesting post&lt;/a&gt; about Nullable&amp;lt;bool&amp;gt; over on his blog, about the pitfalls of using nullable boolean values:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;What does null mean? It&apos;s got to mean something. You&apos;re either pregnant or you&apos;re not. You can&apos;t have a third state. A light switch is either on or off. If a light switch doesn&apos;t exist then it&apos;s potentially very dangerous. Almost as dangerous as having a nullable bool.&lt;/p&gt; &lt;/blockquote&gt; &lt;a href=&quot;http://lh6.ggpht.com/_LV_l8kYLOwo/SaXpR_Eh8UI/AAAAAAAAAKk/5NvoV8hgXf4/s1600-h/nullable_field_warning%5B6%5D.jpg&quot;&gt;   &lt;p&gt;&lt;strong&gt;&lt;/strong&gt;&lt;a href=&quot;http://lh6.ggpht.com/_LV_l8kYLOwo/SaXpR_Eh8UI/AAAAAAAAAKs/X5d6m2VGyfA/s1600-h/nullable_field_warning%5B7%5D.jpg&quot;&gt;&lt;img title=&quot;nullable_field_warning&quot; style=&quot;border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; margin: 0px 20px 20px 0px; border-right-width: 0px&quot; height=&quot;181&quot; alt=&quot;nullable_field_warning&quot; src=&quot;http://lh5.ggpht.com/_LV_l8kYLOwo/SaXpSs1BlEI/AAAAAAAAAKM/l1EVZVadYyc/nullable_field_warning_thumb%5B3%5D.jpg?imgmax=800&quot; width=&quot;244&quot; align=&quot;left&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;&lt;/p&gt; &lt;/a&gt;Personally, I like null; I think it’s a wonderfully useful and often-misunderstood concept. Null is not dangerous &lt;em&gt;per se&lt;/em&gt;, but used correctly, it can be a very effective warning sign. &lt;/a&gt;  &lt;p&gt;&lt;strong&gt;Null means &lt;em&gt;we don’t know – &lt;/em&gt;or alternatively, &lt;em&gt;that question doesn’t make sense in this context&lt;/em&gt;.&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;To run with Iain’s example for a moment, let’s write a patient tracking system for a hospital; every time a patient arrives, we store their medical details in a database – blood type, next of kin, whether they’re pregnant, that kind of thing – so that the doctors can recommend appropriate treatment. All fields are required – no nulls allowed. It’s nice, simple, saves time, saves lives.&lt;/p&gt;  &lt;p&gt;All very well, until an ambulance brings in an unconscious woman who might need X-rays. Is she pregnant? She might be – you don’t know; she came in alone, and she’s unconscious. The clock is ticking… an X-ray might save her life, but if she’s pregnant, it could harm her unborn child. If our patient system makes us choose true/false with no third option, we’re potentially making a very dangerous assumption either way. &lt;strong&gt;We need a third option, &lt;/strong&gt;so when a doctor gets that patient’s chart, they see that &lt;strong&gt;we don’t know&lt;/strong&gt;, and they can make sure they do a pregnancy test before sending the patient for X-rays or administering potentially dangerous medication.&lt;/p&gt;  &lt;p&gt;I’m not arguing that NULL is the only solution to this problem. The &lt;strong&gt;problem&lt;/strong&gt; is universal, and nullable fields is just one of many possible solutions. It happens to be a solution that’s natively supported in most databases and platforms, and I personally think the semantics of “null = don’t know” are rather nice. You may disagree – but you still need &lt;em&gt;something&lt;/em&gt; to indicate when data in your model is potentially inaccurate or missing. &lt;/p&gt;  &lt;h3&gt;Yes, null has no place in a perfect model…&lt;/h3&gt;  &lt;p&gt;If your model (whether it’s an OO domain model or a relational database model) is complete, perfect, accurate and consistent, then you’re laughing. You will never have null values because your model maps perfectly onto the problem domain you’re working in, and you know every detail of every&amp;#160; entity in that problem domain. You know every single customer’s date of birth; you have detailed records of the marketing preferences of every person in your database, and your model is so perfectly tuned to your business that there’s no sparse tables, no outer joins - &lt;/p&gt;  &lt;h3&gt;…but there’s &lt;em&gt;no such thing &lt;/em&gt;as a perfect model&lt;/h3&gt;  &lt;p&gt;A model is an abstraction, and &lt;a href=&quot;http://www.joelonsoftware.com/articles/LeakyAbstractions.html&quot; target=&quot;_blank&quot;&gt;abstractions always leak&lt;/a&gt;. The real world isn’t domain-driven, or relational, or object-oriented – these paradigms are just ways of slicing and storing information about the real world that help us solve a problem or do a job. A lot of the time we’re making decisions based on the value of the information, but sometimes we need to make decisions based on whether that information is present or not. &lt;/p&gt;  &lt;p&gt;A slightly less contrived real-life example might help. In one of the systems I work on, we have a nullable bit field in our main customer database for e-mail opt-in preferences. We interpret the true/false/null values as follows:&lt;/p&gt;  &lt;table cellspacing=&quot;0&quot; cellpadding=&quot;0&quot; width=&quot;575&quot; border=&quot;1&quot;&gt;&lt;tbody&gt;     &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;59&quot;&gt;True&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;514&quot;&gt;This customer has &lt;strong&gt;agreed&lt;/strong&gt; to received marketing information via e-mail&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;60&quot;&gt;False&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;514&quot;&gt;This customer has chosen &lt;strong&gt;not&lt;/strong&gt; to receive marketing information via e-mail&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;61&quot;&gt;Null&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;514&quot;&gt;We&lt;strong&gt; have no record&lt;/strong&gt; of this customer&apos;s preferences.&lt;/td&gt;     &lt;/tr&gt;   &lt;/tbody&gt;&lt;/table&gt;  &lt;p&gt;&lt;a href=&quot;http://images.google.co.uk/images?hl=en&amp;amp;q=null&quot; target=&quot;_blank&quot;&gt;&lt;img title=&quot;This is the second Google Image search result for &amp;quot;null&amp;quot;. I have no idea what it is - something to do with magnetic topology - but I think it&amp;#39;s quite beautiful. &quot; style=&quot;border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; margin: 0px 0px 20px 20px; border-right-width: 0px&quot; height=&quot;240&quot; alt=&quot;This is the second Google Image search result for &amp;quot;null&amp;quot;. I have no idea what it is - something to do with magnetic topology - but I think it&amp;#39;s quite beautiful. &quot; src=&quot;http://solar.physics.montana.edu/colinb/trace_null.gif&quot; width=&quot;217&quot; align=&quot;right&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;When we send out e-mail newsletters, we only include the customers whose field is actually true – that’s the point of opt-in marketing, right? Any customer can log in and change their preferences at any time, so the customers who have opted-out (i.e. their value is false), we leave them alone – they’ve said they don’t want to get hassled, so we don’t hassle them, and if they change their mind, they can log in and reactivate their newsletter any time.&lt;/p&gt;  &lt;p&gt;So what about &lt;strong&gt;null&lt;/strong&gt;? Well, when a customer logs in, they get a personalized welcome page. If we see that their opt-in field is null, we add a message to this page saying “Hey, we have this e-mail newsletter you can subscribe to - could you take a moment to let us know whether you’re interested?” The point is, as soon as they’ve expressed a preference &lt;strong&gt;one way or the other&lt;/strong&gt;, we’ll stop showing them this message, so a customer should only see this message the first few times they’ve logged in. We end up with better data; they don’t get unwanted e-mail, and everyone’s happy.&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;&lt;small&gt;The weird swirly picture there turned up in a &lt;a href=&quot;http://images.google.co.uk/images?hl=en&amp;amp;q=null&quot; target=&quot;_blank&quot;&gt;Google Images search for “null”&lt;/a&gt; – I don’t know enough about magnetic topology to have the faintest idea what it is, but it’s quite beautiful, don’t you think? Image © &lt;a href=&quot;http://solar.physics.montana.edu/colinb/&quot;&gt;Colin Beveridge&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;  </description>
          <pubDate>2009-02-19T17:29:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/02/19/post-about-nothing-or-how-i-learned-to.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/02/19/post-about-nothing-or-how-i-learned-to.html</guid>
        </item>
      
    
      
        <item>
          <title>Specs, Bugs &amp; Rock’n’Roll</title>
          <description>&lt;p&gt;&lt;a href=&quot;http://lh6.ggpht.com/_LV_l8kYLOwo/SZy5Zvf7RMI/AAAAAAAAAJ0/nJc_wP1-ijY/s1600-h/backfuture_l%5B6%5D.jpg&quot;&gt;&lt;img title=&quot;Great Scott!&quot; style=&quot;border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; margin: 0px 0px 10px 10px; border-right-width: 0px&quot; height=&quot;184&quot; alt=&quot;Great Scott!&quot; src=&quot;http://lh5.ggpht.com/_LV_l8kYLOwo/SZy5aLasW4I/AAAAAAAAAJ4/tea1QDS1lho/backfuture_l_thumb%5B4%5D.jpg?imgmax=800&quot; width=&quot;244&quot; align=&quot;right&quot; border=&quot;0&quot; /&gt;&lt;/a&gt; I have probably seen &lt;a href=&quot;http://www.imdb.com/title/tt0088763/&quot;&gt;Back to the Future&lt;/a&gt; over 100 times. It&amp;#8217;s one of my favourite movies, and it&amp;#8217;s also astonishing how a movie that I loved when I was seven years old has retained it&amp;#8217;s appeal for over twenty years - it&amp;#8217;s like it somehow evolved from a time-travel movie into a 50s/80s &lt;em&gt;period&lt;/em&gt; time-travel movie without ever really looking dated in between. (That, or I&amp;#8217;m just a huge nerd with an 80s obsession who never really grew up.)&lt;/p&gt;  &lt;p&gt;Anyway, you know the scene where Marty&amp;#8217;s playing at his parents&amp;#8217; high-school dance, and he&amp;#8217;s about to leave, and the band ask if he&amp;#8217;ll play one more &amp;#8211; &amp;#8220;c&amp;#8217;mon, something that really &lt;em&gt;cooks&lt;/em&gt;&amp;#8221; &amp;#8211; and Marty steps up to the mic, jokes about &amp;#8220;this one&amp;#8217;s an oldie&amp;#8230; at least, it&amp;#8217;s an oldie where I come from&amp;#8221; &amp;#8211; and then he plays a storming version of Johnny B. Goode, and invents rock&amp;#8217;n&amp;#8217;roll? Well, as a kid watching this movie over and over, that&amp;#8217;s one of the things that never, ever made any sense to me. Time travel, fine &amp;#8211; I mean, they have a nuclear DeLorean, right? &amp;#8211; but the band jamming along, in perfect time, to a song they&amp;#8217;d never, ever heard before? &lt;a href=&quot;http://www.imdb.com/title/tt0093779/quotes&quot;&gt;Inconceivable&lt;/a&gt;!&lt;/p&gt;  &lt;p&gt;A couple of years later I took up the guitar, probably inspired in no small part by Marty McFly, and along the way I started picking up odd bits of music theory. I remember watching&amp;#160; Back to the Future one day around this time, and noticing something I&amp;#8217;d never noticed before&amp;#8230; just before Marty starts his whole Johnny B. Goode riff, he says to the band &amp;#8220;Ok, guys, this is a blues riff in B; watch me for the changes, and try to keep up, OK?&amp;#8221; Right up until that point, I&amp;#8217;d been teaching myself guitar, on my own, by playing along to tapes and stuff. I&amp;#8217;d never been in a band and never really taken any formal music classes. I had some music theory books, and I knew vaguely what a &amp;quot;blues riff&amp;quot; was, but I&amp;#8217;d never stopped to think why that &lt;em&gt;particular&lt;/em&gt; chord sequence had a special name. See, when you&amp;#8217;re playing guitar by yourself in your bedroom, you &lt;strong&gt;don&amp;#8217;t need to communicate&lt;/strong&gt; with other musicians, so a lot of things &amp;#8211; sheet music, sight reading, key changes and music theory &amp;#8211; just seem like a complete waste of time. Of course, to seasoned pros like the musicians in Marvin Berry&amp;#8217;s Starlighters, those two simple bits of information - &amp;#8220;&lt;a href=&quot;http://en.wikipedia.org/wiki/Twelve_bar_blues&quot;&gt;blues riff&lt;/a&gt;&amp;#8221; and &amp;#8220;in B&amp;#8221; &amp;#8211; tell them &lt;em&gt;exactly&lt;/em&gt; what&amp;#8217;s about to happen &amp;#8211; at least, in enough detail that they can join in at the right point, hit the right notes, and work the rest out as they go along.&lt;/p&gt;  &lt;p&gt;What&amp;#8217;s this got to do with software? Well, &lt;em&gt;blues riff&lt;strong&gt; &lt;/strong&gt;&lt;/em&gt;refers to a 12-bar blues chord progression, which is a &lt;em&gt;pattern - &lt;/em&gt;in both senses of the word. Trivially, it&amp;#8217;s a repeating sequence of things; in this case musical chords. It&amp;#8217;s also a pattern in the software/architecture sense - a recurring &amp;#8216;solution&amp;#8217; that&amp;#8217;s implemented in many different contexts. Whilst the implementation details may vary wildly from one implementation to the next, the underlying structure doesn&amp;#8217;t. Whether it&amp;#8217;s jazz, thrash, funk or fusion, you&amp;#8217;ll find twelve-bar patterns cropping up all over the place in contemporary music, and &lt;/p&gt;  &lt;p&gt;This is why design patterns are important in software development. Knowing them doesn&amp;#8217;t necessarily make you a better programmer. Just as someone can play a wonderful tune without knowing what all the notes are called, it&amp;#8217;s quite possible to implement design patterns without being aware that&amp;#8217;s what you&amp;#8217;re doing. I built some code years ago that used an &amp;#8216;active record&amp;#8217; design.&amp;#160; At the time, I had never worked with design patterns. I had no idea that my &amp;#8220;objects-based-on-tables&amp;#8221; solution had a name, or that anyone else was doing the same thing. Without that shared vocabulary, though, collaborating on that code was &lt;em&gt;painful&lt;/em&gt;. I had to explain that code line-by-line, explain what it does, how it works, how all the pieces fit together. It&amp;#8217;s time-consuming, it&amp;#8217;s error-prone, and it&amp;#8217;s probably really, really boring for the person listening to the explanation.&lt;/p&gt;  &lt;p&gt;Imagine Marty McFly turning to the band and saying &amp;#8220;OK, guys, this song has this chord here (&lt;em&gt;plays a B chord)&lt;/em&gt; for four bars, then two bars of a this one (&lt;em&gt;plays an A chord) &amp;#8211; &lt;/em&gt;got that? &amp;#8211; and then two bars of that first chord again&amp;#8230;&amp;#8221; and so on for about ten minutes; and the band are working it out and making notes as he goes along, so they can remember what goes where, and by the time he&amp;#8217;s done, the crowd&amp;#8217;s got bored and lost interest, Lorraine&amp;#8217;s gone home with Biff, and George is out in the parking lot crying quietly into a copy of &lt;em&gt;Amazing Stories&lt;/em&gt;. &lt;/p&gt;  &lt;p&gt;On the other hand, you can learn the patterns. Not because they&amp;#8217;ll make you a better coder, but because they&amp;#8217;ll transform your ability to communicate with other pattern-literate developers. You&amp;#8217;ll learn them, and you&amp;#8217;ll practice them at home, on your own little projects, and then when Lead Architect Martin McFowler turns round at the start of your next project and says &amp;#8220;OK, this is a &lt;a href=&quot;http://martinfowler.com/eaaCatalog/domainModel.html&quot;&gt;domain model&lt;/a&gt; with data access via a &lt;a href=&quot;http://martinfowler.com/eaaCatalog/repository.html&quot;&gt;repository&lt;/a&gt;, we&amp;#8217;ll manage references via an &lt;a href=&quot;http://martinfowler.com/eaaCatalog/identityMap.html&quot;&gt;identity map&lt;/a&gt; and use &lt;a href=&quot;http://martinfowler.com/eaaCatalog/concreteTableInheritance.html&quot;&gt;concrete table inheritance&lt;/a&gt; to map the subclasses&amp;#8221;, you can sit down, work out how to apply those patterns to your particular project, and &lt;em&gt;&lt;strong&gt;bam!&lt;/strong&gt;&lt;/em&gt; &amp;#8211; you&amp;#8217;ve just invented rock&amp;#8217;n&amp;#8217;roll.&lt;/p&gt;  &lt;p&gt;&lt;img style=&quot;display: block; float: none; margin-left: auto; margin-right: auto&quot; alt=&quot;http://www.seeing-stars.com/Locations/BTTF/Dance-JohnnyBGoode(smaller).JPG&quot; src=&quot;http://www.seeing-stars.com/Locations/BTTF/Dance-JohnnyBGoode%28smaller%29.JPG&quot; /&gt;&lt;/p&gt;  </description>
          <pubDate>2009-02-19T01:44:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/02/19/specs-bugs-rocknroll.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/02/19/specs-bugs-rocknroll.html</guid>
        </item>
      
    
      
        <item>
          <title>OCTV: Coming Soon to a Screen Near You?</title>
          <description>&lt;p&gt;Video surveillance is in the news again; the &lt;a href=&quot;http://www.guardian.co.uk/uk/2009/feb/14/pub-islington-cctv&quot;&gt;police insisted&lt;/a&gt; that an Islington pub landlord install CCTV cameras as a condition of granting his license application &amp;#8211; and the Information Commissioner subsequently pointed out that &lt;a href=&quot;http://www.theregister.co.uk/2009/02/17/met_cctv/&quot;&gt;they&amp;#8217;re not really allowed to do that&lt;/a&gt;, and now the usual suspects are weighing in on both sides of the same old privacy vs. security debate.&amp;#160; I personally have two contrasting experiences with video surveillance, that make me wonder whether we&amp;#8217;re getting this whole thing completely backwards and missing a great opportunity here.&lt;/p&gt;  &lt;p&gt;&lt;a title=&quot;Photo &amp;#169; jodi.martorell via Flickr, used under Creative Commons license.&quot; href=&quot;http://www.flickr.com/photos/12614773@N07/2417532356/&quot;&gt;&lt;img title=&quot;Photo &amp;#169; jodi.martorell via Flickr, used under Creative Commons license.&quot; style=&quot;border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; margin: 0px 0px 20px 20px; border-right-width: 0px&quot; height=&quot;162&quot; alt=&quot;Photo &amp;#169; jodi.martorell via Flickr, used under Creative Commons license.&quot; src=&quot;http://lh4.ggpht.com/_LV_l8kYLOwo/SZysWiJNryI/AAAAAAAAAJo/MMHH7zaz6mY/2417532356_fe56a28983%5B13%5D.jpg?imgmax=800&quot; width=&quot;242&quot; align=&quot;right&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;Several years ago, a friend and I were in a pub in central London, and her phone vanished. One moment it was out on the table in front of us; a few minutes later, it was gone &amp;#8211; nowhere to be found. We hadn&amp;#8217;t left the table, but at some point either it got knocked onto the floor and someone picked it up, or some light-fingered individual just grabbed it whilst we were looking the other way. This happened in a well-lit bar, full of witnesses - and &lt;strong&gt;right underneath a CCTV camera&lt;/strong&gt;. It didn&amp;#8217;t do the slightest bit of good. Nobody saw anything &amp;#8211; or if they did, they weren&amp;#8217;t telling. The CCTV tapes would have shown clearly exactly what happened, but the bar staff point-blank refused to let us see the footage, citing the usual data protection excuses.&lt;sup&gt;1&lt;/sup&gt;&lt;/p&gt;  &lt;p&gt;As a counter-example, when I commuted from Southampton to Winchester years ago, the drive home took anywhere from 20 minutes to 2 hours, depending on traffic &amp;#8211; so I used to check out the BBC&amp;#8217;s &lt;a href=&quot;http://www.bbc.co.uk/hampshire/in_pictures/webcams/traffic_webcams/&quot;&gt;traffic webcams&lt;/a&gt; before leaving. Even a blurry 320x240 picture that&amp;#8217;s five minutes old was good enough to see whether there was a tailback on the M3 or not &amp;#8211; sure, it meant once in a while I&amp;#8217;d sit at work for an extra hour or two waiting for the traffic to clear, but that was way better than spending that time sat in the traffic jam itself.&lt;/p&gt;  &lt;p&gt;Personally, I don&amp;#8217;t really object to video surveillance in public places. You&amp;#8217;re in a public place, your behaviour and actions can directly affect the people around you. If you&amp;#8217;re doing something that you&amp;#8217;d rather wasn&amp;#8217;t captured on videotape, then I&amp;#8217;d probably rather you weren&amp;#8217;t doing it in the same pub as me. The problem is that in most cases the only people who can review the resulting footage are the CCTV owners and the police - and whether you question their motives or not, they clearly&amp;#160; have better things to do. The problem isn&amp;#8217;t the TV bit of CCTV &amp;#8211; it&amp;#8217;s the CC. Closing that circuit creates an imbalance of power and feeds an &amp;#8220;us and them&amp;#8221; mentality that really doesn&amp;#8217;t do anybody any favours. &lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh4.ggpht.com/_LV_l8kYLOwo/SZysXMlj73I/AAAAAAAAAJs/lQd_WSDs938/s1600-h/mumsmile%5B3%5D.jpg&quot;&gt;&lt;img title=&quot;mumsmile&quot; style=&quot;border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; margin: 0px 20px 20px 0px; border-right-width: 0px&quot; height=&quot;228&quot; alt=&quot;mumsmile&quot; src=&quot;http://lh6.ggpht.com/_LV_l8kYLOwo/SZysXcMkQgI/AAAAAAAAAJw/79YV7-FUUk4/mumsmile_thumb%5B1%5D.jpg?imgmax=800&quot; width=&quot;172&quot; align=&quot;left&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;So what if we turned the whole thing upside down? Instead of CCTV, let&amp;#8217;s put up signs saying &amp;#8220;This area is covered by &lt;strong&gt;open circuit television&lt;/strong&gt;&amp;#8221; &amp;#8211; and websites where you, or anyone else, can see the footage? Suddenly, all that information is accessible by somebody who &lt;strong&gt;actually cares enough to look at it &lt;/strong&gt;&amp;#8211; i.e. &lt;em&gt;you&lt;/em&gt;. Even if we&amp;#8217;re just dealing with real-time feeds, you can pull up an OCTV feed and see if any of your friends in the pub yet, or whether it&amp;#8217;s raining in Camden right now, or how big the queue is outside the cinema. &lt;/p&gt;  &lt;p&gt;What about if we made the archived footage available? 24 hours worth would let you pull up the day&amp;#8217;s coverage of the street outside your house &amp;#8211; so you can &lt;strong&gt;&lt;em&gt;prove &lt;/em&gt;&lt;/strong&gt;that the phone company are lying to you about the engineer who they claim &amp;#8220;waited outside for an hour&amp;#8221;.&amp;#160; You can see who &amp;#8211; or what - caused that scratch on the side of your car. A weeks&amp;#8217; worth of archives could show you how many people are out &amp;amp; about on the streets in that neighbourhood where you&amp;#8217;re considering buying a house. A year&amp;#8217;s worth will show you how many sunny days they&amp;#8217;ve had, and how busy it gets when there&amp;#8217;s a football game on up the road, and how often the council &amp;#8216;forget&amp;#8217; to pick up the recycling.&lt;/p&gt;  &lt;p&gt;The Metropolitan Police don&amp;#8217;t have time to find CCTV evidence for every stolen car or domestic burglary that takes place&amp;#8230; because there are too many crimes, too much footage, and not enough surveillance officers. So share it. A police officer can&amp;#8217;t justify spending seven hours reviewing footage of a single car theft&amp;#8230; but what if it was &lt;em&gt;your&lt;/em&gt; car? Would you be prepared to put in those hours? Would your insurance company? &lt;/p&gt;  &lt;p&gt;OK, it&amp;#8217;s not quite that simple. It&amp;#8217;ll make extramarital affairs, gambling problems and skiving off work a bit difficult&amp;#8230; sorry about that. On a more serious note, it&amp;#8217;ll bring things out into the open that some people would rather stayed hidden&amp;#8230; your employer can see where you spend your evenings, your family can see where you &lt;em&gt;really &lt;/em&gt;work. Technology&amp;#8217;s never going to solve that kind of problem, though - unless by &amp;#8220;solve it&amp;#8221; you mean &amp;#8220;drag it out into the open and mess up everybody&amp;#8217;s lives until they learn how to deal with the truth&amp;#8221;. &lt;/p&gt;  &lt;p&gt;The technology&amp;#8217;s not quite there yet, but I think it&amp;#8217;s probably pretty close. Solar-powered webcams with built-in GPS and 16Gb of online storage, IPv6 wi-fi connectivity, capturing a HD image every few seconds and uploading the results to some big store in that cloud thingy that all the cool kids are talking about&amp;#8230; not cheap (yet), but by no means impossible &amp;#8211; and technology just keeps getting cheaper. Our capacity to record and store information is increasing exponentially, and I really don&amp;#8217;t think that&amp;#8217;s going to stop. When that information ends up locked in vaults and government servers, it&amp;#8217;s basically useless &amp;#8211; the cost of retrieval and analysis is prohibitive, and short of serious investigations or legal proceedings, that data never comes out again. The internet has demonstrated time and time again that if you share your stuff &amp;#8211; images, stories, &lt;em&gt;information&lt;/em&gt; &amp;#8211; then people will find &lt;a href=&quot;http://www.nihilogic.dk/labs/wolfenflickr/&quot;&gt;remarkable&lt;/a&gt;, &lt;a href=&quot;http://www.typobuddy.com/&quot;&gt;innovative&lt;/a&gt;, &lt;a href=&quot;http://joblighted.com/&quot;&gt;practical&lt;/a&gt; and &lt;a href=&quot;http://www.doodlebuzz.com/&quot;&gt;beautiful&lt;/a&gt; things to do with it &amp;#8211; things you&amp;#8217;d never have imagined. To a government, company or police force, picking valuable data out of literally millions of digital images is an expensive and daunting prospect&amp;#8230; but on the web, it&amp;#8217;s the kind of thing people do all day, every day, &lt;em&gt;for fun&lt;/em&gt;. So what is everyone so afraid of?&lt;/p&gt;  &lt;p&gt;&lt;small&gt;1. I discovered recently &amp;#8211; far too late to be of any use &amp;#8211; that we could have made a formal request under the Data Protection Act, in writing, and paid the &amp;#163;10 administration charge, and received a copy of any footage that we appeared in. Oh well. I know for next time.&lt;/small&gt;&lt;/p&gt;  &lt;p&gt;&lt;small&gt;Banksy photograph &amp;#169; &lt;a href=&quot;http://www.flickr.com/photos/12614773@N07/2417532356/&quot;&gt;jodi.martorell via Flickr&lt;/a&gt;, used under Creative Commons license.&lt;/small&gt;&lt;/p&gt;  </description>
          <pubDate>2009-02-19T00:48:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/02/19/octv-coming-soon-to-screen-near-you.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/02/19/octv-coming-soon-to-screen-near-you.html</guid>
        </item>
      
    
      
        <item>
          <title>SQL Server 2005 Performance Dashboard</title>
          <description>&lt;p&gt;One of those quick &amp;quot;this helped me, it might help you&amp;quot; posts. I&apos;ve been trying to run &lt;a href=&quot;http://www.microsoft.com/downloads/details.aspx?FamilyId=1d3a4a0d-7e0c-4730-8204-e419218c1efc&amp;amp;displaylang=en&quot;&gt;SQL Server 2005 Performance Dashboard&lt;/a&gt; on one of our live servers, and hit a couple of snags along the way.&amp;#160; &lt;/p&gt;  &lt;p&gt;Having installed the dashboard reports and run the setup.sql script included with the download, I tried to view the report on one of our databases and got the following error:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;strong&gt;Error: &lt;/strong&gt;Index (zero based) must be greater than or equal to zero and less than the size of the argument list&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Whoa. Bizarre - and rather unhelpful - error message. Clearly this Performance Dashboard thing hasn&apos;t been tested at &lt;em&gt;all&lt;/em&gt;... and then it occurred to me that I was using SQL2008 client tools to talk to a SQL2005 server, but that the actual catalog (database) was still running in SQL2000 compatibility mode. &lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh4.ggpht.com/_LV_l8kYLOwo/SZHC9wDzUmI/AAAAAAAAAJc/aYY_ObEwlCc/s1600-h/image%5B16%5D.png&quot;&gt;&lt;img style=&quot;border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px&quot; height=&quot;480&quot; alt=&quot;image&quot; src=&quot;http://lh4.ggpht.com/_LV_l8kYLOwo/SZHC-ObaklI/AAAAAAAAAJg/hVLAyGy7HKA/image_thumb%5B8%5D.png?imgmax=800&quot; width=&quot;535&quot; border=&quot;0&quot; /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;Trying &lt;em&gt;exactly the same thing &lt;/em&gt;(right-click the database, Reports, performance_dashboard_main) using SQL Server 2005 Management Studio (instead of 2008 - same server, different version of the client tools) produces a far more helpful error message:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;strong&gt;Error: &lt;/strong&gt;Unable to display report because the database has a compatibility level of 80. To view this report, you need to use the Database Properties dialog to change the compatibility level to SQL Server 2005 (90).&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;So - over to the test server, bring up a test instance of the database, and change the compatibility level to 90. Database and apps still appear to be running fine, but trying to run the performance dashboard now produced the following error:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;strong&gt;Error: &lt;/strong&gt;Difference of two datetime columns caused overflow at runtime.&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;This time &lt;a href=&quot;http://blogs.msdn.com/sqlrem/archive/2007/03/07/Performance-Dashboard-Reports-Now-Available.aspx&quot;&gt;Google had the answer&lt;/a&gt; (thanks David) although it&apos;s not immediately clear what the solution is referring to. What you need to do is open the Setup.SQL script (which is installed along with the performance dashboard - you&apos;ll find it at C:\Program Files\Microsoft SQL Server\90\Tools\PerformanceDashboard\). Find line 276, which says:&lt;/p&gt;  &lt;pre&gt;sum(convert(bigint, datediff(ms, login_time, getdate()))) - sum(convert(bigint, s.total_elapsed_time)) as idle_connection_time, &lt;/pre&gt;

&lt;p&gt;and &lt;strong&gt;replace this line&lt;/strong&gt; with&lt;/p&gt;

&lt;pre&gt;sum(convert(bigint, CAST ( DATEDIFF ( minute, login_time, getdate()) AS BIGINT)*60000 + DATEDIFF ( millisecond, DATEADD ( minute, DATEDIFF ( minute, login_time, getdate() ), login_time ),getdate() ))) - sum(convert(bigint, s.total_elapsed_time)) as idle_connection_time,&lt;/pre&gt;

&lt;p&gt;As David explains on &lt;a href=&quot;http://blogs.msdn.com/sqlrem/archive/2007/03/07/Performance-Dashboard-Reports-Now-Available.aspx&quot;&gt;the linked thread&lt;/a&gt;, DATEDIFF is returning an int (here, it&apos;s calculating the number of milliseconds between two DATETIME instances) - so any connection that&apos;s been active for longer than ~24 days will overflow an INT when you try and convert the connection time to milliseconds.&lt;/p&gt;

&lt;p&gt;Anyway, change that line, run setup.sql again, and it works - acres of lovely statistical goodness at my fingertips:&lt;/p&gt;

&lt;p&gt;&lt;img style=&quot;border-right: 0px; border-top: 0px; margin: 10px 0px; border-left: 0px; border-bottom: 0px&quot; height=&quot;480&quot; alt=&quot;image&quot; src=&quot;http://lh6.ggpht.com/_LV_l8kYLOwo/SZHC-pvClzI/AAAAAAAAAJk/x4P0z-ysmgE/image%5B23%5D.png?imgmax=800&quot; width=&quot;603&quot; border=&quot;0&quot; /&gt;&amp;#160;&lt;/p&gt;

&lt;p&gt;Next step - make sure the SQL 2005 compatibility hasn&apos;t broken anything, then modify the compatibility level on the live server, and &lt;em&gt;then &lt;/em&gt;I&apos;ll be able to get some real live performance stats based on actual web traffic, which should make for interesting reading.&lt;/p&gt;  </description>
          <pubDate>2009-02-10T18:10:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/02/10/sql-server-2005-performance-dashboard.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/02/10/sql-server-2005-performance-dashboard.html</guid>
        </item>
      
    
      
        <item>
          <title>Videos from SkillsMatter Open Source .NET Exchange</title>
          <description>&lt;p&gt;Reviews and videos from the &lt;a href=&quot;http://skillsmatter.com/event/open-source-dot-net/open-source-dot-net-exchange&quot;&gt;SkillsMatter Open Source .NET event&lt;/a&gt; last month, including &lt;a href=&quot;http://skillsmatter.com/podcast/open-source-dot-net/jquery-349&quot;&gt;my jQuery lecture&lt;/a&gt;, are now online at the &lt;a href=&quot;http://skillsmatter.com/event/open-source-dot-net/open-source-dot-net-exchange&quot;&gt;SkillsMatter&lt;/a&gt; site.&lt;/p&gt;  &lt;p&gt;(Note to self: Don’t say “um” so much next time.)&lt;/p&gt;  </description>
          <pubDate>2009-02-08T13:36:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/02/08/videos-from-skillsmatter-open-source.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/02/08/videos-from-skillsmatter-open-source.html</guid>
        </item>
      
    
      
        <item>
          <title>Is a Crisp a Value Object?</title>
          <description>&lt;p&gt;I’m sat in a railway station in the French Alps, waiting for the TGV to Paris, with nothing much to do except eat crisps and watch &lt;a href=&quot;http://www.youtube.com/watch?v=xY69p53VA6M&quot;&gt;Oggy and the Cockroaches&lt;/a&gt; on French TV. They say good writing draws inspiration from the world around it, so – because Oggy and the Cockroaches defies all rational discussion – let’s talk about crisps. Ed left a comment on my “&lt;a href=&quot;http://dylanbeattie.blogspot.com/2009/01/story-of-lazy-loading-lunchbox.html&quot;&gt;lazy loading lunchbox&lt;/a&gt;” post that got me thinking about the semantics of value objects – what exactly is a value object, and what role would it play in the whole rucksack/lunchbox scenario.&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;Ruck sack = Aggregate Root. RuckSackRepository is the only mechanism for retrieving the object graph. The CrispBag is an Entity and of course has no repository. The Crisp Bag is just an array / collection of Crisps, which are in turn Value Objects.&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;&lt;img title=&quot;Mmm. Cajun Squirrel.&quot; style=&quot;border-right: 0px; border-top: 0px; display: inline; margin: 0px 0px 10px 10px; border-left: 0px; border-bottom: 0px&quot; height=&quot;161&quot; alt=&quot;Mmm. Cajun Squirrel.&quot; src=&quot;http://lh5.ggpht.com/_LV_l8kYLOwo/SY7fDkJYCUI/AAAAAAAAAJY/Jckq3oj9Sy8/crisps%5B13%5D.jpg?imgmax=800&quot; width=&quot;242&quot; align=&quot;right&quot; border=&quot;0&quot; /&gt;On careful reflection, I don&apos;t think a crisp is a value object – at least, not in the playground scenario I was dealing with - because I&apos;d argue that every crisp has a distinct identity and lifecycle. Don&apos;t believe me? Here, have some crisps. No, it&apos;s OK, go on, have the whole bag... &amp;lt;crunch, crunch, crunch&amp;gt; Now that you&apos;re happily tucking into your crisps - guess what? &lt;em&gt;Colin licked one of those crisps earlier when you weren&apos;t looking.&lt;/em&gt; Suddenly the question of &amp;quot;which crisp?&amp;quot; becomes extremely important. Is the contaminated trouser-crisp one of the handful left safely in the bag - or have &lt;em&gt;you already eaten it&lt;/em&gt;? If we model crisps as value objects, we have no way of telling. We can tell how many crisps are left, sure, but we can’t distinguish between them, and we can’t model any operation that would affect the state of a single crisp whilst leaving&amp;#160; the other crisps unaffected. &lt;/p&gt;  &lt;p&gt;OK, so if a crisp isn’t a value object, then what is? I think that’s a much harder question to address, because as with so many patterns, the answer &lt;em&gt;depends on the scenario you’re modelling&lt;/em&gt;. &lt;/p&gt;  &lt;p&gt;The &lt;a href=&quot;http://c2.com/cgi/wiki?ValueObject&quot;&gt;value object&lt;/a&gt; pattern is typically used to model quantities, descriptions and measurements, where two objects can be considered equal if they have the same value (state). Two classic examples are dates and money. In most domain models, “January 2nd, 1978” will always refer to exactly the same thing – a particular 24-hour period sometime in the late 1970s – so you can model your date/time fields as value objects (remembering to include time zone information if necessary). If you and I are both born on 2nd January 1978, then it’s valid to assume we have the same birthday, because that date can &lt;em&gt;only refer to one thing.&lt;/em&gt; (By way of comparison, if your father and my father are both called Dave Beattie, we’re not necessarily related, because there could be any number of people with that name in our domain model) &lt;/p&gt;  &lt;p&gt;Money is interesting, because &lt;strong&gt;money is a value object &lt;em&gt;by law&lt;/em&gt;&lt;/strong&gt; in most economies - that&apos;s how it&apos;s treated in business and in court. In legal terms, this is known as &lt;a href=&quot;http://en.wikipedia.org/wiki/Fungibility&quot;&gt;fungibility&lt;/a&gt;. Point is, when we ask “does this credit equal that debt?”, we’re only interested in the amount. You can repay a £10 debt with &lt;strong&gt;any &lt;/strong&gt;ten pounds as long as the numbers add up; you don&apos;t need to track the specific ten-pound-note that was originally borrowed. When you deposit £50 at the bank, you can&apos;t go back a week later and ask if &lt;em&gt;that particular&lt;/em&gt; fifty pounds is still there - as soon as you deposit it, it becomes part of your account balance and ceases to exist as £50 in its own right. &lt;/p&gt;  &lt;p&gt;Remember, though, that value objects are just one modelling pattern, and you need to be sure that you’ve chosen the right pattern for your particular scenario. If you&apos;re writing a system for the CIA to track marked dollar bills, you&apos;ll need to model those bills as entities so you can track individual bills (e.g. using their serial number). Point is, when you do this, you&apos;re &lt;strong&gt;not treating them as currency, you&apos;re treating them as artifacts&lt;/strong&gt;.&lt;strong&gt;&amp;#160;&lt;/strong&gt;The same would apply to an antiques house dealing in rare coins - it&apos;s not the face value of the coins that matters, it&apos;s their age, rarity and historical significance that&apos;s important. If you’re writing a game where the player can travel through an infinite number of parallel universes, you might need to track an infinite number of &lt;em&gt;different instances &lt;/em&gt;of “January 12th, 1978”.&amp;#160;&amp;#160; &lt;/p&gt;  &lt;p&gt;If it’s not clear how to model a particular element in your model, try asking “which one?” If the question makes sense within your own scenario:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;Me: “Colin licked one of those crisps!”      &lt;br /&gt;You : “Which one?”&lt;/p&gt;    &lt;p&gt;Me: “Hey, says here this house was once owned by Chris Columbus!”      &lt;br /&gt;You : “&lt;a href=&quot;http://en.wikipedia.org/wiki/Chris_Columbus&quot;&gt;Which one&lt;/a&gt;?”&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;- then you’re probably dealing with entities. If the question “which one” is meaningless &lt;strong&gt;in the context of your domain&lt;/strong&gt;:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;Me: “I only paid £1 in income tax last year!”      &lt;br /&gt;You: “Oh yeah? Which one?”&lt;/p&gt;    &lt;p&gt;Me: “I was born on January 12th, 1978”      &lt;br /&gt;You: “Really? Which one?”&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;- then you’re probably better off modelling the subject of the question as a value object.&lt;/p&gt;  </description>
          <pubDate>2009-02-07T13:29:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/02/07/is-crisp-value-object.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/02/07/is-crisp-value-object.html</guid>
        </item>
      
    
      
        <item>
          <title>jQuery at WebTech Exchange 2009</title>
          <description>&lt;p&gt;So, just got back from doing my jQuery bit at the SkillsMatter Open Source .NET Exchange, which was great fun. Thanks to Gojko and SkillsMatter for putting the whole thing together, to the other speakers for their excellent sessions, and to everyone who came up to me afterwards with feedback and questions; it’s really inspiring to see so many people giving up their own time to learn about writing better software. &lt;/p&gt;  &lt;p&gt;I’ve been asked to do a full session at the &lt;a href=&quot;http://skillsmatter.com/event/soa-rest/webtech-exchange-2009&quot;&gt;WebTech Exchange conference&lt;/a&gt; in May; it’ll be talking about jQuery in one form or another, but I thought I’d throw the exact topic open to see if there’s anything people particularly want to see. A couple of ideas I’m already playing around with:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;A detailed analysis of how jQuery actually works. How have they actually implemented the selectors, live events, event handler syntax, fluent method chaining and that all-powerful jQuery object? (which, by the way, is a functional object that behaves like an array, in case you were wondering…)&lt;/li&gt;    &lt;li&gt;Implementing a jQuery plug end-to-end – starting with the basics of jQuery’s plugin architecture and actually implementing a full jQuery plug-in module from scratch during the session.&lt;/li&gt;    &lt;li&gt;A “top 10 plug-ins” – showcasing some of the third-party modules that really show off the extensibility and power of the jQuery framework, and how you can mix&apos; and match modules within your own pages to deliver amazingly rich UI experiences with very little code.&lt;/li&gt;    &lt;li&gt;What’s new in jQuery 1.3 – the new Sizzle selector engine, live events (which allow you to attach handlers to elements that don’t exist yet), the new .support API, and so on.&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Any other ideas you think might be worth a look – either leave a comment on here, e-mail me on &lt;a href=&quot;mailto:dylan@dylanbeattie.net&quot;&gt;dylan@dylanbeattie.net&lt;/a&gt;, or you can find me on twitter as @dylanbeattie – and if you’re at all interested in progressive .NET and web technology, I really recommend you check out the conference programme. Hamilton “Hammett” Verissimo de Oliviera, project leader on the Castle Project, will be delivering the keynote and talking about plans for Castle 2.0, and there’s great sessions lined up on Prototype, Dojo, cloud computing. BBC iPlayer, REST and OpenRasta, and a whole lot more. Oh, and there’s an early-bird discount if you &lt;a href=&quot;http://skillsmatter.com/event/soa-rest/webtech-exchange-2009&quot;&gt;register before 28th February&lt;/a&gt;. &lt;/p&gt;  </description>
          <pubDate>2009-01-23T00:30:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/01/23/jquery-at-webtech-exchange-2009.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/01/23/jquery-at-webtech-exchange-2009.html</guid>
        </item>
      
    
      
        <item>
          <title>jQuery at SkillsMatter Open Source .NET Exchange</title>
          <description>&lt;p&gt;Here&apos;s the slides, sample code and links from my jQuery talk at the &lt;a href=&quot;http://skillsmatter.com/event/open-source-dot-net/open-source-dot-net-exchange&quot;&gt;SkillsMatter Open Source .NET Exchange&lt;/a&gt; here in London on 22nd January. &lt;/p&gt;  &lt;h2&gt;Downloads&lt;/h2&gt;  &lt;ul&gt;   &lt;li&gt;Slides : &lt;a href=&quot;http://www.dylanbeattie.net/jquery/jquery.pptx&quot;&gt;Powerpoint 2007&lt;/a&gt;, &lt;a href=&quot;http://www.dylanbeattie.net/jquery/jquery.pdf&quot;&gt;PDF&lt;/a&gt;, &lt;a href=&quot;http://www.dylanbeattie.net/jquery/jquery_slides.zip&quot;&gt;PNG&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;Code: &lt;a href=&quot;http://www.dylanbeattie.net/jquery/jquery_examples.zip&quot;&gt;jquery_examples.zip&lt;/a&gt; - requires Visual Studio 2008 and the &lt;a href=&quot;http://www.asp.net/mvc/&quot;&gt;ASP.NET MVC Beta&lt;/a&gt; &lt;/li&gt; &lt;/ul&gt;  &lt;h2&gt;Links&lt;/h2&gt;  &lt;p&gt;jQuery is available from &lt;a href=&quot;http://www.jquery.com&quot;&gt;www.jquery.com&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;jQuery Intellisense in Visual Studio 2008 requires &lt;a href=&quot;http://msdn.microsoft.com/en-us/vstudio/cc533448.aspx&quot;&gt;Service Pack 1 for Visual Studio 2008&lt;/a&gt; and VS2008 Patch &lt;a href=&quot;http://code.msdn.microsoft.com/KB958502/Release/ProjectReleases.aspx?ReleaseId=1736&quot;&gt;KB958502&lt;/a&gt;, which causes Visual Studio to check for the &amp;quot;-vsdoc.js&amp;quot; annotated Javascript file. More info on these is available from &lt;a href=&quot;http://weblogs.asp.net/scottgu/archive/2008/11/21/jquery-intellisense-in-vs-2008.aspx&quot;&gt;ScottGu&apos;s blog&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;You&apos;ll find up-to-date -vsdoc.js files, including support for jQuery 1.3, on &lt;a href=&quot;http://blogs.ipona.com/james/archive/2008/02/15/JQuery-IntelliSense-in-Visual-Studio-2008.aspx&quot;&gt;James Hart&apos;s blog&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;Since ASP.NET MVC is still in beta, your best bet for working code and examples integrating it with other libraries like jQuery is probably &lt;a href=&quot;http://stackoverflow.com/search?q=mvc+jquery&quot;&gt;Stack Overflow&lt;/a&gt;, which has a fairly active and enthusiastic ASP.NET MVC contingent.&lt;/p&gt;  </description>
          <pubDate>2009-01-22T01:25:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/01/22/jquery-at-skillsmatter-open-source-net.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/01/22/jquery-at-skillsmatter-open-source-net.html</guid>
        </item>
      
    
      
        <item>
          <title>The Story of the Lazy-Loading Lunchbox</title>
          <description>&lt;p&gt;&lt;a href=&quot;http://lh3.ggpht.com/_LV_l8kYLOwo/SW058_8qNcI/AAAAAAAAAIM/GZW7y6k6tdM/s1600-h/baby_dylan%5B4%5D.jpg&quot;&gt;&lt;img style=&quot;border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; margin: 0px 0px 10px 10px; border-right-width: 0px&quot; height=&quot;240&quot; alt=&quot;This is me just after I got my Amstrad 6128&quot; src=&quot;http://lh3.ggpht.com/_LV_l8kYLOwo/SW059PCNOVI/AAAAAAAAAIQ/utQ0W8lHoS0/baby_dylan_thumb%5B2%5D.jpg?imgmax=800&quot; width=&quot;178&quot; align=&quot;right&quot; border=&quot;0&quot; /&gt;&lt;/a&gt; Many years ago when I was a lot smaller and still thought &lt;strong&gt;20 GOTO 10 &lt;/strong&gt;was pretty cool, my long-suffering mother would pack me off to school every day, with a lovingly-packed rucksack containing my homework, gym kit, packed lunch, pencil case and various other educational essentials. At lunchtime, all the packed-lunch kids would go outside and play at being spies and Thundercats until the sandwich-bell rang, which meant it was time to go inside and eat. We&apos;d all troop into the dinner-hall and get out our plastic MASK and He-Man lunchboxes and unwrap our squashed-looking sandwiches and warm bananas and little plastic Thermos flasks of Ribena. Ham sandwiches, jam sandwiches, or - for special occasions - ham &lt;strong&gt;and&lt;/strong&gt; jam, all the better for spending three hours in a warm airtight plastic box alongside a banana.  &lt;/p&gt;  &lt;p&gt;Once in a while, something would liven up the lunch break. I&apos;ll never forget what happened when Jeremy&apos;s parents went on holiday and left the babysitter in charge. Next day, Jeremy opened his lunchbox and instead of sandwiches, there was a pizza menu, and a £10 note in there. He had to ask for special permission to use the phone in the office... good thing Chris had an older sister who worked at Speedy Pizza otherwise I don&apos;t think they&apos;d have believed us. Next day the headmistress shouted at everyone in assembly,  and there were letters sent home to parents. (My dad laughed about it all weekend.)&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh6.ggpht.com/_LV_l8kYLOwo/SW059pPiZWI/AAAAAAAAAIU/7G5zSEZ6reM/s1600-h/lunchboxheman%5B7%5D.jpg&quot;&gt;&lt;img style=&quot;border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; margin: 10px 20px 10px 0px; border-right-width: 0px&quot; height=&quot;158&quot; alt=&quot;He-Man Lunchbox from www.retrojunk.com&quot; src=&quot;http://lh3.ggpht.com/_LV_l8kYLOwo/SW05-PCNiVI/AAAAAAAAAIY/VgMw_FmIWJw/lunchboxheman_thumb%5B5%5D.jpg?imgmax=800&quot; width=&quot;240&quot; align=&quot;left&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;Then Jim joined our class. I happened to be &lt;strong&gt;lunch monitor &lt;/strong&gt;on his first day, which meant I got to hand out all the lunchboxes when the sandwich bell rang. As I lined them up to hand out, I realized that Jim&apos;s was &lt;em&gt;empty&lt;/em&gt;.  Light as a feather, no noise when you shook it - clearly there was nothing inside. Poor kid, I thought, forgetting his packed lunch on his very first day! I figured I could give him half of my sandwich, and if Chris had bacon crisps he&apos;d probably give them to Jim &apos;cos Chris hated bacon crisps... it was OK, I told myself. We&apos;d work something out.&lt;/p&gt;  &lt;p&gt;Then Jim sat down with his empty lunchbox... and before we knew it, he was tucking into a full three-course roast lunch, piping hot - with gravy, and a glass of milk, and &lt;em&gt;ice-cream for afters&lt;/em&gt;. We sat around with envious eyes, eating our banana-scented ham sandwiches, aware that something funny was clearly going on (&lt;em&gt;&quot;why doesn&apos;t it melt?&quot;&lt;/em&gt;) Next day - same thing. On Fridays, he&apos;d get roast pork and dumplings (his favourite, apparently). We asked Jim one day how he did this, but he didn&apos;t know. &quot;My mum always does my lunchbox&quot;, he said, between mouthfuls of potatoes and gravy, and so the mystery went on. Finally, just before the end of term, Jim asked if I wanted to go over and play after school one afternoon.  I jumped at the chance - finally, I could ask his mum about those amazing packed lunches.&lt;/p&gt;  &lt;p&gt;The long-awaited day came, and after Jim and I were all done playing &lt;a title=&quot;Ghostbusters wins? That&apos;s not how *I* remember it...&quot; href=&quot;http://tweetfu.com/fights/81-thundercats-ghostbusters&quot;&gt;Thundercats vs. Ghostbusters&lt;/a&gt; in the garden, Jim&apos;s mum called us in for tea. We sat around their big dining table and we ate raspberry jelly and ice-cream, and eventually I plucked up the courage to ask about the lunches.&lt;/p&gt;  &lt;p&gt;&quot;Mrs. Fowler&quot;, I said politely (for that was her name), &quot;me&apos;n&apos;the other children were wondering about Jim&apos;s packed lunch, please... how come the ice-cream doesn&apos;t melt from being next to the roast potatoes all day? An&apos; how does Jim get all that food into such a tiny box?&quot;&lt;/p&gt;  &lt;p&gt;&quot;Oh, that&apos;s easy&quot;, she said. &quot;See, we bought Jim a &lt;strong&gt;lazy-loaded lunchbox&lt;/strong&gt;.  He&apos;s a growing lad, and he needs plenty to eat, but I think carrying all that food around all day would give him a bad back. Besides, most days he&apos;s off out the door before I&apos;ve even put the potatoes in the oven - and if he kept turning up late because I didn&apos;t have his packed lunch ready for him, I&apos;m sure his teacher would complain. So I get his lunch ready for him and keep it warm in the oven, and then when it&apos;s time to eat it, I just pop it into his lunchbox - easy peasy! Now, who&apos;d like more ice-cream?&quot;&lt;/p&gt;  &lt;p&gt;I went home even more confused than when I arrived. She must be winding me up, I thought - that&apos;s &lt;em&gt;impossible - &lt;/em&gt;but Jim&apos;s amazing lunches just kept on going. One by one, we all tried to get OUR mothers to do us roast-beef-and-ice-cream packed lunches, but it never worked - from the day Pete&apos;s bag dripped gravy and raspberry ripple all over the nature corner, to the day Rob&apos;s mum got in trouble for trying to break into the school kitchen, none of us ever got a three-course roast packed lunch except Jim. &lt;/p&gt;  &lt;h2&gt;Hang on... you&apos;re making this up!&lt;/h2&gt;  &lt;p&gt;OK, ok, busted. It&apos;s not a true story. Except the bit about Thundercats. And Chris getting gravy in the nature corner, come to think of it. But there&apos;s a couple of details in here that might help explain a few things if you&apos;re trying to get your head round OO architecture and enterprise design patterns.&lt;/p&gt;  &lt;h3&gt;Separation of Concerns - aka &quot;Why is a Eight-Year-Old Ordering Pizza?&quot;&lt;/h3&gt;  &lt;p&gt;The various parts of an object-oriented application should have their own distinct responsibilities, and should depend on each other for help when they need to do something outside their immediate remit. Web pages shouldn&apos;t send e-mail. Database queries shouldn&apos;t return HTML. When objects end up doing things they&apos;re not supposed to, that&apos;s a warning sign - &lt;strong&gt;just like an eight-year-old ordering a pizza&lt;/strong&gt;.&lt;/p&gt;  &lt;p&gt;If you&apos;re relying on a eight-year-old to arrange their own lunch, you&apos;re not separating your concerns properly. To make lunch, we need to go shopping, handle money - and get the Penguin bars from their secret hiding place on top of the fridge. Kids don&apos;t know where the food &lt;em&gt;comes&lt;/em&gt; from - and they shouldn&apos;t have to; if they were in charge of this, it would lead to all sorts of chaos.&lt;/p&gt;  &lt;p&gt;Kids have clearly defined responsibilities. They torment the guinea pig, throw up during maths, eat lunch, play Thundercats, have nap time and go home. They shouldn&apos;t be shopping, they shouldn&apos;t be cooking, and they get &lt;em&gt;really &lt;/em&gt;funny looks if they tell the teacher they&apos;re just popping out for falafel and a skinny latté. (Incidentally, the the teachers, Mr. Namespace and Mrs. Interface, understand all this, and the reason they don&apos;t let kids go out for falafel is that they&apos;re enforcing this separation of concerns.)&lt;/p&gt;  &lt;h3&gt;Aggregates and Repositories - aka &quot;Mum, Did You Pack My Gym Kit?&quot;&lt;/h3&gt;  &lt;p&gt;That rucksack that my mum would pack for me before sending me off for a day in the big wide world was an &lt;strong&gt;aggregate &lt;/strong&gt;- a top-level container, containing a whole collection of objects that make sense when you put them inside each other, as in the following example:&lt;/p&gt;  &lt;p&gt; &lt;a href=&quot;http://lh4.ggpht.com/_LV_l8kYLOwo/SW05-uUkIVI/AAAAAAAAAIc/undIyVDWKbU/s1600-h/image%5B19%5D.png&quot;&gt;&lt;img style=&quot;border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; margin: 0px; border-right-width: 0px&quot; height=&quot;229&quot; alt=&quot;Yeah? Well yo mama&apos;s so lazy she implements deferred execution!&quot; src=&quot;http://lh4.ggpht.com/_LV_l8kYLOwo/SW05-zZH3zI/AAAAAAAAAIg/sl0W2V_ESys/image_thumb%5B11%5D.png?imgmax=800&quot; width=&quot;459&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;   &lt;/p&gt;  &lt;p&gt;When I was growing up, kids didn&apos;t carry money, and they wouldn&apos;t let us out of school to go to the shops. I had no way of getting another pencil or a bag of crisps if I realized I&apos;d forgotten it halfway through the day. I was completely dependent on mum making sure all of these things were in my bag to start with.&lt;/p&gt;  &lt;p&gt;This is one of the distinguishing features of the repository, as opposed to just cherry-picking bits of data using ad-hoc queries. When using a repository, you are retrieving &lt;strong&gt;deep&lt;/strong&gt; &lt;strong&gt;object graphs. &lt;/strong&gt;Once I had &quot;retrieved&quot; my rucksack from the repository, I could assume that I also had all the rucksack contents - the lunchbox, the pencil tin, the curiosities and the yo-yo.  (The tantrums I&apos;d throw when I got to school and my yo-yo was missing were really just an early form of NullReferenceException.)&lt;/p&gt;  &lt;p&gt;So, assuming our repository pattern is returning nicely-populated object graphs, we now have a whole different set of problems.  &lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;strong&gt;Memory consumption. &lt;/strong&gt;I had to carry &lt;strong&gt;everything, all day - &lt;/strong&gt;and there was only so much space in the locker room at school. The day everyone brought their skateboards in, there wasn&apos;t enough space for them all. &lt;/li&gt;    &lt;li&gt;&lt;strong&gt;Long-running queries.  &lt;/strong&gt;If I&apos;d lost my gym shoes or we&apos;d forgotten to defrost any bread to make sandwiches, I&apos;d always end up being late for school. Even though I didn&apos;t need the gym shoes until after morning break and the bread until lunchtime, I couldn&apos;t leave home without them.  Some days it would rain and games would be cancelled and I wouldn&apos;t even &lt;em&gt;need&lt;/em&gt; the gym shoes in the end - I&apos;d just carry them around all day for no reason. &lt;/li&gt;    &lt;li&gt;&lt;strong&gt;Stale objects. &lt;/strong&gt;By the time I actually got around to eating lunch, it had been knocking around in my bag for a couple of hours and invariably smelled of bananas. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Obviously Jim&apos;s lazy-loading lunchbox - and presumably the lazy-loading gym-bag, the lazy-loading pencil-case and the lazy-loading gym kit - could solve all these problems, but first let&apos;s look at another approach.&lt;/p&gt;  &lt;h3&gt;Value Holders - aka &quot;Forget lunch, just give &apos;em a pizza menu&quot;&lt;/h3&gt;  &lt;p&gt;The day Jeremy&apos;s parents were away and the babysitter sent him to school with a pizza menu - that&apos;s what PoEAA calls a &lt;strong&gt;&lt;a href=&quot;http://martinfowler.com/eaaCatalog/lazyLoad.html&quot;&gt;Value Holder&lt;/a&gt;&lt;/strong&gt;. That particular lunchbox didn&apos;t &lt;strong&gt;contain&lt;/strong&gt; any lunch, but it &lt;em&gt;did &lt;/em&gt;contain a very simple method that Jeremy could use to &lt;strong&gt;get lunch&lt;/strong&gt; when he needed it. This works, sure - but asking Jeremy to order pizza and pay for it is violating our separation of concerns. Sometimes, this is OK - but be aware you&apos;re doing it. You&apos;d should have a damn good reason for letting a eight-year-old order pizza. I suspect Jeremy&apos;s babysitter has no such reason; maybe OO principles aren&apos;t required reading at babysitter school any more.&lt;/p&gt;  &lt;p&gt;In OO, this will typically show up as a method like Customer.GetOrders() - the customer doesn&apos;t actually contain the orders, but it knows how to get them. This obviously requires that GetOrders() knows something about the data store (repository, data access layer, whatever) - so our object is &lt;em&gt;not &lt;/em&gt;&lt;strong&gt;persistence ignorant.&lt;/strong&gt;&lt;/p&gt;  &lt;h3&gt;Virtual Proxy - aka &quot;The Magic Lazy-Loading Lunchbox&quot;&lt;/h3&gt;  &lt;p&gt;Jim&apos;s magic lunchbox is something much smarter, though. Jim&apos;s lunchbox is a &lt;strong&gt;virtual proxy&lt;/strong&gt; - a lightweight, portable &quot;pretend lunchbox&quot; that, as long as we don&apos;t open it, looks and behaves just like a regular lunchbox. Jim&apos;s lunch isn&apos;t in there. It&apos;s at home, keeping warm (or cold, ), but - and this is the kicker - the &lt;strong&gt;virtual proxy holds a reference back to it. &lt;/strong&gt;Jim can&apos;t go home and get his lunch - because lunch-retrieval is not his responsibility - but the magic lunchbox can not only fetch lunches on demand, it can do so completely seamlessly, without Jim being aware that it&apos;s even happening. All Jim knows is that every day his mum gives him a rucksack, and when he takes his lunchbox out at lunchtime and opens it, he gets a three-course dinner. &lt;/p&gt;  &lt;p&gt;This is much harder to implement seamlessly, but once it&apos;s working, it&apos;s an absolutely joy to use. Just fire up Customer, get back an apparently fully-populated object, and then do something with Customer.Orders and watch as the database quietly retrieves the exact records you need - no more, no less - and seamlessly weaves the new data into your existing object graph. Almost as cool as watching someone get a three-course roast lunch out of a plastic lunchbox. &lt;/p&gt;  &lt;h3&gt;Finally... Eager Loading - aka &quot;Colin&quot;&lt;/h3&gt;  &lt;p&gt;Colin carried a huge sports bag with him, everywhere he ever went, with half-a-dozen paperback books, drinks, snacks, sweets, a Spiderman outfit, poster paints, his father&apos;s old harmonica... the kid was like a walking junkyard. He&apos;d carry absolutely everything he could think of with him, all the time, just in case he needed it. &lt;/p&gt;  &lt;p&gt;Colin&apos;s a great example of what we call &lt;strong&gt;eager loading&lt;/strong&gt; - where you actually load &lt;strong&gt;more&lt;/strong&gt; than you need at initialization time, because (for whatever reason) it&apos;s going to be really, really hard to get it later on. If you&apos;ve ever run Google Reader in offline mode, where it uses the Google Gears engine to get all your blog posts &lt;strong&gt;now&lt;/strong&gt; so you can read them later on the train - &lt;strong&gt;that&apos;s&lt;/strong&gt; eager loading. As with all these techniques, it&apos;s worth knowing that it exists (so you know when it might help you), and what it&apos;s called (so you know what to Google when you need to use it for real).&lt;/p&gt;  &lt;h3&gt;Further Reading - aka &quot;stop with the metaphors already!&quot;&lt;/h3&gt;  &lt;p&gt;The principles behind aggregates, domain models and the repository pattern are covered in depth by &lt;a title=&quot;Eric Evans on Domain-Driven Design&quot; href=&quot;http://www.domainlanguage.com/ddd/index.html&quot;&gt;Eric Evans&apos; work on Domain-Driven Design&lt;/a&gt; and there are good explanations of the supporting patterns in both Eric&apos;s book and Martin Fowler&apos;s &lt;a href=&quot;http://martinfowler.com/books.html#eaa&quot;&gt;Patterns of Enterprise Application Architecture&lt;/a&gt; (PoEAA)&lt;/p&gt;  &lt;p&gt;The &lt;a href=&quot;http://martinfowler.com/eaaCatalog/lazyLoad.html&quot;&gt;Value Holder and Virtual Proxy&lt;/a&gt; patterns are described under &quot;Lazy Load&quot; in PoEAA, and the Castle Project includes a .NET implementation of the virtual proxy pattern called &lt;a href=&quot;http://www.castleproject.org/dynamicproxy/index.html&quot;&gt;DynamicProxy&lt;/a&gt;, which is used by the lazy-loading features provided in several object-relational mappers, including &lt;a href=&quot;http://www.hibernate.org/hib_docs/nhibernate/1.2/reference/en/html_single/#collections-lazy&quot;&gt;NHibernate&lt;/a&gt; and &lt;a href=&quot;http://www.castleproject.org/activerecord/documentation/trunk/usersguide/lazy.html&quot;&gt;Castle ActiveRecord&lt;/a&gt; (which is built on NHibernate) &lt;/p&gt;  &lt;p&gt;Many LINQ providers implement &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/bb351562.aspx&quot;&gt;IQueryable&amp;lt;T&amp;gt;&lt;/a&gt; as a variation on the lazy-loading pattern; take a look at Mike Hadlow&apos;s Linq-to-SQL implementation of &lt;a title=&quot;Mike Hadlow - Using the IRepository pattern with LINQ to SQL&quot; href=&quot;http://mikehadlow.blogspot.com/2008/03/using-irepository-pattern-with-linq-to.html&quot;&gt;IRepository&amp;lt;T&amp;gt;&lt;/a&gt; to see an example of this in action (and don&apos;t miss the &lt;a href=&quot;http://mikehadlow.blogspot.com/2009/01/in-search-of-wild-repository.html&quot;&gt;controversy over whether IQueryable&amp;lt;T&amp;gt;&lt;/a&gt; is violating separation of concerns or not!)&lt;/p&gt;  &lt;p&gt;Oh, and the trick is to eat the banana on the way to school, and then at lunchtime you put the bacon crisps inside the ham-and-jam sandwiches. Honest.&lt;/p&gt;  &lt;p&gt;&lt;small&gt;For the record, names are fictional, any resemblance to any persons living or dead is purely coincidental; the He-Man lunchbox is from www.retrojunk.com , and &lt;a href=&quot;http://tweetfu.com/&quot;&gt;tweetfu&lt;/a&gt; rocks.&lt;/small&gt;&lt;/p&gt;  

&lt;a href=&quot;http://www.dotnetkicks.com/kick/?url=http%3a%2f%2fdylanbeattie.blogspot.com%2f2009%2f01%2fstory-of-lazy-loading-lunchbox.html&quot;&gt;&lt;img src=&quot;http://www.dotnetkicks.com/Services/Images/KickItImageGenerator.ashx?url=http%3a%2f%2fdylanbeattie.blogspot.com%2f2009%2f01%2fstory-of-lazy-loading-lunchbox.html&amp;bgcolor=215670&quot; border=&quot;0&quot; alt=&quot;kick it on DotNetKicks.com&quot; /&gt;&lt;/a&gt;</description>
          <pubDate>2009-01-14T01:03:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/01/14/story-of-lazy-loading-lunchbox.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/01/14/story-of-lazy-loading-lunchbox.html</guid>
        </item>
      
    
      
        <item>
          <title>Two things I&apos;d love to see in my next mobile phone</title>
          <description>&lt;ol&gt;   &lt;li&gt;When the phone&apos;s first switched on, it&apos;s locked. When the phone is locked, it display&apos;s the owner&apos;s name and number and - here&apos;s the twist - it can &lt;strong&gt;call certain predefined numbers.&lt;/strong&gt; So if I drop it in the street and you pick it up, you can use it to call my house and tell me you&apos;ve found it - but you can&apos;t use it for anything else.&lt;/li&gt;    &lt;li&gt;I want to be able to call my own phone (e.g. from a landline), enter a PIN, and switch it to Loud Ringing remotely - so next time I lose it somewhere around my house whilst it&apos;s on &lt;strong&gt;silent&lt;/strong&gt;, I can reactivate the ringer from another phone.&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;Both are basic software features you can probably implement in any modern handset - they don&apos;t use any new hardware or infrastructure. I guess since they make it easier to find missing phones, they make it less likely you&apos;ll end up buying another one, though...&lt;/p&gt;  </description>
          <pubDate>2009-01-03T09:55:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2009/01/03/two-things-i-love-to-see-in-my-next.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2009/01/03/two-things-i-love-to-see-in-my-next.html</guid>
        </item>
      
    
      
        <item>
          <title>Mocking the QueryString collection in ASP.NET</title>
          <description>&lt;p&gt;One of the hardest parts of building testable web applications using ASP.NET is the HttpContext object, which encapsulates access to the HTTP request and response, server state like the Session and Application objects, and ASP.NET&apos;s implementation of various other bits of the HTTP specification.&lt;/p&gt;  &lt;p&gt;HttpContext has a God complex. It&apos;s all-seeing, all-knowing, ever-present, and most WebForms apps just call HttpContext.Current and work with whatever comes back. This approach really doesn&apos;t lend itself to test-driven designs, though, so the ASP.NET MVC team have implemented a collection of &lt;a href=&quot;http://haacked.com/archive/2008/02/21/versioning-issues-with-abstract-base-classes-and-interfaces.aspx&quot;&gt;virtual base classes&lt;/a&gt; - HttpContextBase, HttpRequestBase, etc. - which gives us the ability to isolate elements of the HttpContext for testing purposes, either using a mocking framework or by writing our own test classes that inherit from those base classes. On the whole, this approach works fairly well - especially once you start explicitly passing an HttpContextBase into your controllers instead of letting them run amok with HttpContext.Current - but there&apos;s still some legacy implementation details inherited from ASP.NET that can cause a bit of confusion with your isolation tests.&lt;/p&gt;  &lt;p&gt;In ASP.NET - both MVC and WebForms - the QueryString property of the HttpContext.Request claims to be a NameValueCollection. It isn&apos;t - which becomes immediately apparent if you&apos;re trying to test a controller method that handles IIS 404 errors. In classic mode, IIS will invoke a custom error handler as follows. Let&apos;s say you&apos;ve mapped 404 errors to /MyMvcApp/Error/NotFound - where MyMvcApp is a virtual directory containing an ASP.NET MVC application, which contains an ErrorController with a NotFound() method.&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh4.ggpht.com/_LV_l8kYLOwo/SUkC5PsO8QI/AAAAAAAAAIE/obdBbqCyXlc/s1600-h/image%5B4%5D.png&quot;&gt;&lt;img style=&quot;border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; margin: 10px 0px; border-right-width: 0px&quot; height=&quot;445&quot; alt=&quot;image&quot; src=&quot;http://lh6.ggpht.com/_LV_l8kYLOwo/SUkC55y12NI/AAAAAAAAAII/Ryx3rdj21Io/image_thumb%5B2%5D.png?imgmax=800&quot; width=&quot;431&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;When your browser requests &lt;strong&gt;&lt;a href=&quot;http://myserver/page/is/not/here.aspx&quot;&gt;http://myserver/page/is/not/here.aspx&lt;/a&gt;&lt;/strong&gt;; IIS doesn&apos;t find anything, so it invokes your configured handler by effectively requesting the following URL:&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;http://myserver/MyMvcApp/Error/NotFound?404;http://myserver:80 /page/is/not/here.aspx&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;Notice that there&apos;s no key/value pairs in that query string. The code in my controller that parses it is using HttpContext.Request.QueryString.ToString() to extract the raw query string - but here&apos;s where it gets a bit weird. The framework claims that Request.QueryString is a NameValueCollection, but at runtime, it&apos;s actually a System.Web.HttpValueCollection. The difference is significant because HttpValueCollection.ToString() returns the URL-encoded raw query string, but NameValueCollection.ToString() returns the default Object.ToString() result - in this case &amp;quot;System.Collections.Specialized.NameValueCollection&amp;quot; - which really isn&apos;t much use to our URL parsing code.&lt;/p&gt;  &lt;p&gt;So - to test our parsing code, we need our mock to return an HttpValueCollection. Problem is - this class is internal, so we can&apos;t see it or create new instances of it. The trick is to use System.Web.HttpUtility.ParseQueryString(), which will take the raw query string and return something that claims to be a NameValueCollection but is actually an HttpValueCollection. Pass in the URL you need to test, and it&apos;ll give you back a querystring object you can pass into your tests.&lt;/p&gt;  &lt;p&gt;Putting it all together gives us something along these lines - this is using NUnit and Moq, but the query string technique should work with any test framework.&lt;/p&gt;  &lt;pre&gt;[Test]
public void Verify_Page_Is_Parsed_Correctly_From_IIS_Error_String() {

	// Here, we inject a test query string similar to that created
	// by the IIS custom error handling system.
	var iisQueryString = &amp;quot;404;http://myserver:80/i/like/chutney.html&amp;quot;;
	var testQueryString = HttpUtility.ParseQueryString(iisQueryString);

	Mock&amp;lt;HttpRequestBase&amp;gt; request = new Mock&amp;lt;HttpRequestBase&amp;gt;();
	request.ExpectGet(req =&amp;gt; req.QueryString).Returns(testQueryString);

	Mock&amp;lt;HttpContextBase&amp;gt; context = new Mock&amp;lt;HttpContextBase&amp;gt;();
	context.Expect(ctx =&amp;gt; ctx.Request).Returns(request.Object);

	// Note that we&apos;re injecting an HttpContextBase into ErrorController
	// In the real app, this dependency is resolved using Castle Windsor.
	ErrorController controller = new ErrorController(context.Object);

	ActionResult result = controller.NotFound();

	// TODO: inspect ActionResult to check it&apos;s looked up the requested page
	// or whatever other behaviour we&apos;re expecting.
}&lt;/pre&gt;  </description>
          <pubDate>2008-12-17T13:47:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2008/12/17/mocking-querystring-collection-in.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2008/12/17/mocking-querystring-collection-in.html</guid>
        </item>
      
    
      
        <item>
          <title>Open Source .NET Exchange</title>
          <description>&lt;p&gt;I’ll be presenting a short session on jQuery at the &lt;a href=&quot;http://skillsmatter.com/event/open-source-dot-net/open-source-dot-net-exchange&quot;&gt;SkillsMatter Open Source .NET Exchange&lt;/a&gt; here in London on January 22nd.&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh5.ggpht.com/_LV_l8kYLOwo/SUbmNz1jffI/AAAAAAAAAH8/PxEA78NHUsY/s1600-h/open-src-dot-net-exchange-l%5B3%5D.jpg&quot;&gt;&lt;img title=&quot;open-src-dot-net-exchange-l&quot; style=&quot;border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; margin: 0px 10px 10px 0px; border-right-width: 0px&quot; height=&quot;122&quot; alt=&quot;open-src-dot-net-exchange-l&quot; src=&quot;http://lh6.ggpht.com/_LV_l8kYLOwo/SUbmOBGbvpI/AAAAAAAAAIA/9FWze6pJsDo/open-src-dot-net-exchange-l_thumb%5B1%5D.jpg?imgmax=800&quot; width=&quot;164&quot; align=&quot;left&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;If you’re a .NET developer of any kind, you’ve probably seen or heard people talking about stuff like web UI frameworks, object-relational mapping, fluent APIs, asynchronous messaging, aspect-oriented programming – and you might well be wondering what they are, and why they’re relevant. These events are designed as a sort of “tasting menu” of open source frameworks and techniques – six fifteen-minute sessions that’ll give you some idea of what these technologies can do, why you might want to consider using them, and where you can find more information if you’re interested. &lt;/p&gt;  &lt;p&gt;In the jQuery session, I’ll be showing you how jQuery’s CSS-based selector syntax and flexible “chaining” API let you add rich, cross-browser behaviour and effects to your web pages. I’ll demonstrate how to add animation, dynamic content and AJAX callbacks to your web pages,&amp;#160; and hopefully include a few examples from the multitude of freely-available plug-ins and libraries built on top of the jQuery framework. Yes, all that in fifteen minutes. Like I said, jQuery makes things &lt;em&gt;easy&lt;/em&gt;.&lt;/p&gt;  &lt;p&gt;You can see the &lt;a href=&quot;http://skillsmatter.com/event/open-source-dot-net/open-source-dot-net-exchange&quot;&gt;full programme&lt;/a&gt; at SkillsMatter’s site. I’m really pleased that I’m speaking first, because it means I get to relax and listen to the rest of the speakers afterwards – in particular, &lt;a href=&quot;http://mikehadlow.blogspot.com/&quot;&gt;Mike Hadlow&lt;/a&gt;’s session on the repository pattern. Mike’s code (and help!) were invaluable on one of my projects earlier this year, and in particular his Linq-to-SQL repository – part of the &lt;a href=&quot;http://code.google.com/p/sutekishop/&quot;&gt;Suteki Shop&lt;/a&gt; project - was a great example of how this pattern can make your code cleaner and your life easier. &lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;If any or all of this sounds interesting (or if you just fancy an evening of beer, pizza and geek chat) then please &lt;a href=&quot;http://skillsmatter.com/event/open-source-dot-net/open-source-dot-net-exchange&quot;&gt;sign up&lt;/a&gt; and come along - especially if you’ve not come along to an event like this before. &lt;/p&gt;  </description>
          <pubDate>2008-12-15T23:20:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2008/12/15/open-source-net-exchange.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2008/12/15/open-source-net-exchange.html</guid>
        </item>
      
    
      
        <item>
          <title>Stuff You&apos;ll Wish You&apos;d Known When You Switched To 64-bit Windows</title>
          <description>&lt;p&gt;&lt;a href=&quot;http://lh6.ggpht.com/_LV_l8kYLOwo/SUJQU2eMVaI/AAAAAAAAAH0/DAS6FxtULRs/s1600-h/Dylan_IMG_3203%5B7%5D.jpg&quot;&gt;&lt;img style=&quot;border-right: 0px; border-top: 0px; margin: 0px 0px 10px 10px; border-left: 0px; border-bottom: 0px&quot; height=&quot;240&quot; alt=&quot;A pagoda at sunset in Kyoto, Japan. This has nothing to do with 64-bit Windows, but it is quite pretty.&quot; src=&quot;http://lh5.ggpht.com/_LV_l8kYLOwo/SUJQVDEikAI/AAAAAAAAAH4/e3uiG0sI8Gw/Dylan_IMG_3203_thumb%5B5%5D.jpg?imgmax=800&quot; width=&quot;180&quot; align=&quot;right&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;64-bit Windows is great. I&apos;ve been running on XP 64 and Vista 64&amp;#160; for about a year now. My extremely old Canon USB scanner isn&apos;t supported, and I had to wait a &lt;em&gt;long &lt;/em&gt;time for 64-bit drivers for my &lt;a href=&quot;http://line6.com/podxt/index.html&quot;&gt;Line6 Pod XT&lt;/a&gt;, but otherwise everything works very nicely - and for running virtual PCs, the extra memory is really worth it.&lt;/p&gt;  &lt;p&gt;That said, there&apos;s a couple of underlying differences that result in some very odd behaviour in day-to-day usage. It&apos;s important to realize that 32-bit and 64-bit Windows subsystems exist as parallel but separate environments. Confusingly, the 64-bit tools and utilities live in &lt;strong&gt;C:\Windows\System32&lt;/strong&gt;, and their 32-bit counterparts live in &lt;strong&gt;C:\Windows\SysWOW64&lt;/strong&gt;. 32-bit processes on x64 Windows are actually running inside a virtual environment, which redirects requests for underlying system resources for 32-bit processes. &lt;/p&gt;  &lt;p&gt;64-bit Windows ships with 32-bit and 64-bit versions of lots of common applications - including Internet Explorer and the Windows Script Host. Check out the links below for some more detailed discussion of the architecture and reasoning behind this.&lt;/p&gt;  &lt;h4&gt;Internet Explorer&lt;/h4&gt;  &lt;p&gt;You&apos;ll find &lt;strong&gt;Internet Explorer &lt;/strong&gt;and &lt;strong&gt;Internet Explorer (64 bit) &lt;/strong&gt;in your Start menu. The 64-bit version can&apos;t see any 32-bit components - so no Flash player, no Java, no plugins, no ActiveX controls, nothing. This is useful for testing, but not much else.&lt;/p&gt;  &lt;h4&gt;Windows Scripting Host&lt;/h4&gt;  &lt;p&gt;If you run a Windows script (myscript.vbs) from cmd.exe or directly from Explorer, it&apos;ll run as a 64-bit process, which means it can&apos;t see any 32-bit COM objects like ADO connections and datasets. If you explicitly invoke it using C:\Windows\SysWOW64\cscript.exe, it&apos;ll run as a 32-bit process. &lt;/p&gt;  &lt;h4&gt;The Windows\System32 folder&lt;/h4&gt;  &lt;p&gt;64-bit apps - like the notepad.exe that ships with Windows - can see C:\Windows\System32\ and it&apos;s various subfolders. 32-bit apps - like TextPad - can&apos;t see this folder because Windows is &amp;quot;hiding&amp;quot; the system folders from the 32-bit process. This is completely baffling when you try to edit your &lt;strong&gt;/etc/hosts&lt;/strong&gt; file using your normal editor and it appears to be completely missing - even though you had it open in Notepad a second ago. There&apos;s a thing called the &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/aa384187(VS.85).aspx&quot;&gt;sysnative file system redirector&lt;/a&gt; that you&apos;ll need to set up to be able to see these folders from 32-bit apps. &lt;/p&gt;  &lt;h4&gt;The Registry&lt;/h4&gt;  &lt;p&gt;The same caveat applies to the registry.&amp;#160; When a 32-bit app asks for a value stored under, say, &lt;/p&gt;  &lt;p&gt;&lt;strong&gt; HKEY_LOCAL_MACHINE\Software\MyCompany\MyProject&lt;/strong&gt;, &lt;/p&gt;  &lt;p&gt;64-bit Windows will actually return the value from&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;strong&gt;HKEY_LOCAL_MACHINE\Software\Wow6432Node\MyCompany\MyProject&lt;/strong&gt;.&lt;/p&gt;  &lt;p&gt;This is normally fine, because most 32-bit apps are installed by a 32-bit installer, so the redirection is in place both during install (when the keys are created) and at runtime (when they&apos;re used). If you&apos;re manually importing registry keys - e.g. by doubleclicking a .reg file - they&apos;ll import into the locations specified in the file, and then your 32-bit apps won&apos;t be able to find them. You&apos;ll need to manually copy the keys and values into the Wow6432Node subtree (or edit the original .reg file and re-import)&lt;/p&gt;  &lt;h4&gt;References&lt;/h4&gt;  &lt;p&gt;&lt;a title=&quot;http://blogs.msdn.com/helloworld/archive/2007/12/12/activex-component-can-t-create-object-when-creating-a-32-com-object-in-a-64-bit-machine.aspx&quot; href=&quot;http://blogs.msdn.com/helloworld/archive/2007/12/12/activex-component-can-t-create-object-when-creating-a-32-com-object-in-a-64-bit-machine.aspx&quot;&gt;http://blogs.msdn.com/helloworld/archive/2007/12/12/activex-component-can-t-create-object-when-creating-a-32-com-object-in-a-64-bit-machine.aspx&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;&lt;a title=&quot;http://blogs.sepago.de/helge/2008/03/11/windows-x64-all-the-same-yet-very-different-part-5/&quot; href=&quot;http://blogs.sepago.de/helge/2008/03/11/windows-x64-all-the-same-yet-very-different-part-5/&quot;&gt;http://blogs.sepago.de/helge/2008/03/11/windows-x64-all-the-same-yet-very-different-part-5/&lt;/a&gt;&lt;/p&gt;  </description>
          <pubDate>2008-12-12T11:30:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2008/12/12/stuff-you-wish-you-known-when-you.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2008/12/12/stuff-you-wish-you-known-when-you.html</guid>
        </item>
      
    
      
        <item>
          <title>Fun with Server.GetLastError() in classic ASP on Windows Server 2008</title>
          <description>&lt;p&gt;One of our sites, written many moons ago in classic ASP using JScript, uses a bunch of custom error pages to handle 404 errors, scripting errors, and so on.&lt;/p&gt;

&lt;p&gt;Our error handling code looks like this:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;GetLastError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; 
&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;errorMessage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; 
&lt;span class=&quot;nx&quot;&gt;errorMessage&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;HTMLEncode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Category&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; 
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ASPCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;errorMessage&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;HTMLEncode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;  &lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ASPCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; 
&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;errorNumber&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;errorNumber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;errorNumber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;errorNumber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x100000000&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;errorNumber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;errorMessage&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; error 0x&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;errorNumber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; (from &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ServerVariables&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;SCRIPT_NAME&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Item&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n\r\n&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ASPDescription&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;errorMessage&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;ASPDescription: &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ASPDescription&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; 
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Description&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;errorMessage&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Description: &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Description&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; 
&lt;span class=&quot;c1&quot;&gt;// and then we can display/log the error message &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On our old server, this worked because the HTTP 500 error page was mapped to a custom URL, /errors/500.asp, which included the code above.&lt;/p&gt;

&lt;p&gt;When we migrated our site onto IIS7 recently, this stopped working - the custom page was still executing, but Server.GetLastError() wasn’t returning any information about what had gone wrong.&lt;/p&gt;

&lt;p&gt;There was a very similar known bug in Vista which was supposedly fixed in SP1, but it looks like the same fix isn’t part of Windows 2008 Server yet. There is a workaround, though - if you set the site’s &lt;strong&gt;default error&lt;/strong&gt; property (under IIS settings &amp;gt; Error Pages &amp;gt; Edit Feature Settings…)to the custom page (see below), IIS will invoke this page whenever an error is &lt;strong&gt;not handled by an explicitly configured status-code
      handler&lt;/strong&gt; (so your 404, etc. handlers will still work) - but for some reason, handling the error this way means Server.GetLastError() still works properly.&lt;/p&gt;

&lt;p&gt;&lt;img style=&quot;border-right: 0px; border-top: 0px; border-left: 0px; border-bottom: 0px&quot; height=&quot;369&quot; alt=&quot;image&quot; src=&quot;http://lh5.ggpht.com/_LV_l8kYLOwo/STfYR70eJfI/AAAAAAAAAHw/t4wZRKQpCNI/image_thumb%5B1%5D.png?imgmax=800&quot; width=&quot;400&quot; border=&quot;0&quot; /&gt;&lt;/p&gt;
</description>
          <pubDate>2008-12-04T13:17:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2008/12/04/fun-with-servergetlasterror-in-classic.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2008/12/04/fun-with-servergetlasterror-in-classic.html</guid>
        </item>
      
    
      
        <item>
          <title>Lies, Damned Lies, and Statistics</title>
          <description>&lt;p&gt;One of our big customer contracts is up for renegotiation next month. This involves pulling a list of all the search &amp;amp; site activity that originated from that customer over the last year, and then negotiating based on whether usage is up or down. Over the last few years we&apos;ve seen 10%-15% increases from this particular account, year-on-year, which is good. Yesterday morning I ran the stats report and got this:&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh6.ggpht.com/_LV_l8kYLOwo/SS7_YywMOPI/AAAAAAAAAHk/XH-G4JdCtHQ/image%5B15%5D.png&quot;&gt;&lt;img style=&quot;border-right: 0px; border-top: 0px; border-left: 0px; border-bottom: 0px&quot; height=&quot;346&quot; alt=&quot;&quot; src=&quot;http://lh4.ggpht.com/_LV_l8kYLOwo/SS7_Zq_3EqI/AAAAAAAAAHo/BwNyrq-yMfo/image_thumb%5B7%5D.png&quot; width=&quot;534&quot; border=&quot;0&quot; /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;Not good. In fact, very very worrying indeed. Whilst the marketing team went into crisis mode to work out what the hell we were going to do if this was &lt;strong&gt;real, &lt;/strong&gt;I started double-checking to make sure this was genuine. It certainly &lt;em&gt;looked&lt;/em&gt; genuine. The graph is horribly organic, the way the decline is gradual, occasional peaks and troughs, but with a very, very definite downward trend. In my experience, when software fails, it tends to fail in big straight lines - everything just stops working completely and stays there.&lt;/p&gt;  &lt;p&gt;Turns out the stats were wrong - huge sigh of relief all round - but the reason &lt;em&gt;why &lt;/em&gt;they were wrong is, I think, quite interesting. These statistics are calculated using some custom logging routines in our (legacy ASP) web code. When a user first hits the site, we create a record in the UserSession table in our database that stores their IP address, user agent string, user ID, and so on. There&apos;s some counter fields in that table that are incremented over the course of the session as the user accesses particular resources, so we can build up a fairly accurate picture of which resources get accessed heavily, by whom, and at what times throughout the day.&lt;/p&gt;  &lt;p&gt;Well, it turns out our CreateUserSession() routine was failing if the browser&apos;s UserAgent string was more than 127 characters. Historically, this was never a problem, but at some point last year Microsoft started putting all sorts of information about .NET framework versions and plugins into the HTTP_USER_AGENT header sent by Internet Explorer (Scott Hanselman has a &lt;a href=&quot;http://www.hanselman.com/blog/TheNETFrameworkAndTheBrowsersUserAgentString.aspx&quot;&gt;great post about this&lt;/a&gt; if you&apos;re interested)&amp;#160; As various updates were pushed out to our users via Windows Update and corporate rollouts, the user agent strings were getting longer and longer, until one day they&apos;d exceed 127 characters - and that particular PC would stop showing up in our logs. Whenever they&apos;d roll out new hardware, we&apos;d see the stats increase temporarily, until those new boxes were upgraded and the same thing happened. Hence the gradual decline and the fact that non-IE users were unaffected. &lt;/p&gt;  &lt;p&gt;We would have noticed this a long time ago, of course - but the CreateUserSession() call was wrapped in a try/catch block that called a notification function when it caught an exception, and somewhere along the line, the notification mechanism for this particular system had been commented out. I&apos;d love to blame someone else for this, but Subversion has a commit with my name on it sometime last year with the relevant line mysteriously commented out.&lt;/p&gt;  &lt;p&gt;I believe the kids are calling that an &amp;quot;epic fail&amp;quot;. I believe they have a point.&lt;/p&gt;  </description>
          <pubDate>2008-11-27T20:13:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2008/11/27/lies-damned-lies-and-statistics.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2008/11/27/lies-damned-lies-and-statistics.html</guid>
        </item>
      
    
      
        <item>
          <title>HQL-lo World</title>
          <description>&lt;p&gt;I&apos;ve been playing with Castle ActiveRecord for a project I&apos;m working on, and hit a brick wall earlier tonight that left me completely stuck for a couple of hours... and turned out to be incredibly simple and obvious. Turns out I&apos;d refactored one of my business objects - from Page to CmsPage - and hadn&apos;t noticed that in one particular place in the code, I was doing this:&lt;/p&gt;  &lt;pre class=&quot;code&quot;&gt;&lt;span style=&quot;background: black; color:#40c4ff;&quot;&gt;var &lt;/span&gt;&lt;span style=&quot;background: black; color:#eddac0;&quot;&gt;rootPages &lt;/span&gt;&lt;span style=&quot;background: black; color:silver;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;background: black; color:#40c4ff;&quot;&gt;new &lt;/span&gt;&lt;span style=&quot;background: black; color:cyan;&quot;&gt;SimpleQuery&lt;/span&gt;&lt;span style=&quot;background: black; color:silver;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;background: black; color:cyan;&quot;&gt;CmsPage&lt;/span&gt;&lt;span style=&quot;background: black; color:silver;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;background: black; color:#e0e0e0;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;background: black; color:#cc80d2;&quot;&gt;@&quot;from Page p where p.Parent is null&quot;&lt;/span&gt;&lt;span style=&quot;background: black; color:#e0e0e0;&quot;&gt;);
&lt;/span&gt;&lt;span style=&quot;background: black; color:#40c4ff;&quot;&gt;return &lt;/span&gt;&lt;span style=&quot;background: black; color:#e0e0e0;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;background: black; color:#eddac0;&quot;&gt;rootPages&lt;/span&gt;&lt;span style=&quot;background: black; color:silver;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;background: black; color:#eddac0;&quot;&gt;Execute&lt;/span&gt;&lt;span style=&quot;background: black; color:#e0e0e0;&quot;&gt;());&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;The Execute() call there was throwing an ActiveRecordException that just said &lt;strong&gt;{&quot;Could not perform ExecuteQuery for CmsPage&quot;} - &lt;/strong&gt;no InnerException, nothing showing up in SQL Profiler, nothing except a bunch of query strings that all looked fine to me:&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;http://lh6.ggpht.com/_LV_l8kYLOwo/SSSoPYXwOYI/AAAAAAAAAHc/rlc-XhkmYkg/s1600-h/image%5B10%5D.png&quot;&gt;&lt;img style=&quot;border-right: 0px; border-top: 0px; margin: 10px 0px; border-left: 0px; border-bottom: 0px&quot; height=&quot;416&quot; alt=&quot;image&quot; src=&quot;http://lh5.ggpht.com/_LV_l8kYLOwo/SSSoP0dNz8I/AAAAAAAAAHg/DghJ3Mu8pZ4/image_thumb%5B6%5D.png?imgmax=800&quot; width=&quot;563&quot; border=&quot;0&quot; /&gt;&lt;/a&gt; &lt;/p&gt;&lt;p&gt;Even &lt;a href=&quot;http://using.castleproject.org/display/AR/Troubleshooting&quot;&gt;enabling ActiveRecord logging&lt;/a&gt; (which was wonderfully easy, by the way) didn&apos;t help - I couldn&apos;t see anything obviously amiss in the NHibernate logs.&lt;/p&gt;&lt;p&gt;Turns out I&apos;d not yet got my head around a fundamental concept of object-relational mapping, namely that &lt;strong&gt;you are querying your&lt;/strong&gt; &lt;strong&gt;objects&lt;/strong&gt;, &lt;strong&gt;not your database. &lt;/strong&gt;The string literal in the SimpleQuery definition that looks a bit like LINQ is HQL - Hibernate Query Language. I&apos;d used the [ActiveRecord(Table=&quot;Page&quot;)] attribute to map the renamed class to the underlying DB table, which is still called Page, and it just completely didn&apos;t occur to me that the HQL query needs to be changed to reflect the new class name. Change that query to&lt;/p&gt;&lt;pre class=&quot;code&quot;&gt;&lt;span style=&quot;background: black; color:#40c4ff;&quot;&gt;var &lt;/span&gt;&lt;span style=&quot;background: black; color:#eddac0;&quot;&gt;rootPages &lt;/span&gt;&lt;span style=&quot;background: black; color:silver;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;background: black; color:#40c4ff;&quot;&gt;new &lt;/span&gt;&lt;span style=&quot;background: black; color:cyan;&quot;&gt;SimpleQuery&lt;/span&gt;&lt;span style=&quot;background: black; color:silver;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;background: black; color:cyan;&quot;&gt;CmsPage&lt;/span&gt;&lt;span style=&quot;background: black; color:silver;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;background: black; color:#e0e0e0;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;background: black; color:#cc80d2;&quot;&gt;@&quot;from CmsPage p where p.Parent is null&quot;&lt;/span&gt;&lt;span style=&quot;background: black; color:#e0e0e0;&quot;&gt;);&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;and it works as intended. I fear this ORM stuff is going to take some getting used to...&lt;/p&gt;</description>
          <pubDate>2008-11-19T23:58:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2008/11/19/hql-lo-world.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2008/11/19/hql-lo-world.html</guid>
        </item>
      
    
      
        <item>
          <title>Usability Tip of the Day: Label your Form Elements, Dammit.</title>
          <description>&lt;p&gt;I see high-profile, expensive, shiny, corporate websites all the time that don’t label their form inputs. It’s easy. It’s accessible. And – in the case of checkboxes and radio buttons, where the form inputs themselves are about this big :&lt;input type=&quot;checkbox&quot;&gt;&lt;input type=&quot;radio&quot;&gt;, it’s massively helpful, because in almost every modern browser, &lt;strong&gt;you can click the label instead of having to click the actual form element.&lt;/strong&gt; It’s staggering that so-called professional web developers don’t label their form elements properly. Here’s how you do it:&lt;/p&gt;  &lt;pre&gt;&amp;lt;input type=”radio” id=”beerRadioButton” name=”beverage” value=”beer” /&amp;gt;
&amp;lt;label for=”beerRadioButton”&amp;gt;Beer&amp;lt;/label&amp;gt;
&amp;lt;input type=”radio” id=”wineRadioButton” name=”beverage” value=”wine” /&amp;gt;
&amp;lt;label for=”wineRadioButton”&amp;gt;Wine&amp;lt;/label&amp;gt;&lt;/pre&gt;&lt;p&gt;(In ASP.NET WebForms, if you set the AssociatedControlId of an &amp;lt;asp:Label /&amp;gt; control, it’ll render an HTML label element with the correct for=”” attribute; if you omit the AssociatedControlId attribute, it won’t even render as a label...)&lt;/p&gt;&lt;p&gt;Here&apos;s a form example without the labels wired up properly:&lt;/p&gt;&lt;fieldset&gt;&lt;input name=&quot;beverage&quot; type=&quot;radio&quot;&gt; &lt;label&gt;Beer&lt;/label&gt;
&lt;input name=&quot;beverage&quot; type=&quot;radio&quot;&gt; &lt;label&gt;Wine&lt;/label&gt; &lt;/fieldset&gt; And here&apos;s the same radio buttons, with the labels wired up properly. See how in the this example, clicking the labels will select their associated radio-buttons, but in the previous form, you have to actually click the radio-button itself?
&lt;fieldset&gt;&lt;input id=&quot;beerRadioButton&quot; name=&quot;beverage&quot; type=&quot;radio&quot;&gt; &lt;label for=&quot;beerRadioButton&quot;&gt;Beer&lt;/label&gt;
&lt;input id=&quot;wineRadioButton&quot; name=&quot;beverage&quot; type=&quot;radio&quot;&gt; &lt;label for=&quot;wineRadioButton&quot;&gt;Wine&lt;/label&gt; &lt;/fieldset&gt;
Just do it, OK? It helps people using screen readers. It helps mobile browsers. And it helps people downloading Lego instructions after a couple of beers. Trust me.</description>
          <pubDate>2008-11-08T23:28:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2008/11/08/usability-tip-of-day-label-your-form.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2008/11/08/usability-tip-of-day-label-your-form.html</guid>
        </item>
      
    
      
        <item>
          <title>Working on ASP.NET MVC Beta and Preview code side-by-side</title>
          <description>&lt;p&gt;I have an app built against ASP.NET MVC Preview 3 that needs some tweaking, and I&apos;m also working on a couple of projects using ASP.NET MVC Beta, so I&apos;m in the slightly odd situation of trying to build and run preview 3 and beta projects side-by-side. (Yes, I will be updating this code to run against the beta version. I don&apos;t have time to do that this weekend, though, and I need some changes live before Monday afternoon.) &lt;/p&gt;  &lt;p&gt;I&apos;ve just checked-out the preview 3 project to make some changes, and although it builds absolutely fine, I&apos;m seeing the lovely Yellow Screen of Death when I try and run it:&lt;/p&gt;&lt;table cellspacing=&quot;0&quot; cellpadding=&quot;2&quot; border=&quot;1&quot;&gt;&lt;tbody&gt;     &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;603&quot;&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;word-spacing: 0px; font: 11px verdana; text-transform: none; color: rgb(0,0,0); text-indent: 0px; white-space: normal; letter-spacing: normal; border-collapse: separate; orphans: 2; widows: 2; -webkit-border-horizontal-spacing: 0px; -webkit-border-vertical-spacing: 0px; -webkit-text-decorations-in-effect: none; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0&quot;&gt;&lt;span&gt;             &lt;h3 style=&quot;font-weight: normal; font-size: 18pt; color: red; font-family: verdana&quot;&gt;Server Error in &apos;/&apos; Application.                &lt;hr width=&quot;100%&quot; color=&quot;#c0c0c0&quot; size=&quot;1&quot;&gt;&lt;/h3&gt;              &lt;h4 style=&quot;font-weight: normal; font-size: 14pt; color: maroon; font-family: verdana&quot;&gt;&lt;i&gt;Method not found:               
&apos;Void System.Web.Mvc.RouteCollectionExtensions.IgnoreRoute   (System.Web.Routing.RouteCollection, System.String)&apos;.&lt;/i&gt;&lt;/h4&gt;           &lt;/span&gt;&lt;span style=&quot;font-family:arial, helvetica, geneva, sunsans-regular, sans-serif;&quot;&gt;&lt;b   style=&quot;margin-top: -5px; font-weight: bold;  font-family:verdana;color:black;&quot;&gt;Description:&lt;span class=&quot;Apple-converted-space&quot;&gt; &lt;/span&gt;&lt;/b&gt;An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.&lt;span class=&quot;Apple-converted-space&quot;&gt; &lt;/span&gt;            
        
&lt;b   style=&quot;margin-top: -5px; font-weight: bold;  font-family:verdana;color:black;&quot;&gt;Exception Details:&lt;span class=&quot;Apple-converted-space&quot;&gt; &lt;/span&gt;&lt;/b&gt;System.MissingMethodException: Method not found: &apos;Void System.Web.Mvc.RouteCollectionExtensions.IgnoreRoute(System.Web.Routing.RouteCollection, System.String)&apos;. &lt;/span&gt;&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;This is weird, because this code is deployed and running live on a box that doesn&apos;t have any versions of MVC installed; in theory, the project is entirely self-contained and XCOPY-deployable. First thing I tried was to shut down Visual Studio, uninstall ASP.NET MVC Beta, reinstall Preview 3, reload VS2008. That worked, so it&apos;s definitely the beta doing something strange. This project has hard-wired references to copies of the MVC assemblies in the \Dependencies folder of the solution, which are copied to the \bin folder during the build. It looks like the beta is installing something that&apos;s interfering with this process. Frustratingly, the installers also set up the MVC Web Application project type in Visual Studio, so although I can &lt;strong&gt;run&lt;/strong&gt; the site without any versions of MVC installed, I can&apos;t open it in VS2008 because of the &quot;project type is not supported&quot; error.&lt;/p&gt;&lt;p&gt;Ok, first thing to realize is that, according to &lt;a href=&quot;http://weblogs.asp.net/scottgu/archive/2008/10/16/asp-net-mvc-beta-released.aspx#eleven&quot;&gt;ScottGu&apos;s beta release blog post&lt;/a&gt;, the beta installs System.Web.Mvc, System.Web.Routing and System.Web.Abstractions to the GAC to allow them to be automatically updated. The preview versions of MVC would only install them to C:\Program Files\Microsoft ASP.NET\.&lt;/p&gt;&lt;p&gt;Given this particular chunk of web.config code:&lt;/p&gt;&lt;pre class=&quot;code&quot;  style=&quot;padding-right: 8px; padding-left: 8px; background: black; padding-bottom: 8px; padding-top: 8px; font-family:consolas;&quot;&gt;&lt;span style=&quot;background: black; color:#40c4ff;&quot;&gt;&amp;lt;system.web&amp;gt;
&lt;/span&gt;&lt;span style=&quot;background: black; color:#40c4ff;&quot;&gt; &amp;lt;compilation &lt;/span&gt;&lt;span style=&quot;background: black; color:#eddac0;&quot;&gt;debug&lt;/span&gt;&lt;span style=&quot;background: black; color:#40c4ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;background: black; color:#ff80ff;&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span style=&quot;background: black; color:#40c4ff;&quot;&gt;&amp;gt;
&amp;lt;assemblies&amp;gt;
&lt;/span&gt;&lt;span style=&quot;background: black; color:#40c4ff;&quot;&gt;    &amp;lt;add &lt;/span&gt;&lt;span style=&quot;background: black; color:#eddac0;&quot;&gt;assembly&lt;/span&gt;&lt;span style=&quot;background: black; color:#40c4ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;background: black; color:#ff80ff;&quot;&gt;&quot;System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35&quot;&lt;/span&gt;&lt;span style=&quot;background: black; color:#40c4ff;&quot;&gt;/&amp;gt;
&lt;/span&gt;&lt;span style=&quot;background: black; color:#40c4ff;&quot;&gt;   &amp;lt;/assemblies&amp;gt;
&amp;lt;/compilation&amp;gt;
&amp;lt;/system.web&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;a href=&quot;http://11011.net/software/vspaste&quot;&gt;&lt;/a&gt;&lt;p&gt;the runtime is going to use the first version of System.Web.Mvc matching the specified culture, version number and public key token. This is significant because &lt;strong&gt;the CLR checks the GAC first &lt;/strong&gt;when resolving assembly references - and if it finds a matching assembly in the GAC, it won&apos;t look anywhere else. The ASP.NET MVC previews and beta release all use the same assembly version, culture and public keys, so the CLR has no way of distinguishing between the preview 3 version of System.Web.Mvc and the beta version of the same assembly. They&apos;re different DLLs with different &lt;strong&gt;file versions&lt;/strong&gt;, but because the &lt;strong&gt;assembly version&lt;/strong&gt; is the same, the CLR regards them as the same assembly.&lt;/p&gt;&lt;p&gt;There are techniques you can use to override this behaviour, but, according to &lt;a href=&quot;http://stackoverflow.com/questions/267693/how-can-i-force-net-to-use-a-local-copy-of-an-assembly-thats-in-the-gac&quot;&gt;this thread on StackOverflow&lt;/a&gt;, these techniques&lt;strong&gt; &lt;/strong&gt;only work if &lt;strong&gt;the assembly in the GAC has a different version to the assembly that&apos;s deployed with your application.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Ok - no problem, we&apos;ll just remove System.Web.Mvc from the GAC, by running &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/ex0ss12c.aspx&quot;&gt;gacutil.exe&lt;/a&gt; /u to uninstall it.&lt;/p&gt;&lt;pre&gt;C:\Documents and Settings\dylan.beattie&amp;gt;&lt;strong&gt;gacutil /u system.web.mvc&lt;/strong&gt;
Microsoft (R) .NET Global Assembly Cache Utility. Version 3.5.30729.1
Copyright (c) Microsoft Corporation. All rights reserved.

Assembly: system.web.mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL
Unable to uninstall: assembly is required by one or more applications
Pending references:
SCHEME: &amp;lt;windows_installer&amp;gt; ID: &amp;lt;msi&amp;gt; DESCRIPTION : &amp;lt;windows installer&amp;gt;
Number of assemblies uninstalled = 0
Number of failures = 0

C:\Documents and Settings\dylan.beattie&amp;gt;&lt;/pre&gt;&lt;p&gt;&lt;a href=&quot;http://www.codinghorror.com/blog/archives/000818.html&quot;&gt;&lt;img style=&quot;border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; margin: 0px 0px 10px 10px; border-right-width: 0px&quot; height=&quot;96&quot; alt=&quot;Works on MY Machine!&quot; src=&quot;http://lh5.ggpht.com/_LV_l8kYLOwo/SRW4OTOKy2I/AAAAAAAAAHY/JBymuNqRM7U/works-on-my-machine-starburst%5B9%5D.png?imgmax=800&quot; width=&quot;100&quot; align=&quot;right&quot; border=&quot;0&quot; /&gt;&lt;/a&gt; OK, that didn&apos;t work. Because we installed the ASP.NET MVC beta using Windows Installer, it&apos;s registered a dependency on System.Web.Mvc that means we can&apos;t uninstall it. So... registry hack time. This is the bit that might kill your PC, wife, cat, whatever.  Editing the registry is dangerous and can cause all kinds of problems, so read this stuff first, and if it sounds like a good idea, proceed at your own risk.&lt;/p&gt;&lt;p&gt;Fire up regedit and navigate to &lt;strong&gt;HKEY_CLASSES_ROOT\Installer\Assemblies\Global&lt;/strong&gt;, and you should find a key in there called&lt;/p&gt;&lt;p&gt;&lt;strong&gt;System.Web.Mvc,version=&quot;1.0.0.0&quot;,culture=&quot;neutral&quot;,publicKeyToken=&quot;31BF3856AD364E35&quot;,processorArchitecture=&quot;MSIL&quot;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;I deleted this key. I also got a bit carried away and deleted the key&lt;/p&gt;&lt;p&gt;&lt;strong&gt;System.Web.Mvc,1.0.0.0,,31bf3856ad364e35,MSIL  &lt;/strong&gt;from&lt;/p&gt;&lt;p&gt;&lt;strong&gt;HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Fusion\GACChangeNotification\Default&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;as well... but I forgot to try gacutil /u first, so I don&apos;t know whether this second step is necessary or not. It seemed like a good idea, though, and doesn&apos;t appear to have broken anything, so you may or may not need to delete this second key as well.&lt;/p&gt;&lt;p&gt;Having removed those keys, I could run gacutil /u and remove System.Web.Mvc quite happily:&lt;/p&gt;&lt;pre&gt;C:\&gt;gacutil /u System.Web.Mvc
Microsoft (R) .NET Global Assembly Cache Utility. Version 3.5.30729.1
Copyright (c) Microsoft Corporation. All rights reserved.

Assembly: System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL
Uninstalled: System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL
Number of assemblies uninstalled = 1
Number of failures = 0

C:\&gt; &lt;/pre&gt;&lt;p&gt;My preview 3 project now builds and runs quite happily against the System.Web.Mvc DLLs installed as part of the website, and the VS2008 MVC Project template still works just like it did before.&lt;/p&gt;</description>
          <pubDate>2008-11-08T15:53:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2008/11/08/working-on-aspnet-mvc-beta-and-preview.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2008/11/08/working-on-aspnet-mvc-beta-and-preview.html</guid>
        </item>
      
    
      
        <item>
          <title>A Rant about RAID, with a Bad Metaphor about Eggs, and No Happy Ending.</title>
          <description>&lt;p&gt;I went in to work this morning and my main workstation had died over the weekend. Bluescreen on boot, no safe mode, nothing. Windows Update gone bad? We&apos;l l probably never know, given I don&apos;t think it&apos;s coming back any time soon... but, as with previous overnight machine suicides, it looks like a problem with SATA RAID - specifically, two WD Velociraptors in a RAID-1 (mirror) array controlled by an Intel ICH10R chipset on an Asus P5Q motherboard.&lt;/p&gt;  &lt;p&gt;You know your whole eggs &amp;amp; baskets thing, right? SATA RAID is like carefully dividing your eggs into two really good baskets, then tying them together with six feet of wet spaghetti and hanging them off a ceiling fan. &lt;/p&gt;  &lt;p&gt;&lt;img title=&quot;Photo courtesy of bartmaguire via Flickr, used under Creative Commons license&quot; style=&quot;margin: 0px 0px 20px 20px&quot; src=&quot;http://farm1.static.flickr.com/81/239170816_9859e09045_m.jpg&quot; align=&quot;right&quot; /&gt;Long story short, I lost a day, and counting. I had to split the mirror into individual drives, switch the BIOS back to IDE, which gave me a bootable OS but - seriously - no text. No captions, no icon labels, no button text, nothing. Just these weird, ghostly empty buttons. Running a repair off the WinXP x64 CD got my labels back, but somehow left Windows on drive D. Another half-hour of registry hacks to get it back to drive C: where it belongs, and I had a creaking but functional system - VS2008 and Outlook are working, but most of &lt;a href=&quot;http://www.timesnapper.com/&quot;&gt;my&lt;/a&gt; &lt;a href=&quot;http://www.red-gate.com/products/SQL_Compare/index.htm&quot;&gt;beloved&lt;/a&gt; &lt;a href=&quot;http://instant-eyedropper.com/&quot;&gt;little&lt;/a&gt; &lt;a href=&quot;http://www.digsby.com/&quot;&gt;apps&lt;/a&gt; are complaining that someone&apos;s moved their cheese. Reinstalling is probably inevitable, along with the deep, deep joy that is reinstalling Adobe Creative Suite when your last remaining &amp;quot;activation&amp;quot; is bound to a PC that now refuses to deactivate it. Even Adobe&apos;s support team don&apos;t understand activation. Best they could come up with was &amp;quot;yes, that means there&apos;s no activations on that system.&amp;quot; Err, no, Mr. Adobe, there are. It was very clear on that point. Wouldn&apos;t let me run Photoshop without it, you see. &amp;quot;Oh... then you&apos;d better just reformat, and when you reinstall, you&apos;ll need to phone us for an activation override&amp;quot;. Thanks, guys. I feel the love. &lt;/p&gt;  &lt;p&gt;Sorry, I digress. This whole experience is all the more frustrating because RAID mirrors are supposed to be a Good Thing. If you believe the theory, RAID-1 will let you keep on working in the event of a single drive failure. Well... In the last 5 years or so, I haven&apos;t had a single workstation die because of a failed hard drive, but I&apos;ve &lt;strong&gt;lost count&lt;/strong&gt; of the number of times an Intel SATA RAID controller has suddenly thrown a hissy-fit under Windows XP and taken the system down with it. Every time it starts with a bit of instability, ends up a week or two later with bluescreens on boot and general wailing and gnashing of teeth, and every time, running drive diagnostics on the physical disks shows them to be absolutely fine. &lt;/p&gt;  &lt;p&gt;This is across four different Intel motherboards - two Abit, one Asus, and a Dell Precision workstation - running both the ICH9R (P35) and ICH10R (P45) chipsets, and various matched pairs of WD Caviar, WD Raptor, WD Velociraptor and Seagate drives. One system was a normal Dell Precision workstation, the others are various home-built combinations, all thoroughly memtest86&apos;ed and burned-in before being put into production doing anything important. &lt;/p&gt;  &lt;p&gt;Am I doing something wrong here? I feel like I&apos;ve invested enough of both my and my employer&apos;s time and money in &amp;quot;disaster-proofing&amp;quot; my working environment, and just ended up shooting myself in the foot. I&apos;m beginning to think that having two identical workstations, with a completely non-RAID-related disk-mirroring strategy, is the only way to actually guarantee any sort of continuity - if something goes wrong, you just stick the spare disk in the spare PC and keep on coding. Or hey, just keep stuff backed up and whenever you lose a day or two to HD failure, tell yourself it&apos;s nothing compared to the 5-10 days you&apos;d have lost if you&apos;d done something sensible like using desktop RAID in the first place.&lt;/p&gt;  &lt;p&gt;&lt;small&gt;[Photo from &lt;a href=&quot;http://www.flickr.com/photos/bartmaguire/&quot;&gt;bartmaguire&lt;/a&gt; via Flickr, used under Creative Commons license. Thanks Bart.]&lt;/small&gt;&lt;/p&gt;  </description>
          <pubDate>2008-11-03T21:53:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2008/11/03/rant-about-raid-with-bad-metaphor-about.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2008/11/03/rant-about-raid-with-bad-metaphor-about.html</guid>
        </item>
      
    
      
        <item>
          <title>The Roadcraft of Programming</title>
          <description>&lt;p&gt;I was chatting with Jason &amp;quot;Argos&amp;quot; Hughes after the &lt;a href=&quot;http://skillsmatter.com/podcast/open-source-dot-net/dependency-injection-with-castle-windsor&quot;&gt;Skillsmatter event last week&lt;/a&gt;, and he said something I think is really quite brilliant, so I hope he doesn&apos;t mind if I quote him here and expand on his ideas a little.&lt;/p&gt;  &lt;p&gt;We were discussing the merits of various different platforms and programing languages, and he said &amp;quot;knowing a language inside-out doesn&apos;t make you a better programmer, any more than knowing a lot about a particular car makes you a better driver&amp;quot;.&lt;/p&gt;  &lt;p&gt;&lt;img style=&quot;margin: 20px auto; border-right: 0px; border-top: 0px; border-left: 0px; border-bottom: 0px&quot; height=&quot;171&quot; alt=&quot;wacky_races&quot; src=&quot;http://lh5.ggpht.com/_LV_l8kYLOwo/SQ9z5yYalDI/AAAAAAAAAGg/gFj_CI7Z4Q4/wacky_races_thumb%5B2%5D.jpg?imgmax=800&quot; title=&quot;Penelope Pitstop is driving Ruby; Dastardly &amp;amp; Muttley are in Object COBOL.&quot; width=&quot;600&quot; border=&quot;0&quot; /&gt;&lt;/p&gt;  &lt;p&gt;That comment has been going round and round my head ever since, and I think that&apos;s one of the most insightful metaphors about programming languages that I&apos;ve heard. Anyone who&apos;s owned a car will know that every make and model - and every individual example of a particular model - has its idiosyncrasies and quirks. I drive a slightly knackered Vauxhall Tigra. On this particular car, I know that I need to replace the cam-belt every 40,000 miles or Really Bad Things might happen. I know that I need to clean the gunk out of the frame around the back window otherwise it fills up with rainwater; I know where the little lever to adjust the seats is, and where all the various controls and switches are, and how to check the oil and change the headlamp bulbs.&amp;#160; &lt;/p&gt;  &lt;p&gt;None of this makes me a good driver. In fact, it has absolutely nothing to do with my driving ability. Beyond a basic familiarity with a vehicle&apos;s controls and signals, the Highway Code has very little to say about the quirks and idiosyncrasies of particular cars.&amp;#160; On the other hand, it has rather a lot to say about stopping distances, speed limits, lane discipline, the importance of maintaining awareness of your surroundings and communicating your intentions clearly to other road uses. In other words, being a good driver boils down to discipline, restraint, awareness and communication - your choice of vehicle is largely irrelevant. Good drivers are good whatever they&apos;re driving, and the choice of car alone can&apos;t turn a poor driver into a good one. &lt;/p&gt;  &lt;p&gt;I think there are strong parallels here with software development. Good coders are like good drivers; they&apos;ll work within the safe parameters of whatever technology they&apos;re using, exercise restraint and discipline in the application of that technology, and rely on awareness and communication to make sure that they&apos;re doing doesn&apos;t create problems for other people. &lt;/p&gt;  &lt;p&gt;Programming interviews can easily degenerate into a pop-quiz about the characteristics of a particular language or platform, but maybe we should be approaching them more like a driving test - even to the extent of letting the candidate demonstrate their problem-solving capabilities using whatever languages and tools they&apos;re comfortable with, and then discussing the results in terms of clarity, effective communication, restraint and awareness. Even though we&apos;re a .NET shop, I can see how a developer who can create elegant solutions in Ruby or Java and explain clearly what they&apos;ve done might be a better .NET programmer than somebody who knows every quirk of C# and ASP.NET but can&apos;t demonstrate those core qualities of discipline, restraint, awareness and communication.&lt;/p&gt;  </description>
          <pubDate>2008-11-02T18:02:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2008/11/02/roadcraft-of-programming.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2008/11/02/roadcraft-of-programming.html</guid>
        </item>
      
    
      
        <item>
          <title>Huagati DBML Tools for Linq-to-SQL</title>
          <description>&lt;p&gt;&lt;a href=&quot;http://lh6.ggpht.com/_LV_l8kYLOwo/SQ3lRabHdCI/AAAAAAAAAGQ/aKC_T2QXOfA/s1600-h/IMG_0746%5B8%5D.jpg&quot;&gt;&lt;img style=&quot;border-right: 0px; border-top: 0px; margin: 0px 10px 10px 0px; border-left: 0px; border-bottom: 0px&quot; height=&quot;200&quot; alt=&quot;The bridge in Central Park, NY. Nothing to do with DBML tools. Just looks pretty.&quot; src=&quot;http://lh4.ggpht.com/_LV_l8kYLOwo/SQ3lR1lzDLI/AAAAAAAAAGU/s8WPjlK-8hE/IMG_0746_thumb%5B6%5D.jpg?imgmax=800&quot; width=&quot;260&quot; align=&quot;left&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt; I haven&apos;t had a chance to use them yet, but the &lt;a href=&quot;http://www.huagati.com/dbmltools/&quot;&gt;Huagati DBML/EDMX tools&lt;/a&gt; look interesting - a set of extensions to the DBML designer in Visual Studio 2008 that provide some additional functionality, including the much-needed ability to update your DBML to reflect changes in your database schema. It&apos;s a commercial package costing $119.95 per user, but a free trial license is available.&lt;/p&gt;  &lt;p&gt;With Microsoft effectively abandoning Linq-to-SQL, it&apos;s good to see tools like this in the wild. Of course, it&apos;d be &lt;strong&gt;really&lt;/strong&gt; good to see Microsoft open-source Linq-to-SQL and let the community develop it as they see fit... but failing that, these tools can make things easier if you&apos;re maintaining an existing Linq-to-SQL system.&lt;/p&gt;  </description>
          <pubDate>2008-11-02T17:37:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2008/11/02/huagati-dbml-tools-for-linq-to-sql.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2008/11/02/huagati-dbml-tools-for-linq-to-sql.html</guid>
        </item>
      
    
      
        <item>
          <title>Help! I have Multiple Internet Personality Disorder!</title>
          <description>&lt;blockquote&gt;   &lt;p&gt;Update - looks like you can already do this. chrome.exe will accept a --user-data-dir=&amp;quot;&amp;quot; switch, so you can &lt;a href=&quot;http://www.labnol.org/software/create-family-profiles-in-google-chrome/4394/&quot;&gt;set up shortcuts with different profiles&lt;/a&gt; - and it works, really quite well. I now have three Chrome shortcuts that bring up different homepages with different sets of persisted cookies. No colour-coding or cool icons, though... &lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;I have too many Google accounts. Or rather, I have the right number of Google accounts &lt;strong&gt;for me&lt;/strong&gt;, but that&apos;s too many for Google, who would seemingly be much happier if I only had the one. &lt;/p&gt;  &lt;p&gt;I&apos;ll explain. I have a Gmail mailbox, which I forward copies of stuff to, so I can get hold of it from anywhere. My main mailbox runs on a Linux box so old I think it&apos;s actually running Redhat instead of Fedora, so Gmail acts as a second-level backup strategy as well. There&apos;s a couple of calendars and things in this Google account as well. I also have a Google account linked to my &apos;real&apos; e-mail address, which I use to sign in to Blogger and various other online services that have ended up under the Google umbrella. Then I have another set of credentials, which are the accounts we&apos;ve set up at work for access to stuff like Google Webmaster Tools and Google Analytics. I&apos;m not entirely happy with Google linking my e-mail or my blog to my employer&apos;s website statistics, so I keep this separate as well.&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh3.ggpht.com/dmb197/SPyHoMvaBuI/AAAAAAAAAE4/vREYyKfb1LY/s1600-h/image%5B10%5D.png&quot;&gt;&lt;img style=&quot;border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; margin: 0px 0px 20px 20px; border-right-width: 0px&quot; height=&quot;110&quot; alt=&quot;image&quot; src=&quot;http://lh6.ggpht.com/dmb197/SPyHocZ4u4I/AAAAAAAAAE8/6-sprSxWu4Y/image_thumb%5B6%5D.png?imgmax=800&quot; width=&quot;356&quot; align=&quot;right&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;Basically - I want multiple, independently-persisted identities, with their own history, their own cookies and their own shortcuts, so when I say &amp;quot;remember me&amp;quot;, I&apos;m actually saying &amp;quot;remember who I&apos;m pretending to be right now&amp;quot; Google Chrome already has &apos;incognito mode&apos; (and we all know what &lt;em&gt;that &lt;/em&gt;means, right?). Can we have work mode, home mode, geek mode, pretending-to-be-a-client-so-I-can-test-my-own-website mode, and as many other modes as we want? With their own colours? And icons? And desktop / start menu shortcuts? &lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh3.ggpht.com/dmb197/SPyHo9ET0OI/AAAAAAAAAFA/3oMU3uU7MG0/s1600-h/image%5B12%5D.png&quot;&gt;&lt;img style=&quot;border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px&quot; height=&quot;423&quot; alt=&quot;image&quot; src=&quot;http://lh6.ggpht.com/dmb197/SPyHpu_rnsI/AAAAAAAAAFE/wH1-t2os82Y/image_thumb%5B8%5D.png?imgmax=800&quot; width=&quot;622&quot; border=&quot;0&quot; /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;Actually, it doesn&apos;t have to be Google Chrome at all, it&apos;s just that their little &amp;quot;secret agent&amp;quot; icon guy worked really well for the screen mockup. Firefox could do this. Or even Internet Explorer. I know there are cookie-switcher add-ons for Firefox et al, but what none of these solutions offer, as far as I can tell, is the ability to use multiple identities simultaneously - and since Google&apos;s made such a big thing of Chrome&apos;s separate-processes-for-each-tab technology, it seems like it couldn&apos;t be too hard to give those processes their own profiles and history.&lt;/p&gt;  </description>
          <pubDate>2008-10-20T00:13:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2008/10/20/help-i-have-multiple-internet.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2008/10/20/help-i-have-multiple-internet.html</guid>
        </item>
      
    
      
        <item>
          <title>Adding a work-in-progress to Subversion</title>
          <description>&lt;p&gt;I love Subversion, but from time to time I&apos;ll stumble across a bit of SVN behaviour that just doesn&apos;t feel quite right. Case in point - you&apos;ve created 10-15 files, set up a folder structure for a new project, made rather more progress than you were expecting to, and now you want to check the whole thing into revision control. &lt;/p&gt;  &lt;p&gt;The &apos;proper&apos; way of adding existing code to a repository is via the &lt;a href=&quot;http://svnbook.red-bean.com/en/1.0/re12.html&quot;&gt;svn import&lt;/a&gt; command, but that &lt;strong&gt;doesn&apos;t turn your local folder into a Subversion working copy.&lt;/strong&gt; Having completed the import, you&apos;ll then need to move/rename/delete your work in progress, and then do an &lt;a href=&quot;http://svnbook.red-bean.com/en/1.0/re04.html&quot;&gt;svn checkout&lt;/a&gt; to download the version of your project that&apos;s now under revision control. This can take a while if you&apos;re working on big files and your repository is on the far end of a slow connection... and even when that&apos;s not applicable, it&apos;s still frustrating.&lt;/p&gt;  &lt;p&gt;So, here&apos;s how you can add a new project to Subversion without having to do the import-checkout shuffle.&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Use the repo-browser to create a new empty folder &lt;strong&gt;in the repository&lt;/strong&gt; - this will form the root folder of your new project, so call this folder /myproject/trunk or whatever you&apos;d normally use. &lt;/li&gt;    &lt;li&gt;&lt;strong&gt;Check out &lt;/strong&gt;the empty folder into the folder containing your work-in-progress project.&amp;#160; You&apos;ll get this warning - which is fine, because what you&apos;re doing is &apos;wrapping&apos; an empty SVN folder around your existing work.       &lt;p&gt;&lt;img style=&quot;border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; margin: 20px 0px; border-right-width: 0px&quot; height=&quot;363&quot; alt=&quot;image&quot; src=&quot;http://lh3.ggpht.com/dmb197/SPnmOB2deNI/AAAAAAAAAEs/v4uY3wsW7sQ/image_thumb%5B11%5D.png?imgmax=800&quot; width=&quot;644&quot; border=&quot;0&quot; /&gt;&lt;/p&gt;   &lt;/li&gt;    &lt;li&gt;You&apos;ll check out a single folder, and you&apos;ll see that your project now consists of a root folder with the happy green SVN icon, containing a bunch of folders with the question-mark overlay that means &amp;quot;Subversion doesn&apos;t know about this folder yet...&amp;quot;      &lt;p&gt;&lt;a href=&quot;http://lh3.ggpht.com/dmb197/SPnmO4euz9I/AAAAAAAAAEw/XA_QUgny3H4/s1600-h/image%5B28%5D.png&quot;&gt;&lt;img style=&quot;border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; margin: 10px 0px; border-right-width: 0px&quot; height=&quot;390&quot; alt=&quot;image&quot; src=&quot;http://lh6.ggpht.com/dmb197/SPnmPf5XvcI/AAAAAAAAAE0/4rqtkWsPtvw/image_thumb%5B14%5D.png?imgmax=800&quot; width=&quot;612&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;&lt;/p&gt;   &lt;/li&gt;    &lt;li&gt;Now you can do an svn commit in the usual way, and it&apos;s trivial to add the &apos;new&apos; files (i.e. all of them) that should be added to the repository. On the first commit, you&apos;ll need to uncheck the bin/obj folders for .NET projects, and then on the subsequent commit, you&apos;ll be able to add them to the SVN ignore list (you can only ignore a folder whose parent is already under version control) &lt;/li&gt; &lt;/ol&gt;  </description>
          <pubDate>2008-10-18T13:35:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2008/10/18/adding-work-in-progress-to-subversion.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2008/10/18/adding-work-in-progress-to-subversion.html</guid>
        </item>
      
    
      
        <item>
          <title>Googling the Zeitgeist</title>
          <description>&lt;p&gt;Just for fun, I &lt;a href=&quot;http://www.google.com/search?q=website&quot;&gt;googled &amp;quot;website&amp;quot;&lt;/a&gt;, and got&amp;#160; a little glimpse into the internet zeitgeist as Googlebot sees it. With such a generic query, it&apos;s basically comparing websites based on a lowest common denominator and, presumably, the sites with the greatest number of incoming links and highest page-rank bubble to the top.&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://www.google.com&quot;&gt;www.google.com&lt;/a&gt; tells us the most important website-related websites in the world, right now, are &lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/Website&quot;&gt;&lt;/a&gt;&lt;a href=&quot;http://lh6.ggpht.com/dmb197/SPii0JkZztI/AAAAAAAAAEg/GkgwXYtkib8/s1600-h/IMG_5280%5B4%5D.jpg&quot;&gt;&lt;img style=&quot;border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; margin: 0px 0px 20px 20px; border-right-width: 0px&quot; height=&quot;200&quot; alt=&quot;The painted shutters of Serra san Quirico, in the Marche, Italy.&quot; src=&quot;http://lh3.ggpht.com/dmb197/SPii0rSA1II/AAAAAAAAAEk/qf-24B0tirs/IMG_5280_thumb%5B2%5D.jpg?imgmax=800&quot; width=&quot;260&quot; align=&quot;right&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;Website&lt;/a&gt; - Wikipedia, the free encyclopedia &lt;/li&gt;    &lt;li&gt;Welcome to &lt;a href=&quot;http://www.barackobama.com/&quot;&gt;Obama for America&lt;/a&gt; - Barack Obama&apos;s presidential campaign website &lt;/li&gt;    &lt;li&gt;Microsoft &lt;/li&gt;    &lt;li&gt;Website.com (who are presumably here because they talk about websites a lot) &lt;/li&gt;    &lt;li&gt;Adobe &lt;/li&gt;    &lt;li&gt;Apple &lt;/li&gt;    &lt;li&gt;The IRS (US tax and revenue agency) &lt;/li&gt;    &lt;li&gt;Starbucks &lt;/li&gt;    &lt;li&gt;McDonalds &lt;/li&gt;    &lt;li&gt;Subway (restaurants) &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/Website&quot;&gt;&lt;/a&gt;&lt;/a&gt;That&apos;s interesting... Wikipedia, Barack Obama, tech companies, coffee, taxes and fast food. It&apos;s like a little summary of the daily lives of hi-tech America. (Worth noting that of the sites in that list,&amp;#160; Microsoft, Apple and Adobe have a Pagerank of 9/10, while website.com has a fairly unremarkable 6/10)&lt;/p&gt;  &lt;p&gt;Then &lt;a href=&quot;http://twitter.com/bentayloruk&quot;&gt;Ben Taylor&lt;/a&gt; tried the &lt;a href=&quot;http://www.google.co.uk/search?hl=en&amp;amp;q=website&amp;amp;btnG=Search&amp;amp;meta=cr%3DcountryUK%7CcountryGB&quot;&gt;same thing on Google UK&lt;/a&gt;, which gives us&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;The BBC &lt;/li&gt;    &lt;li&gt;Banksy (the &amp;quot;street artist&amp;quot;) &lt;/li&gt;    &lt;li&gt;en.wikipedia.org/wiki/Website &lt;/li&gt;    &lt;li&gt;Oasis (the band) &lt;/li&gt;    &lt;li&gt;The Secret Intelligence Service &lt;/li&gt;    &lt;li&gt;The British royal family &lt;/li&gt;    &lt;li&gt;Bloc Party (the band) &lt;/li&gt;    &lt;li&gt;The National Trust (an organisation that works to protect historic buildings and sites of natural beauty in the United Kingdom) &lt;/li&gt;    &lt;li&gt;Number 10 Downing Street (official residence of the Prime Minister) &lt;/li&gt;    &lt;li&gt;Shakespeare&apos;s &amp;quot;Romeo &amp;amp; Juliet&amp;quot; on Google Books. &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;That&apos;s us... Graffiti, Shakespeare, history, and rock&apos;n&apos;roll... how very British. &lt;/p&gt;  &lt;p&gt;It&apos;ll be interesting to see how those lists change over time... graphing the progress of specific topics up and down the Google &amp;quot;website&amp;quot; results over time would make for interesting viewing. Watch this space. Or rather, come back in about six months when I&apos;ve got the data, and then watch this space.&lt;/p&gt;  </description>
          <pubDate>2008-10-17T14:36:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2008/10/17/googling-zeitgeist.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2008/10/17/googling-zeitgeist.html</guid>
        </item>
      
    
      
        <item>
          <title>Updated VS2008 / Castle / NHibernate solution bundle</title>
          <description>&lt;p&gt;&lt;img style=&quot;border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; margin: 0px 20px 20px 0px; border-right-width: 0px&quot; height=&quot;279&quot; alt=&quot;image&quot; src=&quot;http://lh6.ggpht.com/dmb197/SPaKdK7LyBI/AAAAAAAAAEc/uGfX75hqXy4/image%5B6%5D.png?imgmax=800&quot; width=&quot;256&quot; align=&quot;left&quot; border=&quot;0&quot; /&gt; I&apos;ve just uploaded a new version of my &lt;a href=&quot;http://dylanbeattie.blogspot.com/2008/09/ready-to-hack-visual-studio-2008.html&quot;&gt;quick&apos;n&apos;dirty VS2008 solution&lt;/a&gt;, which now includes the log4net and Iesi.Collections projects (these were formerly being referenced from Program Files\Castle Project\ and so wouldn&apos;t build on a box without Castle installed).&amp;#160; The whole thing is now completely self-referential, so it should build &amp;amp; run without any dependencies other than the .NET framework itself, and everything&apos;s done using relative project references so you should be able to get step-debugging right down to the SQL command calls. But it&apos;s late and I haven&apos;t tried that yet.&lt;/p&gt;  &lt;p&gt;As I&apos;ve said before, this is aimed at getting something up and running with the Castle ActiveRecord stack as easily as possible, so I can play with it and see what it does. &lt;/p&gt;  &lt;p&gt;Having seen &lt;a href=&quot;http://ayende.com/Blog/archive/2007/08/06/Running-on-the-trunk-Building-Rhino-Commons.aspx&quot;&gt;Ayende&apos;s post about building Rhino-Tools&lt;/a&gt; from the various libraries&apos; SVN trunks, I&apos;m now convinced there might be a way of using SVN externals and NAnt to create a single project that automatically builds against latest trunk revisions of the various libraries - I guess this is one of those areas where only experience will tell you whether you&apos;re better off running against nightly trunk commits or just picking a stable revision and building against that, but I&apos;m sure it&apos;ll be educational finding out.&lt;/p&gt;  &lt;p&gt;You can &lt;a href=&quot;http://www.dylanbeattie.net/misc/metafink.zip&quot;&gt;get the ZIP here&lt;/a&gt; if you&apos;re interested. Again, I must restate that I didn&apos;t write any of this; log4net is distributed by &lt;a href=&quot;http://logging.apache.org/log4net/index.html&quot;&gt;Apache&lt;/a&gt;, NHibernate is from &lt;a href=&quot;http://www.nhibernate.org&quot;&gt;www.nhibernate.org&lt;/a&gt;,&amp;#160; Castle is from &lt;a href=&quot;http://www.castleproject.org&quot;&gt;www.castleproject.org&lt;/a&gt;, and all I&apos;ve done is package them together for convenience.&lt;/p&gt;  </description>
          <pubDate>2008-10-16T00:26:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2008/10/16/updated-vs2008-castle-nhibernate.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2008/10/16/updated-vs2008-castle-nhibernate.html</guid>
        </item>
      
    
      
        <item>
          <title>Cycling, Eyedroppers and the Benefits of Laziness Applied with the Proper Tools</title>
          <description>&lt;p&gt;I&amp;#160; cycle to work. I work in Leicester Square in London, and I live about three miles away. On a really good day, the trip door-to-door by public transport takes about 25 minutes. On a really bad day, you can end up stuck on a crowded bus full of angry people, in standstill traffic, for two hours, because there&apos;s a problem on the Underground on the same day they&apos;re digging up the water mains along the bus route. &lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://www.foldingbikes.co.uk/dahon_matrix.htm&quot;&gt;&lt;img style=&quot;border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; margin: 0px 20px 20px 0px; border-right-width: 0px&quot; height=&quot;180&quot; alt=&quot;Dahon Matrix 2007 - yep, it&amp;#39;s a proper bike that folds in half. That&amp;#39;s how I get it up to the fourth floor in the lift every morning.&quot; src=&quot;http://lh6.ggpht.com/dmb197/SO3MR36dXVI/AAAAAAAAAEY/uVL_R1mdbJo/matrix%5B9%5D.jpg?imgmax=800&quot; width=&quot;240&quot; align=&quot;left&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;By bike, it takes about 20 minutes, plus time to shower &amp;amp; change when you get there. But that&apos;s a constant. It doesn&apos;t vary depending on traffic or roadworks or industrial action by Underground staff.&lt;/p&gt;  &lt;p&gt;I believe that cycling is the &apos;optimum&apos; way to travel to and from work. If software development was travel, cycling would be agile, test-driven and all that jazz. It&apos;s healthy, it&apos;s cheap, it&apos;s green, it&apos;s often fun. What I really &lt;em&gt;love &lt;/em&gt;about it, though, is that even after a rotten day when I&apos;m tired and fed up and just want to go home, I still get 20 minutes of exercise on the way home, because I have to get home &lt;em&gt;somehow&lt;/em&gt;, and cycling is the fastest and easiest way to do it.&lt;/p&gt;  &lt;p&gt;Good software tools should be like bikes; they should encourage better habits by making the &lt;em&gt;right &lt;/em&gt;thing to do&amp;#160; the same as the &lt;em&gt;easy&lt;/em&gt; thing to do.&lt;/p&gt;  &lt;p&gt;Which, by a rather circuitous route, brings us to &lt;a href=&quot;http://instant-eyedropper.com/&quot;&gt;Instant Eyedropper&lt;/a&gt;, which I stumbled upon earlier this week and find myself rather smitten with. It&apos;s tiny. It&apos;s fast. It&apos;s free. It works ridiculously well. It loads on startup, sits in your system tray, and when you need a colour , you just drag it out of the system tray, drop it onto the colour you need, and it copies the appropriate hex code to your clipboard. It takes about a second - literally - and then gets the hell out of your way so you can get on with whatever you were doing.&lt;/p&gt;  &lt;p&gt;I have occasionally, in the past, &amp;quot;guessed&amp;quot; HTML colour codes on the fly because I can&apos;t face digging through CSS looking for the or opening Photoshop just to use the eyedropper tool to pick a colour off a screen capture. I&apos;ve used eyedropper tools before, but somehow they&apos;ve never quite got the formula right. With Instant Eyedropper, thought, when you&apos;re in a hurry, it&apos;s &lt;strong&gt;quicker to do it properly than it is to guess. &lt;/strong&gt;I like that. &lt;/p&gt;  &lt;p&gt;Check it out. It&apos;s free and you might like it.&lt;/p&gt;  </description>
          <pubDate>2008-10-09T09:17:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2008/10/09/cycling-eyedroppers-and-benefits-of.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2008/10/09/cycling-eyedroppers-and-benefits-of.html</guid>
        </item>
      
    
      
        <item>
          <title>ASP.NET MVC Preview 3 and Linq-to-SQL - One Month On</title>
          <description>&lt;p&gt;At the start of September, we launched a web app based on ASP.NET MVC preview 3 and Linq-to-SQL, and I&apos;m happy to say that it&apos;s generally gone really, really well.&lt;/p&gt;  &lt;p&gt;Our primary codebase is legacy ASP in JScript, but for this latest project - an online proof and payment system for actors renewing their Spotlight membership - we needed something faster, more robust and generally &lt;em&gt;better&lt;/em&gt;. I&apos;d been playing with the ASP.NET MVC previews since version 1, and while the framework&apos;s obviously still very much in development, I figured my ASP.NET background would mean a much easier learning curve than trying to pick up MVC at the same time as learning a new view engine like Brail or NVelocity. I used a Linq-to-SQL implementation of IRepository&amp;lt;T&amp;gt; based on code from &lt;a href=&quot;http://mikehadlow.blogspot.com/&quot;&gt;Mike Hadlow&apos;s&lt;/a&gt; &lt;a href=&quot;http://code.google.com/p/sutekishop/&quot;&gt;Suteki Shop&lt;/a&gt; project -the original intent was just to try it out for a couple of hours to see how it worked, but it performed so well for what we needed that I just went ahead and built the rest of the app on top of it.&lt;/p&gt;  &lt;p&gt;Linq-to-SQL clearly has some interesting potential, but it&apos;s also clearly showing right now the biggest problems with it are the slightly clunky tools (having to hack the XML to create cascade delete relationships, no way to refresh a table in the LINQ designer if the schema&apos;s changed, for example) and the big question mark hanging over its future. Between Entity Framework and the various open-source OR mappers competing for mindshare, not to mention talk of a Linq-to-NHibernate implementation, it&apos;s really not clear whether Linq-to-SQL will ever see another release which fixes the problems with the current release, or whether it&apos;s just going to be quietly retired as a historical curiosity.&lt;/p&gt;  &lt;p&gt;ASP.NET MVC, on the other hand, looks like it&apos;s really going to go places - especially with the news that &lt;a href=&quot;http://weblogs.asp.net/scottgu/archive/2008/09/28/jquery-and-microsoft.aspx&quot;&gt;Microsoft are going to be shipping - and supporting - jQuery with ASP.NET MVC and Visual Studio&lt;/a&gt;. The day I get my hands on Intellisense for jQuery will be a good day indeed. I can&apos;t wait.&lt;/p&gt;  </description>
          <pubDate>2008-10-02T13:52:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2008/10/02/aspnet-mvc-preview-3-and-linq-to-sql.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2008/10/02/aspnet-mvc-preview-3-and-linq-to-sql.html</guid>
        </item>
      
    
      
        <item>
          <title>A Ready-To-Hack Visual Studio 2008 Solution including NHibernate and Castle Active Record</title>
          <description>&lt;p&gt;&lt;a href=&quot;http://lh5.ggpht.com/dmb197/SOKmiwq_iBI/AAAAAAAAAEQ/kc1Bjj6md0Q/s1600-h/metafink%5B6%5D.jpg&quot;&gt;&lt;img style=&quot;border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; margin: 0px 0px 20px 20px; border-right-width: 0px&quot; height=&quot;270&quot; alt=&quot;metafink&quot; src=&quot;http://lh6.ggpht.com/dmb197/SOKmjSTq3zI/AAAAAAAAAEU/yejlkWkBBkg/metafink_thumb%5B4%5D.jpg?imgmax=800&quot; width=&quot;260&quot; align=&quot;right&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;I&apos;ve been taking my first steps into the wonderful world of NHibernate and Castle ActiveRecord, and to make things easier, I&apos;ve put together a Visual Studio 2008 solution that &lt;em&gt;should&lt;/em&gt; build NHibernate, Castle Core, Castle Windsor, Castle Microkernel and Castle ActiveRecord from source, just by firing it up in VS2008 and hitting Ctrl-Shift-B, so you can load it, build it, run it, and start hacking around and getting your hands dirty.&lt;/p&gt;  &lt;p&gt;I didn&apos;t actually create any&amp;#160; of this - NHibernate is from &lt;a href=&quot;http://www.nhibernate.org&quot;&gt;www.nhibernate.org&lt;/a&gt;, Castle is from &lt;a href=&quot;http://www.castleproject.org&quot;&gt;www.castleproject.org&lt;/a&gt;. Thing is, these projects are all interdependent to some extent, the official binaries aren&apos;t always up-to-date or in sync with each other, and building them from source means setting up nant, working through various machine-specific configuration glitches... so hopefully this will save you a bit of head-scratching. I&apos;ve just done an svn trunk checkout from the various projects over the last few days, built each project using nant as per the included instructions, then extracted the Visual Studio projects for the actual runtime libraries (so I&apos;ve left out unit tests, etc.) and combined it into a single solution, along with a very rudimentary ActiveRecord model project, a simple console app showing how to get things up and running using app.config, and a SQL Express .mdf file containing some test data to make sure it&apos;s working.&lt;/p&gt;  &lt;p&gt;This is undocumented and full of holes - but if, like me, you&apos;ve decided it&apos;s time to learn this stuff and you just want a working build to play around with, it&apos;ll probably save you a couple of hours.&lt;/p&gt;  &lt;p&gt;Oh, and it&apos;s called Metafink. No reason - things just need a name. &lt;/p&gt;  &lt;p&gt;Download it from &lt;a href=&quot;http://www.dylanbeattie.net/misc/metafink.zip&quot;&gt;http://www.dylanbeattie.net/misc/metafink.zip&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;EDIT: &lt;/strong&gt;Turns out this isn&apos;t quite complete - trying to build it on a machine that had never had any of this stuff on it before, and it appears that&amp;#160; log4net and a couple of other DLLs that are required by NHibernate are missing from the package. For the sake of completeness (and being able to step-debug through the whole stack), I&apos;ll try and incorporate these as projects rather than just linking to the binaries - should get this sorted out later today / tonight. Sorry!&lt;/p&gt;  </description>
          <pubDate>2008-09-30T22:22:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2008/09/30/ready-to-hack-visual-studio-2008.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2008/09/30/ready-to-hack-visual-studio-2008.html</guid>
        </item>
      
    
      
        <item>
          <title>Making Your SVN mod_dav_svn Repository Firefox-Friendly</title>
          <description>&lt;p&gt;There&apos;s a great add-on module for Subversion - &lt;a href=&quot;http://svnbook.red-bean.com/en/1.4/svn.serverconfig.httpd.html&quot;&gt;mod_dav_svn&lt;/a&gt;, which I&apos;ve &lt;a href=&quot;http://dylanbeattie.blogspot.com/2008/05/subversion-webdav-wiki-cool-fact.html&quot;&gt;blogged about before&lt;/a&gt; - that exposes the contents of your repository through a Web server interface. This is great for bringing up designs, ideas and HTML prototypes in meetings - we&apos;ve got one of those interactive whiteboard things, and we&apos;ve&amp;#160; saved lots of time, and probably a couple of acres of forest, by showing designs on the screen instead of printing handouts.&lt;/p&gt;  &lt;p&gt;This doesn&apos;t quite work out-of-the-box, though. It&apos;ll sort-of work if you&apos;re using Internet Explorer to browse your WebDAV repository, but Firefox and Opera will probably display everything as plain text. Or gibberish. This is because Apache is sending a Content-Type header telling the browser that the content is text/plain, and Apache in turn is getting this information directly from Subversion. To get everything displaying properly, you&apos;ll need to make sure that every file in your repository has the proper MIME type associated with it in Subversion.&lt;/p&gt;  &lt;h3&gt;Using auto-props to set MIME types automatically when adding files to a repository&lt;/h3&gt;  &lt;p&gt;This bit depends on your client. Check out the &lt;a href=&quot;http://svnbook.red-bean.com/nightly/en/svn.advanced.props.html#svn.advanced.props.auto&quot;&gt;official documentation on the auto-props feature&lt;/a&gt;; it&apos;s also worth knowing that you can open the svn configuration file in Notepad via the handy Edit button in the Tortoise settings dialog - right-click any folder window in Windows Explorer, hit TortoiseSVN -&amp;gt; Settings... &lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh3.ggpht.com/dmb197/SM7G8dIrGGI/AAAAAAAAAEI/_hWtKqH5jqQ/image%5B11%5D.png&quot;&gt;&lt;img style=&quot;border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px&quot; height=&quot;264&quot; alt=&quot;image&quot; src=&quot;http://lh3.ggpht.com/dmb197/SM7G9KJMc7I/AAAAAAAAAEM/D_FLpLgtRlo/image_thumb%5B7%5D.png&quot; width=&quot;400&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;&amp;#160;&lt;/p&gt;  &lt;h4&gt;To update files already in the repository&lt;/h4&gt;  &lt;p&gt;The auto-props feature is all very well, but if (as I did) you don&apos;t find out about it until you&apos;ve already got a repository full of stuff, you have a second problem - how do you set the MIME-type properties on everything that&apos;s already in your repository?&lt;/p&gt;  &lt;p&gt;This works on Windows, via the command shell, and needs the command-line version of svn installed - try the &lt;a href=&quot;http://www.sliksvn.com/en/download&quot;&gt;SlikSVN installer&lt;/a&gt; if you don&apos;t have svn already installed. Remember that although your repository is probably organised into projects, with their own trunks, tags and branches, it&apos;s still just a great big hierarchy of files and folders - if you do an svn checkout from svn://my.subversion.server/ without specifying /myproject/trunk, &lt;strong&gt;you will check out the HEAD version of your entire repository.&lt;/strong&gt; (These techniques work just as well on individual trunks, branches and sub-folders, of course.)&lt;/p&gt;  &lt;p&gt;First, check out the folder, branch or even the entire repository into a working folder - say c:\repository\. &lt;/p&gt;  &lt;p&gt;Then run this:&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;C:\repository&amp;gt;for /r %1 in (*.gif) do svn propset svn:mime-type image/gif &amp;quot;%~f1&amp;quot;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;for&lt;/strong&gt; is the Windows shell command that basically says &amp;quot;repeat the following for every file matching this specification&amp;quot; - and we&apos;re saying &lt;strong&gt;for /r %1 in (*.gif), &lt;/strong&gt;meaning &amp;quot;recursively find every file matching *.gif in or below the current folder, temporarily reference that file as %1, and run the following command&amp;quot; - where the command itself is &lt;strong&gt;svn propset svn:mime-type image/gif &amp;quot;%~f1&amp;quot;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;Note that the %1 reference there is quoted, and we&apos;re using the &lt;strong&gt;~f&lt;/strong&gt; modifier to expand it to the full path - you may find&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;C:\repository&amp;gt;for /r %1 in (*.gif) do echo &amp;quot;%~f1&amp;quot;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;enlightening if this doesn&apos;t make sense - remember, everything after the &lt;strong&gt;do&lt;/strong&gt; is invoked for each matching file.&lt;/p&gt;  &lt;p&gt;So, when &lt;strong&gt;for&lt;/strong&gt; matches something.gif under myproject\trunk in your repository, it&apos;ll call svn.exe with the command line &lt;/p&gt;  &lt;p&gt;&lt;strong&gt;svn propset mime-type image/gif &amp;quot;C:\repository\myproject\trunk\something.gif&amp;quot;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;- which will set the MIME-type on something.gif to image/gif.&lt;/p&gt;  &lt;p&gt;Repeat this incantation using the various file extensions and MIME types you need to configure, e.g.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;C:\repository&amp;gt;for /r %1 in (*.jpg) do svn propset svn:mime-type image/jpeg &amp;quot;%~f1&amp;quot;      &lt;br /&gt;&lt;/strong&gt;&lt;strong&gt;C:\repository&amp;gt;for /r %1 in (*.htm*) do svn propset svn:mime-type text/html &amp;quot;%~f1&amp;quot;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;and once you&apos;re done, commit your changes back to the repository. You&apos;ll see a whole lot of SVN &amp;quot;Property changed&amp;quot; messages, and next time you browse your repository via mod_dav_svn, you should find things are working as expected.    &lt;/p&gt;  </description>
          <pubDate>2008-09-30T20:11:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2008/09/30/making-your-svn-moddavsvn-repository.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2008/09/30/making-your-svn-moddavsvn-repository.html</guid>
        </item>
      
    
      
        <item>
          <title>Colour transformation in .NET and GDI+... aka &quot;What Is The Matrix?&quot;</title>
          <description>&lt;p&gt;I&apos;ve been working on some code that converts colour photographs uploaded by users to black-and-white. To do this, I&apos;m using the following code to render an image onto a canvas, using GDI+, and applying a colour transformation in the process:&lt;/p&gt;  &lt;pre class=&quot;code&quot; style=&quot;padding-right: 8px; padding-left: 8px; padding-bottom: 8px; padding-top: 8px; font-family: consolas, andale mono, monospaced; background-color: black&quot;&gt;&lt;span style=&quot;background: black; color: #40c4ff&quot;&gt;private &lt;/span&gt;&lt;span style=&quot;background: black; color: cyan&quot;&gt;Bitmap &lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;ApplyMatrix&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;background: black; color: cyan&quot;&gt;Bitmap &lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;source&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;) {
    &lt;/span&gt;&lt;span style=&quot;background: black; color: cyan&quot;&gt;ColorMatrix &lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;matrix &lt;/span&gt;&lt;span style=&quot;background: black; color: silver&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;background: black; color: #7cfc00&quot;&gt;//TODO: determine appropriate colour matrix! 

    &lt;/span&gt;&lt;span style=&quot;background: black; color: cyan&quot;&gt;Bitmap &lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;result &lt;/span&gt;&lt;span style=&quot;background: black; color: silver&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;background: black; color: #40c4ff&quot;&gt;new &lt;/span&gt;&lt;span style=&quot;background: black; color: cyan&quot;&gt;Bitmap&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;source&lt;/span&gt;&lt;span style=&quot;background: black; color: silver&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;Width&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;source&lt;/span&gt;&lt;span style=&quot;background: black; color: silver&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;Height&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;);
    &lt;/span&gt;&lt;span style=&quot;background: black; color: cyan&quot;&gt;Rectangle &lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;sourceRectangle &lt;/span&gt;&lt;span style=&quot;background: black; color: silver&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;background: black; color: #40c4ff&quot;&gt;new &lt;/span&gt;&lt;span style=&quot;background: black; color: cyan&quot;&gt;Rectangle&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;background: black; color: #ff80ff&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;background: black; color: #ff80ff&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;source&lt;/span&gt;&lt;span style=&quot;background: black; color: silver&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;Width&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;source&lt;/span&gt;&lt;span style=&quot;background: black; color: silver&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;Height&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;);
    &lt;/span&gt;&lt;span style=&quot;background: black; color: #40c4ff&quot;&gt;using &lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;background: black; color: cyan&quot;&gt;Graphics &lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;g &lt;/span&gt;&lt;span style=&quot;background: black; color: silver&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;background: black; color: cyan&quot;&gt;Graphics&lt;/span&gt;&lt;span style=&quot;background: black; color: silver&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;FromImage&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;)) {
        &lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;g&lt;/span&gt;&lt;span style=&quot;background: black; color: silver&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;SmoothingMode &lt;/span&gt;&lt;span style=&quot;background: black; color: silver&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;background: black; color: cyan&quot;&gt;SmoothingMode&lt;/span&gt;&lt;span style=&quot;background: black; color: silver&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;HighQuality&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;;
        &lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;g&lt;/span&gt;&lt;span style=&quot;background: black; color: silver&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;CompositingQuality &lt;/span&gt;&lt;span style=&quot;background: black; color: silver&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;background: black; color: cyan&quot;&gt;CompositingQuality&lt;/span&gt;&lt;span style=&quot;background: black; color: silver&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;HighQuality&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;;
        &lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;g&lt;/span&gt;&lt;span style=&quot;background: black; color: silver&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;InterpolationMode &lt;/span&gt;&lt;span style=&quot;background: black; color: silver&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;background: black; color: cyan&quot;&gt;InterpolationMode&lt;/span&gt;&lt;span style=&quot;background: black; color: silver&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;HighQualityBicubic&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;;

        &lt;/span&gt;&lt;span style=&quot;background: black; color: cyan&quot;&gt;ImageAttributes &lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;ia &lt;/span&gt;&lt;span style=&quot;background: black; color: silver&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;background: black; color: #40c4ff&quot;&gt;new &lt;/span&gt;&lt;span style=&quot;background: black; color: cyan&quot;&gt;ImageAttributes&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;();
        &lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;ia&lt;/span&gt;&lt;span style=&quot;background: black; color: silver&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;SetColorMatrix&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;matrix&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;);

        &lt;/span&gt;&lt;span style=&quot;background: black; color: cyan&quot;&gt;Point &lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;upperLeft &lt;/span&gt;&lt;span style=&quot;background: black; color: silver&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;background: black; color: #40c4ff&quot;&gt;new &lt;/span&gt;&lt;span style=&quot;background: black; color: cyan&quot;&gt;Point&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;background: black; color: #ff80ff&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;background: black; color: #ff80ff&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;);
        &lt;/span&gt;&lt;span style=&quot;background: black; color: cyan&quot;&gt;Point &lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;upperRight &lt;/span&gt;&lt;span style=&quot;background: black; color: silver&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;background: black; color: #40c4ff&quot;&gt;new &lt;/span&gt;&lt;span style=&quot;background: black; color: cyan&quot;&gt;Point&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;background: black; color: silver&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;Width&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;background: black; color: #ff80ff&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;);
        &lt;/span&gt;&lt;span style=&quot;background: black; color: cyan&quot;&gt;Point &lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;lowerLeft &lt;/span&gt;&lt;span style=&quot;background: black; color: silver&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;background: black; color: #40c4ff&quot;&gt;new &lt;/span&gt;&lt;span style=&quot;background: black; color: cyan&quot;&gt;Point&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;background: black; color: #ff80ff&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;background: black; color: silver&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;Height&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;);
        &lt;/span&gt;&lt;span style=&quot;background: black; color: cyan&quot;&gt;Point&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;[] &lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;destinationPoints &lt;/span&gt;&lt;span style=&quot;background: black; color: silver&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;background: black; color: #40c4ff&quot;&gt;new &lt;/span&gt;&lt;span style=&quot;background: black; color: cyan&quot;&gt;Point&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;[] { &lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;upperLeft&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;upperRight&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;lowerLeft &lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;};
        &lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;g&lt;/span&gt;&lt;span style=&quot;background: black; color: silver&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;DrawImage&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;source&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;destinationPoints&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;sourceRectangle&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;background: black; color: cyan&quot;&gt;GraphicsUnit&lt;/span&gt;&lt;span style=&quot;background: black; color: silver&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;Pixel&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;ia&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;);
    }
    &lt;/span&gt;&lt;span style=&quot;background: black; color: #40c4ff&quot;&gt;return &lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;background: black; color: #eddac0&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;background: black; color: #e0e0e0&quot;&gt;);
}
&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;The key to these colour transformations is a ColorMatrix - the GDI+ colour model lets you treat the &amp;lt;R,G,B&amp;gt; elements of a colour as a point in three-dimensional colour space, and apply geometric transformations to that point using matrix multiplication. There&apos;s some in-depth discussion of this at &lt;a title=&quot;http://www.codeproject.com/KB/GDI-plus/colormatrix.aspx&quot; href=&quot;http://www.codeproject.com/KB/GDI-plus/colormatrix.aspx&quot;&gt;http://www.codeproject.com/KB/GDI-plus/colormatrix.aspx&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;&amp;quot;Nobody can be told what the matrix is... you have to see it for yourself.&amp;quot;&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;http://lh6.ggpht.com/dmb197/SJ1sQ-I1feI/AAAAAAAAAEA/uJuNvxUzYUM/s1600-h/image%5B4%5D.png&quot;&gt;&lt;img style=&quot;border-right: 0px; border-top: 0px; border-left: 0px; border-bottom: 0px&quot; height=&quot;255&quot; alt=&quot;image&quot; src=&quot;http://lh5.ggpht.com/dmb197/SJ1sRg_dehI/AAAAAAAAAEE/Rl7DTtZj1us/image_thumb%5B2%5D.png?imgmax=800&quot; width=&quot;324&quot; align=&quot;right&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;Even once you&apos;ve got your head around the underlying mathematics, it&apos;s still not easy to work out what matrix values you need to achieve a particular result, so I&apos;ve hacked together a WinForms app that&apos;ll let you tweak the values in real time and see what effect they have.&amp;#160; Nothing fancy - just a bunch of numeric up/down boxes, with tooltips explaining what effect they&apos;ll have on the resulting image.&lt;/p&gt;

&lt;p&gt;You can download ColorMatrixLab (source and binary) here:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Download &lt;a title=&quot;Download source project for ColorMatrixLab&quot; href=&quot;http://www.dylanbeattie.net/misc/colormatrixlab.zip&quot;&gt;ColorMatrixLab.zip&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(requires the Microsoft .NET 2.0 framework.)&lt;/p&gt;  </description>
          <pubDate>2008-08-09T10:07:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2008/08/09/colour-transformation-in-net-and-gdi.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2008/08/09/colour-transformation-in-net-and-gdi.html</guid>
        </item>
      
    
      
        <item>
          <title>Crazy from the Heat...</title>
          <description>&lt;p&gt;Computers don&apos;t like heat. Apparently. Years ago, I was putting together a system for my brother based on one of the old AMD Athlon CPUs.&amp;#160; Built it, tested it, installed Windows, everything running beautifully. Fire it up an hour or two before he arrives to pick it up... it bluescreens and won&apos;t boot. Open up the case, check everything&apos;s seated properly... you know the drill. It&apos;s all fine, of course. Three hours later, I still can&apos;t work out what&apos;s wrong. Every component is fine. Every diagnostic passes. The disks are fine. The memory is fine. Eventually, and completely by chance, I actually move the case off the desk onto the floor whilst it&apos;s running... and it crashes. Turns out the heatsink clamp was ever-so-slightly bent out of shape. Unlike the LGA775 heatsinks of today with their wonderfully-engineered motherboard mountings, the old Athlon heatsinks just clipped onto the plastic CPU socket, and what was happening was that when the box - a generic mini-tower case - was up on the desk, &lt;strong&gt;on its side&lt;/strong&gt;, running tests and diagnostics, everything was fine. When I put it back together and flipped it the right way up - i.e. standing vertically - the weight of the heatsink combined with the bent clip was just enough to pull the heatsink out of contact with the CPU, which would then shoot up to 96&amp;#176;C and crash spectacularly. A new heatsink clip and some arctic silver and it worked perfectly. &lt;/p&gt;  &lt;p&gt;Anyway. Moral of the story is, in my experience, PCs go funny in the summer. Whether it&apos;s the heat or just plain coincidence I don&apos;t know, but they do. And when they do, the first thing to check - &lt;strong&gt;always&lt;/strong&gt; - is the memory. Get the &lt;a href=&quot;http://www.ultimatebootcd.com/&quot;&gt;Ultimate Boot CD&lt;/a&gt;, load up MemTest86, and let it run overnight. (If anything&apos;s wrong, it generally shows up in about two minutes... but if it&apos;ll run overnight without any problems, your RAM is almost certainly OK.)&lt;/p&gt;  &lt;p&gt;Faulty memory creates the most bewildering array of crashes, faults, errors and bluescreens I have ever seen. Having inadvertently run a system with a stick of bad RAM for a couple of weeks, I would at various points have sworn it was the RAID controller, the hard drive, the video card, Windows, the printer driver - in fact, pretty much every component of the system seemed to have caused it to crash at one point or another. I&apos;d ignored the possibility of the memory, because the system in question isn&apos;t that old and it was tested when I put it together... I was wrong, and just running Memtest86 in the first place would have saved literally hours of troubleshooting and head-scratching.&lt;/p&gt;  </description>
          <pubDate>2008-07-28T23:30:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2008/07/28/crazy-from-heat.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2008/07/28/crazy-from-heat.html</guid>
        </item>
      
    
      
        <item>
          <title>Gadgets and Gizmos Time...</title>
          <description>&lt;p&gt;We&apos;re on a green drive. London is having a full-fledged heatwave, the temperature is regularly hitting 30&amp;#176;, and sitting in a room surrounded by electrical equipment that basically sits there spitting out heat all day suddenly doesn&apos;t seem like such a good idea. When you realise that all that heat is basically wasted electricity - that I&apos;m &lt;strong&gt;paying&lt;/strong&gt; for, and then trying desperately to push out of my windows to cool the room down - a little geek spring-cleaning suddenly seems like a pretty good idea.&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://catalog.belkin.com/IWCatProductPage.process?Product_Id=377018&quot;&gt;&lt;img style=&quot;border-right: 0px; border-top: 0px; border-left: 0px; border-bottom: 0px&quot; height=&quot;240&quot; alt=&quot;Belkin N1 Vision&quot; src=&quot;http://lh5.ggpht.com/dmb197/SI5SJxe_80I/AAAAAAAAAD4/HEEcuHdEg5k/belkin-n1%5B5%5D.jpg&quot; width=&quot;150&quot; align=&quot;right&quot; border=&quot;0&quot; /&gt;&lt;/a&gt; First off, last month I replaced the motley collection of switches, DSL routers and wireless access points - along with their accompanying individual power supplies - with a &lt;a href=&quot;http://catalog.belkin.com/IWCatProductPage.process?Product_Id=377018&quot;&gt;Belkin N1 Vision&lt;/a&gt;. This truly wonderful gizmo combines a DSL router, four Gigabit wired ethernet ports and draft 802.11n, in a really innovative package. The LCD readout on the front is frankly genius. Download monitor; wi-fi status readout; desk clock, DSL speed meter - it&apos;s intuitive, versatile and just very, very cool. And it works. I don&apos;t have to keep unplugging it to get wi-fi working again, like I did with the old one. The DSL automatically comes back on when power is restored (a major gripe I had with my old Speedtouch router). I love it. My computers love it. Maria&apos;s iPhone &lt;em&gt;really&lt;/em&gt; loves it. And it&apos;s fast enough to watch movies over wi-fi from pretty much anywhere in the house.&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://www.wdc.com/en/products/products.asp?DriveID=279&quot;&gt;&lt;img style=&quot;border-right: 0px; border-top: 0px; border-left: 0px; border-bottom: 0px&quot; height=&quot;240&quot; alt=&quot;wdfMyBook_World_2N&quot; src=&quot;http://lh6.ggpht.com/dmb197/SI5SKR2iQ3I/AAAAAAAAAD8/B7HeEM5Jkds/wdfMyBook_World_2N%5B6%5D.jpg&quot; width=&quot;240&quot; align=&quot;left&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;The really neat part, though, is a &lt;a href=&quot;http://www.wdc.com/en/products/products.asp?DriveID=279&quot;&gt;Western Digital My Book World Edition II&lt;/a&gt;. It&apos;s basically two cheap SATA hard drives in a box, with a fan and a low-powered CPU running a cut-down version of Linux, and some dreadful but completely optional Mionet software. You might remember these as the subject of some truly awful press coverage last year. The truth is, the supplied (but optional) MioNet software won&apos;t share certain file types &lt;strong&gt;with anonymous users over a public-facing connection&lt;/strong&gt;. The press picked this up as &amp;quot;network hard drive won&apos;t share files&amp;quot; - which is almost total bollocks - and ran with it.&lt;/p&gt;  &lt;p&gt;But I digress. Despite the &lt;a href=&quot;http://review.zdnet.com/external-hard-drives/western-digital-mybook-world/4505-3190_16-32401221.html&quot;&gt;apparently slow network interface&lt;/a&gt;, it works - easy to set up, easy to get files onto it, easy to get them off again. The fun really starts, though, when you work out how to get &lt;a href=&quot;http://mybookworld.wikidot.com/hacks-and-howto&quot;&gt;console access to the onboard Linux OS&lt;/a&gt;. (Neat tip - once you&apos;ve got SSH access, it&apos;s much quicker to transfer big files - music, video, etc. - by copying them onto an external USB drive, plugging it into the back of the MyBook, SSHing into the console and copying it onto the internal HD using Linux &apos;cp&apos; command, than to copy them over the network.) With a couple of evening&apos;s tinkering and copius assistance from the wiki at &lt;a title=&quot;http://mybookworld.wikidot.com/&quot; href=&quot;http://mybookworld.wikidot.com/&quot;&gt;mybookworld.wikidot.com&lt;/a&gt;, it&apos;s now acting as an iTunes media server (via &lt;a href=&quot;http://mediatomb.cc/&quot;&gt;MediaTomb&lt;/a&gt;) and print server. The kind of stuff I used to leave a &amp;quot;proper&amp;quot; PC running 24/7 to do. The print server, in particular, is very neat - any computer in the house, including wireless, printing to a completely normal USB Canon Pixma iP5300, and it&apos;s even smart enough to work with the printer&apos;s power-save features... send a print job, printer wakes up, prints it, and goes back to sleep with nary so much as a standby light.&lt;/p&gt;  &lt;p&gt;Basically, the &amp;quot;infrastructure&amp;quot; - file server, wi-fi, DSL, printing support - that all the other computers rely on is now running on two small appliances and a printer that only switches itself on when it&apos;s needed. I&apos;m going to get one of those electricity meter things to get some hard stats on how much power this lot actually draws, but in the meantime it&apos;s definitely cooler and quieter than a full-blown server and pile of networking gear - and I figure that has to be good for the electricity bill and the planet. Not to mention how much fun it is doing things that aren&apos;t supposed to be possible in the first place. &lt;/p&gt;  </description>
          <pubDate>2008-07-28T23:11:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2008/07/28/gadgets-and-gizmos-time.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2008/07/28/gadgets-and-gizmos-time.html</guid>
        </item>
      
    
      
        <item>
          <title>Strongly-Typed View References with ASP.NET MVC Preview 3</title>
          <description>&lt;p&gt;Two short methods that&apos;ll give you compile-time type checking for your ASP.NET MVC views:&lt;/p&gt;  &lt;pre class=&quot;code&quot;&gt;&lt;span style=&quot;color: gray&quot;&gt;///&amp;lt;summary&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: green&quot;&gt;Render the view whose implementation is defined by the supplied type.&lt;/span&gt;&lt;span style=&quot;color: gray&quot;&gt;&amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;span style=&quot;color: blue&quot;&gt;protected &lt;/span&gt;&lt;span style=&quot;color: #2b91af&quot;&gt;ActionResult &lt;/span&gt;View(&lt;span style=&quot;color: #2b91af&quot;&gt;Type &lt;/span&gt;viewType, &lt;span style=&quot;color: blue&quot;&gt;object &lt;/span&gt;viewData) {
    &lt;span style=&quot;color: blue&quot;&gt;return&lt;/span&gt;(View(viewType.Name, viewData));
}

&lt;span style=&quot;color: gray&quot;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: green&quot;&gt;Render the view whose implementation is defined by the supplied type.&lt;/span&gt;&lt;span style=&quot;color: gray&quot;&gt;&amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;span style=&quot;color: blue&quot;&gt;protected &lt;/span&gt;&lt;span style=&quot;color: #2b91af&quot;&gt;ActionResult &lt;/span&gt;View(&lt;span style=&quot;color: #2b91af&quot;&gt;Type &lt;/span&gt;viewType) {
    &lt;span style=&quot;color: blue&quot;&gt;return&lt;/span&gt;(View(viewType.Name));
}&lt;/pre&gt;

&lt;p&gt;I&apos;ve added these methods to a BaseController : Controller class, and my MVC controllers then inherit from my custom base class, but you could always add them via the extension-method syntax to the ordinary Controller supplied with ASP.NET MVC.&lt;/p&gt;

&lt;p&gt;This means you can call your View() methods via a type reference that&apos;s checked by the compiler when you build, so instead of:&lt;/p&gt;

&lt;pre class=&quot;code&quot;&gt;&lt;span style=&quot;color: blue&quot;&gt;public &lt;/span&gt;&lt;span style=&quot;color: #2b91af&quot;&gt;ActionResult &lt;/span&gt;Info(&lt;span style=&quot;color: blue&quot;&gt;int &lt;/span&gt;id) {
    &lt;span style=&quot;color: #2b91af&quot;&gt;Movie &lt;/span&gt;movie = dataContext.Movies.Single&amp;lt;&lt;span style=&quot;color: #2b91af&quot;&gt;Movie&lt;/span&gt;&amp;gt;(m =&amp;gt; m.Id == id);
    &lt;span style=&quot;color: blue&quot;&gt;return &lt;/span&gt;(View(&amp;quot;Info&amp;quot;, movie));
}&lt;/pre&gt;

&lt;p&gt;&lt;a href=&quot;http://11011.net/software/vspaste&quot;&gt;&lt;/a&gt;you can say&lt;/p&gt;

&lt;pre class=&quot;code&quot;&gt;&lt;span style=&quot;color: blue&quot;&gt;public &lt;/span&gt;&lt;span style=&quot;color: #2b91af&quot;&gt;ActionResult &lt;/span&gt;Info(&lt;span style=&quot;color: blue&quot;&gt;int &lt;/span&gt;id) {
    &lt;span style=&quot;color: #2b91af&quot;&gt;Movie &lt;/span&gt;movie = dataContext.Movies.Single&amp;lt;&lt;span style=&quot;color: #2b91af&quot;&gt;Movie&lt;/span&gt;&amp;gt;(m =&amp;gt; m.Id == id);
    &lt;span style=&quot;color: blue&quot;&gt;return &lt;/span&gt;(View(&lt;span style=&quot;color: blue&quot;&gt;typeof&lt;/span&gt;(Views.Movie.&lt;span style=&quot;color: #2b91af&quot;&gt;Info&lt;/span&gt;), movie));
}&lt;/pre&gt;

&lt;p&gt;and the reference to typeof(Views.Movie.Info) will be type-checked when you compile, so renaming or moving your view classes will cause a broken build until you fix the controllers that refer to them.&lt;/p&gt;  </description>
          <pubDate>2008-05-28T00:49:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2008/05/28/strongly-typed-view-references-with.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2008/05/28/strongly-typed-view-references-with.html</guid>
        </item>
      
    
      
        <item>
          <title>Let&apos;s Usurp &quot;Web 3.0&quot; and Do Something Useful With It...</title>
          <description>&lt;p&gt;Everyone&apos;s been talking about Web 2.0 for a while now, and I still get the feeling no-one really knows what it is. I think Stephen Fry&apos;s description of web 2.0 as &amp;quot;genuine interactivity, if you like, simply because people can upload as well as download.&amp;quot; comes close to my understanding of the phenomena... but that&apos;s not really the point. The point is, &amp;quot;web two point oh&amp;quot; sounds cool. Tim O&apos;Reilly probably knew this when he coined the phrase. People and companies want web 2.0, &lt;em&gt;despite&lt;/em&gt; the fact that they&apos;re not really sure what it is, because it sounds cool.&lt;/p&gt;  &lt;p&gt;On the one hand, we have web 2.0 mash-ups and tag clouds and Ajax and all that lovely interactive multimedia goodness. On the other hand, we have web standards. Standards are not as cool as Web 2.0. They sound a bit... boring, frankly (and the W3C spec documents really don&apos;t help with this. Informative, yes - but readable?) Many companies would rather spend their time and money investing in potential revenue sources instead of the endless hours of testing and tweaking that&apos;s involved in getting semantically clean, standards-compliant pages that look good and work across all modern browsers... and as soon as they want something clever and interactive, they reach for Flash.&lt;/p&gt;  &lt;p&gt;IE8 is coming, and will supposedly offer the standards support that we&apos;ve all been waiting for. Joel Spolsky has written &lt;a href=&quot;http://www.joelonsoftware.com/items/2008/03/17.html&quot;&gt;this post&lt;/a&gt; about the fact that there really isn&apos;t an acceptable compromise between standards compliance and backward compatibility. Either you follow the standards and break old sites, or you maintain bugwards compatibility at the expense of standards compliance. &lt;/p&gt;  &lt;p&gt;When you say &amp;quot;IE8&apos;s default rendering view conforms to the W3C XHTML+CSS standards&amp;quot;, people yawn. I mean, c&apos;mon. Double-you-three-ex-aitch-cee-ess-what?&amp;#160; &lt;/p&gt;  &lt;p&gt;So how about if we just take a reasonable baseline set&amp;#160; W3C guidelines - &lt;a href=&quot;http://www.w3.org/TR/xhtml11/&quot;&gt;XHTML 1.1&lt;/a&gt;, &lt;a href=&quot;http://www.w3.org/TR/CSS21/&quot;&gt;CSS 2.1&lt;/a&gt;, &lt;a href=&quot;http://www.w3.org/TR/XMLHttpRequest/&quot;&gt;XmlHttpRequest&lt;/a&gt; - and say that &amp;quot;Web 3.0&amp;quot; means full, complete support for those standards? It could be that simple. IE8 can be a Web 3.0 browser. Firefox 3 can be a Web 3.0 browser; Opera 10 can be a Web 3.0 browser (if Opera 9 isn&apos;t already, that is). Google SpacePirate or whatever they think of next will be a Web 3.0 application, which works as intended on any web 3.0 browser. Technically, it&apos;s exactly the same as what&apos;s going on right now - but I wonder what&apos;ll happen if we slap a cool name on it and make standards sound like the Next Big Thing? &lt;/p&gt;  </description>
          <pubDate>2008-05-16T10:25:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2008/05/16/let-usurp-30-and-do-something-useful.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2008/05/16/let-usurp-30-and-do-something-useful.html</guid>
        </item>
      
    
      
        <item>
          <title>The Future of Subversion?</title>
          <description>&lt;p&gt;Following a &lt;a href=&quot;http://blog.red-bean.com/sussman/?p=90&quot; target=&quot;_blank&quot;&gt;blog post&lt;/a&gt; by &lt;a href=&quot;http://www.red-bean.com/sussman/&quot; target=&quot;_blank&quot;&gt;Ben Collins-Sussman&lt;/a&gt;, one of the original developers of Subversion, about the future of the open-source revision control system, &lt;a href=&quot;http://tech.slashdot.org/tech/08/05/09/1528250.shtml&quot; target=&quot;_blank&quot;&gt;Slashdot asks&lt;/a&gt; &amp;quot;is there still a need for centralized version control in some environments?&amp;quot;&lt;/p&gt;  &lt;p&gt;Frankly - yes, there is. Just like we&apos;re always going to need one-bedroom apartments, even though most people end up getting a bigger place sooner or later. For some people, it&apos;s all they&apos;ll ever need - and for other people, it will &lt;em&gt;always&lt;/em&gt; be an important stepping-stone on the path to bigger and better things.&lt;/p&gt;  &lt;p&gt;I wrote my first program on an Amstrad 6128, many years ago. Committing a revision meant saving it onto a 3&amp;quot; floppy disk and putting it in a desk drawer. Tagging meant I&apos;d write &amp;quot;dylan - hello.bas&amp;quot; on the label in pen instead of pencil, and then ask Dad for another disk. That approach - saving frequently and using offline copies for important milestones - got me through school, college, university and my first four years of gainful employment. These were all self-contained, short-term projects of one sort or another, and generally I&apos;d be the only developer working on them. I didn&apos;t really get what revision control was, because I wasn&apos;t aware of having any problems to which it offered a better solution. &lt;/p&gt;  &lt;p&gt;I should mention that, at university, they did teach us CVS briefly, as part of a ten-week course on &amp;quot;Software Tools and Techniques&amp;quot;. Problem is, after that initial exposure to it, it played no part in any of the course material or assignments we did over the following two years - and we weren&apos;t really expected to use it except on the &amp;quot;learning CVS&amp;quot; assignment - so I think a lot of us, myself included, left with CVS filed alongside awk and sed as tools that were useful in certain circumstances but for which better alternatives existed for day-to-day development.&lt;/p&gt;  &lt;p&gt;It wasn&apos;t until 2005, about twenty years after &amp;quot;hello.bas&amp;quot;, that revision control actually become a day-to-day problem. One of my previous projects had turned into a full-time job, and we&apos;d hired someone else - who also had no team development experience - to help out. At first, it was chaos. Our initial solution was using &lt;a href=&quot;http://www.scootersoftware.com/&quot; target=&quot;_blank&quot;&gt;Beyond Compare&lt;/a&gt; (the nicest file comparison program I&apos;ve used) to sync our local working copies against the live code. This lasted a couple of months, I think - until we hit a problem that meant rolling-back the code to a specific point, and neither of us had an appropriate snapshot. Whilst Beyond Compare was great, and simple file-comparison syncing was easy to understand, we needed something better. I installed Subversion, imported our codebase, and we&apos;ve never looked back.&lt;/p&gt;  &lt;p&gt;This is what I think makes Subversion interesting - and what guarantees a growing user base for the foreseeable future. It&apos;s a significant milestone on the road to becoming a better developer. I&apos;m sure there&apos;s people out there who learn this stuff as a junior developer on a 50-strong team, with a sysadmin managing their &lt;a href=&quot;http://www.bitkeeper.com/&quot; target=&quot;_blank&quot;&gt;BitKeeper&lt;/a&gt; repository and a &lt;a href=&quot;http://images.google.com/images?q=obi-wan+kenobi&quot; target=&quot;_blank&quot;&gt;mentor&lt;/a&gt; patiently explaining how it all works. I don&apos;t think they&apos;re the people we need to worry about. It&apos;s the people who have have already moved from simple copies to syncing tools, and are looking for the next step. When you start building a team around people who are used to working individually, revision control can get very complex, very fast, and I&apos;ve found installing and running my own Subversion repository to be a great way to slowly get to grips with lots of the underlying concepts.&lt;/p&gt;  </description>
          <pubDate>2008-05-11T00:49:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2008/05/11/future-of-subversion.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2008/05/11/future-of-subversion.html</guid>
        </item>
      
    
      
        <item>
          <title>I Want Tony Stark&apos;s Build Server (with possible &quot;Iron Man&quot; Spoilers...)</title>
          <description>&lt;p&gt;Personally, I think the ideal software development process boils down to three things:&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;1. Every decision is reflected in exactly one place in your project. &lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;By this, I don&apos;t necessarily mean documented. Documentation is there to &lt;em&gt;direct&lt;/em&gt; the people who are implementing the decisions, and to &lt;em&gt;explain&lt;/em&gt; the business context and background information that&apos;s necessary to understand the actual code/schema/designs/whatever. I mean that, if a customer&apos;s name is limited to 32 characters, you type that number 32 &lt;em&gt;exactly once&lt;/em&gt; in your entire project, and everything that needs to know how long a customer name can be refers to that one place where you originally recorded it.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;2. Your tools allow each decision to be expressed clearly, succintly and quickly&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;Most of the worthwhile progress I&apos;ve seen with software development is about letting the developer express their intent quickly and without ambiguity. String code in C is basically arithmetic manipulation of arrays of numbers. String code in .NET or Ruby is a whole lot friendlier; there&apos;s a compile and runtime overhead, but Moore&apos;s Law is on our side and, with a few exceptions, I think speed of development and ease of maintenance are becoming more important than speed of execution for most software these days.&amp;#160; &lt;/p&gt;  &lt;p&gt;&lt;strong&gt;3. Everything else is generated automatically.&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;If I change my mind about something, I don&apos;t want to have to tell a whole bunch of classes that I&apos;ve changed my mind. I don&apos;t believe it&apos;s possible to build software without making some assumptions - even if I have a signed-off set of requirements, I&apos;m still assuming that the person who signed the requirements understood what the client actually &lt;em&gt;wants&lt;/em&gt; - and when these assumptions turn out to be wrong, I want to be able to correct them quickly and painlessly. &lt;/p&gt;  &lt;p&gt;I have a homebrewed code-generation system based on &lt;a href=&quot;http://www.codesmithtools.com/&quot; target=&quot;_blank&quot;&gt;CodeSmith&lt;/a&gt; that will generate a pretty comprehensive set of domain model objects and supporting DAL and stored procedures based on a SQL Server database. If we decide that a customer&apos;s name is compulsory and is limited to 32 characters, I change the Name column in the Customer table in our DB to a varchar(32) NOT NULL, and re-generate the code. 30 seconds later, my Customer class includes validation rules that check that Customer.Name is not null, not empty, and no greater than 32 characters - and throw a descriptive exception &amp;quot;Sorry, Customer.Name is limited to 32 characters&amp;quot; if you exceed the limit. The generated objects implement the &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/system.componentmodel.idataerrorinfo.aspx&quot; target=&quot;_blank&quot;&gt;IDataErrorInfo interface&lt;/a&gt; for automatic validation of data-bound WinForms apps, and use a variation on the MVP pattern that means that for each business object, we also generate an interface defining that object&apos;s editable fields - so you make your CustomerEditForm.ascx.cs code-behind class implement ICustomerView, and you can validate by calling Customer.ValidateView(this, &amp;quot;Name&amp;quot;) from your user controls and get a nice (and completely auto-generated) error message if there&apos;s anything wrong with the name you&apos;ve just entered.&lt;/p&gt;  &lt;p&gt;That example is from Real Life. That&apos;s why it&apos;s a bit... technical.&lt;/p&gt;  &lt;p&gt;[Potential spoilers after the photo... careful now.]&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p style=&quot;text-align: center&quot;&gt;&lt;a href=&quot;http://lh6.ggpht.com/dmb197/SCDQtPvt-XI/AAAAAAAAADE/XPR0lkdLZ2s/hr_Iron_Man_22%5B1%5D.jpg&quot;&gt;&lt;img style=&quot;border-right: 0px; border-top: 0px; border-left: 0px; border-bottom: 0px&quot; height=&quot;324&quot; alt=&quot;Tony Stark working on his Iron Man suit (Copyright &amp;#169; Marvel / Paramount)&quot; src=&quot;http://lh4.ggpht.com/dmb197/SBz8Rvvt-WI/AAAAAAAAADM/WTt3wFnxPL0/hr_Iron_Man_22_thumb.jpg&quot; width=&quot;484&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;Driving home from watching &lt;a href=&quot;http://www.imdb.com/title/tt0371746/&quot; target=&quot;_blank&quot;&gt;Iron Man&lt;/a&gt; tonight, it occurred to me... in the movie, they do basically the same thing, but they do it with &lt;em&gt;style&lt;/em&gt;. There&apos;s a scene about halfway through Iron Man where Tony Stark, our intrepid millionaire-playboy-genius-weapons-designer-turned-superhero, is putting the finishing touches on his Iron Man suit in his Malibu beachfront workshop. (I told you fiction made it look cool.) His computer system - known as &apos;Jarvis&apos;, apparently - brings up a 3D visualisation of his latest design; Tony casually asks Jarvis to &amp;quot;throw a little hot-rod red in there&amp;quot;, and then goes off to drink scotch and dance with &lt;a href=&quot;http://images.google.com/images?q=gwyneth+paltrow&quot; target=&quot;_blank&quot;&gt;Gwyneth Paltrow&lt;/a&gt; while the system does the actual suit fabrication.&lt;/p&gt;  &lt;p&gt;Ok, so I&apos;m assuming there&apos;s some A.I. involved and that a certain visual style is implied by the phrase &amp;quot;hot-rod red&amp;quot; - but that&apos;s just about configuring your tools to suit your preference. Otherwise, it&apos;s just a really powerful configuration and build server... you make a decision, you record it &lt;em&gt;once&lt;/em&gt;, and the system does the rest while you go dancing. Oh, and there&apos;s also the fact that it makes experimental rocket-powered bulletproof flying superhero suits instead of database-driven websites... but we can work on the details later, right? &lt;/p&gt;  &lt;p&gt;Anyway. Point is - next time I have to explaining code generation and continuous integration to a non-developer, I&apos;ll start by asking if they saw Iron Man, and we&apos;ll take it from there.&lt;/p&gt;  </description>
          <pubDate>2008-05-03T23:59:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2008/05/03/i-want-tony-stark-build-server-with.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2008/05/03/i-want-tony-stark-build-server-with.html</guid>
        </item>
      
    
      
        <item>
          <title>Subversion + WebDAV + Wiki = Cool. Fact.</title>
          <description>&lt;p&gt;Following on from &lt;a href=&quot;http://dylanbeattie.blogspot.com/2008/05/getting-started-with-confluence.html&quot;&gt;yesterday&apos;s post about Confluence&lt;/a&gt;, I&apos;ve been thinking how to get the same sort of workflow going for images, screenshots and designs - basically, stuff that isn&apos;t text.&lt;/p&gt;  &lt;p&gt;A lot of our projects start life as Visio diagrams. A couple of us use Visio to actually put them together; everyone else has the free Visio viewer plug-in for Internet Explorer which means they can browse and view our work but can&apos;t edit it. Which is probably a good thing. Likewise, most of our designs start life as PSDs, and we always seem to end up with a couple of random spreadsheets full of data that isn&apos;t clean enough to import into the actual project DB yet but which is too important to ignore.&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh3.ggpht.com/dmb197/SBtauvvt-RI/AAAAAAAAACU/9bLXXG_mujk/s1600-h/IMG_4217%5B4%5D.jpg&quot;&gt;&lt;img style=&quot;border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px&quot; height=&quot;184&quot; alt=&quot;Erzs&amp;#233;bet Bridge, Budapest. &quot; src=&quot;http://lh5.ggpht.com/dmb197/SBtavPvt-SI/AAAAAAAAACc/dTb1nxz65Rs/IMG_4217_thumb%5B2%5D.jpg?imgmax=800&quot; width=&quot;244&quot; align=&quot;right&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;There&apos;s this principle in software engineering - Don&apos;t Repeat Yourself (DRY) - which basically means if you do something, do it exactly once, so that if it changes you only need to modify one piece of code. I think it&apos;s one of the absolute core principles of writing maintainable software, but I also believe the same principle is equally applicable to documentation, configuration - in fact, &lt;strong&gt;anything&lt;/strong&gt; that involves manual intervention when changes are required. If I create a class diagram, I want to maintain a &lt;strong&gt;single master copy&lt;/strong&gt; of that class diagram and, in a perfect world, any documents or pages that refer to it should be smart enough to always refer to that master copy. &lt;/p&gt;  &lt;p&gt;We use &lt;a href=&quot;http://subversion.tigris.org/&quot;&gt;Subversion&lt;/a&gt; as our version control system, but the principle of update-merge-edit-commit applies pretty well to almost any resource in a collaborative development process - there&apos;s even folks out there who keep their &lt;a href=&quot;http://www.onlamp.com/pub/a/onlamp/2005/01/06/svn_homedir.html&quot;&gt;entire life in Subversion&lt;/a&gt;. So...&amp;#160; assuming we&apos;re keeping all our diagrams, designs and documents in Subversion, how can I link a page in our wiki directly to the latest revision of a document inside the repository?&lt;/p&gt;  &lt;p&gt;Turns out it&apos;s pretty straightforward, mainly because other people have already built all the hard bits. &lt;/p&gt;  &lt;p&gt;The key to all this is an Apache module, mod_dav_svn, that&apos;s part of Subversion and exposes your Subversion repository via WebDAV.&amp;#160; Since WebDAV is an extension of HTTP/1.1, that means you can use pretty much any web browser to navigate and view documents in the repository.&lt;/p&gt;  &lt;p&gt;We already had Subversion up and running, so to add Apache and the WebDAV module I just followed the &lt;a href=&quot;http://svnbook.red-bean.com/en/1.4/svn.serverconfig.httpd.html&quot;&gt;Apache install instructions in the Subversion book&lt;/a&gt;. &lt;/p&gt;  &lt;p&gt;One thing to note - Subversion on Windows will not work with Apache 2.2.x; the mod_dav_svn module isn&apos;t compatible with this version and will give you warning messages about &amp;quot;mod_dav_svn.so is garbled&amp;quot;. You&apos;ll need to install the latest version of the 2.0 series (2.0.63 in my case), and you&apos;ll also ideally want to disable IIS or any other web server on the box that&apos;s hosting your repository so that Apache can use port 80 on the server&apos;s primary IP.&lt;/p&gt;  &lt;p&gt;Once it&apos;s all up and running, next time you&apos;re editing a Wiki page and think &amp;quot;hey, I really want to link to the Visio user experience workflow diagram here&amp;quot; - easy. Just point the link at the full URL of that diagram inside your WebDAV repository - something like &lt;u&gt;&lt;/u&gt;&lt;/p&gt;  &lt;p&gt;&lt;u&gt;http://my.subversion.server/svn/myproject/docs/workflow/User+Experience.vsd&lt;/u&gt; &lt;/p&gt;  &lt;p&gt;I think the real advantage of this approach is that you&apos;re not constantly exporting Visio docs as JPEGs and uploading them to the wiki - that&apos;s high-maintenance, and there&apos;s never any guarantee that the JPEGs in the wiki actually reflect the latest state of the original diagrams.&lt;/p&gt;  &lt;p&gt;With this approach, you just run svn update locally to get the latest set of documents, make your changes, and&amp;#160; run svn commit - just like checking in regular code. Any links pointing to your document via the WebDAV repository will always point to the latest version - and you never have to worry about maintaining multiple copies.&lt;/p&gt;  &lt;p&gt;Confluence, the wiki engine we&apos;re using right now, should theoretically let you embed images that are hosted under WebDAV - so your tags end up something like &lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&amp;lt;img src=&amp;quot;&lt;/strong&gt;&lt;strong&gt;http://my.subversion.server/svn/myproject/docs/homepage01.png&amp;quot;&lt;/strong&gt;&lt;strong&gt; alt=&amp;quot;Homepage design (01)&amp;quot; /&amp;gt; &lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;as well as just creating hyperlinks to documents - but Confluence doesn&apos;t seem to like that right now. &lt;/p&gt;  &lt;p&gt;UPDATE: It&apos;s possible to work around this bug by inserting the image URL directly into the Wiki markup surrounded by exclamation marks - &lt;code style=&quot;color: #000000&quot;&gt;!http://www.myserver.com/image.jpg!&lt;/code&gt; will display image.jpg from www.myserver.com as a normal IMG tag. Not quite as streamlined as drag&apos;n&apos;drop but it does work.&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh3.ggpht.com/dmb197/SBtavvvt-TI/AAAAAAAAACk/CHcxeOFmVWQ/s1600-h/IMG_4307%5B7%5D.jpg&quot;&gt;&lt;img style=&quot;border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; margin: 0px 10px 10px 0px; border-right-width: 0px&quot; height=&quot;184&quot; alt=&quot;Spring blossom in Budapest&quot; src=&quot;http://lh5.ggpht.com/dmb197/SBtawPvt-UI/AAAAAAAAACs/nxS-hD25yyM/IMG_4307_thumb%5B5%5D.jpg?imgmax=800&quot; width=&quot;244&quot; align=&quot;left&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;As an aside - right now we&apos;ve got a build server running on IIS, a wiki engine running J2EE on Tomcat, and Subversion hosted from Apache, but the resulting environment actually feels more cohesive and integrated than most of the IIS-only setups I&apos;ve played with over the last year or so. Personally, I think this is because the quality of the apps themselves - Apache, Subversion, Confluence, FinalBuilder, CruiseControl.NET - is so high that the underlying platform is basically irrelevant in day-to-day use. What&apos;s been really apparent getting this configuration up and running, though, is that the package authors (or vendors - never quite sure what you call the people you get free stuff from...) have put enough effort into the installers that the platform doesn&apos;t really matter at installation time either. Sure, it means we&apos;re running three different web servers right now - but they seem to be working, and maybe we should be thinking in terms of eggs &amp;amp; baskets instead.&lt;/p&gt;  </description>
          <pubDate>2008-05-02T18:17:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2008/05/02/subversion-webdav-wiki-cool-fact.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2008/05/02/subversion-webdav-wiki-cool-fact.html</guid>
        </item>
      
    
      
        <item>
          <title>Getting Started with Confluence</title>
          <description>&lt;p&gt;In principle, I think wikis are great. I&apos;ve worked on too many projects that have ended up with fifteen different copies of the &amp;quot;definitive&amp;quot; functional spec - all called something like &lt;strong&gt;Copy (2) of Rhubarb - Functional Spec - FINAL (3).doc&lt;/strong&gt; - and no-one&apos;s really sure what&apos;s going on any more. The idea of a centralised documentation repository that everyone can read and anyone can edit, with a full history of who changed what, is appealing to say the least.&lt;/p&gt;  &lt;p&gt;In practise - software specs are weird, awkward documents. Some concepts and ideas are best described as English prose. Some things are best described using screenshots, sketches, flow charts, sequence diagrams, class diagrams. Some things are best included by just pasting the code - &apos;cos you&apos;ve already written it, and at the end of the day C# is just a really, really accurate description of an algorithmic solution to a particular problem.&lt;/p&gt;  &lt;p&gt;I&apos;ve played with &lt;a href=&quot;http://www.mediawiki.org/&quot;&gt;MediaWiki&lt;/a&gt;, but found the installation process a bit daunting - we&apos;re a Microsoft shop; we have Windows, SQL Server and the like already up and running, and no real LAMP expertise to speak of...&lt;/p&gt;  &lt;p&gt;I&apos;ve set up &lt;a href=&quot;http://sharpforge.org/&quot;&gt;Sharpforge&lt;/a&gt;, I&apos;ve used the wiki engine built in to &lt;a href=&quot;http://www.fogcreek.com/FogBUGZ/&quot;&gt;FogBugz&lt;/a&gt;. I&apos;ve recently been playing with &lt;a href=&quot;http://www.high-beyond.com/&quot;&gt;PerspectiveWiki&lt;/a&gt; (and I have to say, the v3 alpha is very, very nice indeed - but covered in warnings about how it&apos;s early alpha and you shouldn&apos;t use it for real). &lt;/p&gt;  &lt;p&gt;One notable platform I haven&apos;t played with is Sharepoint... call me cynical, but when it&apos;s a Microsoft business platform that you can attend a five-day training course on, it&apos;s probably not quick &amp;amp; easy. We&apos;re not that big a team, we&apos;re all in one building, there&apos;s relatively little collaborative authoring going on, and Sharepoint just feels like total overkill for what we need. &lt;/p&gt;  &lt;p&gt;So... this morning, I googled &amp;quot;project documentation wiki&amp;quot; or some such phrase, and stumbled across a commercial wiki engine called &lt;a href=&quot;http://www.atlassian.com/software/confluence/&quot;&gt;Confluence&lt;/a&gt;. &lt;a href=&quot;http://blog.mikecoon.net/&quot;&gt;Mike Coon&lt;/a&gt; wrote a post a while back called &amp;quot;&lt;a href=&quot;http://blog.mikecoon.net/2007/11/04/include-everything-your-application-needs.aspx&quot;&gt;It&apos;s the Installation, Stupid&lt;/a&gt;&amp;quot; - basically saying that nobody wants to install and configure a whole application stack just to try out a new package. He&apos;s absolutely right. Confluence uses J2EE, Tomcat, Apache and a whole raft of stuff I really don&apos;t want to install - but I didn&apos;t have to. I just ran setup.exe. Despite the fact it&apos;s based on J2EE, a platform I&apos;ve never even touched - I had an evaluation up and running on one of our dev servers within half an hour. So far, I&apos;m very impressed with it. The WYSIWYG editing is slick and intuitive, the admin and configuration is excellent, and if all goes well I&apos;ll be putting my money where my mouth is before the month is up :)&lt;/p&gt;  &lt;p&gt;A couple of hours later, and it was up and running against our existing MS SQL Server database, authenticating via LDAP against our Active Directory server, and generally making the world a better place. One of these days I really have to get my head around LDAP a little better... when it works, it&apos;s absolutely wonderful (&amp;quot;no, no new password, just log in with your normal one&amp;quot;) but my LDAP filters are too close to &lt;a href=&quot;http://en.wikipedia.org/wiki/Cargo_cult_programming&quot;&gt;cargo-cult programming&lt;/a&gt; for comfort right now.&lt;/p&gt;  &lt;p&gt;One major gotcha that was almost a showstopper until I found a way around it was getting Confluence to run on the default port 80 on the same server as our existing IIS intranet site, so here&apos;s what I did - and so far, it&apos;s working.&lt;/p&gt;  &lt;h3&gt;Confluence coexisting with IIS on a single server&lt;/h3&gt;  &lt;p&gt;Microsoft IIS, by default, grabs port 80 of every IP address on your server - and there&apos;s no way to switch this off using built-in Windows admin capabilities. This means if you want another web server running on the same box, you need to do a bit of tweaking to both IIS and the other server to make them coexist happily.&lt;/p&gt;  &lt;p&gt;Confluence installs by default on port 8080, but for various reasons (mainly being I just don&apos;t like it), I wanted to get it running on port 80 - alongside the existing IIS sites.&lt;/p&gt;  &lt;p&gt;There&apos;s extensive docs at the Confluence site about installing an ISAPI redirect so you can have &lt;a href=&quot;http://www.mysite.com&quot;&gt;www.mysite.com&lt;/a&gt; hosted by IIS and &lt;a href=&quot;http://www.mysite.com/wiki&quot;&gt;www.mysite.com/wiki&lt;/a&gt; invisibly proxy all requests to a JIRA or Confluence server - but that&apos;s not really what I was after. &lt;/p&gt;  &lt;p&gt;First off, I bound a second IP address to the adapter of the server via Windows&apos; TCP/IP controls - so my server was now running on 192.168.0.78 and 192.168.0.79&lt;/p&gt;  &lt;p&gt;Next thing to do was to set up a DNS record (you could also spoof this using your /etc/hosts file) on our local server, so that &lt;a href=&quot;http://wiki/&quot;&gt;http://wiki/&lt;/a&gt; resolved to 192.168.0.79 - all our existing intranet addresses still resolve to the previous 192.168.0.78 address&lt;/p&gt;  &lt;p&gt;Now for the fun part, which is lovingly documented in &lt;a href=&quot;http://support.microsoft.com/kb/813368&quot;&gt;Microsoft Knowledge Base Article 813368&lt;/a&gt;. Basically, you need to download the &lt;a href=&quot;http://www.microsoft.com/downloads/details.aspx?FamilyID=96a35011-fd83-419d-939b-9a772ea2df90&amp;amp;displaylang=en&quot;&gt;Windows Server 2003 Support Tools&lt;/a&gt;, and use the included httpcfg.exe to modify the IIS metabase so that IIS will only listen on specified IP addresses. This done, it meant that IIS was still running fine on 192.168.0.78, and 192.168.0.79:80 was still available.&lt;/p&gt;  &lt;p&gt;Next step was to modify the server.xml file that&apos;s shipped with Confluence. I have to admit this part was pretty much guesswork, but it made sense and it seems to have worked, so use it at your own risk. &lt;/p&gt;  &lt;p&gt;By default, this file is installed at C:\Program Files\Atlassian Confluence\Application\conf\server.xml, and the clue is the &amp;lt;Connector /&amp;gt; section which refers to port 8080. By default, Confluence installs at &lt;a href=&quot;http://localhost:8080/&quot;&gt;http://localhost:8080/&lt;/a&gt;, so on a hunch I tried switching port=&amp;quot;8080&amp;quot; to port=&amp;quot;80&amp;quot; in this node, restarting Confluence, and as if by magic, everything started working.&lt;/p&gt;  &lt;pre class=&quot;code&quot;&gt;&lt;span style=&quot;color: blue&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #a31515&quot;&gt;Server &lt;/span&gt;&lt;span style=&quot;color: red&quot;&gt;port&lt;/span&gt;&lt;span style=&quot;color: blue&quot;&gt;=&lt;/span&gt;&amp;quot;&lt;span style=&quot;color: blue&quot;&gt;8005&lt;/span&gt;&amp;quot; &lt;span style=&quot;color: red&quot;&gt;shutdown&lt;/span&gt;&lt;span style=&quot;color: blue&quot;&gt;=&lt;/span&gt;&amp;quot;&lt;span style=&quot;color: blue&quot;&gt;SHUTDOWN&lt;/span&gt;&amp;quot; &lt;span style=&quot;color: red&quot;&gt;debug&lt;/span&gt;&lt;span style=&quot;color: blue&quot;&gt;=&lt;/span&gt;&amp;quot;&lt;span style=&quot;color: blue&quot;&gt;0&lt;/span&gt;&amp;quot;&lt;span style=&quot;color: blue&quot;&gt;&amp;gt;&lt;br /&gt;    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #a31515&quot;&gt;Service &lt;/span&gt;&lt;span style=&quot;color: red&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: blue&quot;&gt;=&lt;/span&gt;&amp;quot;&lt;span style=&quot;color: blue&quot;&gt;Tomcat-Standalone&lt;/span&gt;&amp;quot;&lt;span style=&quot;color: blue&quot;&gt;&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: blue&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #a31515&quot;&gt;Connector &lt;/span&gt;&lt;span style=&quot;color: red&quot;&gt;className&lt;/span&gt;&lt;span style=&quot;color: blue&quot;&gt;=&lt;/span&gt;&amp;quot;&lt;span style=&quot;color: blue&quot;&gt;org.apache.coyote.tomcat4.CoyoteConnector&lt;/span&gt;&amp;quot; &lt;br /&gt;           &lt;span style=&quot;color: red&quot;&gt;port&lt;/span&gt;&lt;span style=&quot;color: blue&quot;&gt;=&lt;/span&gt;&amp;quot;&lt;span style=&quot;color: blue&quot;&gt;80&lt;/span&gt;&amp;quot; &lt;br /&gt;           &lt;span style=&quot;color: red&quot;&gt;minProcessors&lt;/span&gt;&lt;span style=&quot;color: blue&quot;&gt;=&lt;/span&gt;&amp;quot;&lt;span style=&quot;color: blue&quot;&gt;5&lt;/span&gt;&amp;quot;&lt;br /&gt;           &lt;span style=&quot;color: red&quot;&gt;maxProcessors&lt;/span&gt;&lt;span style=&quot;color: blue&quot;&gt;=&lt;/span&gt;&amp;quot;&lt;span style=&quot;color: blue&quot;&gt;75&lt;/span&gt;&amp;quot;&lt;br /&gt;           &lt;span style=&quot;color: red&quot;&gt;enableLookups&lt;/span&gt;&lt;span style=&quot;color: blue&quot;&gt;=&lt;/span&gt;&amp;quot;&lt;span style=&quot;color: blue&quot;&gt;false&lt;/span&gt;&amp;quot; &lt;br /&gt;           &lt;span style=&quot;color: red&quot;&gt;redirectPort&lt;/span&gt;&lt;span style=&quot;color: blue&quot;&gt;=&lt;/span&gt;&amp;quot;&lt;span style=&quot;color: blue&quot;&gt;8444&lt;/span&gt;&amp;quot; &lt;br /&gt;           &lt;span style=&quot;color: red&quot;&gt;acceptCount&lt;/span&gt;&lt;span style=&quot;color: blue&quot;&gt;=&lt;/span&gt;&amp;quot;&lt;span style=&quot;color: blue&quot;&gt;10&lt;/span&gt;&amp;quot; &lt;br /&gt;           &lt;span style=&quot;color: red&quot;&gt;debug&lt;/span&gt;&lt;span style=&quot;color: blue&quot;&gt;=&lt;/span&gt;&amp;quot;&lt;span style=&quot;color: blue&quot;&gt;0&lt;/span&gt;&amp;quot; &lt;br /&gt;           &lt;span style=&quot;color: red&quot;&gt;connectionTimeout&lt;/span&gt;&lt;span style=&quot;color: blue&quot;&gt;=&lt;/span&gt;&amp;quot;&lt;span style=&quot;color: blue&quot;&gt;20000&lt;/span&gt;&amp;quot;&lt;br /&gt;           &lt;span style=&quot;color: red&quot;&gt;useURIValidationHack&lt;/span&gt;&lt;span style=&quot;color: blue&quot;&gt;=&lt;/span&gt;&amp;quot;&lt;span style=&quot;color: blue&quot;&gt;false&lt;/span&gt;&amp;quot; &lt;br /&gt;           &lt;span style=&quot;color: red&quot;&gt;URIEncoding&lt;/span&gt;&lt;span style=&quot;color: blue&quot;&gt;=&lt;/span&gt;&amp;quot;&lt;span style=&quot;color: blue&quot;&gt;UTF-8&lt;/span&gt;&amp;quot;&lt;br /&gt;        &lt;span style=&quot;color: blue&quot;&gt;/&amp;gt;&lt;/span&gt;&lt;/pre&gt;  </description>
          <pubDate>2008-05-01T18:01:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2008/05/01/getting-started-with-confluence.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2008/05/01/getting-started-with-confluence.html</guid>
        </item>
      
    
      
        <item>
          <title>Firing Static Events from Instance Methods in C#</title>
          <description>&lt;p&gt;In a current project, I have a UserDocument class, which inherits from EditableBase&amp;lt;T&amp;gt;, and stores metadata about the contents of a file that&apos;s stored somewhere on my app server. What I needed to do was to add an event to the business object that would fire just after deleting the UserDocument&apos;s record, so that I could delete the corresponding file from the server&apos;s filesystem. The solution is &lt;em&gt;almost&lt;/em&gt; what I expected, with one slightly odd workaround.&lt;/p&gt;  &lt;p&gt;OK, the original code looked (very roughly) like this. We&apos;re using generics throughout so T denotes &amp;quot;whatever kind of business object this is&amp;quot; - Customer, Invoice, whatever.&lt;/p&gt;  &lt;pre class=&quot;code&quot;&gt;&lt;span style=&quot;color: green&quot;&gt;// Our event handler methods have to match the following delegate&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: blue&quot;&gt;public delegate void &lt;/span&gt;&lt;span style=&quot;color: #2b91af&quot;&gt;DataEventHandler&lt;/span&gt;&amp;lt;T&amp;gt;(T t);&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: green&quot;&gt;// The base class for our editable business object.&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: blue&quot;&gt;public class &lt;/span&gt;&lt;span style=&quot;color: #2b91af&quot;&gt;EditableBase&lt;/span&gt;&amp;lt;T&amp;gt; {&lt;br /&gt;&lt;br /&gt;    &lt;span style=&quot;color: green&quot;&gt;// The event we want to fire immediately after deleting a database record.&lt;br /&gt;    &lt;/span&gt;&lt;span style=&quot;color: blue&quot;&gt;public static event &lt;/span&gt;&lt;span style=&quot;color: #2b91af&quot;&gt;DataEventHandler&lt;/span&gt;&amp;lt;T&amp;gt; DeletingRecord;&lt;br /&gt;&lt;br /&gt;    &lt;span style=&quot;color: green&quot;&gt;// The event we want to fire immediately after deleting a database record.&lt;br /&gt;    &lt;/span&gt;&lt;span style=&quot;color: blue&quot;&gt;public static event &lt;/span&gt;&lt;span style=&quot;color: #2b91af&quot;&gt;DataEventHandler&lt;/span&gt;&amp;lt;T&amp;gt; DeletedRecord;&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: green&quot;&gt;// A class representing a document or file that&apos;s been uploaded to our &lt;br /&gt;// application by a user.&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: blue&quot;&gt;public class &lt;/span&gt;&lt;span style=&quot;color: #2b91af&quot;&gt;UserDocument &lt;/span&gt;: &lt;span style=&quot;color: #2b91af&quot;&gt;EditableBase&lt;/span&gt;&amp;lt;&lt;span style=&quot;color: #2b91af&quot;&gt;UserDocument&lt;/span&gt;&amp;gt; {&lt;br /&gt;&lt;br /&gt;    &lt;span style=&quot;color: blue&quot;&gt;private string &lt;/span&gt;filename;&lt;br /&gt;&lt;span style=&quot;color: blue&quot;&gt;    public string &lt;/span&gt;Filename {&lt;br /&gt;        &lt;span style=&quot;color: blue&quot;&gt;get &lt;/span&gt;{ &lt;span style=&quot;color: blue&quot;&gt;return &lt;/span&gt;filename; }&lt;br /&gt;        &lt;span style=&quot;color: blue&quot;&gt;set &lt;/span&gt;{ filename = &lt;span style=&quot;color: blue&quot;&gt;value&lt;/span&gt;; }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;span style=&quot;color: blue&quot;&gt;public void &lt;/span&gt;Delete() {&lt;br /&gt;        &lt;span style=&quot;color: green&quot;&gt;// this is the call to the DAL to actually remove the record &lt;br /&gt;        // from the UserDocument table.&lt;br /&gt;        &lt;/span&gt;DataContext.Current.DeleteUserDocument(filename);&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;

&lt;p&gt;First approach - let&apos;s just fire the event in the usual way:&lt;/p&gt;

&lt;div&gt;
  &lt;div style=&quot;padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &amp;#39;Courier New&amp;#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none&quot;&gt;
    &lt;pre style=&quot;padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &amp;#39;Courier New&amp;#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none&quot;&gt;&lt;span style=&quot;color: #0000ff&quot;&gt;public&lt;/span&gt; &lt;span style=&quot;color: #0000ff&quot;&gt;void&lt;/span&gt; Delete() {&lt;/pre&gt;

    &lt;pre style=&quot;padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &amp;#39;Courier New&amp;#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none&quot;&gt;    &lt;span style=&quot;color: #008000&quot;&gt;// this is the call to the DAL to actually remove the record &lt;/span&gt;&lt;/pre&gt;

    &lt;pre style=&quot;padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &amp;#39;Courier New&amp;#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none&quot;&gt;    &lt;span style=&quot;color: #008000&quot;&gt;// from the UserDocument table.&lt;/span&gt;&lt;/pre&gt;

    &lt;pre style=&quot;padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &amp;#39;Courier New&amp;#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none&quot;&gt;    DataContext.Current.DeleteUserDocument(filename);&lt;/pre&gt;

    &lt;pre style=&quot;padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &amp;#39;Courier New&amp;#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none&quot;&gt;    &lt;span style=&quot;color: #0000ff&quot;&gt;if&lt;/span&gt; (DeletedRecord != &lt;span style=&quot;color: #0000ff&quot;&gt;null&lt;/span&gt;) DeletedRecord(&lt;span style=&quot;color: #0000ff&quot;&gt;this&lt;/span&gt;);&lt;/pre&gt;

    &lt;pre style=&quot;padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &amp;#39;Courier New&amp;#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none&quot;&gt;}&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Erk. Compiler doesn&apos;t like that... &lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;The event &apos;EditableBase&amp;lt;UserDocument&amp;gt;.DeletedRecord&apos; can only appear on the left hand side of += or -= (except when used from within the type &apos;EditableBase&amp;lt;T&amp;gt;&apos;)&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It would appear that you can&apos;t fire static events from instance methods. No idea why this is the case - I can&apos;t see any reason for it - but the workaround is pretty straightforward. First, we provide a &lt;strong&gt;static&lt;/strong&gt; wrapper method for each of our EditableBase&amp;lt;T&amp;gt; events:&lt;/p&gt;

&lt;pre class=&quot;code&quot;&gt;&lt;span style=&quot;color: green&quot;&gt;// A wrapper method that can &apos;see&apos; the static event, but can be called    &lt;br /&gt;// from instance methods.     &lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: blue&quot;&gt;public static void&lt;/span&gt;NotifyDeletedRecord(T t) {   &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &lt;span style=&quot;color: blue&quot;&gt;if&lt;/span&gt;(DeletedRecord != &lt;span style=&quot;color: blue&quot;&gt;null&lt;/span&gt;) DeletedRecord(t);   &lt;br /&gt;}&lt;/pre&gt;

&lt;p&gt;What&apos;s cool about this method is that it provides an adapter between instance methods and static events. Instance methods can call static methods; static methods can fire static events. Our instance method can now quite happily do this:&lt;/p&gt;

&lt;pre class=&quot;code&quot;&gt;&lt;span style=&quot;color: blue&quot;&gt;public void &lt;/span&gt;Delete() {&lt;br /&gt;    &lt;span style=&quot;color: green&quot;&gt;// this is the call to the DAL to actually remove the record&lt;br /&gt;    // from the UserDocument table.&lt;br /&gt;    &lt;/span&gt;DataContext.Current.DeleteUserDocument(filename);&lt;br /&gt;    NotifyDeletedRecord(&lt;span style=&quot;color: blue&quot;&gt;this&lt;/span&gt;);&lt;br /&gt;}&lt;/pre&gt;

&lt;p&gt;and then the NotifyDeletedRecord method will raise the static event, and any handlers are attached to it will fire as expected.&lt;/p&gt;

&lt;p&gt;What&apos;s cool about this particular example is the ability to do this sort of thing - this code is from global.asax, and shows clearly how we can bind a very simple event handler to the static event on the UserDocument class that will clean up the underlying files whenever a record is removed: 
  &lt;br /&gt;&lt;/p&gt;

&lt;pre class=&quot;code&quot;&gt;&lt;span style=&quot;color: blue&quot;&gt;namespace &lt;/span&gt;MyProject.Website {&lt;br /&gt;    &lt;span style=&quot;color: blue&quot;&gt;public class &lt;/span&gt;&lt;span style=&quot;color: #2b91af&quot;&gt;Global &lt;/span&gt;: System.Web.HttpApplication {&lt;br /&gt;&lt;br /&gt;        &lt;span style=&quot;color: blue&quot;&gt;protected void &lt;/span&gt;Application_Start(&lt;span style=&quot;color: blue&quot;&gt;object &lt;/span&gt;sender, &lt;span style=&quot;color: #2b91af&quot;&gt;EventArgs &lt;/span&gt;e) {&lt;br /&gt;            &lt;span style=&quot;color: green&quot;&gt;// Attach event handler that cleans up files on disk &lt;br /&gt;            // after DB records are deleted.&lt;br /&gt;            &lt;/span&gt;&lt;span style=&quot;color: #2b91af&quot;&gt;UserDocument&lt;/span&gt;.DeletedRecord += &lt;br /&gt;                &lt;span style=&quot;color: blue&quot;&gt;new &lt;/span&gt;&lt;span style=&quot;color: #2b91af&quot;&gt;DataEventHandler&lt;/span&gt;&amp;lt;&lt;span style=&quot;color: #2b91af&quot;&gt;UserDocument&lt;/span&gt;&amp;gt;(DeleteUserDocumentFile);&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        &lt;span style=&quot;color: green&quot;&gt;// This method removes the file associated with the specified UserDocument&lt;br /&gt;        // from the application servers&apos; filesystem.&lt;/span&gt;&lt;span style=&quot;color: green&quot;&gt; &lt;br /&gt;        &lt;/span&gt;&lt;span style=&quot;color: blue&quot;&gt;void &lt;/span&gt;DeleteUserDocumentFile(&lt;span style=&quot;color: #2b91af&quot;&gt;UserDocument &lt;/span&gt;t) {&lt;br /&gt;            string userPath = HttpContext.Current.Server.MapPath(&lt;span style=&quot;background: #f2d7fd; color: #a31515&quot;&gt;&amp;quot;~/UserDocuments/&amp;quot;&lt;/span&gt;);&lt;br /&gt;            &lt;span style=&quot;color: blue&quot;&gt;string &lt;/span&gt;filePath = &lt;span style=&quot;color: #2b91af&quot;&gt;Path&lt;/span&gt;.Combine(userPath, t.Filename);&lt;br /&gt;            &lt;span style=&quot;color: blue&quot;&gt;if &lt;/span&gt;(&lt;span style=&quot;color: #2b91af&quot;&gt;File&lt;/span&gt;.Exists(filePath)) {&lt;br /&gt;                &lt;span style=&quot;color: blue&quot;&gt;try &lt;/span&gt;{&lt;br /&gt;                    &lt;span style=&quot;color: #2b91af&quot;&gt;File&lt;/span&gt;.Delete(filePath);&lt;br /&gt;                } &lt;span style=&quot;color: blue&quot;&gt;catch &lt;/span&gt;(&lt;span style=&quot;color: #2b91af&quot;&gt;Exception &lt;/span&gt;ex) {&lt;br /&gt;                    &lt;span style=&quot;color: green&quot;&gt;// Something went wrong - maybe log the error, or add&lt;br /&gt;                    // to a queue of files to be manually cleaned up later?&lt;br /&gt;                    // In the meantime, just throw the exception again.&lt;br /&gt;                    &lt;/span&gt;&lt;span style=&quot;color: blue&quot;&gt;throw &lt;/span&gt;(ex);&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;

&lt;h4&gt;But why not just put the File.Delete() inside the UserDocument class?&lt;/h4&gt;

&lt;p&gt;Because, depending on the context in which our business objects are running, we could be deleting from the local filesystem (via Server.MapPath() because we&apos;re a website), or via a WebDAV call to a remote file server, or by calling some web service that deletes the remote file for us. Separating the requirement and the implementation in this way means our business objects implement deletion consistently and our application itself is free to run cleanup code.&lt;/p&gt;

&lt;p&gt;I think it&apos;s quite a nice approach - we&apos;re now using it with NotifyCreating, NotifyInserting / NotifyInserted, and all sorts of other hooks around CRUD data access methods, and it seems to be working really rather nicely.&lt;/p&gt;  </description>
          <pubDate>2008-05-01T17:20:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2008/05/01/firing-static-events-from-instance.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2008/05/01/firing-static-events-from-instance.html</guid>
        </item>
      
    
      
        <item>
          <title>The OUTPUT clause in SQL Server 2005</title>
          <description>One of those things that I can&apos;t believe I&apos;ve come this far without knowing - or needing. There&apos;s a little-known clause you can use in your INSERT, UPDATE and DELETE statements in SQL Server 2005 that will let you return the affected rows within a single query. &lt;br /&gt;
Picture this: Hotmail gets shut down, and you need to remove all Hotmail addresses from your Customer table and create a list of the affected customers so you can phone them all and ask what their new e-mail address is. And, to make it interesting, you&apos;re trying to do it in a one stored procedure call. &lt;br /&gt;
CREATE PROCEDURE HotmailGoByeBye AS BEGIN    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; UPDATE Customer SET Email = null WHERE Email LIKE &apos;%hotmail.com&apos;     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; SELECT Name, Phone FROM Customer where Email LIKE &apos;%hotmail.com&apos;     &lt;br /&gt;END&lt;br /&gt;
Hands up if you can see why that&apos;s not going to work... &lt;br /&gt;
Now, check this out.&lt;br /&gt;
CREATE PROCEDURE HotmailGoByeBye AS BEGIN    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; UPDATE Customer     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; SET Email = null     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; OUTPUT inserted.Name, inserted.Phone     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; WHERE Email LIKE &apos;%hotmail.com&apos;     &lt;br /&gt;END&lt;br /&gt;
The inserted keyword there works just like it does in triggers - i.e. it contains all the rows affected by the update &lt;em&gt;after&lt;/em&gt; they&apos;ve been updated. It can do a lot more besides - &lt;a href=&quot;http://technet.microsoft.com/en-us/library/ms177564.aspx&quot; target=&quot;_blank&quot;&gt;Books Online&lt;/a&gt; has the full details as usual.&lt;br /&gt;
I&apos;m incorporating this into some ORM code I&apos;m working with, because it&apos;ll let me clear down any references to, say, the Address that&apos;s about to get deleted, return the affected records, update the corresponding in-memory objects so their timestamps are accurate, delete the address itself, and proceed with the rest of the batch update without lots of nasty timestamp violations. &lt;br /&gt;
Interestingly, even the much-requested SQL Server 2005 support for multiple cascade paths wouldn&apos;t alleviate this problem, since I&apos;m using timestamps for concurrency control and ON CASCADE SET NULL works like a trigger - i.e. it modifies the records but doesn&apos;t return them to the calling application. </description>
          <pubDate>2008-03-20T23:45:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2008/03/20/output-clause-in-sql-server-2005.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2008/03/20/output-clause-in-sql-server-2005.html</guid>
        </item>
      
    
      
        <item>
          <title>Encoding videos for the Xbox 360 using ffmpeg</title>
          <description>&lt;p&gt;One of the cooler features of Microsoft&apos;s Xbox 360 console is the ability to play back video from a USB memory stick or external hard disk. Only problem is, encoding video can be a bit of a hit-and-miss affair at the best of times, and when you&apos;re limited to the codecs supported by the Xbox 360 and can&apos;t install any more, a bit of trial and error is required to get good results. &lt;/p&gt;  &lt;p&gt;For this little exercise, I&apos;m using a 500Gb &lt;a href=&quot;http://www.wdc.com/en/products/Products.asp?DriveID=232&quot;&gt;Western Digital MyBook Essential Edition&lt;/a&gt; which is on special offer from &lt;a href=&quot;http://www.amazon.co.uk/dp/B000EXZB0M/ref=pd_bbs_sr_2?qid=1202661674&quot;&gt;amazon.co.uk&lt;/a&gt;, right now for &amp;#xA3;69.99, and I&apos;ve got the original footage as raw MPEG-2 files.&lt;/p&gt;  &lt;p&gt;I&apos;ve played with lots of video encoders over the years, both commercial and free, and the best one I&apos;ve ever used in terms of output quality and encoding speed is an open-source command line tool called &lt;a href=&quot;http://ffmpeg.mplayerhq.hu/&quot;&gt;ffmpeg&lt;/a&gt;.     &lt;br /&gt;    &lt;br /&gt;So... let&apos;s say we&apos;ve downloaded a Win32 binary of ffmpeg from &lt;a href=&quot;http://arrozcru.no-ip.org/ffmpeg_builds/&quot;&gt;here&lt;/a&gt;, we&apos;ve got our source video file as a raw MPEG-2 file (or just about anything that ffmpeg will read); now it&apos;s just a matter of finding the right settings for ffmpeg.&lt;/p&gt;  &lt;p&gt;According to the &lt;a href=&quot;http://blogs.msdn.com/xboxteam/archive/2007/11/30/december-2007-video-playback-faq.aspx&quot;&gt;Xbox December 2007 Video Playback FAQ&lt;/a&gt;:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;Xbox 360 supports the following for MPEG-4:&lt;/p&gt;    &lt;ul&gt;     &lt;li&gt;File Extensions: .mp4, .m4v, .mp4v, .mov &lt;/li&gt;      &lt;li&gt;Containers: MPEG-4, QuickTime &lt;/li&gt;      &lt;li&gt;Video Profiles: Simple &amp;amp; **Advanced Simple Profile &lt;/li&gt;      &lt;li&gt;Video Bitrate: 5 Mbps with resolutions of 1280 x 720 at 30fps. &lt;/li&gt;      &lt;li&gt;Audio Profiles: 2 channel AAC low complexity (LC) &lt;/li&gt;      &lt;li&gt;Audio Max Bitrate: No restrictions. &lt;/li&gt;   &lt;/ul&gt; &lt;/blockquote&gt;  &lt;p&gt;Your basic ffmpeg command line looks something like:&lt;/p&gt;  &lt;p&gt;C:\&amp;gt;ffmpeg [options] [output file]&lt;/p&gt;  &lt;p&gt;The particular invocation we need here is:&lt;/p&gt;  &lt;p&gt;ffmpeg -i myfile.mpg -f mp4 -vcodec mpeg4 -b 2000000 -acodec libfaac -ac 2 -ab 128000 -s 640x480 m:\output.mp4&lt;/p&gt;  &lt;p&gt;Now let&apos;s break those options down and explain what they actually mean...&lt;/p&gt;  &lt;p&gt;   &lt;table cellspacing=&quot;0&quot; cellpadding=&quot;4&quot; width=&quot;544&quot; border=&quot;1&quot;&gt;&lt;tbody&gt;       &lt;tr&gt;         &lt;td valign=&quot;top&quot; width=&quot;156&quot;&gt;           &lt;p&gt;-i myfile.mpg&lt;/p&gt;         &lt;/td&gt;          &lt;td valign=&quot;top&quot; width=&quot;386&quot;&gt;specifies &amp;quot;myfile.mpg&amp;quot; as the input file&lt;/td&gt;       &lt;/tr&gt;        &lt;tr&gt;         &lt;td valign=&quot;top&quot; width=&quot;159&quot;&gt;-f mp4&lt;/td&gt;          &lt;td valign=&quot;top&quot; width=&quot;384&quot;&gt;Specifies that the output file should use the MPEG-4 container format&lt;/td&gt;       &lt;/tr&gt;        &lt;tr&gt;         &lt;td valign=&quot;top&quot; width=&quot;161&quot;&gt;-vcodec mpeg4&lt;/td&gt;          &lt;td valign=&quot;top&quot; width=&quot;382&quot;&gt;Specifies that within the container, the video stream should be encoded using the MPEG-4 codec&lt;/td&gt;       &lt;/tr&gt;        &lt;tr&gt;         &lt;td valign=&quot;top&quot; width=&quot;163&quot;&gt;-b 2000000&lt;/td&gt;          &lt;td valign=&quot;top&quot; width=&quot;381&quot;&gt;Specifies the video bitrate - in this case 2,000,000 bits per second, or approximately 2Mbit. Much lower and you&apos;ll notice lots of compression artefacts; much higher and you&apos;ll have problems with the filesize - see below!&lt;/td&gt;       &lt;/tr&gt;        &lt;tr&gt;         &lt;td valign=&quot;top&quot; width=&quot;164&quot;&gt;-acodec libfaac&lt;/td&gt;          &lt;td valign=&quot;top&quot; width=&quot;380&quot;&gt;Specifies that the audio stream should be encoded using the libfaac codec - an open-source implementation of the AAC audio encoding standard.&lt;/td&gt;       &lt;/tr&gt;        &lt;tr&gt;         &lt;td valign=&quot;top&quot; width=&quot;165&quot;&gt;-ac 2&lt;/td&gt;          &lt;td valign=&quot;top&quot; width=&quot;380&quot;&gt;Specifies that the output should use two-channel (stereo) output. (The X-Box 360 will not play 5.1 audio from a USB device, apparently)&lt;/td&gt;       &lt;/tr&gt;        &lt;tr&gt;         &lt;td valign=&quot;top&quot; width=&quot;165&quot;&gt;-ab 128000&lt;/td&gt;          &lt;td valign=&quot;top&quot; width=&quot;380&quot;&gt;Specifies the audio bitrate - roughly 128kbps in this example&lt;/td&gt;       &lt;/tr&gt;        &lt;tr&gt;         &lt;td valign=&quot;top&quot; width=&quot;165&quot;&gt;-s 640x480&lt;/td&gt;          &lt;td valign=&quot;top&quot; width=&quot;380&quot;&gt;Specifies the size of the output file - 640x480. Output video will be resized to fit the specified size but aspect ratio will be preserved.&lt;/td&gt;       &lt;/tr&gt;        &lt;tr&gt;         &lt;td valign=&quot;top&quot; width=&quot;165&quot;&gt;m:\output.mp4&lt;/td&gt;          &lt;td valign=&quot;top&quot; width=&quot;380&quot;&gt;The last option is the name of the output file (and the USB hard drive is installed on my system as drive M:)&lt;/td&gt;       &lt;/tr&gt;     &lt;/tbody&gt;&lt;/table&gt; &lt;/p&gt;  &lt;p&gt;The X-Box 360 only supports USB storage devices formatted using the &lt;a href=&quot;http://en.wikipedia.org/wiki/File_Allocation_Table#FAT32&quot;&gt;FAT32 filesystem&lt;/a&gt;, and FAT32 is limited to 4Gb per file. You&apos;ll therefore need to calculate your bitrates based on the length of the video files you&apos;re encoding. 2Mbps means you&apos;re using approximately two million bits = i.e. (2000000 / 8) = 250,000 bytes per second, or 250Kb for each second of video - which equates to (250*60*60) = 900,000 Kb = 900Mb per hour, so you can happily encode movies up to 4.5 hours long at 2Mbps before you hit the FAT32 4Gb-per-file ceiling. &lt;/p&gt;  &lt;p&gt;You also need to consider the resolution (width x height) of the output. Higher resolutions means a larger - and more detailed - picture, but costs more bandwidth to maintain the same image quality; in other words, for a specific bitrate, you&apos;ll have to choose between big and blurry, or small and sharp.&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh4.google.com/dmb197/R68r6HIwXOI/AAAAAAAAAB0/9zO9HzIhIdI/lilies_large%5B3%5D&quot;&gt;&lt;img style=&quot;border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; margin: 4px; border-right-width: 0px&quot; height=&quot;364&quot; alt=&quot;lilies_large&quot; src=&quot;http://lh6.google.com/dmb197/R68r-nIwXPI/AAAAAAAAAB8/A3bOylraVHA/lilies_large_thumb%5B1%5D&quot; width=&quot;484&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;&lt;a href=&quot;http://lh3.google.com/dmb197/R68sG3IwXQI/AAAAAAAAACE/NqRpme-x4o8/lilies_small%5B6%5D&quot;&gt;&lt;img style=&quot;border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; margin: 4px; border-right-width: 0px&quot; height=&quot;184&quot; alt=&quot;lilies_small&quot; src=&quot;http://lh5.google.com/dmb197/R68sNXIwXRI/AAAAAAAAACM/il8CEQcCP_Y/lilies_small_thumb%5B2%5D&quot; width=&quot;244&quot; align=&quot;left&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;These image files are both 12.5Kb in size; you can clearly see the compression artefacts in the larger image above, where we&apos;ve had to discard more detail in the compression process to reach our target file-size. The smaller image doesn&apos;t have such pronounced artefacts - but contains less detail to begin with because it&apos;s been reduced to fit a smaller frame size.&lt;/p&gt;  &lt;p&gt;If you&apos;re encoding video to play back on a specific device, you probably want to use the native resolution of your player. Standard HDTV resolutions are 1280x720 and 1920x1080. The iPod Touch has a native resolution of 480x320, the most recent iPod Nano has a resolution of 320x240. Normal DVD video uses a resolution of 720x576 pixels (on PAL systems) or 720x480 (on NTSC systems). &lt;/p&gt;  &lt;p&gt;Obviously this is all highly subjective - your own definition of &apos;acceptable quality&apos; depends on your equipment, your eyes, and the sort of video footage you&apos;re working with - but I find 640x480 at 2Mbps seems to work pretty well. Likewise audio bitrate - if you&apos;ve got very good ears &amp;amp; speakers and you&apos;re encoding something like concert footage, you might want to specify an audio bitrate of 256Kbps (-ab 256000) or higher. &lt;/p&gt;  &lt;p&gt;Finally, a note about multi-core systems. ffmpeg does not support multi-threading - this means if you&apos;ve got multiple CPUs or multiple cores, it&apos;ll only run on a single core. However, if you&apos;re encoding more than one video file, you can quite happily run two instances of ffmpeg in parallel - Windows is smart enough to run each instance on it&apos;s own core, so on my quad-core box I can encode four files simultaneously with each encoder running at full speed. My box will encode about 100 frames per second on each core, so running all four cores flat-out I can encode four seperate one-hour video clips in about fifteen minutes.&lt;/p&gt;  &lt;p&gt;Finally, plug the drive with your MP4 files into your Xbox 360, go into the Xbox Dashboard, pick &amp;quot;video&amp;quot; and choose the option to play from external device, and away you go. You may find you need to sign in to Xbox Live! to download the required media updates, but this worked perfectly well when I tried it out so it shouldn&apos;t cause any problems.&lt;/p&gt; </description>
          <pubDate>2008-02-09T17:38:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2008/02/09/encoding-videos-for-x-box-360-using.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2008/02/09/encoding-videos-for-x-box-360-using.html</guid>
        </item>
      
    
      
        <item>
          <title>Reflections on Alt.Net.UK</title>
          <description>&lt;p&gt;&lt;a href=&quot;http://lh3.google.com/dmb197/R6ez72m03xI/AAAAAAAAABk/OBRU3ZR3JC8/altnetuk%5B5%5D.jpg&quot;&gt;&lt;img style=&quot;border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; margin: 0px 10px 10px 0px; border-right-width: 0px&quot; height=&quot;254&quot; alt=&quot;altnetuk&quot; src=&quot;http://lh6.google.com/dmb197/R6ez8mm03yI/AAAAAAAAABs/D8kUaUXqloA/altnetuk_thumb%5B3%5D.jpg&quot; width=&quot;260&quot; align=&quot;left&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;I spent last Friday night and Saturday at the &lt;a href=&quot;http://www.altnetuk.com/&quot; target=&quot;_blank&quot;&gt;Alt.Net.UK&lt;/a&gt; conference here in London. Based on the &amp;quot;&lt;a href=&quot;http://www.altnetconf.com/home/open_spaces&quot; target=&quot;_blank&quot;&gt;open spaces&lt;/a&gt;&amp;quot; philosophy, it was an event quite unlike anything I&apos;ve ever been to...&lt;/p&gt;  &lt;p&gt;Actually, that&apos;s not quite true. In a strange way, it was the geek equivalent of a really good jam session. If you&apos;ve ever seen a bunch of musicians get together with no rehearsal and no sheet music, and just improvise a couple of hours of free-form, creative, spontaneous music, you&apos;ll know what I mean. Now imagine they&apos;re not musicians, they&apos;re software developers, and instead of grooving away in D minor they&apos;re having a variety of open and unstructured discussions about REST frameworks, MVC, mock objects, evolutionary database design, the .Net user community and suchlike.&lt;/p&gt;  &lt;p&gt;Personally, I thought it worked very, very well. The Friday night kick-off was a great idea; an hour or so to get some idea what to expect, then a few hours of beer &amp;amp; chat to break the ice and generally get things moving, meant that by the time we started off on Saturday morning the feeling of standing in a room full of strangers was pretty much gone and apart from a couple of rather severe hangovers, things got moving really quickly.&lt;/p&gt;  &lt;p&gt;I&apos;m posting the actual technical content of the sessions on &lt;a href=&quot;http://www.altnetpedia.com/London%20Alt.Net.UK%202nd%20Feb%202008.ashx&quot; target=&quot;_blank&quot;&gt;altnetpedia&lt;/a&gt;, but there&apos;s a couple of things I&apos;ll add here.&lt;/p&gt;  &lt;p&gt;Firstly, a big thanks to everybody who put this together - especially &lt;a href=&quot;http://blog.benhall.me.uk/&quot; target=&quot;_blank&quot;&gt;Ben Hall&lt;/a&gt; (for sorting me out with a last-minute place after I completely missed both rounds of tickets - thanks Ben!),&amp;#160; &lt;a href=&quot;http://codebetter.com/blogs/ian_cooper/&quot; target=&quot;_blank&quot;&gt;Ian Cooper&lt;/a&gt;, &lt;a href=&quot;http://thoughtpad.net/alan-dean.html&quot; target=&quot;_blank&quot;&gt;Alan Dean&lt;/a&gt;, and Conchango and Red Gate are also providing support. (Red Gate not only make the best database tools I&apos;ve ever used, they&apos;ve now given me free beer as well. You guys rock.)&lt;/p&gt;  &lt;p&gt;Second, thanks to everyone who attended. It was a pleasure meeting you all; the discussions we had were engaging, informative and enlightening, and I generally found the whole thing to be an incredibly worthwhile experience. Knowing that there&apos;s so many real people out there who&apos;&apos;ll give up a Saturday to hang out and talk about how we can improve the whole software development process is genuinely inspiring, and I&apos;m hoping to see you again at one .NET event or another over the coming months. &lt;/p&gt;  &lt;p&gt;The session on improving the developer community, in particular, had some really great ideas. In particular,&amp;#160; &lt;a href=&quot;http://www.zimakki.com/blog/&quot; target=&quot;_blank&quot;&gt;Zi&lt;/a&gt; suggested one-off afternoons or days of &amp;quot;pair programming&amp;quot; - employers and security permitting, inviting another member of the .NET circle to spend an afternoon working with you on something you&apos;re doing, and vice versa. Given that, at any given point, I think most of us are normally working at a level slightly beyond our comfort zone (that&apos;s what keeps it challenging, right? - or is that just me...?), I think this would be a great way to share knowledge, develop skills and generally improve as developers. I&apos;m looking forward to seeing if we can make something like this happen, and I&apos;d be interested to hear from anyone out there who&apos;s done anything similar.&lt;/p&gt;  &lt;p&gt;Finally - on a purely practical geek note, next time we do this, can I suggest we transpose the rows and columns on the planning board?&lt;/p&gt;  &lt;p&gt;This (which is what we did)&lt;/p&gt;  &lt;table cellspacing=&quot;0&quot; cellpadding=&quot;2&quot; width=&quot;399&quot; border=&quot;1&quot;&gt;&lt;tbody&gt;     &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;81&quot;&gt;&amp;#160;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;79&quot;&gt;Track A&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;79&quot;&gt;Track B&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;79&quot;&gt;Track C&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;79&quot;&gt;Track D&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;82&quot;&gt;09:30-10:30&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;79&quot;&gt;&amp;#160;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;79&quot;&gt;&amp;#160;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;79&quot;&gt;&amp;#160;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;79&quot;&gt;&amp;#160;&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;82&quot;&gt;11:30-13:00&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;79&quot;&gt;&amp;#160;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;79&quot;&gt;&amp;#160;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;79&quot;&gt;&amp;#160;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;79&quot;&gt;&amp;#160;&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;82&quot;&gt;14:00-15:30&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;79&quot;&gt;&amp;#160;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;79&quot;&gt;&amp;#160;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;79&quot;&gt;&amp;#160;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;79&quot;&gt;&amp;#160;&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;82&quot;&gt;16:00-17:00&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;79&quot;&gt;&amp;#160;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;79&quot;&gt;&amp;#160;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;79&quot;&gt;&amp;#160;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;80&quot;&gt;&amp;#160;&lt;/td&gt;     &lt;/tr&gt;   &lt;/tbody&gt;&lt;/table&gt;  &lt;p&gt;means you&apos;ve always got at least four people trying to see the entire width of the wall (that&apos;s what&apos;s going on in the photo above...), whereas if we&apos;d done it this way round...&lt;/p&gt;  &lt;table cellspacing=&quot;0&quot; cellpadding=&quot;2&quot; width=&quot;402&quot; border=&quot;1&quot;&gt;&lt;tbody&gt;     &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;80&quot;&gt;&amp;#160;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;80&quot;&gt;09:30-10:30&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;80&quot;&gt;11:30-13:00&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;80&quot;&gt;14:00-15:30&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;80&quot;&gt;16:00-17:00&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;80&quot;&gt;Track A&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;80&quot;&gt;&amp;#160;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;80&quot;&gt;&amp;#160;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;80&quot;&gt;&amp;#160;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;80&quot;&gt;&amp;#160;&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;80&quot;&gt;Track B&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;80&quot;&gt;&amp;#160;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;80&quot;&gt;&amp;#160;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;80&quot;&gt;&amp;#160;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;80&quot;&gt;&amp;#160;&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;80&quot;&gt;Track C&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;80&quot;&gt;&amp;#160;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;80&quot;&gt;&amp;#160;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;80&quot;&gt;&amp;#160;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;80&quot;&gt;&amp;#160;&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign=&quot;top&quot; width=&quot;80&quot;&gt;Track D&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;80&quot;&gt;&amp;#160;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;80&quot;&gt;&amp;#160;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;80&quot;&gt;&amp;#160;&lt;/td&gt;        &lt;td valign=&quot;top&quot; width=&quot;80&quot;&gt;&amp;#160;&lt;/td&gt;     &lt;/tr&gt;   &lt;/tbody&gt;&lt;/table&gt;  &lt;p&gt;everyone starts at the left - where they can see all four choices for the 09:30 session - looks up &amp;amp; down, picks their first track, moves to the right, picks their second, and it all just unfolds a lot more... elegantly :)&lt;/p&gt;  </description>
          <pubDate>2008-02-05T00:55:00+00:00</pubDate>
          <link>https://dylanbeattie.net/2008/02/05/reflections-on-altnetuk.html</link>
          <guid isPermaLink="true">https://dylanbeattie.net/2008/02/05/reflections-on-altnetuk.html</guid>
        </item>
      
    
  </channel>
</rss>