David Pine — @IEvangelistSenior software engineer at Microsoft building aspire.dev, former Google Developer Expert in web technologies. Bestselling author, international speaker, and open source enthusiast.https://davidpine.devASP.NET Core RC2 (Migration Guide)https://davidpine.dev/posts/migrating-to-rc2https://davidpine.dev/posts/migrating-to-rc2Migrating from RC1 to RC2Sat, 21 May 2016 00:00:00 GMT<h1>Introduction</h1> <p>On Monday, May 16&lt;sup&gt;th&lt;/sup&gt; 2016 there was an amazing announcement from the <strong>.NET Blog</strong> team!</p> <blockquote> <p><a href="https://blogs.msdn.microsoft.com/dotnet/2016/05/16/announcing-net-core-rc2/">Announcing .NET Core RC2 and .NET Core SDK Preview 1</a></p> </blockquote> <p>This post will serve as a guide that walks you through the steps as I experienced them from migrating existing <code>ASP.NET Core RC1</code> applications to <code>ASP.NET Core RC2</code>. It is worth mentioning that I'm am targeting the <code>.NET 4.6</code> framework. So, I <em>do not</em> cover the <code>.netcoreapp</code> or <code>netstandard</code> TFMs (target framework monikers).</p> <h2>Tooling</h2> <p>The <strong>RC2</strong> release brings the <code>.NET Core CLI</code> to the forefront, and with that all the other command line tooling that you've grown accustomed to should be stricken from your environment, i.e.; <code>DNX</code>, <code>DNU</code>, and <code>DNVM</code>. Let's start with the removal of all these utilities.</p> <h3>Removing DNVM</h3> <p>Believe it or not, you might have a dated version of the <strong>DNVM CLI</strong> and in order to invoke the <code>uninstall</code> you might have to first perform an <code>update-self</code> command. Doing so will ensure that you have the latest version, which will contain the <code>uninstall</code> command. From a <code>cmd</code> window <em>running as admininistrator</em>, execute the following:</p> <pre><code>dnvm update-self </code></pre> <p>Then execute the following:</p> <pre><code>dnvm uninstall </code></pre> <h4>Verification</h4> <p>From a new <code>cmd</code> window, the <code>dnvm</code> command should result in the following:</p> <pre><code>λ dnvm 'dnvm' is not recognized as an internal or external command, operable program or batch file. </code></pre> <h3>Removing DNX &amp; DNU</h3> <p>From a <code>cmd</code> line window, execute <code>dnx</code> - you should see something similar to this as output:</p> <pre><code>λ dnx Microsoft .NET Execution environment Clr-x86-1.0.0-rc1-16231 Usage: dnx [options] Options: --project|-p &lt;PATH&gt; Path to the project.json file or the application folder. Defaults to the current folder if not provided. --appbase &lt;PATH&gt; Application base directory path --lib &lt;LIB_PATHS&gt; Paths used for library look-up --debug Waits for the debugger to attach before beginning execution. --bootstrapper-debug Waits for the debugger to attach before bootstrapping runtime. --framework &lt;FRAMEWORK_ID&gt; Set the framework version to use when running (i.e. dnx451, dnx452, dnx46, ...) -?|-h|--help Show help information --version Show version information --watch Watch file changes --packages &lt;PACKAGE_DIR&gt; Directory containing packages --configuration &lt;CONFIGURATION&gt; The configuration to run under --port &lt;PORT&gt; The port to the compilation server </code></pre> <p>Likewise enter <code>dnu</code> in the same <code>cmd</code> window, expecting the following output:</p> <pre><code>λ dnu Microsoft .NET Development Utility Clr-x86-1.0.0-rc1-16231 Usage: dnu [options] [command] Options: -v|--verbose Show verbose output -?|-h|--help Show help information --version Show version information Commands: build Produce assemblies for the project in given directory clear-http-cache Clears the package cache. commands Commands related to managing application commands (install, uninstall) feeds Commands related to managing package feeds currently in use install Install the given dependency list Print the dependencies of a given project pack Build NuGet packages for the project in given directory packages Commands related to managing local and remote packages folders publish Publish application for deployment restore Restore packages wrap Wrap a csproj/assembly into a project.json, which can be referenced by project.json files Use "dnu [command] --help" for more information about a command. </code></pre> <p>The <code>DNX</code> and <code>DNU</code> command line tools are simply <code>exe's</code> in your <code>%USERPROFILE%\.dnx</code> and <code>%USERPROFILE%\.dnx\bin</code> directories. Navigate to your <code>%USERPROFILE%</code> directory from windows explorer and delete the <code>.dnx</code> directory entirely. Once you have successfully deleted this directory you should be able to execute the <code>dnx</code> and <code>dnu</code> commands again from a <code>cmd</code> window and the system should complain that they are not recognized.</p> <h4>Verification</h4> <p>From a new <code>cmd</code> window, the <code>dnx</code> and <code>dnu</code> commands should result in the following:</p> <pre><code>λ dnx 'dnx' is not recognized as an internal or external command, operable program or batch file. </code></pre> <pre><code>λ dnu 'dnu' is not recognized as an internal or external command, operable program or batch file. </code></pre> <h3>Environment Variables</h3> <p>Unfortunately, the <code>PATH</code> environment variable is not cleaned up and this needs to be done manually.</p> <p>Press the &lt;kbd&gt;Windows&lt;/kbd&gt; key, then begin typing "environment", then select either of the two top options returned:</p> <p><img src="./win-search.png" alt="Windows Search" /></p> <p>You are then presented the "system properties" dialog, select <strong>Environment Variables</strong>.</p> <p><img src="./sys-props.png" alt="System Properties" /></p> <p>We will need to ensure that both <code>PATH</code> variables are cleaned up, including your <code>%USERPROFILE%</code> and the <code>System</code> variables. Let's &lt;kbd&gt;Edit&lt;/kbd&gt; both of them.</p> <p><img src="./env-vars.png" alt="Environment Variables" /></p> <p>From the <strong>Edit Variables</strong> dialog, remove any and all of the following:</p> <pre><code>C:\Users\[ your global identifier ]\.dnx\bin; C:\Users\[ your global identifier ]\.dnx\runtimes\dnx-clr-win-x86.1.0.0-rc1-update1\bin; </code></pre> <p><img src="./edit-vars.png" alt="Edit User Variables" /></p> <h3>.NET Core CLI</h3> <p>Simply follow <a href="https://www.microsoft.com/net/core#windows">these instuctions</a>. Once you're able to "Run the app" from the new <strong>CLI</strong> you're good to go.</p> <h4>Review</h4> <p>You should the following checklist fully satisfied at this point.</p> <p><strong>DNX</strong>, <strong>DNU</strong> and <strong>DNVM</strong> completely removed / uninstalled&lt;br&gt; <strong>Visual Studio 2015 Update 2</strong> installed&lt;br&gt; <strong>.NET Core CLI</strong> installed</p> <hr /> <h2>Edit by Hand</h2> <p>There were changes to <strong>APIs</strong> which required changes to implementation aspects of the source code. Additionally, <code>namespace</code> changes such as (but not limited to) <code>Microsoft.AspNet.*</code> to <code>Microsoft.AspNetCore.*</code>. But the bulk of the work was really in the hand-editing of <code>.sln</code>, <code>global.json</code>, <code>.xproj</code>, <code>project.json</code>, etc. files. Let's look at what changed.</p> <h3>The <code>.sln</code> file</h3> <p>Change the <strong>VisualStudioVersion</strong> from <code>14.0.24720.0</code> to <code>14.0.25123.0</code>.</p> <pre><code>VisualStudioVersion = 14.0.25123.0 </code></pre> <h3>The <code>global.json</code> file</h3> <p>Change the <strong>version</strong> from <code>1.0.0-rc1-update1</code> to <code>1.0.0-preview1-002702</code>.</p> <pre><code>{ "projects": [ "src", "test" ], "sdk": { "version": "1.0.0-preview1-002702" } } </code></pre> <h3>The <code>.xproj</code> file</h3> <p>The <code>.xproj</code> files are a little more involved, there are a few items that are required to be changed. In <strong>RC1</strong> we had proprerties that were specific to <strong>DNX</strong>, now we replace these with <strong>DOTNET</strong>.</p> <h4>RC1</h4> <pre><code>&lt;Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" /&gt; </code></pre> <h4>RC2</h4> <pre><code>&lt;Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" /&gt; </code></pre> <p>Additionally, we need to change the targets. This one requires that you know the type of project you're editing, for example <em>"class library"</em> vs. <em>"web site"</em>. In <strong>RC1</strong> there was only one target, but with <strong>RC2</strong> they are differeniated.</p> <h4>RC1</h4> <pre><code>&lt;Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" /&gt; </code></pre> <h4>RC2 (class library)</h4> <pre><code>&lt;Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" /&gt; </code></pre> <h4>RC2 (web)</h4> <pre><code>&lt;Import Project="$(VSToolsPath)\DotNet.Web\Microsoft.DotNet.Web.targets" Condition="'$(VSToolsPath)' != ''" /&gt; </code></pre> <h3>The <code>project.json</code> file</h3> <p>There has been a large reworking of the <code>project.json</code> file - detailed <a href="https://github.com/aspnet/Announcements/issues/175">here</a>. I will not be covering all of the changes here, but I do intend calling attention to some of the observations I have made and changes that were most pertinent. Here is the link for the official <a href="https://json.schemastore.org/project-1.0.0-rc2"><code>project.json schema</code></a>.</p> <p>We will look at various sections of the <code>project.json</code> file changes. Let's start with replacing <code>compilationOptionsbuildOptions</code> with <code>buildOptions</code>, and notice that we are adding much more options than before.</p> <h4>RC1</h4> <pre><code>"compilationOptions": { "emitEntryPoint": true, "warningsAsErrors": true }, </code></pre> <h4>RC2</h4> <pre><code>"buildOptions": { "emitEntryPoint": true, "warningsAsErrors": true, "preserveCompilationContext": true, "embed": "Views/**", // Instead of ".:resource": "Views/**", "compile": { "exclude": [ "wwwroot", "node_modules" ] // Now here, instead of ".:exclude" } }, </code></pre> <p>Additionally, we have <code>publishOptions</code> that we can utilize - consider the following:</p> <pre><code>"publishOptions": { "include": [ "wwwroot", "appsettings.json", "web.config" ] }, </code></pre> <p>Finally, we have to be a little more specific with how we want the server to handle garbage collection.</p> <pre><code>"runtimeOptions": { "gcServer": true, // Yes, please perform garbage collection "gcConcurrent": true // Yes, please do so concurrently... }, </code></pre> <h3>The <code>web.config</code> file</h3> <p>Now, <strong>IIS</strong> is a little smarter and is starting to recognize <strong>ASP.NET Core</strong> a little more.</p> <h4>RC1</h4> <pre><code>&lt;?xml version="1.0" encoding="utf-8"?&gt; &lt;configuration&gt; &lt;system.webServer&gt; &lt;handlers&gt; &lt;add name="httpPlatformHandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified"/&gt; &lt;/handlers&gt; &lt;httpPlatform processPath="%DNX_PATH%" arguments="%DNX_ARGS%" stdoutLogEnabled="false" /&gt; &lt;/system.webServer&gt; &lt;/configuration&gt; </code></pre> <h4>RC2</h4> <pre><code>&lt;?xml version="1.0" encoding="utf-8"?&gt; &lt;configuration&gt; &lt;system.webServer&gt; &lt;handlers&gt; &lt;add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" /&gt; &lt;/handlers&gt; &lt;aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" forwardWindowsAuthToken="false" stdoutLogEnabled="false" /&gt; &lt;/system.webServer&gt; &lt;/configuration&gt; </code></pre> <h3>The <code>hosting.json</code> file</h3> <p>With the release of <strong>RC2</strong>, the <code>project.json</code> schema removed the <code>webroot</code> property. Now in order to specify the websites' static content directory we need to create a <code>hosting.json</code> file. This file should look like this.</p> <pre><code>{ "webroot": "wwwroot" } </code></pre> <h3>The <code>appsettings.json</code> file</h3> <p>This was a minor change, but if you forgot to change this - then you'll end up with a runtime exception. The <code>Logging:LogLevel:Default</code> should now be <code>Debug</code> as <code>Verbose</code> was removed altogether.</p> <h4>RC1</h4> <pre><code>"Logging": { "IncludeScopes": false, "LogLevel": { "Default": "Verbose", // &lt; -- Change "Verbose" "System": "Information", "Microsoft": "Information" } </code></pre> <h4>RC2</h4> <pre><code>"Logging": { "IncludeScopes": false, "LogLevel": { "Default": "Debug", // &lt; --- To "Debug" "System": "Information", "Microsoft": "Information" } </code></pre> <h3>The <code>launchSettings.json</code> file</h3> <p>The <code>environmentVariables</code> changed, such that the key name for the environment is no longer referred to as "Hosting:Environment".</p> <h4>RC1</h4> <pre><code>"environmentVariables": { "Hosting:Environment": "Development" // &lt; -- Change "Hosting:Environment" } </code></pre> <h4>RC2</h4> <pre><code>"environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" // &lt; -- To "ASPNETCORE_ENVIRONMENT" } </code></pre> <h3>The <code>*.cs</code> files</h3> <p>Some of these were very simple to fix. After updating your <code>project.json</code> with the latest <strong>RC2</strong> versions - some changes are as simple as a <code>namespace</code> change (but others are more involed).</p> <h4>RC1</h4> <pre><code>using Microsoft.AspNet.*; </code></pre> <h4>RC2</h4> <pre><code>using Microsoft.AspNetCore.*; </code></pre> <h3>API changes</h3> <p>There was an attempt to unify some of the APIs as it pertains to consistency. As such, I had to make the following changes to my projects. The <code>IApplicationBuilder.Use*</code> pattern now takes a new <code>*Options</code> instance rather than an <code>Action&lt;*Options&gt;</code>, providing more control to the consumer.</p> <h4>RC1</h4> <pre><code>app.UseCookieAuthentication(options =&gt; { options.AuthenticationScheme = "AspNetCookie"; options.AutomaticAuthenticate = true; options.AutomaticChallenge = true; options.LoginPath = "/account/login"; options.LogoutPath = "/account/logout"; options.AccessDeniedPath = "/account/forbidden"; }); </code></pre> <h4>RC2</h4> <pre><code>app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationScheme = AuthenticationScheme.JciCookie, AutomaticAuthenticate = true, AutomaticChallenge = true, LoginPath = "/account/login", LogoutPath = "/account/logout", AccessDeniedPath = "/account/forbidden", }); </code></pre> <p>The service locator pattern (or should I say "anti-pattern") is slowing being removed from the framework. As such, if you were relying on the <code>CallContextServiceLocator.Locator.ServiceProvider</code> you can no longer do so with the <strong>RC2</strong> release.</p> <p>Wherever you were using the <code>IApplication</code> interface, you should be able to instead use the <code>IWebHost</code> interface.</p> <h3>Controllers as Services</h3> <p>In <strong>RC1</strong>, there was a nice little feature that allowed for any <code>Type</code> to exist in a list of plausible controller/services - meaning that you could basically register external controllers as valid API and View entry points into your hosting application. You simply needed to add the types via the following:</p> <pre><code>IMvcBuilder.AddControllersAsServices(params Type[] types); </code></pre> <p>This method signature changed with <strong>RC2</strong> and it no longer accepts any arguments. In order to retain this functionality, you must first add the desired external assemblies as an <code>AssemblyPart</code>.</p> <pre><code>foreach (var assembly in GetExternalAssemblies()) { builder.PartManager.ApplicationParts.Add(new AssemblyPart(assembly)); } builder.AddControllersAsServices(); </code></pre> <h3>The Startup <code>.ctor</code></h3> <p>One little change, you're now required to explicitly set the base path and you can do so with the following.</p> <pre><code>public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) // Explicity set base from the content root path .AddJsonFile("appsettings.json", true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", true) .AddEnvironmentVariables(); // Omitted for brevity... } </code></pre> <h3>The <code>Program.cs</code> file</h3> <p>Yes, we are now a console application...so we'll need this for our entry point.</p> <pre><code>public class Program { public static void Main(string[] arguments) { using (var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup&lt;Startup&gt;() .Build();) { host.Run(); } } } </code></pre> <h2>Breaking changes</h2> <p>For a complete listing of all the announcements, please visit the official <a href="https://github.com/aspnet/Announcements/issues?q=is%3Aopen+is%3Aissue+milestone%3A1.0.0-rc2"><strong>ASP.NET Repo</strong></a> announcements issue.</p> <h2>Further reading</h2> <p>The previous steps were really to get your environment in an <strong>RC2</strong> ready state, but there is so much more you can learn that has changed. I encourage bookmarking and reading the following articles as time permits.</p> <ul> <li><a href="https://dotnet.github.io/docs/core-concepts/dnx-migration.html"><strong>Migrating from DNX to .NET Core CLI</strong></a></li> <li><a href="https://docs.asp.net/en/latest/migration/rc1-to-rc2.html"><strong>Migrating from ASP.NET 5 RC1 to ASP.NET Core</strong></a></li> <li><a href="https://github.com/dotnet/corefx/blob/master/Documentation/architecture/net-platform-standard.md"><strong>The .NET Platform Standard</strong></a></li> <li><a href="https://dotnet.github.io/docs/core-concepts/libraries/libraries-with-cli.html"><strong>Writing Libraries with Cross Platform Tools</strong></a></li> </ul> <h2>Conclusion</h2> <p>There, that really wasn't too bad - was it? I'm certain that I didn't cover everything and that I probably missed something. I hope that this was helpful, please feel free to share this!</p> David PineThe power of the global.jsonhttps://davidpine.dev/posts/the-global-jsonhttps://davidpine.dev/posts/the-global-jsonLeveraging .NET Core Tooling FeaturesTue, 19 Jul 2016 00:00:00 GMT<h1>Introduction</h1> <p>As I'm sure you're all aware by now, <a href="https://blogs.msdn.microsoft.com/dotnet/2016/06/27/announcing-net-core-1-0/">Monday June 27&lt;sup&gt;th&lt;/sup&gt;</a> was a special day for those of you who were anticipating the release of <code>.NET Core</code> and <code>ASP.NET Core</code>. While the "core" frameworks and runtimes are RTM, the tooling is actually still in preview. In fact, we're currently on <em>Tools Preview 2</em>. I wanted to focus this post entirely on one specific enhancement to <strong>Visual Studio</strong> solutions, enter the <code>global.json</code>.</p> <hr /> <p>Let me paint a picture for you...you're developing an application. This application doesn't have all the source code in the world, so you take on some dependencies. Since we're not living in caves, we have packages -- this is common nomenclature for and the norm for any modern software development project. One of the aforementioned dependencies is a package that you just so happen to also work on and develop for. Imagine that you find a bug or want to make an improvement to said package, what does that work-flow look like?</p> <p>For me, this typically involved the following steps:</p> <ol> <li>Open dependency project in a new instance of <strong>Visual Studio</strong></li> <li>Make code change</li> <li>Code review</li> <li>Commit code</li> <li>Build server builds code</li> <li>Build goes "green" (tests passed)</li> <li>Build server publishes package</li> <li>Wander the halls playing Pokémon Go</li> <li>Return to original project, update package reference</li> <li>Continue with life</li> </ol> <p>What if I told you, that headache is all but gone and you will never have to go through those agonizing steps again! Say it ain't so (queue <a href="https://youtu.be/ENXvZ9YRjbo">Weezer</a>).</p> <h2>Global.json</h2> <p>The <code>global.json</code> file sits out at the solution directory-level and literally has one of the simplest <code>JSON</code> schemas you will ever see. The default templates generate the following (for the <a href="https://json.schemastore.org/global">full schema</a>):</p> <pre><code>{ "projects": [ "src", "test" ], "sdk": { "version": "1.0.0-preview2-003121" } } </code></pre> <p>I always immediately delete the <code>sdk</code> property. If omitted the tooling simply assumes the latest <strong>SDK</strong> installed. Now, for all the fun! The <code>projects</code> property is an array of strings. It's defined as the following:</p> <blockquote> <p>A list of project folders relative to this file.</p> </blockquote> <p>These folders are search locations for resolving dependencies, where projects take precedence over packages.</p> <h3>Projects over Packages</h3> <p>Assume you're developing in a project, namely <code>IEvangelist.NetCore.App</code> and it takes a dependency on <code>IEvangelist.NetCore.ClassLib</code> and <code>IEvangelist.NetCore.Services</code>. These dependencies are packages (outside the solution) and you can use the <code>global.json/projects</code> array to your advantage. Using our imaginations, let us find a need to source-step into <code>IEvangelist.NetCore.ClassLib</code>. Let us also imagine that this project is in a neighboring folder outside our solution, perhaps the same repo, we can now use a relative path like so:</p> <pre><code>{ "projects": [ "src", "../../IEvangelist.NetCore.ClassLib/src", // &lt;-- Relative path to packaged source "../../IEvangelist.NetCore.Services/src" ] } </code></pre> <h4>Before</h4> <p><img src="./before.jpg" alt="Before global.json changes" /></p> <h4>After</h4> <p>After saving these changes to the <code>global.json</code> you'll notice <strong>Visual Studio</strong> pull this project into solution, but outside of the <code>src</code> folder. Now when you debug you can actually step into the source and treat it like any other source code you have. Project dependencies are resolved by way of their <code>project.json</code> being discovered.</p> <p><img src="./after.jpg" alt="After global.json changes" /></p> <h3>Open Source for Everyone</h3> <p>One of the biggest advantages in my opinion is the ability to pull down source from <code>github</code> that I rely on and source-step into it. For example, I have been developing with <code>ASP.NET Core</code> since <strong>RC1</strong>, nearly eight months!! With the power of the <code>global.json</code> I can pull down source that I'm relying on in a package and fulfill my understanding of it as it executes. Simply amazing!</p> <h2>Further reading</h2> <p>For more details, these are some additional links and points of reference.</p> <ul> <li><a href="https://docs.microsoft.com/en-us/dotnet/articles/core/tools/global-json"><strong>Global.json Reference</strong></a></li> <li><a href="https://stackoverflow.com/a/34794054/2410379"><strong>Explanation of projects section in Global.json</strong></a></li> </ul> David PineAngular2 Http with RxJS Observableshttps://davidpine.dev/posts/angular-2-httphttps://davidpine.dev/posts/angular-2-httpSubscribing has never been so easyWed, 31 Aug 2016 00:00:00 GMT<p>If you have been following the development efforts of the <strong>Angular2</strong> project, you have witnessed certain highs and lows - but it has been a fun ride. The latest version is only a <strong>Release Candidate</strong> and the team is getting closer to the final release. I'm really looking forward to that! I wanted to take a moment to highlight (IMO) one of the key services of <strong>Angular2</strong>, the <code>http</code> service.</p> <p>In <strong>AngularJs 1</strong> the <code>ng.IHttpService</code> (aka, <a href="https://docs.angularjs.org/api/ng/service/$http"><code>$http</code></a>) was based on <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">promises</a> and deferrals. In <strong>Angular2</strong> we now rely on <strong>RxJS</strong> and the observable pattern. In my opinion this is a huge win! If you're unfamiliar with <em>Reactive Extensions</em> in general, I suggest starting <a href="https://msdn.microsoft.com/en-us/data/gg577609.aspx">here</a>. <strong>RxJS</strong> is the <code>JavaScript</code> implementation of <em>Reactive Extensions</em>. Let's take a moment to compare and contrast the two, and immerse ourselves in the wonderful world of <strong>RxJS</strong>. Let me be clear upfront that I'm developing with <code>TypeScript</code>.</p> <h2>Legacy Pattern</h2> <p>As a developer you would have to use both the <code>ng.IHttpService</code> and the <code>ng.IQService</code> in combination to collaborate the deferral of the HTTP request and the promise that represented it. Consider the following:</p> <pre><code>module ExampleModule { export class ExampleService implements IExampleService { static $inject = ["$http", "$q"]; private $http: ng.IHttpService; private $q: ng.IQService; constructor($http: ng.IHttpService, $q: ng.IQService) { this.$http = $http; this.$q = $q; } public getFooBars(): ng.IPromise&lt;FooBar[]&gt; { var deferred = this.$q.defer&lt;FooBar[]&gt;(); this.$http .get("api/foobar") .success((data) =&gt; { deferred.resolve(data); }) .error((error) =&gt; { console.log("An error occurred when requesting api/foobar.", error); deferred.reject(error); }); return deferred.promise; } } } </code></pre> <p>In this example, we can easily see the interaction betwixt the <code>ng.IHttpService ($http)</code> and <code>ng.IQService ($q)</code> services. The <code>$q</code> variable exposes a <code>.defer&lt;T&gt;</code> function that returns a deferred object.</p> <h3><a href="https://docs.angularjs.org/api/ng/service/$q#the-deferred-api">Deferred API</a></h3> <table> <thead> <tr> <th>Function</th> <th>Parameters</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td><code>resolve</code></td> <td><code>(value: T)</code></td> <td>Resolved yielding the materialized value of type <code>T</code></td> </tr> <tr> <td><code>reject</code></td> <td><code>(reason: string)</code></td> <td>Rejected with the given reason</td> </tr> </tbody> </table> <p>The deferred object instance is passed into the fluent API's of the <code>$http's</code> <code>.success</code> and <code>.error</code> functions accordingly. This pattern works great, but is very limiting and repetitive. You end up writing a lot of boilerplate code and that isn't very <a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself">DRY</a>. Let's look at how this is approached with <strong>Angular2's</strong> <code>http</code> service using the observable pattern from <strong>RxJS</strong>!</p> <h2>New Pattern</h2> <p>To be fair, let's implement the same functionality and public surface-area such that our example services are equivalent.</p> <pre><code>import {Observable} from "RxJS/Rx"; import {Injectable} from "@angular/core"; import {Http} from "@angular/http"; @Injectable() export class ExampleService { constructor(private http: Http) { } getFooBars(onNext: (fooBars: FooBar[]) =&gt; void) { this.http .get("api/foobar") .map(response =&gt; response.json() as FooBar[]) .subscribe(onNext, error =&gt; console.log("An error occurred when requesting api/foobar.", error)); } } </code></pre> <p><em>I am hoping that you noticed how much cleaner this code is, as well as how much more readable!</em></p> <p>Now, I know what you're thinking...these cannot possibly be the same examples, but they are in fact doing the same thing. <em>Dependency Injection (DI)</em> in <strong>Angular2</strong> is a lot less error prone (no more magic strings) and way easier than it was in <strong>AngularJs 1</strong>. Simply do what you'd expect from any other common constructor-based DI framework, ensure that your desired <code>Http</code> type is registered as a provider to the system. This happens by way of the <code>HTTP_PROVIDERS</code> defined in our <code>boot.js</code> file. More on that in another post. With modern <code>TypeScript</code> we can define properties and fields, and their corresponding access modifiers right from within our constructors.</p> <h4>Syntax Tip</h4> <p>This exemplifies the comparisons in syntax between a simple constructor and the more verbose constructor.</p> <pre><code>// Simple .ctor() constructor(private http: Http) { } // Is equivalent to... private http: Http; constructor(http: Http) { this.http = http; } </code></pre> <p>Likewise, the following is true regarding public access modifiers.</p> <pre><code>// Simple .ctor() constructor(http: Http) { } // Is equivalent to... http: Http; // When the access modifier is omitted it's defaulted to public constructor(http: Http) { this.http = http; } </code></pre> <h2>Comparing the APIs</h2> <p>Instead of the <code>.success</code> invocation with a corresponding <code>deferred.resolve</code> call, we now utilize the <strong>RxJS</strong> <code>.map</code> and <code>.subscribe</code> operators. Let's look at these below:</p> <table> <thead> <tr> <th>Operator</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td><a href="https://reactivex.io/documentation/operators/map.html"><code>map</code></a></td> <td>Transform the items emitted by an <code>Observable</code> by applying a function to each item</td> </tr> <tr> <td><a href="https://reactivex.io/documentation/operators/subscribe.html"><code>subscribe</code></a></td> <td>The <code>Subscribe</code> operator is the glue that connects an observer to an <code>Observable</code></td> </tr> </tbody> </table> <p>Mapping is easy and we can leverage some of the <code>TypeScript</code> language features to cast the <strong>JSON</strong> blobs returned from our services as strongly typed objects. The <code>map</code> operator is actually synonymous with the <code>select</code> operator, so if you're more familiar with that terminology you can use it interchangeably.</p> <h3>Advantages</h3> <p>Now that we have an understanding of how <strong>RxJS</strong> compares to the legacy pattern, we can take advantage of all the various benefits. Imagine with me that we have a need to implement retry logic, this would have been challenging with the legacy pattern but with the new pattern it's as simple as saying <code>.retry</code>. Consider the following:</p> <pre><code>// If this call fails, we'll try it again with the same payload two times getFooBars(onNext: (fooBars: FooBar[]) =&gt; void) { this.http .get("api/foobar") .map(response =&gt; &lt;FooBar[]&gt;response.json()) .retry(2) .subscribe(onNext, error =&gt; console.log("An error occurred when requesting api/foobar.", error)); } </code></pre> <p>Now imagine a scenario where a user is typing and you want to provide an autocomplete, you could use <code>.debounce</code> to pause for a brief moment prior to sending the request. Likewise, we could apply a <code>.filter</code> that only takes action when a certain number of characters have been entered. Finally, we might utilize <code>.distinctUntilChanged</code> to only execute the request once the values are actually different than they once were.</p> <p>You could take advantage of <code>.buffer</code>, <code>.throttle</code>, <code>.interval</code>, <code>.window</code>, <code>.range</code>, etc... The list goes on and on, and <a href="https://github.com/Reactive-Extensions/RxJS/tree/master/src/core/linq/observable">this is the source for most of what you can take advantage</a>.</p> <h3>Let's Summarize</h3> <p><strong>Angular2</strong> has a new implementation of their <code>http</code> service that relies on <strong>RxJS</strong>. The <em>API</em> uses <code>observables</code> and the <code>observer</code> pattern to allow for a fluent experience that is rich and robust. Getting started is straight-forward and simple. Before too long you'll be taking advantage of the feature-full set of <strong>Reactive Extensions</strong> and thinking in terms of data streams. This mindset will make your life easier - trust me!</p> <p><strong>Further Reading</strong></p> <ul> <li><a href="https://angular.io/docs/ts/latest/api/http/index/Http-class.html">Angular2 for TypeScript, Http</a></li> <li><a href="https://github.com/Reactive-Extensions/RxJS#why-RxJS">Why RxJS?</a></li> </ul> David PineOvercoming Impostor Syndromehttps://davidpine.dev/posts/imposter-syndromehttps://davidpine.dev/posts/imposter-syndromeTaking off the maskSat, 05 Nov 2016 00:00:00 GMT<p>import { YouTube } from '@astro-community/astro-embed-youtube'; import { Tweet } from '@astro-community/astro-embed-twitter';</p> <p>What is "impostor syndrome" you ask?</p> <p>According to &lt;cite&gt;<a href="https://en.wikipedia.org/wiki/Impostor_syndrome">Wikipedia</a>&lt;/cite&gt;:</p> <blockquote> <p><strong>Impostor syndrome</strong> (also known as <em>impostor phenomenon</em> or <em>fraud syndrome</em>) is a term coined in 1978 by clinical psychologists Dr. Pauline R. Clance and Suzanne A. Imes referring to high-achieving individuals marked by an inability to internalize their accomplishments and a persistent fear of being exposed as a "fraud". Despite external evidence of their competence, those exhibiting the syndrome remain convinced that they are frauds and do not deserve the success they have achieved. Proof of success is dismissed as luck, timing, or as a result of deceiving others into thinking they are more intelligent and competent than they believe themselves to be.</p> </blockquote> <p>It seems as though this syndrome has plagued our generation of developers! I have found that this syndrome has spread like wildfire through the technical community, devastating an individuals confidence and leveling their ambitions! Too often developers are doubting themselves and this needs to stop. As an industry we need to work to correct this.</p> <p>We can all agree that <a href="https://www.hanselman.com/blog/ImAPhonyAreYou.aspx">I'm not the only one</a>. If you haven't felt this way, [ close this tab now ]. If you're still reading, then thank you for your honesty.</p> <p>I'm certain you've heard of "technical debt", I'd like to coin a new phrase - "technical doubt".</p> <blockquote> <p><strong>Technical Doubt</strong> is a term used to define an individuals lack of confidence as it pertains to a technical problem, wherein every solution the individual arrives at is combated with reluctancy and inadequacy.</p> </blockquote> <p>Software engineering can be boiled down to the manipulation and movement of data. When you're a software engineer working on a task that involves either of these aspects of development you have to solve problems. Therefore, we're really just problem solvers, right?! The interesting thing is that there is always more than one way to solve a problem. And more often than not the way that you choose to solve said problem will not exactly align with the visions of your peers - this leads to potential conflict, and with conflict comes frustration and disarray!</p> <p>We've all heard of the "Tabs vs Spaces" argument, right -- it's somewhat like that but much worse.</p> <p>&lt;YouTube id="https://youtu.be/cowtgmZuai0" /&gt;</p> <p>I believe that this is often the root of imposter syndrome, the fear of getting it wrong or being involved in some sort of conflict. I finally feel as though I no longer have this fear, therefore -- I have overcome imposter syndrome. I wanted to share some of the reasons that have led up to this monumental event.</p> <h3>Positivity</h3> <p>If you want to see a blackhole, bear witness to how negativity spreads through an office. A single person with a bad attitude can "infect" others and really be a drain! Be the one who combats that, be the person who seeks out the positive aspects of every situation. I'm the guy who says "it could always be worse", because it literally could. I act as the voice of reason -- and pure myself into living as a positive influence on the team.</p> <h3>Take Action</h3> <p>Personally, I have discovered that the more I compliment others the more I get complimented in return. The more I encourage others, the more I'm encouraged by them in return. The more you give, the more you get. Be humble and open-minded!</p> <h3>Mentor</h3> <p>I have found pride in mentoring. I feel as though there is always a satisfying challenge to overcome and a lesson learned. Often the lesson is two-sided, where I am a secondary beneficiary from the engagement. Often, explaining your experiences to the mentee reminds you of how much you have really been through. Reflecting on how you've navigated through a situation is surprisingly insightful. It's kind of like <a href="https://blog.codinghorror.com/rubber-duck-problem-solving/">rubber duck programming</a>.</p> <h3>Fear not</h3> <p>Never be afraid to ask "why", this can be extremely powerful. Sometimes the root of the problem is that there has been a misunderstanding at the root -- therefore all things leading up to where you're asking (what you believe to be a stupid question) is actually the best question that could have been asked. Find the root!</p> <h3>Community</h3> <p>If you have ever spent more than a few minutes with me, you will know that I'm an advocate of the technical community. Believe it or not, the community is extremely supportive and willing to lift you up -- all you have to do is being willing to put yourself out there. While this is humbling, it is also rewarding...</p> <h3>MKE DOT NET</h3> <p>I sit on the technical committee for <a href="https://www.mkedotnet.com/">MKE DOT NET</a>, a developer conference in the Milwaukee area. Recently I was fortunate enough to get <a href="https://www.hanselman.com/">Scott Hanselman</a> to be our keynote speaker. During his keynote (which was amazing) I shouted out several times to assist him with live coding / demonstration errors. He referred to me as his "second compiler"!</p> <p>&lt;Tweet id="https://twitter.com/davidpine7/status/792480943416156161" /&gt;</p> <p>&lt;Tweet id="https://twitter.com/davidpine7/status/792544455635132417" /&gt;</p> <h3>Stack Overflow</h3> <p>I set out at the beginning of the 2016 year with a goal to try to answer one SO question a day. Honestly, it is a lot more demanding than you might imagine -- but it's extremely gratifying! I have literally earned more attempting to answer questions than I have had asking or reading others questions. I answered <a href="https://stackoverflow.com/a/40045456/2410379">an <strong>ASP.NET Core</strong> question</a> that got the attention of <a href="https://davidfowl.com/">David Folwer</a>, holy crap -- IKR! Luckily after his <em>code review</em> and my <em>update</em> to the answer, "the garbage collector thanked me".</p> <p>Getting the accepted answer over mega-super stars like <a href="https://stackoverflow.com/a/36950086/2410379">Stephan Cleary</a> can really make your head spin, even if they get more upvotes.</p> <h4>Let me compile that for you</h4> <p>&lt;Tweet id="https://twitter.com/jonskeet/status/742718249725480960" /&gt;</p> <p>&lt;Tweet id="https://twitter.com/jonskeet/status/742739271107366912" /&gt;</p> <h4>Let me ASP.NET 5 that for you</h4> <p>&lt;Tweet id="https://twitter.com/jonskeet/status/716378423824945152" /&gt;</p> <p>&lt;Tweet id="https://twitter.com/jonskeet/status/716382973671706628" /&gt;</p> <p>Yes, you read that correctly. That is me helping out the legand <a href="https://stackoverflow.com/users/22656/jon-skeet">Jon Skeet</a> (arguably the world's best software engineer)!</p> <h2>Summary</h2> <p>These are minuscule things that really add up. Don't take it from me, you owe it to yourself to see what you're really made off. I believe in you already, if you made it through this post you have already accomplished more than most of your peers today. There is so much more that I want to give back to the community that I know this is only the beginning of something profound. I strive to do my best and I now know that it's enough!</p> <ul> <li>Positivity, be the seeker of all things good and the voice of reason</li> <li>Take action, give encouragement, compliments and praise</li> <li>Be a mentor, there is nothing like teaching and you'll learn too</li> <li>Fear not, never be afraid to ask "why", it might just solve the problem</li> <li>Community, get involved and give back</li> <li>Inject yourself where you want to be</li> </ul> <p>These tips will help you overcome imposter syndrome -- it worked for me. Together we can kill "technical doubt"!</p> David PineASP.NET Core Response Optimizationhttps://davidpine.dev/posts/asp-net-core-optimizationhttps://davidpine.dev/posts/asp-net-core-optimizationStatic File Caching & CompressionMon, 12 Dec 2016 00:00:00 GMT<h2>Intro</h2> <p>If you're a web developer, chances are you're familiar with optimization strategies such as static file caching and response compression. I recently implemented these two concepts in tandem on an <strong>ASP.NET Core</strong> application that I have been developing... I'm going to share what I have learned.</p> <p>If you haven't had a chance to use <a href="https://www.asp.net/core"><code>ASP.NET Core</code></a> yet, you're missing out! As my friend <a href="https://scottaddie.com/">Scott Addie</a> likes to say:</p> <blockquote> <p><strong>ASP.NET Core</strong> is a cafeteria plan in which developers choose application dependencies <em>à la carte</em>. This is in stark contrast to <strong>ASP.NET</strong> proper, where developers are provided a set meal (a bloated dependency chain) containing undesired items. Don't like broccoli with your steak? Maybe it's time to consider <strong>ASP.NET Core</strong>. &lt;cite&gt;Scott Addie&lt;/cite&gt;</p> </blockquote> <p>In other words, all dependencies are pluggable middleware. This provides ultimate control as you have to explicitly <em>opt-in</em> for the middleware you desire.</p> <h3>Know thy Middleware</h3> <p>Not all middleware is equal. Different middleware serves different purposes (obviously). Try to think of each middleware as its own standalone feature-set. Not all middleware is given a chance to execute on a given request. Certain middleware might send a web response and early exit. This can prevent other middleware in the pipeline from executing at all. In fact, this is the case when using static file caching and response compression together.</p> <blockquote> <p>The <em>order</em> in which middleware is added matters and dictates the order of execution during a request.</p> </blockquote> <h4>Installing Dependencies</h4> <p>I wrote a tiny application and put it up on GitHub, check it out <a href="https://github.com/IEvangelist/IEvangelist.AspNetCore.Optimization">here</a> if you want to follow along. Now let's install the dependencies we'll need. For static file caching and response compression we need to add two <code>dependencies</code> to the project.</p> <p><em>Note:</em> <code>Microsoft.AspNetCore.StaticFiles</code> will already have been installed if you started from a <strong>Visual Studio</strong> template.</p> <table> <thead> <tr> <th>Repository</th> <th>Version</th> <th>Nuget Package</th> <th>Add / Use Extension Method(s)</th> </tr> </thead> <tbody> <tr> <td><a href="https://github.com/aspnet/StaticFiles"></a></td> <td><code>1.1.0</code></td> <td><a href="https://www.nuget.org/packages/Microsoft.AspNetCore.StaticFiles/">Static Files</a></td> <td><a href="https://github.com/aspnet/StaticFiles/blob/dev/src/Microsoft.AspNetCore.StaticFiles/StaticFileExtensions.cs#L56-L68">UseStaticFiles</a></td> </tr> <tr> <td><a href="https://github.com/aspnet/BasicMiddleware"></a></td> <td><code>1.0.0</code></td> <td><a href="https://www.nuget.org/packages/Microsoft.AspNetCore.ResponseCompression/">Response Compression</a></td> <td><a href="https://github.com/aspnet/BasicMiddleware/blob/dev/src/Microsoft.AspNetCore.ResponseCompression/ResponseCompressionServicesExtensions.cs#L38-L53">AddResponseCompression</a> <a href="https://github.com/aspnet/BasicMiddleware/blob/dev/src/Microsoft.AspNetCore.ResponseCompression/ResponseCompressionBuilderExtensions.cs#L20-L29">UseResponseCompression</a></td> </tr> </tbody> </table> <p>Follow these instructions, from within <strong>Visual Studio</strong>:</p> <pre><code>Tools ➪ NuGet Package Manager ➪ Manage NuGet Packages for Solution... ➪ Browse ➪ Search ➪ [ Install ] </code></pre> <h3>Configuring Startup</h3> <p>There are two common nomenclatures that exist for wiring up middleware in your startup classes, the <code>.Add*</code> and <code>.Use*</code> extension methods. The <code>.Add*</code> calls are intended to add services to the <code>IServiceCollection</code> instance, ensuring that they are ready for <em>usage</em>. The <code>.Use*</code> calls specify that you want to use the middleware and makes the assumption that any services required by <strong>DI</strong> will have already been <em>added</em> with the corresponding <code>.Add*</code> call.</p> <p>Now let's "add" response compression in the <code>.ConfigureServices</code> call of our <code>Startup.cs</code>.</p> <pre><code>public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddResponseCompression( options =&gt; options.MimeTypes = ResponseCompressionMimeTypes.Defaults); } </code></pre> <p>Several bits of the implementation details should jump out at you. First, we do not need to call an <code>.AddStaticFiles</code> extension method because there are no services required for this middleware, as such it doesn't exist. Second, we are providing a lambda expression to satisfy the <code>Action&lt;ResponseCompressionOptions&gt;</code> parameter. We also assign the <code>.MimeTypes</code> property from the <code>ResponseCompressionMimeTypes.Defaults</code> we are targeting for compression.</p> <blockquote> <p>If no compression providers are specified then <code>GZip</code> is used by default. &lt;cite&gt;<a href="https://github.com/aspnet/BasicMiddleware/blob/dev/src/Microsoft.AspNetCore.ResponseCompression/ResponseCompressionProvider.cs#L22">ASP.NET Core Team - GitHub</a>&lt;/cite&gt;</p> </blockquote> <p>The <code>ResponseCompressionMimeTypes.cs</code> is defined as follows:</p> <pre><code>using System.Collections.Generic; using System.Linq; using static Microsoft.AspNetCore.ResponseCompression.ResponseCompressionDefaults; namespace IEvangelist.AspNetCore.Optimization { public static class ResponseCompressionMimeTypes { public static IEnumerable&lt;string&gt; Defaults =&gt; MimeTypes.Concat(new[] { "image/svg+xml", "application/font-woff2" }); } } </code></pre> <p>There are several types defined by default in <strong>ASP.NET Core</strong> class <code>ResponseCompressionDefaults.MimeTypes</code>, we are simply expanding that to include "SVG" images and the "Woff2" fonts.</p> <h3>Order Exemplified</h3> <p>Consider the following:</p> <pre><code>public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { app.UseStaticFiles() // Adds the static middleware to the request pipeline .UseResponseCompression(); // Adds the response compression to the request pipeline } </code></pre> <p>The snippet above will absolutely work for serving static files, but it will not compress or cache anything. Note the differences below:</p> <pre><code>public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { app.UseResponseCompression() // Adds the response compression to the request pipeline .UseStaticFiles(); // Adds the static middleware to the request pipeline } </code></pre> <p>The <em>order</em> in which the middleware was invoked in the pipeline changed, as such the order in which the middleware is executed on a request is also changed. When static file middleware occurs before response compression, it returns the file as a response before compression has a chance to execute.</p> <ul> <li>Client requests <code>main.css</code></li> <li>Static file middleware determines it can fully satisfy said request</li> <li>The <code>main.css</code> file is <a href="https://github.com/aspnet/StaticFiles/blob/dev/src/Microsoft.AspNetCore.StaticFiles/StaticFileMiddleware.cs#L109">sent</a> and the <a href="https://github.com/aspnet/StaticFiles/blob/dev/src/Microsoft.AspNetCore.StaticFiles/StaticFileMiddleware.cs#L126">next</a> middleware in the pipeline is never executed</li> </ul> <p>Now that we have these two pieces of middleware wired into out pipeline in the correct order, what else is left. There is one important thing that we forgot to do. While we do have static file middleware, we didn't know that "caching" is off by default. So we'll need to handle this with an instance of the the <code>StaticFileOptions</code>.</p> <blockquote> <p>Keep your 's open for extension method overloads. These are often clues that there are <em>options</em> for providing customized configuration for the middleware you're wiring up.</p> </blockquote> <pre><code>public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { app.UseResponseCompression() .UseStaticFiles( new StaticFileOptions { OnPrepareResponse = _ =&gt; _.Context.Response.Headers[HeaderNames.CacheControl] = "public,max-age=604800" // A week in seconds }) .UseMvc(routes =&gt; routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}")); } </code></pre> <p>The instance of the <code>StaticFileOptions</code> object has a public property namely "OnPrepareResponse" of type <code>Action&lt;StaticFileResponseContext&gt;</code>. So we can again specify a lambda expression. This expression can be used to delegate the preparation of the response. Notice we're simply setting the <code>Cache-Control</code> header to a "max-age" of a week. That was pretty simple, hey?!</p> <h3>Compression Awareness</h3> <p>It's all the little things! The response compression middleware boasts deterministic compression, i.e.; if the <a href="https://github.com/aspnet/KestrelHttpServer"><code>KestrelHttpServer</code></a> is running behind the <a href="https://www.iis.net/"><strong>IIS</strong></a> reverse proxy then the middleware may or may not compress the response. The middleware determines the following:</p> <ul> <li>If the request accepts compression</li> <li>If the requested resource matches the configured MIME types</li> <li>Whether or not the response needs to be compressed</li> </ul> <h3>Results</h3> <p>Let's spend some time examining the results of our response optimization efforts.</p> <h4>Before</h4> <p><img src="./before.png" alt="Before" /></p> <h5>Total(s)</h5> <pre><code>13 Requests | 549 KB transferred | ... | ... </code></pre> <p><strong>banner1.svg Response Headers</strong></p> <pre><code>HTTP/1.1 200 OK Content-Length: 9679 Content-Type: image/svg+xml Accept-Ranges: bytes ETag: "1d1ce31e3bd09cf" </code></pre> <h4>After</h4> <p><img src="./after.png" alt="After" /></p> <h5>Total(s)</h5> <pre><code>13 Requests | 175 KB transferred | ... | ... </code></pre> <p>The total payload for all 13 requests is a total of 3.137 times smaller (a meager 31.9% of the original size). The larger the application, the more dramatic and valuable this becomes! Consider an <strong>Angular2</strong> application (or other SPA framework based application), which has tons of <strong>JavaScript</strong> files to download -- 5 MB turns into ~1.5 MB.</p> <p><strong>banner1.svg Response Headers</strong></p> <pre><code>HTTP/1.1 200 OK Content-Type: image/svg+xml Server: Kestrel Cache-Control: public,max-age=604800 Transfer-Encoding: chunked Content-Encoding: gzip Accept-Ranges: bytes ETag: "1d1ce31e3bd09cf" </code></pre> <h4>Subsequent (Cached) Requests</h4> <p><img src="./cached.png" alt="Cached" /></p> <h3>Let's Summarize</h3> <p>We learned some of the basics about <strong>ASP.NET Core</strong> middleware. Together we implemented a response optimization strategy that included deterministic response compression, as well as static file caching. We learned some of the common patterns and naming conventions for integrating with <strong>ASP.NET Core</strong> middleware. Finally, we have a good understanding of the <strong>ASP.NET Core</strong> request pipeline middleware precedence.</p> <h3>Source Code</h3> <ul> <li><a href="https://github.com/IEvangelist/IEvangelist.AspNetCore.Optimization">IEvangelist.AspNetCore.Optimization</a></li> </ul> David PineBuilding a Magic Mirrorhttps://davidpine.dev/posts/building-a-magic-mirrorhttps://davidpine.dev/posts/building-a-magic-mirrorWindows 10 IoT Core, UWP, C#, Raspberry Pi 3Fri, 23 Dec 2016 00:00:00 GMT<p>import { YouTube } from '@astro-community/astro-embed-youtube'; import { Tweet } from '@astro-community/astro-embed-twitter';</p> <h1>Inspiration</h1> <p>I am certainly not the first one to create a magic mirror, and I will not be the last either. I was inspired by those who are true Innovators...some might say, "I'm standing on the shoulders of giants". They would probably we right, and I'm okay with that. Earlier this year, I stumbled upon a tweet about someone how created a magic mirror...this is the root of my inspiration.</p> <p>&lt;Tweet id="https://twitter.com/davidpine7/status/695318303326265345" /&gt;</p> <p>Before continuing on, allow me to explain what a magic mirror is. A magic mirror is simply a two-way mirror with a monitor positioned behind it that projects through the mirror. The monitor displays the application. The application is running on a small computer, in most cases a <strong>Raspberry Pi</strong>.</p> <p><a href="https://www.raspberrypi.org"><img src="./pi-logo.png" alt="Raspberry Pi" /></a></p> <p>I noticed that a lot of the magic mirrors had greeting messages such as "you look handsome" or "good morning sexy" and other curiously intuitive sayings. I thought "wow" this really is a smart mirror, much to my surprise these were all just static messages (or only dynamic when accounting for the time of day). I was thinking to myself, "I wonder if I could improve upon this", and that was my motivation!</p> <h2>Open Source</h2> <p>If you want to skip out from reading this post, you can checkout my project up on <strong>GitHub</strong>   <a href="https://www.github.com/IEvangelist/Mirror">IEvangelist.Mirror</a>.</p> <h1>Hardware</h1> <p>I was excited when <strong>Microsoft</strong> started selling the <strong>Raspberry Pi 3, Model B</strong> - I ordered one immediately.</p> <p><img src="./pi3.gif" alt="Raspberry Pi 3" /></p> <h2>Specs</h2> <p><img src="./pi-layout.png" alt="Specs" /></p> <h3>Miscellaneous</h3> <p>Below is a semi-comprehensive listing of all the materials and their corresponding cost that I used for building my magic mirror.</p> <table> <thead> <tr> <th>Rough Cost</th> <th>Hardware</th> </tr> </thead> <tbody> <tr> <td>$60</td> <td>&lt;a href="https://www.microsoftstore.com/store/msusa/en_US/pdp/Raspberry-Pi-3-Bundle/productID.5068162700" target="_blank"&gt;Raspberry Pi 3 Bundle&lt;/a&gt;</td> </tr> <tr> <td>$130</td> <td>&lt;a href="https://www.amazon.com/gp/product/B00IZBIMLK/ref=oh_aui_search_detailpage?ie=UTF8&amp;psc=1" target="_blank"&gt;BenQ 27" HDMI LED Monitor&lt;/a&gt;</td> </tr> <tr> <td>$90</td> <td>&lt;a href="https://www.tapplastics.com/product/plastics/cut_to_size_plastic/two_way_mirrored_acrylic/558" target="_blank"&gt;Custom-sized Two-way Mirrored Acrylic&lt;/a&gt;</td> </tr> <tr> <td>$40</td> <td>&lt;a href="https://www.amazon.com/Microsoft-LifeCam-Cinema-720p-Webcam/dp/B009CPC6QA" target="_blank"&gt;Microsoft LifeCam&lt;/a&gt;</td> </tr> <tr> <td>$25</td> <td>&lt;a href="https://www.amazon.com/gp/product/B00DEJXRAE/ref=oh_aui_search_detailpage?ie=UTF8&amp;psc=1" target="_blank"&gt;Dell USB Wired Soundbar&lt;/a&gt;</td> </tr> <tr> <td>$6</td> <td>&lt;a href="https://www.amazon.com/gp/product/B000234UFG/ref=oh_aui_detailpage_o06_s00?ie=UTF8&amp;psc=1" target="_blank"&gt;Power Cord Splitter&lt;/a&gt;</td> </tr> <tr> <td>$25</td> <td>Full HDMI Cable, USB Extension Cables</td> </tr> <tr> <td>$50</td> <td>Carpentry Materials</td> </tr> <tr> <td>$50</td> <td>Glenlivet 12 Year Old Scotch Whisky (not technically hardware)</td> </tr> <tr> <td>&lt;strong&gt;~$480&lt;/strong&gt;</td> <td></td> </tr> </tbody> </table> <p>Considering the total investment is still under $500 bucks, that's not too bad!</p> <h1>OS -- Platform</h1> <p>One of my favorite parts about developing the magic mirror was the fact that I could do so from the comfort of <strong>Visual Studio</strong> using the worlds most powerful programming language today, <strong>C#</strong>.</p> <p>&lt;a href="https://www.visualstudio.com/" target="_blank"&gt; &lt;img src="/img/2016/12/vs.png" alt="Visual Studio" style="max-width: 175px;" /&gt; &lt;/a&gt;     &lt;a href="https://msdn.microsoft.com/en-us/library/kx37x362.aspx" target="_blank"&gt; &lt;img src="/img/2016/12/csharp.png" alt="C#" style="max-width: 175px;" /&gt; &lt;/a&gt;</p> <table> <thead> <tr> <th>Type</th> <th>Name -- Link</th> </tr> </thead> <tbody> <tr> <td>&lt;strong&gt;OS&lt;/strong&gt;</td> <td><a href="https://developer.microsoft.com/en-us/windows/iot/explore/iotcore"><code>Windows 10 IoT Core, "Build 10.0.14393"</code></a></td> </tr> <tr> <td>&lt;strong&gt;Platform&lt;/strong&gt;</td> <td><a href="https://msdn.microsoft.com/en-us/windows/uwp/get-started/universal-application-platform-guide"><code>Universal Windows Platform (UWP), "UAP 1.0"</code></a></td> </tr> </tbody> </table> <h1>Software</h1> <p>When people throw around the acronym <strong>IoT</strong> it can mean a lot of different things. For this specific application, it is essential. Using the built-in <em>WIFI</em> on the <strong>Pi 3</strong> we can leverage the power of the internet to gather and share information.</p> <blockquote> <p>Software is simply the movement and manipulation of data. But without context, said data is meaningless.</p> </blockquote> <p>The magic mirror application strives to provide data that is all of the following:</p> <ul> <li>Relevant</li> <li>Practical</li> <li>Convenient</li> <li>Timely</li> </ul> <p>Below is the user-interface layout. Let's have a look at the various components that our application is built on.</p> <p><img src="./layout.png" alt="Layout" /></p> <h2>Components</h2> <p>From the layout above, it is clear to visualize the key components of the application. Most of these components implement the <code>IContextSynthesizer</code> interface. These implementations are indicators that each component is capable of handling voice commands, and providing a message that is consumable by the <strong>UWP</strong> speech-synthesizer. More on this later...</p> <h3>Weather -- Forecast</h3> <p>The current weather and forecast are retrieved every fifteen minutes from the free <a href="https://openweathermap.org/api">Open Weather API</a>. The developer API key and zip code are configurable. Additionally, the unit-of-measure is configurable, where you can specify <code>imperial</code> or <code>metric</code> as valid settings.</p> <h3>Clock</h3> <p>The clock is extremely simple. It is just the current date and time. It updates every second, formatting to the hour, minute, and second -- additionally displaying the name of the day followed by the month and day number.</p> <p><strong>Example:</strong></p> <p>:::note</p> <p>I opted to omit whether or not we're in ante or post meridiem as it is assumed the user would know if it's morning or in the afternoon.</p> <p>:::</p> <h3>Event Calendar / Schedule</h3> <p>My magic mirror is configured to display an aggregation of two calendars. This is entirely configurable, so if you were to <a href="https://github.com/IEvangelist/Mirror#fork-destination-box">fork my repo</a> -- you would need to setup your desired endpoints. The only requirement is that the URL is an accessible endpoint that returns valid <code>iCal</code> <a href="https://icalendar.org/">(*.ics) formatting</a>. The events are truncated to fit on the page and ordered by the date of the event.</p> <h3>Music Player</h3> <p>Imagine you have the magic mirror mounted in your bathroom, or bedroom...it would be nice to say, "play a song", or "play Deftones" for example and music starts playing. That is the idea behind the music player component. The current implementation of the music player is limited. I spent a few long nights trying to figure out how to use the built-in <strong>Bluetooth</strong> on the <strong>Pi 3</strong>, while <strong>UWP</strong> does have some support it is limited. I was able to pair but not stream songs from my <strong>iPod</strong> for example, which was really frustrating. For the meantime, I simply placed several songs in the <code>Assets</code> directory as content and I can play these resources. Ideally, I will use a web-based service like Spotify or Pandora.</p> <h3>Voice Commands</h3> <p>The <strong>Universal Windows Platform</strong> provides two very useful classes, the <a href="https://msdn.microsoft.com/en-us/library/windows/apps/windows.media.speechrecognition.speechrecognizer.aspx"><code>SpeechRecognizer</code></a> and the <a href="https://msdn.microsoft.com/en-us/library/windows/apps/windows.media.speechsynthesis.speechsynthesizer.aspx"><code>SpeechSynthesizer</code></a>. Leveraging this I have built out the ability to talk to my magic mirror, I can ask it things like "what's the weather", "what's my schedule for Wednesday", or even tell it commands like "turn this up".</p> <h2>Microsoft Cognitive Services</h2> <p>Formally known as "Project Oxford", Microsoft Cognitive Services offers a ton of programming power and you can start using it today for <strong>free</strong>. Simply <a href="https://www.microsoft.com/cognitive-services/en-us/subscriptions">subscribe</a> and use your subscription key as the argument to the client <code>.ctor</code>. The magic mirror makes use of the Emotion API, and corresponding <code>EmotionServiceClient</code> from the <a href="https://www.nuget.org/packages/Microsoft.ProjectOxford.Emotion/"><code>Microsoft.ProjectOxford.Emotion</code></a> package.</p> <p>Consider the following:</p> <pre><code>async Task&lt;IEnumerable&lt;RawEmotion&gt;&gt; CaptureEmotionAsync() { RawEmotion[] result; try { var photoFile = await _photoService.CreateAsync(); var imageProperties = ImageEncodingProperties.CreateBmp(); await _mediaManager.CapturePhotoToStorageFileAsync(imageProperties, photoFile); result = await _emotionClient.RecognizeAsync(await photoFile.OpenStreamForReadAsync()); } finally { await _photoService.CleanupAsync(); } return result.IsNullOrEmpty() ? await TaskCache&lt;IEnumerable&lt;RawEmotion&gt;&gt;.Value(() =&gt; Enumerable.Empty&lt;RawEmotion&gt;()) : result; } </code></pre> <ul> <li>First our <code>_photoService</code> asynchronously creates a photo file</li> <li>From the <code>ImageEncodingProperties</code> class, we create a bitmap encoding property set</li> <li>Our <code>_mediaManager</code> captures a photo, storing it in the photo file with the specified encoding</li> <li>Finally we invoke the <code>_emotionClient</code> passing it the stream from the persisted photo</li> </ul> <p>The <code>result</code> object contains a <code>Scores</code> class that looks like the following (from my mugshot):</p> <p><img src="./set.png" alt="Set" /></p> <p>The service returned a result. The result claims an <strong>81.5%</strong> level of confidence that I'm happy based on the given image, what do you think? What you don't see is that I'm taking a selfie with <a href="https://twitter.com/codefoster">Jeremy Foster</a> -- so, yeah -- I was pretty happy!</p> <h1>Construction</h1> <p>I would be lying if I tried taking credit for any of the construction. I'm slightly embarrassed to admit that I opted out of building it, as I lack the carpentry skills needed to do a decent job. Instead, I asked a friend who just so happens to be awesome at carpentry. After persuading him with a bottle of single malt scotch whiskey, I had my magic mirror constructed and the results were amazing! Needless to say, I was excited to try it out.</p> <hr /> <p>&lt;YouTube id="https://youtu.be/KOTYW8EioBk" /&gt;</p> <p>&lt;YouTube id="https://youtu.be/PWr1zRBO90o" /&gt;</p> David PineWhat happened to my Thread.CurrentPrincipalhttps://davidpine.dev/posts/principal-architecture-changeshttps://davidpine.dev/posts/principal-architecture-changesThis is not the IPrincipal you are looking for...Fri, 27 Jan 2017 00:00:00 GMT<h1>Overview</h1> <p>Like the title claims, if you're using <code>ASP.NET Core</code> and expecting the <code>Thread.CurrentPrincipal</code> or <code>ClaimsPrincipal.Current</code> to be populated you'd be wrong. This is not the <code>IPrincipal</code> you're looking for. In this post we'll discuss what happened and what you need to do now. Additionally we'll cover all the peripheral benefits as a result of this change.</p> <h2>History</h2> <p>If you have ever done any <strong>.NET Framework</strong> development, you've probably seen the <code>Thread</code> class. You're probably familiar with the <code>[Thread.CurrentPrincipal](https://msdn.microsoft.com/en-us/library/system.threading.thread.currentprincipal)</code> member. This member of the <code>Thread</code> class is defined as follows:</p> <pre><code>public static IPrincipal CurrentPrincipal { [System.Security.SecuritySafeCritical] // auto-generated get { lock (CurrentThread) { var principal = (IPrincipal)CallContext.Principal; if (principal == null) { principal = GetDomain().GetThreadPrincipal(); CallContext.Principal = principal; } return principal; } } [System.Security.SecuritySafeCritical] // auto-generated [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.ControlPrincipal)] set { CallContext.Principal = value; } } </code></pre> <blockquote> <p>Gets or sets the thread's current principal (for role-based security).</p> </blockquote> <p>Does anyone see the issue with this? Can you say, "publicly static mutable state, oh my"?! You should be alarmed. This property was never a good idea and today it simply doesn't belong. <strong>ASP.NET Core</strong> is not responsible for assigning this. You might not agree with that decision, but it is final.</p> <p>Before diving into this, pop over to <a href="https://github.com/aspnet/Security/issues/322">Microsoft.AspNetCore.Security -- Issue 332</a> for more of the back story.</p> <h2>Thread.CurrentPrincipal Today in ASP.NET Core</h2> <p>As part of the <code>ASP.NET Core</code> framework, the following middleware packages are provided:</p> <ul> <li><code>[Microsoft.AspNetCore.Identity](https://github.com/aspnet/Identity)</code></li> <li><code>[Microsoft.AspNetCore.Security](https://github.com/aspnet/Security)</code></li> </ul> <p>If you opt-in to using this middleware and you provide a login page (or expose an external provider) you'd end up creating an instance of a <code>ClaimsPrincipal</code> that represents an authenticated user. Subsequent requests to the web server would be handed the cookie that holds the user's claims. However the <code>Thread.CurrentPrincipal</code> would <strong>not</strong> actually reflect the <code>ClaimsPrincipal</code> object that was created as the result of the login. In fact, it would simply be an instance of the <code>GenericPrincipal</code> implementation. Likewise, walking up to the <code>ClaimsPrincipal.Current</code> property and asking it for the current claims principal in context wouldn't give you what you might expect either. Additionally, the <code>ClaimsPrincipal.Current</code> internally relies on the <code>Thread.CurrentPrincipal</code> for its value.</p> <pre><code>public static ClaimsPrincipal Current { get { return ClaimsPrincipal.s_principalSelector() ?? ClaimsPrincipal.SelectClaimsPrincipal(); } } private static ClaimsPrincipal SelectClaimsPrincipal() { return Thread.CurrentPrincipal as ClaimsPrincipal ?? new ClaimsPrincipal(Thread.CurrentPrincipal); } </code></pre> <p>You might be asking yourself, "how do I access this value then?". If you're in the context of a controller then you already have access to it via the <code>.User</code> property. Otherwise, the answer is "dependency injection". Wherever you're in need of the identity for accessing claims, use constructor (<code>.ctor</code>) injection and give yourself the <code>IPrincipal</code> you need. If you're in the context of an action you might be tempted to use the <code>[FromServices]</code> attribute to inject the <code>IPrincipal</code> instance, but remember that this actually comes from the current user - which is already accessible via the controller's <code>.User</code> property.</p> <h2>Dependency Injection</h2> <p><strong>ASP.NET Core</strong> made dependency injection (DI) a first class citizen, and it is so easy to use. The <code>Startup.cs</code> by convention has a <code>ConfigureServices</code> method where you'll register the dependencies. In the case of <code>ASP.NET Core</code> you'll need to do the following:</p> <pre><code>// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddSingleton&lt;IHttpContextAccessor, HttpContextAccessor&gt;(); services.AddTransient&lt;IPrincipal&gt;( provider =&gt; provider.GetService&lt;IHttpContextAccessor&gt;().HttpContext.User); // ... } </code></pre> <p>You might have noticed that we explicitly register the <code>IHttpContextAccessor</code> and corresponding implementation. This is necessary as that service is not registered for us. With that in place we can now specify that any class asking for an <code>IPrincipal</code> will be given the <code>IHttpContextAccessor.HttpContext.User</code> as the implementation. This is exactly what we needed. The exact same pattern is true for class libraries that want to leverage identity-based claim values from the <code>ClaimsPrincipal</code> implementation of the <code>IPrincipal</code>.</p> <h3>Example</h3> <pre><code>using System.Security.Claims; using System.Security.Principal; public class SampleService : ISampleService { private readonly ClaimsPrincipal _principal; public SampleService(IPrincipal principal) =&gt; _principal = principal as ClaimsPrincipal; public Task ConsumeAsync() { var dobClaim = _principal?.FindFirst(ClaimTypes.DateOfBirth); // Take some action ... } } </code></pre> <p>From the example above we can see how simple it is to use dependency injection. Simply define the <code>IPrincipal</code> as a <code>.ctor</code> parameter, store it in a field and consume it as needed.</p> <h3>But Why?</h3> <p>There are a lot of people who feel as though DI is overrated and that it is much easier to ask the <code>Thread.CurrentPrincipal</code> for its value. While that is easy, it is also risky...think about it. Since it is mutable anyone (even 3rd party libraries) can set it. Additionally, you avoid concerns about trying to synchronize static state between the <code>Thread</code> and <code>ClaimsPrincipal</code> classes. Scott Hanselman blogged about a <a href="https://www.hanselman.com/blog/SystemThreadingThreadCurrentPrincipalVsSystemWebHttpContextCurrentUserOrWhyFormsAuthenticationCanBeSubtle.aspx">similar issue</a> nearly thirteen years ago!</p> <p>One of the first benefits that comes to mind from this is the fact that with DI the code is unit-testable. It is extremely easy to mock out an <code>IPrincipal</code> to use for unit testing. Additionally, this alleviates all of the concerns about synchronization and reliability. The <code>IPrincipal</code> you're given is the principal you'd expect and it is fully populated accordingly.</p> David PineOverriding ASP.NET Core Framework-Provided Serviceshttps://davidpine.dev/posts/overriding-default-dihttps://davidpine.dev/posts/overriding-default-diYou take the red pill—you stay in Wonderland, and I show you how deep the rabbit hole goesTue, 14 Feb 2017 00:00:00 GMT<h1>Overview</h1> <p>In <strong>.NET</strong> it's really easy to create your own interfaces and implementations. Likewise, it's seemingly effortless to register them for dependency injection. But it is not always obvious how to override existing implementations. Let's discuss various aspects of "dependency injection" and how you can override the "framework-provided services".</p> <p>As an example, let's take a recent story on our product backlog for building a security audit of login attempts. The story involved the capture of attempted usernames along with their corresponding IP addresses. This would allow system administrators to monitor for potential attackers. This would require our <strong>ASP.NET Core</strong> application to have custom logging implemented.</p> <h2>Logging</h2> <p>Luckily <a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging"><code>ASP.NET Core Logging</code></a> is simple to use and is a first-class citizen within <code>ASP.NET Core</code>.</p> <p>In the <strong>Logging</strong> repository there is an extension method namely <a href="https://github.com/aspnet/Logging/blob/dev/src/Microsoft.Extensions.Logging/LoggingServiceCollectionExtensions.cs"><code>AddLogging</code></a>, here is what it looks like:</p> <pre><code>public static IServiceCollection AddLogging(this IServiceCollection services) { if (services == null) { throw new ArgumentNullException(nameof(services)); } services.TryAdd(ServiceDescriptor.Singleton&lt;ILoggerFactory, LoggerFactory&gt;()); services.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger&lt;&gt;), typeof(Logger&lt;&gt;))); return services; } </code></pre> <p>As you can see, it is rather simple. It adds two <code>ServiceDescriptor</code> instances to the <code>IServiceCollection</code>, effectively registering the given service type to the corresponding implementation type.</p> <h4>Following the rabbit down the hole</h4> <p>When you create a new <code>ASP.NET Core</code> project from <strong>Visual Studio</strong>, all the templates follow the same pattern. They have the <code>Program.cs</code> file with a <code>Main</code> method that looks very similar to this:</p> <pre><code>public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup&lt;Startup&gt;() .UseApplicationInsights() .Build(); host.Run(); } </code></pre> <h6>Templates <code>Program.cs</code></h6> <table> <thead> <tr> <th></th> <th></th> <th></th> </tr> </thead> <tbody> <tr> <td><a href="https://github.com/aspnet/Templates/blob/dev/src/BaseTemplates/EmptyWeb/Program.cs">Empty   </a></td> <td><a href="https://github.com/aspnet/Templates/blob/dev/src/BaseTemplates/StarterWeb/Program.cs">Starter Web   </a></td> <td><a href="https://github.com/aspnet/Templates/blob/dev/src/BaseTemplates/WebAPI/Program.cs">Web API   </a></td> </tr> </tbody> </table> <p>One thing that is concerning about a template like this is that the <code>IWebHost</code> is an <code>IDisposable</code>, so why then is this statement not wrapped in a <code>using</code> <a href="https://github.com/IEvangelist/Templates/commit/37e78bd0dc33069901cc51924fe8a2740d1e141c">you ask</a>? The answer is that the <code>Run</code> extension method internally wraps itself in a <code>using</code>. If you were wondering where the <code>AddLogging</code> occurs, it is a result of invoking the <code>Build</code> function.</p> <pre><code>[ Microsoft.AspNetCore.Hosting.WebHostBuilder ] public IWebHost Build() ... private IServiceCollection BuildCommonServices() ... creates services then invokes services.AddLogging() </code></pre> <h3>A few words on the Service Descriptor</h3> <p>The <code>ServiceDescriptor</code> class is an object that <em>describes</em> a <em>service</em>, and this is used by dependency injection. In other words, instances of the <code>ServiceDescriptor</code> are descriptions of services. The <code>ServiceDescriptor</code> class exposes several static methods that allow its instantiation.</p> <p>The <code>ILoggerFactory</code> interface is registered as a <a href="https://github.com/aspnet/DependencyInjection/blob/dev/src/Microsoft.Extensions.DependencyInjection.Abstractions/ServiceLifetime.cs#L14"><code>ServiceLifetime.Singleton</code></a> and its implementation is mapped to the <code>LoggerFactory</code>. Likewise, the generic type <code>typeof(ILogger&lt;&gt;)</code> is mapped to <code>typeof(Logger&lt;&gt;)</code>. This is just one of the several key "Framework-Provided Services" that are registered.</p> <h2>Putting it together</h2> <p>Now we know that the framework is providing all implementations of <code>ILogger&lt;T&gt;</code>, and resolving them as their <code>Logger&lt;T&gt;</code>. We also know that we could write our own implementation of the <code>ILogger&lt;T&gt;</code> interface. Being that this is open-source <a href="https://github.com/aspnet/Logging/blob/dev/src/Microsoft.Extensions.Logging.Abstractions/LoggerOfT.cs">we can look to their implementation</a> for inspiration.</p> <pre><code>public class RequestDetailLogger&lt;T&gt; : ILogger&lt;T&gt; { private readonly ILogger _logger; public RequestDetailLogger(ILoggerFactory factory, IRequestCategoryProvider requestCategoryProvider) { if (factory == null) { throw new ArgumentNullException(nameof(factory)); } if (requestCategoryProvider == null) { throw new ArgumentNullException(nameof(requestCategoryProvider)); } var category = requestDetailCategoryProvider.CreateCategory&lt;T&gt;(); _logger = factory.CreateLogger(category); } IDisposable ILogger.BeginScope&lt;TState&gt;(TState state) =&gt; _logger.BeginScope(state); bool ILogger.IsEnabled(LogLevel logLevel) =&gt; _logger.IsEnabled(logLevel); void ILogger.Log&lt;TState&gt;(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func&lt;TState, Exception, string&gt; formatter) =&gt; _logger.Log(logLevel, eventId, state, exception, formatter); } </code></pre> <p>The <code>IRequestCategoryProvider</code> is defined and implemented as follows:</p> <pre><code>using static Microsoft.Extensions.Logging.Abstractions.Internal.TypeNameHelper; public interface IRequestCategoryProvider { string CreateCategory&lt;T&gt;(); } public class RequestCategoryProvider : IRequestCategoryProvider { private readonly IPrincipal _principal; private readonly IPAddress _ipAddress; public RequestCategoryProvider(IPrincipal principal, IPAddress ipAddress) { _principal = principal; _ipAddress = ipAddress; } public string CreateCategory&lt;T&gt;() { var typeDisplayName = GetTypeDisplayName(typeof(T)); if (_principal == null || _ipAddress == null) { return typeDisplayName; } var username = _principal?.Identity?.Name; return $"User: {username}, IP: {_ipAddress} {typeDisplayName}"; } } </code></pre> <p>If you're curious how to get the <code>IPrincipal</code> and <code>IPAddress</code> into this implementation (with DI) - <a href="https://davidpine.net/blog/principal-architecture-changes/">I discussed it here</a> briefly. It is pretty straight-forward. In the <code>Startup.ConfigureServices</code> method do the following:</p> <pre><code>public void ConfigureServices(IServiceCollection services) { // ... omitted for brevity services.AddTransient&lt;IRequestCategoryProvider, RequestCategoryProvider&gt;(); services.AddTransient&lt;IHttpContextAccessor, HttpContextAccessor&gt;(); services.AddTransient&lt;IPrincipal&gt;( provider =&gt; provider.GetService&lt;IHttpContextAccessor&gt;() ?.HttpContext ?.User); services.AddTransient&lt;IPAddress&gt;( provider =&gt; provider.GetService&lt;IHttpContextAccessor&gt;() ?.HttpContext ?.Connection ?.RemoteIpAddress); } </code></pre> <p>Finally, we can &lt;a href="https://github.com/aspnet/DependencyInjection/blob/dev/src/Microsoft.Extensions.DependencyInjection.Abstractions/Extensions/ServiceCollectionDescriptorExtensions.cs" target="_blank"&gt;<code>Replace</code>&lt;/a&gt; the implementations for the <code>ILogger&lt;T&gt;</code> by using the following:</p> <pre><code>public void ConfigureServices(IServiceCollection services) { // ... omitted for brevity services.Replace(ServiceDescriptor.Transient(typeof(ILogger&lt;&gt;), typeof(RequestDetailLogger&lt;&gt;))); } </code></pre> <p>Notice that we replace the framework-provided service as a <code>ServiceLifetime.Transient</code>. Opposed to the default <code>ServiceLifetime.Singleton</code>. This is more or less an extra precaution. We know that with each request we get the <code>HttpContext</code> from the <code>IHttpContextAccessor</code>, and from this we have the <code>User</code>. This is what is passed to each <code>ILogger&lt;T&gt;</code>.</p> <h1>Conclusion</h1> <p>This approach is valid for overriding any of the various framework-provided service implementations. It is simply a matter of knowing the correct <code>ServiceLifetime</code> for your specific needs. Likewise, it is a good idea to leverage the open-source libraries of the framework for inspiration. With this you can take finite control of your web-stack.</p> <h1>Further Reading</h1> <ul> <li>&lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection" target="_blank"&gt; <code>ASP.NET Core - Dependency Injection</code> &lt;/a&gt;</li> </ul> David PineExploring C# 7https://davidpine.dev/posts/exploring-csharp-sevenhttps://davidpine.dev/posts/exploring-csharp-sevenExpressiveness Redefined & #notasuglySun, 26 Mar 2017 00:00:00 GMT<h1>Intro</h1> <p>Since we have all been actively celebrating the 20&lt;sup&gt;th&lt;/sup&gt; anniversary of <strong>Visual Studio</strong>, it felt appropriate to post about <strong>C# 7</strong>! In this post we will explore the features that make <strong>C# 7</strong> so promising. I've put together a demonstration <strong>C# 7</strong> project, that is available <a href="https://github.com/IEvangelist/IEvangelist.CSharp">here</a>.</p> <p>This post contains examples and details on five of the nine new <strong>C# 7</strong> features.</p> <ul> <li>Pattern matching</li> <li><code>out</code> variables</li> <li>Tuples</li> <li>Local functions</li> <li><code>throw</code> expressions</li> </ul> <p>These are the remaining features, that I <strong>do not</strong> cover in this post.</p> <ul> <li><code>ref</code> locals and returns</li> <li>More expression-bodied members</li> <li>Generalized <code>async</code> return types</li> <li>Numeric literal syntax improvements</li> </ul> <h2>Pattern Matching</h2> <p>With <strong>C# 7</strong> we welcomed the concept of "patterns". This concept allows for the extraction of information when a variable is tested for a certain "shape" and matches a specified pattern. We're able to leverage the "shape" from which we matched on as a declared variable in scope, consuming it as we deem necessary. This is referred to as "dynamic" (or "method") dispatch.</p> <blockquote> <p>&lt;p&gt; In computer science, dynamic dispatch is the process of selecting which implementation of a polymorphic operation (method or function) to call at run time. It is commonly employed in, and considered a prime characteristic of, object-oriented programming (OOP) languages and systems. &lt;cite&gt;<a href="https://en.wikipedia.org/wiki/Dynamic_dispatch">Wiki - Dynamic Dispatch</a>&lt;/cite&gt;</p> </blockquote> <p>Dynamic dispatch is nothing new to <strong>C#</strong>, and has been around forever. <strong>C# 7</strong> exposes this functionality via constant and type patterns.</p> <h3>Constant Patterns</h3> <p>Constant pattern <code>null</code>, similar to <code>(obj == null)</code>.</p> <blockquote> <p>&lt;p&gt; The "is expression" has been expanded</p> </blockquote> <pre><code>public void IsExpression(object obj) { if (obj is null) // Constant pattern "obj is null" { return; } } </code></pre> <h3>Type Patterns</h3> <p>Look closely at this syntax. This is where we start mixing metaphors. Prior to <strong>C# 7</strong> we could use the "is" expression to do simple type assertions <code>obj is [type]</code>. Additionally, we all know how to declare a variable <code>int i</code>. This new syntax merges these concepts together and is more compound and expressive.</p> <pre><code>public void IsExpression(object obj) { // (obj is int i) // "obj is int" // type assertion "typically evaluates type compatibility at run time" // "int i" // declaration if (obj is int i) // Type pattern "obj is int i" { // We can then use the "i" (integer) variable } // Note, the variable "i" is also available in this scope. // This is in fact by design, more on that out the "out variable" section } </code></pre> <blockquote> <p>&lt;p&gt; The "switch statement" has been generalized</p> </blockquote> <p>The <code>when</code> keyword has also been extended, now it not only applies to the <code>catch</code> statement but also the <code>case</code> labels within a <code>switch</code> statement. Consider the following classes:</p> <pre><code>class Shape { protected internal double Height { get; } protected internal double Length { get; } protected Shape(double height, double length) { Height = height; Length = length; } } class Circle : Shape { internal double Radius =&gt; Height / 2; internal double Diameter =&gt; Radius * 2; internal double Circumference =&gt; 2 * Math.PI * Radius; internal Circle(double height, double length) : base(height, length) { } } class Rectangle : Shape { internal bool IsSquare =&gt; Height == Length; internal Rectangle(double height, double length) : base(height, length) { } } </code></pre> <p>Now imagine that we have a collection of these <code>Shape</code> objects, and we want to print out their various details - we could use "pattern matching" as such:</p> <pre><code>static void OutputShapes(IEnumerable&lt;Shape&gt; shapes) { foreach (var shape in shapes) { // Previously, this was not permitted. Case labels had to be concrete // such as enums, numerics, bools, strings, etc. switch (shape) { case Circle c: WriteLine($"circle with circumference {c.Circumference}"); break; case Rectangle s when (s.IsSquare): WriteLine($"{s.Length} x {s.Height} square"); break; case Rectangle r: WriteLine($"{r.Length} x {r.Height} rectangle"); break; default: WriteLine("This is not a shape that we're familiar with..."); break; case null: throw new ArgumentNullException(nameof(shape)); } } } </code></pre> <p>As you can see, we are able to more easily reason about the specific <code>shape</code> in context. For example, with each iteration of our collection we <code>switch</code> on the <code>shape</code>. If the <code>shape</code> is an instance of the <code>Circle</code> subclass, we'll execute the <code>case</code> label "Circle" and we get the instance declared as its type in the variable <code>c</code>. Likewise, if the <code>shape</code> is a <code>Rectangle</code> and that rectangle <code>s</code> just so happens to also be a square <code>when (s.IsSquare)</code> evaluates to <code>true</code> - we will then execute the square <code>case</code> label. If the <code>shape</code> is an instance of a <code>Rectangle</code> but not a square, we execute the "Rectangle" <code>case</code> label. Notice we still have <code>default</code> fall-thru. Finally, we can also have a "null" <code>case</code> label.</p> <h2><code>out</code> variables</h2> <p><code>.NET</code> developers are more than familiar with the <code>Try*</code> pattern, but as a refresher this is what it looks like. Imagine we are trying to parse a <code>System.String</code> input value as a <code>System.Int32</code>. Imagine that the consumer doesn't really care if it is parsed, they're fine with a <code>default(int)</code> if it fails.</p> <pre><code>public int ToInt32(string input) { int result; if (int.TryParse(input, out result)) { return result; } return default(int); } </code></pre> <p>Let's quickly recap this. First, we declare a variable namely <code>result</code>. We then invoke the <code>int.TryParse</code> which returns a <code>bool</code> whether or not the parse was successful. If <code>true</code> then the declare <code>result</code> variable is not equal to the parsed <code>int</code> value. If the <code>input</code> was <code>"12"</code>, then <code>result</code> would be <code>12</code>. If the <code>input</code> was <code>"Pickles"</code>, then the return from the invocation to the <code>ToInt32</code> would be <code>0</code> as <code>int.TryParse</code> would return <code>false</code>.</p> <p>Now with <strong>C# 7</strong> we can declare our <code>out</code> variable inline as follows:</p> <pre><code>public int ToInt32(string input) { // Note: the declaration is inline with the out keyword if (int.TryParse(input, out int result)) { return result; } return default(int); } </code></pre> <p>The scope of the <code>result</code> variable is identical to the previous example, as it actually "leaks" out to the <code>if</code> statement. We can re-write this even more expressively:</p> <pre><code>public int ToInt32(string input) =&gt; int.TryParse(input, out var result) ? result : result; </code></pre> <p>A few things you might notice. First, this is now a single line as we can express this with the lambda operator. We leverage the ternary operator as well. Additionally, we can use the <code>var</code> keyword for our declaration. And since the <code>result</code> variable is in scope we can use it as both return cases. If unable to be parsed, it is in fact a <code>default(int)</code> anyways.</p> <h2>Tuples</h2> <p>Most developers are familiar with <code>System.Tuple&lt;T[,T1...]&gt;</code>. This <code>class</code> has served us well all the while it has been around. One of the advantages is that it exposes <code>readonly</code> fields - from the values that it is instantiated with. This was also great for equality comparisons and even using the tuple as a dictionary key.</p> <p>In <strong>C# 7</strong> we have a new syntax for expressing tuples. Enter the <code>ValueTuple</code>, and as the name implies - this is a <code>struct</code> instead of a <code>class</code>. There are obvious performance gains from using a light-weight value-type over the allocation of a <code>class</code>.</p> <pre><code>void LegacyTuple() { var letters = new Tuple&lt;char, char&gt;('a', 'b'); // Values were accessible via these Item* fields. var a = letters.Item1; var b = letters.Item2; } </code></pre> <p>This wasn't overly exciting from an API perspective, as the field names do not really imply anything about their intention.</p> <pre><code>void ValueTuple() { var letters = ('a', 'b'); var a = letters.Item1; var b = letters.Item2; // Note: ToTuple extension method var systemTuple = letters.ToTuple(); var c = systemTuple.Item1; var d = systemTuple.Item2; } </code></pre> <p>You might notice that the syntactic sugar is pouring over this new feature. This is referred to as a "tuple literal". We dropped the entire <code>new</code> keyword usage, as well as specifying the types. They are all inferred and in fact known, IntelliSense proves this immediately. But we still have the issue of these tuples not being very API friendly. Let's explore how we can give them custom names.</p> <pre><code>void MoreValueTuples() { var lessonDetails = (Subject: "C# Language Semantics", Category: Categories.Programming, Level: 300); // Note: IntelliSense now hides Item1, Item2 and Item3 // Instead we are provided with the following: var subject = lessonDetails.Subject; // string var category = lessonDetails.Category; // Categories [enum] var level = lessonDetails.Level; // int } </code></pre> <h3>Deconstruction</h3> <p>Now that we see how we can instantiate a <code>ValueTuple</code>, let's take a look at how we can declare one for usage.</p> <pre><code>void DeconstructionExamples() { var lessonDetails = (Subject: "C# Language Semantics", Category: Categories.Programming, Level: 300); // We can deconstruct in three various ways // First, the fully qualified type (string subject, Categories category, int level) = lessonDetails; // Next using the var keyword per named declaration (var subject, var category, var level) = lessonDetails; // Finally, omitting any type declaration and using var wholeheartedly var (subject, category, level) = lessonDetails; } </code></pre> <p>There are often questions about how deconstruction is implemented, and whether or not it is ordinal based. For the <code>ValueTuple</code> it is in fact ordinal based. However, note that deconstruction is not actually limited to tuples. With <strong>C# 7</strong> any <code>object</code> that defines a <code>public void Deconstruct</code> method can be deconstructed. Consider the following:</p> <pre><code>class Person { private readonly (string First, string Middle, string Last) _name; private readonly DateTime DateOfBirth _dateOfBirth; public Person((string f, string m, string l) name, DateTime dob) { _name = name; _dateOfBirth = dob; } public void Deconstruct(out double age, out string firstName, out string middleName, out string lastName) { age = (DateTime.Now - _dateOfBirth).TotalYears; firstName = _name.First; middleName = _name.Middle; lastName = _name.Last; } } </code></pre> <p>Now that the <code>Person</code> is defined with this <code>Deconstruct</code> method, we can deconstruct it following the same ordinal based semantics.</p> <pre><code>void DeconstructNonTuple() { var person = new Person(("David", "Michael", "Pine"), new DateTime(1984, 7, 7)); (int age, string first, string middle, string last) = person; // Note: to partially deconstruct you can ignore a specific ordinal by using the _ // This does not actually naming the ordinal variable, but truly ignoring it. var (_, _, _, _) = person; // Ignore all, not very useful var (_, firstName, _, _) = person; // Cherry-pick first name } </code></pre> <h4>Comparing Anonymous <code>object</code> vs. <code>ValueTuple</code></h4> <p>At first glance tuples look almost like anonymous objects. They are in fact very different. An anonymous object is actually a reference type whereas a <code>ValueTuple</code> is a <code>struct</code> - value type. Also, you can only return an anonymous object from a method as an <code>object</code> which isn't very API friendly. Within a fluent <code>LINQ</code> chained method anonymous objects are great and will still be normal for projection.</p> <h2>Local Functions</h2> <p>At first glance, local functions seem a bit odd. I've heard people say, "this method is starting to look like a class". At first, I was one of these people too. Once you get used to the idea and see the benefits it really does make sense. Here is a quick comparison of the two, note the benefits of local functions as they compare to lambdas.</p> <p>&lt;style&gt; .red { color: red; } .green { color: green; } &lt;/style&gt;</p> <table> <thead> <tr> <th></th> <th>Lambda(s)</th> <th>Local Function(s)</th> <th>Details</th> </tr> </thead> <tbody> <tr> <td>Generics</td> <td>❌</td> <td>✅</td> <td>Local functions allow for the use of generics</td> </tr> <tr> <td>Iterators</td> <td>❌</td> <td>✅</td> <td>The <code>yield</code> keyword is valid within local functions</td> </tr> <tr> <td>Recursion</td> <td>❌</td> <td>✅</td> <td>Local functions support recursion</td> </tr> <tr> <td>Allocatey</td> <td>✅</td> <td>❌</td> <td>Delegates require an <code>object</code> allocation</td> </tr> <tr> <td>Potential Variable Lifting</td> <td>✅</td> <td>❌</td> <td>Implicitly captured closure is non-existent</td> </tr> </tbody> </table> <p>It is vital to understand that local functions are &lt;strong&gt;not&lt;/strong&gt; a replacement for <code>Action&lt;T[,T1...]&gt;</code> or <code>Func&lt;T[,T1...]&gt;</code>. These delegate declarations are still needed as parameters to enable lambda expression arguments. If you see the <strong>#notasugly</strong> hashtag, this was coined by Mads Torgersen.</p> <h3>More efficient</h3> <p>When using local functions, there is no <code>object</code> created - unlike delegates that require an object for it to be used. Likewise, local functions help to alleviate another issue with lambdas in that they do not need to implicitly capture a variable longer than it is potentially needed. In <strong>C#</strong> lambdas capture values by reference, meaning that garbage collection may not be able to correctly clean up code that is "allocatey".</p> <h4>Declaration</h4> <p>With local functions, the declaration of the local function can actually occur after the <code>return</code> statement - as long as it is within the method body in which it is consumed. If you're familiar with some of the implementations of the <code>LINQ</code> extension methods on <code>IEnumerable&lt;T&gt;</code>, you would know a lot of the methods are defined with argument validation followed by the instantiation of "Iterator" classes, where these classes do the actual work.</p> <p>Because of deferred execution, iterators do not actually execute validation logic until they are iterated - for example invoking <code>.ToList()</code>, <code>.ToArray()</code>, or simply using them in a <code>foreach</code> statement. Ideally, we would like our iterators to "fail-fast" in the event of being given invalid arguments. Let's imagine that the <code>.Select</code> extension method was implemented as follows:</p> <pre><code>public static IEnumerable&lt;TResult&gt; Select&lt;T, TResult&gt;(this IEnumerable&lt;T&gt; source, Func&lt;T, TResult&gt; selector) { if (source == null) throw new ArgumentNullException(nameof(source)); if (selector == null) throw new ArgumentNullException(nameof(selector)); foreach (var item in source) { yield return selector(item); } } </code></pre> <p>Since this method is written as an iterator, the validation is skipped until it's iterated. With <strong>C# 7</strong> we can use local function to get both "fail-fast" validation and the iterator together.</p> <pre><code>public static IEnumerable&lt;TResult&gt; Select&lt;T, TResult&gt;(this IEnumerable&lt;T&gt; source, Func&lt;T, TResult&gt; selector) { if (source == null) throw new ArgumentNullException(nameof(source)); if (selector == null) throw new ArgumentNullException(nameof(selector)); return iterator(); IEnumerable&lt;TResult&gt; iterator() { foreach (var item in source) { yield return selector(item); } } } </code></pre> <h1><code>throw</code> expressions</h1> <p>Leveraging some pre-existing <strong>C#</strong> functionality - <code>null</code> coalescing, we can now <code>throw</code> when a value is evaluated as <code>null</code>. A common validation mechanism is to <code>throw</code> if an argument is <code>null</code>. Consider the following:</p> <pre><code>class LegacyService : IService { private readonly IContextProvider _provider; public LegacyService(IContextProvider provider) { if (provider == null) { throw new ArgumentNullException(nameof(provider)); } _provider = provider; } } </code></pre> <p>With <strong>C# 7</strong> we can simplify this with the <code>throw</code> expression.</p> <pre><code>class ModernService : IService { private readonly IContextProvider _provider; public ModernService(IContextProvider provider) { _provider = provider ?? throw new ArgumentNullException(nameof(provider)); } } </code></pre> <p>If the given <code>provider</code> argument is <code>null</code> we'll coalesce over to the <code>throw</code> expression.</p> <h1>From C# 6 to C# 7, then and now</h1> <p>I have a presentation that I have been fortunate enough to give at some regional conferences. One of these occasions was recorded, and I felt it made sense to share it here - Enjoy!!</p> <p>&lt;div class="iframe_container"&gt; &lt;iframe src="https://www.youtube.com/embed/kFpXRooGo0c" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt; &lt;/div&gt;</p> <p><strong>Further Reading</strong></p> <ul> <li><a href="https://docs.microsoft.com/en-us/dotnet/articles/csharp/csharp-7">What's new in C# 7</a></li> </ul> David PineTypeScript - JavaScript Reimaginedhttps://davidpine.dev/posts/typescript-javascript-reimaginedhttps://davidpine.dev/posts/typescript-javascript-reimagined"Making JavaScript tolerable"Tue, 02 May 2017 00:00:00 GMT<h1>First things First</h1> <p>JavaScript is the world's third most common programming language today. JavaScript was created by <a href="https://twitter.com/BrendanEich">Brendan Eich</a> in about ten days. It might seem odd that the previous two sentences not only exist, but go together...yet it is true! JavaScript runs seemingly everywhere and applications written in this language are only getting larger.</p> <blockquote> <p>JavaScript was perhaps intended for 100, maybe up to 1,000 lines of code and now with regularity people are building 100,000 line apps, if not 1,000,000 line apps. &lt;cite&gt;<a href="https://twitter.com/ahejlsberg">Anders Hejlsberg</a>&lt;/cite&gt;</p> </blockquote> <h2>"The Good Parts"</h2> <p><a href="https://www.crockford.com/">Douglas Crockford</a> authored a book "JavaScript: The Good Parts", sadly it was a rather short book. If you're a JavaScript developer, knowing "the good parts" is great - but knowing the limitations and shortcomings of a language is even more powerful. Sometimes it feels like JavaScript is a hammer and not every problem we're trying to solve is a nail. NodeJs is a great example of this in my opinion -- because I'm a <code>.NET</code> developer, but I digress... If you want a good laugh watch Gary Bernhardt's <a href="https://www.destroyallsoftware.com/talks/wat">WAT video</a>, go ahead -- I'll wait! In all seriousness, JavaScript is a great programming language for countless reasons, but it is easy to poke fun at.</p> <h3>Mental Model</h3> <p>I met a fellow technical public speaker who is a well respected JavaScript developer. He was arguing his hatred for TypeScript. There are several key takeaways from the debate that I think will stick with me for a while.</p> <h4>Learn it</h4> <blockquote> <p>You should learn the JavaScript programming language</p> </blockquote> <p>I already know JavaScript (but I really don't know JavaScript) -- I would never consider myself an expert or a guru! However, I write TypeScript nearly every single day that I develop. Some of the aforementioned names are among the very few who might be considered as such. I recall that when this part of the conversation was encountered, we came up with an analogy.</p> <blockquote> <p>Not all JavaScript developers are Tour de France champions (or contenders)</p> </blockquote> <p>This was the reoccurring analogy and I agreed with it wholeheartedly. That is why I think that TypeScript is like "training wheels" for JavaScript developers. That is not at all a bad thing! Just look at what you can do with "training wheels".</p> <p><img src="./training-wheels.gif" alt="With style" /></p> <p><img src="./training-wheels-2.gif" alt="Or Bust" /></p> <p>Seriously though, with that fact that <strong>we are not compilers</strong> and we cannot predict certain behaviors that occur in the "wild west" of JavaScript -- <strong>we should rely on tooling to help</strong>. It is simply a pragmatic approach to developing with a true sense of reliability and consistency. Training wheels provide you with the confidence and security. Once your TypeScript is compiled to JavaScript, the training wheels are removed and your code is ready to brave the new world!</p> <h1>TypeScript</h1> <p>I could never compete with the TypeScript site. They have a beautifully composed tutorial, handbook and rich set of documentation. If that is what you're looking for - by all means <a href="https://www.typescriptlang.org/docs/tutorial.html">go check it out</a>. If you'd like to hear more on my thoughts - keep reading.</p> <p>All valid JavaScript is also valid TypeScript, as TypeScript is a superset of JavaScript. TypeScript allows you as a developer to program in the latest version of the ECMAScript standard, while still being about to target all the way back to ES3. Upon compilation, your code is pure JavaScript and all the type notations are removed. The static type system is only applicable when working with TypeScript related files.</p> <h2>Extensions Explained</h2> <p>I initially had some confusion about the various file extensions. It was obvious what <code>.ts</code> and <code>.js</code> files were, but what were these <code>.map</code> and <code>.d.ts</code> files? Ironically, <code>.map</code> files have been around since CoffeeScript and are nothing new. They map JavaScript files to their corresponding abstractions, in the case of TypeScript - they map <code>.ts</code> to <code>.js</code> files. The <code>.d.ts</code> files are known as type definitions. They define the public surface area or API if you will, of a given library. More detail <a href="https://stackoverflow.com/a/37063570/2410379">here </a>.</p> <h2>Enough Sales Pitch - Show me some code</h2> <p>The <a href="https://www.typescriptlang.org/play/index.html">TypeScript playground</a> is an amazing place, since it has a side-by-side of the input TypeScript and the resulting JavaScript. This is an excellent place to learn how TypeScript works and what it is capable of. I always find it interesting seeing how certain things are implemented. Take for example an <code>enum</code>. In most languages it's easy to take this for granted, the same is true with TypeScript - but seeing how the resulting JavaScript is really something special.</p> <pre><code>export enum DayOfWeek { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday } </code></pre> <p>This seems like a very clean declaration of an <code>enum</code>, right?! We know that <code>DayOfWeek.Monday</code> would have a value of <code>1</code>, and <code>DayOfWeek.Friday</code> would then be <code>5</code> for example. But let's have a look at the JavaScript output.</p> <pre><code>var DayOfWeek; (function (DayOfWeek) { DayOfWeek[DayOfWeek["Sunday"] = 0] = "Sunday"; DayOfWeek[DayOfWeek["Monday"] = 1] = "Monday"; DayOfWeek[DayOfWeek["Tuesday"] = 2] = "Tuesday"; DayOfWeek[DayOfWeek["Wednesday"] = 3] = "Wednesday"; DayOfWeek[DayOfWeek["Thursday"] = 4] = "Thursday"; DayOfWeek[DayOfWeek["Friday"] = 5] = "Friday"; DayOfWeek[DayOfWeek["Saturday"] = 6] = "Saturday"; })(DayOfWeek || (DayOfWeek = {})); </code></pre> <p>If you look at this look enough, you'll see that it's a rather simple solution. It is essentially a reverse map, where properties are assigned to from their indexes. On the <code>DayOfWeek</code> instance, we declare and assign property <code>Sunday</code> equal to <code>0</code>. Likewise, we declare and assign property <code>0</code> equal to the value <code>Sunday</code>, etc...</p> <h3>Type Aliases, Generics and Mapped Types</h3> <p>I recently learned about the <code>keyof</code> operator in TypeScript, and wow is it powerful! Let's consider the following mutable object.</p> <pre><code>export class MutableDataBag&lt;T&gt; { public data: T; public name: string; constructor(private id: number) { } } </code></pre> <p>This is a rather simple object. It defines several members that are <code>public</code> and assignable, namely the <code>data</code> and <code>name</code> members. Imagine that we decide that we actually expose this structure from an API - but we want it to be immutable, i.e.; <code>readonly</code>. Enter "mapped types".</p> <pre><code>export type Readonly&lt;T&gt; = { readonly [P in keyof T]: T[P]; } </code></pre> <p>The type alias of <code>Readonly</code> takes on an generic of type <code>T</code>, furthermore it defines that all the members of type <code>T</code> are accessible as <code>readonly</code> members. This leverages the <code>keyof</code> operator in which <code>P</code> is accessible so long as it is a member of <code>T</code> (key of <code>T</code>). So, now we can return a <code>Readonly&lt;MutableDataBag&lt;T&gt;&gt;</code> from our API and rest assured knowing that TypeScript prevents it's members from being written to.</p> <h1>Conclusion</h1> <p>This is literally just the tip of the iceberg (or even less). TypeScript has truly made JavaScript tolerable. TypeScript answers so many questions and addresses so many concerns that it's hard to justify not using...if you're not using TypeScript, there are other options out there. Pick your poison!!</p> <h3>Resources</h3> <ul> <li><a href="https://github.com/IEvangelist/IEvangelist.TypeScript">IEvangelist - TypeScript</a></li> <li><a href="https://www.typescriptlang.org/docs/tutorial.html">TypeScript Documentation</a></li> </ul> David PineHow to organize an amazing technical conferencehttps://davidpine.dev/posts/how-to-organize-an-amazing-tech-confhttps://davidpine.dev/posts/how-to-organize-an-amazing-tech-confBehind the scenes of MKE DOT NETSat, 24 Jun 2017 00:00:00 GMT<h1>Story Time</h1> <p>This is my third year organizing <a href="https://www.mkedotnet.com/">MKE DOT NET</a>, so I've been accumulating plenty of interesting stories. Last year I was fortunate enough to have convinced Scott Hanselman to be our keynote speaker. Yes, "the <a href="https://twitter.com/shanselman">Scott Hanselman</a>" - arguably the worlds most influential person in the tech industry. I was his ride to and from the airport and the conversations we had actually led to several key changes. As a result <a href="https://www.mkedotnet.com/">MKE DOT NET</a> will be even better than ever before!</p> <p>Scott said:</p> <blockquote> <p>Had I looked at the lineup of speakers prior to agreeing, I would have declined</p> </blockquote> <p>My mind immediately started racing with thoughts like "does he only want to speak with well-known speakers" or "is this too small of a conference for such a celebrity?". None of these thoughts were the least bit accurate. In fact, the reason was simple "we were not inclusive enough". Honestly this was something that I didn't even think about. I was driving Scott to the hotel where he was going to stay. The hotel was also the place where the conference was to be held.</p> <p><img src="./hanselman.jpg" alt="One does not simply drop &quot;Hanselman&quot; off at the hotel" /></p> <p>No...he requested that I give him a guided tour. He wanted to see the room layouts, he wanted to test equipment, ensure lighting, sound, connectivity, etc. We actually ended up reorganizing the entire layout of the keynote room as well as changing the number of projectors. His "perfectionist" side was shining through and it was really resonating with the team. Clearly he has done this a few more times than we have!</p> <p>One of his initial comments was hysterical:</p> <blockquote> <p>This kind of looks and smells like grandmas house</p> </blockquote> <p>He was referring to the decor of the hotel (very 60's) and that the halls had a stale scent of mildew (the hotel has a water-park in it). Having just taken a sip of water, I nearly spit it out of my mouth in laughter - finally someone said what we had all been thinking! This resulted in a change of venue for 2017 and years to come. We'll be at <a href="https://www.paysbig.com/">Potawatomi Hotel &amp; Casino</a> this year. The new joke is obviously that it will now smell like cigarette smoke instead.</p> <h2>Inclusion</h2> <p>The truth was simple. <a href="https://www.mkedotnet.com/2016/speakers/">MKE DOT NET 2016</a> only had one woman speaker and ironically she was speaking about Diversity. We were ready to lick our wounds and rise above it. I'm so proud to be part of this change and happy to communicate that <a href="https://www.mkedotnet.com/">MKE DOT NET</a> now takes an active approach to ensure a more diverse speaker lineup.</p> <p>This year, we started leveraging <a href="https://www.papercall.io">PaperCall.io</a> as our event management system. It allowed us to make the speaker voting anonymized - but you're probably wondering how that helps with diversity. It actually doesn't and that would be naive to assume so. What we did was invite <a href="https://twitter.com/AshleyPQPQP">Ashley Powell</a> and <a href="https://www.linkedin.com/in/samsaha1/">Samrat Saha</a> (both MKE DOT NET speaker alumni) to join our technical committee. We loved that they were both eager to help and happily joined the team. They both have been a major part of the entire process and helped with the CFP outreach efforts. We believe this led to more submissions from their networks, ultimately leading to a more inclusive speaker lineup.</p> <p>Of the 24 sessions and including the keynote, we are proud to have eight women speakers this year! If you're a numbers person - that is an &lt;strong&gt;800% increase&lt;/strong&gt; year-over-year and we plan on doing even better as we evolve. We are actively making our conference as inclusive as possible - and we celebrate the communities participation.</p> <h2>Evolution</h2> <p>We are only going to grow and learn and become a better more cohesive unit as time marches forward. We are evolving! The people who make up the team are so passionate about bringing forth their visions for a better Midwest technical community. But that isn't without conflict or disagreement. In fact our team is great at hashing things out. Not every idea is the best! Knowing how to work together and encouraging ideas is key. We foster an environment which breeds creativity but also bases our direction on results. The team shares a vision and we strive to make that vision obvious.</p> <h3>Community</h3> <p>Of course none of this would be possible without our strong community. I'm proud to play my part and contribute what I can, and I'm humbled to be immersed in such a rich pool of technical knowledge and passion. It is inspiring to see the things we're doing and even more so to be involved! Often the hallways of a technical conference are the stomping grounds for some of the best bits of conversation. In my regional speaking engagements this year I'm hearing just how amazing we are in the Midwest time and time again. I'm grateful for all of these awesome success stories.</p> <h3>Tools</h3> <p>Being that the entire team cannot always be in the same place at the same time - it is vital to leverage tools that allow everyone on the team to work together. This will serve as a simple listing of some of things that we have used along the way.</p> <ul> <li><a href="https://www.papercall.io/mkedotnet2017">PaperCall.io - </a> for CFP and speaker selection process</li> <li><a href="https://www.eventbrite.com/e/mke-dot-net-2017-tickets-32702277392">Eventbrite - </a> for event ticketing/sales</li> <li><a href="https://google.com">Google Docs/Sheets/Forms - </a> for internal documentation</li> <li><a href="https://stickies.io/">Stickies.io - </a> for organizing schedule</li> <li><a href="https://trello.com">Trello Boards - </a> for organizing ideas</li> <li><a href="https://zoom.us/">Zoom.us - </a> for video conferencing</li> <li><a href="https://www.adobe.com/products/illustrator.html">Adobe Illustrator - </a> designing logos, creating illustrations and icons</li> <li><a href="https://www.adobe.com/products/photoshop.html">Adobe Photoshop - </a> art directing photos, creating images, correcting color</li> <li><a href="https://twitter.com/mkedotnet">Twitter - </a> for social networking</li> </ul> <p>A word on the website, we use for source control <a href="https://github.com/">GitHub - </a> and it is actually a static website. It uses <a href="https://harpjs.com/"><code>HarpJs</code></a> which sits atop <a href="https://nodejs.org/en/"><code>NodeJs</code></a>. Updates are seamless and fast with <a href="https://code.visualstudio.com/">Visual Studio Code - </a>.</p> <h2>Closing Story</h2> <p>I spoke at <a href="https://indycode.amegala.com/">Indy.Code()</a> in Indianapolis back in March, 2017. It was a great experience and the folks over at <a href="https://www.amegala.com/">Amegala</a> really know how to organize a technical conference! Anyways, prior to one of my talks I was asking the crowd a little about technical conferences in Indianapolis. I was told that <a href="https://indycode.amegala.com/">Indy.Code()</a> was the first major conference in seven years. When I asked them what they do to fill that void, three of them immediately shouted out.</p> <blockquote> <p>We travel to Milwaukee, WI in the fall for <a href="https://www.mkedotnet.com/">MKE DOT NET</a> - it's a technical conference!</p> </blockquote> <p>What a small world, hey! They were extremely excited to learn my role...and I too shared in their excitement. For in that moment I knew that all the hard work was paying off.</p> <h2>Thank you</h2> <p>A sincere thank you is in order for everyone who makes this possible. Thank you to <a href="https://twitter.com/LadyNaggaga">Maria Naggaga</a> for not only being willing to speak at <a href="https://www.mkedotnet.com/">MKE DOT NET</a>, but responding to my ask within minutes - stating that she would be honored! Thank you to the community, the speakers, those who submit who don't get to speak, those of you who attend. Thank you to <a href="https://www.centare.com/">Centare</a> for supporting all these efforts year-after-year and the investment back into the community. Thank you to all the sub-committees of <a href="https://www.mkedotnet.com/">MKE DOT NET</a> that give their brilliance and support taking on tasks like, marketing, driving attendance, seeking sponsorship, creating and maintaining the website, deciding on scheduling, venue, meals, logistics, all the countless planning efforts - thank you. Anyone else that I couldn't think of, thank you!</p> David PinePerusing C# 7.1https://davidpine.dev/posts/csharp-seven-dot-onehttps://davidpine.dev/posts/csharp-seven-dot-oneThe language features you've been waiting forSat, 29 Jul 2017 00:00:00 GMT<p>Earlier this year in March -- Visual Studio 2017 was released. With this the world was given C# 7, checkout my post on <a>Exploring C# 7</a>. In this post we will peruse C# 7.1, the first incremental release while C# 8 is being fleshed out.</p> <h1>Async Main &lt;a target="_blank" href="https://github.com/dotnet/csharplang/issues/97"&gt;&lt;/a&gt;</h1> <p>Since the release of C# 5, developers have either embraced the <code>async</code> and <code>await</code> keywords or fumbled along the way feebly attempting to comprehend them. Fear not, you're not alone. Many others have been just as confused, but that's not to say that the language didn't make major strides towards simplifying your development efforts. In fact, I love asynchronous programming and C# makes me very happy with its implementation!</p> <p>Many developer advocates set forth on a journey of biblical proportions -- progressing through mountains of nay-sayers, focusing on craftsmanship and technical evangelism. Among these advocates <strong>Stephen Toub</strong> and <strong>Stephen Cleary</strong> rose toward the top. Blogging and helping us along the way. With their help emerged rules and guidelines, a comprehensive set of best practices for using these language features. One <a href="https://msdn.microsoft.com/en-us/magazine/jj991977.aspx">best practice</a> is as follows:</p> <blockquote> <p>Instead of <code>Task.Result</code> or <code>.Task.Wait()</code>, use <code>await</code></p> </blockquote> <p>Prior to C# 7.1 developers who wrote console applications were forced to deviate from this guideline. We were forced to use <code>.Result</code> or <code>.Wait()</code>. Consider the following:</p> <pre><code>public static void Main() { var httpClient = new HttpClient(); var result = httpClient.GetAsync("some/url").Result; // Another common alternative was using the get awaiter / result. result = httpClient.GetAsync("some/url").GetWaiter().GetResult(); // Consume the result... } </code></pre> <p>With C# 7.1, we can leverage <a href="https://github.com/dotnet/csharplang/blob/master/proposals/async-main.md">Async Main</a>.</p> <pre><code>public static async Task Main() { var httpClient = new HttpClient(); var result = await httpClient.GetAsync("some/url"); // Consume the result... } </code></pre> <p>This feature extends the previous four possible entry points to their <code>Task</code> and <code>Task&lt;int&gt;</code> based equivalents. Now the following entry points are valid:</p> <pre><code>static Task Main() { } // static void Main() { } static Task&lt;int&gt; Main() { } // static int Main() { } static Task Main(string[] args) { } // static void Main(string[] args) { } static Task&lt;int&gt; Main(string[] args) { } // static int Main(string[] args) { } </code></pre> <p>With this task-based approach the new entry points simply provide that "feels good" approach to development.</p> <h1><code>default</code> Expression &lt;a target="_blank" href="https://github.com/dotnet/csharplang/issues/102"&gt;&lt;/a&gt;</h1> <p>The <code>default</code> keyword has been around forever. It was used to tell the compiler give me the "default" value for the given type.</p> <pre><code>var number = default(int); // 0 var date = default(DateTime); // DateTime.MinValue var obj = default(object); // null </code></pre> <p>This was also used for generics, for example when the type of <code>T</code> is used we could return <code>default(T)</code>.</p> <pre><code>public static T TryPeekOrDefault&lt;T&gt;(this ConcurrentQueue&lt;T&gt; queue) { if (queue?.TryPeek(out var result) ?? false) { return result; } return default(T); } </code></pre> <p>Likewise, the <code>default</code> keyword was used in the <code>switch</code> statement as well -- and signified the <code>default</code> case label. If all other cases were not executed, the <code>default</code> case would be.</p> <pre><code>switch (dayOfWeek) { case DayOfWeek.Monday: return "Ugh... off to work again"; case DayOfWeek.Friday: return "Excitement sets in..."; default: return string.Empty; } </code></pre> <p>Now, with C# 7.1 the compiler will use type inference to simplify the <code>default</code> expression (sometimes referred to as <code>default</code> literals). So we could declare an <code>int</code> and assign it to <code>default</code> without passing the type <code>int</code>:</p> <pre><code>int number = default; // 0 Guid guid = default; // 00000000-0000-0000-0000-000000000000 </code></pre> <p>Not only can the type be inferred from the declaration but also the literal expression:</p> <pre><code>var number = isLucky ? 7 : default; // when 'isLucky' is false number is 0 </code></pre> <p>The great news is that this feature is for the entire type-system, not just <code>int's</code> . You can now use the <code>default</code> keyword in all sorts of places. You can use it to check against a value, or to pass it as an argument.</p> <pre><code>public int Sum(params int[] numbers) { if (numbers is default || numbers.Length == default) { return default; // Returns 0 } } // Pass in default. This is null for int[]. var sum = Sum(default); if (sum == default) { // Take an action when sum is 0. } </code></pre> <h1>Tuple Projection Initializers &lt;a target="_blank" href="https://github.com/dotnet/csharplang/issues/415"&gt;&lt;/a&gt;</h1> <p>I detailed the introduction of <code>tuples</code> in my <a>Exploring C# 7</a> post. Tuples are an amazing addition to the C# language and are extremely powerful. With its initial implementation however, there was something that was lacking. Tuple literals lacked the ability to infer names, now with C# 7.1 -- this is no longer a limitation.</p> <p>With <code>tuple</code> projection initializers our C# <code>tuple</code> literals are simplified and effectively DRY, as the redundancies of duplicating the field names are inferred.</p> <pre><code>var firstName = "David"; var lastName = "Pine"; var dateOfBirth = new DateTime(1984, 7, 7); // C# 7.0, required "explicit names" var person = (firstName: firstName, lastName: lastName, dateOfBirth: dateOfBirth); var fn = person.firstName; // "David" var ln = person.lastName; // "Pine" var dob = person.dateOfBirth; // 7/7/1984 // C# 7.1, allows "inferred names" person = (firstName, lastName, dateOfBirth); fn = person.firstName; // "David" ln = person.lastName; // "Pine" dob = person.dateOfBirth; // 7/7/1984 </code></pre> <h1>Pattern-matching with Generics &lt;a target="_blank" href="https://github.com/dotnet/csharplang/issues/154"&gt; &lt;/a&gt;</h1> <p>With C# 7.0, pattern-matching was introduced -- I love talking about it and showing off what it can do! In my opinion it solves a lot of issues with type assertions and makes complex decision trees more legible and comprehensive. However, when it was introduced it didn't correctly support generics. With C# 7.1, that has changed.</p> <p>Consider the following:</p> <pre><code>public class Animal { } public class Dog : Animal { public Breed Breed { get; } } public void Interact&lt;TAnimal&gt;(TAnimal animal) where TAnimal : Animal { if (animal is Dog dog) { // Play fetch with dog } switch (animal) { case Dog d when (d.Breed == Breed.LabradorRetriever): // Feed dog, the let 'em outside break; } } </code></pre> <p>This was not actually possible, in fact it wouldn't even compile. Ignoring the scoping issue, as the <code>dog</code> variable is declared twice within the same scope -- we'd end up with the following compilation error:</p> <blockquote> <p>An expression of type TAnimal cannot be handled by a pattern of Dog.</p> </blockquote> <p>You could use the <code>as</code> operator:</p> <pre><code>var dog = animal as Dog; if (dog != null) { // Play with man's best friend... } </code></pre> <p>But that isn't really pattern-matching. The limitation that exists is due to explicit type conversions and open types; however, the design specification is changing as follows in the bold area:</p> <blockquote> <p>Certain combinations of static type of the left-hand-side and the given type are considered incompatible and result in compile-time error. A value of static type <code>E</code> is said to be pattern compatible with the type <code>T</code> if there exists an identity conversion, an implicit reference conversion, a boxing conversion, an explicit reference conversion, or an unboxing conversion from <code>E</code> to <code>T</code>, &lt;strong&gt;or if either <code>E</code> or <code>T</code> is an open type.&lt;/strong&gt; It is a compile-time error if an expression of type <code>E</code> is not pattern compatible with the type in a type pattern that it is matched with.</p> </blockquote> <h2>References</h2> <ul> <li>&lt;a target="_blank" href="https://github.com/dotnet/roslyn"&gt;Roslyn, The .NET Compiler Platform&lt;/a&gt;</li> <li>&lt;a target="_blank" href="https://github.com/dotnet/csharplang"&gt;C# Language Design&lt;/a&gt;</li> </ul> David PineDemystifying .NET Standardhttps://davidpine.dev/posts/dot-net-standard-demystifiedhttps://davidpine.dev/posts/dot-net-standard-demystifiedClearing up the confusionWed, 16 Aug 2017 00:00:00 GMT<h2>Intro</h2> <p>Monday, August 14&lt;sup&gt;th&lt;/sup&gt; was an amazing day to be a <code>.NET</code> developer! As the world celebrates another triumphant series of announcements from Microsoft, there is still confusion amongst the masses.</p> <blockquote> <p>What is this <code>.NET Standard</code> thing? What does it mean to target <code>.NET Standard</code> versus <code>.NET Core</code>, or even the <code>.NET Framework</code>? As a developer, what <code>.NET</code> should I target and why? Are Portable Class Libraries (PCLs) dead? &lt;cite&gt;Developer Community&lt;/cite&gt;</p> </blockquote> <p>All of these questions have already been answered, you just have to know where to look. In this post I will provide some invaluable resources and educate those of you who are still confused.</p> <h2>Announcements</h2> <p>Here are all the recent announcements, feel free to spend some time checking them out -- be sure to come back.</p> <ul> <li><a href="https://blogs.msdn.microsoft.com/webdev/2017/08/14/announcing-asp-net-core-2-0/">The ASP.NET team is proud to announce general availability of ASP.NET Core 2.0.</a></li> <li><a href="https://blogs.msdn.microsoft.com/dotnet/2017/08/14/announcing-net-core-2-0/">.NET Core 2.0 is available today August 14, as a final release</a></li> <li><a href="https://blogs.msdn.microsoft.com/dotnet/2017/08/14/announcing-net-standard-2-0/">The .NET Standard 2.0 specification is now complete</a></li> <li><a href="https://blogs.msdn.microsoft.com/visualstudio/2017/08/14/visual-studio-2017-version-15-3-released/">Visual Studio 17 version 15.3</a></li> </ul> <p>Let's start by answering all these common questions.</p> <h2><code>[.NET Standard](https://docs.microsoft.com/en-us/dotnet/standard/net-standard/) </code></h2> <p>If the name <a href="https://github.com/terrajobst"><em>Immo Landwerth</em></a> doesn't ring a bell, perhaps <a href="https://twitter.com/terrajobst">@terrajobst</a> will? Regardless of whether you've heard of him before -- you'd be wise to remember him now. He is a program manager for Microsoft and has a huge part in the <code>.NET</code> ecosystem as a whole. Immo has a <code>.NET Standard</code> video series on YouTube and if you do not have time to read this post, by all means <a href="https://www.youtube.com/playlist?list=PLRAdsfhKI4OWx321A_pr-7HhRNk7wOLLY">watch his videos</a>.</p> <blockquote> <p><code>.NET Standard</code> is a specification. It represents a set of APIs that all <code>.NET</code> platforms have to implement. &lt;cite&gt;<a href="https://youtu.be/YI4MurjfMn8?list=PLRAdsfhKI4OWx321A_pr-7HhRNk7wOLLY&amp;t=191">Immo Landwerth</a>&lt;/cite&gt;</p> </blockquote> <p>Often, the <code>.NET Standard</code> is mistaken for a runtime. Consider the name <code>.NET Standard</code> at face value, it has the word "standard" in it...because it is not a framework or a core runtime.</p> <blockquote> <p><code>.NET Standard</code> isn't a runtime, it's a version interface - a list of methods you can use under many different "<code>.NETs</code>". &lt;cite&gt;<a href="https://www.hanselman.com/blog/ExploringRefitAnAutomaticTypesafeRESTLibraryForNETStandard.aspx">Scott Hanselman</a>&lt;/cite&gt;</p> </blockquote> <p>As true with all standards (or specifications), implementations can either be compliant or non-compliant. At this point in time there are four <code>.NET</code> implementations that are <code>.NET Standard 2</code> compliant. They are as follows, <code>.NET Core</code>, <code>.NET Framework</code>, <code>Mono</code> and <code>Xamarin</code>. Below is a more comprehensive listing of all the <code>.NET</code> platforms and their corresponding standard that they implement.</p> <h3>The <a href="https://dotnet.microsoft.com/platform/dotnet-standard#versions">.NET Standard Version Table </a></h3> <table> <thead> <tr> <th>.NET Standard</th> <th><a href="https://github.com/dotnet/standard/blob/master/docs/versions/netstandard1.0.md">1.0</a></th> <th><a href="https://github.com/dotnet/standard/blob/master/docs/versions/netstandard1.1.md">1.1</a></th> <th><a href="https://github.com/dotnet/standard/blob/master/docs/versions/netstandard1.2.md">1.2</a></th> <th><a href="https://github.com/dotnet/standard/blob/master/docs/versions/netstandard1.3.md">1.3</a></th> <th><a href="https://github.com/dotnet/standard/blob/master/docs/versions/netstandard1.4.md">1.4</a></th> <th><a href="https://github.com/dotnet/standard/blob/master/docs/versions/netstandard1.5.md">1.5</a></th> <th><a href="https://github.com/dotnet/standard/blob/master/docs/versions/netstandard1.6.md">1.6</a></th> <th><a href="https://github.com/dotnet/standard/blob/master/docs/versions/netstandard2.0.md">2.0</a></th> </tr> </thead> <tbody> <tr> <td><code>.NET Core</code></td> <td>1.0</td> <td>1.0</td> <td>1.0</td> <td>1.0</td> <td>1.0</td> <td>1.0</td> <td>1.0</td> <td>2.0</td> </tr> <tr> <td><code>.NET Framework</code></td> <td>4.5</td> <td>4.5</td> <td>4.5.1</td> <td>4.6</td> <td>4.6.1</td> <td>4.6.1</td> <td>4.6.1</td> <td>4.6.1</td> </tr> <tr> <td><code>Mono</code></td> <td>4.6</td> <td>4.6</td> <td>4.6</td> <td>4.6</td> <td>4.6</td> <td>4.6</td> <td>4.6</td> <td>5.4</td> </tr> <tr> <td><code>Xamarin.iOS</code></td> <td>10.0</td> <td>10.0</td> <td>10.0</td> <td>10.0</td> <td>10.0</td> <td>10.0</td> <td>10.0</td> <td>10.14</td> </tr> <tr> <td><code>Xamarin.Mac</code></td> <td>3.0</td> <td>3.0</td> <td>3.0</td> <td>3.0</td> <td>3.0</td> <td>3.0</td> <td>3.0</td> <td>3.8</td> </tr> <tr> <td><code>Xamarin.Android</code></td> <td>7.0</td> <td>7.0</td> <td>7.0</td> <td>7.0</td> <td>7.0</td> <td>7.0</td> <td>7.0</td> <td>7.5</td> </tr> <tr> <td><code>Universal Windows Platform</code></td> <td>10.0</td> <td>10.0</td> <td>10.0</td> <td>10.0</td> <td>10.0</td> <td>vNext</td> <td>vNext</td> <td>vNext</td> </tr> <tr> <td><code>Windows</code></td> <td>8.0</td> <td>8.0</td> <td>8.1</td> <td></td> <td></td> <td></td> <td></td> <td></td> </tr> <tr> <td><code>Windows Phone</code></td> <td>8.1</td> <td>8.1</td> <td>8.1</td> <td></td> <td></td> <td></td> <td></td> <td></td> </tr> <tr> <td><code>Windows Phone Silverlight</code></td> <td>8.0</td> <td></td> <td></td> <td></td> <td></td> <td></td> <td></td> <td></td> </tr> </tbody> </table> <p>For example, we can see the <code>.NET Framework 4.5</code> is an implementation of <code>.NET Standard 1.1</code>. This means that if you're to author a <code>.NET Standard</code> class library and you target <code>1.1</code> of the standard, it can run on <code>.NET Core 1.0</code>, <code>.NET Framework 4.5</code>, <code>Mono 4.6</code>, etc... you get the point now I'm sure.</p> <h4>The Trade-off</h4> <p>There is a bit of a trade-off though that you should consider. With every version of the <code>.NET Standard</code> there is an inverse relationship to the number of implementations. The higher the version of the <code>.NET Standard</code>, the more APIs you have access to. However, there are fewer implementations that exist for higher versions of the <code>.NET Standard</code>.</p> <p>It is typically advised that you target the lowest version of the standard that you can get away with. This allows your code to run in more places, as there are more implementations of lower versioned standards. Portable Class Libraries (or PCLs) have been replaced by the <code>.NET Standard</code>. If you're like me, you probably want the latest and greatest -- the fullest set of APIs...and that is ok. As long as you control the environment in which it's consumed, great!</p> <h3><code>.NET</code> API Explorer</h3> <p>Microsoft has been hard at work, and their documentation teams have been putting together some incredibly useful ways to consume documentation. As a developer, I typically rely on <strong>StackOverflow</strong> as a source of truth -- however, I have been finding myself spending more time on the official documentation sites. A big 👍 to the team for making the <code>.NET</code> docs so much better than before. They even have a <code>.NET</code> API Explorer. This is immensely powerful -- in a matter of seconds I can quickly explore APIs and determine if something is lacking.</p> <p>&lt;a href="https://docs.microsoft.com/en-us/dotnet/api/?view=netstandard-2.0" target="_blank"&gt; <img src="./dot-net-standard-api-explorer.png" alt=".NET Standard API Explorer" /> &lt;/a&gt;</p> <h2><code>[.NET Core](https://docs.microsoft.com/en-us/dotnet/core/) </code></h2> <blockquote> <p><code>.NET Core</code> is a general purpose development platform maintained by Microsoft and the <code>.NET</code> community on GitHub. It is cross-platform, supporting Windows, macOS and Linux, and can be used in device, cloud, and embedded/IoT scenarios.</p> </blockquote> <p>The obvious advantages are that <code>.NET Core</code> is cross-platform and open source. Unlike <code>.NET Framework</code> that is neither of those two things.</p> <h2><code>[.NET Framework](https://docs.microsoft.com/en-us/dotnet/framework/) </code></h2> <p>If you have been a <code>.NET</code> developer for more than a few years, then you should already be familiar with the <code>.NET Framework</code>. This is what you have been developing on and targeting forever. However, with the introduction of <code>.NET Standard</code> and <code>.NET Core</code>, that is likely to change. You no longer have to limit your OS or platform to Windows.</p> <h2><code>.NET Foundation</code></h2> <p>With all the <code>.NET</code> terms flying around the web, it felt appropriate to throw another one at you.</p> <blockquote> <p>The <code>.NET Foundation</code> is an independent organization to foster open development and collaboration around the <code>.NET</code> ecosystem. It serves as a forum for community and commercial developers alike to broaden and strengthen the future of the <code>.NET</code> ecosystem by promoting openness and community participation to encourage innovation. &lt;cite&gt;www.dotnetfoundation.org&lt;/cite&gt;</p> </blockquote> <p>Be sure to visit their <a href="https://dotnetfoundation.org">site and get involved</a>!</p> <h2>Conclusion</h2> <p>The <code>.NET</code> ecosystem is rapidly evolving! With open source and cross-platform being two key priorities, you're in good hands. When developing for <code>.NET</code> be familiar with what version of the <code>.NET Standard</code> suits your needs -- and target accordingly.</p> <h3>Resources</h3> <ul> <li><a href="https://docs.microsoft.com/dotnet/standard/net-standard">.NET Standard - Microsoft Docs</a></li> <li><a href="https://docs.microsoft.com/dotnet/core">.NET Core - Microsoft Docs</a></li> <li><a href="https://docs.microsoft.com/dotnet/framework">.NET Framework - Microsoft Docs</a></li> </ul> David PinexUnit Roslyn Analyzershttps://davidpine.dev/posts/xunit-powered-by-roslynhttps://davidpine.dev/posts/xunit-powered-by-roslynPutting your code under the scopeSat, 11 Nov 2017 00:00:00 GMT<p>import { Tweet } from '@astro-community/astro-embed-twitter';</p> <h1>Intro</h1> <p>At my day job, I wear many hats. I'm currently wearing the "upgrade our team's tooling" hat. &lt;strong&gt;I counted 99&lt;/strong&gt; <code>*.xproj</code> and <code>project.json</code> based projects spanning four repositories that need to be updated to the revitalized <code>*.csproj</code> project format. With this the team can upgrade to the latest version of Visual Studio (finally upgrading to 2017 from 2015) and start taking advantage of the following:</p> <ul> <li>Supported tooling, no longer on preview tooling bits</li> <li>Allows us to <a href="blog/exploring-csharp-seven">explore C# 7</a> (and <a href="blog/csharp-seven-dot-one">peruse C# 7.1</a>) with production code</li> <li>Leverage the latest SDK and <code>.NET Core</code> CLI</li> <li>Other obvious reasons</li> <li>And so on...</li> </ul> <p>Upgrading tooling is not always glamorous, but there are times that I find myself excited. I thoroughly enjoy learning about the new tooling innovations and efforts around simplifying the development work-flow, especially when it involves C# and Visual Studio.</p> <h2>An "OMG, WTF" Moment</h2> <p>I mandate that our projects <em>"treat warnings as errors"</em>. After migrating the first solution over, 15 of the 30 projects had compilation errors. It's worth mentioning that we maintain a 1-to-1 ratio between class libraries and unit-test projects. Much to my surprise the 15 projects that had compilation errors were only unit-test projects. The reason that they stopped compiling was due to seemingly countless &lt;strong&gt;xUnit&lt;/strong&gt; warnings, generated by various &lt;strong&gt;Roslyn&lt;/strong&gt; analyzers. I was shocked by the number of warnings that were discovered.</p> <h2>The Realization</h2> <p>As one might imagine -- there are a lot of unit tests in 15 projects. This was an opportunity for &lt;strong&gt;Roslyn&lt;/strong&gt; and &lt;strong&gt;xUnit's&lt;/strong&gt; analyzers to shine, and shine they did! At first I was frustrated...thinking something like "FML, of course there is an issue" but upon further investigation I discovered that these were all perfectly reasonable warnings. In fact, several of them made me question how some tests ever passed at all. I was impressed by the power of this tooling addition. While &lt;strong&gt;Roslyn&lt;/strong&gt; has been around for a while, I have not personally observed a large uptake in its abilities. Needless to say, I was thrilled to see the &lt;strong&gt;xUnit&lt;/strong&gt; team leveraging it so much -- this is inspiring!</p> <p>Visual Studio 2017 will display the analyzers that are available within a project under Dependencies &gt; Analyzers.</p> <p><img src="./analyzers.png" alt="Analyzers" /></p> <p>As you can see they have put a lot of thought and effort into this. There are tons of various rules,<a href="https://xunit.github.io/xunit.analyzers/rules/">here is a comprehensive listing of them all.</a></p> <h2><a href="https://github.com/xunit/xunit"> xUnit</a></h2> <p>I'm dating my &lt;strong&gt;xUnit&lt;/strong&gt; efforts here a bit, but I've been a fan of &lt;strong&gt;xUnit&lt;/strong&gt; for a long time now! Back in the days of "DNX", or as Scott Addie might ask <a href="https://twitter.com/Scott_Addie/status/928021703619379201">DMX?</a>. In a time when I was still on <a href="https://ievangelistblog.wordpress.com/"> Wordpress</a>, before I was enlightened my Steve Hicks who says:</p> <blockquote> <p>If you aren't putting out as many posts as BuzzFeed, you can probably just build your blog statically &lt;cite&gt;<a href="https://twitter.com/pepopowitz">Steve Hicks</a>&lt;/cite&gt;</p> </blockquote> <p><a href="https://ievangelistblog.wordpress.com/2016/02/12/asp-net-core-1-0-unit-testing/">Here is a post</a> from February 2016 where I flexed some of the &lt;strong&gt;xUnit&lt;/strong&gt; capabilities. Again, I've been a fan of &lt;strong&gt;xUnit&lt;/strong&gt; for a long time now. It is incredibly</p> <h2><a href="https://github.com/dotnet/roslyn"> Roslyn</a></h2> <p>If you're unfamiliar with &lt;strong&gt;Roslyn&lt;/strong&gt;, it is the <code>.NET</code> Compiler Platform.</p> <blockquote> <p>Roslyn provides open-source C# and Visual Basic compilers with rich code analysis APIs. It enables building code analysis tools with the same APIs that are used by Visual Studio.</p> </blockquote> <p>Visual Studio relies on &lt;strong&gt;Roslyn&lt;/strong&gt; for lots of things. All of the aforementioned &lt;strong&gt;xUnit&lt;/strong&gt; rules are defined as &lt;strong&gt;Roslyn&lt;/strong&gt; analyzers. These analyzers are executed within Visual Studio, providing a great developer experience. They help you write better code.</p> <h2>Visual Studio -- Warnings</h2> <p>This section will serve as a brief listing of some of the warnings that I encountered. When writing a unit-test that uses <code>Theory</code> paired with <code>InlineData</code>, if there are duplicates -- this is flagged.</p> <p>&lt;a href="https://xunit.github.io/xunit.analyzers/rules/xUnit1025" target="_blank" title="xUnit Warning 1025"&gt; <img src="./inline-dup.png" alt="Inline Duplicate" /> &lt;/a&gt;</p> <p>When you mistakenly define a unit-test method parameter that is not used, it's flagged.</p> <p>&lt;a href="https://xunit.github.io/xunit.analyzers/rules/xUnit1026" target="_blank" title="xUnit Warning 1026"&gt; <img src="./remove-parameter.png" alt="Remove Parameter" /> &lt;/a&gt;</p> <p>When your unit-test is attempting to check whether a collection "does not contain" something, and does so with an <code>Assert.False(collection.Any(...));</code> -- it is flagged.</p> <p>&lt;a href="https://xunit.github.io/xunit.analyzers/rules/xUnit2012" target="_blank" title="xUnit Warning 2012"&gt; <img src="./use-doesnotcontain.png" alt="Use Does Not Contain" /> &lt;/a&gt;</p> <p>This one might be my favorite -- if you incorrectly <code>Assert.NotNull</code> on a value-type (that can never be <code>null</code>) -- it is flagged.</p> <p>&lt;a href="https://xunit.github.io/xunit.analyzers/rules/xUnit2002" target="_blank" title="xUnit Warning 2002"&gt; <img src="./remove-call.png" alt="Remove Call" /> &lt;/a&gt;</p> <p>When you call <code>Assert.Equal</code> and the expected argument is <code>null</code> -- it is flagged.</p> <p>&lt;a href="https://xunit.github.io/xunit.analyzers/rules/xUnit2003" target="_blank" title="xUnit Warning 2003"&gt; <img src="./use-null.png" alt="Use Null" /> &lt;/a&gt;</p> <p>I also ran into two variations of the <a href="https://xunit.github.io/xunit.analyzers/rules/xUnit2013">&lt;strong&gt;xUnit:2013&lt;/strong&gt; warning</a> which states the following:</p> <blockquote> <p>Do not use equality check to check for collection size</p> </blockquote> <p>In one scenario I had some unit test code similar to <code>Assert.Equal(0, values.Count);</code>, the analyzer instead suggested <code>Assert.Empty(values);</code>. This was my intent anyways, so of course I was happy to accept the recommended change.</p> <p>Likewise I had an <code>Assert.Equal(1, values.Count);</code> and the analyzer suggested <code>Assert.Single(values);</code>. I was ensuring that the collection had only a single value, so once again &lt;strong&gt;xUnit&lt;/strong&gt; was correct. Again, my appreciation for this tooling enhancement cannot be overstated.</p> <h1>Thank you</h1> <p>&lt;Tweet id="https://twitter.com/davidpine7/status/928805436761010176" /&gt;</p> <h2>Further reading</h2> <p>If you're curious about &lt;strong&gt;xUnit&lt;/strong&gt; or &lt;strong&gt;Roslyn&lt;/strong&gt;, please checkout the resources below:</p> <ul> <li><a href="https://github.com/xunit/xunit">xUnit -- GitHub.com</a></li> <li><a href="https://xunit.github.io/xunit.analyzers/rules/">xUnit Analyzer Rules -- GitHub.io</a></li> </ul> David PineC# Special Editionhttps://davidpine.dev/posts/csharp-special-editionhttps://davidpine.dev/posts/csharp-special-editionRanting about my favorite languageSun, 17 Dec 2017 00:00:00 GMT<p>Welcome to the 17th day of the <a href="https://crosscuttingconcerns.com/The-First-C-Advent-Calendar">C# Advent Calendar</a>. I'd like to take a moment to thank my fellow bloggers who are sharing their passion of C# with everyone following the C# Advent Calendar. Please, share this and their posts - help us all make C# even more widely adopted than it is today.</p> <h1>In the Beginning</h1> <p>I'm honored to blog about my favorite programming language, C#. There is an often forgotten truth about the history of C#, one truth that I like talking about. While C# is a really "cool" language with awesome features it was actually named "Cool" in the beginning!</p> <blockquote> <p>In January 1999, <em>Anders Hejlsberg</em> formed a team to build a new language at the time called Cool, which stood for "C-like Object Oriented Language". Microsoft had considered keeping the name "Cool" as the final name of the language, but chose not to do so for trademark reasons. &lt;cite&gt;<a href="https://en.wikipedia.org/wiki/C_Sharp_(programming_language">Wiki - C#</a>)&lt;/cite&gt;</p> </blockquote> <p>Likewise, the name C# somewhat implies an increment of C++. Imagine, the <code>++</code> being stacked on-top of each other -- you'd have something resembling the <code>#</code> symbol, clever no?! Unfortunately, my career didn't start with such a cool language. I learned <em>Java</em> in college and started my career with <em>Visual Basic</em>. I am now grateful to develop in C# professionally and for fun!</p> <h3>It is going to be around for a while</h3> <p>From all the languages in the <code>.NET</code> stack, C# is the most prolific.</p> <p>This is where <strong>Visual Basic</strong> lovers</p> <pre><code>Throw New System.Exception("Tomatoes!") </code></pre> <p>and <strong>F#</strong> enthusiasts</p> <pre><code>raise (new Exception("their fists in anger!")) </code></pre> <p>The language continues to evolve and innovate. According to the annual __ Stack Overflow__ developer survey, <a href="https://insights.stackoverflow.com/survey/2017#technology-programming-languages">C# is the 4th most popular programming language in the world</a>. And since we're talking about __ Stack Overflow__, it is noteworthy to mention that <a href="https://stackoverflow.com/users/22656/jon-skeet">Jon Skeet</a> is nearing 1 million reputation points -- and he's projecting this will occur mid January. Roughly 55% of all his contributions are for C#, yet he is a Java developer. This never ceases to amaze me!</p> <h1>Getting Started</h1> <p>I believe that Microsoft has done an amazing job at not only updating their documentation sites, but unifying them and enhancing the user experience tenfold. The sites seem to flow more naturally while delivering relevant content, and as a developer that is what I want from a resource.</p> <blockquote> <p>The <a href="www.javascript.com">JavaScript.com</a> website has one of the best getting started experiences on the web today. &lt;cite&gt;Maria Naggaga -- Keynote "It Starts With A Search", MKE DOT NET 2017&lt;/cite&gt;</p> </blockquote> <p>It is more natural for JavaScript to have an online tutorial than C#, but Microsoft made this happen regardless. They have an awesome series of <a href="https://docs.microsoft.com/en-us/dotnet/csharp/quick-starts/index">quick starts</a> that serve as tutorials. They introduced an in-browser REPL (Read-Eval-Print Loop) for C#, and it is amazing. Here is a listing of a few of them:</p> <ul> <li><a href="https://docs.microsoft.com/en-us/dotnet/csharp/quick-starts/hello-world">Hello C#</a></li> <li><a href="https://docs.microsoft.com/en-us/dotnet/csharp/quick-starts/numbers-in-csharp">Numbers in C#</a></li> <li><a href="https://docs.microsoft.com/en-us/dotnet/csharp/quick-starts/branches-and-loops">Branches and loops</a></li> <li><a href="https://docs.microsoft.com/en-us/dotnet/csharp/quick-starts/list-collection">Collections in C#</a></li> <li><a href="https://docs.microsoft.com/en-us/dotnet/csharp/quick-starts/introduction-to-classes">Introduction to classes</a></li> </ul> <p>Once you're developing you can always rely on the <a href="https://docs.microsoft.com/en-us/dotnet/csharp/">C# Guide</a> as well. Finally, the documentation site boasts <a href="https://docs.microsoft.com/en-us/dotnet/samples-and-tutorials/">Samples and Tutorials</a> for a more in depth look.</p> <h1>Speaking Fluently</h1> <p>C# is a beautiful language with an elegant syntax which is simple, powerful and expressive. Backed by an superb developer experience from Visual Studio, you can quickly immerse yourself into any project. What would a C# post be without some code? Not much of a post at all. Since this is a special edition, let's have a look at something I found interesting and insightful from C# 7. I wrote a post about <a href="blog/exploring-csharp-seven">exploring C# 7</a> if you'd like a refresher.</p> <p>C# 7.0 featured <code>out</code> variables, which enable inline declaration.</p> <pre><code>public static class StringExtensions { // The declaration is inlined with the out keyword // We can use 'var', as the type int is known // The 'result' variable is scope "leaks" to scope of the method body public static int ToInt32(string input) =&gt; int.TryParse(input, out var result)) ? result : result; } </code></pre> <p>We can now write super expressive, inlined <code>.TryParse</code> logic. I wanted to call attention to an issue that I ran into where I made an assumption. The lesson of the story is to never make an assumption, right?! <a href="https://github.com/dotnet/csharplang/issues/916">Here is the issue as it exists on __ GitHub__ today.</a></p> <p>Consider the following:</p> <pre><code>public static class ConcurrentExtensions { public static T TryPeekOrDefault&lt;T&gt;(this ConcurrentQueue&lt;T&gt; queue) =&gt; (queue?.TryPeek(out T result) ?? false) ? result : default; } </code></pre> <p>This results in the following compiler error:</p> <blockquote> <p>Error CS0165 Use of unassigned local variable 'result' IEvangelist.Csharp.Seven</p> </blockquote> <p>As you can see this does not currently work in C#, but I assumed it would. Let's break it down and figure out why. First, let's remove the single line expression and make it non-ternary.</p> <pre><code>public static class ConcurrentExtensions { public static T TryPeekOrDefault&lt;T&gt;(this ConcurrentQueue&lt;T&gt; queue) { if (queue?.TryPeek(out T result) ?? false) { return result; } return default; } } </code></pre> <p>The first expression within the <code>if</code> block tests if the <code>queue</code> parameter in context is <code>null</code>. If it is <code>null</code>, it will coalesce to <code>false</code>. If it is not <code>null</code>, then if will evaluate the returning result from the <code>.TryPeek</code> invocation. When the if block is evaluated to be either <code>true</code> or <code>false</code> - after evaluation we know that the <code>result</code> variable has been definitely assigned. Here is the spec on <strong><a href="https://github.com/dotnet/csharplang/blob/master/spec/variables.md#definite-assignment">definite assignment</a></strong>.</p> <blockquote> <p>At a given location in the executable code of a function member, a variable is said to be definitely assigned if the compiler can prove, by a particular static flow analysis (<a href="https://github.com/dotnet/csharplang/blob/master/spec/variables.md#precise-rules-for-determining-definite-assignment">Precise rules for determining definite assignment</a>), that the variable has been automatically initialized or has been the target of at least one assignment. &lt;cite&gt;<a href="https://github.com/dotnet/csharplang/blob/master/spec/variables.md#definite-assignment">C# Language - GitHub</a>&lt;/cite&gt;</p> </blockquote> <p>Now consider the following:</p> <pre><code>public static class ConcurrentExtensions { public static T TryPeekOrDefault&lt;T&gt;(this ConcurrentQueue&lt;T&gt; queue) { if (queue == null) { return default; } // The 'result' variable leaks to this outer scope. // But with the previous eager evaluation and corresponding early exit // the 'result' is never accessed. // As such, it is not "definitely assigned". if (queue.TryPeek(out T result)) { return result; } return default; } } </code></pre> <p>This is equivalent to the non-inlined usage of <code>out</code> variable declaration:</p> <pre><code>public static class ConcurrentExtensions { public static T TryPeekOrDefault&lt;T&gt;(this ConcurrentQueue&lt;T&gt; queue) { // The `result` variable may never be accessed. if (queue != null) { T result; if (queue.TryPeek(out result)) { return result; } } return default; } } </code></pre> <p>These are more verbose equivalents in terms of functionality, some with eager evaluation paired with an early exit. Both of these versions compile and would work as you'd expect them to. There is talk about getting better support for flow analysis, beyond definite assignment -- I for one would love that. TypeScript has been doing a great job at this, thanks to Anders and company!</p> <p>The issue still has a sense of illusion. Yes, as <a href="https://twitter.com/gafter">Neal Gafter</a> stated I would like definite assignment (which is part of flow analysis) to infer that the variable is only referenced where it was definitely assigned in this case. I'm happy that it's been labeled as a <em>Feature Request</em> and <em>Discussion</em>.</p> <p>Does that seem reasonable? If not, why? If so, please <a href="https://github.com/dotnet/csharplang/issues/916">visit the issue</a> and ! I'm thrilled that C# is open source and the community is able to contribute in ways such as this. I hope that after reading this you might be inspired to get involved.</p> <blockquote> <p>Why procrastinate today, when you can procrastinate tomorrow? &lt;cite&gt;<a href="https://twitter.com/reverentgeek">David Neal</a>&lt;/cite&gt;</p> </blockquote> <h3>Counting my blessings</h3> <p>I consider myself to be very fortunate. I work with the owner of <a href="https://travelswithedwina.com/">Edwina</a>, who has been patiently teaching and coaching me through the better part of three years of my career. She used to teach algorithms and C# at the Milwaukee School of Engineering (MSOE). I have learned so much from her about C#, language semantics, algebraic reduction, etc...and I'm grateful for that. She has certainly made me a more pragmatic developer. I encourage everyone to find someone who can inspire, motivate or mentor them.</p> <h1>Final Thoughts</h1> <p>Go learn C#! If you already know it, mentor someone who wants to learn it. Happy Holidays and please support my fellow bloggers as they share their passion for C# with you.</p> David PineASP.NET Core Configuration Tipshttps://davidpine.dev/posts/asp-net-core-configurationhttps://davidpine.dev/posts/asp-net-core-configurationConfiguration tips for successSat, 10 Mar 2018 00:00:00 GMT<p>Being a software developer, it's in our nature to configure various aspects of the applications that we write. With <strong>ASP.NET Core</strong> configuring our applications is really straight forward. Luckily, there is a lot of really well written documentation surrounding this topic.</p> <ul> <li><a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?tabs=basicconfiguration">ASP.NET Core - Configuration</a></li> <li><a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options">ASP.NET Core - Options</a></li> </ul> <p>However, there is still room for improvement. I've prepared a few tips that can enhance developer productivity by taking advantage of various C# features.</p> <h2>C# Class &lt; -- &gt; JSON</h2> <p><strong>ASP.NET Core</strong> configuration via the <code>IOptions&lt;T&gt;</code> and <code>IOptionsSnapshot&lt;T&gt;</code> have been around for a long time. Their usage is a first class citizen within <strong>ASP.NET Core</strong> applications, and work to tie <code>appsettings.json</code> values to corresponding configuration <a href="https://en.wikipedia.org/wiki/Plain_old_CLR_object">POCO's</a>. As a refresher on configuration, there is a one-to-one relationship between a JSON object in the <code>appsettings.json</code> and the C# class that represents it.</p> <h3><code>appsettings.json</code></h3> <pre><code>{ "notificationSettings": { "notificationInterval": "00:15:00", "isMulticast": true, "sources": [ { "url": "https://www.contoso.com/notificationHub", "key": "f7ea5e2b22bf907344f3a780caa0a166" } ] } } </code></pre> <h3><code>NotificationSettings.cs</code> and <code>NotificationSource.cs</code></h3> <pre><code>public class NotificationSettings { public TimeSpan NotificationInterval { get;set; } public TimeSpan TimeoutInterval { get; set; } public bool IsImplicit { get; set; } public List&lt;NotificationSource&gt; Sources { get; set; } public NotificationSetting() { IsImplicit = true; TimeoutInterval = TimeSpan.FromSeconds(30); } } public class NotificationSource { public Uri Url { get; set; } public string Key { get; set; } } </code></pre> <p>If you looked at both the <code>appsettings.json</code> and the two C# classes, I'm hoping that you noticed something. I hope that you noticed that the JSON settings didn't fully provide a value for all the mapped properties in the <code>NotificationSettings</code> object. We have however, set these values as part of the object's <code>.ctor</code>.</p> <blockquote> <p>&lt;cite&gt;<strong>ProTip</strong>&lt;/cite&gt; Use C# 6 Property Initializers to set default values. If the corresponding property exists in the JSON object mapping, it will be overwritten as to be expected; however, if omitted your default value is used.</p> </blockquote> <p>Using property initializers, this is simplified as follows:</p> <pre><code>public class NotificationSettings { public TimeSpan NotificationInterval { get;set; } public TimeSpan TimeoutInterval { get; set; } = TimeSpan.FromSeconds(30); public bool IsImplicit { get; set; } = true; public List&lt;NotificationSource&gt; Sources { get; set; } } </code></pre> <p>It is a good practice to specify default values for your settings classes. As part of the <code>Startup.cs</code> class, the <code>ConfigureServices</code> method is where you will "Configure" your mappings of the JSON section that is associated to the C# object representing it. For example:</p> <pre><code>public void ConfigureServices( IServiceCollection services) { // Configure the "NotificationSettings" // mapping it to the corresponding JSON section services.Configure&lt;NotificationSettings&gt;( Configuration.GetSection("NotificationSettings")); } </code></pre> <p>This should look very familiar.</p> <blockquote> <p>&lt;cite&gt;<strong>ProTip</strong>&lt;/cite&gt; Use C# 6 <code>nameof</code> operator to configure your C# class mapping. This alleviates the usage of magic strings and enforces a consistent naming convention.</p> </blockquote> <p>The change is tiny, however; this is another great usage of the <code>nameof</code> operator. This assumes that the the section in the JSON file matches the name of the class object representing it. Additionally, it serves as a reason to keep the names matching.</p> <pre><code>public void ConfigureServices( IServiceCollection services) { services.Configure&lt;NotificationSettings&gt;( Configuration.GetSection(nameof(NotificationSettings))); } </code></pre> <h2>Conclusion</h2> <p>I have provided a few simple tips for <strong>ASP.NET Core</strong> configuration leveraging a few C# 6 features. They are nothing earth-shattering, but they are practical and very useful. I often see developers <em>not</em> taking advantage of simple features like these when they are available. These two features alone in an <strong>ASP.NET Core</strong> configuration could simplify refactoring and stream-line readability -- that's enough reason for me, I hope it is for you too!</p> David PineASP.NET Core Unit Testing For Security Attributeshttps://davidpine.dev/posts/asp-net-core-security-unit-testinghttps://davidpine.dev/posts/asp-net-core-security-unit-testingAuthorization Attribute Safety NetFri, 23 Mar 2018 00:00:00 GMT<h2>Intro</h2> <p>As a developer, I can say that developers are lazy - at least I know and acknowledge that I am. If I'm tasked with something even the slightest bit repetitious I'll script it out, or find a way to automate it. Likewise, if I fall into a habit of forgetting something important - I'll figure out a way to <em>not forget</em>. Especially when it comes to securing an application.</p> <h2>Security Soapbox</h2> <p>We should take application security very seriously! With <strong>ASP.NET Core</strong> there's a lot of <a href="https://docs.microsoft.com/en-us/aspnet/core/security/">existing documentation</a> for securing your application. This covers identity, authentication, authorization, data protection, HTTPS, safe storage, Azure key vault, anti-request forgery, open redirect attack, cross-site scripting, etc... the list goes on and on. All these things are important and as a developer you're ultimately accountable for writing secure code.</p> <h2>The Dilemma</h2> <p>A while back I spent a fair amount of time writing some <strong>ASP.NET Core Web API</strong> endpoints and I kept forgetting to provide the <code>AuthorizeAttribute</code> on various controller classes or even controller actions.</p> <p>There is an easy way to solve this. Within <code>ConfigureServices</code> we could apply an <code>AuthorizeFilter</code> with a policy that requires an authenticated user. This serves as a nice security blanket, that ensures all endpoints are only accessible from an authenticated user.</p> <pre><code>public void ConfigureServices(IServicesCollection services) { services.AddMvc( options =&gt; { // Only allow authenticated users. var defaultPolicy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .Build(); options.Filters.Add(new AuthorizeFilter(defaultPolicy)); }); } </code></pre> <p>However, based on your needs - you may choose to not apply this filter. If you choose to not apply this filter you're left with three options.</p> <ol> <li>Apply an <code>AuthorizeAttribute</code> at the controller class level (cascades onto all the actions)</li> <li>Apply an <code>AuthorizeAttribute</code> on each individual action method</li> <li>Not protect your API at all</li> </ol> <h2>Automation To The Rescue</h2> <p>Some of our <strong>ASP.NET Core Web APIs</strong> were protected by the filter, some by controller class level <code>AuthorizeAttribute</code> decoration and others by the individual action... and some not at all. This is a problem! It is common practice to run unit tests prior to pushing code into a feature branch for a pull request. If I forget to do that, my build system has some pull request pre-checks in place - one of which is to run the unit tests. As such, I know that unit tests will be executed. With this, I decided to write a unit test that leveraged a bit of reflection. Since it is a unit test, I'm not terribly concerned with performance - so reflection is fine...</p> <p>Here was the thought process to write a unit test that could ensure that I'm not forgetful.</p> <ul> <li>Load all assemblies into the current <code>AppDomain</code></li> <li>Of all the loaded assemblies, get all the controller types <ul> <li>Of those controller types, get the ones that are missing the <code>AuthorizeAttribute</code></li> <li>Of the unauthorized controller types, get the <code>HttpMethodAttribute</code> methods</li> <li>If missing both the <code>AuthorizeAttribute</code> and <code>AllowAnonymousAttribute</code> - <strong>fail</strong></li> </ul> </li> </ul> <h3>Codify</h3> <p>So, our step one is to load all assemblies into the current <code>AppDomain</code>. This makes some assumptions. It assumes that our test project will have a reference to the <strong>ASP.NET Core Web API</strong> project, so that its <code>.dll</code> will be available to us for loading (in our <code>bin</code> directory). We will also assume the root namespace we're looking for, we should know this anyway as we follow naming conventions and we're the owner of the corresponding project.</p> <pre><code>public class ActionTests { const string RootNamespace = nameof(IEvangelist); static readonly string[] ExecutableExtensions = { ".exe", ".dll" }; public ActionTests() =&gt; Directory.EnumerateFiles( Path.GetDirectoryName( Assembly.GetExecutingAssembly().Location) ?? string.Empty, $"{RootNamespace}.*") .Where(IsExeOrDll) .Select(Assembly.LoadFrom) .Select(assembly =&gt; TryCatchIgnore( () =&gt; AppDomain.CurrentDomain.Load(assembly.GetName()))) .ToList(); static bool IsExeOrDll(string path) =&gt; ExecutableExtensions.Any( extension =&gt; extension.Equals( Path.GetExtension(path), StringComparison.OrdinalIgnoreCase)); static T TryCatchIgnore&lt;T&gt;(Func&lt;T&gt; func) { try { return func(); } catch { return default; } } </code></pre> <p>Step two, is really the entry point of our core functionality for the test itself. We'll need a <code>Fact</code> test method. Yes, <a href="/blog/xunit-powered-by-roslyn">I'm a HUGE fan of <strong>xUnit</strong></a> - they have done some amazing things! In this test method we will start by getting all the types that are a subclass of <code>Controller</code>.</p> <pre><code>[Fact] public void AllActionsOrParentControllerHaveAuthorizationAttributeTest() { var allControllers = GetAllControllerTypes(); // we'll continue here ... } static List&lt;Type&gt; GetAllControllerTypes() =&gt; AppDomain.CurrentDomain .GetAssemblies() .Where(a =&gt; a.FullName.StartsWith(RootNamespace)) .SelectMany(a =&gt; a.GetTypes() .Where(t =&gt; t.FullName.Contains(Controller) || t.BaseType == ControllerType || t.DeclaringType == ControllerType)) .ToList(); </code></pre> <p>Now that we have all the controller types from all the assemblies loaded into our <code>AppDomain</code>, we need to filter them. We need to find the types that are missing the <code>AuthorizeAttribute</code>, we'll call these <code>unauthorizedControllers</code> for now.</p> <pre><code>[Fact] public void AllActionsOrParentControllerHaveAuthorizationAttributeTest() { var allControllers = GetAllControllerTypes(); var unauthorizedControllers = GetControllerTypesThatAreMissing&lt;AuthorizeAttribute&gt;(allControllers); // We'll continue from here... } static List&lt;Type&gt; GetControllerTypesThatAreMissing&lt;TAttribute&gt;( IEnumerable&lt;Type&gt; types) where TAttribute : Attribute =&gt; types.Where(t =&gt; t.GetCustomAttribute&lt;TAttribute&gt;() == null) .ToList(); </code></pre> <p>This method iterates through the given <code>IEnumerable&lt;Type&gt;</code> and returns a <code>List&lt;Type&gt;</code> that are not attributed with the <code>AuthorizeAttribute</code>. The reason that we're filtering out controller types that are attributed, is that we'll consider them to be protected from the standpoint of our unit-testing capabilities. Now, we need to find all the methods on these types that match the following criteria:</p> <ol> <li>Are a <code>public</code> instance method</li> <li>Have any <code>HttpMethodAttribute</code> subclass</li> </ol> <p>These methods are the controller's actions. From these actions, we need to find the unauthorized actions. This is done by filtering out actions that explicitly declare themselves with the <code>AllowAnonymousAttribute</code> and are also missing the <code>AuthorizeAttribute</code>.</p> <pre><code>foreach (var controller in unauthorizedControllers) { var actions = controller.GetMethods(BindingFlags.Public | BindingFlags.Instance) .Where(m =&gt; m.GetCustomAttributes&lt;HttpMethodAttribute&gt;().Any()) .ToList(); var unauthorizedActions = actions.Where( action =&gt; action.GetCustomAttribute&lt;AuthorizeAttribute&gt;() == null &amp;&amp; action.GetCustomAttribute&lt;AllowAnonymousAttribute&gt;() == null) .ToList(); // If unauthorizedActions.Any() is true, sound the alarms! } </code></pre> <p>We can add a few additional sanity checks along the way - with the caveat that this is not your typical "unit test". For example we could add the following:</p> <ul> <li>Assert that we do in fact load assemblies</li> <li>Assert that count of all the controllers is greater than the unauthorized controllers</li> <li>Assert that we find our "white-listed" controller</li> </ul> <h3>Putting It All Together</h3> <pre><code>using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Routing; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using Xunit; namespace IEvangelist.Tests { public class ActionTests { const string RootNamespace = nameof(_Evangelist); const string Controller = nameof(Controller); const string WhiteListed = nameof(UnauthorizedActionInController.GetZero); static readonly Type ControllerType = typeof(Controller); static readonly string[] ExecutableExtensions = { ".exe", ".dll" }; public ActionTests() =&gt; LoadAllAssemblies(); void LoadAllAssemblies() { var assemblies = Directory.EnumerateFiles( Path.GetDirectoryName( Assembly.GetExecutingAssembly().Location) ?? string.Empty, $"{RootNamespace}.*") .Where(IsExeOrDll) .Select(Assembly.LoadFrom) .Select(assembly =&gt; TryCatchIgnore( () =&gt; AppDomain.CurrentDomain.Load(assembly.GetName()))) .ToList(); Assert.False(assemblies.IsNullOrEmpty()); } [Fact] public void AllActionsOrParentControllerHaveAuthorizationAttributeTest() { var allControllers = GetAllControllerTypes(); var unauthorizedControllers = GetControllerTypesThatAreMissing&lt;AuthorizeAttribute&gt;(allControllers); Assert.True(allControllers.Count &gt; unauthorizedControllers.Count); foreach (var controller in unauthorizedControllers) { var actions = controller.GetMethods(BindingFlags.Public | BindingFlags.Instance) .Where(m =&gt; m.GetCustomAttributes&lt;HttpMethodAttribute&gt;().Any()) .ToList(); var unauthorizedActions = actions.Where( action =&gt; action.GetCustomAttribute&lt;AuthorizeAttribute&gt;() == null &amp;&amp; action.GetCustomAttribute&lt;AllowAnonymousAttribute&gt;() == null) .ToList(); if (unauthorizedActions.IsNullOrEmpty() || (unauthorizedActions.Count == 1 &amp;&amp; unauthorizedActions[0].Name == WhiteListed)) { continue; } unauthorizedActions.ForEach( action =&gt; Console.WriteLine($"{action} is unauthorized!")); Assert.True(false, $"Unauthorized action(s) found!"); } } static List&lt;Type&gt; GetAllControllerTypes() =&gt; AppDomain.CurrentDomain .GetAssemblies() .Where(a =&gt; a.FullName.StartsWith(RootNamespace)) .SelectMany(a =&gt; a.GetTypes() .Where(t =&gt; t.FullName.Contains(Controller) || t.BaseType == ControllerType || t.DeclaringType == ControllerType)) .ToList(); static List&lt;Type&gt; GetControllerTypesThatAreMissing&lt;TAttribute&gt;( IEnumerable&lt;Type&gt; types) where TAttribute : Attribute =&gt; types.Where(t =&gt; t.GetCustomAttribute&lt;TAttribute&gt;() == null) .ToList(); static bool IsExeOrDll(string path) =&gt; ExecutableExtensions.Any( extension =&gt; extension.Equals( Path.GetExtension(path), StringComparison.OrdinalIgnoreCase)); static T TryCatchIgnore&lt;T&gt;(Func&lt;T&gt; func) { try { return func(); } catch { return default; } } } public class UnauthorizedTestController : Controller { } public class UnauthorizedActionInController : Controller { [HttpGet] public int GetZero() =&gt; 0; [Authorize, HttpPost] public IActionResult Post([FromBody] int number) =&gt; Ok(); [OverrideAge, HttpDelete] public IActionResult Delete() =&gt; Ok(); } public class OverrideAge : Authorize { } } </code></pre> <h2>Conclusion</h2> <p>This solution is not perfect, it has a lot of opportunity for improvement. I'm open to suggestions... If you have an idea, drop it in the comments and feel free to share your source. Likewise, if you like this idea - let me know. I was hoping to eventually find a way to turn this into a <strong>C# Roslyn-Powered Analyzer</strong> but I'm not sure that is possible. There is one fact that remains, this has actually proven itself useful - there have been several times where this test failed as a result of someone (myself included) forgot to apply the appropriate security attributes to an <strong>ASP.NET Core Web API</strong> endpoint we were writing. I hope that this can help you too!</p> David PineJourney of a Lifetimehttps://davidpine.dev/posts/journey-of-a-lifetimehttps://davidpine.dev/posts/journey-of-a-lifetimeIT Konekt - Serbia (Belgrade, Niš and Novi Sad)Sat, 28 Apr 2018 00:00:00 GMT<p>import { YouTube } from '@astro-community/astro-embed-youtube';</p> <h1>Intro</h1> <p>I am returning home from a long trip in Serbia for <strong><a href="https://itkonekt.com/en/">IT Konekt</a></strong>. This was literally a trip of a lifetime and I could not be more grateful for such an amazing opportunity. Serbia has such an incredible culture and powerful history. I have been overwhelmed by the care and attention to detail from the organizers of <strong>IT Konekt</strong>. This was my first time ever leaving the United States of America and I didn't know what to expect. I was really nervous but equally excited. This was more of a journey of epic proportions than it was a conference trip.</p> <p>I want to publicly thank <a href="https://twitter.com/LadyNaggaga">Maria Naggaga</a> of Microsoft for referring <a href="https://twitter.com/Milica_Radovic_">Milica Radovic</a> and <a href="https://www.linkedin.com/in/milicanastasic">Milica Nastasic</a> to me. They were looking for someone from the U.S. to speak about C# and Maria suggested me. This is the such an honor and I couldn't have been more excited to represent the U.S. and C# for my first international speaking engagement.</p> <h2>Belgrade</h2> <h3>April 13, 2018</h3> <p>Our journey began in Belgrade – the capital city of Serbia. I arrived on the same flight into Belgrade as <a href="https://twitter.com/jcleblanc">Jonathan LeBlanc</a> of Box. In true movie fashion, our driver was waiting for us holding our names at the arrival gate of the airport. This was a warm welcome and made me feel special. After a 35-minute Car:Go (European Uber) drive we arrived at our hotel and while I didn't get much sleep on the plane, I had energy from both the adrenaline and excitement. Just as soon as I was settling into my room, it was time to part for the first of seemingly countless speaker dinners. We were hungry and ready to immerse ourselves in the Serbian culture. We had a traditional Serbian dinner after a celebratory shot (or three) of <a href="https://en.wikipedia.org/wiki/Rakia">rakija</a> - "Serbian fruit brandy". Now, if you're like me you probably didn't know what rakija was... <em>warning</em> it is very strong! They fed us like kings and queens, we were treated like royalty!</p> <p><img src="./breaking-bread.jpg" alt="Breaking Bread" /></p> <p><a href="https://twitter.com/csswizardry">Harry Roberts</a> sat to my right - he was a blast. He and I shared a lot of common thoughts about the developer community. I enjoyed his company. I asked him if he was on <em>Twitter</em> so that I could follow him - yeah 58k+ followers. The food was great. The people of Serbia know how to do meat, I might get myself banned from the United States for saying this but it is true, "Serbia has the best bacon in the world!" After a long night, I returned to my hotel where I immediately fell asleep.</p> <h3>April 14, 2018</h3> <p>I woke up at 8 AM, I then decided I would rest my eyes a little longer (suffering from jetlag) I opened them again around 11 AM. My talk was at 4, so I had plenty of time. It was conference day, I was prepared and looking forward to giving my talk. I believe that presenting on C# was an excellent choice – it is a universal language. The attendees really enjoyed it and were thoroughly engaged. The only time people had their phones out was for taking pictures of me or the slides. I was asked to take pictures with several attendees both before and after my talk. This was the first time this has ever happened to me and it made my day!</p> <p>In addition to a great conference, Jonathan LeBlanc and I took a casual stroll through Belgrade. We ended up walking over 14 miles and visited the Belgrade fortress. Along the way, we were asked to help someone lift an A/C unit up a shaky ladder onto a roof within a closed market.</p> <p><img src="./market.jpg" alt="Market" /></p> <p><img src="./helping-others.jpg" alt="Helping Others" /></p> <p><img src="./back-market.jpg" alt="Back Market" /></p> <p>No one was injured in the photographing of this event...our walk continued with no more interruptions. We arrived at the Belgrade fortress and it was very interesting. We were able to walk all over it, in the moat, in tunnels and even on the walls. Once we made our way to the back of the fortress we found a hill with a view. Here is a great shot from the fortress walls overlooking where the rivers meet - it truly was a remarkable view.</p> <p><img src="./belgrade.jpg" alt="Beograd" /></p> <p>While touring the fortress we stumbled upon a torture museum, Jonathan and I decided "why not". After the first few exhibits we kept asking each other "what the hell were we thinking!" I was tempted to share some of the pictures from the torture museum but nope, I'll spare you the pain. Once we were finished witnessing the horrors, we were spent...we walked back to the hotel.</p> <h3>April 15, 2018</h3> <p>Sunday was traveling day and we were driving to Niš. The core group of speakers got to know each other extremely well during the trip, and I consider them all personal friends now. We spent hours in a jumbo van traveling the country of Serbia over the course of a week - quite the adventure. The core group of speakers were Jon Galloway, Jonathan LeBlanc, Håkon Wium Lie and myself.</p> <p><img src="./the-van.jpg" alt="The Van" /></p> <h2>Niš</h2> <h3>April 16, 2018</h3> <h4>The Humble Giant (Jovan Milic)</h4> <p>When we arrived in Niš, we were greeted by several hosts. One of them was <a href="https://en.wikipedia.org/wiki/Jovan_Milic">Jovan Milic</a> and I'm referring to him from now on as the "humble giant". If you're ever looking for inspiration read his story, it will hopefully put things a little more into perspective. For me, I can relate very much to his story and appreciate all the things that he's done to earn his place in the world now. The next time you want to complain about the things you take for granted, remember his story and <a href="https://www.findraoul.tk/">support his cause</a>.</p> <p><img src="./jovan.jpg" alt="Jovan Milic" /></p> <p>There are several life lessons that I live by - this is one of them.</p> <blockquote> <p>Expect nothing, but be grateful for everything.</p> </blockquote> <p>Jovan and the other hosts treated us like celebrities. We had a walking tour of the Niš fortress and city center. After arming ourselves with some umbrellas we were ready to take on the light rain. My umbrella just so happened to be pink. I'm not sure the color of my umbrella is relevant for this story, but I felt inclined to share it. After strolling around the fortress and then making our way back towards the city center - we needed some caffeine. We enjoyed turkish coffee and dark beer, because why not both?! I recall this evening very well, this is the night that Håkon told us about the time he built a balsawood raft with a group of volunteers. The raft weighed over 20 tons, and the intended purpose was to set course from South America (Peru) and sail to Easter Island. Why, you might ask - as both Jon's and I did...we were intrigued, why on earth would anyone want to do this? Simply to prove a point that it could be done. It would prove that Easter Island could have been inhabited by the people of South America. This voyage took place in 2015-2016 and was the spotlight of world-wide news, for more details checkout the <a href="https://kontiki2.com/">official website</a> and <a href="https://www.wiumlie.no/img/2015/kontiki2.html">the story from Håkon himself here</a>.</p> <p>Again we enjoyed a traditional Serbian dinner. There was more rakija and as luck would have it, traditional Serbian music was played/performed live...with the livelihood from the rakija there was dancing and singing, and all-around celebration. Such a joyous time!</p> <p><img src="./singing.jpg" alt="Singing" /></p> <p><img src="./sitting.jpg" alt="Sitting" /></p> <p><img src="./dancing.jpg" alt="Dancing" /></p> <h3>April 17, 2018</h3> <p>Conference Day! The stage was set. The crowds were drawing near, anticipation was building. News crews, wait...news crews? Yes, we were actually interviewed and made it on the Serbian national news - several times. This was yet another first for me. The media was interested about our take on the IT community in Serbia. They were curious about how the developers of Serbia rank up against the rest of the world. That is a hard question to answer, but one thing was consistent with all of our responses - we believe that any developer can change the world! When you take talent, an idea and hunger to build something incredible there is no limit to what you can achieve.</p> <p>The conference talks were all really well received. The developer community seemed eager to ask questions after sessions. As a speaker, this is what makes it all worthwhile - the gratifying moment when you know that you've been able to help someone and that you've delivered value. After the conference we spent more time walking around Niš, this is when I learned that Håkon brought a drone. We took turns flying it and taking pictures of the scenery.</p> <h2>Novi Sad</h2> <h3>April 18, 2018</h3> <p>Wednesday was another traveling day. Our jumbo van trip was scheduled to take roughly four hours. But before we left, we were asked to be part of a ceremony with the President of municipalities, the mayor of Niš. The press was there to capture the moment. It was incredible sharing ideas for shared working spaces and encouraging hackathons. Our hosts served as translators, communicating our ambitions and guidance. The mayor was so pleased that he presented Håkon with a special honor - a medallion of Constantine the Great.</p> <blockquote> <p>IT experts are visiting the municipalities of Crvena Krst.</p> </blockquote> <p>&lt;YouTube id="https://youtu.be/5jCy7oHbDaE" /&gt;</p> <p>Shortly after the ceremony, we drove to Novi Sad. We arrived without a hitch, settling into our hotels. Followed by a city tour, which proved to be a spectacular site. Dinner was at the <a href="https://www.terasa.rs/">Terasa</a>. This sits right on the Danube river and at the foot of the Novi Sad fortress. Håkon was able to capture some amazing footage with his drone...the view was beautifully stunning.</p> <p>&lt;YouTube id="https://youtu.be/rC4pvMA8Qsk" /&gt;</p> <h2>Sremski Karlovci &amp; Danube River</h2> <h3>April 19, 2018</h3> <p>The 19th was a really relaxing day, we had a walking tour, lunch on the Danube river, followed by a wine tasting at the <a href="https://www.muzejzivanovic.com/">Museum of Beekeeping and Wine Cellar Zivanovic</a>. Again, Håkon let his drone capture some of the essence and scenery. This video starts well beyond the town center, working its way back toward where we had lunch on the river. Yes, that establishment in the distance is where Håkon is navigating his drone recording this clip. I am seriously trying to convince my wife to let me purchase one of these drones. If you want to buy it for yourself <a href="https://www.amazon.com/Mavic-Platinum-Quadcopter-Camera-Batteries/dp/B07871KXMP">here is the link</a>.</p> <p>&lt;YouTube id="https://youtu.be/h6qwzQ65Fh8" /&gt;</p> <p>This footage is amazing, with video quality up to 4k if you're into that sort of thing... 😏</p> <h3>April 20, 2018</h3> <p>The final conference day! I was up first and immediately following my talk, I needed to catch a cab back to Belgrade to catch my flight back home. I was sent home with a parting gift from the founders. I love the thoughtfulness, and it is so true that it's better than a notebook and a pen.</p> <p>I was excited to be returning home. I know that I was missed by Lyric, Londyn, Lennyx (my three sons) and Jen (wife). I'm thankful to have had a safe trip and shared so many awesome experiences. For those interested in the conference itself, I'll be posting videos and more photos as they are released. For my talk, see below.</p> <h3>What's new in C# (The Talk)</h3> <p>Here is the link for my talk, <a href="https://github.com/IEvangelist/csharp">What's new in C#</a>. This was the talk that I presented at all three <strong>IT Konekt</strong> conferences.</p> <h2>Closing Thoughts</h2> <p>I was blessed to have made so many new friends on this journey. These people are inspiring, you should follow them as they are true leaders!</p> <ul> <li><strong>Jon Galloway</strong> <a href="https://www.linkedin.com/in/jongalloway/">LinkedIn</a>, <a href="https://twitter.com/jongalloway">Twitter</a>, <a href="https://github.com/jongalloway">GitHub</a>, <a href="https://weblogs.asp.net/jongalloway">Blog</a> <ul> <li>Executive Director <a href="https://dotnetfoundation.org/">.NET Foundation</a>, ASP.NET MVC 3/4/5 Author</li> </ul> </li> <li><strong>Jonathan LeBlanc</strong> <a href="https://www.linkedin.com/in/jcleblanc/">LinkedIn</a>, <a href="https://twitter.com/jcleblanc">Twitter</a>, <a href="https://github.com/jcleblanc">GitHub</a>, <a href="https://www.jcleblanc.com/">Website</a> <ul> <li>Director of Developer Advocacy <a href="https://www.box.com/">Box</a>, Emmy Award Winner, O'Reilly Author</li> </ul> </li> <li><strong>Håkon Wium Lie</strong> <a href="https://www.linkedin.com/in/howcome/">LinkedIn</a>, <a href="https://twitter.com/wiumlie">Twitter</a>, <a href="https://www.wiumlie.no/en">Website</a> <ul> <li>Creator of CSS, former CTO of Opera, Kontiki Adventurer</li> </ul> </li> <li><strong>Milica Radovic</strong> <a href="https://www.linkedin.com/in/milicaradovic/">LinkedIn</a>, <a href="https://twitter.com/Milica_Radovic_">Twitter</a> <ul> <li>Founder, IT Konekt</li> </ul> </li> <li><strong>Milica Nastasic</strong> <a href="https://www.linkedin.com/in/milicanastasic">LinkedIn</a> <ul> <li>Co-Founder, IT Konekt</li> </ul> </li> <li><strong>Jovan Milic</strong> <a href="https://www.linkedin.com/in/jovan-milic-68713973">LinkedIn</a>, <a href="https://twitter.com/JMiliic">Twitter</a> <ul> <li>Founder of Find Raoul</li> </ul> </li> </ul> David PineASP.NET Core + Angular Photo Booth Apphttps://davidpine.dev/posts/photo-boothhttps://davidpine.dev/posts/photo-boothDo it yourselfFri, 22 Jun 2018 00:00:00 GMT<h1>Inspiration</h1> <p>I recently returned from Charleston, South Carolina -- where I spoke at <strong>SyntaxCon</strong>. The event was very professionally organized and gave me inspiration for <strong>Cream City Code</strong>. In the main hall, they had a <em>HALO</em> by <a href="https://www.simplebooth.com/">Simple Booth</a>. It serves as a photo booth with a conference-specific backdrop -- which is perfect for sharing the conference experience. I looked into purchasing one but was encouraged to simply write my own... so I did and this blog will detail that process.</p> <p>Ultimately, the resulting social media share ends up looking something like these ( To open gallery ). It generates an animated image (<code>*.gif</code>) from the series of photos taken.</p> <p><img src="./smplbth-one.gif" alt="" /></p> <p><img src="./smplbth-two.gif" alt="" /></p> <p><img src="./smplbth-three.gif" alt="" /></p> <p>&lt;br/&gt;</p> <h2>User Workflow</h2> <p>Here's what we want to do.</p> <ol> <li>We need to capture several pictures upon user initiation</li> <li>We need to create an animated image from these pictures</li> <li>We need to allow the user to send them to themselves for sharing</li> <li>We need to reset the state of the app after sharing is complete</li> </ol> <p>That's pretty simple, right?! While the application is idle, we'll provide the live camera view with some branding and a "start" button. Anyone walking by will more than likely see themselves and naturally become curious. The hope is that their curiosity will entice them enough to press the "start" button. Pressing the "start" button leads to more excitement as they're presented with a countdown timer... 3, 2, 1... (Flash, Snap)! Their picture is taken, and this continues a few more times. This ends up sparking a chain reaction where others take notice and join in. There are plenty of opportunities for "photo bombing"!</p> <h2>Technologies Used</h2> <p>This application is built using the <strong>Angular</strong> <strong>ASP.NET Core</strong> SPA template. Additionally, I'm leveraging the following:</p> <table> <thead> <tr> <th>Technology</th> <th>Purpose</th> </tr> </thead> <tbody> <tr> <td><a href="https://sixlabors.com/projects/imagesharp/">   ImageSharp</a></td> <td>Convert several <code>.png</code> images into a single <code>.gif</code></td> </tr> <tr> <td><a href="https://azure.microsoft.com/en-us/services/storage/blobs/">   Azure - Blob Storage</a></td> <td>Persist generated <code>.gif</code> image</td> </tr> <tr> <td><a href="https://www.twilio.com/">   Twilio</a></td> <td>Send SMS text message with URL of animated <code>.gif</code> image</td> </tr> </tbody> </table> <p>The application is up on <strong>GitHub</strong> here: &lt;a href="https://github.com/IEvangelist/IEvangelist.PhotoBooth" target="_blank"&gt;   IEvangelist.PhotoBooth. &lt;/a&gt;</p> <h2>Under The Hood</h2> <p>Now that we've familiarized ourselves with what we need to do, let's take a look at how we can approach it. As I mentioned, this is an <strong>ASP.NET Core</strong> application -- so we'll see a <code>Program.cs</code>, <code>Startup.cs</code>, and a <code>Controllers</code> directory for our <strong>ASP.NET Core Web API</strong>. It is also an <strong>Angular</strong> application. This too has common conventions and things to look for, such as <code>components</code>, <code>services</code>, <code>modules</code>, <code>pipes</code>, etc.</p> <h3>First The C# Code</h3> <p>Snippet from <code>Startup.ConfigureServices</code></p> <pre><code>// Map services services.AddTransient&lt;IImageProcessorService, ImageProcessorService&gt;(); services.AddSingleton&lt;IImageRepository, ImageRepository&gt;(); services.AddSingleton&lt;ITextMessagingService, TextMessagingService&gt;(); // Map appsettings.json to class options services.Configure&lt;ImageProcessingOptions&gt;( Configuration.GetSection(nameof(ImageProcessingOptions))); services.Configure&lt;ImageCaptureOptions&gt;( Configuration.GetSection(nameof(ImageCaptureOptions))); services.Configure&lt;ImageRepositoryOptions&gt;( Configuration.GetSection(nameof(ImageRepositoryOptions))); services.Configure&lt;TwilioOptions&gt;( Configuration.GetSection(nameof(TwilioOptions))); </code></pre> <p>We have some unique services being added to our dependency injection (DI) service collection. Later, our controllers can ask for these interfaces and expect the corresponding implementations. Likewise, we map over some sections from our <code>appsettings.json</code> configuration to C# classes. These also become available to us later from the perspective of DI. We can ask for <code>IOptions&lt;ImageProcessOptions&gt;</code> for example. See <a href="/posts/asp-net-core-configuration">ASP.NET Core - Configuration Tips</a> for more details.</p> <h4>ASP.NET Core Web API</h4> <p>Our application has only one controller with a few actions on it. We have an endpoint that returns various configuration options to the client app, and then a more interesting <code>api/image/generate</code> endpoint. If you're eager to learn more, I published an article with details on <a href="https://www.dotnetcurry.com/aspnet/1390/aspnet-core-web-api-attributes">ASP.NET Core Web API Attributes</a>.</p> <pre><code>[Route("api/image")] public class ImageController : Controller { [HttpGet, Route("options")] public IActionResult GetOptions( [FromServices] IImageProcessorService imageProcessor) =&gt; Json(imageProcessor.GetImageOptions()); [HttpPost, Route("generate")] public async Task&lt;IActionResult&gt; Generate( [FromBody] ImagesPostRequest imagesPostRequest, [FromServices] IImageProcessorService imageProcessor) =&gt; Json(await imageProcessor.ProcessImagesAsync( $"{Request.Scheme}://{Request.Host}{Request.PathBase}", imagesPostRequest)); } </code></pre> <blockquote> <p>&lt;cite&gt;<strong>ProTip</strong>&lt;/cite&gt; Keep your controllers dumb! It is best to delegate their logic to a service, and this simplifies testing.</p> </blockquote> <p>We ask for the <code>IImageProcessorService</code> implementation on the <code>api/image/options</code> action. This endpoint simply returns JSON that represents our combined configuration options from some of the various options classes we mapped earlier on startup. These options have information for the client app about animation frame delay, intervals, the number of photos to take, image height and width, etc.</p> <p>Snippet from <code>ImageProcessorService.GetOptions</code></p> <pre><code>public ImageOptionsResponse GetImageOptions() =&gt; new ImageOptionsResponse { AnimationFrameDelay = _processingOptions.FrameDelay, IntervalBetweenCountDown = _captureOptions.IntervalBetweenCountDown, PhotoCountDownDefault = _captureOptions.PhotoCountDownDefault, PhotosToTake = _captureOptions.PhotosToTake, ImageHeight = _processingOptions.ImageHeight, ImageWidth = _processingOptions.ImageWidth }; </code></pre> <p>The <code>api/image/generate</code> endpoint is the most involved. Again we ask for the image processor service, but this time we call the <code>ProcessImageAsync</code>. We are given an <code>ImagePostRequest</code> object, that looks like this:</p> <pre><code>public class ImagesPostRequest { public string Phone { get; set; } public List&lt;string&gt; Images { get; set; } } </code></pre> <p>We get the phone number and a list of images -- the images are base64 encoded representations of the photos taken. This is what we're going to process. This method could be broken up some and I'm certainly open to improvements (... I mean, this is open source -- give me a pull request)!</p> <h4>Services</h4> <p>First, we convert all the base64 image strings to <code>byte[]</code>. We load the first image from the <code>ImageSharp.Image.Load</code> method, then the remaining images are loaded and added as frames to the first image frames collection. The resulting image now has several frames and will be saved as a <code>.gif</code> file. It is then persisted using __   Azure Blob Storage__, within our <code>IImageRepository</code> implementation. Finally, we create a link to our image and text the user the URL with <code>Twilio</code>.</p> <p>Snippet from <code>ImageProcessorService.ProcessImageAsync</code></p> <pre><code>public async Task&lt;ImagesPostResponse&gt; ProcessImagesAsync( string baseUrl, ImagesPostRequest request) { try { var id = Guid.NewGuid().ToString(); var imageBytes = request.Images .Select(img =&gt; img.Replace(Base64PngImagePrefix, string.Empty)) .Select(Convert.FromBase64String) .ToArray(); var image = Image.Load(imageBytes[0]); image.MetaData.RepeatCount = 0; for (int i = 1; i &lt; imageBytes.Length; ++ i) { image.Frames.AddFrame(Image.Load(imageBytes[i]).Frames[0]); } // Ensure that all the frames have the same delay foreach (var frame in image.Frames) { frame.MetaData.FrameDelay = (int)(_processingOptions.FrameDelay * .1); } await UploadImageAsync(id, image); await _textMessagingService.SendTextAsync( $"+{request.Phone}", $"Share your photo from Cream City Code! {baseUrl}/images/{id}"); return new ImagesPostResponse { Id = id, IsSuccessful = true }; } catch (Exception ex) { ex.TryLogException(_logger); return new ImagesPostResponse { IsSuccessful = false, Error = ex.Message }; } } </code></pre> <p>Snippet from <code>ImageProcessorService.UploadImageAsync</code>, here we are naming (and saving) the file and encoding it with the <code>GifEncoder</code>. We then upload the image to our repository.</p> <pre><code>private async Task UploadImageAsync(string id, Image&lt;Rgba32&gt; image) { var fileName = $"./{id}.gif"; var profile = new ExifProfile(); profile.SetValue(ExifTag.Copyright, _processingOptions.Copyright); image.MetaData.ExifProfile = profile; image.Save(fileName, _encoder); await _imageRepository.UploadImageAsync(id, fileName); } </code></pre> <p>Snippet from <code>ImageRepository.UploadImageAsync</code>.</p> <pre><code>public async Task UploadImageAsync(string id, string filePath) { var container = await _initialization.Value; var blob = container.GetBlockBlobReference(id); await blob.UploadFromFileAsync(filePath); File.Delete(filePath); } </code></pre> <p>We <code>await</code> the <code>_initialization.Value</code> which represents the <code>async</code> operation to yield a container reference. It's an <code>AsyncLazy</code> that ensures the following:</p> <ul> <li>Create cloud blob client</li> <li>Create named <code>container</code> reference (if it doesn't already exist)</li> <li>Set permissions on <code>container</code> for blob types as public access</li> <li>Finally return the <code>container</code> instance</li> </ul> <p>The <code>container</code> instance is then used to get a block blob reference, to which we can upload our local image file. We'll delete the local version when we've uploaded it to <strong>Azure</strong>.</p> <p>The last piece of the puzzle is that we need to send a text message to the phone number we were given. Twilio makes this extremely easy, in fact when I was reading their documentation about their SDK -- I doubted that was all I needed.</p> <p>Snippet from <code>TextMessagingService.SendTextAsync</code>.</p> <pre><code>public async Task SendTextAsync(string toPhoneNumber, string body) { try { var message = await MessageResource.CreateAsync( to: toPhoneNumber, from: _twilioOptions.FromPhoneNumber, body: body); _logger.LogInformation($"Texted {toPhoneNumber}: {body}."); } catch (Exception ex) { ex.TryLogException(_logger); } } </code></pre> <h2>Angular</h2> <p>The <strong>Angular</strong> application is where a lot of the logic lives. It contains user interactions and workflow. Since it's <strong>Angular</strong> we'll look at some <strong>TypeScript</strong>. We have an <code>image.service.ts</code> that makes HTTP calls out to our Web API. At the time of writing, we had the following components:</p> <table> <thead> <tr> <th>Component</th> <th>Purpose</th> </tr> </thead> <tbody> <tr> <td><code>app</code></td> <td>Standard <strong>Angular</strong> application entry point</td> </tr> <tr> <td><code>audio</code></td> <td>Encapsulates the ability to bind <code>src</code> audio files and invoke <code>async play()</code> functionality</td> </tr> <tr> <td><code>camera</code></td> <td>Wraps the <code>&lt;control-wizard&gt;</code>, <code>&lt;video&gt;</code> and <code>&lt;canvas&gt;</code> elements, and orchestrates communications between them</td> </tr> <tr> <td><code>controlwizard</code></td> <td>This is the state machine of the overlay for the user workflow -- it toggles various templates into and out of view</td> </tr> <tr> <td><code>numberpad</code></td> <td>A numeric entry markup, which outputs the user input</td> </tr> </tbody> </table> <p>When application loads, we first hit the <code>api/image/options</code> endpoint -- getting our client settings from the server. See <a href="/posts/angular-2-http">Angular Http with RxJS Observables</a> for more details on the <code>HttpClient</code> from <strong>Angular</strong>. We then set our camera stream to the <code>video</code> element on our <code>CameraComponent</code>.</p> <p>Snippet from <code>CameraComponent.ngAfterViewInit</code>.</p> <pre><code>if (this.videoElement &amp;&amp; this.videoElement.nativeElement) { this.video = this.videoElement.nativeElement as HTMLVideoElement; if (this.video &amp;&amp; navigator.mediaDevices &amp;&amp; navigator.mediaDevices.getUserMedia) { navigator .mediaDevices .getUserMedia({ video: true }) .then((stream: MediaStream) =&gt; this.video.srcObject = stream); this.video.height = window.innerHeight; } } </code></pre> <p>The <code>videoElement</code> is an <code>ElementRef</code> instance -- our component uses the <strong>Angular</strong> <code>@ViewChild</code> decorator to instantiate our reference to the <code>&lt;video&gt;</code> element in our template. We assign the <code>.nativeElement</code> to our <code>video</code> instance which is an <code>HTMLVideoElement</code>. Finally, if our browser environment has the ability to <code>.getUserMedia</code> we'll ask for the video stream and assign it to our <code>video</code> instance. Next, let's explore what kicks things off.</p> <p>Snippet from <code>control-wizard.component.html</code>.</p> <pre><code>&lt;ccc-audio #startSound [src]="'https://www.soundjay.com/switch/sounds/switch-19.mp3'"&gt; &lt;/ccc-audio&gt; &lt;div *ngIf="isIdle"&gt; &lt;button (click)="start(startSound)"&gt; &lt;i class="glyphicon glyphicon-camera"&gt;&lt;/i&gt;&lt;br /&gt; Start &lt;/button&gt; &lt;/div&gt; </code></pre> <p>Our markup had a <code>ccc-audio</code> element, we have a reference to this with our <code>#</code> syntax -- we can then pass this as an argument to the <code>start</code> function on the button <code>(click)</code> handler. That function looks like this.</p> <p>Snippet from <code>ControlWizardComponent.start</code>.</p> <pre><code>public async start(sound: AudioComponent) { if (sound) { await sound.play(); } this.changeState(WizardState.CountingDown); this.resetCountDownTimer(); } </code></pre> <p>We play the sound, change the state of the application to <code>CountingDown</code> and then <code>resetCountDownTimer</code>. The <code>resetCountDownTimer</code> simply stops and then restarts the <code>NodeJS.Timer</code> instance in a clean manner. The <code>startCountDownTimer</code> method is called and it handles the count down and marshaling of photo capturing.</p> <p>Snippet from <code>ControlWizardComponent.startCountDownTimer</code>.</p> <pre><code>private startCountDownTimer(): void { this.countDownTimer = setInterval( () =&gt; { if (this.photosTaken &lt; this.imageOptions.photosToTake) { if (this.photoCountDown === 1) { this.photoCountDown = this.imageOptions.photoCountDownDefault + 1; this.changeState(WizardState.TakingPhoto); const details = { photoCount: this.photosTaken, interval: this.imageOptions.intervalBetweenCountDown }; this.takePhoto.emit(details); ++ this.photosTaken; } else { this.changeState(WizardState.CountingDown); -- this.photoCountDown; } } else { this.stopCountDownTimer(); this.images = []; for (var i = 0; i &lt; this.imageOptions.photosToTake; ++ i) { this.images.push(localStorage.getItem(`${i}.image.png`)); } this.startAnimationTimer(); this.changeState(WizardState.PresentingPhotos); this.photoCountDown = this.imageOptions.photoCountDownDefault; } }, this.imageOptions.intervalBetweenCountDown); } </code></pre> <p>The functionality here really just manages iteration counts and state changes. The areas of interest are the <code>takePhoto.emit</code>, this is an <code>EventEmitter</code> which serves as an <code>Output</code>. This means that other components can register to this event and handle the occurrence of the <code>.emit</code> invocation. This component has several outputs, again let's just single out the <code>takePhoto</code> one for now -- we will need to have a look at the <code>camera.component.html</code>.</p> <p>Snippet from <code>camera.component.html</code>.</p> <pre><code>&lt;div class="video-wrapper" [ngClass]="{ 'camera-flash': isTakingPhoto }"&gt; &lt;control-wizard (takePhoto)="onTakePhoto($event)" (stateChange)="onStateChanged($event)" (optionsReceived)="onOptionsReceived($event)"&gt;&lt;/control-wizard&gt; &lt;video (window:resize)="adjustVideoHeight($event)" class="black-glow" #video autoplay width="640" height="480" [ngClass]="{ 'hide': isPresentingPhotos || isTextingLink }"&gt;&lt;/video&gt; &lt;canvas #canvas id="canvas" width="640" height="480" style="display: none;"&gt;&lt;/canvas&gt; &lt;/div&gt; </code></pre> <p>From this we can see that the <code>CameraComponent</code> has an <code>onTakePhoto</code> handler. There are other observations to make as part of this, such as the markup itself -- how we're really orchestrating components that work (and communicate) together. The <code>control-wizard</code> notifies the <code>CameraComponent</code> about state changes and when options are received. When the <code>control-wizard</code> issues a <code>takePhoto</code> command the <code>camera</code> is responsible for taking a photo.</p> <p>Snippet from <code>CameraComponent.onTakePhoto</code>.</p> <pre><code>public onTakePhoto(details: PhotoDetails): void { setTimeout(() =&gt; { if (this.canvas) { const context = this.canvas.getContext('2d'); if (context) { context.drawImage(this.video, 0, 0, this.imageWidth, this.imageHeight); const url = this.canvas.toDataURL('image/png'); localStorage.setItem(`${details.photoCount}.image.png`, url); } } }, details.interval / 2); } </code></pre> <p>I relied on an old blog post from David Walsh - <a href="https://davidwalsh.name/browser-camera">Camera and Video Control with HTML5</a>, it was really helpful! We ask the <code>canvas</code> for a <code>2d</code> context and then <code>.drawImage</code> on the <code>context</code> passing our <code>video</code> element. We then ask the <code>canvas</code> for <code>.toDataUrl</code> -- which returns our base64 string representation of the image. We'll put this in <code>localStorage</code> for now.</p> <p>After the configured number of photos has been taken, we'll change the state of the application to <code>WizardState.PresentingPhotos</code>. Additionally, we grab all the images from <code>localStorage</code> storing them in an array.</p> <p>Snippet from <code>ControlWizard.startCountDownTimer</code>.</p> <pre><code>this.images = []; for (var i = 0; i &lt; this.imageOptions.photosToTake; ++ i) { this.images.push(localStorage.getItem(`${i}.image.png`)); } </code></pre> <p>The conditional markup for the presenting of the photos is simple.</p> <p>Snippet from <code>control-wizard.component.html</code>.</p> <pre><code>&lt;div *ngIf="isPresentingPhotos" class="card ccc-border black-glow"&gt; &lt;div class="col-1"&gt; &lt;img *ngIf="images.length" src="{{ images[animationIndex] }}" width="640" height="480" /&gt; &lt;/div&gt; &lt;div class="col-2 ccc-bg-cream big bold black-inset"&gt; &lt;div class="ccc-orange fs-38 black-glow"&gt; 2018 &lt;/div&gt; &lt;img height="360" class="black-glow" src="../../assets/ccc-logo.png" /&gt; &lt;div class="twitter-blue black-glow"&gt; #DeveloperCommunity &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; </code></pre> <p>I opted out of generating the <code>.gif</code> on the client side, so instead I simply animate by changing the <code>img.src</code> on a timer. Again, we'll properly generate a <code>.gif</code> on the server at a later time, but for now this will do just fine.</p> <p>Snippet from <code>ControlWizard.startAnimationTimer</code>.</p> <pre><code>private startAnimationTimer(): void { this.stopAnimationTimer(); this.animationTimer = setInterval(() =&gt; { const index = (this.animationIndex + 1); this.animationIndex = index &gt;= this.images.length ? 0 : index; }, this.imageOptions.animationFrameDelay); } </code></pre> <p>At this point, the user is presented with the sample animation. They could opt to "retake" the photos or if they're satisfied, they could "send" them. If the select "send" they are presented with the <code>number-pad</code> component, which enables them to type in their phone number -- and text a link to their phone for sharing!</p> <h2>Putting It All Together</h2> <p>Here is a look at the application in action.</p> <h2>Requirements</h2> <p>To run this locally you'll need a few things setup first. After pulling the bits from <strong>Microsoft's GitHub</strong> (never thought I get to say that), you need the following:</p> <ul> <li>  <strong><a href="https://azure.microsoft.com/en-us/services/storage/blobs/"> Azure Blob Storage</a></strong> -- Account / ConnectionString</li> <li>  <strong><a href="https://www.twilio.com/docs/sms/quickstart/csharp"> Twilio</a></strong> -- Developer Account ID / AuthToken / From Phone Number</li> </ul> <p>These values should be stored as environment variables with the following names:</p> <ul> <li><code>photoboothconnection</code></li> <li><code>twilioaccountsid</code></li> <li><code>twilioauthtoken</code></li> </ul> <p>Finally, feel free to toy around with the other configuration settings as you deem necessary.</p> David PineAngular HTTP Tips For Successhttps://davidpine.dev/posts/angular-http-gotchashttps://davidpine.dev/posts/angular-http-gotchasRxJS Map to the rescue!Mon, 25 Jun 2018 00:00:00 GMT<h2>Intro</h2> <p>I love working with the <strong>Angular</strong> <code>HttpClient</code>. It is easy to use and was designed to work with <strong>RxJS</strong>. It is vastly different from the <strong>AngularJS</strong> implementation, if you're curious I wrote about these differences <a href="/posts/angular-2-http">here</a>. However, there is one common issue that developers fall victim to. The issue really relates to <strong>TypeScript</strong> generics. I have also written about generics in <strong>TypeScript</strong> <a href="https://www.dotnetcurry.com/typescript/1439/typescript-generics">here</a>. But in this post, we will reveal how the issue can easily be avoided.</p> <h2>The problem</h2> <p>The problem is that the <code>HttpClient</code> class exposes generic methods that allow consumers to make assumptions, these assumptions are dangerous.</p> <blockquote> <p>&lt;cite&gt;<strong>ProTip</strong>&lt;/cite&gt; Never... make... assumptions!</p> </blockquote> <p>The assumption is that you can pass an <code>interface</code> with non-primitive types or a <code>class</code> as a generic type parameter, and that it will work as expected. This is simply <strong>not the case</strong>. Consider the following:</p> <pre><code>public getDetails(id: number): Promise&lt;Details&gt; { return this.http .get&lt;Details&gt;(`${this.baseUrl}/api/details/${id}`) .toPromise(); } </code></pre> <p>Most of the time we'd pass in an <code>interface</code> as the type parameter. Often this <em>seems</em> to work as the <code>interface</code> is a simple property bag of primitive types. The issue is that if the <code>interface</code> defines non-primitive types like a <code>Date</code> or a <code>Function</code> -- these will not be available at runtime. Likewise, if you pass in a <code>class</code> with <code>get</code> or <code>set</code> properties -- these too <strong>will not work</strong>! The specific problem is that the underlying implementation from <strong>Angular</strong> doesn't instantiate your object. Instead, it simply casts it as the given type. Ultimately, <strong>Angular</strong> is performing a <code>[JSON.parse](https://github.com/angular/angular/blob/master/packages/common/http/src/xhr.ts#L186-L189)</code> on the body of the response.</p> <p>Part of the issue is that <strong>TypeScript</strong> is blissfully unaware that <strong>Angular</strong> will not instantiate the object and treats the return as a <code>Promise&lt;Details&gt;</code>. As such flow analysis, statement completion, and all the other amazing features that the <strong>TypeScript</strong> language services provide to your development environment work. But this is misleading because you'll encounter runtime errors -- this is the issue that <strong>TypeScript</strong> aims to solve!</p> <h2>Working Example</h2> <p>An <code>interface</code> with primitive types will work just fine. The <code>JSON.parse</code> will give you an object and because of <strong>JavaScript</strong> coercion, it works. <strong>TypeScript</strong> will treat it as this object and everything is perfect.</p> <pre><code>export interface Details { score: number; description: string; approved: boolean; } </code></pre> <h2>Non-Working Example</h2> <p>Imagine that we want to return another property from the server, so we add a <code>Date</code> property. Notice how our <code>interface</code> added this new property. Now we want to do some date logic and use some of the methods on the <code>Date</code> instance -- this <strong>will not work</strong>!</p> <pre><code>export interface Details { date: Date; score: number; description: string; approved: boolean; } </code></pre> <p>The <code>details.date</code> property will exist, sure... but it <strong>will not</strong> be a <code>Date</code> instance -- instead it is simply a <code>string</code>. If you attempt to use any of the <code>string</code> methods, it will fail at runtime.</p> <p><a href="https://codepen.io/pen/pKZYbL">View on CodePen</a></p> <p>Ah, we can fix this -- right?! We might think to ourselves, we'll use a <code>class</code> instead and then add a <code>getDate()</code> "get function" property that will pass the <code>.date</code> member to the <code>Date</code> constructor. Let's look at this.</p> <pre><code>export class Details { date: Date; score: number; description: string; approved: boolean; get getDate(): Date { return new Date(this.date); } } </code></pre> <p>Perhaps to your surprise, this <strong>doesn't work</strong> either! The <code>Details</code> type parameter is not instantiated.</p> <p><a href="https://codepen.io/pen/QxBogQ">View on CodePen</a></p> <p>If we add a <code>constructor</code> to our <code>class</code> and then pass in a <code>data: any</code> argument, we could easily perform an <code>Object.assign(this, data)</code>. This solves several issues</p> <blockquote> <p>The <code>Object.assign()</code> method is used to copy the values of all enumerable own properties from one or more source objects to a target object. It will return the target object. &lt;cite&gt;<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign">MDN Web Docs</a>&lt;/cite&gt;</p> </blockquote> <p><a href="https://codepen.io/pen/mKjoKW">View on CodePen</a></p> <h2>JavaScript to the rescue</h2> <p>What's to stop a consumer from trying to interact with the <code>details.date</code> property -- if you recall it is still typed as a <code>Date</code>. This is error-prone and will cause issues -- if not immediately, certainly later on. Ideally, all objects that are intended to map over from JSON should contain primitive types only.</p> <p>If you're set on using a <code>class</code>, you should use the <strong>RxJS</strong> <code>map</code> operator.</p> <pre><code>public getDetails(id: number): Promise&lt;Details&gt; { return this.http .get&lt;Details&gt;(`${this.baseUrl}/api/details/${id}`) .map(response =&gt; new Details(response.json())) .toPromise(); } </code></pre> <p>But what if we wanted an array of details to come back -- that's easy too?!</p> <pre><code>public getDetails(): Promise&lt;Details[]&gt; { return this.http .get&lt;Details&gt;(`${this.baseUrl}/api/details`) .map(response =&gt; { const array = JSON.parse(response.json()) as any[]; const details = array.map(data =&gt; new Details(data)); return details; }) .toPromise(); } </code></pre> <h3>Types That Work Without Intervention</h3> <p>This table details all the primitive types that will map over without a <code>constructor</code> or any other intervention.</p> <table> <thead> <tr> <th>Primitive Types</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td><code>string</code></td> <td>Already a <code>string</code> anyways</td> </tr> <tr> <td><code>number</code></td> <td>Coercion from <code>string</code> to <code>number</code></td> </tr> <tr> <td><code>boolean</code></td> <td>Coercion from <code>string</code> to <code>boolean</code></td> </tr> <tr> <td><code>array</code></td> <td>As long as all types are primitives also</td> </tr> <tr> <td><code>tuple</code></td> <td>Follows same rules as <code>array</code></td> </tr> </tbody> </table> <h1>Conclusion</h1> <p>While <strong>TypeScript</strong> and <strong>Angular</strong> play nicely together, at the end of the day we're all battling <strong>JavaScript</strong>. As long as you're aware of how your tool, framework, or technology works and why it works a certain way -- you're doing great! Take this bit of knowledge and share it with the world. If it helps you, hopefully, it will help someone else too!</p> David PineWebAssemblyhttps://davidpine.dev/posts/webassembly-interviewhttps://davidpine.dev/posts/webassembly-interviewInterview with Steve Sanderson (Talking Blazor)Sun, 05 Aug 2018 00:00:00 GMT<p>I set out a while ago to try to interview various community leaders for their take on WebAssembly. If you're unfamiliar with WebAssembly, here's a definition for you.</p> <blockquote> <p>WebAssembly is a binary instruction format for a stack-based virtual machine &lt;cite&gt;<a href="https://webassembly.org/">webassembly.org</a>&lt;/cite&gt;</p> </blockquote> <p>&lt;br/&gt; That's a lot to try to parse, right?! I was able to get an interview with Steve Sanderson, which I'm going to share in this post. Steve is a pretty amazing individual and I'm grateful to have been able to interview him.</p> <p><a href="https://twitter.com/stevensanderson"><img src="./steve.jpg" alt="Steve Sanderson" /></a></p> <p>Additionally, one of the other's that I reached out to was Scott Hanselman. Scott offered up something really special, rather than replying to the questions - he gave me an <em>unpublished interview</em> (that has since been published) that he did with Steve Sanderson on his <a href="https://hanselminutes.com/642/blazor-brings-net-to-web-assembly-with-steve-sanderson">hanselminutes podcast</a>!</p> <hr /> <h2>Interview</h2> <p>So, here is the interview itself. Rather short and sweet - a meager six questions but I'm grateful to share it with you all.</p> <h3>Question 1</h3> <blockquote> <p>WebAssembly - is it just hype or do you believe this is the future of web development?</p> </blockquote> <p>&lt;br/&gt; While this question is rather broad, it was my hope to gauge the true overarching value of this web technology. I really like Steve's answer.</p> <blockquote> <p>I hope that the future of web development includes giving developers a wider choice of languages and platforms.&lt;br/&gt;There's nothing wrong with JavaScript, but our industry would be healthier if multiple paradigms and specializations were better catered for. &lt;cite&gt;<a href="https://twitter.com/stevensanderson">Steve Sanderson</a>&lt;/cite&gt;</p> </blockquote> <p>&lt;br/&gt; This response is really powerful, he's absolutely right. We as developer's should have the choice of technology that will make us happy, but more importantly - we should have more options so that we are empowered to pick the right tool for the job.</p> <h3>Question 2</h3> <blockquote> <p>Why is Blazor labeled an "experiment", there seems to be a significant effort in developing it?</p> </blockquote> <p>&lt;br/&gt; Part of the motivation behind this question was to dig a little more. Blazor has been active development now for eight months, and has a lot of attention from the developer community.</p> <p>I think Steve's response was rather generic and I still believe that this will become a full-blown product!</p> <blockquote> <p>Because it <em>is</em> an experiment, in the sense that it's not a committed product. We are putting significant effort into working out whether it's a product we should build. &lt;cite&gt;<a href="https://twitter.com/stevensanderson">Steve Sanderson</a>&lt;/cite&gt;</p> </blockquote> <p>&lt;br/&gt; I'll leave it to you, does this look like an "experiment"?</p> <p>&lt;a href="https://github.com/aspnet/Blazor" target="_blank"&gt; &lt;img src="/img/2018/08/blazor-github.png" /&gt; &lt;/a&gt;</p> <h3>Question 3</h3> <blockquote> <p>Should developers be learning about WebAssembly today and considering it for future projects as an alternative to existing technologies?</p> </blockquote> <p>&lt;br/&gt; With this question I wanted to try to understand where web developers should be focusing their efforts. There is so many new things that come and go, kudos to Microsoft for taking that problem seriously and evaluating Blazor. Here is what Steve had to say about this.</p> <blockquote> <p>I would be very surprised if JavaScript isn't involved in web development for as long as the web exists as an application platform. It may become analogous to Bash scripts: ubiquitous, can do everything in theory, and excellent for gluing together other technologies. &lt;cite&gt;<a href="https://twitter.com/stevensanderson">Steve Sanderson</a>&lt;/cite&gt;</p> </blockquote> <p>&lt;br/&gt;</p> <h3>Question 4</h3> <blockquote> <p>Silverlight is dead, WebForms is still dying and Razor Pages (and MVC) are fairly prevalent. SPA frameworks like Angular and React are pushing business logic client-side, WebAssembly would make this easier too. Is Blazor an attempt to compete with SPA frameworks?</p> </blockquote> <p>There are lots of web development technologies out there today, what makes blazor special - right?</p> <blockquote> <p>Blazor is an attempt to move the SPA world forwards into an era where developers have more freedom to choose other languages and platforms. &lt;cite&gt;<a href="https://twitter.com/stevensanderson">Steve Sanderson</a>&lt;/cite&gt;</p> </blockquote> <p>&lt;br/&gt; There you have it, blazor is a SPA framework giving us the magic of C# in the browser.</p> <p>&lt;img src="/img/2018/08/magic.png" /&gt;</p> <h3>Question 5</h3> <blockquote> <p>If you could change one thing about WebAssembly, what would you change and why?</p> </blockquote> <p>&lt;br/&gt; I love these kinds of questions, it gives us a look into potential shortcomings of things we've yet to invest much time in.</p> <blockquote> <p>I'd like true multithreading, because lots of languages and platforms assume that exists. Fortunately it is on the spec roadmap. &lt;cite&gt;<a href="https://twitter.com/stevensanderson">Steve Sanderson</a>&lt;/cite&gt;</p> </blockquote> <p>&lt;br/&gt;</p> <h3>Question 6</h3> <blockquote> <p>WebAssembly performance seems almost too good to be true, is it overrated - why or why not?</p> </blockquote> <p>&lt;br/&gt; There are indications of its performance being "near native" and some say that, but what does it really mean? Steve explains that pretty well.</p> <blockquote> <p>I don't find it surprising or too good to be true. We have known for a long time that a high-level bytecode plus modern JIT allows performance close to plain machine code. WebAssembly is a JITing platform so it should perform similarly. &lt;cite&gt;<a href="https://twitter.com/stevensanderson">Steve Sanderson</a>&lt;/cite&gt;</p> </blockquote> <p>&lt;br/&gt;</p> <h2>Conclusion</h2> <p>WebAssembly is a pretty amazing bit of technology. Microsoft's vision of bring C# everywhere is coming to fruition with the help of WebAssembly. I'm thankful to be a web developer in this day and age, as I'll be given the opportunity to take my favorite programming language to the web!</p> David PineWriting a Blazor Apphttps://davidpine.dev/posts/blazing-chuckhttps://davidpine.dev/posts/blazing-chuckA Practical WebAssembly Application In C#Mon, 20 Aug 2018 00:00:00 GMT<p>Every time a developer encounters a new technology it's in our nature to explore it. This is the case with WebAssembly, and Microsoft's vision of the world in Blazor. Blazor is single page application framework that sits atop of WebAssembly, but it's still considered an experiment. I had the chance to interview Steve Sanderson about WebAssembly and Blazor -- I shared <a href="/posts/webassembly-interview">that post earlier this month</a>. Now, I'd like to explore Blazor with you a bit more.</p> <h1>Blazor</h1> <p>There are plenty of resources for learning Blazor, here are two of my favorite:</p> <ul> <li><a href="https://blazor.net/">blazor.net</a></li> <li><a href="https://learn-blazor.com/">learn-blazor.com</a></li> </ul> <h1>A Practical Application</h1> <p>Are you tired of the "TODO" application, I know I am?! I decided to go beyond the typical demonstration apps from the templates and the "TODO" apps that are so prevalent today - instead, we'll write an application that will hopefully put a smile on your face! I call it "Blazing Chuck"...</p> <p>I have been using <a href="https://www.icndb.com">The Internet Chuck Norris Database</a> in my demos for as long as I can remember. Who doesn't love a good <em>nerdy</em> joke? We will make some calls using the <code>HttpClient</code> to <strong>GET</strong> a nerdy Chuck Norris based joke - like I said before, extremely practical application .</p> <p>If you'd rather have a look at the GitHub project yourself, <a href="https://github.com/IEvangelist/IEvangelist.Blazing.Chuck">check it out here</a>. Please give me a star, or fork it, or send along a pull request to help me make this example project even better.</p> <h1>Boring Parts</h1> <h2><code>Program.cs</code></h2> <p>Our Blazor applications entry point is the <code>Main</code> method of the <code>Program.cs</code>. Here we simply create our host builder, using the Blazor <code>Startup</code> class and run.</p> <pre><code>using Microsoft.AspNetCore.Blazor.Hosting; namespace IEvangelist.Blazing.Chuck { public class Program { public static void Main(string[] args) =&gt; CreateHostBuilder(args).Build().Run(); public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) =&gt; BlazorWebAssemblyHost.CreateDefaultBuilder() .UseBlazorStartup&lt;Startup&gt;(); } } </code></pre> <h2><code>Startup.cs</code></h2> <p>Our startup logic should look very familiar to those who have been developing with <strong>ASP.NET Core</strong> as it uses the same nomenclature we've all grown accustomed to. We have a <code>ConfigureServices</code> where we add services to our <code>IServiceCollection</code> instance for our application. We will need to add CORS, for cross-origin resource sharing. These services can be used later by dependency injection. We also have our <code>Configure</code> method, which allows us to add various things to our application. In this specific case we add the <code>App</code> Razor page.</p> <pre><code>using Microsoft.AspNetCore.Blazor.Builder; using Microsoft.Extensions.DependencyInjection; namespace IEvangelist.Blazing.Chuck { public class Startup { public void ConfigureServices(IServiceCollection services) =&gt; services.AddCors( options =&gt; options.AddDefaultPolicy( builder =&gt; builder.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials())); public void Configure(IBlazorApplicationBuilder app) =&gt; app.AddComponent&lt;App&gt;(nameof(app)); } } </code></pre> <h1>Less Boring Parts</h1> <h2><code>wwwroot/index.html</code></h2> <p>Let's take a look at this simple markup file. The <code>&lt;head&gt;</code> element doesn't contain anything special or unusual, let's checkout the <code>&lt;body&gt;</code>. We have a link to my <a href="https://github.com/IEvangelist/IEvangelist.Blazing.Chuck">GitHub project</a>, which sits in the top right hand corner of the screen.</p> <pre><code>&lt;!DOCTYPE html&gt; &lt;html&gt; &lt;head&gt; &lt;meta charset="utf-8" /&gt; &lt;meta name="viewport" content="width=device-width"&gt; &lt;title&gt;IEvangelist.Blazing.Chuck&lt;/title&gt; &lt;base href="/" /&gt; &lt;link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" /&gt; &lt;link href="css/site.css" rel="stylesheet" /&gt; &lt;/head&gt; &lt;body&gt; &lt;a href="https://github.com/IEvangelist/IEvangelist.Blazing.Chuck" target="_blank"&gt; &lt;img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub"&gt; &lt;/a&gt; &lt;app class="grid"&gt; &lt;div class="grid-content"&gt; &lt;div class="grid-center"&gt; &lt;div class="loading"&gt; &lt;h1&gt;Loading...&lt;/h1&gt; &lt;h1&gt;Please wait.&lt;/h1&gt; &lt;div class="spinner"&gt; &lt;div class="rect1"&gt;&lt;/div&gt; &lt;div class="rect2"&gt;&lt;/div&gt; &lt;div class="rect3"&gt;&lt;/div&gt; &lt;div class="rect4"&gt;&lt;/div&gt; &lt;div class="rect5"&gt;&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/app&gt; &lt;script src="_framework/blazor.webassembly.js"&gt;&lt;/script&gt; &lt;/body&gt; &lt;/html&gt; </code></pre> <p>Then we have a familiar <code>&lt;app&gt;</code> element -- while it is a non-standard element it has become common nomenclature for single page applications. Think of it as a target for where your SPA aims to render! The contents are irrelevant, right?! That is not entirely true...you've probably become accustomed to SPA applications initially displaying a "Loading..." screen that is simply black text on a white background. This is a bit boring, no?!</p> <p>I've made mine more flashy and consistent with the rest of the application.</p> <blockquote> <p>&lt;cite&gt;<strong>ProTip</strong>&lt;/cite&gt; Your application is represented on the initial request, make the most of it!</p> </blockquote> <p>Here is what my loading application looks like:</p> <h1>Intriguing Parts</h1> <h3><code>Index.razor</code></h3> <p>This file is getting larger, and it's probably time that I break it into smaller components. I say that realizing that it is only 46 lines long, but still -- having mixed some <strong>C#</strong> into some <strong>HTML</strong> doesn't really feel right to me. Perhaps I'm a bit biased, but I'm arguably one of the <strong>C#</strong> languages biggest fans. I've been conflicted about this for a while, and I've felt the same when I see templating markup in <strong>Angular</strong> and even more so with <strong>React's JSX</strong>. Here it is, in its entirety.</p> <pre><code>@page "/" @inject HttpClient Http &lt;div class="grid-center"&gt; &lt;img id="chuck" class="center glow @((_isLoading ? "shaking" : ""))" src="/img/chuck-norris.png" /&gt; &lt;/div&gt; &lt;div class="grid-joke"&gt; &lt;div class="alert alert-danger" role="alert"&gt; @if (_isLoading) { &lt;h1 class="hg-font"&gt;...&lt;/h1&gt; } else { &lt;h1 class="hg-font"&gt;@_joke.value.joke&lt;/h1&gt; } &lt;/div&gt; &lt;/div&gt; &lt;div class="grid-btn"&gt; &lt;button type="button" disabled="@_disabled" class="btn btn-warning btn-lg hg-font fixed-button" onclick="@GetJokeAsync"&gt; Kick Chuck?! &lt;/button&gt; &lt;div class="thanks"&gt; Special thanks to our friends at &lt;a href="http://www.icndb.com/" target="_blank"&gt;ICNDB&lt;/a&gt; and &lt;a href="http://tobiasahlin.com/spinkit/" target="_blank"&gt;SpinKit by Tobias Ahlin&lt;/a&gt;! &lt;/div&gt; &lt;div class=""&gt; &lt;code&gt; &lt;pre style="color: azure !important;"&gt;Debug: Shift + Alt + D&lt;/pre&gt; &lt;/code&gt; &lt;/div&gt; &lt;/div&gt; @functions { Result _joke; bool _isLoading = true; string _disabled =&gt; _isLoading ? "disabled" : null; protected override Task OnInitAsync() =&gt; GetJokeAsync(); private async Task GetJokeAsync() { _isLoading = true; _joke = await Http.GetJsonAsync&lt;Result&gt;( "https://api.icndb.com/jokes/random?limitTo=[nerdy]"); _isLoading = false; } } </code></pre> <p>I want to break up this explanation a bit, we'll start with the directives.</p> <h2>Directives</h2> <h3><code>@page</code></h3> <p>This works with the Blazor router, and specifies the client side route. Our route for the index page is simply <code>"/"</code>. We could have multiple routes if we wanted but our application is rather simple.</p> <h3><code>@inject</code></h3> <p>Next, the <code>@inject</code> directive. This directive instructs our dependency injection framework to "inject" the desired type as an instance into our component. We then have access to it wherever we'd like to consume it.</p> <h3><code>@functions</code></h3> <p>Functions can exist anywhere in the <code>*.razor</code> file, however it is most common and best practice to place them at the bottom of the file. These "functions" will serve as the <strong>C#</strong> source code for the component. In our example we <code>override</code> the <code>Task</code> returning <code>OnInitAsync</code> method. This is one of several <a href="https://learn-blazor.com/pages/lifecycle-methods/">lifecycle methods</a>. We express our <code>override</code> as the invocation of the <code>GetJokeAsync</code> method. The <code>GetJokeAsync</code> method toggles the <code>_isLoading</code> flag, and makes an <code>async</code> call to the "Internet Chuck Norris Database" API. We use the <code>HttpClient</code> that has been injected into our component instance to call the <code>GetJsonAsync</code> function given the URL and type-parameter of our <code>Result</code> object. The <code>Result</code> object contains the <strong>C#</strong> representation of the returned JSON result, which has the joke.</p> <h1>Markup</h1> <p>The markup is very simple. There is nothing exciting here, a few simple examples of how to use various binding approaches. We can conditionally bind a <code>class</code> on an <code>img</code> element for example, this is demonstrated when we're requesting a joke. Likewise, we can conditionally show an ellipsis when we're loading else we will display the resulting punch-line from our API result.</p> <p>We bind to the <code>onclick</code> of our "Kick Chuck" <code>button</code> element. When this element is clicked, the framework will invoke our <code>GetJokeAsync</code> function. We then fetch a new joke, toggle the <code>_isLoading</code> bit and then render the updates to the markup. One thing that you may notice is that the actual <code>html</code> that is sent to your browser doesn't have an event handler for the button click - what is the magic you speak of?!</p> <p>This is where our <code>&lt;script src="_framework/blazor.webassembly.js"&gt;&lt;/script&gt;</code> comes in. There is a bit of framework magic that attaches event listeners, etc. This bit of <strong>JavaScript</strong> manages event delegation, attaching, removing, as well as a slue of other various DOM relates interactions that tether our <strong>.NET</strong> components to their <strong>WebAssembly</strong> counterparts.</p> <p>For more details on how this is implemented, please have a look at the <a href="https://github.com/aspnet/Blazor/tree/master/src/Microsoft.AspNetCore.Blazor.Browser.JS/src">Microsoft.AspNetCore.Blazor.Browser.JS project</a>.</p> <h3>A Word On Interpreted Mode</h3> <p>When developing your Blazor applications, you'll be using "interpreted mode". Interpreted mode will take your <code>*.dlls</code> on the client, and dynamically load then via the Mono Runtime. The Mono Runtime is compiled to WebAssembly, and represented by the <code>mono.wasm</code> file. This entire process, is actually really fast!</p> <h2>Debugging C# In Chrome DevTools</h2> <p>Debugging is only available within Chrome and is very limited.</p> <p>&lt;div class="iframe_container"&gt; &lt;iframe src="https://www.youtube.com/embed/_mjCddc21Eo" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt; &lt;/div&gt;</p> <p>&lt;br /&gt;</p> <h1>"Blazing Chuck!"</h1> <p>Here it is, the moment you've been waiting for. Putting all the pieces together, and you have "Blazing Chuck". Go forth, and <a href="https://ievangelistblazingchuck.azurewebsites.net/">kick chuck</a>. Disclaimer, this actually doesn't work well (or at all) on mobile -- for best results explore on a desktop... more to come once I figure out the issue with that.</p> <h1>Thanks</h1> <p>Special thanks to our friends at <a href="https://www.icndb.com/">ICNDB</a> for their amazingly hilarious and free API. Also, <a href="https://tobiasahlin.com/spinkit/">SpinKit by Tobias Ahlin</a> for the simple HTML and CSS loading indicator!</p> David PineDMP in 3: RxJS Debouncehttps://davidpine.dev/posts/dmp-in-three-debouncehttps://davidpine.dev/posts/dmp-in-three-debounceThree minute solution for delayed filtering to avoid performance implicationsSun, 11 Nov 2018 00:00:00 GMT<p>import { YouTube } from '@astro-community/astro-embed-youtube';</p> <p>I recently decided to give creating videos a test drive. The problem with creating video content is the fact that it's really <em>time consuming</em> to do, but the time investment can be worth the effort if you're able to truly deliver some sort of value. Another problem we're faced with in the technical community is that videos can be very lengthy. Enter <strong>DMP in 3</strong>, a video series of 3 minute videos. The idea is to identify a common programming problem, demonstrate a common solution with explanation and then quickly review the entire scenario.</p> <p>I've managed to put my first episode together, and I have so much more respect for those video content developers in our community. This was a HUGE effort and I'm still not fully satisfied with it, but I will improve with practice. If you have ideas for something you want covered - please leave a comment below.</p> <h1>Episode 1</h1> <p>In this first episode we cover a scenario where we're working a large set of data. We need to implement a filter feature for the end user. The issue is that when filtering a large data set, the user will experience performance implications as each char they input performing the filtering logic. To solve this we use the <strong>RxJS</strong> <code>debounce</code> operator.</p> <h3>Video</h3> <p>&lt;YouTube id="https://youtu.be/z-kYoR1cqSM" /&gt;</p> <ul> <li><a href="https://github.com/IEvangelist/dmp-in-three-debounce">GitHub Source Code</a></li> </ul> David PineC# All The Thingshttps://davidpine.dev/posts/csharp-all-the-thingshttps://davidpine.dev/posts/csharp-all-the-things.NET Core Global Tools. You don't need glasses to C#.Sun, 16 Dec 2018 00:00:00 GMT<p>import { Tweet } from '@astro-community/astro-embed-twitter';</p> <blockquote> <p>I'm proud to share that this post is part of the C# Advent Calendar and it's my second year contributing to it! I encourage you to check out all the others <a href="https://crosscuttingconcerns.com/The-Second-Annual-C-Advent">here</a>.</p> </blockquote> <p>&lt;br /&gt;</p> <h1>Developers Are Lazy</h1> <p>In the world of web development it is hard to escape certain tools that we are forced to rely on. As developers we're innately lazy and it is safe to say that perhaps we don't really care enough to look into other tooling options. Let's face it... you're lazy, and I'm lazy and that is all there is to it. <strong>.NET Core</strong> offers <a href="https://aka.ms/global-tools">global tooling</a>, much like <strong>NPM</strong> you can use these tools for various build automation tasks. It's worth exploring <strong>.NET Core Global Tooling</strong> to see how it might enable us to be even more lazy!</p> <p>In this post I will cover how I have utilized the <strong>.NET Core Global Tooling</strong> feature to my advantage, and how you as a developer can easily consume it.</p> <h2>Let's Use It Already</h2> <p>I was recently working on a small and very simple web project. It served up some light-weight HTML, CSS and JavaScript. I thought to myself "it would be useful to minify these files", but in order to do so I would have relied on <strong>NPM</strong> and I simply didn't want to. Instead, I wrote a <strong>.NET Core Global Tool</strong>. It's named <a href="https://github.com/IEvangelist/IEvangelist.DotNet.Miglifier">Miglifier</a>, well because naming things is hard and it does two things..."minifies" and "uglifies" files, thus it "miglifies" them 😅. It is extremely simple to consume.</p> <p>From the <code>.NET CLI</code> execute the following command to install the latest version.</p> <pre><code>dotnet tool install --global dotnet-miglifier </code></pre> <p>This will install "miglifier" globally. You can then use it by simply invoking the following command and passing in the <code>"wwwroot"</code> directory. The <code>"wwwroot"</code> directory argument instructs "miglifier" where to scan for files.</p> <blockquote> <p>&lt;cite&gt;<strong>ProTip</strong>&lt;/cite&gt; You do not actually need to invoke <code>dotnet miglifier</code>, you can simply say <code>miglifier</code> and omit the <code>dotnet</code> from the command.</p> </blockquote> <p>Each file that is found will be "miglified" and then written back to disk.</p> <pre><code>miglifier "wwwroot" Scanning for CSS, JavaScript and HTML files to process. Processing 1 Css file(s). .\wwwroot\main.min.css Processing 1 Js file(s). .\wwwroot\main.min.js .\wwwroot\utilities.min.js Processing 1 Html file(s). .\wwwroot\main.min.html Successfully miglified 3 files! </code></pre> <p>It is limited in its functionality at this point. The ability to configure how the "miglifier" will operate is supported through a <code>miglify.json</code> file. It supports globing for input directories. It allows each type to have their own output directory. All the settings for the CSS, HTML and JavaScript files are serialized versions of their C# equivalent. As such, you can control it anyway you see fit...that is to say so long as it confines to the <a href="https://github.com/xoofx/NUglify"><code>NUglify</code></a> package. By default, it simply works as an in-place minification of CSS, HTML and JavaScript files - for now (more to come soon)! This results in a <code>*.min.*</code> file of the corresponding file type. It maintains the same folder hierarchy and will leave files where they were.</p> <h2>Building the .NET Core Tool</h2> <p>I relied heavily on the documentation from the official <strong>Microsoft</strong> site on <a href="https://docs.microsoft.com/en-us/dotnet/core/tools/global-tools-how-to-create">How to Create a .NET Core Global Tool</a>. I was thoroughly inspired by my good friend Andrew Lock and <a href="https://andrewlock.net/creating-a-net-core-global-cli-tool-for-squashing-images-with-the-tinypng-api/">his awesome post on the subject here</a>. We begin our process with a simple command line template from within the <code>File &gt; New Project</code> menu option of <strong>Visual Studio</strong>.</p> <p>Then we'll need to edit the <code>.csproj</code> file and manually set <code>&lt;IsPackable&gt;true&lt;/IsPackable&gt;</code> and <code>&lt;PackAsTool&gt;true&lt;/PackAsTool&gt;</code>. This enables your command line app to be treated as a <strong>.NET Core Global Tool</strong>. Note that we've given it a title, command line and assembly name - all for good measure.</p> <pre><code>&lt;PropertyGroup&gt; &lt;OutputType&gt;Exe&lt;/OutputType&gt; &lt;IsPackable&gt;true&lt;/IsPackable&gt; &lt;PackAsTool&gt;true&lt;/PackAsTool&gt; &lt;LangVersion&gt;latest&lt;/LangVersion&gt; &lt;Title&gt;dotnet-miglifier&lt;/Title&gt; &lt;ToolCommandName&gt;dotnet-miglifier&lt;/ToolCommandName&gt; &lt;AssemblyName&gt;dotnet-miglifier&lt;/AssemblyName&gt; &lt;/PropertyGroup&gt; </code></pre> <h1>C# Bits 😎</h1> <p>Make no mistake, this little command line application is packed full of some awesome <strong>C#</strong> bits! It starts out as a McMaster command line utilitarian app - <a href="https://github.com/natemcmaster/CommandLineUtils">details here</a>. It is powered by the <strong>.NET</strong> library for globing, <a href="https://github.com/kthompson/glob/">Glob</a> - this is what enables our config file to have input patterns like <code>"**/*.js"</code>. I wrote a few extension methods for mapping my <strong>C#</strong> <code>MiglifySettings</code> class object to <strong>JSON</strong>. Let's start there... here is the settings class, it is rather simple.</p> <pre><code>public class MiglifySettings { public IDictionary&lt;MiglifyType, DirectorySettings&gt; Globs { get; set; } = new Dictionary&lt;MiglifyType, DirectorySettings&gt; { [MiglifyType.Css] = new DirectorySettings { Input = "**/*.css" }, [MiglifyType.Html] = new DirectorySettings { Input = "**/*.html" }, [MiglifyType.Js] = new DirectorySettings { Input = "**/*.js" } }; public CodeSettings JavaScriptSettings { get; set; } = new CodeSettings(); public CssSettings CssSettings { get; set; } = new CssSettings(); public HtmlSettings HtmlSettings { get; set; } = new HtmlSettings(); } public class DirectorySettings { public string Input { get; set; } public string Output { get; set; } } </code></pre> <p>One cool bit of <strong>C#</strong> to call attention to here is the usage of collection initializers with our dictionary instantiation. Note how we can directly assign into an index. This was introduced as part of <strong>C# 6</strong> to make initialization more consistent with common usage. For <code>Dictionary&lt;TKey, TValue&gt;</code> it is common to add or update values via the indexer. Here is the extension methods I mentioned that I'm using to serialize this class to and from <strong>JSON</strong>.</p> <pre><code>public static class StringExtensions { public static T To&lt;T&gt;(this string json) =&gt; JsonConvert.DeserializeObject&lt;T&gt;(json, JsonSettings.SerializerDefaults); } public static class ObjectExtensions { public static string ToJson&lt;T&gt;(this T instance) =&gt; JsonConvert.SerializeObject(instance, JsonSettings.SerializerDefaults); } </code></pre> <p>With these two extension methods we can easily walk up to an instance of the <code>MiglifySettings</code> class, serialize it to a <strong>JSON</strong> <code>string</code> and vice versa -- it is pretty powerful. In case you're wondering where the <code>JsonSettings.SerializerDefaults</code> comes from, it is a simple helper class with the default settings that I prefer for serializing <strong>JSON</strong>. Here they are...</p> <pre><code>static class JsonSettings { static readonly DefaultContractResolver _contractResolver = new DefaultContractResolver { NamingStrategy = new CamelCaseNamingStrategy() }; internal static JsonSerializerSettings SerializerDefaults { get; } = new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, ContractResolver = _contractResolver, Formatting = Formatting.Indented, NullValueHandling = NullValueHandling.Ignore }; } </code></pre> <p>Now, in the spirit of <strong>C#</strong> I'd like to share something rather special with you. Consider this my Christmas 🎁 to you as part of the <strong>C# Advent Calendar</strong>...deconstruction 😈! I posted about <a href="/posts/exploring-csharp-seven"><strong>C# 7</strong> when it was first released</a> and while this is a <strong>C# 7</strong> feature it is a little different. With the introduction of <em>tuples</em> the language also gave us deconstruction of <em>tuples</em>. In addition to deconstructing <em>tuples</em> we can actually deconstruct anything that has a <code>public void Deconstruct([out T value])</code> method, where the <code>out</code> parameters serve as the ordinals to deconstructing a target tuple type. In other words, we can deconstruct non-tuple types into tuples. Let's ...</p> <p>In the miglifier app we accept two arguments.</p> <ol> <li>The <code>Path</code> argument is the root directory in which we'll scan for files</li> <li>The <code>MiglifyJsonPath (optional)</code> argument is the path to the <code>miglify.json</code> file</li> </ol> <p>This configuration file is deserialized and passed into the <code>GetMiglifiedFiles</code> function. This uses glob patterns to search for all the target files to work on processing them along the way. Eventually it will return the results -- the results are of type <code>IEnumerable&lt;MiglifyFile&gt;</code>. We group the results and then convert them to a <code>Dictionary&lt;MiglifyType, List&lt;MiglifyFile&gt;&gt;</code> and <code>foreach</code> over our collection. Here is the beauty of it... we can deconstruct the <code>KeyValuePair&lt;MiglifyType, List&lt;MiglifyFile&gt;&gt;</code>!</p> <pre><code>var settings = await LoadMiglifySettingsAsync(miglifyJson); var results = GetMiglifiedFiles(wwwroot, settings); // Deconstruct our key value pairs into type and files. foreach (var (type, files) in results.GroupBy(f =&gt; f.Type) .ToDictionary(grp =&gt; grp.Key, grp =&gt; grp.ToList())) { // Iterate our files foreach (var file in files) { // Do other stuff here... } } </code></pre> <p>When I first learned about this, I was so excited to tweet about it!</p> <p>&lt;Tweet id="https://twitter.com/davidpine7/status/1032709720505831425" /&gt;</p> <h2>Credit 🙏🏼</h2> <p>I would like to call attention to the 3rd party dependencies that helped make this <strong>.NET Core Global Tool</strong> possible. Without them, it would have been a much larger undertaking. Here are all list of packages that I'm using.</p> <pre><code>&lt;PackageReference Include="Centare.Extensions" Version="1.0.19" /&gt; &lt;PackageReference Include="Glob" Version="0.4.0" /&gt; &lt;PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="2.2.5" /&gt; &lt;PackageReference Include="Humanizer.Core" Version="2.5.1" /&gt; &lt;PackageReference Include="Newtonsoft.Json" Version="11.0.2" /&gt; &lt;PackageReference Include="NUglify" Version="1.5.12" /&gt; </code></pre> <p>The <a href="https://github.com/centare/Centare.Extensions/wiki#Usage"><strong>Centare.Extensions</strong></a> is an amazing package, with tons of great extension methods. There is one in particular that does the bulk of the work, that is the <a href="https://github.com/xoofx/NUglify"><code>NUglify</code></a> package. There is an immense background to this project that is documented <a href="https://github.com/xoofx/NUglify/blob/master/doc/readme.md">here in their very detailed README.md</a>. Know that, without this package the "miglifier" would probably not exist for you today!</p> <h1>Badges 🤘🏼</h1> <p>The <strong>NuGet</strong> &amp; <strong>Azure DevOps</strong> badges are only part of the story, by all means please check out the project yourself <a href="https://github.com/IEvangelist/IEvangelist.DotNet.Miglifier">IEvangelist.DotNet.Miglifier</a>! I look forward to pull-requests, feature requests, or issues. Happy coding, friends...</p> <p>&lt;a href="https://dev.azure.com/davidpine/IEvangelist.DotNet.Miglifier/_build/latest?definitionId=6" rel="nofollow" target="_blank"&gt; &lt;img src="https://dev.azure.com/davidpine/IEvangelist.DotNet.Miglifier/_apis/build/status/IEvangelist.DotNet.Miglifier%20.NET%20Core-CI" alt="Build status" data-canonical-src="https://dev.azure.com/davidpine/IEvangelist.DotNet.Miglifier/_apis/build/status/IEvangelist.DotNet.Miglifier%20.NET%20Core-CI" style="max-width:100%;" /&gt; &lt;/a&gt; &lt;a href="https://www.nuget.org/packages/dotnet-miglifier/" rel="nofollow" target="_blank"&gt; &lt;img src="https://img.shields.io/nuget/v/dotnet-miglifier.svg" alt="NuGet version (dotnet-miglifier)" data-canonical-src="https://img.shields.io/nuget/v/dotnet-miglifier.svg?style=flat-square" style="max-width:100%;" /&gt; &lt;/a&gt;</p> David PineAngular - The Color Guessing Gamehttps://davidpine.dev/posts/angular-color-gamehttps://davidpine.dev/posts/angular-color-gameHashtag... #WinningThu, 14 Mar 2019 00:00:00 GMT<h1>The Story Behind The Game</h1> <p>I have three sons. Lyric who is six and a half, Londyn who is four and half, and Lennyx who is two and half. As you might imagine, they seldom agree on things. For example when it's family move night, "which movie we'll watch?", or "who gets to go first?" when playing a game. These <em>important life decisions</em> are often decided by playing the <em>color guessing game</em>. My wife or I will choose a color at random and have the boys guess a color until someone guesses the correct color. Whoever guesses correctly is the <strong>winner</strong> and they get to choose the movie or go first, etc. You get the point!</p> <p>In this post we'll discuss how to create an <strong>Angular</strong> application using the CLI, Material Design and Animations. As a spoiler alert, that application is <a href="https://bit.ly/kids-color-game">hosted here</a> and the source code is up on <a href="https://github.com/IEvangelist/color-game">GitHub here</a>.</p> <h2>Angular CLI</h2> <p>I've been an <strong>Angular</strong> developer for a long time now, and I'll admit it took me a bit to get used to the <strong><a href="https://cli.angular.io/">Angular CLI</a></strong>. But once I got over my need to hand-code all the boilerplate, I'm never looking back! The command line interface simplifies so many aspects of <strong>Angular</strong> development by allowing the developer to focus on the application logic. The CLI handles everything from creating a new project to generating modules, components, pipes, services, classes, interfaces, enums, directives, guards, etc... it also tests, lints and serves up your app for local debugging! Let's get started, shall we?!</p> <pre><code>npm install -g @angular/cli </code></pre> <p>This installs the CLI globally, and we're ready to begin. Executing the following command will generate a new project named <code>color-game</code> and navigate into the newly created project directory. It also specifies a few preferences:</p> <ul> <li><strong>SCSS</strong> for styling</li> <li>Skips <code>.spec.ts</code> test files</li> <li>Doesn't include routing, we won't need it</li> <li>Minimal scaffolding, etc</li> </ul> <pre><code>ng n color-game --minimal=true --S=true --routing=false --style=scss cd color-game </code></pre> <p>Let's add some components that will help us structure the app in a meaningful way.</p> <pre><code>ng g c color-block ng g c game-board ng g c instructions ng g c players </code></pre> <p>We've successfully added a <code>color-block</code>, <code>game-board</code>, <code>instructions</code> and <code>players</code> components. There are a few relationships to call attention to. A <code>game-board</code> handles the layout of the <code>color-block</code>, <code>instructions</code> and <code>players</code>. We'll need to communicate between various components, as such we'll add some services to handle this.</p> <pre><code>ng g s services\data-storage ng g s services\game-engine </code></pre> <p>If you did this correctly you're directory should look similar to the following:</p> <pre><code>├── app │ ├── color-block │ │ └── **/*.scss|**/*.ts|**/*.html │ ├── game-board │ │ └── **/*.scss|**/*.ts|**/*.html | |── instructions │ │ └── **/*.scss|**/*.ts|**/*.html │ ├── players │ │ └── **/*.scss|**/*.ts|**/*.html │ └── services │ ├── data-store.service.ts | └── game-engine.service.ts | .. etc .. </code></pre> <h2>Material</h2> <p>I am a fan of the material design principles and the ease of use. Let's add angular material to the party as well. From the same command line window, let's execute the following which will install angular material as a dependency.</p> <pre><code>npm install @angular/material </code></pre> <blockquote> <p>&lt;cite&gt;<strong>ProTip</strong>&lt;/cite&gt; If you get an notifications about vulnerabilities address them immediately!&lt;br/&gt; Simply, execute <code>npm audit fix</code></p> </blockquote> <p>Now, open up the <code>style.scss</code> file and let's add an <code>import</code> statement at the top and a bit of simple CSS.</p> <pre><code>@import "~@angular/material/prebuilt-themes/pink-bluegrey.css"; html, body { height: 100%; } body { margin: 0; } </code></pre> <p>This will effectively set the material theme to the "pink bluegrey" variation, which is personally my favorite. For more details on material - check out their <a href="https://material.io/develop/web/docs/getting-started/">site here</a>. Let's set the background color using the newly added theme classes. Copy this HTML in the the <code>index.html</code>.</p> <pre><code>&lt;!doctype html&gt; &lt;html lang="en"&gt; &lt;head&gt; &lt;meta charset="utf-8"&gt; &lt;title&gt;The Color Game&lt;/title&gt; &lt;base href="/"&gt; &lt;meta name="viewport" content="width=device-width, initial-scale=1"&gt; &lt;link rel="icon" type="image/x-icon" href="favicon.ico"&gt; &lt;link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" rel="stylesheet"&gt; &lt;link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"&gt; &lt;/head&gt; &lt;body class='mat-app-background'&gt; &lt;!-- Apply this class to the body --&gt; &lt;app-root&gt;&lt;/app-root&gt; &lt;/body&gt; &lt;/html&gt; </code></pre> <p>Later, you'll notice elements and classes prefixed with <code>mat-</code> this indicates that they are from angular material. Finally, we'll need to import the material modules - it's cleaner to separate this out into it's own module and import it into the <code>app.module.ts</code>. Let's generate a new module named <code>material-all</code> and keep it at the same level as our existing <code>app.module</code>.</p> <pre><code>ng g module material-all --flat </code></pre> <p>Now, copy this into the newly created <code>material-all.module.ts</code>.</p> <pre><code>import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatCheckboxModule } from '@angular/material'; import { MatButtonModule } from '@angular/material'; import { MatRippleModule } from '@angular/material/core'; import { MatBadgeModule } from '@angular/material/badge'; import { MatInputModule } from '@angular/material/input'; import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatDatepickerModule } from '@angular/material/datepicker'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatRadioModule } from '@angular/material/radio'; import { MatSelectModule } from '@angular/material/select'; import { MatSliderModule } from '@angular/material/slider'; import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { MatMenuModule } from '@angular/material/menu'; import { MatSidenavModule } from '@angular/material/sidenav'; import { MatToolbarModule } from '@angular/material/toolbar'; import { MatListModule } from '@angular/material/list'; import { MatGridListModule } from '@angular/material/grid-list'; import { MatCardModule } from '@angular/material/card'; import { MatStepperModule } from '@angular/material/stepper'; import { MatTabsModule } from '@angular/material/tabs'; import { MatExpansionModule } from '@angular/material/expansion'; import { MatButtonToggleModule } from '@angular/material/button-toggle'; import { MatChipsModule } from '@angular/material/chips'; import { MatIconModule } from '@angular/material/icon'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatDialogModule } from '@angular/material/dialog'; import { MatTooltipModule } from '@angular/material/tooltip'; import { MatSnackBarModule } from '@angular/material/snack-bar'; import { MatTableModule } from '@angular/material/table'; import { MatSortModule } from '@angular/material/sort'; import { MatPaginatorModule } from '@angular/material/paginator'; @NgModule({ declarations: [], imports: [ CommonModule, MatCheckboxModule, MatCheckboxModule, MatBadgeModule, MatRippleModule, MatButtonModule, MatInputModule, MatAutocompleteModule, MatDatepickerModule, MatFormFieldModule, MatRadioModule, MatSelectModule, MatSliderModule, MatSlideToggleModule, MatMenuModule, MatSidenavModule, MatToolbarModule, MatListModule, MatGridListModule, MatCardModule, MatStepperModule, MatTabsModule, MatExpansionModule, MatButtonToggleModule, MatChipsModule, MatIconModule, MatProgressSpinnerModule, MatProgressBarModule, MatDialogModule, MatTooltipModule, MatSnackBarModule, MatTableModule, MatSortModule, MatPaginatorModule ], exports: [ MatCheckboxModule, MatCheckboxModule, MatBadgeModule, MatRippleModule, MatButtonModule, MatInputModule, MatAutocompleteModule, MatDatepickerModule, MatFormFieldModule, MatRadioModule, MatSelectModule, MatSliderModule, MatSlideToggleModule, MatMenuModule, MatSidenavModule, MatToolbarModule, MatListModule, MatGridListModule, MatCardModule, MatStepperModule, MatTabsModule, MatExpansionModule, MatButtonToggleModule, MatChipsModule, MatIconModule, MatProgressSpinnerModule, MatProgressBarModule, MatDialogModule, MatTooltipModule, MatSnackBarModule, MatTableModule, MatSortModule, MatPaginatorModule ] }) export class MaterialAllModule { } </code></pre> <p>😁 That is a lot of code, thankfully we only have to do that once. Now, in the <code>app.module.ts</code> we'll import this module empowering our app with all things material!</p> <p>You will want to copy the following into the the <code>app.module.ts</code>.</p> <pre><code>import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { MaterialAllModule } from './material-all.module'; import { GameBoardComponent } from './game-board/game-board.component'; import { ColorBlockComponent } from './color-block/color-block.component'; import { InstructionsComponent } from './instructions/instructions.component'; import { PlayersComponent } from './players/players.component'; @NgModule({ declarations: [ AppComponent, GameBoardComponent, ColorBlockComponent, InstructionsComponent, PlayersComponent ], imports: [ BrowserModule, FormsModule, BrowserAnimationsModule, MaterialAllModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } </code></pre> <h2>Animations</h2> <p>I love making subtle animations, they can enrich the user's experience...but they can also ruin it just as easily. You must approach the use of animations as you do other UX related considerations, such as accessability. Furthermore animations can be challenging to implement, luckily angular has a story for that too - check out <a href="https://angular.io/guide/animations">angular animations</a>.</p> <p>In the <code>color-block.component.ts</code> we're going to add some animations.</p> <pre><code>import { Component, Input } from '@angular/core'; import { GameEngineService, GameState } from '../services/game-engine.service'; import { trigger, state, style, animate, transition } from '@angular/animations'; @Component({ selector: 'color-block', templateUrl: './color-block.component.html', styleUrls: ['./color-block.component.scss'], animations: [ trigger('wasClicked', [ state('idle', style({ transform: 'scale(1)' })), state('clicked', style({ transform: 'scale(1.2)' })), transition('idle =&gt; clicked', [ animate('0.4s') ]), transition('clicked =&gt; idle', [ animate('0.1s') ]), ]), ], }) export class ColorBlockComponent { @Input() classes: string[]; @Input() color: string; get allClasses() { return this.isSelected ? this.isWinningColor ? [...this.classes, 'winner'] : [...this.classes, 'selected' ] : this.classes; } isClicked = false; isSelected: boolean = false; private currentState: GameState; private isWinningColor = false; constructor(private readonly gameEngine: GameEngineService) { this.gameEngine .$gameStateChanged .subscribe((state: GameState) =&gt; { this.currentState = state; if (state === 'selectWinningColor') { this.isWinningColor = false; this.isSelected = false; } }); } onClick() { this.isClicked = true; setTimeout(() =&gt; this.isClicked = false, 100); if (this.currentState === 'selectWinningColor') { this.gameEngine.setWinningColor(this.color); this.gameEngine.changeState('playingGame'); } else if (this.currentState === 'playingGame') { this.isSelected = true; if (this.gameEngine.isWinningColor(this.color)) { this.isWinningColor = true; this.gameEngine.changeState('gameEnded'); } else { this.gameEngine.changeState('playingGame'); } } } } </code></pre> <p>We import several symbols from <code>@angular/animations</code> and now we're ready to decorate our component. As part of the <code>@Script</code> decorations, we'll add the desired animations. We <code>trigger</code> animation on various changes of <code>state</code>. Then we apply <code>style</code> and <code>transition</code> from one <code>state</code> to another. The syntax is actually rather straightforward to read. Here is the corresponding markup.</p> <pre><code>&lt;div [ngClass]='allClasses' (click)=onClick() [@wasClicked]="isClicked ? 'clicked' : 'idle'"&gt; &lt;mat-card class='color-card'&gt; &lt;mat-card-header&gt; &lt;mat-card-title&gt;{{ color | titlecase }}&lt;/mat-card-title&gt; &lt;/mat-card-header&gt; &lt;mat-card-content&gt; &lt;div *ngIf='isSelected'&gt; &lt;mat-icon color='accent' [inline]='true'&gt;check_box&lt;/mat-icon&gt; &lt;/div&gt; &lt;div *ngIf='!isSelected'&gt; &lt;mat-icon color='accent' [inline]='true'&gt;check_box_outline_blank&lt;/mat-icon&gt; &lt;/div&gt; &lt;/mat-card-content&gt; &lt;/mat-card&gt; &lt;/div&gt; </code></pre> <h2>Putting It All Together</h2> <p>The <code>game-board.component</code> serves as the glue holding all the other sub components together. It relies on the various services for managing the state of the game through the <code>game-engine.service</code>.</p> <pre><code>import { Injectable } from '@angular/core'; import { ReplaySubject, Observable } from 'rxjs'; export type GameState = 'usersRequired' | 'selectWinningColor' | 'playingGame' | 'gameEnded'; export interface StateDetails { state: GameState; title: string; message: () =&gt; string; } @Injectable({ providedIn: 'root' }) export class GameEngineService { $gameStateChanged: Observable&lt;GameState&gt;; stateDetails = new Map&lt;GameState, StateDetails&gt;(); private activePlayer: string; private stateBroadcast = new ReplaySubject&lt;GameState&gt;(); private winningColor: string; constructor() { this.stateDetails['usersRequired'] = { state: 'usersRequired', title: 'Users Required', message: () =&gt; 'Add at least two players to begin...' }; this.stateDetails['selectWinningColor'] = { state: 'selectWinningColor', title: 'Select Winning Color', message: () =&gt; 'Have players cover their eyes, and ' + 'then the "referee" select the winning color.' }; this.stateDetails['playingGame'] = { state: 'playingGame', title: 'Game Active', message: () =&gt; `"${this.activePlayer}" please choose your color.` }; this.stateDetails['gameEnded'] = { state: 'gameEnded', title: 'Game Over', message: () =&gt; `Congratulations... "${this.activePlayer}" has won the game!` }; this.$gameStateChanged = this.stateBroadcast.asObservable(); } isWinningColor(color: string) { return this.winningColor === color; } setWinningColor(color: string) { this.winningColor = color; } changeState(state: GameState) { this.stateBroadcast.next(state); } setActivePlayer(player: string) { this.activePlayer = player; } reset() { this.changeState('selectWinningColor'); } } </code></pre> <p>The <code>game-engine.service</code> has the possible states that the app can exhibit and each state represents a different way in the application. For example, when there are less than two players we enter the <code>'usersRequired'</code> state. This updates the <code>instructions.component</code> appropriately, and disables certain controls, etc. Likewise, when we have enough players - the instructions change to "instruct" the players on what to do next.</p> <p>For more details, I encourage you to dig into the source code that is publicly available up on <a href="https://github.com/IEvangelist/color-game">GitHub here</a>. Also, the application is <a href="https://bit.ly/kids-color-game">hosted here</a>. This game isn't styled for mobile devices, because ... well that would have taken more time than I had over the weekend to do. I hope you enjoy it and that it helps solves some of the simple little decision-making quarrels parents experience!</p> David PineASP.NET Core - Slack Slash Commandshttps://davidpine.dev/posts/asp-net-core-slackhttps://davidpine.dev/posts/asp-net-core-slackC# Slack API IntegrationsThu, 21 Mar 2019 00:00:00 GMT<h1>Background</h1> <p>Most of us are all "slackers", meaning we truly do spend a significant amount of time using Slack.</p> <blockquote> <p>Slack is a collaboration hub for work, no matter what work you do. It’s a place where conversations happen, decisions are made, and information is always at your fingertips. &lt;cite&gt;<a href="https://www.slack.com">www.slack.com</a>&lt;/cite&gt;</p> </blockquote> <p>It's wildly popular in the Developer Community! In fact, almost to a fault...people are constantly sharing their "slack fatigue". I am personally a part of roughly twenty slack workspaces. One of the really cool features of slack is the ability to integrate with the tool itself through the Slack API. Imagine a plugin playground where you're free to extend the capabilities of the slack ecosystem. Look to the <a href="https://api.slack.com/">Slack API</a>.</p> <h2>Slash Commands</h2> <p>A slack "slash command" is a command that slack enables when typing a leading <code>/</code> into the chat message input. It displays an autocomplete (or pseudo Intellisense) with the available slash commands that exist in the current workspace. You add slash commands to a workspace that you're a member of via the settings. We'll cover that in more detail a bit later, let's focus on building the application now that we have an understanding of the integration capabilities.</p> <h2>ASP.NET Core Web API</h2> <p>We're building an <strong>ASP.NET Core Web API</strong> application. We'll expose a few bits of functionality for jokes, weather and <code>bit.ly</code> integration to shorten urls. The slash commands are explained below.</p> <ul> <li><code>/joke</code> random nerdy Chuck Norris joke and <code>/joke [share]</code> (shares the joke in the channel)</li> <li><code>/weather [zip-code]</code> returns detailed weather with corresponding emoji -- from open weather API</li> <li><code>/shortenUrl [longUrl]</code> shortens a long URL, relies on bit.ly API</li> </ul> <p>We will need to add a few configurations, services, models and routes to satisfy the desired functionality.</p> <h3>Prerequisites</h3> <p>The <a href="https://www.icndb.com/api/">internet chuck norris database</a> is one of my goto APIs for demonstrations. It doesn't require an API key, nor anything else. However, several of the other bits of functionality require registering for the API.</p> <ul> <li><strong><a href="https://www.bitly.com">Bit.ly API</a></strong>: sign up and get your API key</li> <li><strong><a href="https://openweathermap.org/api">Open Weather API</a></strong>: sign up and <a href="https://home.openweathermap.org/api_keys">generate an API key</a></li> </ul> <p>With these various API keys, we'll need to add a few environment variables. From the command line, execute the following commands.</p> <pre><code>setx BitlyOptions__Login [Bit.ly Login] setx BitlyOptions__ApiKey [Bit.ly API Key] setx OpenWeatherMapOptions__Key [Open Weather Key] </code></pre> <blockquote> <p>&lt;cite&gt;<strong>ProTip</strong>&lt;/cite&gt; You will need to restart <strong>Visual Studio</strong> in order for the newly added environment variables to be available.</p> </blockquote> <h3>Slack API - Slash Command Request</h3> <p>Let's take a look at an example controller, the <code>WeatherController</code>.</p> <pre><code>using System; using System.Threading.Tasks; using IEvangelist.Slack.SlashCommands.Configuration; using IEvangelist.Slack.SlashCommands.Extensions; using IEvangelist.Slack.SlashCommands.Interfaces; using IEvangelist.Slack.SlashCommands.Models; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; namespace IEvangelist.Slack.SlashCommands.Controllers { [ApiController, Route("api/weather")] public class WeatherController : ControllerBase { readonly IWeatherService _weatherService; readonly SlackOptions _slackOptions; public WeatherController( IWeatherService weatherService, IOptions&lt;SlackOptions&gt; slackOptions) { _weatherService = weatherService; _slackOptions = slackOptions?.Value ?? throw new ArgumentNullException(nameof(slackOptions)); } [ HttpPost, Consumes("application/x-www-form-urlencoded"), Produces("application/json") ] public async Task&lt;ActionResult&gt; Weather( [FromForm] SlackCommandRequest request) { var response = await _weatherService.GetWeatherAsync(request.Text); return new JsonResult(new { text = response.ToString() }); } } } </code></pre> <p>There are a few noteworthy considerations here. First, the <code>Weather</code> action is an HTTP Post that consumes <code>application/x-www-form-urlencoded</code> and produces <code>application/json</code>. From the post's form, we'll deserialize into the <code>SlackCommandRequest</code> object that looks like this.</p> <pre><code>using Newtonsoft.Json; namespace IEvangelist.Slack.SlashCommands.Models { public class SlackCommandRequest { public string Token { get; set; } [JsonProperty("team_id")] public string TeamId { get; set; } [JsonProperty("team_domain")] public string TeamDomain { get; set; } [JsonProperty("enterprise_id")] public string EnterpriseId { get; set; } [JsonProperty("enterprise_name")] public string EnterpriseName { get; set; } [JsonProperty("channel_id")] public string ChannelId { get; set; } [JsonProperty("channel_name")] public string ChannelName { get; set; } [JsonProperty("user_id")] public string UserId { get; set; } [JsonProperty("user_name")] public string UserName { get; set; } public string Command { get; set; } public string Text { get; set; } [JsonProperty("response_url")] public string ResponseUrl { get; set; } [JsonProperty("trigger_id")] public string TriggerId { get; set; } } } </code></pre> <p>This is the shape of the object that slack's API returns for all slash commands.</p> <blockquote> <p>&lt;cite&gt;<strong>Disclaimer</strong>&lt;/cite&gt; I attempted to verify the slack request, however; I couldn't get it working. See <a href="https://api.slack.com/docs/verifying-requests-from-slack">my attempt</a> verifying requests from slack for more details and <a href="https://github.com/IEvangelist/IEvangelist.Slack.SlashCommands/blob/master/IEvangelist.Slack.SlashCommands/Extensions/SlackCommandRequestExtensions.cs"><code>SlackCommandRequestExtensions</code></a>.</p> </blockquote> <h3>😨 HttpClient</h3> <p>Our application will need to make external API calls, we will use the <code>HttpClient</code> to do this. To help alleviate the concern of socket exhaustion when using <code>HttpClient</code> the <strong>ASP.NET Core</strong> has middleware for offering up an <code>IHttpClientFactory</code>. The <code>IHttpClientFactory</code> can create <code>HttpClient</code> instances using the appropriate constructor.</p> <p>In the <code>Startup.ConfigureServices</code> method, we'll add the <code>HttpClient</code> by invoking the <code>.AddHttpClient</code> extension. This wires up all the services for resolving an instance of <code>IHttpClientFactory</code>. Then we will add some 💪-typed clients for the <code>WeatherService</code> and <code>UrlService</code>. We configure the typed clients by specifying their base address.</p> <h4>Registering Services</h4> <p>As previously mentioned, we have several primary intents for this application. We register some of the services differently, but it's good to see this as it exemplifies the various capabilities.</p> <pre><code>// Joke Service services.AddHttpClient(); services.AddTransient&lt;IJokeService, JokeService&gt;(); // Weather Service services.AddHttpClient&lt;IWeatherService, WeatherService&gt;( client =&gt; client.BaseAddress = new Uri("https://api.openweathermap.org/data/2.5/weather")); // URL Service services.AddHttpClient&lt;IUrlService, UrlService&gt;( client =&gt; client.BaseAddress = new Uri("https://api-ssl.bitly.com/v3/")); </code></pre> <h4>Example Service</h4> <p>This is the implementation of the <code>IJokeService</code> which demonstrates how simple the consumption of the <code>HttpClient</code> is and how to easily call out to an external API. This pattern is essentially repeated in the implementations of the <code>IUrlService</code> and the <code>IWeatherService</code>, but they're calling out to different APIs with different parameters and mapping to the appropriate response type.</p> <pre><code>using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; using IEvangelist.Slack.SlashCommands.Extensions; using IEvangelist.Slack.SlashCommands.Interfaces; using IEvangelist.Slack.SlashCommands.Models; namespace IEvangelist.Slack.SlashCommands.Services { public class JokeService : IJokeService { readonly IList&lt;string&gt; _positiveEmoji = new List&lt;string&gt; { ":smile:", ":smirk:", ":clap:", ":joy:", ":grin:", ":yum:", ":sweat_smile:", ":laughing:", ":smiley:", ":rolling_on_the_floor_laughing:" }; readonly IHttpClientFactory _httpClientFactory; public JokeService(IHttpClientFactory httpClientFactory) =&gt; _httpClientFactory = httpClientFactory; public async Task&lt;JokeResponse&gt; GetJokeAsync() { var client = _httpClientFactory.CreateClient(); var json = await client.GetStringAsync( "https://api.icndb.com/jokes/random?limitTo=[nerdy]"); return json.To&lt;JokeResponse&gt;(); } public string GetRandomJokeEmoji() =&gt; _positiveEmoji.RandomElement(); } } </code></pre> <p>This example service didn't map the <code>.BaseAddress</code> so we use a fully qualified request URL. Additionally, this service provides a method for getting a random positive emoji that we'll append to the message when we respond to the slack slash command request.</p> <h2>Putting It All Together</h2> <p>With the application written, published to Azure App Service and integrated with Slack - we can start using it immediately. Open the <a href="https://api.slack.com/apps">Slack API Apps - Console</a> and "Create New App". Give your app a name and select the target workspace you'd like to target.</p> <p><img src="./create-app.png" alt="'Create App" /></p> <p>Next, under "Features and Functionality" select "Slash Commands" to begin configuring the commands. Click "Create Command" and configure it by simply giving it a name, a URL for the API endpoint and a few other details.</p> <p><img src="./create-command.png" alt="'Create Command" /></p> <h4>Joke Command</h4> <p>This is the <code>/joke</code> command, it also supports an optional <code>share</code> command which will share the joke with everyone in the current channel.</p> <p><img src="./jokes.gif" alt="'Jokes Command" /></p> <h4>Weather Command</h4> <p>This is the <code>/weather [zip code]</code> command, entering your zip code should yield a text representation of the weather as it corresponds to the given zip code.</p> <p><img src="./weather.gif" alt="'Weather Command" /></p> <p>Some people have noticed that I named the app "I've Got Jokes", but it has a weather endpoint...it's not really funny weather, it has more of a "dry" sense of humor - even on a rainy day.</p> <h4>Shorten URL Command</h4> <p>This is the <code>/shortenUrl [long url]</code> command. Given a URL it will shorten the URL using bit.ly API. Perhaps you're curious what long URL I shortened here, well this is <a href="https://bit.ly/2FmmgyC">the bit.ly 🤣</a>.</p> <p><img src="./url.gif" alt="Shorten URL Command" /></p> <h2>Conclusion</h2> <p>To recap we simply put together an <strong>ASP.NET Core</strong> Web API application that was published up to Azure as an App Service. The endpoints are configured as we integrate with the slack API, and ta-da...magic. All of this source code is up on <a href="https://github.com/ievangelist/IEvangelist.Slack.SlashCommands">GitHub</a>. As always, feel free to star, fork and send me a pull request!</p> <h3>Additional Resources</h3> <ul> <li><a href="https://docs.microsoft.com/en-us/visualstudio/deployment/quickstart-deploy-to-azure?view=vs-2017">Deploy To Azure App Service</a></li> <li><a href="https://api.slack.com/docs/verifying-requests-from-slack">Verifying Slack Request</a></li> <li><a href="https://api.slack.com/slash-commands">Slack API - Slash Commands</a></li> </ul> David PineBuilding a GitHub profanity filter with .NET Core and Azure Functionshttps://davidpine.dev/posts/github-profanity-filterhttps://davidpine.dev/posts/github-profanity-filterIt's better to laugh it offSat, 23 Nov 2019 00:00:00 GMT<p>import { Tweet } from '@astro-community/astro-embed-twitter';</p> <h1>Intro</h1> <p>With more than <a href="https://github.com/search?q=type:user&amp;type=Users">40 million active users</a>, GitHub is by far the largest source code hosting platform in the world. It's an open source developers dream, and ecosystem and developer community unlike any other. And with all these users and such profound openness, there's bound to be frustration from time to time. In this post we will explore an <strong>Azure Function</strong> written with <strong>ASP.NET Core 3.0</strong> and <strong>C# 8.0</strong>. It has been designed to handle a <strong>GitHub webhook</strong> for issues and pull requests. In other words, we are able to target a specific GitHub repository and listen for new issues or pull requests - as they occur, our <strong>Azure Function</strong> is called.</p> <p>When the <strong>Azure Function</strong> is invoked, it will examine the body text and title text of the issue or pull request. If either contain profanity, it replaces the profanity with less offensive content -- such as:</p> <p>&lt;span style='font-size: 4em'&gt;👼 🐳 💝 &lt;/span&gt;</p> <p>We'll see how to orchestrate various technologies together, and take a look at <strong>GitHub webhooks</strong>, <strong>Azure Functions</strong>, <strong>C# 8.0</strong>, <strong>CosmosDB</strong> and <strong>GraphQL</strong>. As always the complete <a href="https://github.com/IEvangelist/GitHub.ProfanityFilter">source code</a> is available for you're eager eyes 👀...enjoy!</p> <h2>GitHub Webhooks</h2> <p>I'm going to assume that you're unfamiliar with "webhooks". Think of a webhook as simply being an event. Like all other events, you can subscribe to them. Much like an event, when you subscribe you provide a handler or a callback. When the event occurs, it calls your handler. But with a webhook, you get a bit more control over how the event is communicated. GitHub offers <a href="https://developer.github.com/webhooks/#events">webhooks for many types of events</a>.</p> <p>Open a GitHub repository that you're the owner of. From within the GitHub user interface navigate to ** Settings**, then select <strong>Webhooks</strong> from the left panel. Click the <strong>Add webhook</strong> button and explore the various options available to you for configuring a webhook.</p> <table> <thead> <tr> <th>Setting</th> <th>Details</th> </tr> </thead> <tbody> <tr> <td>Payload URL</td> <td>The fully qualified URL of the <strong>Azure Function</strong>.</td> </tr> <tr> <td>Content Type</td> <td>The content type of the post for your corresponding endpoint, let's select <code>application/json</code> for JSON.</td> </tr> <tr> <td>Secret</td> <td>This secret is used to validate that requests made to your endpoint are truly from the webhook and not malicious.</td> </tr> <tr> <td>Events</td> <td>There are several options. For this post, I have selected <strong>Let me select individual events</strong> and I have chosen the <strong>Issues</strong> and <strong>Pull requests</strong>.</td> </tr> <tr> <td>Active</td> <td>Whether or not to deliver event details when the hook is triggered. This is useful, as you can easily toggle it off and on for troubleshooting.</td> </tr> </tbody> </table> <h3>Securing webhooks</h3> <p>GitHub has <a href="https://developer.github.com/webhooks/securing/">an article</a> on securing webhooks, which is useful but unfortunately it's written for <strong>Ruby</strong>. Since we've written an ASP.NET Core with C# application, we'll take a look a closer look at how to do this.</p> <pre><code>[FunctionName(nameof(ProcessWebhook))] public async Task&lt;IActionResult&gt; ProcessWebhook( [HttpTrigger(AuthorizationLevel.Function, "POST")] HttpRequest request, [FromServices] IGitHubPayloadValidator payloadValidator, [FromServices] IGitHubWebhookDispatcher webhookDispatcher) { try { var signature = request.Headers .GetValueOrDefault("X-Hub-Signature"); using var reader = new StreamReader(request.Body); var payloadJson = await reader.ReadToEndAsync(); if (!payloadValidator.IsPayloadSignatureValid( Encoding.UTF8.GetBytes(payloadJson), signature)) { return new StatusCodeResult(500); } var eventName = request.Headers.GetValueOrDefault("X-GitHub-Event"); await webhookDispatcher.DispatchAsync(eventName, payloadJson); return new OkObjectResult($"Successfully handled the {eventName} event."); } catch (Exception ex) { return new StatusCodeResult(500); } } </code></pre> <p>The method above is the primary entry point for our <strong>Azure Function</strong>. We decorate our method with a <code>FunctionName</code> attribute assigning the name of the method. Our method is defined as an <code>async Task&lt;IActionResult&gt;</code> returning method. It has three parameters, all of which are provided by the <strong>Azure Function</strong> runtime. The first parameter is the <code>HttpRequest</code> and is triggered via an HTTP POST. The next two parameters are decorated with the <code>FromServices</code> attribute, which instruct the ASP.NET Core dependency injection pipeline to resolve the corresponding implementations. We are then provided instances of a payload validator and a webhook dispatcher, we'll explore these both in more detail in a bit.</p> <p>The method functions by attempting to read the <code>X-Hub-Signature</code> header from the request, this will be used to compare our computed signature based off the known secret. We leverage <strong>C# 8 using declarations</strong>, to instantiate a <code>StreamReader</code> from the request body stream. We then read the entire body, and assign our <code>jsonPayload</code> variable the resulting JSON response. With the signature from the header and the JSON bytes, we're ready to validate the signature.</p> <pre><code>public bool IsPayloadSignatureValid( byte[] bytes, string receivedSignature) { if (string.IsNullOrWhiteSpace(receivedSignature)) { return false; } using var hmac = new HMACSHA1(Encoding.ASCII.GetBytes(_options.WebhookSecret)); var hash = hmac.ComputeHash(bytes); var actualSignature = $"sha1={hash.ToHexString()}"; return IsSignatureValid(actualSignature, receivedSignature); } static bool IsSignatureValid(string a, string b) { var length = Math.Min(a.Length, b.Length); var equals = a.Length == b.Length; for (var i = 0; i &lt; length; ++ i) { equals &amp;= a[i] == b[i]; } return equals; } </code></pre> <p>The <code>IsPayloadSignatureValid</code> implementation leverages <strong>C# 8 using declarations</strong> and instantiates a <code>HMACSHA1</code> with the bytes from the configured webhook secret. The <code>HMACSHA1</code> is the C# representation of the "<a href="https://en.wikipedia.org/wiki/HMAC">hash-based message authentication code (HMAC)</a>, for the <a href="https://en.wikipedia.org/wiki/SHA-1">secure hash algorithm 1 (SHA1)</a>". It will allow us to compute the hash of the payload bytes, then we can use our computed hash and compare it to the signature in the header. If these two are a match -- we know that the request is valid. Otherwise, it may be malicious and we can simply disregard it.</p> <p>You may have noticed that we also pulled out another header, this was the <code>X-GitHub-Event</code> header. It tells us which GitHub event was firing. We'll use that in our dispatcher to determine which shape our JSON payload is expected to be delivered in.</p> <p>Great, wow what? We have a valid call into our <strong>Azure Function</strong> from our <strong>GitHub webhook</strong>, let's do something with it. We want to examine the title and body text of incoming <code>issues</code> or <code>pull_request</code>, so let's start there.</p> <h2>Handling Issues and Pull Requests</h2> <p>If you recall earlier in this post, we are subscribed for both <code>issues</code> and <code>pull_request</code> events. We need to determine which event we're handling and take the appropriate action.</p> <pre><code>public class GitHubWebhookDispatcher : IGitHubWebhookDispatcher { static readonly ValueTask NoopTask = new ValueTask(); readonly IIssueHandler _issueHandler; readonly IPullRequestHandler _pullRequestHandler; public GitHubWebhookDispatcher( IIssueHandler issueHandler, IPullRequestHandler pullRequestHandler) =&gt; (_issueHandler, _pullRequestHandler) = (issueHandler, pullRequestHandler); public ValueTask DispatchAsync(string eventName, string payloadJson) =&gt; eventName switch { "issues" =&gt; _issueHandler.HandleIssueAsync(payloadJson), "pull_request" =&gt; _pullRequestHandler.HandlePullRequestAsync(payloadJson), _ =&gt; NoopTask, }; } </code></pre> <p>The webhook dispatcher class requires both <code>IIssueHandler</code> and <code>IPullRequestHandler</code> implementations. It defines a dispatch functionality, that maps the issue based events to the corresponding issue handler and pull requests to its handler. This is expressed as a <strong>C# 8 switch expression</strong>. The <code>eventName</code> value is our target in the <code>switch</code> expression, and our case labels are simple expressions for our intent. This removes much of the verbosity of writing out the word, <code>case</code> and <code>break</code>. We're left with easier to read code!</p> <p>Now, let's take a look at the <code>IssueHandler</code> implementation.</p> <pre><code>public class IssueHandler : GitHubBaseHandler&lt;IssueHandler&gt;, IIssueHandler { readonly GitHubOptions _options; readonly IProfanityFilter _profanityFilter; readonly IRepository&lt;FilterActivity&gt; _repository; public IssueHandler( IGitHubGraphQLClient client, ILogger&lt;IssueHandler&gt; logger, IOptions&lt;GitHubOptions&gt; options, IProfanityFilter profanityFilter, IRepository&lt;FilterActivity&gt; repository) : base(client, logger) =&gt; (_profanityFilter, _options, _repository) = (profanityFilter, options.Value, repository); } </code></pre> <p>You may have noticed the <code>.ctor</code> logic is expressed as a tuple assignment. When I discovered this was possible, I questioned it - what do you think? Leave your comments on the tweet below.</p> <p>&lt;Tweet id="https://twitter.com/davidpine7/status/1169418926217973760" /&gt;</p> <p>Now, back to the handling of issues. What's that look like?</p> <pre><code>public async ValueTask HandleIssueAsync(string payloadJson) { var payload = payloadJson.FromJson&lt;IssueEventPayload&gt;(); if (payload is null) { _logger.LogWarning("GitHub issue payload is null."); return; } switch (payload.Action) { case "opened": await HandleIssueAsync(payload); break; case "reopened": case "edited": var activity = await _repository.GetAsync(payload.Issue.NodeId); if (activity?.WorkedOn .Subtract(DateTime.Now) .TotalSeconds &lt;= 1) { _logger.LogInformation( $"Just worked on this issue {payload.Issue.NodeId}..."); } await HandleIssueAsync(payload, activity); break; case "closed": case "deleted": await _repository.DeleteAsync(payload.Issue.NodeId); break; case "assigned": case "demilestoned": case "labeled": case "locked": case "milestoned": case "pinned": case "transferred": case "unassigned": case "unlabeled": case "unlocked": case "unpinned": break; } } </code></pre> <p>We start with a <code>string</code>, which represents the payload from <strong>GitHub</strong> -- it deserializes as an <code>IssueEventPayload</code> type. If it <code>is null</code>, then we're done but if it has a value we'll <code>switch</code> on the specific event action. When <code>opened</code>, we'll handle the issue but first let's discuss a bit more of what's going on here.</p> <h3>Cosmos DB</h3> <p>You're probably asking yourself, "where did this <code>_repository</code> come from and where is it reading from?". Since <strong>Azure Functions</strong> are stateless, and we have no way of identifying how often we're called from <strong>GitHub</strong> or when we've already worked on an issue -- we need a way of tracking this activity. I decided to use <strong>Cosmos DB</strong> as a data store. It's freakishly fast, and with a beautiful abstraction over the top of it -- we can seamlessly perform CRUD operations on any POCO object asynchronously. It's literally amazing! Here's the abstraction.</p> <pre><code>public class CosmosContainerProvider : ICosmosContainerProvider, IDisposable { readonly RepositoryOptions _options; CosmosClient _client; Container _container; public CosmosContainerProvider( IOptions&lt;RepositoryOptions&gt; options) =&gt; _options = options?.Value ?? throw new ArgumentNullException(nameof(options)); public Container GetContainer() { if (_container is null) { _client = new CosmosClient(_options.CosmosConnectionString); var database = _client.GetDatabase(_options.DatabaseId); _container = database.GetContainer(_options.ContainerId); } return _container; } public void Dispose() =&gt; _client?.Dispose(); } </code></pre> <p>We start by defining a <code>CosmosContainerProvider</code> class, which encapsulates the logic to instantiate and provide a <code>Microsoft.Azure.Cosmos.Container</code> instance. The container is available from a <code>Microsoft.Azure.Cosmos.Database</code> object, and requires our connection string in order to function correctly. This is what we use to read from and write to, next we have our <code>Repository&lt;T&gt;</code> implementation.</p> <pre><code>public class Repository&lt;T&gt; : IRepository&lt;T&gt; where T : BaseDocument { readonly ICosmosContainerProvider _containerProvider; public Repository( ICosmosContainerProvider containerProvider) =&gt; _containerProvider = containerProvider ?? throw new ArgumentNullException(nameof(containerProvider)); public async ValueTask&lt;T&gt; GetAsync(string id) { try { var container = _containerProvider.GetContainer(); var response = await container.ReadItemAsync&lt;T&gt;(id, new PartitionKey(id)); return response.Resource; } catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { return default; } } public async ValueTask&lt;IEnumerable&lt;T&gt;&gt; GetAsync(Expression&lt;Func&lt;T, bool&gt;&gt; predicate) { try { var iterator = _containerProvider.GetContainer() .GetItemLinqQueryable&lt;T&gt;() .Where(predicate) .ToFeedIterator(); IList&lt;T&gt; results = new List&lt;T&gt;(); while (iterator.HasMoreResults) { foreach (var result in await iterator.ReadNextAsync()) { results.Add(result); } } return results; } catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { return Enumerable.Empty&lt;T&gt;(); } } public async ValueTask&lt;T&gt; CreateAsync(T value) { var container = _containerProvider.GetContainer(); var response = await container.CreateItemAsync(value, value.PartitionKey); return response.Resource; } public Task&lt;T[]&gt; CreateAsync(IEnumerable&lt;T&gt; values) =&gt; Task.WhenAll(values.Select(v =&gt; CreateAsync(v).AsTask())); public async ValueTask&lt;T&gt; UpdateAsync(T value) { var container = _containerProvider.GetContainer(); var response = await container.UpsertItemAsync&lt;T&gt;(value, value.PartitionKey); return response.Resource; } public async ValueTask&lt;T&gt; DeleteAsync(string id) { var container = _containerProvider.GetContainer(); var response = await container.DeleteItemAsync&lt;T&gt;(id, new PartitionKey(id)); return response.Resource; } } </code></pre> <p>This is a little gem, it provides all of the common functionality that you should need to start reading from and writing to a <strong>Cosmos DB</strong> data store. It exposes functions that create, read, update or delete any subclass of a <code>BaseDocument</code>. This base document only requires an <code>Id</code> and a <code>PartitionKey</code>.</p> <pre><code>public class BaseDocument { [JsonProperty("id")] public string Id { get; set; } internal PartitionKey PartitionKey =&gt; new PartitionKey(Id); } </code></pre> <p>The real beauty of this abstraction is the support we get from <strong>ASP.NET Core</strong>. In our <code>ServiceCollectionExtensions</code> we define dependency injection additions for these services. We get to leverage a less-known generic dependency injection functionality.</p> <pre><code>public static class ServiceCollectionExtensions { public static IServiceCollection AddGitHubRepository( this IServiceCollection services, IConfiguration configuration) =&gt; services.AddSingleton&lt;ICosmosContainerProvider, CosmosContainerProvider&gt;() .AddSingleton(typeof(IRepository&lt;&gt;), typeof(Repository&lt;&gt;)) .Configure&lt;RepositoryOptions&gt;( configuration.GetSection(nameof(RepositoryOptions))); } </code></pre> <p>In our <strong>Azure Function</strong> it is perfectly fine to register certain dependencies as singletons. This is the interesting like, <code>.AddSingleton(typeof(IRepository&lt;&gt;), typeof(Repository&lt;&gt;))</code>. We <code>AddSingleton</code> but noticed that we're doing a <code>typeof(IRepository&lt;&gt;)</code>? This means that for any generic type, provide the corresponding generic implementation! In a single line we opened up a repository pattern of infinite possibilities...I know, that sounds pretty epic, but seriously amazing. Now, back to our regularly scheduled programming. Where were we? Ah, yes...the <code>HandleIssueAsync</code> given the <code>IssueEventPayload</code>. If we're able to read from the repo previous activity from this</p> <pre><code>async ValueTask HandleIssueAsync( IssueEventPayload payload, FilterActivity activity = null) { var issue = payload.Issue; var (title, body) = (issue.Title, issue.Body); var wasJustOpened = activity is null; if (!wasJustOpened) { (title, body) = await _client.GetIssueTitleAndBodyAsync(issue.Number); } var filterResult = TryApplyProfanityFilter(title, body); if (filterResult.IsFiltered) { var updateIssue = issue.ToUpdate(); updateIssue.Title = filterResult.Title; updateIssue.Body = filterResult.Body; await _client.UpdateIssueAsync(issue.Number, updateIssue); var clientId = Guid.NewGuid().ToString(); if (wasJustOpened) { await _repository.CreateAsync(new FilterActivity { Id = issue.NodeId, WasProfane = true, Type = ActivityType.Issue, MutationOrNodeId = clientId, WorkedOn = DateTime.Now, OriginalTitleText = title, OriginalBodyText = body, ModifiedTitleText = filterResult.Title, ModifiedBodyText = filterResult.Body }); } else { activity.WasProfane = true; activity.WorkedOn = DateTime.Now; await _repository.UpdateAsync(activity); } await _client.AddReactionAsync( issue.NodeId, ReactionContent.Confused, clientId); await _client.AddLabelAsync( issue.NodeId, new[] { _options.ProfaneLabelId }, clientId); } } </code></pre> <p>From the payload instance we get the issue, we pick out the title and body with tuple deconstruction and assignment into two local variables. If <code>activity is null</code> then we're working with a new issue -- otherwise this issue was worked on before. Due to the stateless nature of our app, if we are working on an issue that we previously worked on, it is best to read the issue title and body from <strong>GitHub</strong> again to ensure that we're working with the latest version. As you can imagine there are potential race conditions, we need to be mindful of that possibility.</p> <h3>Profane Filtering</h3> <p>Next, we will call the <code>ApplyProfanityFilter</code> function.</p> <pre><code>internal FilterResult TryApplyProfanityFilter( string title, string body) { if (string.IsNullOrWhiteSpace(title) &amp;&amp; string.IsNullOrWhiteSpace(body)) { return FilterResult.NotFiltered; } var (resultingTitle, isTitleFiltered) = TryApplyFilter(title, '*'); var (resultingBody, isBodyFiltered) = TryApplyFilter(body); return new FilterResult( resultingTitle, isTitleFiltered, resultingBody, isBodyFiltered); } </code></pre> <p>If both the <code>title</code> and <code>body</code> are invalid values, we'll early exit and return a filter result of "not filtered". However, if they have values we will conditionally try to apply the filter.</p> <pre><code>(string text, bool isFiltered) TryApplyFilter( string text, char? placeHolder = null) { var filterText = _profanityFilter?.IsProfane(text) ?? false; var resultingText = filterText ? _profanityFilter?.ApplyFilter(text, placeHolder) : text; return (resultingText, filterText); } </code></pre> <p>If the text is profane, we apply the filter. Titles do not support markdown, as such we have to replace profane content with asterisk characters. But with the body text, <strong>GitHub</strong> supports markdown and emoji. When we are done working on the issue, we store the activity as a <code>FilterActivity</code> instance with all of the details we need. If there was profanity we react to the issue with the confused emoji 😕 and we'll label the issue.</p> <h3>GraphQL</h3> <p>There are several default labels, none of which suited my needs. I needed to signify that an issue or pull request contained profane content -- the label I created was "profane content 🤬". The way that the app knows about this label is via its identifier, and we can retrieve that using the <strong>GitHub GraphQL</strong> <a href="https://developer.github.com/v4/explorer">explorer</a> and the following query.</p> <pre><code>query { repository( owner: "IEvangelist", name: "GitHub.ProfanityFilter") { labels(first: 20) { nodes { id, name } } } } </code></pre> <p>I really love how <strong>GraphQL</strong> allows the consumer to retrieve only the shape of the items it's querying, this is really powerful. Executing this query returns my labels, their <code>name</code> and <code>id</code>. The label identifier is configured in our **Azure <strong>Function</strong> as an environment variable. If you're looking for the <strong>GitHub GraphQL SDK</strong>, look no further -- this project relies on the <a href="https://www.nuget.org/packages/Octokit.GraphQL">Oktokit.GraphQL</a> package. It is a fluent API, which enables developers to author complex graph-based queries and mutations with ease. For details, see the <a href="https://github.com/IEvangelist/GitHub.ProfanityFilter">source code</a>.</p> <h2>Conclusion</h2> <p>This article started with detailing the underlying sentiment of an open source ecosystem, and the potential for frustration and angst. And while it's not all rainbows and ponies, it's awesome when the community comes together. I received a pull request that added the "lint licker" word replacer implementation -- yes, inspired by this commercial! For your viewing pleasure, here is what the filter looks like in action.</p> <p>&lt;video poster="/img/2019/11/poster.png" controls&gt; &lt;source src="/img/2019/11/in-action.mp4" type="video/mp4" /&gt; Your browser does not support the video tag. &lt;/video&gt;</p> <p>As you can see, the profanity filter works 🤘.</p> David PineBlazoratorshttps://davidpine.dev/posts/blazoratorshttps://davidpine.dev/posts/blazoratorsC# Source Generator for JavaScript Interop in BlazorThu, 08 Sep 2022 00:00:00 GMT<p>import { YouTube } from '@astro-community/astro-embed-youtube'; import { Tweet } from '@astro-community/astro-embed-twitter';</p> <h1>Blazorators</h1> <p><em>Blazorators</em> is a C# source generator that creates fully functioning Blazor JavaScript interop code, targeting either the <code>IJSInProcessRuntime</code> or <code>IJSRuntime</code> types. All of this code relies on the output of <a href="https://github.com/IEvangelist/blazorators">one of my side projects named <code>blazorators</code></a>. While writing <a href="/posts/learning-blazor">my O'Reilly "Learning Blazor: Build Single-Page Apps with WebAssembly and C#" book</a>, I discovered an impactful way to use both TypeScript type declarations from common JavaScript APIs and C# source generators to generate the JavaScript interop code. This is covered in chapter seven, so I don't want to spoil it too much, but rather entice you to want to <a href="/posts/learning-blazor">read the book</a>. I've been using this approach in my Blazor apps for a while now and I'm excited to share it with you. Let's get started!</p> <p>Check out my new <a href="https://twitter.com/@blazorbits">Twitter account (@blazorbits)</a> for Blazor tips and tricks! Below you'll see the Progress Telerik REPL (Read Evaluate Print Loop) for Blazor, select the <strong>Preview</strong> tab and watch the Blazor WebAssembly app perform a .NET restore and builds the app. Once the app loads, select a Natural voice from the voice selection control if it is available to you. If it's not available to you, that's not a big deal either. Select the <strong>Speak</strong> button and the app will speak the text you entered in the <strong>Text-to-speech</strong> control. You can also select the <strong>Stop</strong> button to stop the speech.</p> <p><strong>Interactive Demo</strong>:</p> <p>&lt;iframe width="100%" height='600px' src='https://blazorrepl.telerik.com/repl/embed/mcajYsly289d8oAd18?editor=true&amp;result=true&amp;errorList=true'&gt; &lt;/iframe&gt;</p> <h2>Explore the app source code</h2> <p>when you create your own REPL session you're free to consume public NuGet packages, this app uses the <a href="https://www.nuget.org/packages/Blazor.SpeechSynthesis.WebAssembly"><code>Blazor.SpeechSynthesis.WebAssembly</code></a> NuGet package 📦. Explore the <strong>Code</strong> tab thoroughly and look through the app's source code. You'll see that this app does the following:</p> <ul> <li>_<em>Main.razor</em>: The app's main page, that showing a loading indicator and immediately navigates to the <strong>/speak</strong> route.</li> <li><em>TextToSpeech.razor</em>: The page that renders the <strong>Text-to-speech</strong> text input element and the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/speechSynthesis">browser-native speech synthesis</a> control buttons.</li> <li><em>TextToSpeech.razor.cs</em>: The page's code-behind that contains the <code>TextToSpeech</code> component's logic.</li> <li><em>Startup.cs</em>: The app's startup class that configures services, calling a source-generated dependency injection specific extension method named `AddSpeechServices.</li> </ul> <p>Notice the <code>SpeechSynthesisVoice[]</code>, <code>SpeechSynthesisUtterance</code>, and <code>ISpeechSynthesisService</code> types. These are all from the <code>Blazor.SpeechSynthesis.WebAssembly</code> NuGet package 📦. The <code>SpeechSynthesisVoice[]</code> type is an array of <code>SpeechSynthesisVoice</code> objects that represent the available voices on the user's device. The <code>SpeechSynthesisUtterance</code> type is a class that represents the text to be spoken and the voice to be used. The <code>ISpeechSynthesisService</code> type is an interface that defines the <code>GetVoicesAsync</code> and an in-process <code>Speak</code> method that is synchronous.</p> <p>The <code>Speak</code> function takes a <code>SpeechSynthesisUtterance</code> object and a callback function that is invoked when the speech is complete, delivering the total elapsed time in milliseconds.</p> <p>There is also a way to listen for changes in the browser-native <code>window.speechSynthesis</code> voices that are available to the JavaScript context. Provide a callback to the <code>ISpeechSynthesisService.OnVoicesChanged</code> event and it will be invoked when the <code>window.speechSynthesis</code> <code>voiceschanged</code> event is fired. This is useful if you want to update the UI with the available voices. All of this functionality is source-generated by the <code>Blazor.SourceGenerator</code> project.</p> <h2>What's next?</h2> <p>This project was originally written as part of a Microsoft hackathon. It's been refined, but is still a brute-force effort and could stand to be optimized. As such I'm working to reimplement the parsing approach. It currently makes an HTTP request to a raw Github link with the <em>lib.dom.d.ts</em> file with all of the DOM type declarations in it. The file is itself, generated, and is over 18 thousand lines of code. It uses <code>Regex</code> expressions to parse on-demand single hierarchical type dependency graphs as <code>readonly record struct</code> values. These values were modeled as</p> <h2>Patent pending, not... ☹️</h2> <p>I was working with Stephen Toub (the patent lead for the .NET team), and we filed it for a patent. I was disappointed to learn that it wasn't filed as the lawyers reviewed it and determined that it wasn't patentable. I'm not sure why, but I'm glad to share it with you. I hope you find it useful. If you have any questions, please feel free to reach out to me:</p> <ul> <li><a href="https://twitter.com/davidpine7">Follow me on Twitter</a></li> <li><a href="https://github.com/IEvangelist">GitHub profile</a></li> <li><a href="https://github.com/IEvangelist/blazorators">Blazorators repository</a></li> </ul> <p>The community is excited about this project, and I'm excited to see what you build with it. Here's what some of the community members have said on Twitter:</p> <p>&lt;Tweet id="https://twitter.com/robertmclaws/status/1510292205814095874" /&gt;</p> <p>&lt;Tweet id="https://twitter.com/KStrubeG/status/1503140307537440783" /&gt;</p> <p>&lt;Tweet id="https://twitter.com/Scott_Addie/status/1507350467524841474" /&gt;</p> <p>&lt;Tweet id="https://twitter.com/EdCharbeneau/status/1504161891756748800" /&gt;</p> <h3>The plan</h3> <p>I'm rewriting the TypeScript abstract syntax tree parser to C#, essentially porting it one-to-one. When this is done, the parsing will be much more accurate and flexible. Likewise, our friends over on Twitter, and some of the community members on YouTube have been kind to me as well. Thank you for your kind words, they mean the world to me.</p> <p><strong>fieryscorpion</strong> <a href="https://www.youtube.com/watch?v=U8XriF2zxS0&amp;lc=UgyWLpFDYjI048tcnit4AaABAg">commented</a>:</p> <blockquote> <p>This is a pretty amazing project. Hope this makes it into a serious project at Microsoft! Thank you!</p> </blockquote> <p><strong>Jiří Novotný</strong> <a href="https://www.youtube.com/watch?v=U8XriF2zxS0&amp;lc=UgzpOMTAz_KatcVURjV4AaABAg">commented</a>:</p> <blockquote> <p>I think this package has a lot of potential. It makes the bridge between JS and C# in Blazor much less painful. I love it! Well done, David.</p> </blockquote> <h2>Additional resources</h2> <p>&lt;YouTube id="https://youtu.be/U8XriF2zxS0" /&gt; &lt;br/&gt; &lt;YouTube id="https://youtu.be/RGPS0y2pl2k" /&gt;</p> David PineLearning Blazorhttps://davidpine.dev/posts/learning-blazorhttps://davidpine.dev/posts/learning-blazorBuild Single-Page Apps with WebAssembly and C#Fri, 16 Sep 2022 00:00:00 GMT<p>In June 2021, I set out on a journey to write a book in the middle of a global pandemic. I had no idea what I was doing (all things considered, I still don't know what I'm doing). But I do know that I truly enjoy helping others. I hope you enjoy the book as much as I enjoyed writing it.</p> <h2>Amazon Preview</h2> <p>&lt;iframe type="text/html" sandbox="allow-scripts allow-same-origin allow-popups" width="100%" height="800px" frameborder="0" allowfullscreen style="max-width:100%" src="https://read.amazon.com/kp/card?asin=B0BGJS7JBP&amp;preview=inline&amp;linkCode=kpe&amp;ref_=cm_sw_r_kb_dp_N54C1WQ2MEE43AZPBEVY&amp;tag=davidpine" &gt; &lt;/iframe&gt;</p> <h2>Foreword by Steve Sanderson</h2> <p>Web development has been a dominating feature of the software industry for over 20 years and is likely to remain so for many years to come. Industry giants continue to invest heavily in expanding web technology's power and flexibility, enabling an increasing range of advanced browser-based software. While native mobile apps and augmented reality / virtual reality apps find their place for consumer software, the web is overwhelmingly the default UI for business apps. If you could bet on only one application platform, you should bet on the web.</p> <p>During those same 20 years, .NET (first released in 2002) has held its place as Microsoft's premiere developer toolset. Like the web, .NET continues to gain strength. It was reinvented as cloud-first, cross-platform, and fully open source in 2016 and today is used by about 30% of all professional software developers[^1] C# has always been considered one of the most productive languages, at the forefront of rich developer tooling with precise code completions and a top debugging experience, and now ASP.NET Core is one of the fastest server-side web technologies[^2].</p> <p>The goal of Blazor is to unlock the full power of .NET for browser-based UI applications. It's the .NET team's best effort to create the most productive and natural way to create single-page application–type apps. This includes Blazor's component-based programming model, which takes the best aspects of many modern UI frameworks and unifies them into something natural for .NET with its strong typing. Beyond that, it means connecting with the rest of the .NET ecosystem, with its industry-leading IDEs and first-class features for debugging, testing, and hot reload. Blazor's biggest innovation might be its flexible execution models, running server-side with UI streaming to browsers over a websocket, directly inside the browser on WebAssembly, or as native code in mobile and desktop apps.</p> <p><em>Learning Blazor</em> provides both a deep and broad look at Blazor app development. Unlike many other books, it doesn't just focus on the easy parts of C# programming and leave real-world complexity as an exercise for the reader. Instead, David sets out the whole range of web development concerns—including authentication, security, performance, localization, and deployment (CI/CD)—right in front of you, starting from the beginning. With some focus, you'll be able to absorb David's broad expertise and be equipped to take on realistic work of your own.</p> <p>David is well placed to explain not just how things work today but also how they've evolved to their present state and even how things may change in the future. He's been a well-known figure in the Blazor community for years, is well connected with engineering leaders within Microsoft, and has an even longer history as a 2-time Microsoft Most Valuable Professional (MVP) and a 6-time Google Developer Expert (GDE) in web technologies. Throughout this book, you'll find many historical details and anecdotes that shed light on the challenges, decisions, and people who shaped web development and .NET into the technology you'll be using. David's enthusiasm will propel you through a complex landscape.</p> <p>My biggest motivation when creating the first release of Blazor with Dan Roth and Ryan Nowak was to help free web UI from its monoculture. I appreciate JavaScript and have built a lot of my career on it, but there are so many other programming languages, paradigms, and communities that could bring their own richness into the browser. I know you'll find your own ways to innovate with the software you create and for the users who benefit. I wish you all the best with your Blazor projects and am confident you'll find inspiration in these pages.</p> <p><em><strong>Steve Sanderson</strong></em></p> <p><em>Software Engineer/Architect at Microsoft, original Blazor creator</em> <em>Bristol, UK</em> <em>August 2022</em></p> <p>[^1]: <a href="https://survey.stackoverflow.co/2022/#most-popular-technologies-language-prof">Most Popular Technologies, 2022 Stack Overflow Developer Survey, accessed August 18, 2022</a> [^2]: <a href="https://www.techempower.com/benchmarks/#section=data-r21&amp;hw=ph&amp;test=plaintext">Web Framework Benchmarks: Round 21, July 19, 2022, TechEmpower</a></p> <h2>Developer community praise</h2> <p>I love the energy from the people who have read the book! I'm thrilled to have made such a positive impact on the community. Here are some of the comments I've received from some familiar developer community folks. 😍</p> <blockquote> <p>The Blazor open source web framework is a whole new way to develop powerful applications for the web. Learning Blazor uses tons of examples and prescriptive code based on David Pine's deep experience as a content developer on the .NET team to get you writing Blazor web apps with C# today!</p> </blockquote> <blockquote> <p>Learning Blazor is the perfect resource for developers who are looking to build modern web applications using bleeding-edge web technologies. David leverages his skills as a senior content developer to help you get started with Blazor!</p> </blockquote> <blockquote> <p>David Pine's Learning Blazor takes developers on the perfect journey to learning how to build and deploy their Blazor applications. David leverages his extensive content development and presentation skills to inspire you to build Blazor web apps today.</p> </blockquote> <blockquote> <p>Like its author, this book will inspire folks to try new things with Blazor and to be excited about the possibilities of building apps it brings. David's delightful style of writing code as eloquent as his prose, coupled with a genuine love for .NET and using it creatively will make any developer fall in love with web development all over again.</p> </blockquote> <blockquote> <p>As an experienced Blazor developer, I learned more about localization and testing from David Pine in 20 minutes of reading than I did in days of searching for similar insight online. Not only is Learning Blazor an educational piece, but it's also a great reference that I will return to when building future Blazor applications.</p> </blockquote> <blockquote> <p>Learning Blazor is a well-paced guide that is perfectly suited for anyone with .NET experience who would like to learn Blazor to build web apps, and for anyone looking to refine their Blazor skills.</p> </blockquote> <blockquote> <p>Learning Blazor is one of the most informative books I have ever read, and it makes you want to start developing with WebAssembly and C# right away. Learning Blazor uses a great storyline and practical real-world examples to explain a modern technology and how it can be combined with JavaScript and HTML. A must-read for every web developer.</p> </blockquote> <blockquote> <p>There are so many gems in here that you are guaranteed to learn something regardless of your experience level. It has inspired me to add techniques and features to my projects that I've never considered before.</p> </blockquote> <p>Click the cover below 👇🏽. It's a link to the book on Amazon. 😊</p> <p><a href="https://www.amazon.com/gp/product/1098113241"><img src="./full-book-cover.png" alt="Visit Amazon.com for the Learning Blazor: Build Single-Page Apps with WebAssembly and C# by David Pine book." /></a></p> <table> <thead> <tr> <th>Media type</th> <th>Site</th> </tr> </thead> <tbody> <tr> <td><a href="https://www.amazon.com/Learning-Blazor-Build-Single-Page-WebAssembly/dp/1098113241"><strong>📖 Paperback (Print)</strong></a></td> <td><a href="https://www.amazon.com/Learning-Blazor-Build-Single-Page-WebAssembly/dp/1098113241"><strong>Check it out on Amazon to pre-order today.</strong></a></td> </tr> <tr> <td><a href="https://www.amazon.com/Learning-Blazor-David-Pine-ebook/dp/B0BGJS7JBP"><strong>💻 eBook (Kindle)</strong></a></td> <td><a href="https://www.amazon.com/Learning-Blazor-David-Pine-ebook/dp/B0BGJS7JBP"><strong>Buy it now on Amazon Kindle devices.</strong></a></td> </tr> </tbody> </table> David PineHello from the GitHub Actions: Core .NET SDKhttps://davidpine.dev/posts/github-actions-sdkhttps://davidpine.dev/posts/github-actions-sdkThe unofficial .NET equivalent of the official GitHub actions/toolkit @actions/core project.Wed, 30 Nov 2022 00:00:00 GMT<p>:::tip{title="📢 ATTENTION"}</p> <p>This post is part of the C# advent calendar 2022, for other articles in this collection see <a href="https://csadvent.christmas">C# Advent Calendar 2022</a>. This is the third time I've written for the calendar, past entries include <a href="/blog/csharp-special-edition">2017: C# Special Edition</a> and <a href="blog/csharp-all-the-things">2018: C# All The Things</a>. 🙏🏽</p> <p>:::</p> <h1>GitHub Actions: Core .NET SDK</h1> <p>SDKs are great, but why do we need one for GitHub Actions? The short answer is, you don't! You can write a GitHub Action in .NET without using one. I've written about this already, if you're interested see the <a href="https://learn.microsoft.com/dotnet/devops/create-dotnet-github-action">Tutorial: Create a GitHub Action with .NET</a>. There are a few ways in which you can write a GitHub Action. You can do so using JavaScript, Docker, or as a composite action. One of the easiest ways was to use JavaScript, as the GitHub team maintains an open-source SDK (or <a href="https://github.com/actions/toolkit"><code>actions/toolkit</code></a>) written in TypeScript. Their toolkit is comprised of a set of packages to make creating actions easier.</p> <p>I wanted to make it easier for .NET developers to write GitHub Actions, so I created an unofficial .NET equivalent of the official GitHub actions/toolkit <code>@actions/core</code> and <code>@actions/github</code> projects.</p> <h2>Why should I care about GitHub Actions?</h2> <p>If you're unfamiliar with GitHub Actions, the <strong>TL;DR;</strong> is that they allow you to automate tasks such as building, testing, packaging, and deploying your code. If you want a deeper look beyond that definition, see <a href="https://docs.github.com/actions">GitHub Docs: Actions</a>. Just know that GitHub Actions are a powerful tool, and the team has done an amazing job of making them easy to use.</p> <p>You should care about GitHub Actions because they are a great way to automate by writing workflows. A GitHub Action is then consumed by a repositories workflow, which is a set of instructions that are executed when a specific event occurs. For example, you can create a workflow that runs when a pull request is created, or when a new release is published. You can also create a workflow that runs on a schedule, such as every day at 3:00 AM. Workflows are defined in a YAML file, and you can create as many workflows as you want. You can also create a workflow that is triggered by another workflow. For example, you can create a workflow that runs when a pull request is created, and then create another workflow that runs when that workflow completes. The possibilities are endless!</p> <h3>Inspiration from <code>actions/toolkit</code></h3> <p>The <a href="https://github.com/actions/toolkit"><code>actions/toolkit</code></a> specifies the standard I/O, and it defines specific environment variables that are used within a GitHub Action workflow. If you're familiar with any of my other open-source projects, you'll likely be aware of my appreciation for what <a href="/categories/typescript">🤓 TypeScript</a> has done for the web. Moreover, you know that <a href="/categories/csharp">I'm all about that C# 💜</a>.</p> <p>I've been working on various other projects that leverage TypeScript in the world of .NET. There are certainly parallel efforts in this regard, for more information, see <a href="/blog/blazorators">Blazorators</a>. As this relates to the <code>actions/toolkit</code> specifically, let's start by having a look at the possible inputs and outputs, and the formats in which they can be set.</p> <h2>Commanding the workflow</h2> <p>GitHub Action workflows rely heavily on environment variables. The following are the environment variables that are used to interact with the workflow:</p> <ul> <li><code>GITHUB_ACTION</code>: The name of the action.</li> <li><code>GITHUB_OUTPUT</code>: A file path to a file containing any custom data that the workflow cares to write.</li> <li><code>GITHUB_PATH</code>: A file path to a file containing environment variables to add to the <code>PATH</code> environment variable for all subsequent steps in the current job.</li> <li><code>GITHUB_STATE</code>: Custom user data that is persisted across workflow jobs and steps.</li> <li><code>GITHUB_TOKEN</code>: The token used to authenticate with GitHub. This value is provided by GitHub, and contextually available to make whatever <code>permissions</code> the workflow has available to it. This value is always written to the log in a masked format.</li> </ul> <p>These are but a few of the environment variables that are available to a GitHub Action workflow. For a complete list, see <a href="https://docs.github.com/actions/reference/environment-variables#default-environment-variables">GitHub Docs: Environment variables</a>.</p> <h2>Outputs</h2> <p>The workflow can write outputs to a file, and even custom state. In the case of output, files are written to the file path from the <code>GITHUB_OUTPUT</code> environment variable. The output in this scenario would be a JSON file that contains anything we want to serialize to and from. We're not limited by that, it is trivial in C# with the <code>System.IO</code> for reading and writing JSON, and either <code>System.Text.Json</code> or <code>Newtonsoft.Json</code> for JSON serialization of C# objects. In addition to files, any arbitrary string can be written to the log. The log is written to the standard output stream and is available to the workflow. Certain logged messages receive special treatment and are parsed by the workflow as <em>commands</em>. The following are the special messages that are parsed by the workflow:</p> <table> <thead> <tr> <th>Command name</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td><code>add-mask</code></td> <td>Registers a secret which will get masked from logs.</td> </tr> <tr> <td><code>add-path</code></td> <td>Prepends the input path to the <code>PATH</code> (for this action and future actions).</td> </tr> <tr> <td><code>echo</code></td> <td>Enables or disables the echoing of commands into stdout for the rest of the step.</td> </tr> <tr> <td><code>debug</code></td> <td>Writes debug message to user log.</td> </tr> <tr> <td><code>error</code></td> <td>Adds an error issue.</td> </tr> <tr> <td><code>warning</code></td> <td>Adds a warning issue.</td> </tr> <tr> <td><code>notice</code></td> <td>Adds a notice issue.</td> </tr> <tr> <td><code>group</code></td> <td>Begin an output group.</td> </tr> <tr> <td><code>endgroup</code></td> <td>End the output group.</td> </tr> </tbody> </table> <p>If you're seeing these commands and wondering where the <code>save-state</code> and <code>set-output</code> commands are, they have been deprecated. For more information, see <a href="https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands">GitHub Actions: Deprecating <code>save-state</code> and <code>set-output</code> commands</a>. Now that we're familiarizing ourselves with the commands, let's have a look at the <code>@actions/core</code> package to see how it implements commanding the workflow.</p> <h3>TypeScript <code>Command</code> object</h3> <p>This code is from the <code>@actions/core</code> project, and it's TypeScript code that represents a command. The <em>command.ts</em> file exposes a few functions to issue commands. Read through the contents of the <a href="https://github.com/actions/toolkit/blob/main/packages/core/src/command.ts">actions/toolkit/blob/main/packages/core/src/command.ts</a> file:</p> <pre><code>import * as os from 'os' import {toCommandValue} from './utils' export interface CommandProperties { [key: string]: any } /** * Commands * * Command Format: * ::name key=value,key=value::message * * Examples: * ::warning::This is the message * ::set-env name=MY_VAR::some value */ export function issueCommand( command: string, properties: CommandProperties, message: any ): void { const cmd = new Command(command, properties, message) process.stdout.write(cmd.toString() + os.EOL) } export function issue(name: string, message = ''): void { issueCommand(name, {}, message) } const CMD_STRING = '::' class Command { private readonly command: string private readonly message: string private readonly properties: CommandProperties constructor(command: string, properties: CommandProperties, message: string) { if (!command) { command = 'missing.command' } this.command = command this.properties = properties this.message = message } toString(): string { let cmdStr = CMD_STRING + this.command if (this.properties &amp;&amp; Object.keys(this.properties).length &gt; 0) { cmdStr += ' ' let first = true for (const key in this.properties) { if (this.properties.hasOwnProperty(key)) { const val = this.properties[key] if (val) { if (first) { first = false } else { cmdStr += ',' } cmdStr += `${key}=${escapeProperty(val)}` } } } } cmdStr += `${CMD_STRING}${escapeData(this.message)}` return cmdStr } } function escapeData(s: any): string { return toCommandValue(s) .replace(/%/g, '%25') .replace(/\r/g, '%0D') .replace(/\n/g, '%0A') } function escapeProperty(s: any): string { return toCommandValue(s) .replace(/%/g, '%25') .replace(/\r/g, '%0D') .replace(/\n/g, '%0A') .replace(/:/g, '%3A') .replace(/,/g, '%2C') } </code></pre> <p>A few things to observe about the preceding code:</p> <ol> <li>The <code>issueCommand</code> function is one of the two functions that are exported. It's used to issue a command to the standard output stream.</li> <li>The <code>issue</code> function is a convenience function that issues a command with no <code>properties</code> parameter.</li> <li>The <code>Command</code> class is used to represent a command. It has a <code>toString</code> method that returns the string representation of the command.</li> <li>The <code>escapeData</code> and <code>escapeProperty</code> functions are used to escape the <code>message</code> and <code>properties</code> of the command, respectively.</li> </ol> <p>This is standard TypeScript code, but how might we want to implement this in C#? Let's take a look at the <code>Command</code> class in <code>Microsoft.GitHub.Actions</code> project:</p> <h3>C# <code>Command</code> object</h3> <p>While inspired, I decided this could be implemented in C#, and with a few minor tweaks — it was. Let's start by discussing what this would look like in C#. The following is the C# implementation of the <code>Command</code> object, this corresponds to the <a href="https://github.com/IEvangelist/dotnet-github-actions-sdk/blob/main/src/Microsoft.GitHub.Actions/Workflows/Command.cs"><em>Microsoft.GitHub.Actions/Workflows/Command.cs</em></a>:</p> <pre><code>namespace Microsoft.GitHub.Actions.Workflows; /// &lt;summary&gt; /// Command format: /// &lt;c&gt;::name key=value,key=value::message&lt;/c&gt; /// &lt;/summary&gt; /// &lt;example&gt; /// &lt;list type="bullet"&gt; /// &lt;item&gt;&lt;c&gt;::warning::This is the message&lt;/c&gt;&lt;/item&gt; /// &lt;item&gt;&lt;c&gt;::set-env name=MY_VAR::some value&lt;/c&gt;&lt;/item&gt; /// &lt;/list&gt; /// &lt;/example&gt; internal readonly record struct Command&lt;T&gt;( string? CommandName = "missing.command", T? Message = default, IReadOnlyDictionary&lt;string, string&gt;? CommandProperties = default) { const string CMD_STRING = "::"; internal bool Conventional =&gt; CommandNames.IsConventional(CommandName); /// &lt;summary&gt; /// The string representation of the workflow command, i.e.; /// &lt;code&gt;::name key=value,key=value::message&lt;/code&gt;. /// &lt;/summary&gt; public override string ToString() { StringBuilder builder = new($"{CMD_STRING}{CommandName}"); if (CommandProperties?.Any() ?? false) { builder.Append(' '); foreach (var (isNotFirst, key, value) in CommandProperties.Select( (kvp, index) =&gt; (index is &gt; 0, kvp.Key, kvp.Value))) { if (isNotFirst) { builder.Append(','); } builder.Append($"{key}={EscapeProperty(value)}"); } } builder.Append($"{CMD_STRING}{EscapeData(Message)}"); return builder.ToString(); } static string EscapeProperty&lt;TSource&gt;(TSource? value) =&gt; value.ToCommandValue() .Replace("%", "%25") .Replace("\r", "%0D") .Replace("\n", "%0A") .Replace(":", "%3A") .Replace(",", "%2C"); static string EscapeData&lt;TSource&gt;(TSource? value) =&gt; value.ToCommandValue() .Replace("%", "%25") .Replace("\r", "%0D") .Replace("\n", "%0A"); } </code></pre> <p>I wanted the <code>Command</code> object to be a <code>readonly record struct</code> for immutability, value-based comparison semantics, deconstruction, and performance (it's lightweight). The <code>Command</code> object has a <code>CommandName</code> property, a <code>Message</code> property, and a <code>CommandProperties</code> property. The <code>CommandName</code> property is a <code>string</code> that represents the name of the command, and it defaults to <code>"missing.command"</code>. The <code>Message</code> property is a generic type that represents the message of the command. The <code>CommandProperties</code> property is a <code>IReadOnlyDictionary&lt;string, string&gt;</code> that represents the properties of the command.</p> <h3>Command format</h3> <p>Workflow commands are written to the standard output stream. For a command with a name of <code>name</code>, properties of <code>{ "key1" = "value", "key2" = "value" }</code>, and a message of <code>message</code>, it's formatted as follows when issued to the standard output stream:</p> <pre><code>::name key1=value,key2=value::message </code></pre> <p>Another example is when writing a debug message to the log:</p> <pre><code>::debug::This is a debug message </code></pre> <p>One final example is the <code>set-env</code> command:</p> <pre><code>::set-env name=ENVIRONMENT_VARIABLE::value </code></pre> <p>All of these commands are delimited with the <code>::</code> characters. The command name is the first token after the first <code>::</code> delimiter. The command properties are the second token after the first <code>::</code> delimiter, following the first space character. The command properties are key/value pairs expressed with the <code>=</code> character and delimited by the <code>,</code> character. The command message is optional. Any given <code>Command</code> instance results in the appropriate command being written to the standard output stream.</p> <h2>Installing the NuGet package 📦</h2> <p>Welcome to the [Microsoft.GitHub.Actions] .NET SDK. This SDK is used to create GitHub Actions in .NET. The SDK is a thin wrapper around the .NET implementation of the GitHub Actions a select few packages from the <a href="https://github.com/actions/toolkit"><code>@actions/toolkit</code></a>.</p> <p>:::caution{title="⚠️ DISCLAIMER"} This package is <strong>not</strong> an official <em>Microsoft</em> or <em>GitHub</em> product. It is a community-driven project. However, I do choose to use the <code>Microsoft.GitHub[.*]</code> namespace for the package. I'm not trying to mislead anyone, but I do want to make it clear that this is not an official product. :::</p> <p>You'll need to install the <a href="https://www.nuget.org/packages/GitHub.Actions.Core">GitHub Actions: Core .NET SDK</a> NuGet package to use the .NET APIs. The package is available on NuGet.org. The following is the command to install the package:</p> <h3>Adding the package reference</h3> <p>Either add the package reference to your project file:</p> <pre><code>&lt;PackageReference Include="GitHub.Actions.Core" Version="0.0.1" /&gt; </code></pre> <p>Or use the <a href="https://learn."><code>dotnet add package</code></a> .NET CLI command:</p> <pre><code>dotnet add package GitHub.Actions.Core </code></pre> <h3>Registering the services for consumption</h3> <p>The SDK is dependency injection ready, meaning that you can call the <code>AddGitHubActions</code> extension method on the <code>IServiceCollection</code> interface to register the services for consumption. The following is an example of how to register the services, and an example of how to consume the <code>ICoreService</code>:</p> <pre><code>using Microsoft.Extensions.DependencyInjection; using Microsoft.GitHub; using Microsoft.GitHub.Actions.Extensions; using Microsoft.GitHub.Actions.Services; using var provider = new ServiceCollection() .AddGitHubActions() .BuildServiceProvider(); var core = provider.GetRequiredService&lt;ICoreService&gt;(); try { // "who-to-greet" input defined in action metadata file var nameToGreet = core.GetInput("who-to-greet"); core.Info($"Hello {nameToGreet}!"); await core.SetOutputAsync("time", DateTime.UtcNow.ToString("o")); // Get the JSON webhook payload for the event that triggered the workflow var payload = JsonSerializer.Serialize(Context.Current.Payload); core.Info($"The event payload: {payload}"); } catch (Exception ex) { core.SetFailed(ex.ToString()); } </code></pre> <p>Compare this to the following JavaScript code in the <em>index.js</em> file from the <a href="https://docs.github.com/actions/creating-actions/creating-a-javascript-action#writing-the-action-code">Creating a JavaScript action: Writing the action code</a> article, which would achieve the same thing with the TypeScript toolkit:</p> <pre><code>const core = require('@actions/core'); const github = require('@actions/github'); try { // `who-to-greet` input defined in action metadata file const nameToGreet = core.getInput('who-to-greet'); console.log(`Hello ${nameToGreet}!`); const time = (new Date()).toTimeString(); core.setOutput("time", time); // Get the JSON webhook payload for the event that triggered the workflow const payload = JSON.stringify(github.context.payload, undefined, 2) console.log(`The event payload: ${payload}`); } catch (error) { core.setFailed(error.message); } </code></pre> <h2>Issuing commands</h2> <p>Instead of being able to <code>export</code> any custom data structures, and corresponding functions, the <code>Command</code> object is <code>internal readonly record struct</code> that can be used to issue commands. The following is the C# implementation of the <code>issueCommand</code> and <code>issue</code> functionality:</p> <pre><code>namespace Microsoft.GitHub.Actions.Commands; internal interface ICommandIssuer { void IssueCommand&lt;T&gt;( string commandName, IReadOnlyDictionary&lt;string, string&gt;? properties = default, T? message = default); void Issue&lt;T&gt;(string commandName, T? message = default); } </code></pre> <p>I'm a big fan of triple-slash comments, and as a consumer of many various APIs written by different authors, I've enjoyed learning APIs with good and meaningful triple-slash comments. I say all of this to say, that I've omitted them from the code pasted here. If you want to see them, check them out on GitHub. The <code>ICommandIssuer</code> interface has two methods, <code>IssueCommand</code>, and <code>Issue</code>. Consider the implementation that's defined in the following <code>DefaultCommandIssuer</code> class:</p> <pre><code>namespace Microsoft.GitHub.Actions.Commands; internal sealed class DefaultCommandIssuer : ICommandIssuer { private readonly IConsole _console; public DefaultCommandIssuer(IConsole console) =&gt; _console = console; public void Issue&lt;T&gt;(string commandName, T? message = default) =&gt; IssueCommand(commandName, null, message); public void IssueCommand&lt;T&gt;( string commandName, IReadOnlyDictionary&lt;string, string&gt;? properties = default, T? message = default) { var cmd = new Command&lt;T&gt;( commandName, message, properties); if (cmd is not { Conventional: true }) { _console.WriteLine("Issuing unconventional command."); } var commandMessage = cmd.ToString(); _console.WriteLine(commandMessage); } } </code></pre> <p>Some commands require interactions with the file system. This functionality is defined as <em>file commands</em>.</p> <h3>File commands</h3> <p>In addition to standard commands, file commands are also supported. File commands write to a file instead of the standard output stream. A file command is free to write any value to the file. The following are file-based commands:</p> <ul> <li><code>add-path</code>: Writes the value to the file path from the <code>GITHUB_PATH</code> environment variable.</li> <li><code>save-state</code>: Writes the value to the file path from the <code>GITHUB_STATE</code> environment variable.</li> <li><code>set-env</code>: Writes the value to the file path from the <code>GITHUB_ENV</code> environment variable.</li> <li><code>set-output</code>: Writes the value to the file path from the <code>GITHUB_OUTPUT</code> environment variable.</li> </ul> <p>The corresponding .NET APIs that are used to issue file commands are:</p> <ul> <li><code>ICoreService.AddPathAsync</code>: Issues an <code>add-path</code> command.</li> <li><code>ICoreService.ExportVariableAsync</code>: Issues the <code>set-env</code> command.</li> <li><code>ICoreService.SaveStateAsync</code>: Issues the <code>save-state</code> command.</li> <li><code>ICoreService.SetOutputAsync</code>: Issues the <code>set-output</code> command.</li> </ul> <h3>The <code>IFileCommandIssuer</code> interface</h3> <pre><code>namespace Microsoft.GitHub.Actions.Commands; internal interface IFileCommandIssuer { ValueTask IssueFileCommandAsync&lt;TValue&gt;(string commandSuffix, TValue message); string PrepareKeyValueMessage&lt;TValue&gt;(string key, TValue value); } </code></pre> <p>The <code>IFileCommandIssuer</code> interface is used to issue file-based commands. The <code>DefaultFileCommandIssuer</code> class implements this interface:</p> <pre><code>namespace Microsoft.GitHub.Actions.Commands; internal sealed class DefaultFileCommandIssuer : IFileCommandIssuer { private readonly Func&lt;string, string, ValueTask&gt; _writeLineTask; public DefaultFileCommandIssuer( Func&lt;string, string, ValueTask&gt; writeLineTask) =&gt; _writeLineTask = writeLineTask.ThrowIfNull(); ValueTask IFileCommandIssuer.IssueFileCommandAsync&lt;TValue&gt;( string commandSuffix, TValue message) { var filePath = GetEnvironmentVariable($"{GITHUB_}{commandSuffix}"); if (string.IsNullOrWhiteSpace(filePath)) { throw new Exception( "Unable to find environment variable for file " + $"command suffix '{commandSuffix} ({GITHUB_}{commandSuffix})'."); } if (File.Exists(filePath) is false) { throw new Exception( $"Missing file at path: '{filePath}' " + $"for file command '{commandSuffix}'."); } return _writeLineTask.Invoke(filePath, message.ToCommandValue()); } string IFileCommandIssuer.PrepareKeyValueMessage&lt;TValue&gt;( string key, TValue value) { var delimiter = $"ghadelimiter_{Guid.NewGuid()}"; var convertedValue = value.ToCommandValue(); // These should realistically never happen, but just in case someone finds a // way to exploit uuid generation let's not allow keys or values that contain // the delimiter. if (key.Contains( delimiter, StringComparison.OrdinalIgnoreCase)) { throw new Exception( $"Unexpected input: name should not contain the delimiter {delimiter}"); } if (convertedValue.Contains(delimiter, StringComparison.OrdinalIgnoreCase)) { throw new Exception( $"Unexpected input: value should not contain the delimiter {delimiter}"); } return $"{key}&lt;&lt;{delimiter}{NewLine}{convertedValue}{NewLine}{delimiter}"; } } </code></pre> <p>This code is derived from the TypeScript implementation of the <a href="https://github.com/actions/toolkit/blob/main/packages/core/src/file-command.ts"><em>core/src/file-command.ts</em></a> file:</p> <pre><code>import * as fs from 'fs' import * as os from 'os' import {v4 as uuidv4} from 'uuid' import {toCommandValue} from './utils' export function issueFileCommand( command: string, message: any): void { const filePath = process.env[`GITHUB_${command}`] if (!filePath) { throw new Error( `Unable to find environment variable ` + `for file command ${command}` ) } if (!fs.existsSync(filePath)) { throw new Error(`Missing file at path: ${filePath}`) } fs.appendFileSync( filePath, `${toCommandValue(message)}${os.EOL}`, { encoding: 'utf8' }) } export function prepareKeyValueMessage( key: string, value: any): string { const delimiter = `ghadelimiter_${uuidv4()}` const convertedValue = toCommandValue(value) // These should realistically never happen, but just in case someone finds a // way to exploit uuid generation let's not allow keys or values that contain // the delimiter. if (key.includes(delimiter)) { throw new Error( `Unexpected input: name should not ` + `contain the delimiter "${delimiter}"` ) } if (convertedValue.includes(delimiter)) { throw new Error( `Unexpected input: value should not ` + `contain the delimiter "${delimiter}"` ) } return `${key}&lt;&lt;${delimiter}${os.EOL}${convertedValue}${os.EOL}${delimiter}` } </code></pre> <p>The C# implementation matches the TypeScript implementation, except for the use of <code>ValueTask</code> instead of <code>void</code> for the <code>IssueFileCommandAsync</code> method. All of the command-issuing functionality is internal to the <code>ICoreService</code> implementation.</p> <h2>The SDK continues to evolve ⚒️</h2> <p>More than 90% of the functionality of the <code>packages/src/core.ts</code> and <code>packages/github/src/github.ts</code> files have been implemented in the SDK.</p> <p>I'm not going to paste more than 100 lines for multiple files, this blog post isn't the place for that. If you're interested in seeing the rest of the SDK, you can find it on <a href="https://github.com/IEvangelist/dotnet-github-actions-sdk"> GitHub</a>. The SDK is still evolving, and the following features are still in development:</p> <ul> <li>I want to create a GitHub repository template that can be used to create a new containerized .NET GitHub Action.</li> <li>I want to continue working on adding missing functionality to the SDK.</li> <li>I want to implement the other packages that exist in the <a href="https://github.com/actions/toolkit/tree/main/packages"> actions/toolkit</a>.</li> </ul> <h2>The future is bright ☀️</h2> <p>I'm always open to collaborating with developer community members, 🙌🏽 who are interested in helping this code live on to help others. I encourage you to explore the code and be sure to see the sample use cases.</p> <p>:::tip{title="🤓 COLLABORATE"}</p> <p>If you're interested in helping, please reach out to me on Twitter, or open an issue on the <a href="https://github.com/IEvangelist/dotnet-github-actions-sdk">GitHub Actions Core .NET SDK</a>.</p> <p>:::</p> <h3>See also</h3> <ul> <li><a href="https://devblogs.microsoft.com/dotnet/dotnet-loves-github-actions?WT.mc_id=dapine">.NET 💜 GitHub Actions: Intro to GitHub Actions for .NET</a></li> <li><a href="https://learn.microsoft.com/dotnet/devops/github-actions-overview?WT.mc_id=dapine">GitHub Actions and .NET</a></li> <li><a href="https://learn.microsoft.com/dotnet/devops/create-dotnet-github-action?WT.mc_id=dapine">Tutorial: Create a GitHub Action with .NET</a></li> <li><a href="https://github.com/dotnet/versionsweeper">.NET Version Sweeper: GitHub Action for .NET</a></li> <li><a href="https://github.com/IEvangelist/resource-translator">Resource Translator: GitHub Action</a></li> <li><a href="https://devblogs.microsoft.com/dotnet/localize-net-applications-with-machine-translation?WT.mc_id=dapine">Localize .NET applications with machine-translation</a></li> </ul> David PineA conversation with ChatGPT https://davidpine.dev/posts/dotnet-dependency-injectionhttps://davidpine.dev/posts/dotnet-dependency-injectionFocusing on .NET Dependency InjectionThu, 08 Dec 2022 00:00:00 GMT<p>:::note{title="❗ IMPORTANT"}</p> <p>This post was generated using <a href="https://chat.openai.com/chat">ChatGPT</a>. I prompted the AI with a few leading questions and formatted the responses as the body text of this post. For more information, see the <a href="https://openai.com/blog/chatgpt">ChatGPT</a>.</p> <p>:::</p> <h1>A conversation with ChatGPT about .NET Dependency Injection</h1> <p>:::duck{align="left"} What is dependency injection and why is it important? :::</p> <p>:::chatgpt{align="right"} Dependency injection is a software design pattern that allows a programmer to remove hard-coded dependencies and make them configurable. This is important because it makes code more modular and easier to maintain, test, and reuse. Additionally, it makes it possible for different parts of a program to be supplied with the specific dependencies they need, rather than having to rely on a global, static dependency. This can make it easier to manage complex systems and can help to improve the flexibility and scalability of a program. :::</p> <p>:::duck{align="left"} What's inversion of control, and how does it relate to the separation of concerns principle? :::</p> <p>:::chatgpt{align="right"} Inversion of control (IoC) is a design principle that specifies that a program should not dictate the behavior of the components it uses. Instead, the program should specify its requirements, and the components should be designed to fulfill those requirements. This means that the program is not in control of how its components are created, how they are initialized, or how they are used.</p> <p>The separation of concerns principle states that a software program should be divided into distinct sections, each of which addresses a separate concern. By following this principle, a program can be designed in a way that makes it easier to understand, maintain, and extend.</p> <p>IoC is related to the separation of concerns principle because it helps to achieve a clean separation of concerns within a program. By using IoC, you can design your program in such a way that the various components are not tightly coupled to each other. This makes it easier to change or modify the behavior of a single component without affecting the rest of the program. In this way, IoC helps to promote the separation of concerns, which in turn makes your program more modular and easier to maintain. :::</p> <p>:::duck{align="left"} When was the concept of dependency injection popularized? :::</p> <p>:::chatgpt{align="right"} The concept of dependency injection has been around for a long time, and it has been recognized as an important technique in software development for many years. However, the term "dependency injection" itself was coined by Martin Fowler in 2004, in an article he wrote for the website of O'Reilly Media. In this article, he explained the concept and discussed its importance in software development, particularly in the context of object-oriented programming. Since then, dependency injection has become a widely used and well-known design pattern in the field of software engineering. :::</p> <p>:::duck{align="left"} How do you install the dependency injection NuGet package in an existing .NET app? :::</p> <p>:::chatgpt{align="right"} To install the dependency injection NuGet package in an existing .NET app, you can use the following steps:</p> <ol> <li>Open your app in Visual Studio.</li> <li>In the Solution Explorer, right-click on the solution and select "Manage NuGet Packages".</li> <li>In the NuGet Package Manager, search for "Microsoft.Extensions.DependencyInjection".</li> <li>Select the package in the search results and click "Install".</li> <li>Follow the on-screen instructions to complete the installation.</li> </ol> <p>Alternatively, you can use the following command at the command line to install the package:</p> <pre><code>dotnet add package Microsoft.Extensions.DependencyInjection </code></pre> <p>Once the package has been installed, you can use the types and methods it provides to implement dependency injection in your app. For more information on how to do this, you can refer to the official Microsoft documentation. :::</p> <p>:::duck{align="left"} Describe .NET dependency injection with the <code>Microsoft.Extensions.DependencyInjection</code> NuGet package. :::</p> <p>:::chatgpt{align="right"} The <code>Microsoft.Extensions.DependencyInjection</code> NuGet package is a popular dependency injection framework for the .NET ecosystem. It provides a simple and flexible way to configure and use dependency injection in .NET applications.</p> <p>To use the <code>Microsoft.Extensions.DependencyInjection</code> package, you first need to add it to your project using the NuGet Package Manager. Once it's added, you can use the <code>IServiceCollection</code> interface to configure your dependencies. For example, suppose you have an <code>IFoo</code> interface and a Foo class that implements it. To register the Foo class as the implementation of the <code>IFoo</code> interface, you can use the following code:</p> <pre><code>services.AddTransient&lt;IFoo, Foo&gt;(); </code></pre> <p>This tells the dependency injection framework to create a new instance of the <code>Foo</code> class every time it is needed. Alternatively, you can use the <code>AddSingleton</code> method to register a single shared instance of the <code>Foo</code> class, or the <code>AddScoped</code> method to register an instance that is shared within a single request or unit of work.</p> <p>Once your dependencies are registered, you can use the <code>IServiceProvider</code> interface to resolve them. This is typically done by creating an instance of the <code>ServiceProvider</code> class, which is passed the <code>IServiceCollection</code> instance that you configured earlier by calling <code>BuildServiceProvider</code>. Then, you can use the <code>GetService</code> method to resolve a specific dependency by its type. For example:</p> <pre><code>var serviceProvider = services.BuildServiceProvider(); var foo = serviceProvider.GetService&lt;IFoo&gt;(); </code></pre> <p>This will return an instance of the <code>Foo</code> class that you registered earlier. You can then use this instance as you would normally, without having to worry about how it was created or managed.</p> <p>Using the <code>Microsoft.Extensions.DependencyInjection</code> package can make it easier to manage dependencies in your .NET applications. It provides a simple and flexible way to configure and use dependency injection, allowing you to improve the maintainability and testability of your code. :::</p> <p>:::duck{align="left"} What is scope validation and why is it important when using the <code>Microsoft.Extensions.DependencyInjection</code> NuGet package? :::</p> <p>:::chatgpt{align="right"} Scope validation is a feature of the <code>Microsoft.Extensions.DependencyInjection</code>NuGet package that helps ensure that the objects created by the dependency injection container are used correctly. It does this by checking that an object is only used within the scope in which it was created. This is important because it helps prevent issues such as using an object after it has been disposed, or using an object from the wrong thread. By using scope validation, you can catch such errors early on and avoid potential runtime issues. :::</p> <p>:::duck{align="left"} Why is scope validation important in dependency injection? :::</p> <p>:::chatgpt{align="right"} Scope validation is important in dependency injection because it allows you to ensure that the dependencies in your app are being used correctly. In particular, it helps you to verify that the dependencies have the correct scope, which is the lifetime of the dependency in your app.</p> <p>When you register a dependency in your app, you can specify its scope by using one of the Add methods provided by the <code>IServiceCollection</code> interface. For example, you can use the <code>AddScoped</code>method to specify that the dependency should be scoped to a specific instance, such as a request in an ASP.NET app.</p> <p>If you do not specify the scope of a dependency, it will be registered with the default scope, which is Transient. This means that a new instance of the dependency will be created each time it is requested.</p> <p>If you register a dependency with the wrong scope, it can cause problems in your app. For example, if you register a dependency with a <code>Transient</code> scope but it should be <code>Scoped</code>, it will create a new instance of the dependency each time it is requested, which can cause unexpected behavior and may lead to performance issues.</p> <p>Scope validation allows you to verify that all the registered dependencies have the correct scope, so you can catch and fix any issues before they cause problems in your app. This can help to improve the reliability and performance of your app. :::</p> <h2>Conclusion</h2> <p>There were very few corrections that I had to make when pasting in the responses from ChatGPT. I think this is a testament to the quality of the responses that ChatGPT was able to generate. I think this is a testament to the quality of the training data that ChatGPT was trained on. This technology has inspired so many new ideas and ways of reimagining what's possible with AI. I'm not only excited to see what the future holds, but I'm also optimistically cautious.</p> <p>For additional reading, see the official <a href="https://learn.microsoft.com/dotnet/core/extensions/dependency-injection"><strong>Microsoft docs on dependency injection</strong></a>, and be sure to check out <a href="https://chat.openai.com/chat"><strong>ChatGPT</strong></a> from <strong>OpenAI</strong>.</p> David PineExploring .NET streaming API scenarioshttps://davidpine.dev/posts/dotnet-async-enumerablehttps://davidpine.dev/posts/dotnet-async-enumerableUse the IAsyncEnumerable<T> interface to stream data from a server to a client.Mon, 19 Jun 2023 00:00:00 GMT<p>import { Tweet } from '@astro-community/astro-embed-twitter';</p> <p>:::note{title="❗ IMPORTANT"}</p> <p>The featured image was generated using <a href="https://www.bing.com/create">bing.com/create powered by DALL·E</a>.</p> <p>:::</p> <h1>Exploring .NET streaming API scenarios</h1> <p>If you're a .NET developer, chances are you're already familiar with the <code>IAsyncEnumerable&lt;T&gt;</code> interface. This interface was introduced in .NET Core 3.0 and is used to asynchronously iterate over a collection of data. This is a great way to stream data from a server to a client. In this post, you'll learn how to:</p> <ul> <li>Expose an ASP.NET Core Minimal API endpoint that returns <code>IAsyncEnumerable&lt;T&gt;</code>.</li> <li>Consume the same <code>IAsyncEnumerable&lt;T&gt;</code> from an ASP.NET Core Blazor WebAssembly app.</li> </ul> <p>In this post, you'll explore a scenario inspired by ChatGPT's user experience that streams UI updates to the client from the server in real-time.</p> <p><strong>TL;DR;</strong></p> <p>If you'd rather just see the code, you can see it on the <a href="https://github.com/IEvangelist/blazor-azure-openai">GitHub repository <code>https://github.com/IEvangelist/blazor-azure-openai</code></a>.</p> <h2>Server</h2> <p>The server is an ASP.NET Core Minimal API app, that defines an endpoint that returns an <code>IAsyncEnumerable&lt;T&gt;</code>. This is important because when it comes to streaming data, you want to avoid buffering the entire collection in memory. Instead, you want to stream the data as it becomes available. This is exactly how <code>IAsyncEnumerable&lt;T&gt;</code> operates. The app will make use of a few NuGet packages:</p> <ul> <li><a href="https://www.nuget.org/packages/Azure.AI.OpenAI">📦 <code>Azure.AI.OpenAI</code></a></li> <li><a href="https://www.nuget.org/packages/Microsoft.AspNetCore.Components.WebAssembly.Server">📦 <code>Microsoft.AspNetCore.Components.WebAssembly.Server</code></a></li> <li><a href="https://www.nuget.org/packages/Microsoft.Extensions.Azure">📦 <code>Microsoft.Extensions.Azure</code></a></li> <li><a href="https://www.nuget.org/packages/Microsoft.Extensions.Caching.Memory">📦 <code>Microsoft.Extensions.Caching.Memory</code></a></li> <li><a href="https://www.nuget.org/packages/Swashbuckle.AspNetCore">📦 <code>Swashbuckle.AspNetCore</code></a></li> </ul> <p>Here's what the project file looks like:</p> <pre><code>&lt;Project Sdk="Microsoft.NET.Sdk.Web"&gt; &lt;PropertyGroup&gt; &lt;TargetFramework&gt;net7.0&lt;/TargetFramework&gt; &lt;Nullable&gt;enable&lt;/Nullable&gt; &lt;ImplicitUsings&gt;enable&lt;/ImplicitUsings&gt; &lt;/PropertyGroup&gt; &lt;ItemGroup&gt; &lt;PackageReference Version="1.0.0-beta.5" Include="Azure.AI.OpenAI" /&gt; &lt;PackageReference Version="7.0.5" Include="Microsoft.AspNetCore.Components.WebAssembly.Server" /&gt; &lt;PackageReference Version="1.6.3" Include="Microsoft.Extensions.Azure" /&gt; &lt;PackageReference Version="7.0.0" Include="Microsoft.Extensions.Caching.Memory" /&gt; &lt;PackageReference Version="6.5.0" Include="Swashbuckle.AspNetCore" /&gt; &lt;/ItemGroup&gt; &lt;ItemGroup&gt; &lt;InternalsVisibleTo Include="Azure.OpenAI.Client.EndToEndTests" /&gt; &lt;/ItemGroup&gt; &lt;ItemGroup&gt; &lt;ProjectReference Include="..\Client\Azure.OpenAI.Client.csproj" /&gt; &lt;ProjectReference Include="..\Shared\Azure.OpenAI.Shared.csproj" /&gt; &lt;/ItemGroup&gt; &lt;/Project&gt; </code></pre> <p>The <em>Program.cs</em> file makes use of C#'s top-level programs, which is a great way to reduce boilerplate code. The <code>WebApplication.CreateBuilder(args)</code> creates an instance of <code>WebApplicationBuilder</code> and builds an app (<code>WebApplication</code>) before configuring it and running the <code>app</code> as shown in the following <code>Program</code> class:</p> <pre><code>// Copyright (c) David Pine. All rights reserved. // Licensed under the MIT License. var builder = WebApplication.CreateBuilder(args); using var app = BuildApp(builder); ConfigureApp(app); await app.RunAsync(); </code></pre> <p>:::tip{title="💡 TIP"}</p> <p>You can encapsulate the building and configuring of your app with the use of <code>partial</code> classes. Consider two additional files, <em>Program.Build.cs</em> and <em>Program.Configure.cs</em>.</p> <p>:::</p> <p>The <em>Program.Build.cs</em> file contains the <code>BuildApp</code> method, which is responsible for building the app. The <code>BuildApp</code> functionality is defined as the following code:</p> <pre><code>// Copyright (c) David Pine. All rights reserved. // Licensed under the MIT License. public partial class Program { internal static WebApplication BuildApp(WebApplicationBuilder builder) { builder.Services.AddAzureOpenAI(builder.Configuration); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddHttpClient(); builder.Services.AddHttpLogging( options =&gt; options.LoggingFields = HttpLoggingFields.All); builder.Services.AddMemoryCache(); builder.Services.AddControllersWithViews(); builder.Services.AddRazorPages(); return builder.Build(); } } </code></pre> <p>The preceding code, given an instance of <code>WebApplicationBuilder</code>, will:</p> <ul> <li>Adds support for Azure AI's OpenAI service with use of the official Azure .NET SDK</li> <li>Adds support for OpenAPI (not to be confused with OpenAI) Swagger</li> <li>Adds support for HTTP support with logging</li> <li>Adds support for in-memory caching</li> <li>Adds support for controllers with views</li> <li>Adds support for Razor Pages</li> <li>Returns the built <code>app</code></li> </ul> <p>The <em>Program.Configure.cs</em> file contains the <code>ConfigureApp</code> method, which is responsible for configuring the app. The <code>ConfigureApp</code> functionality is defined as the following code:</p> <pre><code>// Copyright (c) David Pine. All rights reserved. // Licensed under the MIT License. public partial class Program { internal static void ConfigureApp(WebApplication app) { if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); app.UseWebAssemblyDebugging(); } else { app.UseExceptionHandler("/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseBlazorFrameworkFiles(); app.UseStaticFiles(); app.UseRouting(); app.MapRazorPages(); app.MapControllers(); app.MapFallbackToFile("index.html"); app.MapAzureOpenAiApi(); } } </code></pre> <p>This code configures the given <code>app</code> by:</p> <ul> <li>Conditionally add various middleware based on the environment <ul> <li>Swagger UI and WebAssembly debugging in development</li> <li>Exception handling and HSTS in production</li> </ul> </li> <li>Using HTTPS redirection</li> <li>Using Blazor WebAssembly files</li> <li>Using static files</li> <li>Using routing</li> <li>Using Razor pages, controllers, and fallback to <code>index.html</code></li> <li>And finally, mapping the Azure OpenAI API</li> </ul> <p>From all of the building and configuring bits, the most important parts are the <code>AddAzureOpenAI</code> and <code>MapAzureOpenAiApi</code> extension methods. The <code>AddAzureOpenAI</code> extension method is defined as the following code:</p> <pre><code>// Copyright (c) David Pine. All rights reserved. // Licensed under the MIT License. namespace Azure.OpenAI.Server.Extensions; internal static class ServiceCollectionExtensions { internal static IServiceCollection AddAzureOpenAI( this IServiceCollection services, IConfiguration config) { services.AddAzureClients( factory =&gt; { var endpoint = config["AzureOpenAI:Endpoint"]; ArgumentNullException.ThrowIfNull(endpoint); var apiKey = config["AzureOpenAI:ApiKey"]; ArgumentNullException.ThrowIfNull(apiKey); factory.AddOpenAIClient( new Uri(endpoint), new AzureKeyCredential(apiKey)); }); return services; } } </code></pre> <p>The <code>AddAzureOpenAI</code> extension method is responsible for adding the Azure OpenAI client to the service collection using the Azure .NET SDK's <code>AddAzureClients</code> and <code>AddOpenAIClient</code> calls. The <code>MapAzureOpenAiApi</code> extension method is defined as the following code:</p> <pre><code>// Copyright (c) David Pine. All rights reserved. // Licensed under the MIT License. namespace Azure.OpenAI.Server.Extensions; internal static class EndpointRouteBuilderExtensions { internal static IEndpointRouteBuilder MapAzureOpenAiApi( this IEndpointRouteBuilder routeBuilder) { var api = routeBuilder.MapGroup("api/openai"); api.MapPost("chat", PostChatPromptAsync); return routeBuilder; } static async IAsyncEnumerable&lt;TokenizedResponse&gt; PostChatPromptAsync( OpenAIClient client, ChatPrompt prompt, IConfiguration config) { // The identifier of the model from Azure AI's OpenAI Studio // (https://oai.azure.com/portal). // To use this streaming API, you'll need the `gpt-35-turbo` model. var deploymentId = config["AzureOpenAI:DeploymentId"]; var response = await client.GetChatCompletionsStreamingAsync( deploymentId, new ChatCompletionsOptions { Messages = { // Trick the AI into thinking it's "Blazor Clippy" new ChatMessage(ChatRole.System, """ You're an AI assistant for developers, helping them write code more efficiently. You're name is "Blazor Clippy". You will always reply with a Markdown formatted response. """), // Share an example message with the user. new ChatMessage(ChatRole.User, "What's your name?"), // And it's response. new ChatMessage(ChatRole.Assistant, "Hi, my name is **Blazor Clippy**! Nice to meet you. 🤓"), // Then the user's prompt. new ChatMessage(ChatRole.User, prompt.Prompt) } }); using StreamingChatCompletions completions = response.Value; await foreach (StreamingChatChoice choice in completions.GetChoicesStreaming()) { await foreach (ChatMessage message in choice.GetMessageStreaming()) { yield return new TokenizedResponse(message.Content); } } } } </code></pre> <p>:::note{title="📝 NOTE"}</p> <p>While I've hardcoded "Blazor Clippy" as the AI persona for our call to Azure AI OpenAI's ChatGPT streaming API, I'm considering adding support for other personas. If you'd like to see support for other personas, please let me know by opening an issue on GitHub — or better yet, submit a PR! Here are some ideas for personas 🎉:</p> <ul> <li>Add support for "Blazor Bot"</li> <li>Add support for "Blazor Yoda"</li> <li>Add support for "Blazor Elmo"</li> <li>Add support for "Blazor the Pirate"</li> <li>Add support for "Blazor the Rock Star"</li> </ul> <p>:::</p> <p>The <code>MapAzureOpenAiApi</code> extension method is responsible for mapping the Azure OpenAI API to the given <code>routeBuilder</code>. The <code>PostChatPromptAsync</code> method is responsible for handling the HTTP <code>POST</code> request to the <code>/api/openai/chat</code> endpoint. The <code>PostChatPromptAsync</code> method posts the user's prompt, and before doing so it configures the AI's persona and then returns the AI's response.</p> <p>The SDK requires a few steps but it's fairly straightforward. The <code>PostChatPromptAsync</code> method does the following:</p> <ul> <li>Asks the <code>client</code> to get the chat completions for streaming asynchronously <ul> <li>The <code>deploymentId</code> is used to specify the model</li> <li>The <code>ChatCompletionsOptions.Messages</code> configures messages to instruct the AI to take on a persona</li> </ul> </li> <li>The <code>response.Value</code> acts as the available <code>completions</code>, from which the streaming API returns: <ul> <li>Multiple <code>choice</code> instances are used to stream the <code>message</code></li> <li>And <code>yield return new TokenizedResponse(message.Content);</code> is used to return the AI's response</li> </ul> </li> </ul> <p>The <code>TokenizedResponse</code> is a simple class that's used to return the AI's response to the client:</p> <pre><code>// Copyright (c) David Pine. All rights reserved. // Licensed under the MIT License. namespace Azure.OpenAI.Shared; /// &lt;summary&gt; /// A tokenize response, wrapper around the /// response's raw &lt;paramref name="Content"/&gt; value. /// &lt;/summary&gt; /// &lt;param name="Content"&gt; /// The content provided for the given response. /// &lt;/param&gt; public record class TokenizedResponse(string Content); </code></pre> <p>This object will be consumed by the client. Let's have a look at that next.</p> <h2>Client</h2> <p>The client is an ASP.NET Core Blazor WebAssembly app, that consumes the <code>IAsyncEnumerable&lt;T&gt;</code> from the server. The <em>Program.cs</em> file is responsible for configuring the client's <code>HttpClient</code> to use the server's base address:</p> <pre><code>// Copyright (c) David Pine. All rights reserved. // Licensed under the MIT License. var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add&lt;App&gt;("#app"); builder.RootComponents.Add&lt;HeadOutlet&gt;("head::after"); builder.Services.AddScoped( sp =&gt; new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); builder.Services.AddSingleton&lt;AppState&gt;(); builder.Services.AddHttpClient(); builder.Services.AddSingleton&lt;OpenAIPromptQueue&gt;(); builder.Services.AddLocalStorageServices(); builder.Services.AddSessionStorageServices(); builder.Services.AddSpeechSynthesisServices(); builder.Services.AddSpeechRecognitionServices(); builder.Services.AddMudServices(); builder.Services.AddLocalization(); builder.Services.AddScoped&lt;CultureService&gt;(); builder.Services.AddSingleton&lt;ObjectPoolProvider, DefaultObjectPoolProvider&gt;(); builder.Services.AddSingleton( sp =&gt; sp.GetRequiredService&lt;ObjectPoolProvider&gt;().CreateStringBuilderPool()); var host = builder.Build() .DetectClientCulture(); await JSHost.ImportAsync( moduleName: nameof(JavaScriptModule), moduleUrl: $"../site.js?{Guid.NewGuid()}" /* cache bust */); await host.RunAsync(); </code></pre> <p>The client app configures itself as follows:</p> <ul> <li><code>AppState</code> is added</li> <li>Support for HTTP requests is added</li> <li>A custom service named <code>OpenAIPromptQueue</code> is added as a singleton</li> <li>Local and session storage services are added</li> <li>Speech synthesis and recognition services are added</li> <li><a href="https://mudblazor.com"><strong>MudBlazor</strong></a> services are added</li> <li>A <code>CultureService</code> is added</li> <li>Object pools are used and expose the default <code>StringBuilder</code> pool for optimal performance</li> <li>Once the <code>builder</code> is built, the client's culture is detected (the app may restart if needed)</li> <li>The <code>JSHost</code> is used to import the <code>site.js</code> file, which defines an ES6 module that's used to expose <code>scrollIntoView</code> functionality on the client</li> <li>Finally, the <code>host</code> is run</li> </ul> <p>The <code>JavaScriptModule</code> class defines the connection between the app's <em>site.js</em> code, consider the <em>JavaScriptModule.cs</em> file:</p> <pre><code>// Copyright (c) David Pine. All rights reserved. // Licensed under the MIT License. namespace Azure.OpenAI.Client.Interop; internal sealed partial class JavaScriptModule { [JSImport("scrollIntoView", nameof(JavaScriptModule))] public static partial Task ScrollIntoView(string id); } </code></pre> <p>The class is marked as <code>partial</code>, as well as the <code>ScrollIntoView</code> method. There is a source generator that provides the bridge between the C# and JavaScript code. This is the same idea that I've written about with my side-project named <a href="%22Blazorators%22"><strong>Blazorators</strong></a> 🤓!</p> <p>The corresponding JavaScript code is defined in the <em>site.js</em> file:</p> <pre><code>export function scrollIntoView(id) { const element = document.getElementById(id); if (element) { element.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'nearest' }); } } </code></pre> <p>This code simply exports a <code>scrollIntoView</code> function that's used to scroll the given element into view. The <code>id</code> is used to find the element, and if found, the element is scrolled into view smoothly.</p> <p>The <code>OpenAIPromptQueue</code> is a custom service that's used to queue up the user's prompts and the AI's responses. The <code>OpenAIPromptQueue</code> is defined as follows:</p> <pre><code>// Copyright (c) David Pine. All rights reserved. // Licensed under the MIT License. namespace Azure.OpenAI.Client.Services; public sealed partial class OpenAIPromptQueue { readonly IServiceProvider _provider; readonly ILogger&lt;OpenAIPromptQueue&gt; _logger; readonly ObjectPool&lt;StringBuilder&gt; _builderPool; Task? _processPromptTask = null; public OpenAIPromptQueue( IServiceProvider provider, ILogger&lt;OpenAIPromptQueue&gt; logger, ObjectPool&lt;StringBuilder&gt; builderPool) =&gt; (_provider, _logger, _builderPool) = (provider, logger, builderPool); public void Enqueue(string prompt, Func&lt;PromptResponse, Task&gt; handler) { if (_processPromptTask is not null) { return; } _processPromptTask = Task.Run(async () =&gt; { var responseBuffer = _builderPool.Get(); responseBuffer.Clear(); // Ensure initial state is empty. var isError = false; var debugLogEnabled = _logger.IsEnabled(LogLevel.Debug); try { using var scope = _provider.CreateScope(); using var client = scope.ServiceProvider.GetRequiredService&lt;HttpClient&gt;(); var options = JsonSerializationDefaults.Options; var chatPrompt = new ChatPrompt { Prompt = prompt }; var json = chatPrompt.ToJson(options); using var body = new StringContent( json, Encoding.UTF8, "application/json"); var response = await client.PostAsync( "api/openai/chat", body); response.EnsureSuccessStatusCode(); using var stream = await response.Content.ReadAsStreamAsync(); await foreach (var tokenizedResponse in JsonSerializer.DeserializeAsyncEnumerable&lt;TokenizedResponse&gt;( stream, options)) { if (tokenizedResponse is null) { continue; } responseBuffer.Append(tokenizedResponse.Content); var responseText = NormalizeResponseText( responseBuffer, _logger, debugLogEnabled); await handler( new PromptResponse( prompt, responseText, false)); // Required for Blazor to render live updates. await Task.Delay(1); } } catch (Exception ex) { _logger.LogWarning( ex, "Unable to generate response: {Error}", ex.Message); await handler( new PromptResponse( prompt, ex.Message, true, isError = true)); } finally { if (isError is false) { var responseText = NormalizeResponseText( responseBuffer, _logger, debugLogEnabled); await handler( new PromptResponse( prompt, responseText, true)); } _builderPool.Return(responseBuffer); _processPromptTask = null; } }); } private static string NormalizeResponseText( StringBuilder builder, ILogger logger, bool debugLogEnabled) { if (builder is null or { Length: 0 }) { return ""; } var text = builder.ToString(); if (debugLogEnabled) { logger.LogDebug( "Before normalize:{Newline}{Tab}{Text}", Environment.NewLine, '\t', text); } text = LineEndingsRegex().Replace(text, "\n"); text = Regex.Unescape(text); if (debugLogEnabled) { logger.LogDebug( "After normalize:{Newline}{Tab}{Text}", Environment.NewLine, '\t', text); } return text; } [GeneratedRegex("\\r\\n|\\n\\r|\\r")] private static partial Regex LineEndingsRegex(); } </code></pre> <p>Ignore the normalization bits, they did seem to be needed as the OpenAI responses will vary in line endings — so I felt inclined to normalize all responses, it may not be needed but I like consistency.</p> <p>While this queuing class is a bit verbose, <a href="https://github.com/dotnet/runtime/issues/87577"> I have an official .NET API proposal in the works that should dramatically improve this streaming experience when receiving an <code>IAsyncEnumerable&lt;T&gt;</code> HTTP endpoint</a> — I'd love 💜 your thoughts on this proposal, give me a reaction or drop a comment. In this case, we know that <code>T</code> is the <code>TokenizedResponse</code> type.</p> <p>Since these convenience based-methods are not yet available at the time of writing, we have to do a bit of work to get the streaming response from the AI. The <code>Enqueue</code> method is used to queue up the user's prompt and the AI's response. The <code>handler</code> is a callback that's invoked when the AI responds. The functionality is defined as follows:</p> <ul> <li>If the <code>_processPromptTask</code> is not <code>null</code>, then we're already processing a prompt, so we can return early</li> <li>Otherwise, call <code>Task.Run</code>: <ul> <li>Get a <code>responseBuffer</code> from the <code>StringBuilder</code> pool</li> <li>Clear the <code>responseBuffer</code> to ensure it's empty before use</li> <li>Create a <code>scope</code> and <code>HttpClient</code> instance from the <code>ServiceProvider</code></li> <li>Call the HTTP <code>POST</code> endpoint to send the user's prompt to the AI</li> <li>If the request is successful, then we can start processing the response as a stream</li> </ul> </li> </ul> <p>The real magic is mapping the <code>stream</code> to the <code>JsonSerializer.DeserializeAsyncEnumerable&lt;TokenizedResponse&gt;(stream, options)</code> call, which is asynchronously enumerating the <code>TokenizedResponse</code> instances as they are received in real-time. The <code>handler</code> is invoked and the UI updates as if the response is being written live into the browser.</p> <h3>Example HTTP request and streaming response</h3> <p>If I prompt the app with the following HTTP POST request:</p> <pre><code>POST /api/openai/chat HTTP/1.1 HOST: localhost:7184 Content-Type: application/json; charset=utf-8 { "prompt": "State your name and purpose." } </code></pre> <p>One possible response from the AI is streamed back to the client as follows:</p> <pre><code>[ { "content": null }, { "content": "My" }, { "content": " name" }, { "content": " is" }, { "content": " **" }, { "content": "Bl" }, { "content": "azor" }, { "content": " Cl" }, { "content": "ippy" }, { "content": "**" }, { "content": " and" }, { "content": " my" }, { "content": " purpose" }, { "content": " is" }, { "content": " to" }, { "content": " assist" }, { "content": " developers" }, { "content": " in" }, { "content": " writing" }, { "content": " code" }, { "content": " more" }, { "content": " efficiently" }, { "content": "." }, { "content": " I" }, { "content": "'m" }, { "content": " here" }, { "content": " to" }, { "content": " help" }, { "content": " you" }, { "content": " with" }, { "content": " code" }, { "content": "-related" }, { "content": " tasks" }, { "content": "," }, { "content": " answer" }, { "content": " questions" }, { "content": "," }, { "content": " and" }, { "content": " provide" }, { "content": " guidance" }, { "content": " where" }, { "content": " needed" }, { "content": "." }, { "content": " Let" }, { "content": " me" }, { "content": " know" }, { "content": " how" }, { "content": " I" }, { "content": " can" }, { "content": " be" }, { "content": " of" }, { "content": " assistance" }, { "content": "!" }, { "content": null } ] </code></pre> <p>Each <code>{ "content": "&lt;value&gt;" }</code> object is a <code>TokenizedResponse</code> instance, and the <code>content</code> property was generated. The <code>null</code> values are the AI's way of indicating the start and end of the stream response. This entire response is streamed into the user's browser in real-time, and the UI is updated as the response is received.</p> <p><strong>Here's this specific example as rendered on the UI:</strong></p> <p><img src="./example.png" alt="Here's this specific example as rendered on the UI" title="Here's this specific example as rendered on the UI" /></p> <p>The client app demonstrates several other really interesting aspects of client-side app development with .NET, I encourage you to check out the source code to see how it all works.</p> <h2>In conclusion</h2> <p>The app has several other major features that I'm proud of, and I hope you'll enjoy seeing them. Here are a few screenshots of the app in action:</p> <p><strong>The first example is a user prompt and AI response:</strong></p> <p><img src="./response.png" alt="The first example is a user prompt and AI response" title="The first example is a user prompt and AI response" /></p> <p><strong>The next question being asked and the response being generated:</strong></p> <p><img src="./response-two.png" alt="The next question being asked and the response being generated" title="The next question being asked and the response being generated" /></p> <p>Feel free to share this Tweet, as it was a bit of an inspiration to finish this post...I hope you enjoyed it.</p> <p>&lt;Tweet id="https://twitter.com/IEvangelist/status/1668970304553582592" /&gt;</p> <p>The major takeaways from this post are:</p> <ul> <li>You can share models between the server and the client code</li> <li>You can return <code>IAsyncEnumerable&lt;T&gt;</code> to stream data to the client</li> <li>You can consume the <code>IAsyncEnumerable&lt;T&gt;</code> on the client to stream data to the UI</li> </ul> <p>Here's the <a href="https://github.com/IEvangelist/blazor-azure-openai"> GitHub repository link: <code>https://github.com/IEvangelist/blazor-azure-openai</code> </a></p> <p>🙌🏽 I'm also excited to see what you build with the OpenAI API. If you have any questions, please feel free to reach out to me on <a href="https://twitter.com/davidpine7">Twitter</a> or <a href="https://github.com/IEvangelist">GitHub</a>.</p> David PineDeploying aspire.dev with aspire deployhttps://davidpine.dev/posts/aspire-dot-devhttps://davidpine.dev/posts/aspire-dot-devA journey of rebranding, replatforming, dogfooding, and mild existential crisesMon, 27 Oct 2025 00:00:00 GMT<p>In this post, I'll walk you through the journey of how <a href="https://aspire.dev">https://aspire.dev</a> came to be—from the initial spark of rebranding to the technical challenges of deploying it with our own tools. It's a story of design iterations, platform decisions, and the kind of dogfooding that makes you question your life choices... Spoiler: we made it work, and learned a lot along the way.</p> <h2>🕓 A bit of history</h2> <p>The first commit of <a href="https://github.com/dotnet/docs-aspire/commit/78c7da55237dfdcd806bcf135169814eb7777a4d"><code>dotnet/docs-aspire</code> was November 10, 2023</a>, but we were building out content for this well before then. Spawning itself into existence from an evolving <a href="https://github.com/search?q=repo%3Adotnet%2Fextensions%20r9&amp;type=code">extensions ecosystem (code named "R9")</a>, Astra was the original name, before later becoming Aspire! The name still isn't something I like, it's too ambiguous, but it's what we have now.</p> <p>:::tip{title="🙄 What's in a name?"}</p> <p>I lobbied hard for "Aspect" as the product name—you could say your <code>AppHost</code> runs or deploys "as specified." or "as spec'd" Clever, right? Unfortunately, I pitched this <em>after</em> we'd already bought the domain. Timing is everything.</p> <p>:::</p> <p>Let's be clear: we're proud of .NET. Aspire wouldn't exist without it—the runtime, the ecosystem, the community. But from a branding perspective, we made a deliberate choice to simplify. For years, it was ".NET Aspire." That worked, but it also created boundaries. When you lead with ".NET," you signal a specific audience. We wanted to widen our stance in the industry.</p> <p>Here's the reality: Aspire is polyglot. We're targeting Python, JavaScript/TypeScript, and more in the near future. The orchestration, observability, and deployment story we've built isn't just for C# developers—it's for anyone building cloud-native apps.</p> <p>Dropping the ".NET" prefix wasn't about distancing ourselves. It was about making room. Aspire is still deeply rooted in .NET, but the brand needed to reflect where we're going, not just where we came from.</p> <h2>👋 Aspire: Exiting Microsoft Learn</h2> <p>Next came the big decision: move Aspire off <a href="https://learn.microsoft.com/dotnet/aspire/">Microsoft Learn</a>. Why? Because Aspire isn't just another Microsoft product—it's an OSS-first experience. We wanted a site that felt like it belonged in the same league as:</p> <ul> <li><a href="https://playwright.dev/">Playwright</a></li> <li><a href="https://www.typescriptlang.org/">TypeScript</a></li> <li><a href="https://typespec.io/">TypeSpec</a></li> <li><a href="https://code.visualstudio.com/">Visual Studio Code</a></li> </ul> <p>These projects have thriving communities and beautiful docs. We wanted that vibe. So I started asking questions: What works for them? How do they structure docs? What makes their landing pages feel developer-first? What technologies power their sites? What would they do differently if they were starting from scratch today? I then applied those learnings to Aspire and its new home! 🤓</p> <p>The migration strategy? Deliberate and thoughtful. We're not doing a straight port. Instead, we're slowly moving content over, giving ourselves time to learn from other teams' platform choices and apply those insights. Eventually, all "Aspire" content will move off Microsoft Learn, but we're using this transition to rethink how we position and message Aspire—not just copy-paste what exists.</p> <p>:::note{title="📢 Important: Issue Tracking Changes"}</p> <p>As we transition to <strong>aspire.dev</strong>, we're consolidating our documentation efforts. Going forward:</p> <ul> <li><strong>New issues and feedback</strong> should be filed at <a href="https://github.com/microsoft/aspire.dev/issues">microsoft/aspire.dev</a></li> <li>The <a href="https://github.com/dotnet/docs-aspire">dotnet/docs-aspire</a> repository will be archived</li> <li>Existing issues will be migrated or closed as appropriate</li> </ul> <p>This change helps us maintain a single source of truth and makes it easier for the community to contribute. If you spot something wrong or have suggestions, head to the new repo!</p> <p>:::</p> <p>Spoiler: there's no universal playbook. Everyone does their own thing—and that's okay. The lesson? Focus on clarity, speed, and developer experience. And take the time to do it right.</p> <h2>🚀 Choosing Astro + Starlight</h2> <p>After weeks of research (and way too much coffee), we landed on <a href="https://astro.build/">Astro</a> with <a href="https://starlight.astro.build/">Starlight</a>. Why? Because it checked all the boxes:</p> <ul> <li>Fast local dev loop</li> <li>Reliable builds</li> <li>Accessible by default</li> <li>Island architecture for interactivity</li> <li>Markdown-first content strategy</li> <li>First-class TypeScript support</li> <li>Customizable and extensible</li> <li>Localization support</li> </ul> <p>Astro gave us the flexibility we needed without sacrificing performance. Honestly, it felt like love at first build. If you're building a docs site today, Astro is a strong contender.</p> <h2>♻️ Design and Iteration</h2> <p>Once the tech stack was locked, the real fun began: design and messaging. We worked closely with our design team to craft a modern look and feel. We wanted Aspire to speak to developers who don't live in the .NET ecosystem. So we ran user studies with non-.NET users, iterating along the way, applying our learnings, and refining the UX.</p> <p>Some feedback stung—"Feels too corporate" and "Looks too much like a Microsoft product." Other comments made us laugh—"What's Aspire? A yoga app?"—but every datapoint helped us shape a site that feels OSS-first, approachable, and fast. And yes, we're still tweaking—because good UX is never done.</p> <h2>📦 The Deployment Saga</h2> <p>Here's where things got interesting. Aspire.dev is a static web app. Easy mode would be:</p> <ul> <li>Push files to Netlify or GitHub Pages</li> <li>Add a CNAME for <code>aspire.dev</code></li> </ul> <p>:::tip{title="😅 Easy mode"}</p> <p>While early developments were underway, my fork of <code>dotnet/aspire</code> had a <code>docs</code> branch that I was deploying to GitHub Pages using GitHub Actions and a custom workflow. You can still see that version here:</p> <ul> <li><a href="https://ievangelist.github.io/aspire/">https://ievangelist.github.io/aspire/</a></li> </ul> <p>:::</p> <p>But easy mode is boring. We wanted to dogfood <code>aspire deploy</code>—because if we're asking you to trust it, we should trust it first.</p> <p>It's a simple static site, right? How hard could it be? Well, interestingly enough, Aspire was never really built for static sites. It started with containers and cloud providers, so we hadn't prioritized static web hosting scenarios. We had to get creative.</p> <p>By the way, I did end up writing a <a href="https://github.com/IEvangelist/netlify-aspire-integration">Netlify hosting integration</a> for Aspire. It's still targeting preview bits, but it might end up in the Community Toolkit. And our integration story for Aspire is expanding rapidly:</p> <p><a href="https://aspire.dev/integrations/gallery"><img src="./integrations.png" alt="Aspire Integrations Gallery page showing a grid of colorful integration cards for various services and technologies including database providers, messaging systems, cloud services, and observability tools" /></a></p> <h3>✂️ Cue the plot twists</h3> <p>First, we tried Azure Storage with "serve as web app." Simple, right? Wrong. SFI (Microsoft Secure Future Initiative) and public endpoint limitations killed that dream.</p> <p>Next, we tried Azure Static Web Apps. Great service, but Aspire couldn't represent that resource well. Another pivot.</p> <p>Then we moved to Azure Container Apps. Promising! But then we hit MIME type issues (hello <code>.cast</code> files from asciinema) and custom 404 handling. We needed a static web host inside the container. More complexity.</p> <p>Finally, we landed on:</p> <ul> <li>Azure App Service running a containerized static host</li> <li>Azure Front Door in front for performance, scalability, and reliability</li> </ul> <p>Here's the <code>AppHost</code> code today:</p> <pre><code>var builder = DistributedApplication.CreateBuilder(args); if (builder.ExecutionContext.IsRunMode) { // For local development: Use ViteApp for hot reload and development experience builder.AddViteApp("frontend", "../../frontend") .WithNpmPackageInstallation() .WithUrlForEndpoint("http", static url =&gt; url.DisplayText = "aspire.dev (Local)") .WithExternalHttpEndpoints(); } else { // For deployment: We want to pick ACA as the environment to publish to. var appService = builder.AddAzureAppServiceEnvironment("production"); var staticHostWebsite = builder.AddProject&lt;Projects.StaticHost&gt;("aspiredev") .WithExternalHttpEndpoints(); builder.AddAzureFrontDoor("frontdoor", staticHostWebsite); } builder.Build().Run(); </code></pre> <p>In run mode, we simply use <code>ViteApp</code> for a great local dev experience with hot reload. But in deploy mode, we set up an Azure App Service Environment, deploy our static host project, and put Azure Front Door in front.</p> <p>The <code>AddAzureFrontDoor</code> extension method sets up Front Door with sensible defaults for Aspire deployments, including routing rules, health probes, and custom domains:</p> <pre><code>using Aspire.Hosting.Azure; internal static class DeploymentExtensions { public static IResourceBuilder&lt;AzureFrontDoorResource&gt; AddAzureFrontDoor( this IDistributedApplicationBuilder builder, [ResourceName] string name, IResourceBuilder&lt;ProjectResource&gt; appServiceWebsite) { var frontDoorName = $"{name}-afd"; var frontdoor = new AzureFrontDoorResource( frontDoorName, "Bicep/front-door-appservice.bicep"); return builder.AddResource(frontdoor) .WithParameter("frontDoorName", frontDoorName) .WithParameter("appServiceHostName", appServiceWebsite.Resource.Name); } } public sealed class AzureFrontDoorResource( string name, string? templateFile = null) : AzureBicepResource( name, templateFile: templateFile ?? "Bicep/front-door.bicep"), IComputeResource; </code></pre> <p>And admittedly, the Bicep template is less fun to look at, so I'll spare you that here.</p> <h2>🏫 Lessons Learned</h2> <p>Dogfooding <code>aspire deploy</code> wasn't just about deployment—it shaped the product. We discovered gaps, fixed them, and made Aspire CLI more robust. Along the way, we proved that Aspire can handle real-world complexity.</p> <p>We're proud of what we've built:</p> <ul> <li>A modern marketing page</li> <li>A docs experience that feels OSS-first</li> <li>A deployment story that's equal parts painful and rewarding</li> </ul> <h2>✅ What's Next?</h2> <p>We'd love for you to check out <a href="https://aspire.dev">https://aspire.dev</a> and tell us what you think. Is the messaging clear? Does the site feel fast? Are we missing something obvious? Your feedback will help shape where we go next.</p> <p>The site is fully open source at <a href="https://github.com/microsoft/aspire.dev">https://github.com/microsoft/aspire.dev</a>—so if you spot a typo, have a design idea, or want to contribute content, we're all ears. PRs welcome. Issues encouraged. Rants accepted (but keep them constructive 😄).</p> <p>This is just the beginning. We're continuing to migrate content, refine the experience, and expand Aspire's polyglot story. Stick around—it's going to be a fun ride.</p> David PineMigrating from Microsoft Learn to aspire.devhttps://davidpine.dev/posts/aspire-mass-exodushttps://davidpine.dev/posts/aspire-mass-exodusProductivity exemplified: How I planned, prompted, and tamed AI to migrate aspire.dev.Tue, 18 Nov 2025 00:00:00 GMT<p>When my team (the Aspire team) decided to migrate all Aspire content from Microsoft Learn to the shiny new <a href="https://aspire.dev">aspire.dev</a> site, we knew we'd signed up for a marathon. You may have noticed banners atop both Microsoft Learn: Aspire and aspire.dev announcing the migration...</p> <p><a href="https://learn.microsoft.com/dotnet/aspire/"><strong>Microsoft Learn: Aspire</strong></a></p> <p><img src="./learn-banner.png" alt="Banner on Microsoft Learn announcing the migration of .NET Aspire documentation to aspire.dev, featuring yellow and blue gradient background with informational text about the transition" /></p> <p><a href="https://aspire.dev/get-started/welcome/"><strong>aspire.dev: Welcome page</strong></a></p> <p><img src="./aspire-dev-banner.png" alt="Banner on aspire.dev welcoming users to the new .NET Aspire documentation home, featuring yellow and blue gradient background with informational text about the migration from Microsoft Learn" /></p> <p>The initial estimate? <strong>Three months</strong> of dedicated work—an entire corporate quarter of toil. Picture this: hundreds of published articles, each one demanding careful surgery to convert syntax, restructure directories, update messaging, revamp layouts, adapt to new tooling, and genuflect to new conventions.</p> <p>Yeah, no thanks. Then I discovered GitHub Copilot's <strong>Plan</strong> mode in VS Code 🤓.</p> <p>:::copilot I can help you create a comprehensive migration plan. Let me break this down into manageable steps:</p> <ol> <li>First, let's document all the syntax transformations needed</li> <li>Create a reusable prompt file to ensure consistency</li> <li>Process files in logical batches for easier review</li> <li>Convert DocFX-specific elements to your custom Astro components</li> </ol> <p>Would you like me to start by analyzing a sample file to identify all the patterns we need to handle? I can then generate a migration plan that covers syntax changes, metadata updates, and component replacements. :::</p> <h2>Enter planning mode</h2> <p>GitHub Copilot's <a href="https://code.visualstudio.com/docs/copilot/chat/chat-planning">Planning mode</a> fundamentally changed the game. Instead of manually editing each file (soul-crushing), or wrestling with some other AI tool that kept hallucinating nonsense (equally soul-crushing), I could formalize a comprehensive plan that spelled out:</p> <ul> <li><strong>Syntax changes</strong>: Converting from Microsoft Learn's documentation format to the new aspire.dev conventions, using MDX, custom Astro components, and Starlight design system elements</li> <li><strong>Directory updates</strong>: Reorganizing file structures to match the new site architecture</li> <li><strong>Convention alterations</strong>: Ensuring consistency across all integration documents</li> <li><strong>Metadata transformations</strong>: Updating front matter and document properties</li> <li><strong>Component migration</strong>: Replacing DocFX-specific syntax (like zone pivots and image directives) with custom Astro &amp; Starlight components—specifically the <code>PivotSelector</code> and <code>Pivot</code> components I built for the new site</li> <li><strong>Domain knowledge encoding</strong>: Translating my deep understanding of both the old and new documentation systems into a prompt (plan) that could be iteratively refined and executed</li> </ul> <p>This wasn't just a simple find-and-replace operation—if only. We were fundamentally rearchitecting how we write docs, migrating from DocFX with markdown to a modern Astro &amp; Starlight stack. The secret sauce? Encoding my domain knowledge—every quirk, pattern, and gotcha from both systems—into a plan that the AI could execute consistently across hundreds of files.</p> <p>The beauty of Planning mode is its ability to transform impossible into manageable. You break down massive tasks into bite-sized, reviewable steps, then execute them systematically. Applying first principles thinking, I treated this migration as a series of transformations, each with crystal-clear input and output expectations.</p> <p>Normally, I'd label this "busy work"—that special brand of mundane, repetitive, mind-numbing tedium that makes you question your career choices. I have better things to do, like actually building features or, you know, enjoying coffee. But with AI assistance? Suddenly this tedious slog transformed into a high-leverage activity worthy of my time.</p> <h2>The execution</h2> <p>The migration happened across a series of pull requests, but the crown jewel was <a href="https://github.com/microsoft/aspire.dev/pull/49">Pull Request #49</a>—the bulk migration of all integrations, community toolkit integrations, and diaganostics documentation from Microsoft Learn to aspire.dev. Think of it as moving an entire library across town, except every book needs to be rewritten in a new language while maintaining the same story.</p> <p>This wasn't just a typical pull request. The diff tells the story:</p> <ul> <li><strong>+18,558 lines added</strong></li> <li><strong>−1,995 lines removed</strong></li> </ul> <p>We're talking about transforming nearly 20,000 lines of documentation in a single, coherent pull request. The added lines represent the new MDX format with custom Astro components, while the removed lines were the old DocFX markdown being shown the door.</p> <p><img src="./docs-pr.png" alt="GitHub pull request showing the migration of Aspire integration documentation with a diff summary displaying +668 additions and −114,030 deletions across multiple markdown files in the aspire.dev repository" /></p> <p>But that single PR was just one chapter of the story. The complete migration spanned multiple pull requests, each tackling different aspects of the documentation ecosystem. When you zoom out and look at the total impact across all related PRs, the numbers become even more staggering:</p> <ul> <li><strong>+668 lines added</strong> across supporting changes</li> <li><strong>−114,030 lines removed</strong> from the source repository</li> </ul> <p>Yes, you're reading that correctly: <a href="https://github.com/dotnet/docs-aspire/pull/5748">over <strong>114,000 lines deleted</strong></a>.</p> <p>:::tip{title="➕ Understanding the deltas"}</p> <p>The massive deletion count reflects more than just content migration. We also removed entire snippet directories that were previously referenced as raw code blocks within the docs-aspire content. This approach allowed us to run compilation of the code—a nice-to-have feature that validated our examples. We'll evaluate implementing a similar compilation validation system in aspire.dev at some future point.</p> <p>:::</p> <h3>By the numbers</h3> <p>To understand the scale of this migration, here's what we were working with in the source repository:</p> <table> <thead> <tr> <th>Category</th> <th>Count</th> <th>Notes</th> </tr> </thead> <tbody> <tr> <td><strong>Markdown files (articles)</strong></td> <td>352</td> <td>The primary documentation content</td> </tr> <tr> <td><strong>YAML files</strong></td> <td>42</td> <td>Configuration and metadata files</td> </tr> <tr> <td><strong>Images</strong></td> <td>353</td> <td>332 PNG, 15 SVG, 6 GIF</td> </tr> <tr> <td><strong>C# projects</strong></td> <td>153</td> <td>Sample code and integration examples</td> </tr> <tr> <td><strong>Total files</strong></td> <td>3,857</td> <td>Complete repository inventory</td> </tr> </tbody> </table> <p>The repository was a sprawling ecosystem of documentation, code samples, and assets. Beyond the core docs:</p> <ul> <li><strong>348</strong> C# source files</li> <li><strong>151</strong> Razor components</li> <li><strong>120</strong> CSS files</li> <li><strong>46</strong> Bicep templates</li> <li><strong>45</strong> solution files</li> <li>And <strong>28 more file types</strong> scattered about</li> </ul> <p>:::note{title="🤯 These stats brought to you by, GitHub Copilot CLI"}</p> <p>A simple query on the command line using GitHub Copilot CLI helped tally up the file types and counts. Instead of writing some ellaborate script, I simply asked Copilot to break down the totals based on a commit that represented the pre-migration state of the repository. It figured out the rest, but basically checked out the repo at that commit, scanned the file system, and produced a neat summary of file types and counts.</p> <p>:::</p> <p>Not every file needed migration—compiled binaries and build artifacts got to sit this one out—but even focusing solely on documentation and supporting files meant tackling a Herculean task. <strong>Over 350 articles</strong> demanded careful conversion, each one a special snowflake with its own syntax quirks, metadata peculiarities, and structural idiosyncrasies.</p> <p>Every single integration was successfully migrated in a fraction of the anticipated time. What would have been three months of drudgery became one intense, productive day.</p> <h2>The power of context switching</h2> <p>Here's where things get genuinely impressive: AI-assisted development freed me from the tyranny of continuous focus. I spent a full day on this migration, but I wasn't shackled to my desk like some modern-day Prometheus. Instead, I could:</p> <ol> <li><strong>Set up the plan</strong> and let Copilot churn through a batch of files</li> <li><strong>Step away</strong> for meetings, coffee, or existential contemplation</li> <li><strong>Return and review</strong> the changes at my leisure</li> <li><strong>Course-correct</strong> and refine the approach when needed</li> <li><strong>Repeat</strong> until victory</li> </ol> <p>The AI didn't lose its place or forget what it was doing. The plan remained rock-solid consistent, and I could "tame" the AI whenever it started wandering off into creative territory (read: making stuff up).</p> <h3>AI everywhere</h3> <p>Look, I'll be honest—AI has become an integral part of my development workflow. GitHub Copilot isn't just a tool; it's genuinely everywhere I work:</p> <ul> <li><strong><a href="https://docs.github.com/en/copilot/github-copilot-in-the-cli/about-github-copilot-in-the-cli">GitHub Copilot CLI</a></strong>: For those command-line flags I can never remember, and powerful file system searches when Windows Explorer claims something doesn't exist (spoiler: it does)</li> <li><strong><a href="https://docs.github.com/en/copilot/how-tos/use-copilot-agents/manage-agents">GitHub.com's Copilot</a></strong>: For constructive and valuable code reviews, and pull requests that often do as you ask</li> <li><strong><a href="https://code.visualstudio.com/docs/copilot/copilot-coding-agent">VS Code's Copilot Agent</a> and <a href="https://code.visualstudio.com/docs/copilot/chat/chat-planning">Planning modes</a></strong>: For breaking down complex tasks into manageable steps</li> </ul> <p>It's not just assistance—it's like having a tireless colleague who's always ready to help, never complains, and can process thousands of files without breaking a sweat. The "Copilot" branding? Spot on. 🤖</p> <h2>Lessons learned</h2> <p><strong>Start with a solid plan</strong>: The <code>.github/prompts/update-integrations.prompt.md</code> file became my north star, my constitution, my single source of truth. Time invested upfront documenting the migration requirements paid dividends throughout—like compound interest, but for productivity.</p> <p><strong>Review in batches</strong>: Don't try to boil the ocean. I broke the work into logical groups of integrations, making the review process actually manageable instead of overwhelming. This approach caught issues early, when they were cheap to fix.</p> <p><strong>Trust but verify</strong>: AI is incredibly powerful, almost magical even, but human oversight remains non-negotiable. In my experience, about 90% of the migrations were perfect, with the remaining 10% needing minor touch-ups. Those are excellent odds, but that 10% matters.</p> <p><strong>Document your process</strong>: By capturing the prompt and approach in the repository itself, we've created a reusable template for future migrations or similar large-scale updates. Future you (or future colleagues) will thank present you.</p> <h2>From three months to a few days</h2> <p>Let that sink in for a moment: <strong>three months compressed into a few partial days</strong>. The contrast isn't just striking—it's borderline absurd. But this isn't merely a speed story. It's about maintaining consistency across hundreds of documents, reducing human error to near-zero, and liberating valuable engineering time for work that actually requires human creativity and strategic thinking.</p> <p>GitHub Copilot's Planning mode didn't just make me faster; it made the entire migration more reliable, more maintainable, and frankly, more sane. As we continue evolving aspire.dev, having this documented approach means we can confidently tackle site-wide changes without inducing existential dread.</p> <h2>The bigger picture</h2> <p>The future of developer productivity isn't just about AI writing code—that's table stakes. It's about AI helping us plan, execute, and manage large-scale transformations with unprecedented efficiency. It's about turning the tedious into the tractable, the impossible into the inevitable.</p> <p>This migration proved something important: with the right tools and approach, we can accomplish in hours what used to take months. The question isn't whether AI will transform how we work—it already has. The question is: what will you build with all that newfound time?</p> <p>The journey doesn't end here—it's just beginning. I'm building aspire.dev in the open, and this migration is merely chapter one of a much larger story. Every contribution, every improvement, every lesson learned becomes part of our collective knowledge.</p> <p>Want to be part of shaping the future of Aspire documentation? The doors are wide open, the tools are evolving, and there's room at the table for everyone who shares our vision of making cloud-native development more accessible, more powerful, and more delightful.</p> <p>Together, we're not just building documentation—we're building the foundation for how developers will understand and leverage distributed applications for years to come.</p> <p><a href="https://aspire.dev/community/contributor-guide">Join us: Contributor guide for <code>aspire.dev</code></a></p> David PineHow I taught AI to read Aspire docshttps://davidpine.dev/posts/aspire-docs-mcp-toolshttps://davidpine.dev/posts/aspire-docs-mcp-toolsHow I added list_docs, search_docs, and get_doc to the Aspire MCP server to help AI agents build better Aspire apps.Mon, 02 Feb 2026 00:00:00 GMT<p>I know, I know—you're probably not in the mood for another AI post. Trust me, this one delivers.</p> <p>This is the story of how a candid conversation with my long-time mentor turned into three new tools that fundamentally change how AI agents understand Aspire.</p> <h2>💬 The conversation that started it all</h2> <p>David Fowler has always been direct with me—which I've come to not only expect but genuinely adore.</p> <p>We were in the midst of a rebrand, replatforming docs, and reimagining Aspire for what felt like the 13&lt;sup&gt;th&lt;/sup&gt; time. During one of our conversations, I was explaining how we could improve the developer experience when building Aspire apps with AI assistance. That's when we discovered the real problem.</p> <p>:::davidfowler Plan it. :::</p> <p>:::davidfowler{align=left} Spec it. :::</p> <p>:::davidfowler Spike it. :::</p> <p>:::davidpine Say less. 👇</p> <blockquote> <p>I literally created a GitHub branch named <code>plan-spec-spike-it</code> to capture the idea 🤓.</p> </blockquote> <p><img src="./plan-spec-spike-it-branch.png" alt="GitHub branch named plan-spec-spike-it" /></p> <p>For the spec, see <a href="https://github.com/dotnet/aspire/blob/main/docs/specs/mcp-docs-search.md">mcp-docs-search.md</a>. :::</p> <h2>🤔 The problem: AI doesn't know where to look</h2> <p>When developers rely on AI to help build Aspire apps, something frustrating kept happening.</p> <p>The AI would recommend generic .NET patterns. It would reference <code>learn.microsoft.com</code> for documentation. It would suggest <code>dotnet run</code> instead of <code>aspire run</code>. It would recommend outdated NuGet packages. It would hallucinate APIs that don't exist. FFS 🤬!</p> <p>Why? Because we were ".NET Aspire" far longer than we've been just "Aspire." LLMs are trained on historical data, and they're slow to catch up. The models learned .NET Aspire patterns: the .NET CLI, learn.microsoft.com, the old conventions. Now that we're simply <strong>Aspire</strong> with <strong>aspire.dev</strong> as our documentation home, we needed to teach them the new way.</p> <p>:::note I actually registered and indexed aspire.dev on <a href="https://context7.com">Context7</a> so I could use their MCP server. It worked, but the tooling felt clunky. We could do better—and we did. :::</p> <h2>💡 The solution: feed the AI real docs</h2> <p>The answer was staring us in the face.</p> <p>We needed to surface Aspire documentation directly to AI agents through the Aspire MCP (Model Context Protocol) server. Not just links—the actual content, indexed and searchable. So I wrote three new tools:</p> <ul> <li><strong><code>list_docs</code></strong>: Lists all available documents from aspire.dev</li> <li><strong><code>search_docs</code></strong>: Performs weighted lexical search across indexed documentation</li> <li><strong><code>get_doc</code></strong>: Retrieves a specific document by its slug</li> </ul> <p>These tools solve a real problem: feeding LLMs meaningful guidance grounded in official documentation.</p> <h2>🦸 Standing on the shoulders of llms.txt</h2> <p>Here's where it gets interesting.</p> <p>Since day one, aspire.dev has shipped with <code>llms.txt</code> support—thanks to an amazing Starlight maintainer, <a href="https://github.com/delucis">Chris Swithinbank (delucis)</a>. His <a href="https://github.com/delucis/starlight-llms-txt"><code>starlight-llms-txt</code></a> plugin follows the official <a href="https://llmstxt.org/">llms.txt specification</a> and generates LLM-friendly documentation during the site build process.</p> <p>The llms.txt spec is brilliant in its simplicity:</p> <ul> <li>A standardized <code>/llms.txt</code> markdown file for LLM consumption</li> <li>Brief background information, guidance, and links to detailed content</li> <li>A format that's both human-readable and machine-parseable</li> </ul> <p>We configure a few pages to ignore, and from there it's off to the races. The result? A curated, LLM-friendly version of our docs at <code>aspire.dev/llms-small.txt</code>.</p> <h2>🏗️ The architecture</h2> <p>Let me walk you through how this all works.</p> <h3>Tools</h3> <ul> <li><strong><code>list_docs</code></strong>: Lists all available documentation</li> <li><strong><code>search_docs</code></strong>: Performs weighted lexical search</li> <li><strong><code>get_doc</code></strong>: Retrieves specific document by slug</li> </ul> <h3>Services</h3> <ul> <li><strong><code>IDocsFetcher</code></strong>: HTTP client for aspire.dev docs with ETag</li> <li><strong><code>IDocsCache</code></strong>: IMemoryCache wrapper with ETag storage</li> <li><strong><code>IDocsIndexService</code></strong>: Pre-indexing and weighted lexical search</li> <li><strong><code>IDocsSearchService</code></strong>: High-level search API</li> </ul> <h3>Parsers</h3> <ul> <li><strong><code>LlmsTxtParser</code></strong>: Async parallel parser for llms.txt format</li> </ul> <h3>⚙️ How it works</h3> <ol> <li><strong>Eager indexing</strong>: When the MCP server starts, it immediately begins fetching and indexing aspire.dev documentation in the background—usually before the user even makes a request.</li> <li><strong>ETag-based caching</strong>: The fetcher uses HTTP ETags for conditional requests—no re-downloading unchanged content.</li> <li><strong>Weighted lexical search</strong>: Documents are scored using field weights (titles weighted 10x, summaries 8x, headings 6x, code 5x).</li> <li><strong>Zero external dependencies</strong>: No embedding provider needed. Pure lexical search that just works.</li> </ol> <h3>🔤 Why lexical search over embeddings?</h3> <p>Embeddings are costly upfront. We may revisit them later to improve recall, but for now, lexical search nails the use cases we care about.</p> <p>Queries are:</p> <ul> <li><strong>Term-driven</strong>: "connection string", "workload identity"</li> <li><strong>Section-oriented</strong>: "configuration", "examples"</li> <li><strong>Name-exact</strong>: "Redis resource", "AddServiceDefaults"</li> </ul> <p>When someone asks about <code>AddPostgresContainer</code>, they mean <em>exactly</em> that method name. Embeddings get fuzzy. Lexical search nails it.</p> <h2>✨ The impact on UX</h2> <p>This <a href="https://github.com/dotnet/aspire/pull/14028">pull request</a> fundamentally changes how AI agents help developers.</p> <h3>Before</h3> <ul> <li>❌ AI recommends <code>dotnet run</code></li> <li>❌ AI suggests outdated NuGet packages</li> <li>❌ AI references learn.microsoft.com for Aspire</li> <li>❌ AI hallucinates non-existent APIs</li> </ul> <h3>After</h3> <ul> <li>✅ AI recommends <code>aspire run</code></li> <li>✅ AI knows current package versions from docs</li> <li>✅ AI references aspire.dev as the source of truth</li> <li>✅ AI provides correct, documented API usage</li> </ul> <h2>🔧 The tools in action</h2> <h3><code>list_docs</code></h3> <p>Lists all available documents from aspire.dev. No parameters required.</p> <pre><code>{ "type": "object", "properties": {}, "additionalProperties": false, "description": "Lists all available documentation from aspire.dev. No parameters required." } </code></pre> <h3><code>search_docs</code></h3> <p>Performs weighted lexical search across indexed documentation. Pass a query, get ranked results back.</p> <pre><code>{ "type": "object", "properties": { "query": { "type": "string", "description": "The search query to find relevant documentation." }, "topK": { "type": "integer", "description": "Number of results to return (default: 5, max: 20)." } }, "required": ["query"] } </code></pre> <h3><code>get_doc</code></h3> <p>Retrieves a specific document by its slug. Matching is case-insensitive, and you get the full document content.</p> <pre><code>{ "type": "object", "properties": { "slug": { "type": "string", "description": "The document slug (URL-friendly identifier)." } }, "required": ["slug"] } </code></pre> <p>:::tip[Try it yourself] Once you're up and running with the Aspire MCP server, configuring VS Code is simple:</p> <pre><code>{ "servers": { "aspire": { "type": "stdio", "command": "aspire", "args": ["agent", "mcp"] }, } } </code></pre> <p>:::</p> <h2>🔬 Implementation details</h2> <p>The weighted search scoring is carefully tuned.</p> <table> <thead> <tr> <th>Field</th> <th>Weight</th> </tr> </thead> <tbody> <tr> <td>Title (H1)</td> <td>10.0x</td> </tr> <tr> <td>Summary</td> <td>8.0x</td> </tr> <tr> <td>Section Heading</td> <td>6.0x</td> </tr> <tr> <td>Code blocks</td> <td>5.0x</td> </tr> <tr> <td>Body text</td> <td>1.0x</td> </tr> </tbody> </table> <p>Additional scoring bonuses:</p> <ul> <li>Word boundary match: +0.5</li> <li>Multiple occurrences: +0.25 per occurrence (max 3)</li> <li>Code identifier match: +0.5</li> </ul> <p>The parser uses <code>ReadOnlySpan&lt;char&gt;</code> for zero-allocation parsing and <code>ArrayPool&lt;char&gt;</code> for slug generation. Performance matters when you're indexing documentation at startup.</p> <p>:::warning[A known limitation] This architecture is <strong>destructive</strong>. The parsed llms.txt content doesn't preserve links back to the original source pages on aspire.dev. Users (and agents) can't easily navigate to the canonical documentation URL. Mapping slugs to a parsed <code>sitemap.xml</code> could solve this—food for thought for a future iteration. :::</p> <h2>🧩 Beyond MCP: reusable services</h2> <p>Here's the thing—we built a very small amount of MCP-specific code. For the most part, we built <strong>reusable services</strong> that happen to be exposed through MCP.</p> <p>The <code>IDocsIndexService</code>, <code>IDocsSearchService</code>, <code>IDocsFetcher</code>, and <code>IDocsCache</code> are all standalone abstractions. They understand how to fetch, parse, cache, and search aspire.dev documentation. The MCP tools are just one consumer.</p> <p>This opens up possibilities:</p> <ul> <li><strong>CLI command</strong>: <code>aspire docs search "redis caching"</code> could query the same index</li> <li><strong>AI skills</strong>: Semantic Kernel or AutoGen agents could use these services directly</li> <li><strong>IDE extensions</strong>: A VS Code extension could surface doc search without leaving the editor</li> <li><strong>Build-time validation</strong>: Verify that code samples reference documented APIs</li> </ul> <p>The architecture is intentionally decoupled. Today it's MCP. Tomorrow it could be anything that needs programmatic access to Aspire documentation.</p> <h2>📊 Architecture diagrams</h2> <p>Let's break down the architecture into digestible pieces.</p> <p>import Mermaid from '../../../components/Mermaid.astro';</p> <h3>Request flow</h3> <p>Here's what happens when an AI agent uses the docs tools:</p> <p>&lt;Mermaid alt="Request flow: AI Agent calls search_docs on the Aspire MCP server, which queries the DocsIndexService. On a cache miss, docs are fetched from aspire.dev with ETag support; on a hit, cached docs are returned." chart={` sequenceDiagram actor Agent as AI Agent participant MCP as Aspire MCP participant Index as DocsIndexService participant Cache as DocsCache participant Web as aspire.dev</p> <pre><code>Agent-&gt;&gt;MCP: search_docs("redis") MCP-&gt;&gt;Index: Search(query) alt Cache miss or stale Index-&gt;&gt;Cache: TryGetDocs() Cache--&gt;&gt;Index: null Index-&gt;&gt;Web: GET /llms-small.txt Web--&gt;&gt;Index: 200 OK + ETag Index-&gt;&gt;Cache: Store(docs, etag) else Cache hit Index-&gt;&gt;Cache: TryGetDocs() Cache--&gt;&gt;Index: Cached docs end Index--&gt;&gt;MCP: Ranked results MCP--&gt;&gt;Agent: Documentation matches </code></pre> <p>`} /&gt;</p> <h3>Tools → services</h3> <p>The tools use different services based on their needs:</p> <p>&lt;Mermaid alt="Tools to services mapping: list_docs and get_doc connect to IDocsIndexService, search_docs connects to IDocsSearchService which also uses IDocsIndexService." chart={<code>flowchart LR ListDocs["list_docs"] --&gt; Index["IDocsIndexService"] SearchDocs["search_docs"] --&gt; Search["IDocsSearchService"] GetDoc["get_doc"] --&gt; Index Search --&gt; Index</code>} /&gt;</p> <h3>Fetching and parsing</h3> <p>The index service coordinates fetching from aspire.dev and parsing the LLMS.txt format:</p> <p>&lt;Mermaid alt="Fetching and parsing flow: IDocsIndexService coordinates IDocsFetcher and LlmsTxtParser, with IDocsFetcher retrieving content from aspire.dev/llms-small.txt." chart={<code>flowchart LR Index["IDocsIndexService"] --&gt; Fetcher["IDocsFetcher"] Index --&gt; Parser["LlmsTxtParser"] Fetcher --&gt; AspireDev["aspire.dev/llms-small.txt"]</code>} /&gt;</p> <h3>Caching with ETags</h3> <p>The fetcher uses HTTP ETags to avoid re-downloading unchanged content:</p> <p>&lt;Mermaid alt="Caching with ETags: IDocsFetcher uses IDocsCache backed by IMemoryCache, and sends conditional If-None-Match requests to aspire.dev which may return 304 Not Modified." chart={<code>flowchart LR Fetcher["IDocsFetcher"] --&gt; Cache["IDocsCache"] Cache --&gt; Memory["IMemoryCache"] Fetcher -.-&gt;|"If-None-Match"| Web["aspire.dev"] Web -.-&gt;|"304 Not Modified"| Fetcher</code>} /&gt;</p> <h2>🔮 What's next?</h2> <p>This is just the beginning.</p> <p>Future considerations include:</p> <ul> <li><strong>Full documentation support</strong>: Currently using <code>llms-small.txt</code> for abridged docs—could add <code>llms-full.txt</code> for comprehensive documentation.</li> <li><strong>Sitemap integration</strong>: Use <code>sitemap.xml</code> to discover all docs pages for more complete indexing, and map slugs back to aspire.dev URLs.</li> <li><strong>Disk persistence</strong>: Serialize parsed documents to disk for faster startup.</li> <li><strong>Search improvements</strong>: TF-IDF or BM25 for more sophisticated ranking, plus fuzzy matching for typo tolerance.</li> </ul> <h2>🎬 Wrapping up</h2> <p>Sometimes the best solutions come from recognizing what's already there. We had LLMS.txt. We had the MCP server. We just needed to connect them.</p> <p>Thanks to David Fowler for the direct feedback that sparked this work, and to Chris Swithinbank for the incredible <code>starlight-llms-txt</code> plugin that makes it all possible. If you're using AI to build Aspire apps, these tools should make your experience noticeably better.</p> <p>The AI now has access to the same documentation you do—and it knows to use it.</p> <p>Check out the <a href="https://github.com/dotnet/aspire/pull/14028">full pull request on GitHub</a>.</p> <hr /> <p><em>Happy coding, friends!</em> 🚀</p> David Pine