Shawn Wildermuth's BlogMy Favorite Rants and RavesCopyright (c) Shawn Wildermuth 20232024-01-29T00:00:00Zhttps://wildermuth.com/Shawn Wildermuthshawn@wildermuth.comMy Nuget Packages
2024-01-29T00:00:00Zhttps://wildermuth.com/2024/01/29/nuget-packages/https://wilderminds.blob.core.windows.net/img/2024/01/28/cover.jpg
<p>It’s been a while, huh? I haven’t been blogging much (as I’ve been dedicating my time to my <a href="https://shawnl.ink/yt">YouTube channel</a>) - so I thought it was time to give you a quick update. I have a series of Nuget packages that I’ve created to help with .NET Core development. Let’s take a look:</p>
<h2 id="minimalapis.fluentvalidation">MinimalApis.FluentValidation</h2>
<p>This is my newest package. It adds support to use FluentValidation as an endpointfilter in Minimal APIs. To install:</p>
<pre><code>> dotnet add package MinimalApis.FluentValidation
</code></pre>
<p><a href="https://github.com/shawnwildermuth/minimalapis.fluentvalidation?tab=readme-ov-file#minimalapisfluentvalidation">Read more…</a></p>
<h2 id="minimalapidiscovery">MinimalApiDiscovery</h2>
<p>I created this package to support structuring your Minimal APIs. It has a sourcegenerator that will register all your Minimal APIs with one call in startup. The strategy here was to avoid having to put anything in the DI layer, since Minimal APIs are static lambas. To install:</p>
<pre><code>> dotnet add package WilderMinds.MinimalApiDiscovery
</code></pre>
<p><a href="https://github.com/wilder-minds/MinimalApiDiscovery?tab=readme-ov-file#minimalapidiscovery">Read more…</a></p>
<h2 id="azureimagestorageservice">AzureImageStorageService</h2>
<p>This is a small package I created so I create a wrapper around the complexity of writing images to Azure Blog Storage. Take a look! To install:</p>
<pre><code>> dotnet add package WilderMinds.AzureImageStorageService
</code></pre>
<p><a href="https://github.com/wilder-minds/AzureImageStorageService?tab=readme-ov-file#wildermindsazureimagestorageservice">Read more…</a></p>
<h2 id="metaweblog">MetaWeblog</h2>
<p>This is an older package I wrote to handle the <a href="https://en.wikipedia.org/wiki/MetaWeblog">MetaWeblog API</a> in my own blog. This API is used for some tools to post new blog entries. To install it:</p>
<pre><code>> dotnet add package WilderMinds.MetaWeblog
</code></pre>
<p><a href="https://github.com/shawnwildermuth/MetaWeblog?tab=readme-ov-file#wildermindsmetaweblog">Read more…</a></p>
<h2 id="rsssyndication">RssSyndication</h2>
<p>Another package I wrote to support my blog, but some people find it useful. In early .NET Core, there wasn’t a solution for exposing a RSS feed from some content. This package does just that: To install it:</p>
<pre><code>> dotnet add package WilderMinds.RssSyndication
</code></pre>
<p><a href="https://github.com/shawnwildermuth/RssSyndication?tab=readme-ov-file#rsssyndication">Read more…</a></p>
<h2 id="wilderminds.swaggerhierarchysupport">WilderMinds.SwaggerHierarchySupport</h2>
<p>Finally, a small Nuget package to allow you to inject the Swagger Heirarchy plugin for Swagger/OpenAPI to create levels of hierarchies in your swagger conigurations. Install it here:</p>
<pre><code>> dotnet add package WilderMinds.SwaggerHierarchySupport
</code></pre>
<p><a href="https://github.com/wilder-minds/SwaggerUIHierarchySupport?tab=readme-ov-file#swaggerhierarchysupport">Read more…</a></p>
<div>
<div style="float: left;">
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
<img alt="Creative Commons License" style="border-width: 0" src="http://i.creativecommons.org/l/by-nc-nd/3.0/88x31.png" /></a></div>
<div>
This work by <a xmlns:cc="http://creativecommons.org/ns#" href="http://wildermuth.com"
property="cc:attributionName" rel="cc:attributionURL">Shawn Wildermuth</a> is
licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License</a>.<br />
Based on a work at <a xmlns:dct="http://purl.org/dc/terms/" href="http://wildermuth.com"
rel="dct:source">wildermuth.com</a>.</div>
</div>
<hr/><div>If you liked this article, see Shawn's courses on <a href="http://shawnw.me/pscourses">Pluralsight</a>.</div>
Is it Time to Panic? Suddenly, Everyone Seems to Need a Job
2023-08-15T00:00:00Zhttps://wildermuth.com/2023/08/15/suddenly-everyone-seems-to-need-a-job/https://wilderminds.blob.core.windows.net/img/2023/08/15/cover.jpg
<p>In my last <a href="https://wildermuth.com/2023/07/31/where-have-i-been">blog post</a>, I mentioned that I was pivoting to what I’m doing next. It feels a lot of people are going through an upheaval. Is it systemic?</p>
<p>To be clear, I have no idea what’s happening but it looks like a lot of organizations have taken the current landscape to trim their rolls. Of course, some of you might think AI is the culprit but I talked about that in one of my rants if you want to go flame me there ;)</p>
<div class="embed-responsive embed-responsive-16by9">
<iframe class="embed-responsive-item" src="https://www.youtube.com/embed/90jCxSwzefY" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
</div>
<p>Over the past month or so, I’ve watched as Pluralsight, LinkedIn, Plex, Microsoft and I am sure more that I haven’t noticed. Earlier in the year, the announcement from Facebook, Alphabet, Twitter and Microsoft already left some people worried.</p>
<p>A lot of these jobs seem to be more about developer relations. This seems like a pattern. Sure, startups like SourceGraph are hiring, but can they absorb the other layoffs? I don’t know.</p>
<p>Am I concerned? For my own future, sure. I’m 54 and ageism exists, but I have faith in my abilities. The real concern for me, is that Developer Advocates are being cut. There is a real movement towards Discord as documentation and developer relationships. I think this trend is newer than the pandemic.</p>
<p>The other side of the coin is that overall the unemployment rate is actually pretty low. For me, that meant I wasn’t looking at jobs outside of the tech companies. Sometimes I forget that most tech jobs aren’t in tech companies like Ford, Geico, and Wells Fargo. So, if you’re looking, don’t forget that tons of other companies have openings (in fact, reach out if you are a Vue developer that can work remote, I know of a job or two).</p>
<p>Lastly, I wanted to highlight a few people that I know are looking and that I think are genuinely great. Here are their LinkedIn links:</p>
<ul>
<li>David Neal: <a href="https://www.linkedin.com/in/davidneal/">https://www.linkedin.com/in/davidneal/</a></li>
<li>Joe Guadagno: <a href="https://www.linkedin.com/in/josephguadagno/">https://www.linkedin.com/in/josephguadagno/</a></li>
<li>Ted Neward: <a href="https://www.linkedin.com/in/tedneward/">https://www.linkedin.com/in/tedneward/</a></li>
<li>Lars Klint: <a href="https://www.linkedin.com/in/lklint/">https://www.linkedin.com/in/lklint/</a></li>
</ul>
<p>I am not sure I’m looking for a job yet, but I’m still writing courses and working with clients for the time being. This fall may tell a different tale.</p>
<div>
<div style="float: left;">
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
<img alt="Creative Commons License" style="border-width: 0" src="http://i.creativecommons.org/l/by-nc-nd/3.0/88x31.png" /></a></div>
<div>
This work by <a xmlns:cc="http://creativecommons.org/ns#" href="http://wildermuth.com"
property="cc:attributionName" rel="cc:attributionURL">Shawn Wildermuth</a> is
licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License</a>.<br />
Based on a work at <a xmlns:dct="http://purl.org/dc/terms/" href="http://wildermuth.com"
rel="dct:source">wildermuth.com</a>.</div>
</div>
<hr/><div>If you liked this article, see Shawn's courses on <a href="http://shawnw.me/pscourses">Pluralsight</a>.</div>
Where Have I Been?
2023-07-31T00:00:00Zhttps://wildermuth.com/2023/07/31/where-have-i-been/https://wilderminds.blob.core.windows.net/img/2023/07/31/cover.jpg
<p>I went to my blog the other day and noticed my last story here was in February. I guess I got a little distracted. So, what have I been up to? Let’s talk about it.</p>
<p>Over the last couple of years, like many blogs, I’ve seen the readership dwindle. This doesn’t mean I think it’s time to abandon the blog. But with so many other things taking my time, I suspect I won’t be blogging quite as regularly as I have in the past. After 1730 blog posts, this blog has been really important to me. I’d never abandon it.</p>
<p>So, if I’m not blogging, what am I doing?</p>
<h2 id="the-film">The Film</h2>
<p><a href="https://manenoughfilm.com/"><img src="https://twainfilms.com/img/manenough/poster-horizontal.jpg" alt="Man Enough to Heal" class="float-right w-36 lg:w-96 ml-4" /></a>The most obvious answer to this is the film I’ve been working on since the beginning of Covid: <a href="https://manenoughfilm.com/">Man Enough to Heal</a>. The film post-production wrapped in May. Since then, I’ve submitted it to film festivals and engaged a sales agent to find distribution. I’m quite happy with the results and can’t wait to share here when it’s available to watch!</p>
<h2 id="pluralsight-courses">Pluralsight Courses</h2>
<p>Since the beginning of the year, I’ve working full-time updating and creating new courses for <a href="https://shawl.ink/psauthor">Pluralsight</a>. Right now, I’m in the middle of updating my long <a href="https://www.pluralsight.com/courses/aspnetcore-mvc-efcore-bootstrap-angular-web">ASP.NET Core, End-to-End course</a> for .NET 6 (and .NET 8 when it ships). The other courses I’ve released or updated this year include:</p>
<ul>
<li><a href="https://www.pluralsight.com/courses/javascript-modules">Modules in JavaScript</a></li>
<li><a href="https://www.pluralsight.com/courses/javascript-proxy-objects-reflect">Proxy Objects in JavaScript</a></li>
<li><a href="https://www.pluralsight.com/courses/vue-3-forms">Vue 3 Forms</a></li>
</ul>
<h2 id="coding-shorts">Coding Shorts</h2>
<p>While blogging has waned, I’ve been focused on doing short videos I’m calling “Coding Shorts”. These videos are ten or so minutes long so I can teach one, discrete skill or technology. I’ve made 67 of these so far. Here’s one of my recent ones if you’re interested in getting a taste of them:</p>
<div class="embed-responsive embed-responsive-16by9">
<iframe class="embed-responsive-item" src="https://www.youtube.com/embed/r4WrDfuDT24" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
</div>
<br />
<p>I’ve also authored a handful of “Rants” where I talk about the industry and my opinions about what is important. You can find all the videos at my channel:</p>
<ul>
<li><a href="https://shawnl.ink/yt">https://shawnl.ink/yt</a></li>
</ul>
<h2 id="the-pivot">The Pivot</h2>
<p>Lastly, I’ve been spending time thinking about what is next. Every once in a while (~10 years), I find the need to change the direction of my career. Teaching and training have been great, but I think I’m ready for another challenge. In the past, these pivots have been about what things I’ve focused on. Some of these include:</p>
<ul>
<li>xBase development that pivoted to C++</li>
<li>C++ pivoted to COM and ActiveX</li>
<li>COM and ActiveX that pivoted to ASP<area />.NET and C#</li>
<li>ASP<area />.NET/C# pivoted to desktop development with WPF/XAML</li>
<li>WPF/XAML pivoted to Silverlight training</li>
<li>And lastly, Silverlight pivoted back to ASP<area />.NET and .NET Core</li>
</ul>
<p>But where do I go next? I have no idea. But I realize that this is likely my last pivot. This means I’m looking to do something that excites me and that I think is important to do. But who knows what that is. I’m scaling back my training and doing more client work, but I’d love to find some clients that are doing important things. If you think you’re one of those companies, feel free to reach out on my work site:</p>
<ul>
<li><a href="https://shawn.wildermuth.com/">https://shawn.wildermuth.com</a></li>
</ul>
<h3 id="last-thing%E2%80%A6">Last Thing…</h3>
<p>In case you don’t know, I release a newsletter every week with the articles that I find useful — both software related and other tech (e.g. Space, Science). If you want to subscribe, feel free to visit:</p>
<ul>
<li><a href="https://shawnl.ink/newsletter">https://shawnl.ink/newsletter</a></li>
</ul>
<div>
<div style="float: left;">
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
<img alt="Creative Commons License" style="border-width: 0" src="http://i.creativecommons.org/l/by-nc-nd/3.0/88x31.png" /></a></div>
<div>
This work by <a xmlns:cc="http://creativecommons.org/ns#" href="http://wildermuth.com"
property="cc:attributionName" rel="cc:attributionURL">Shawn Wildermuth</a> is
licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License</a>.<br />
Based on a work at <a xmlns:dct="http://purl.org/dc/terms/" href="http://wildermuth.com"
rel="dct:source">wildermuth.com</a>.</div>
</div>
<hr/><div>If you liked this article, see Shawn's courses on <a href="http://shawnw.me/pscourses">Pluralsight</a>.</div>
A Minimal API Discovery Tool for Large APIs
2023-02-22T00:00:00Zhttps://wildermuth.com/2023/02/22/minimal-api-discovery/https://wilderminds.blob.core.windows.net/img/2023/02/22/cover.jpg
<p>I’ve been posting and making videos about ideas I’ve had for discovering Minimal APIs instead of mapping them all in <code>Program.cs</code> for a while. I’ve finally codified it into an experimental nuget package. Let’s talk about how it works.</p>
<p>I also made a Coding Short video that covers this same topic, if you’d rather watch than read:</p>
<div class="embed-responsive embed-responsive-16by9">
<iframe class="embed-responsive-item" src="https://www.youtube.com/embed/i0sSXHQvRhU" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
</div>
<h2 id="how-it-works">How It Works</h2>
<p>The package can be installed via the dotnet tool:</p>
<pre><code>dotnet add package WilderMinds.MinimalApiDiscovery
</code></pre>
<p>Once it is installed, you can use an interface called <code>IApi</code> to implement classes that can register Minimal APIs. The <code>IApi</code> interface looks like this:</p>
<pre><code class="language-csharp">/// <summary>
/// An interface for Identifying and registering APIs
/// </summary>
public interface IApi
{
/// <summary>
/// This is automatically called by the library to add your APIs
/// </summary>
/// <param name="app">The WebApplication object to register the API </param>
void Register(WebApplication app);
}
</code></pre>
<p>Essentially, you can implement classes that get passed the <code>WebApplication</code> object to map your API calls:</p>
<pre><code class="language-csharp">public class StateApi : IApi
{
public void Register(WebApplication app)
{
app.MapGet("/api/states", (StateCollection states) =>
{
return states;
});
}
}
</code></pre>
<p>This would allow you to register a number of related API calls. I think one class per API is too restrictive. When used in .NET 7 and later, you could make a class per <code>group</code>:</p>
<pre><code class="language-csharp"> public void Register(WebApplication app)
{
var group = app.MapGroup("/api/films");
group.MapGet("", async (BechdelRepository repo) =>
{
return Results.Ok(await repo.GetAll());
})
.Produces(200);
group.MapGet("{id:regex(tt[0-9]*)}",
async (BechdelRepository repo, string id) =>
{
Console.WriteLine(id);
var film = await repo.GetOne(id);
if (film is null) return Results.NotFound("Couldn't find Film");
return Results.Ok(film);
})
.Produces(200);
group.MapGet("{year:int}", (BechdelRepository repo,
int year,
bool? passed = false) =>
{
var results = await repo.GetByYear(year, passed);
if (results.Count() == 0)
{
return Results.NoContent();
}
return Results.Ok(results);
})
.Produces(200);
group.MapPost("", (Film model) =>
{
return Results.Created($"/api/films/{model.IMDBId}", model);
})
.Produces(201);
}
</code></pre>
<p>Because of lambdas missing some features (e.g. default values), you can always move the lambdas to just static methods:</p>
<pre><code class="language-csharp">public void Register(WebApplication app)
{
var grp = app.MapGroup("/api/customers");
grp.MapGet("", GetCustomers);
grp.MapGet("", GetCustomer);
grp.MapPost("{id:int}", SaveCustomer);
grp.MapPut("{id:int}", UpdateCustomer);
grp.MapDelete("{id:int}", DeleteCustomer);
}
static async Task<IResult> GetCustomers(CustomerRepository repo)
{
return Results.Ok(await repo.GetCustomers());
}
//...
</code></pre>
<p>The reason for the suggestion of using static methods (instance methods would work too) is that you do not want these methods to rely on state. You might think that constructor service injection would be a good idea:</p>
<pre><code class="language-csharp">public class CustomerApi : IApi
{
private CustomerRepository _repo;
// MinimalApiDiscovery will log a warning because
// the repo will become a singleton and lifetime
// will be tied to the implementation methods.
// Better to use method injection in this case.
public CustomerApi(CustomerRepository repo)
{
_repo = repo;
}
// ...
</code></pre>
<p>This doesn’t work well as the call to <code>Register</code> happens once at startup and since this class is sharing that state, the injected service becomes a singleton for the lifetime of the server. The library will log a <strong>warning</strong> if you do this to help you avoid it. Because of that I suggest that you use static methods instead to prevent this from accidently happening.</p>
<blockquote>
<p>NOTE: I considered using static interfaces, but that requires that the instance is still a non-static class. It would also limit this library to use in .NET 7/C# 11 - which I didn’t want to do. It works in .NET 6 and above.</p>
</blockquote>
<p>When you’ve created these classes, you can simple make two calls in startup to register all <code>IApi</code> classes:</p>
<pre><code class="language-csharp">using UsingMinimalApiDiscovery.Data;
using WilderMinds.MinimalApiDiscovery;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddTransient<CustomerRepository>();
builder.Services.AddTransient<StateCollection>();
// Add all IApi classes to the Service Collection
builder.Services.AddApis();
var app = builder.Build();
// Call Register on all IApi classes
app.MapApis();
app.Run();
</code></pre>
<p>The idea here is to use reflection to find all <code>IApi</code> classes and add them to the service collection. Then the call to <code>MapApis()</code> will get all <code>IApi</code> from the service collection and call Register.</p>
<h2 id="how-it-works-1">How it works</h2>
<p>The call to <code>AddApis</code> simply uses reflection to find all classes that implement <code>IApi</code> and add them to the service collection:</p>
<pre><code class="language-csharp"> var apis = assembly.GetTypes()
.Where(t => t.IsAssignableTo(typeof(IApi)) &&
t.IsClass &&
!t.IsAbstract)
.ToArray();
// Add them all to the Service Collection
foreach (var api in apis)
{
// ...
coll.Add(new ServiceDescriptor(typeof(IApi), api, lifetime));
}
</code></pre>
<p>Once they’re all registered, the call to <code>MapApis</code> is pretty simple:</p>
<pre><code class="language-csharp">var apis = app.Services.GetServices<IApi>();
foreach (var api in apis)
{
if (api is null) throw new InvalidProgramException("Apis not found");
api.Register(app);
}
</code></pre>
<h2 id="futures">Futures</h2>
<p>While I’m happy with this use of Reflection since it is only a ‘startup’ time cost, I have it on my list to look at using a Source Generator instead.</p>
<blockquote>
<p>If you have experience with Source Generators and want to give it a shot, feel free to do a pull request at <a href="https://github.com/wilder-minds/minimalapidiscovery">https://github.com/wilder-minds/minimalapidiscovery</a>.</p>
</blockquote>
<p>I’m also considering removing the AddApis and just have the <code>MapApis</code> call just reflect to find all the IApis and call register since we don’t actually need them in the Service Collection.</p>
<p>You can see the complete source and example here:</p>
<blockquote>
<p><a href="https://github.com/wilder-minds/minimalapidiscovery">https://github.com/wilder-minds/minimalapidiscovery</a></p>
</blockquote>
<div>
<div style="float: left;">
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
<img alt="Creative Commons License" style="border-width: 0" src="http://i.creativecommons.org/l/by-nc-nd/3.0/88x31.png" /></a></div>
<div>
This work by <a xmlns:cc="http://creativecommons.org/ns#" href="http://wildermuth.com"
property="cc:attributionName" rel="cc:attributionURL">Shawn Wildermuth</a> is
licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License</a>.<br />
Based on a work at <a xmlns:dct="http://purl.org/dc/terms/" href="http://wildermuth.com"
rel="dct:source">wildermuth.com</a>.</div>
</div>
<hr/><div>If you liked this article, see Shawn's courses on <a href="http://shawnw.me/pscourses">Pluralsight</a>.</div>
I'm Hosting Two New Online Courses
2023-02-17T00:00:00Zhttps://wildermuth.com/2023/02/17/hosting-two-new-online-courses/https://wilderminds.blob.core.windows.net/img/2023/02/17/cover.jpg
<p>As you likely know if you’ve read my blog before, I have spent the last decade or so creating courses to be viewed on <a href="https://shawnl.ink/psauthor">Pluralsight</a>. I love making these kinds of video-based courses, but I’ve decided to get back to instructor led training a bit.</p>
<p>While my video courses really benefit a lot of learners, I’ve realized that some people learn better with direct interaction with a live teacher. In addition, I have missed the direct impact of working with students.</p>
<p>I’m proud to announce that my first two instructor-led courses:</p>
<blockquote>
<p><a href="https://buytickets.at/shawnwildermuth/857657">ASP.NET Core: Building Sites and APIs</a> - April 11-13, 2023<br />
<a href="https://buytickets.at/shawnwildermuth/833506">Building Apps with Vue, Vite and TypeScript</a> - May 9-11, 2023</p>
</blockquote>
<p>These courses will be taught online (via Zoom). This sort of remote teaching can be taxing for many people, so I am teaching it as three 1/2 days. Each day, I’ll hold the class from noon to 5pm (Eastern Time Zone).</p>
<blockquote>
<p>Early Bird Pricing until March 24th: $699</p>
</blockquote>
<p>I hope you’ll join me at these new courses!</p>
<div>
<div style="float: left;">
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
<img alt="Creative Commons License" style="border-width: 0" src="http://i.creativecommons.org/l/by-nc-nd/3.0/88x31.png" /></a></div>
<div>
This work by <a xmlns:cc="http://creativecommons.org/ns#" href="http://wildermuth.com"
property="cc:attributionName" rel="cc:attributionURL">Shawn Wildermuth</a> is
licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License</a>.<br />
Based on a work at <a xmlns:dct="http://purl.org/dc/terms/" href="http://wildermuth.com"
rel="dct:source">wildermuth.com</a>.</div>
</div>
<hr/><div>If you liked this article, see Shawn's courses on <a href="http://shawnw.me/pscourses">Pluralsight</a>.</div>
Digging Into Nullable Reference Types in C#
2023-02-13T00:00:00Zhttps://wildermuth.com/2023/02/13/nullable-reference-types-in-csharp/https://wilderminds.blob.core.windows.net/img/2023/02/12/cover.jpg
<p>This topic has been on my <code>TODO:</code> list for quite a while now. As I work with clients, many of them are just ignoring the warnings that you get from Nullable Reference Types. When Microsoft changed to make them the default, some developers seemed to be confused by the need. Here is my take on them:</p>
<p>I also made a Coding Short video that covers this same topic, if you’d rather watch than read:</p>
<div class="embed-responsive embed-responsive-16by9">
<iframe class="embed-responsive-item" src="https://www.youtube.com/embed/tMKcLwlhoEs" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
</div>
<h2 id="before-nullable-reference-types">Before Nullable Reference Types</h2>
<p>There has always been two different types of objects in C#: <code>value types</code> and <code>reference types</code>. Value types are created on the stack (therefore they go away without needing to be garbage collected); and Reference Types are created by the heap (needing to be garbage collected). Primitive types and structs are value types, and everything else is a reference type, including strings. So we could do this:</p>
<pre><code class="language-csharp">int x = 5;
string y = null;
</code></pre>
<p>By it’s design, value-types couldn’t be null. They just where:</p>
<pre><code class="language-csharp">int x = null; // Error
string y = null;
</code></pre>
<p>There were occasions that we needed null on value types. So they introduced the <code>Nullable<T></code> struct. Essentially, this allowed you to make value types nullable:</p>
<pre><code class="language-csharp">Nullable<int> x = null; // No problem
</code></pre>
<p>They did add some syntactical sugar for <code>Nullable<T></code> by just using a question mark:</p>
<pre><code class="language-csharp">int? x = null; // Same as Nullable<int>
</code></pre>
<p>But why nullability? So you can test for whether a value exists:</p>
<pre><code class="language-csharp">int? x = null;
if (x.HasValue) Write(x);
</code></pre>
<p>While this works, you could test for null as well:</p>
<pre><code class="language-csharp">int? x = null;
if (x is not null) Write(x);
</code></pre>
<p>OK, this is what Nullable value types are, but reference types already support null. Reference types do support being null, but do <strong>not</strong> support not allowing null. That’s the difference. By enabling Nullable Reference Types, all reference types (by default) do not support Null unless you use the define them with the question-mark:</p>
<pre><code class="language-csharp">object x = null // Doesn't work
</code></pre>
<p>But utilizing the null type definition:</p>
<pre><code class="language-csharp">object? x = null // works
</code></pre>
<p>As C# developers, we spend a lot of time worrying about whether an object is null (since anyone can pass a null for parameters or properties). So, enabling Nullable Reference Types makes that impossible. By default, new projects (since .NET 6) have enabled Nullable Reference Types by default. But how?</p>
<h2 id="enabling-nullable-reference-types">Enabling Nullable Reference Types</h2>
<p>In C# 8, they added the ability to enable Nullable Reference Types. There are two ways to enable it: file-based declaration or a project level flag. For projects that want to opt into Nullable Reference Types slowly, you can use the file declarations:</p>
<pre><code class="language-csharp">#nullable enable
object x = null; // Doesn't work, null isn't supported
#nullable disable
</code></pre>
<p>But for most projects, this is done at the project level:</p>
<pre><code class="language-xml"><!--csproj-->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
</code></pre>
<p>The <code><Nullable/></code> property is what enables the feature.</p>
<p>When you enable this, it will produce warnings for applying null to reference types. But you can even turn these into errors to force a project to address the changes:</p>
<pre><code class="language-xml"><WarningsAsErrors>Nullable</WarningsAsErrors>
</code></pre>
<h2 id="using-nullable-reference-types">Using Nullable Reference Types</h2>
<p>So, you’ve gotten this far so let’s talk some basics. When defining a variable, you can opt-into nullability by defining the type with nullability:</p>
<pre><code class="language-csharp">string? x = null;
</code></pre>
<p>That means anywhere you’re just defining the type (without inferring the type), C# will assume that null isn’t a valid value:</p>
<pre><code class="language-csharp">string x = "Hello";
if (x is null) // No longer necessary, this can't be null
{
// ...
}
</code></pre>
<p>But what happens when we infer the type? For value types, it is assumed to be a non-nullable type, but for reference type…nullable:</p>
<pre><code class="language-csharp">var u = 15; // int
var s = ""; // string?
var t = new String('-', 20); // string?
</code></pre>
<p>This is actually one of the reasons I’m moving to the new syntax for creating objects:</p>
<pre><code class="language-csharp">object s = new(); // object - not nullable
</code></pre>
<p>Not exactly about nullable reference types, but in this case, the object is not null because we’re making sure it’s not nullable.</p>
<h2 id="classes-and-nullable-reference-types">Classes and Nullable Reference Types</h2>
<p>When clients have moved here, the biggest pain they seem to run into is with classes (et al.). After spending so many years writing simple data classes like so:</p>
<pre><code class="language-csharp">public class Customer
{
public int Id { get; set;}
public string Name { get; set;} // Warning
public DateOnly Birthdate { get; set;}
public string Phone { get;set;} // Warning
}
</code></pre>
<p>Properties that aren’t nullable are expected to be set before the end of the constructor. There are two ways to address make them nullable; and initialize the properties.</p>
<p>Making the properties nullable has the benefit of being more descriptive of the actual usage of the property:</p>
<pre><code class="language-csharp">public class Customer
{
public int Id { get; set;}
public string? Name { get; set;} // null unless you set it
public DateOnly Birthdate { get; set;}
public string? Phone { get;set;} // null unless you set it
}
</code></pre>
<p>Alternatively, you can set the value:</p>
<pre><code class="language-csharp">public class Customer
{
public int Id { get; set;}
public string Name { get; set;} = "";
public DateOnly Birthdate { get; set;}
public string Phone { get;set;} = "";
}
</code></pre>
<p>Or,</p>
<pre><code class="language-csharp">public class Customer
{
public int Id { get; set;}
public string Name { get; set;}
public DateOnly Birthdate { get; set;}
public string Phone { get;set;}
public Customer(string name, string phone)
{
Name = name;
Phone = phone;
}
}
</code></pre>
<p>It may, at first, seem like trouble for certain types of classes. In fact, it’s is not uncommon to opt-out of nullability for entity classes:</p>
<pre><code class="language-csharp">#nullable disable
public class Customer
{
public int Id { get; set;}
public string Name { get; set;} // No Warning
public DateOnly Birthdate { get; set;}
public string Phone { get;set;} // No Warning
}
#nullable enable
</code></pre>
<h2 id="testing-for-null">Testing for Null</h2>
<p>When you start using nullable properties on objects, you quickly run into warnings:</p>
<pre><code class="language-csharp">Customer customer = new();
WriteLine($"Name: {customer.Name}"); // Warning
</code></pre>
<p>The warning is because the compiler can’t confirm it is not null (Name is nullable). This is one of the uncomfortable parts of using Nullable Reference Types. So we can wrap it with a test for null (like you’ve probably been doing for a long time):</p>
<pre><code class="language-csharp">Customer customer = new();
if (customer.Name is not null)
{
WriteLine($"Name: {customer.Name}");
}
</code></pre>
<p>At that point, the compiler can be sure it’s not null because you tested it. But this seems a lot of work to determine null. Instead we can use some syntactical sugar to shorten this:</p>
<pre><code class="language-csharp">Customer customer = new();
WriteLine($"Name: {customer?.Name}"); // Warning
</code></pre>
<p>The <code>?.</code> is simply a shortcut. If <code>customer</code> is null, it just returns a null. This allows you to deal with nested nullable types pretty easily:</p>
<pre><code class="language-csharp">Customer customer = new();
WriteLine($"Name: {customer.Name?.FirstName?}"); // Warning
</code></pre>
<p>In this example, you can see that the <code>?</code> is used at multiple places in the code as <code>Name</code> could be null and <code>FirstName</code> could also be null.</p>
<p>This also affects how you will allocate a variable that might be null. For example:</p>
<pre><code class="language-csharp">Customer customer = new();
string name = customer.Name; // Warning, Name might be null
</code></pre>
<p>The null coalescing operator can be used here to define a default:</p>
<pre><code class="language-csharp">Customer customer = new();
string name = customer.Name ?? "No Name Specified"; // Warning, Name might be null
</code></pre>
<p>The <code>??</code> operator allows for the fallback in case of null. which should simplify some common scenarios.</p>
<p>But sometimes we need to help the compiler figure out whether something is null. You might know that a particular object is not null even if it is a nullable property. There is an additional syntax that supports telling the compiler that you know better. Just use the <code>!</code> syntax.</p>
<pre><code class="language-csharp">Customer customer = new();
string name = customer.Name!; // I know it's never null
</code></pre>
<p>This just tells the compiler what you expect. If the Name is null, it will throw an exception…so only use it when you’re sure. The bang symbol (e.g. <code>!</code>) is used at the end of the variable. So if you need to string these, you’ll put the bang at each level:</p>
<pre><code class="language-csharp">Customer customer = new();
string name = customer.Name!.FirstName!; // I know they're never null
</code></pre>
<p>While using Nullable Reference Types could be seen as a way to over-complicate your code, these bits of syntactical sugar can simplify dealing with nullables.</p>
<h2 id="generics-and-nullable-reference-types">Generics and Nullable Reference Types</h2>
<p>Just like any other code, you can use the question-mark to specify that a value is nullable:</p>
<pre><code class="language-csharp">public class SomeEntity<TKey>
{
public TKey? Key { get; set; }
}
</code></pre>
<p>The problem with this is that the type specified in <code>TKey</code> could also be nullable:</p>
<pre><code class="language-csharp">SomeEntity<string?> entity = new();
</code></pre>
<p>But this results in a warning because you can’t have a nullable of a nullable. The generated type might look like this:</p>
<pre><code class="language-csharp">public class SomeEntity<string?>
{
public string?? Key { get; set; }
}
</code></pre>
<p>Notice the double question-mark. It also suggests that the generic class doesn’t quite know whether to initialize it or not since it doesn’t know about the nullability. To get around this, you can use the <code>notnull</code> constraint:</p>
<pre><code class="language-csharp">public class SomeEntity<TKey> where : notnull
{
public TKey? Key { get; set; }
}
</code></pre>
<p>That way the generic type can be in control of the nullability instead of the caller.</p>
<h2 id="conclusion">Conclusion</h2>
<p>I hope that this quick intro into Nullable Reference Types helps you get your head around the ‘why’ and ‘how’ of Nullable Reference Types. Please comment if you have more questions and/or complaints!</p>
<blockquote>
<p><a href="https://github.com/shawnwildermuth/codingshorts/tree/main/nullability">Nullable Reference Types Example</a></p>
</blockquote>
<div>
<div style="float: left;">
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
<img alt="Creative Commons License" style="border-width: 0" src="http://i.creativecommons.org/l/by-nc-nd/3.0/88x31.png" /></a></div>
<div>
This work by <a xmlns:cc="http://creativecommons.org/ns#" href="http://wildermuth.com"
property="cc:attributionName" rel="cc:attributionURL">Shawn Wildermuth</a> is
licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License</a>.<br />
Based on a work at <a xmlns:dct="http://purl.org/dc/terms/" href="http://wildermuth.com"
rel="dct:source">wildermuth.com</a>.</div>
</div>
<hr/><div>If you liked this article, see Shawn's courses on <a href="http://shawnw.me/pscourses">Pluralsight</a>.</div>
Using Vite's Plugin for Progressive Web Apps (PWAs)
2023-02-09T00:00:00Zhttps://wildermuth.com/2023/02/09/vite-plugin-for-progressive-web-apps/https://wilderminds.blob.core.windows.net/img/2023/02/08/cover.jpg
<p>I’ve worked with Progressive Web Application plug-ins with several SPA frameworks. Most of them are pretty simple to implement. But when I learned about Vite’s plug-in, I was intrigued since that would work across different SPA frameworks. Let’s take a look at it.</p>
<p>I also made a Coding Short video that covers this same topic, if you’d rather watch than read:</p>
<div class="embed-responsive embed-responsive-16by9">
<iframe class="embed-responsive-item" src="https://www.youtube.com/embed/r4WrDfuDT24" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
</div>
<p>The Vite plug-in for PWA works at the Vite/Build level, not for your specific framework (or lack of a framework). That means it will work for Vue, React, SvelteKit and Vanilla JS (and any other Vite-powered development). Before we do any of this, we have a working website:</p>
<p><img src="https://wilderminds.blob.core.windows.net/img/2023/02/08/figure0.jpg" alt="A example website" /></p>
<p>To install it, you just need to add it to your development-time dependencies:</p>
<pre><code>> npm i vite-plugin-pwa --save-dev
</code></pre>
<p>Once installed, you can add it to your vite.config.js file:</p>
<pre><code class="language-js">...
import { VitePWA } from "vite-plugin-pwa";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
VitePWA()
],
...
})
</code></pre>
<p>With this installed, you’ll see that your builds will generate some extra files:</p>
<pre><code>build started...
✓ 30 modules transformed.
../wwwroot/registerSW.js 0.13 kB
../wwwroot/manifest.webmanifest 0.14 kB
../wwwroot/index.html 0.56 kB
../wwwroot/assets/index-cfd5afe3.css 7.14 kB │ gzip: 1.97 kB
../wwwroot/assets/index-25653f73.js 75.09 kB │ gzip: 30.04 kB
built in 1378ms.
PWA v0.14.1
mode generateSW
precache 5 entries (80.98 KiB)
files generated
..\wwwroot\sw.js
..\wwwroot\workbox-519d0965.js
</code></pre>
<p>The file generated by the plug-in include:</p>
<ul>
<li><strong>manifest.webmanifest</strong>: Metadata about the app and an indication that it can be installed.</li>
<li><strong>sw.js</strong>: A, required, service worker that supports running as an app (and offline).</li>
<li><strong>registerSW.js</strong>: A new script that Vite injects into the index.html that registers the service worker.</li>
<li><strong>workbox-*.js</strong>: Workbox specific code to support the PWA.</li>
</ul>
<p>With this generated, you should see the “install icon” on supported browsers:</p>
<p><img src="https://wilderminds.blob.core.windows.net/img/2023/02/08/figure1..jpg" alt="The Install Button on Chrome" /></p>
<p>You can customize the metadata that is used by just adding a metadata object in the plug-in:</p>
<pre><code class="language-js">// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
VitePWA({
manifest: {
icons: [
{
src: "/icons/512.png",
sizes: "512x512",
type: "image/png",
purpose: "any maskable"
}
]
}
})],
...
</code></pre>
<p>The properties that you can customize in the manifest are all defined here.</p>
<p>If you run the example now, you can look at the manifest for errors or omissions:</p>
<p><img src="https://wilderminds.blob.core.windows.net/img/2023/02/08/figure2.jpg" alt="Examining the Manifest" /></p>
<p>If you click on the Application tab in the tools, you can see that it is complaining about missing icons for different operating systems.</p>
<p>If you switch to the Service Worker, you can see it is running:</p>
<p><img src="https://wilderminds.blob.core.windows.net/img/2023/02/08/figure3.jpg" alt="The Service Worker" /></p>
<p>But how does this work? The Service Worker can intercept network requests and serve the content necessary to load up the project. In fact, if you look at the “Cache Storage”, you’ll see the standard cache of the web page’s files:</p>
<p><img src="https://wilderminds.blob.core.windows.net/img/2023/02/08/figure4.jpg" alt="workbox Cache" /></p>
<p>The feature of the browser that supports all of this is called <code>workbox</code>, so if you look at that cache, you’ll see the files that are being cached to load this offline (including .html, .js, .css, etc.). So, let’s try and making the app offline to see what happens:</p>
<p><img src="https://wilderminds.blob.core.windows.net/img/2023/02/08/figure5.jpg" alt="workbox Cache" /></p>
<p>You can go offline in the Network tab by changing the networking to offline. If you refresh the page, you’ll get something that looks like this:</p>
<p><img src="https://wilderminds.blob.core.windows.net/img/2023/02/08/figure6.jpg" alt="Offline Version of Site" /></p>
<p>But what happened? The cache (see earlier) is only caching the files needed to serve the page, not for any functionality.</p>
<p>How do we fix this? Luckily, the plug-in supports changing the workbox settings to create your own caches (called runtimeCaching). To do this we return back to the <code>vite.config.js</code> file:</p>
<pre><code class="language-js">// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
VitePWA({
manifest: {
icons: [
{
src: "/icons/512.png",
sizes: "512x512",
type: "image/png",
purpose: "any maskable",
},
],
},
workbox: {
runtimeCaching: [
{
urlPattern: ({ url }) => {
return url.pathname.startsWith("/api");
},
handler: "CacheFirst" as const,
options: {
cacheName: "api-cache",
cacheableResponse: {
statuses: [0, 200],
},
},
},
],
},
}),
],'
...
</code></pre>
<p>By creating a section for workbox, we can configure a number of things, but for us we want to create an API cache. You can see that i’m testing all requests for <code>/api</code> and caching all <code>GET</code>s into our own cache. By enabling this, the customers reappear. We can see (and interrogate) the cache in the Application tools:</p>
<p><img src="https://wilderminds.blob.core.windows.net/img/2023/02/08/figure7.jpg" alt="The API Cache" /></p>
<p>This sort of all encompassing cache might not be realistic, but you could be caching non-volatile API calls. This isn’t a solution for handling offline changes. You can use application-specific code to write it to session or local storage.</p>
<p>I hope you’ve seen how the Vite PWA plug-in works anbd how you can use it to install your website as a local application!</p>
<p>You can find the example of the project here:</p>
<blockquote>
<p><a href="https://github.com/shawnwildermuth/codingshorts/tree/main/vite-pwa">Module Example</a></p>
</blockquote>
<div>
<div style="float: left;">
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
<img alt="Creative Commons License" style="border-width: 0" src="http://i.creativecommons.org/l/by-nc-nd/3.0/88x31.png" /></a></div>
<div>
This work by <a xmlns:cc="http://creativecommons.org/ns#" href="http://wildermuth.com"
property="cc:attributionName" rel="cc:attributionURL">Shawn Wildermuth</a> is
licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License</a>.<br />
Based on a work at <a xmlns:dct="http://purl.org/dc/terms/" href="http://wildermuth.com"
rel="dct:source">wildermuth.com</a>.</div>
</div>
<hr/><div>If you liked this article, see Shawn's courses on <a href="http://shawnw.me/pscourses">Pluralsight</a>.</div>
What Surprised Me About ECMAScript Modules
2023-02-01T00:00:00Zhttps://wildermuth.com/2023/02/01/what-surprised-me-about-ecmascript-modules/https://wilderminds.blob.core.windows.net/img/2023/02/01/cover.jpg
<p>I’ve spent the last couple of months working on a new <a href="https://shawnl.ink/psauthor">Pluralsight</a> course about <a href="https://shawnl.ink/jsmod">Modules in JavaScript</a>. I’ve been writing JavaScript (and TypeScript) for a lot of years. But digging into the course made me understand how some of this modularity actually worked. Let’s talk about some things that surprised me.</p>
<p>I also made a Coding Short video that covers this same topic, if you’d rather watch than read:</p>
<div class="embed-responsive embed-responsive-16by9">
<iframe class="embed-responsive-item" src="https://www.youtube.com/embed/JBFYyXEC9_8" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
</div>
<h2 id="ecmascript-modules-in-node.js">ECMAScript Modules in Node.js</h2>
<p>While <strong>Node.js</strong> has had module support long before ECMAScript got it’s act together and started supporting modules. CommonJS was an early standard for exposing modules. So most of the <strong>Node.js</strong> projects i’ve worked on just supposed that I had to use CommonJS. For example, a simple import using CommonJS (e.g. require()):</p>
<pre><code class="language-js">// index.js
const invoices = require("./invoices.js");
</code></pre>
<p>Since EMCAScript Modules (ESM) are supported, you could just name your <code>index.js</code> (in our case) to <code>index.mjs</code> and it would allow us to use EMCAScript:</p>
<pre><code class="language-js">// index.mjs
import invoices from "./invoices.mjs";
</code></pre>
<p>But, for me, I like that Node.js allows us to change the default module type to ESM:</p>
<pre><code class="language-json">{
"name": "before",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module", // commonjs is the default
"scripts": {
"start": "node ./index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"lodash": "^4.17.21"
}
}
</code></pre>
<p>Then we can just use ESM in our code without the renaming:</p>
<pre><code class="language-js">// index.js
import invoices from "./invoices.js";
</code></pre>
<h2 id="consuming-commonjs-from-ecmascript">Consuming CommonJS from ECMAScript</h2>
<p>So, you can use ESM to load all your own code where you’ve defined your modules directly. This works with your own projects or npm packages. If you’re using it for your own projects, you can rename your project to .cjs and it will be treated as a CommonJS:</p>
<pre><code class="language-js">// invoices.cjs
module.exports = [...];
</code></pre>
<p>But, more commonly, npm packages are mostly defined as CommonJS modules. How do we use them? For example, we can bring in a npm package (in this case <code>lodash</code>) like so:</p>
<pre><code class="language-js">import lodash from "lodash";
</code></pre>
<p>This allows us to use the lodash object as you like. But there is a limitation. Ordinarily, you could destructure it to get just the round function we need:</p>
<pre><code class="language-js">import { round } from "lodash";
</code></pre>
<p>But ESM with Node.js, it doesn’t work. It is because of a fundamental difference in how CommonJS defines named element and how ESM does it. So, to do it, you will need to import it as the default, but then you can destructure manually:</p>
<pre><code class="language-js">import lodash from "lodash";
const { round } = lodash;
</code></pre>
<p>It’s a minor nit, but if you know how CommonJS modules defined names (as I show in my course), it actually makes sense.</p>
<h2 id="deferred-imports">Deferred Imports</h2>
<p>I’ve been using ESM for a while and never ran into the <code>import()</code> function. I used to think that CommonJS was the only module system that allowed for late binding imports. But, alas, I was wrong.</p>
<p>The import function allows you to request an import at runtime, though it is asynchronous so you have to deal with the promise. For example:</p>
<pre><code class="language-js">export async function calculateTotal(invoice) {
const { taxRates } = await import("./taxRates.js");
const rate = taxRates[invoice.state];
const total = invoice.amount + invoice.amount * rate;
return {
rate,
total,
};
}
</code></pre>
<p>You can see that the import allows you to load the module the first time we use <code>calculateTotal()</code>. This does mean that you have to deal with asynchrony with the caller too:</p>
<pre><code class="language-js">invoices.forEach(async i => {
const { rate, total } = await calculateTotal(i);
console.log(`Invoice: ${i.invoiceNumber}, Date: ${i.invoiceDate}
Gross: $${round(i.amount,2)}
Tax Rate: ${rate * 100}%
Net: $${round(total,2)}`);
});
</code></pre>
<p>Note that the foreach is now async and you can use await to deal with the asynchrony.</p>
<p>You can find the example of the project here:</p>
<blockquote>
<p><a href="https://github.com/shawnwildermuth/codingshorts/tree/main/jsmod">Module Example</a></p>
</blockquote>
<div>
<div style="float: left;">
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
<img alt="Creative Commons License" style="border-width: 0" src="http://i.creativecommons.org/l/by-nc-nd/3.0/88x31.png" /></a></div>
<div>
This work by <a xmlns:cc="http://creativecommons.org/ns#" href="http://wildermuth.com"
property="cc:attributionName" rel="cc:attributionURL">Shawn Wildermuth</a> is
licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License</a>.<br />
Based on a work at <a xmlns:dct="http://purl.org/dc/terms/" href="http://wildermuth.com"
rel="dct:source">wildermuth.com</a>.</div>
</div>
<hr/><div>If you liked this article, see Shawn's courses on <a href="http://shawnw.me/pscourses">Pluralsight</a>.</div>
ChatGPT Wrote This Post
2023-01-31T00:00:00Zhttps://wildermuth.com/2023/01/31/chat-gpt-wrote-this-post/https://wilderminds.blob.core.windows.net/img/2023/01/31/cover.jpg
<p>I was playing with ChatGPT (like we all are) and thought about what if it could write a blog-post in the style of my writing. The exact prompt was:</p>
<blockquote>
<p>Write an article in the style of Shawn Wildermuth</p>
</blockquote>
<p>The article is shown below, but clearly it isn’t in my voice. It doesn’t use the words “pragmatic” or “dogma”. Go figure.</p>
<h2 id="the-power-of-empathy-in-software-development">The Power of Empathy in Software Development</h2>
<p>As a software developer, we are constantly tasked with solving problems for our users and clients. The key to success in this role is to understand the needs of the people you are building software for. This requires a deep understanding of their problems, their perspectives, and the experiences they are going through. In short, it requires empathy.</p>
<p>Empathy is the ability to understand and share the feelings of others. In software development, this means taking the time to understand the needs of our users and clients, and then using that understanding to build software that meets their needs. When we build software with empathy, we create solutions that are intuitive, user-friendly, and effective.</p>
<p>There are several key benefits to building software with empathy:</p>
<p><strong>Better User Experience</strong>: By understanding the needs and perspectives of our users, we can create a better user experience. This means that our software will be more intuitive and easier to use, leading to increased adoption and satisfaction.</p>
<p><strong>Increased Collaboration</strong>: When we have empathy for our users and clients, we are more likely to collaborate with them and work together to solve their problems. This leads to stronger relationships and more successful projects.</p>
<p><strong>Improved Problem-Solving Skills</strong>: When we understand the perspectives of our users and clients, we are better equipped to solve their problems. This leads to more creative and effective solutions, and a better outcome for everyone involved.</p>
<p>There are several ways to develop empathy in software development:</p>
<ul>
<li>Listen actively: Take the time to listen to your users and clients, and truly understand their needs and perspectives. Ask questions and seek to understand their experiences.</li>
<li>Put yourself in their shoes: Try to imagine what it would be like to be in their situation, and use that understanding to inform your work.</li>
<li>Collaborate: Work closely with your users and clients to co-create solutions that meet their needs. This will give you a deeper understanding of their perspectives and help you build software that is more effective.</li>
</ul>
<p>In conclusion, empathy is a powerful tool in software development. By understanding the needs and perspectives of our users and clients, we can build software that is more intuitive, user-friendly, and effective. So, next time you’re working on a project, take a step back, put yourself in your users’ shoes, and build with empathy. Your users and clients will thank you!</p>
<blockquote>
<p>not by: Shawn Wildermuth</p>
</blockquote>
<div>
<div style="float: left;">
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
<img alt="Creative Commons License" style="border-width: 0" src="http://i.creativecommons.org/l/by-nc-nd/3.0/88x31.png" /></a></div>
<div>
This work by <a xmlns:cc="http://creativecommons.org/ns#" href="http://wildermuth.com"
property="cc:attributionName" rel="cc:attributionURL">Shawn Wildermuth</a> is
licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License</a>.<br />
Based on a work at <a xmlns:dct="http://purl.org/dc/terms/" href="http://wildermuth.com"
rel="dct:source">wildermuth.com</a>.</div>
</div>
<hr/><div>If you liked this article, see Shawn's courses on <a href="http://shawnw.me/pscourses">Pluralsight</a>.</div>
Generating Sample Data with Bogus
2023-01-29T00:00:00Zhttps://wildermuth.com/2023/01/29/generating-sample-data-with-bogus/https://wilderminds.blob.core.windows.net/img/2023/01/29/cover.jpg
<p>Most of my job these days is creating tutorials or examples. This means I often start from scratch with a new type of project. I usually just cruft up some sample data for my project to start. While investigating some alternatives to Automapper (video and blog coming soon), I wanted to be able to create a bunch of sample data. I, luckily, ran into a tool that I was surprised I’d never heard of: Bogus. Let’s talk about it.</p>
<p>I also made a Coding Short video that covers this same topic, if you’d rather watch than read:</p>
<div class="embed-responsive embed-responsive-16by9">
<iframe class="embed-responsive-item" src="https://www.youtube.com/embed/14NGiQTioas" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
</div>
<h2 id="what-is-bogus%3F">What is Bogus?</h2>
<p>Bogus is a library that works with C#, F# and <a href="http://vb.net/">VB.NET</a> that can be used to create repeatable, fake data for applications. It is somewhat a port of a similar library Bogus.js. It accompished this by creating generators (called Fakers) that have a set of rules for generating one or more fake objects. Built-into Bogus is a set of generalized rules for common data categories (i.e. Addresses, Companies, People, Phone Numbers, etc.). Enough talk, let’s see how it works. The full repo is at:</p>
<blockquote>
<p><a href="https://github.com/bchavez/Bogus">Bogus</a></p>
</blockquote>
<p>To install Bogus, you can use the Package Manager or just the dotnet CLI:</p>
<pre><code>> dotnet add package Bogus
</code></pre>
<h2 id="creating-a-faker">Creating a Faker</h2>
<p>You start out by creating an instance of a class called <code>Faker<T></code>. From that class you would use a fluent syntax to set up rules on creating sample data. But let’s start with our POCO for a <code>Customer</code>:</p>
<pre><code class="language-csharp">public class Customer
{
public int Id { get; set; }
public string? CompanyName { get; set; }
public string? Phone { get; set; }
public string? ContactName { get; set; }
public int AddressId { get; set; }
public Address? Address { get;set;}
public IEnumerable<Order>? Orders {get;set;}
}
</code></pre>
<p>You can notice that aside from simple properties, we have a one-to-one relationship to an <code>Address</code> and a one-to-many relationship with <code>Orders</code>. Let’s start by creating a faker for the <code>Customer</code> object and the simple properties:</p>
<pre><code class="language-csharp">var customerFaker = new Faker<Customer>();
</code></pre>
<p>We can then use the <code>RuleFor</code> method to specify a rule for the Company Name:</p>
<pre><code class="language-csharp">var customerFaker = new Faker<Customer>()
.RuleFor(c => c.CompanyName, f => f.Company.CompanyName())
</code></pre>
<p>The first parameter of the <code>RuleFor</code> method is a lambda to pick the property on <code>Customer</code> that I want to fake. The second parameter is another lambda to pass in how to generate the property. While we could write any code we need here, the most-common case is to use the <code>Faker</code> object passed to use the built-in semantics. In this case we are using the Company category to generate a company name.</p>
<p>If we continue this, we can fake more simple properties like so:</p>
<pre><code class="language-csharp">var customerFaker = new Faker<Customer>()
.RuleFor(c => c.CompanyName, f => f.Company.CompanyName())
.RuleFor(c => c.ContactName, f => f.Name.FullName())
.RuleFor(c => c.Phone, f => f.Phone.PhoneNumberFormat());
</code></pre>
<p>You can see here that we’re using the Name category and the Phone category. The Bogus library has a large set of these built-in semantics. Sometimes we’ll need to use custom code to generate data we need. For example, we’ll want to generate IDs for the generated customers. One strategy is to just create a local integer and assign it with simple code:</p>
<pre><code class="language-csharp">var id = 1;
var customerFaker = new Faker<Customer>()
.RuleFor(c => c.Id, _ => id++)
.RuleFor(c => c.CompanyName, f => f.Company.CompanyName())
.RuleFor(c => c.ContactName, f => f.Name.FullName())
.RuleFor(c => c.Phone, f => f.Phone.PhoneNumberFormat());
</code></pre>
<p>Here we can see that we just have an integer (which will become a closure to the rule) and we just increment it everytime a new customer is created.</p>
<p>To use the Faker, we can just call <code>Generate()</code> with how many you want:</p>
<pre><code class="language-csharp">var customers = customerFaker.Generate(1000);
</code></pre>
<p>This will create a thousand fake customers.</p>
<h2 id="repeatable-fake-data">Repeatable Fake Data</h2>
<p>By default, the generation of customers is random. So that everytime you create an instance of the Faker object (e.g. <code>new Faker<Customer></code>), you would get different customers. When you want a consistent set of fake data, you can use a seeder to ensure that you get the same data every time. To do this, you just need to set a seed value to the same number:</p>
<pre><code class="language-csharp">public class CustomerFaker : Faker<Customer>
{
public CustomerFaker()
{
var id = 1;
UseSeed(1969) // Use any number
.RuleFor(c => c.Id, _ => id++)
.RuleFor(c => c.CompanyName, f => f.Company.CompanyName())
.RuleFor(c => c.ContactName, f => f.Name.FullName())
.RuleFor(c => c.Phone, f => f.Phone.PhoneNumberFormat());
}
}
var customers = new CustomerFaker().Generate(1000);
</code></pre>
<p>When you do this, you can guarantee to get the same customers. But this affects the entire instance of the faker. This is because every call to <code>Generate</code> will generate the <strong>next</strong> set of faked data. For example:</p>
<pre><code class="language-csharp">var customerFaker = new CustomerFaker();
var customers = customerFaker.Generate(1);
var companyName = customers.First().CompanyName;
var newCustomers = customerFaker.Generate(1);
Assert.IsTrue(companyName == newCustomers.First().CompanyName); // FAILS
</code></pre>
<p>This is because the seed is the repeatable data is per-instance. So that the the first call to <code>Generate</code> will give you the first repeatable object; and the second call to <code>Generate</code> gives you the second object.</p>
<p>But if you create a new instance, the names will be guaranteed:</p>
<pre><code class="language-csharp">var customerFaker = new CustomerFaker();
var customers = customerFaker.Generate(1);
var companyName = customers.First().CompanyName;
var newFaker = new CustomerFaker();
var newCustomers = newFaker.Generate(1);
Assert.IsTrue(companyName == newCustomers.First().CompanyName); // TRUE
</code></pre>
<p>This support the idea of repeatable sample data!</p>
<h2 id="creating-related-sample-data">Creating Related Sample Data</h2>
<p>In our <code>Customer</code> class, we have a property for an <code>Address</code>. We can create a Faker for the <code>Address</code> too:</p>
<pre><code class="language-csharp">public class AddressFaker : Faker<Address>
{
public AddressFaker()
{
var id = 0;
UseSeed(1969)
.RuleFor(c => c.Id, f => ++id)
.RuleFor(c => c.Address1, f => f.Address.StreetAddress())
.RuleFor(c => c.Address2, f => f.Address.SecondaryAddress())
.RuleFor(c => c.City, f => f.Address.City())
.RuleFor(c => c.StateProvince, f => f.Address.State())
.RuleFor(c => c.PostalCode, f => f.Address.ZipCode());
}
}
</code></pre>
<p>Again, there is a category for the type of data we need and can decide how to generate sample addresses. One thing you might want is to optionally not create certain parts of the fake data. For example, for our addresses, I want some of the <code>Address2</code> properties to be null to replicate some apartment/suite numbers and addresses that do not have them. To do this, you can use <code>OrNull()</code> method:</p>
<pre><code class="language-csharp">.RuleFor(c => c.Address2, f => f.Address.SecondaryAddress()
.OrNull(f, .5f))
</code></pre>
<p>The <code>OrNull</code> method takes the faker object and a value between 0 and 1 to determine how often to generate a <code>null</code> value. In this example, we’re specifying that we want half (or 50%) of the Addresses to have a null for it’s secondary address.</p>
<p>Now that we have a faker that does what we want, let’s use it to generate addresses too!</p>
<pre><code class="language-csharp">public class CustomerFaker : Faker<Customer>
{
AddressFaker _addrFaker = new AddressFaker();
public CustomerFaker()
{
var id = 1;
UseSeed(1969) // Use any number
.RuleFor(c => c.Id, _ => id++)
.RuleFor(c => c.CompanyName, f => f.Company.CompanyName())
.RuleFor(c => c.ContactName, f => f.Name.FullName())
.RuleFor(c => c.Phone, f => f.Phone.PhoneNumberFormat())
.RuleFor(c => c.Address, _ => _addrFaker.Generate(1)
.First()
.OrNull(_, .1f));
}
}
</code></pre>
<p>You can notice that we’re creating an instance of the <code>AddressFaker</code> and then using it when we specify the rule for the <code>Customer</code>’s <code>Address</code> property. We can even use <code>OrNull</code> to only generate Addresses for 90% of the customers.</p>
<p>There is a lot more to the Bogus library, but hopefully this will get you started. To get the example code from the video and this blog post, see the Github Repo:</p>
<blockquote>
<p><a href="https://github.com/shawnwildermuth/codingshorts/tree/main/bogus">FixingIt Code</a></p>
</blockquote>
<div>
<div style="float: left;">
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
<img alt="Creative Commons License" style="border-width: 0" src="http://i.creativecommons.org/l/by-nc-nd/3.0/88x31.png" /></a></div>
<div>
This work by <a xmlns:cc="http://creativecommons.org/ns#" href="http://wildermuth.com"
property="cc:attributionName" rel="cc:attributionURL">Shawn Wildermuth</a> is
licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License</a>.<br />
Based on a work at <a xmlns:dct="http://purl.org/dc/terms/" href="http://wildermuth.com"
rel="dct:source">wildermuth.com</a>.</div>
</div>
<hr/><div>If you liked this article, see Shawn's courses on <a href="http://shawnw.me/pscourses">Pluralsight</a>.</div>
Vite-Powered SPAs in ASP.NET Core Projects
2023-01-18T00:00:00Zhttps://wildermuth.com/2023/01/18/vite-in-asp-net-core-projects/https://wilderminds.blob.core.windows.net/img/2023/01/18/cover.jpg
<p>If you’ve heard me talk about Vite in the past (and so commonly mispronouce it), you know I am a fan. With many Vue, React and SvelteKit applications are moving to Vite, I’ve been investigating how to integrate it for development and production into <a href="http://asp.net/">ASP.NET</a> Core applications. Let’s see what I found out.</p>
<p>I also made a Coding Short video that covers this same topic, if you’d rather watch than read:</p>
<div class="embed-responsive embed-responsive-16by9">
<iframe class="embed-responsive-item" src="https://www.youtube.com/embed/YSGLw4T8BgQ" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
</div>
<h3 id="short-intro-to-vite">Short Intro to Vite</h3>
<p>Normally, we’ve used packagers (Webpack, Rollup) to at development-time to watch for changes and hot-swap or reload pages as necessary. For development time, approaches this differently. While Vite also does hot-swapping of code, but it approaches this with actually compiling the project. Instead it exposes a server for a project that relies on script modules.</p>
<p>For example, to start a project, you need to just point at the entry file:</p>
<pre><code class="language-html"><body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</code></pre>
<p>Most modern browsers now support the script-type of module. In this case, Vite loads the main.js and then just follows imports and exports to load all the parts of the project that you need. This means that startup is incredibly fast since there really isn’t any compilation step.</p>
<p>When you’re developing directly with Vite, you can just start it at the command-line and it will server the index.html as well as the script/resource files. Though Vite isn’t really a production ready web-server. The serving of the files is really to have a great development-time experience.</p>
<p>For production time, it still compiles projects (by default with Rollup) in the same way that these frameworks have always done.</p>
<p>With this different approach, integrating with <a href="http://asp.net/">ASP.NET</a> Core presents some challenges.</p>
<h3 id="integrating-a-vite-project-for-production">Integrating a Vite project for Production</h3>
<p>A little background: our project is a simple <a href="http://asp.net/">ASP.NET</a> Core project with a Vite project as a subdirectory called “Client”:</p>
<p><img src="https://wilderminds.blob.core.windows.net/img/2023/01/18/figure1.jpg" alt="Figure 1" /></p>
<p>Before we talk about how to get Vite working for development, let’s talk about how it will work when you publish your app for production (or other non-development builds).</p>
<p>In a Vite project, you can use Vite to build your project by using the build command (shown here in a package.json file’s scripts):</p>
<pre><code class="language-json"> "scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
</code></pre>
<p>Vite uses Rollup (by default) to build and package your project for production. So we can configure Vite to output our project by modifying the vite.config.js file and adding a build configuration:</p>
<pre><code class="language-js">// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
build: {
outDir: "../wwwroot/client",
emptyOutDir: true,
},
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
});
</code></pre>
<p>The <code>outDir</code> is pointing at our wwwroot folder where the <a href="http://asp.net/">ASP.NET</a> Core project will have access to the build files. The <code>emptyOutDir</code> is specifically to empty it before building so you don’t get any extra assets littering that folder.</p>
<p>When we build the project, we get several files generated:</p>
<pre><code class="language-bash">vite v4.0.4 building for production...
✓ 31 modules transformed.
../wwwroot/client/index.html 0.45 kB
../wwwroot/client/assets/index-ca646b5b.css 1.22 kB │ gzip: 0.48 kB
../wwwroot/client/assets/index-bdf9da80.js 77.78 kB │ gzip: 30.96 kB
</code></pre>
<p>Then we can just reference these files in the host page (a Razor page in this example):</p>
<pre><code class="language-html">@page @section Styles {
<link rel="stylesheet" href="/client/assets/index-ca646b5b.css" />
} @section Scripts {
<script src="/client/assets/index-bdf9da80.js"></script>
}
<h1>Film List</h1>
<div id="app"></div>
</code></pre>
<p>Notice that we’re also adding any markup (the <code>div#app</code> in this example) that the Vite project needs.</p>
<p>But we have a problem, the Vite build is giving a cache-busting name (the random string after <code>index-</code>). On every build, this will change, so we can use a special tag helpers:</p>
<pre><code class="language-html">@section Styles {
<link rel="stylesheet" asp-href-include="/client/assets/index-*.css" />
} @section Scripts {
<script asp-src-include="/client/assets/index-*.js"></script>
}
</code></pre>
<p>By using the <code>asp-href-include</code> and <code>asp-src-include</code> tag helpers, we can use a wildcard to include the right files for us.</p>
<p>Lastly, we need to actually run the build. We can do this by just adding the build to the <code>.csproj</code> file. By adding a Target for before publish, we can just execute the build:</p>
<pre><code class="language-xml"><Target Name='CompileClient'
BeforeTargets="Publish">
<Exec WorkingDirectory="./client"
Command="npm install" />
<Exec WorkingDirectory="./client"
Command="npm run build" />
</Target>
</code></pre>
<p>Notice that we’re calling <code>npm install</code> first to be sure that all the packages exist. And that we’re using the <code>WorkingDirectory</code> to specify our <code>client</code> directory.</p>
<p>Now, when you publish the project (manually or in a build script), the Vite project is built too!</p>
<p>But we came to talk about development, let’s talk about that next.</p>
<h3 id="integrating-vite-for-development">Integrating Vite for Development</h3>
<p>Like we saw earlier, during development, you would run Vite and it would load scripts on demand using the <code>type=module</code> method. When you run Vite in this mode, it is essentially running a server for the markup and a single, large SPA. If you’re creating APIs with <a href="http://asp.net/">ASP.NET</a> Core and just hosting your SPA as a single HTML file, this works perfectly.</p>
<blockquote>
<p>NOTE: There is a package called <code>Microsoft.AspNetCore.SpaServices.Extension</code> that is meant to do this, but it is not well documented and may be depreciated by now. It didn’t work well with Vite, though it might for Angular and React using their CLIs</p>
</blockquote>
<p>But in many cases, you’ll want to host one or more SPAs on specific pages of your project. How do we handle this since both <a href="http://asp.net/">ASP.NET</a> Core and Vite will be serving files?</p>
<p>During development you’ll want to run both servers, and just use the vite serving for the assets (.js/.css) for your project. To do this, let’s look at the Razor page again:</p>
<pre><code class="language-html">@page @section Styles {
<link rel="stylesheet" asp-href-include="/client/assets/index-*.css" />
} @section Scripts {
<script asp-src-include="/client/assets/index-*.js"></script>
}
<h1>Film List</h1>
<div id="app"></div>
</code></pre>
<p>What we want to do here is only use these styles and script tags during production, so we can surround it with an <code>environment</code> tag for production:</p>
<pre><code class="language-html"><environment include="Production">
@section Styles {
<link rel="stylesheet" asp-href-include="/client/assets/index-*.css" />
} @section Scripts {
<script asp-src-include="/client/assets/index-*.js"></script>
}
</environment>
</code></pre>
<p>This will set it up to only use the build assets during production. We can then add an <code>environment</code> tag for development:</p>
<pre><code class="language-html"><environment include="Development">
<script type="module" src="http://localhost:5000/src/main.js"></script>
</environment>
</code></pre>
<p>You’ll notice that we’re using the Vite server to serve the main.js file. If you remember from earlier, this will load other assets on-demand and hot-swap them as necessary.</p>
<p>In this way you get the best of both worlds. But we have a problem:</p>
<h3 id="dealing-with-routing-in-vite-projects">Dealing with Routing in Vite Projects</h3>
<p>In our example, we’re hosting the SPA on a page who’s URL is <code>http://localhost:8000/FilmList</code>. This is related to the Razor page’s URL. But our Vite project (Vue in this case) is using history-type routing. That means, when it navigates, it takes over the URL. So when we navigate to our SPA’s home page, it changes it to <code>http://localhost:8000/</code> and for the list page it changes the URL to <code>http://localhost:8000/films</code> (which are based on the projects own routing, not server-side routing).</p>
<p>The problem is if we refresh the page or open that URL, it fails because we’re not serving up our Razor page at those URLs. There are two fixes here. First, we need to tell the Vite project what the base address for our project is. We can do this in <code>vite.config.js</code>:</p>
<pre><code class="language-js">// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
base: "/FilmList",
build: {
outDir: "../wwwroot/client",
emptyOutDir: true,
},
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
});
</code></pre>
<p>You can see the addition of the <code>base</code> property which let’s the app know what the base URL for the project is. In this way, the navigation will start at that base URL. This is better. But then our SPA’s route for the list of films is <code>http://localhost:8080/FilmList/films</code>. But this isn’t a valid server-route either. We need a way to have inter-SPA URLs serve the SPA page (and the internal routing do the right thing).</p>
<p>To do this, you can simply use fallback routes in the <a href="http://asp.net/">ASP.NET</a> Core server. You’ll want to make sure that any fallbacks are specified after all your other routes (e.g. Razor Pages, Controllers and Minimal APIs). You to this by using the <code>MapFallback</code> calls. For example, in our case (since we’re using Razor pages) we can use <code>MapFallbackToPage</code> like so:</p>
<pre><code class="language-csharp">app.MapGet("api/films", async (BechdelDataService ds, int? page, int? pageSize) =>
{
//...
}).Produces<IEnumerable<Film>>(contentType: "application/json").Produces(404).ProducesProblem(500);
app.MapFallbackToPage("/FilmList");
app.Run();
</code></pre>
<p>Note, that this fallback is not redirecting, but just serving that page. That way the URL is preserved for the Vite project to use for it’s own routing.</p>
<p>This will fallback to any page that isn’t found in routing to the FilmList Razor page that contains our SPA. That might be too broad though, you may want to use the fallback with a pattern too (so it only falls back to that page’s URLs) like so:</p>
<pre><code class="language-csharp">app.MapFallbackToPage("/FilmList/{*path}", "/FilmList");
</code></pre>
<p>The first parameter of the <code>MapFallbackToPage</code> allows you to specify a routing pattern to apply this fallback to. In this way, any urls that start with <code>/FilmList</code> will just fallback to that page.</p>
<p>I hope this helps some of you using Vite for your own projects. You can get the example for this project at:</p>
<blockquote>
<p><a href="https://github.com/shawnwildermuth/codingshorts/tree/main/aspnetvite">https://github.com/shawnwildermuth/codingshorts/tree/main/aspnetvite</a></p>
</blockquote>
<p>I’m happy to answer any of your questions below if I’ve been unclear about any of this!</p>
<p>Thanks for reading.</p>
<div>
<div style="float: left;">
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
<img alt="Creative Commons License" style="border-width: 0" src="http://i.creativecommons.org/l/by-nc-nd/3.0/88x31.png" /></a></div>
<div>
This work by <a xmlns:cc="http://creativecommons.org/ns#" href="http://wildermuth.com"
property="cc:attributionName" rel="cc:attributionURL">Shawn Wildermuth</a> is
licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License</a>.<br />
Based on a work at <a xmlns:dct="http://purl.org/dc/terms/" href="http://wildermuth.com"
rel="dct:source">wildermuth.com</a>.</div>
</div>
<hr/><div>If you liked this article, see Shawn's courses on <a href="http://shawnw.me/pscourses">Pluralsight</a>.</div>
More on .NET 7 user-jwts Tool
2023-01-10T00:00:00Zhttps://wildermuth.com/2023/01/10/more-on-net-y-user-jwt-tool/https://wilderminds.blob.core.windows.net/img/2023/01/10/cover.jpg
<p>I recently released a Coding Short video and a blog post about the new JWT Tooling in .NET 7. It was received well, but I didn’t dig into some of the real details of what is happening. If you need to catch up, here is the blog post:</p>
<blockquote>
<p><a href="https://wildermuth.com/2022/12/07/changes-in-jwt-bearer-tokens-in-dotnet-7/">Changes in JWT Bearer Tokens in .NET 7</a></p>
</blockquote>
<p>What I didn’t have a chance to explain is everything that the user-jwts tool actually does. It makes several changes:</p>
<ul>
<li>Adds a section to the appsettings.developer.json file to add user-jwts as a valid issuer of JWTs.</li>
<li>Adds secret key related properties to the user-secrets for the project.</li>
</ul>
<p>What it doesn’t do is wire up your startup to include JwtBearer authentication, it only sets up the tool as an issuer of the JWT. Let’s walk through this.</p>
<h3 id="appsettings.developer.json">AppSettings.Developer.json</h3>
<p>The first step is it adds a new <code>Authentication</code> section to the developer settings:</p>
<pre><code class="language-json"> "Authentication": {
"Schemes": {
"Bearer": {
"ValidAudiences": [
"http://localhost:38015",
"https://localhost:44384",
"http://localhost:5241",
"https://localhost:7254"
],
"ValidIssuer": "dotnet-user-jwts"
}
}
}
</code></pre>
<p>It gets the valid audiences by looking at the <code>launchsettings.json</code> (in the <code>Properties</code> folder) of your project. The ValidIssuer is there to match the issuer of the JWT as configured in user settings (see next section for what I mean).</p>
<h3 id="user-secrets">User Secrets</h3>
<p>In order to allow the JWT to be signed, it needs to have some security information. This information is in the <code>user-secrets</code> file. If your project didn’t have support user-secrets yet, the tool adds the user-secret GUID to the project and then stores some information there. To see what they added, just list the secrets in this project:</p>
<pre><code class="language-bash">> dotnet user-secrets list
</code></pre>
<p>In my case, this returns the secret and valid issuer information (this is a throw-away project, so leaking this secret doesn’t matter):</p>
<pre><code class="language-bash">Authentication:Schemes:Bearer:SigningKeys:0:Value = R98yic+EGjR0asjN8eHe2nSLlhBB8tWcebIxHmcOSko=
Authentication:Schemes:Bearer:SigningKeys:0:Length = 32
Authentication:Schemes:Bearer:SigningKeys:0:Issuer = dotnet-user-jwts
Authentication:Schemes:Bearer:SigningKeys:0:Id = e1b964aa
</code></pre>
<p>What’s interesting here, is that some of these defaults are configurable. By default, when the tool issues a JWT, it uses your machine name identification. If you don’t want that, you can simply override it:</p>
<pre><code class="language-bash">> dotnet user-jwts create -n shawn@aol.com
</code></pre>
<p>You can even change the name of the Issuer with:</p>
<pre><code class="language-bash">> dotnet user-jwts create -n shawn@wildermuth.com --scheme YourIssuerName
</code></pre>
<p>I was able to use this information to just prototype issuing JWTs (via API) to just re-use the tools’ information. You can see here the GetSection matches the information in <code>user-secrets</code>:</p>
<pre><code class="language-csharp">var bearer = _config.GetSection("Authentication:Schemes:Bearer");
if (bearer is not null)
{
var uniqueKey = bearer.GetSection("SigningKeys")
.Get<SymmetricSecurityKey[]>()?
.First()
.Key;
var issuer = bearer["ValidIssuer"];
var audiences = bearer.GetSection("ValidAudiences")
.Get<string[]>();
var key = new SymmetricSecurityKey(uniqueKey);
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256Signature);
</code></pre>
<p>Hope this answers some questions. Ping me below if you have questions!</p>
<div>
<div style="float: left;">
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
<img alt="Creative Commons License" style="border-width: 0" src="http://i.creativecommons.org/l/by-nc-nd/3.0/88x31.png" /></a></div>
<div>
This work by <a xmlns:cc="http://creativecommons.org/ns#" href="http://wildermuth.com"
property="cc:attributionName" rel="cc:attributionURL">Shawn Wildermuth</a> is
licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License</a>.<br />
Based on a work at <a xmlns:dct="http://purl.org/dc/terms/" href="http://wildermuth.com"
rel="dct:source">wildermuth.com</a>.</div>
</div>
<hr/><div>If you liked this article, see Shawn's courses on <a href="http://shawnw.me/pscourses">Pluralsight</a>.</div>
A New Year: Changing Tides
2023-01-02T00:00:00Zhttps://wildermuth.com/stories/2023/01/02/a-new-year/https://wilderminds.blob.core.windows.net/img/2023/01/02/stwsite.jpg
<p>As many of you know, I’ve been doing online training for the past twelve years with Pluralsight. I’ve devoted most of my time to creating courses. It has been amazing and wonderful to do this. But the time has come to diversify what I do. I will continue to make courses for Pluralsight, but I’m also going to be expanding into three areas:</p>
<ul>
<li>In-Person Training (onsite and offsite)</li>
<li>More Coaching</li>
<li>More Project Work</li>
</ul>
<p>I’ve decided to scale back pre-recorded training a bit. I miss working with people directly. I love the experience of teaching classes with actual people and working through problems with a group. I’ve been doing more coaching lately and I find it very rewardining. So, I’ve decided it’s time to do more of that.</p>
<p>As part of this change, I’ll be planning on some public classes. The first one is going to be on <a href="http://asp.net/">ASP.NET</a> Core:</p>
<div class="mx-auto my-8">
<a href="https://shawn.wildermuth.com/calendar">
<img src="https://wilderminds.blob.core.windows.net/img/2023/01/02/workshop.jpg" alt="![ASP.NET Core Workshop" class="max-w-full" />
</a>
</div>
<p>Registration will be open in a week or two. I’ll announce when that happens!</p>
<p>Just to be clear, my relationship with Pluralsight is <strong>not</strong> changing, I’ll just be authoring fewer courses than before. To put out my shingle (and all that), I’ve made a new website with my course library and explanation of what skills I offer to companies. Please give a look!</p>
<blockquote>
<p><a href="https://shawn.wildermuth.com/">https://shawn.wildermuth.com</a></p>
</blockquote>
<p>If you’re interested in any of these new services, you can find them at that new site here:</p>
<blockquote>
<p><a href="https://shawn.wildermuth.com/advice/coaching">Coaching</a><br />
<a href="https://shawn.wildermuth.com/training">Training</a><br />
<a href="https://shawn.wildermuth.com/advice">Software Development</a></p>
</blockquote>
<p>If you have questions about this change or just a comment, feel free to reach out here:</p>
<blockquote>
<p><a href="https://shawn.wildermuth.com/contact">https://shawn.wildermuth.com/contact</a></p>
</blockquote>
<p>Thanks!</p>
<div>
<div style="float: left;">
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
<img alt="Creative Commons License" style="border-width: 0" src="http://i.creativecommons.org/l/by-nc-nd/3.0/88x31.png" /></a></div>
<div>
This work by <a xmlns:cc="http://creativecommons.org/ns#" href="http://wildermuth.com"
property="cc:attributionName" rel="cc:attributionURL">Shawn Wildermuth</a> is
licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License</a>.<br />
Based on a work at <a xmlns:dct="http://purl.org/dc/terms/" href="http://wildermuth.com"
rel="dct:source">wildermuth.com</a>.</div>
</div>
<hr/><div>If you liked this article, see Shawn's courses on <a href="http://shawnw.me/pscourses">Pluralsight</a>.</div>
Changes in JWT Bearer Token Setup in .NET 7
2022-12-07T12:00:00Zhttps://wildermuth.com/2022/12/07/changes-in-jwt-bearer-tokens-in-dotnet-7/https://wilderminds.blob.core.windows.net/img/2022/12/07/jwtcover.jpg
<p>If you’ve taken my <a href="https://shawnl.ink/pscoreapi">“Building an API with ASP.NET Core”</a> course over at Pluralsight, one of the more complicated tasks is to add support for JWT Bearer tokens. In .NET 7, they’ve simplified this quite a bit. Let’s take look.</p>
<blockquote>
<p>A Video Walkthrough on Coding Shorts: <a href="https://youtu.be/osZvEAJrz1Y">https://youtu.be/osZvEAJrz1Y</a></p>
</blockquote>
<p>Instead of showing you how it used to work, let’s just talk about how to add JWT Bearer tokens to a new project. I started out with a simple Web API project (controller-based), though this technique is identical for gRPC or Minimal APIs. When we start out, authorization isn’t even enabled (unless you chose for it to scaffold authorization), but I’m going to assume that Program.cs looks similar to this:</p>
<pre><code class="language-csharp">var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// ...
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
</code></pre>
<p>The only hint about Authorization is the <strong>UseAuthorization</strong> which is just the middleware to check for any Authentication Schemes that are added. You might also note that there is not a <strong>UseAuthentication</strong> any longer. It is no longer necessary to include both.</p>
<p>So let’s support JWT Bearer Tokens. First we still need to add the package to our project:</p>
<pre><code>> dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
</code></pre>
<p>Once added, we can wire up authentication:</p>
<pre><code class="language-csharp">builder.Services.AddAuthentication();
</code></pre>
<p>One thing to note here is that you do not need to specify a default scheme any longer (the first scheme is assumed default). So we can call the <strong>AddJwtBearerToken</strong> call that you might be used to:</p>
<pre><code class="language-csharp">builder.Services.AddAuthentication()
.AddJwtBearer();
</code></pre>
<p>Ordinarily, you would need to configure the token here but finally we have sensible defaults. We’ll get back to configuring it. But first, let’s head over to the Controller Action and add that magic <strong>Authorize</strong> attribute (just like before):</p>
<pre><code class="language-csharp">[ApiController]
[Route("[controller]")]
[Authorize]
public class WeatherForecastController : ControllerBase
</code></pre>
<p>So if we open the API, we get:</p>
<p><img src="https://wilderminds.blob.core.windows.net/img/2022/12/07/net7jwt-figure1.jpg" alt="Unauthorized"" /></p>
<p>Don’t worry, that’s what was supposed to happen!</p>
<p>Since we setup JWT Bearer tokens, we should be able to just add a token to the request and it should work. But how do we get a token? Ah, another gem inside .NET 7: <strong>dotnet user-jwts</strong>. The <strong>user-jwts</strong> took is all about being able to look at a project and build a developer JWT from the configuration. Yes, this means you don’t have to build your API for returning tokens until you actually need to. To use it just call the create sub-command:</p>
<pre><code>>dotnet user-jwts create
</code></pre>
<p>It will spit out:</p>
<pre><code>Token: eyJhbGciOiJI...QpMdl0LRL7w1f1cnEu7dwJMvw4eSi1px56dneQ5tOQg
</code></pre>
<p>No, this isn’t really my token. But you’ll see that it will paste out an entire JWT Bearer token that we can then use in the request testing:</p>
<p><img src="https://wilderminds.blob.core.windows.net/img/2022/12/07/net7jwt-figure2.jpg" alt="Success" /></p>
<p>Last note, we can still configure the JWT Bearer token, but now we can use configuration like adults. In fact, the <strong>user-jwts</strong> tool will add a section in your development <strong>appsettings.json</strong> file the first time you create a JWT:</p>
<pre><code class="language-json"> "Authentication": {
"Schemes": {
"Bearer": {
"ValidAudiences": [
"http://localhost:64693",
"https://localhost:44317",
"http://localhost:5124",
"https://localhost:7125"
],
"ValidIssuer": "dotnet-user-jwts"
}
}
}
</code></pre>
<p>Your exact settings will be a little different, but you can see that you can specify all the JWT settings here in configuration. Since this is in the <strong>appsettings.development.js</strong> file, those tokens created by <strong>user-jwts</strong> will never work in production as an added security gate (see how the <strong>ValidIssuer</strong> is the <strong>user-jwts</strong> tool). In fact, if we decode our JWT that it generated, we can see that it doesn’t have anything special except for machine user name which it uses as the unique id:</p>
<pre><code class="language-json">{
"unique_name": "shawn",
"sub": "shawn",
"jti": "be38d021",
"aud": [
"http://localhost:64693",
"https://localhost:44317",
"http://localhost:5124",
"https://localhost:7125"
],
"nbf": 1670459953,
"exp": 1678235953,
"iat": 1670459954,
"iss": "dotnet-user-jwts"
}
</code></pre>
<blockquote>
<p><a href="https://github.com/shawnwildermuth/codingshorts/tree/main/JwtInSeven">Code Sample</a></p>
</blockquote>
<p>Most of this should look familar! Let me know what you think!</p>
<div>
<div style="float: left;">
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
<img alt="Creative Commons License" style="border-width: 0" src="http://i.creativecommons.org/l/by-nc-nd/3.0/88x31.png" /></a></div>
<div>
This work by <a xmlns:cc="http://creativecommons.org/ns#" href="http://wildermuth.com"
property="cc:attributionName" rel="cc:attributionURL">Shawn Wildermuth</a> is
licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License</a>.<br />
Based on a work at <a xmlns:dct="http://purl.org/dc/terms/" href="http://wildermuth.com"
rel="dct:source">wildermuth.com</a>.</div>
</div>
<hr/><div>If you liked this article, see Shawn's courses on <a href="http://shawnw.me/pscourses">Pluralsight</a>.</div>
Wiring Up Azure AD to ASP.NET 7
2022-12-07T12:00:00Zhttps://wildermuth.com/2022/12/14/wiring-up-azure-ad-to-aspnet-7/
<div>
<div style="float: left;">
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
<img alt="Creative Commons License" style="border-width: 0" src="http://i.creativecommons.org/l/by-nc-nd/3.0/88x31.png" /></a></div>
<div>
This work by <a xmlns:cc="http://creativecommons.org/ns#" href="http://wildermuth.com"
property="cc:attributionName" rel="cc:attributionURL">Shawn Wildermuth</a> is
licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License</a>.<br />
Based on a work at <a xmlns:dct="http://purl.org/dc/terms/" href="http://wildermuth.com"
rel="dct:source">wildermuth.com</a>.</div>
</div>
<hr/><div>If you liked this article, see Shawn's courses on <a href="http://shawnw.me/pscourses">Pluralsight</a>.</div>
C# 11 Strings in the Raw
2022-11-26T12:00:00Zhttps://wildermuth.com/2022/11/26/csharp-11-strings-in-the-raw/https://wilderminds.blob.core.windows.net/img/2022/11/26/cover.jpg
<p>With the release of C# 11, there is a new string in town. It might be getting crazy, but this new “Raw String Literals” is something interesting. Before we dig in, let’s have a refresher about strings:</p>
<h2 id="simple-strings">Simple Strings</h2>
<p>Every language needs a type for storing text, that’s the <strong>String</strong> class/<strong>string</strong> keyword. It’s what is used mainly in C# and has been there since day 1:</p>
<pre><code class="language-csharp">var text = "I thought we had literal strings";
WriteLine(text);
</code></pre>
<p>We could use embedded quotes, but we needed to escape them (or other things like tabs, line breaks):</p>
<pre><code class="language-csharp">text = "John \"Quincy\" Adams";
</code></pre>
<p>We didn’t have a good solution for multi-line, so we ended up with monstrosities (or using a <strong>StringBuilder</strong>):</p>
<pre><code class="language-csharp">text = "First Line\n";
text += "Second Line\n";
text += "Third Line";
</code></pre>
<h2 id="string-interpolation">String Interpolation</h2>
<p>Some time, later C# introduced a way to have strings just use interpolation (instead of the dreaded <strong>string.Format(…)</strong>):</p>
<pre><code class="language-csharp">text = $"This is an interpolated string: {someName} - {someName.Length}";
</code></pre>
<p>The dollar sign said that it should expect C# code inside brackets.</p>
<p>But this introduced a need to double bracket if you needed a bracket in string:</p>
<pre><code class="language-csharp">text = $"This is an interpolated string: {{ {someName} }}";
</code></pre>
<p>A compromise to support this better way of building strings. Of course, that made JSON in strings pretty ugly:</p>
<pre><code class="language-csharp">text = $"{{ \"name\": \"Shawn\" }}";
</code></pre>
<h2 id="verbatim-strings">Verbatim Strings</h2>
<p>In came Verbatim strings. This allowed you to have line breaks and look simpler:</p>
<pre><code class="language-csharp">text = @"This is a verbatim string.
This means you can have line breaks
and use double ""'s to output quotes.";
</code></pre>
<p>While many things didn’t need to be escaped, you still needed something special for double-quotes. If you needed double-quotes you had to double them to get:</p>
<pre><code class="language-console">This is a verbatim string.
This means you can have line breaks
and use double "'s to output quotes.
</code></pre>
<p>Not perfect, but worked in a lot of situations, but if you wanted to use a format like XML or JSON, it ended up with this ugliness:</p>
<pre><code class="language-csharp">text = @
{
""name"": ""Shawn"
}
";
</code></pre>
<h2 id="verbatim-strings-with-interpolation">Verbatim Strings with Interpolation</h2>
<p>But you could mix verbatim and interpolation:</p>
<pre><code class="language-csharp">text = $@"This is a verbatim
interpolated string: {someName}";
</code></pre>
<p>This worked, unless you needed brackets as text:</p>
<pre><code class="language-csharp">// Doesn't work, as it thinks that the whole thing is interpolated
text = $@"
{
""name"": ""{someName}""
}";
</code></pre>
<p>So, now we need to double-quote as well as double curly-brace:</p>
<pre><code class="language-csharp">// Works
text = $@"
{{
""name"": ""{someName}""
}}";
</code></pre>
<p>Hrmph…</p>
<h2 id="ok%2C-raw-string-literals%3F">Ok, Raw String Literals?</h2>
<p>Here’s where Raw String Literals come in. The idea is to allow anything between a set of delimiters. That delimiter is a triple-quote (yes, seriously, but it does make sense).</p>
<p>When you triple-quote, everything is a literal, No magic here:</p>
<pre><code class="language-csharp">text = """This is pure text, which includes " and ' and other literals""";
</code></pre>
<p>This works, but the rules are a tad different for multi-line, in that case the triple-quotes must be on their own lines:</p>
<pre><code class="language-csharp">text = """
Everything between the quotes is literally interpreted,
There is no need for escaping of anything.
Really!
You can use "Whatever you Like"
""";
</code></pre>
<p>What’s important here is that the triple-quoted lines are not included in the string. The string starts on the first line after the triple-quote. This solves a lot of the messiness with commonly used things like JSON:</p>
<pre><code class="language-csharp">text = """
{
"name": "Shawn"
}
""";
</code></pre>
<p>Whew, that’s easy…almost.</p>
<h2 id="interpolated-raw-string-literals%3F">Interpolated Raw String Literals?</h2>
<p>If you add interpolation to a raw string, you find an odd situation where the compiler must know what is an interpolated section (Raw String Literals assume <em>everything</em> is just text). Like the other types of strings, you start interpolated strings with a dollar sign:</p>
<pre><code class="language-csharp">text = $"""<name>{someName}</name>""";
</code></pre>
<p>Simple, sure. But what if you need brackets?</p>
<pre><code class="language-csharp">// Doesn't work, as it's expecting the brace
// to be part of the interpolation
text = $"""
{
"name": "{someName}"
}
""";
</code></pre>
<p>The solution? To do interpolation with brackets, the number of dollar-signs represents the number of brackets to indicate an interpolation:</p>
<pre><code class="language-csharp">// Add number of $ for the depth of the braces needed
text = $$"""
{
"name": "{{someName}}"
}
""";
</code></pre>
<p>This isn’t a typo, this is valid C# 11. Solves a ton of situations, but still requires you to remember some stuff (I actually wish that had used the back-tick like JavaScript as it reads easier IMO).</p>
<p>What do you think of new new syntax (and do you think you’ll be using it?) Tell me in the comments!</p>
<div>
<div style="float: left;">
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
<img alt="Creative Commons License" style="border-width: 0" src="http://i.creativecommons.org/l/by-nc-nd/3.0/88x31.png" /></a></div>
<div>
This work by <a xmlns:cc="http://creativecommons.org/ns#" href="http://wildermuth.com"
property="cc:attributionName" rel="cc:attributionURL">Shawn Wildermuth</a> is
licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License</a>.<br />
Based on a work at <a xmlns:dct="http://purl.org/dc/terms/" href="http://wildermuth.com"
rel="dct:source">wildermuth.com</a>.</div>
</div>
<hr/><div>If you liked this article, see Shawn's courses on <a href="http://shawnw.me/pscourses">Pluralsight</a>.</div>
My Next Film: You Can Help
2022-11-09T12:00:00Zhttps://wildermuth.com/2022/11/09/new-documentary-kickstarter-dont-worry-imfine/https://wilderminds.blob.core.windows.net/img/2022/11/09/cover-withouttext.jpg
<p>So…I know that this isn’t the tech content I usually have on my website, but I have a new feature-lengthed documentary called <a href="https://imfinefilm.com/">“Don’t Worry, I’m Fine”</a>. I want to share some reasons I’m making this film.</p>
<h1 id="my-story">My Story</h1>
<blockquote>
<p><TRIGGER-WARNING></p>
</blockquote>
<p>I kept a secret for a long time. At 6 and 13, my brother sexually abused me many times. That secret festered inside and left me with wounds that I still haven’t completely recovered from.</p>
<p>As I’ve been getting help for these wounds, I realized that there were men out there who didn’t get the help that I did. I decided that the best way to do that is to make a film to shine a light in the dark corner of childhood sexual abuse of boys. I hope this film can reach those men.</p>
<blockquote>
<p></TRIGGER-WARNING></p>
</blockquote>
<h1 id="support-the-film">Support the Film</h1>
<p>To finish the film, I’ve started a Kickstarter to help me with post-production help. While we’ve reached our initial funding, we’ve added stretch goals. If this is something that you would be interested in, you can pre-order the film or just back the project. Every dollar helps:</p>
<blockquote>
<p><a href="https://imfinel.ink/ks">imfinel.ink/ks</a></p>
</blockquote>
<p>Here’s the intro video for the Kickstarter:</p>
<div class="embed-responsive embed-responsive-16by9">
<iframe class="embed-responsive-item" src="https://www.youtube.com/embed/QjUlwaFN4Js" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
</div>
<h1 id="resources">Resources</h1>
<p>If you have a history like mine and need some help, I have a blog entry about resources for you:</p>
<blockquote>
<p><a href="https://twainfilms.com/blog/2022/09/28/resources-for-victims-of-csa/">twainfilms.com/blog</a></p>
</blockquote>
<div>
<div style="float: left;">
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
<img alt="Creative Commons License" style="border-width: 0" src="http://i.creativecommons.org/l/by-nc-nd/3.0/88x31.png" /></a></div>
<div>
This work by <a xmlns:cc="http://creativecommons.org/ns#" href="http://wildermuth.com"
property="cc:attributionName" rel="cc:attributionURL">Shawn Wildermuth</a> is
licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License</a>.<br />
Based on a work at <a xmlns:dct="http://purl.org/dc/terms/" href="http://wildermuth.com"
rel="dct:source">wildermuth.com</a>.</div>
</div>
<hr/><div>If you liked this article, see Shawn's courses on <a href="http://shawnw.me/pscourses">Pluralsight</a>.</div>
Leaving the .NET Foundation Board - a Post Mortem
2022-10-31T12:00:00Zhttps://wildermuth.com/2022/10/31/goodbye-dotnetfoundation/https://wilderminds.blob.core.windows.net/img/2022/10/31/cover-dnf.jpg
<blockquote>
<p>tl;dr; There is no Tea to be Found Here</p>
</blockquote>
<p>In case you didn’t see the notice, my term as a board member of the .NET Foundation is now over. After two years, my term has expired. My term was an interesting time to be inside the .NET Foundation. I wanted to reflect on that time.</p>
<h2 id="being-on-the-board-of-directors">Being on the Board of Directors</h2>
<p>I honestly wasn’t sure I was ready to take on the role. Echoes of my imposter syndrome certainly flared up during those early days. But, for me, I wasn’t alone. When I joined the board, all the board members were brand new. There wasn’t a ton of institutional memory but that also gave us a blank slate to start with.</p>
<p>I could talk about each and every member of both boards I was part of, but I am sure I’d leave someone out. But, suffice to say, I believed that every member was focused on addressing real issues in the community.</p>
<p>I would be remiss if I said that some departures from the board confused and upset me. But, this isn’t where I want to air those issues. I really want to get the story across of what I hope is my legacy on the board.</p>
<h2 id="outreach-committee">Outreach Committee</h2>
<p>Once on the board, I knew immediately that I wanted to help out on the outreach committee. I think I was a bit misguided at first. I was very focused on trying to help broaden .NET adoption by shining a light outside North America and Europe. If I am being honest with myself, there was more than a bit of white savior in that first few months.</p>
<p>The committee reached out to some of the MVPs and others in Africa, Asia and South America. We soon learned that the things we wanted to help them with wasn’t what they needed. It seems that we weren’t the first people to try and throw technology at them (e.g. Azure credits, books, courses). I feel like we really failed in this. It’s one of the disappointments that take with me from the .NET Foundation.</p>
<p>There are accomplishments I am proud of (e.g. launching the Speaker Bureau, continuing commitment to meetup accounts for user groups, etc.)</p>
<p>My hope is that I left the Outreach Commitee better than I found it.</p>
<h2 id="the-turmoil">The Turmoil</h2>
<p>I would be remiss if I didn’t mention the rocky road that the foundation ran into a year ago. I learned a lot about how to work in a crisis. While we might not have handled it as gracefully as we had wished, I will say that I think that everyone involved was sincerely trying to do the right thing. It’s not mine to say whether we succeeded. At the end of the day, I hope the foundation will help adoption of .NET open source projects.</p>
<p>The new board members last year (Mattias Karlsson-<a href="https://twitter.com/devlead">@devlead</a>, Rich Lander-<a href="https://twitter.com/runfaster2000">@runfaster2000</a>, and Rob Prouse-<a href="https://twitter.com/rprouse">@rprouse</a>) were really instrumental in steering the committee to focus on maintainers of .NET open source projects. It was amazing to see these members come in and contribute so quickly. I was humbled by it.</p>
<h2 id="moving-forward">Moving Forward</h2>
<p>I will continue to support the foundation as much as I can. I believe in what they are doing. Sometimes I think the community at large doesn’t see what they are contributing to the ecosystem. Joseph Guadagno-<a href="https://twitter.com/jguadagno">@jguadagno</a> is taking over on the Outreach Committee. If you have a proposal to how the outreach committee could help your corner of the world, don’t forget you can submit a proposal here:</p>
<blockquote>
<p><a href="https://github.com/dotnet-foundation/wg-outreach/issues/new?labels=proposals&template=proposal.md">https://github.com/dotnet-foundation/wg-outreach/issues/new?labels=proposals&template=proposal.md</a></p>
</blockquote>
<p>Thanks so much for letting me serve.</p>
<div>
<div style="float: left;">
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
<img alt="Creative Commons License" style="border-width: 0" src="http://i.creativecommons.org/l/by-nc-nd/3.0/88x31.png" /></a></div>
<div>
This work by <a xmlns:cc="http://creativecommons.org/ns#" href="http://wildermuth.com"
property="cc:attributionName" rel="cc:attributionURL">Shawn Wildermuth</a> is
licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License</a>.<br />
Based on a work at <a xmlns:dct="http://purl.org/dc/terms/" href="http://wildermuth.com"
rel="dct:source">wildermuth.com</a>.</div>
</div>
<hr/><div>If you liked this article, see Shawn's courses on <a href="http://shawnw.me/pscourses">Pluralsight</a>.</div>
New Videos: Coding Short and a Rant
2022-10-31T00:00:00Zhttps://wildermuth.com/2022/10/31/codingshort-and-a-rant/https://wilderminds.blob.core.windows.net/img/2022/10/31/cover.jpg
<p>I can’t believe October is almost over! I forgot to post about a couple of videos I made this month. The first one is about new features in .NET 7 for Minimal APIs. The second one is a rant(ish) about support terms for Open Source projects (including .NET). Take a gander:</p>
<div class="embed-responsive embed-responsive-16by9">
<iframe class="embed-responsive-item" src="https://www.youtube.com/embed/8c73j7RHoSQ" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
</div>
<br />
<br />
<div class="embed-responsive embed-responsive-16by9">
<iframe class="embed-responsive-item" src="https://www.youtube.com/embed/oR4dELwe57Q" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
</div>
<blockquote>
<p><a href="https://shawnl.ink/yt">Subscribe to my YouTube Channel</a></p>
</blockquote>
<div>
<div style="float: left;">
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
<img alt="Creative Commons License" style="border-width: 0" src="http://i.creativecommons.org/l/by-nc-nd/3.0/88x31.png" /></a></div>
<div>
This work by <a xmlns:cc="http://creativecommons.org/ns#" href="http://wildermuth.com"
property="cc:attributionName" rel="cc:attributionURL">Shawn Wildermuth</a> is
licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License</a>.<br />
Based on a work at <a xmlns:dct="http://purl.org/dc/terms/" href="http://wildermuth.com"
rel="dct:source">wildermuth.com</a>.</div>
</div>
<hr/><div>If you liked this article, see Shawn's courses on <a href="http://shawnw.me/pscourses">Pluralsight</a>.</div>
New Video: Coding Shorts - Static Site Generation with Eleventy
2022-10-02T00:00:00Zhttps://wildermuth.com/2022/10/02/coding-shorts-static-site-generation-11ty/https://wilderminds.blob.core.windows.net/img/2022/10/02/cover.jpg
<p>For some of my personal sites that aren’t the dynamic, I’ve moved them from <a href="http://asp.net/">ASP.NET</a> to static site generation. I find it the right solution for a myriad of smaller websites. In this video I’ll show you how you can do it with Eleventy (@11ty):</p>
<div class="embed-responsive embed-responsive-16by9">
<iframe class="embed-responsive-item" src="https://www.youtube.com/embed/JiC9JWRsfcE" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
</div>
<blockquote>
<p><a href="https://shawnl.ink/yt">Subscribe to my YouTube Channel</a></p>
</blockquote>
<div>
<div style="float: left;">
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
<img alt="Creative Commons License" style="border-width: 0" src="http://i.creativecommons.org/l/by-nc-nd/3.0/88x31.png" /></a></div>
<div>
This work by <a xmlns:cc="http://creativecommons.org/ns#" href="http://wildermuth.com"
property="cc:attributionName" rel="cc:attributionURL">Shawn Wildermuth</a> is
licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License</a>.<br />
Based on a work at <a xmlns:dct="http://purl.org/dc/terms/" href="http://wildermuth.com"
rel="dct:source">wildermuth.com</a>.</div>
</div>
<hr/><div>If you liked this article, see Shawn's courses on <a href="http://shawnw.me/pscourses">Pluralsight</a>.</div>
New Video: Coding Shorts - Generic Attributes in C# 11
2022-09-15T00:00:00Zhttps://wildermuth.com/2022/09/15/coding-shorts-generic-attributes-csharp-11/https://wilderminds.blob.core.windows.net/img/2022/09/15/cover.jpg
<p>I’ve been busy with a lot of small projects lately. Among these is to get up to speed with .NET 7 and C# 11. One of the things I’ve been curious about is the new support for Generic Attributes. After some digging, I think I understand where they belong. Let’s discuss it in my new Coding Short video:</p>
<div class="embed-responsive embed-responsive-16by9">
<iframe class="embed-responsive-item" src="https://www.youtube.com/embed/tychin_WjEo" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
</div>
<blockquote>
<p><a href="https://shawnl.ink/yt">Subscribe to my YouTube Channel</a></p>
</blockquote>
<div>
<div style="float: left;">
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
<img alt="Creative Commons License" style="border-width: 0" src="http://i.creativecommons.org/l/by-nc-nd/3.0/88x31.png" /></a></div>
<div>
This work by <a xmlns:cc="http://creativecommons.org/ns#" href="http://wildermuth.com"
property="cc:attributionName" rel="cc:attributionURL">Shawn Wildermuth</a> is
licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License</a>.<br />
Based on a work at <a xmlns:dct="http://purl.org/dc/terms/" href="http://wildermuth.com"
rel="dct:source">wildermuth.com</a>.</div>
</div>
<hr/><div>If you liked this article, see Shawn's courses on <a href="http://shawnw.me/pscourses">Pluralsight</a>.</div>
TailwindCSS Tip: Arbitrary Values in Utility Classes
2022-09-06T00:00:00Zhttps://wildermuth.com/2022/09/06/tailwindcss-arbitrary-values/https://wilderminds.blob.core.windows.net/img/2022/09/06/cover.jpg
<p>I’m currently redesigning this blog (coming soon) and I’ve been using TailwindCSS to handle most of the<br />
heavy lifting. I know TailwindCSS isn’t for everyone, but I think it’s a great tool. Being able to<br />
use the utility classes to quickly design the new version of the site, has been a ton of fun. Something I would rarely say about using just CSS.</p>
<p>While I’ve talked about TailwindCSS quite a lot on the blog (and in my recently released <a href="https://shawnl.ink/pstailwindcss">Pluralsight Course</a>), one thing that I didn’t realize worked was “one-off values” in TailwindCSS.</p>
<p>Let me show you what I mean:</p>
<pre><code class="language-HTML"><div class="md:h-screen
bg-gray-900
text-gray-200
md:sticky
md:top-0"
id="sidebar">
<aside class="p-2
md:flex
md:flex-col
md:h-full">
<div class="md:flex-grow hidden md:block"></div>
<div>
<a href="/"
><img
src="/img/headshots/shawn-head-sm.jpg"
data-src="/img/headshots/shawn-head.gif"
class="lazy
rounded-full
border-2
border-transparent
shadow-md
shadow-gray-700
hover:border-gray-500
hidden md:block
w-40
mx-auto"
alt="Headshot"
/></a>
</code></pre>
<p>On my new site, the sidebar is a named object on the page. What I wanted to do was set it’s width to a specific size. I could that by using one of the built-in values:</p>
<pre><code class="language-HTML"><div class="md:h-screen
bg-gray-900
text-gray-200
md:sticky
md:top-0
w-56"
id="sidebar">
</code></pre>
<p>The <code>w-56</code> would simply set the width to 14rem for me (56 / 4 = 14, by default each value in the numbering system equates to a 1/4 of a rem). But that size wasn’t exactly right I need it to be exactly 14.5rem. And the default TailwindCSS classes do not have a value for that. One approach is to simply add a value in the CSS for this:</p>
<pre><code class="language-css"> #sidebar {
width: 14.5rem;
}
</code></pre>
<p>But that leads to a lot of manually maintainance. I could define this specifically by extending the TailwindCSS configuration:</p>
<pre><code class="language-js">module.exports = {
content: [
"content/**/*.{md,html}"],
theme: {
extend: {
width: {
"54": "14rem"
},
</code></pre>
<p>But extending the entire theme for a single, one-off size seems excessive (and likely harder to manage since if I don’t need it later, it won’t disappear by itself). This trick is actually to support specific values with TailwindCSS’s utility classes. The fix is to just add the size (and TailwindCSS will create a utility class for it):</p>
<pre><code class="language-HTML"><div class="md:h-screen
bg-gray-900
text-gray-200
md:sticky
md:top-0
w-[14.5rem]"
id="sidebar">
</code></pre>
<p>That <code>w-[14.5rem]</code> is telling the TailwindCSS compiler to create a class with that specific name but with that specific value. This is the output:</p>
<pre><code class="language-css">.w-\[14\.5rem\]{
width: 14.5rem;
}
</code></pre>
<p>This relates to this one-off value. You can do it for almost any utility class that is using the <code>spacing</code> system. For example:</p>
<pre><code class="language-HTML"><div class="w-[200px]
h-[30rem]
pl-[1px]
mt-[10px]
tracking-[.001rem]">
</div>
</code></pre>
<p>This extends to other types of properties as well that might benefit from arbitrary values:</p>
<pre><code class="language-html"><div class="bg-[url(img/somebackground.png)]
before:content-['add']
text-[#FFEEDD]">
</div>
</code></pre>
<p>You can see how this is helpful to avoid CSS or inline-style workarounds. One thing to note is that the utility class name must not contain spaces, but you can use underscore (_) to replace a space:</p>
<pre><code class="language-html"><div class="before:content-['add_more']">
</div>
</code></pre>
<p>This will set the before content to <code>add more</code>, but if you really need an underscore, you can escape it:</p>
<pre><code class="language-html"><div class="before:content-['add\_more']">
</div>
</code></pre>
<p>This will set the before content to <code>add_more</code>.</p>
<p>The arbitrary values really help you when you don’t need a whole class or CSS rule but a one-off value. I hope this has been useful!</p>
<blockquote>
<p>You can watch my full TailwindCSS course on Pluralsight here: <a href="https://shawnl.ink/pstailwindcss">TailwindCSS 3 Fundamentals</a></p>
</blockquote>
<div>
<div style="float: left;">
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
<img alt="Creative Commons License" style="border-width: 0" src="http://i.creativecommons.org/l/by-nc-nd/3.0/88x31.png" /></a></div>
<div>
This work by <a xmlns:cc="http://creativecommons.org/ns#" href="http://wildermuth.com"
property="cc:attributionName" rel="cc:attributionURL">Shawn Wildermuth</a> is
licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License</a>.<br />
Based on a work at <a xmlns:dct="http://purl.org/dc/terms/" href="http://wildermuth.com"
rel="dct:source">wildermuth.com</a>.</div>
</div>
<hr/><div>If you liked this article, see Shawn's courses on <a href="http://shawnw.me/pscourses">Pluralsight</a>.</div>
New Video: Coding Shorts - Output Caching in ASP.NET Core 7
2022-08-10T00:00:00Zhttps://wildermuth.com/2022/08/10/coding-shorts-output-caching-asp-net-core/https://wilderminds.blob.core.windows.net/img/2022/08/10/cover.jpg
<p>In continuing my discussion of middleware in my Coding Shorts videos, I have a new one that talks about a change to .NET 7 that brings back Output Caching. If you didn’t see that overview of middleware, you can see it here: <a href="https://wildermuth.com/2022/07/17/coding-shorts-asp-net-core-middleware-explained/">How ASP.NET Core Middleware Works</a>. If you didn’t use Output Caching in <a href="http://asp.net/">ASP.NET</a>, you should definitely take a look and prepare to use it in your <a href="http://asp.net/">ASP.NET</a> Core apps:</p>
<div class="embed-responsive embed-responsive-16by9">
<iframe class="embed-responsive-item" src="https://www.youtube.com/embed/kReNMzHj6ro" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
</div>
<blockquote>
<p><a href="https://shawnl.ink/yt">Subscribe to my YouTube Channel</a></p>
</blockquote>
<div>
<div style="float: left;">
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
<img alt="Creative Commons License" style="border-width: 0" src="http://i.creativecommons.org/l/by-nc-nd/3.0/88x31.png" /></a></div>
<div>
This work by <a xmlns:cc="http://creativecommons.org/ns#" href="http://wildermuth.com"
property="cc:attributionName" rel="cc:attributionURL">Shawn Wildermuth</a> is
licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License</a>.<br />
Based on a work at <a xmlns:dct="http://purl.org/dc/terms/" href="http://wildermuth.com"
rel="dct:source">wildermuth.com</a>.</div>
</div>
<hr/><div>If you liked this article, see Shawn's courses on <a href="http://shawnw.me/pscourses">Pluralsight</a>.</div>
Twenty Plus Years of Blogging
2022-07-28T00:00:00Zhttps://wildermuth.com/2022/07/28/twenty-years-of-blogging/https://wilderminds.blob.core.windows.net/img/2022/07/28/cover.jpg
<p>I’ve been logging a long time. My wife and I were talking about it. We realized that it’s been over twenty years since my first nascient online attempts. It’s been an amazing many years. While many have gone the route of using a CMS or a blog engine (e.g. WordPress), I have always used my personal blog as a testing ground for the bleeding edge of tech that I wanted to learn about.</p>
<p>In addition, I’ve tried to style the site(s) myself to a different level of success. I’m always tweaking it but I’m no designer.</p>
<p>Take a look at the twelve iterations of the blog and see what I built them in:</p>
<div class="carousel-container">
<div class="item">
<div class="image">
<a href="https://wilderminds.blob.core.windows.net/img/2022/07/28/2001-comguru.jpg" class="lightboxed">
<img src="https://wilderminds.blob.core.windows.net/img/2022/07/28/2001-comguru.jpg" alt="2001" /></a>
</div>
<div class="text">2001: comguru.com - Classic ASP</div>
</div>
<div class="item">
<div class="image">
<a href="https://wilderminds.blob.core.windows.net/img/2022/07/28/2002-adoguy.jpg" class="lightboxed">
<img src="https://wilderminds.blob.core.windows.net/img/2022/07/28/2002-adoguy.jpg" alt="2001" /></a>
</div>
<div class="text">2002: adoguy.com - Classic ASP</div>
</div>
<div class="item">
<div class="image">
<a href="https://wilderminds.blob.core.windows.net/img/2022/07/28/2003-adoguy.jpg" class="lightboxed">
<img src="https://wilderminds.blob.core.windows.net/img/2022/07/28/2003-adoguy.jpg" alt="2001" /></a>
</div>
<div class="text">2003: adoguy.com - .NET Web Forms</div>
</div>
<div class="item">
<div class="image">
<a href="https://wilderminds.blob.core.windows.net/img/2022/07/28/2005-adoguy.jpg" class="lightboxed">
<img src="https://wilderminds.blob.core.windows.net/img/2022/07/28/2005-adoguy.jpg" alt="2001" /></a>
</div>
<div class="text">2005: adoguy.com - .NET Web Forms</div>
</div>
<div class="item">
<div class="image">
<a href="https://wilderminds.blob.core.windows.net/img/2022/07/28/2007-adoguy.jpg" class="lightboxed">
<img src="https://wilderminds.blob.core.windows.net/img/2022/07/28/2007-adoguy.jpg" alt="2001" /></a>
</div>
<div class="text">2007: adoguy.com - .NET Web Forms</div>
</div>
<div class="item">
<div class="image">
<a href="https://wilderminds.blob.core.windows.net/img/2022/07/28/2008-wildermuth.jpg" class="lightboxed">
<img src="https://wilderminds.blob.core.windows.net/img/2022/07/28/2008-wildermuth.jpg" alt="2001" /></a>
</div>
<div class="text">2008: wildermuth.com - ASP.NET MVC</div>
</div>
<div class="item">
<div class="image">
<a href="https://wilderminds.blob.core.windows.net/img/2022/07/28/2010-wildermuth.jpg" class="lightboxed">
<img src="https://wilderminds.blob.core.windows.net/img/2022/07/28/2010-wildermuth.jpg" alt="2001" /></a>
</div>
<div class="text">2010: wildermuth.com - ASP.NET MVC</div>
</div>
<div class="item">
<div class="image">
<a href="https://wilderminds.blob.core.windows.net/img/2022/07/28/2011-wildermuth.jpg" class="lightboxed">
<img src="https://wilderminds.blob.core.windows.net/img/2022/07/28/2011-wildermuth.jpg" alt="2001" /></a>
</div>
<div class="text">2011: wildermuth.com - ASP.NET MVC</div>
</div>
<div class="item">
<div class="image">
<a href="https://wilderminds.blob.core.windows.net/img/2022/07/28/2013-wildermuth.jpg" class="lightboxed">
<img src="https://wilderminds.blob.core.windows.net/img/2022/07/28/2013-wildermuth.jpg" alt="2001" /></a>
</div>
<div class="text">2013: wildermuth.com - ASP.NET MVC/Angular</div>
</div>
<div class="item">
<div class="image">
<a href="https://wilderminds.blob.core.windows.net/img/2022/07/28/2016-wildermuth.jpg" class="lightboxed">
<img src="https://wilderminds.blob.core.windows.net/img/2022/07/28/2016-wildermuth.jpg" alt="2001" /></a>
</div>
<div class="text">2016: wildermuth.com - ASP.NET Core/Angular</div>
</div>
<div class="item">
<div class="image">
<a href="https://wilderminds.blob.core.windows.net/img/2022/07/28/2020-wildermuth.jpg" class="lightboxed">
<img src="https://wilderminds.blob.core.windows.net/img/2022/07/28/2020-wildermuth.jpg" alt="2001" /></a>
</div>
<div class="text">2020: wildermuth.com - ASP.NET Core/Vue.js</div>
</div>
<div class="item">
<div class="image">
<a href="https://wilderminds.blob.core.windows.net/img/2022/07/28/2022-wildermuth.jpg" class="lightboxed">
<img src="https://wilderminds.blob.core.windows.net/img/2022/07/28/2022-wildermuth.jpg" alt="2001" /></a>
</div>
<div class="text">2022: wildermuth.com - 11ty/TailwindCSS/Vue.js</div>
</div>
<a class="prev has-dflex-center"><i class="fas fa-chevron-left"></i></a>
<a class="next has-dflex-center"><i class="fas fa-chevron-right"></i></a>
</div>
<p>I wonder, how long have you been reading my rants and ramblings? Please comment below!</p>
<div>
<div style="float: left;">
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
<img alt="Creative Commons License" style="border-width: 0" src="http://i.creativecommons.org/l/by-nc-nd/3.0/88x31.png" /></a></div>
<div>
This work by <a xmlns:cc="http://creativecommons.org/ns#" href="http://wildermuth.com"
property="cc:attributionName" rel="cc:attributionURL">Shawn Wildermuth</a> is
licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License</a>.<br />
Based on a work at <a xmlns:dct="http://purl.org/dc/terms/" href="http://wildermuth.com"
rel="dct:source">wildermuth.com</a>.</div>
</div>
<hr/><div>If you liked this article, see Shawn's courses on <a href="http://shawnw.me/pscourses">Pluralsight</a>.</div>
New Video: Coding Shorts - Response Caching in ASP.NET Core
2022-07-27T00:00:00Zhttps://wildermuth.com/2022/07/27/new-video-response-caching-aspnetcore/https://wilderminds.blob.core.windows.net/img/2022/07/27/cover.jpg
<p>After my recent video about <a href="https://wildermuth.com/2022/07/17/coding-shorts-asp-net-core-middleware-explained/">How ASP.NET Core Middleware Works</a>, I’m continuing to plow through some important pieces of middlware you might not be using. This time, I show you how to use Response Caching to lower load on your systems. Take a look:</p>
<div class="embed-responsive embed-responsive-16by9">
<iframe class="embed-responsive-item" src="https://www.youtube.com/embed/46knd1DFtB4" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
</div>
<blockquote>
<p><a href="https://shawnl.ink/yt">Subscribe to my YouTube Channel</a></p>
</blockquote>
<div>
<div style="float: left;">
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
<img alt="Creative Commons License" style="border-width: 0" src="http://i.creativecommons.org/l/by-nc-nd/3.0/88x31.png" /></a></div>
<div>
This work by <a xmlns:cc="http://creativecommons.org/ns#" href="http://wildermuth.com"
property="cc:attributionName" rel="cc:attributionURL">Shawn Wildermuth</a> is
licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">
Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License</a>.<br />
Based on a work at <a xmlns:dct="http://purl.org/dc/terms/" href="http://wildermuth.com"
rel="dct:source">wildermuth.com</a>.</div>
</div>
<hr/><div>If you liked this article, see Shawn's courses on <a href="http://shawnw.me/pscourses">Pluralsight</a>.</div>