Adding Resilience and Transient Fault handling to your .NET Core HttpClient with Polly

Last week while upgrading my podcast site to ASP.NET Core 2.1 and .NET. Core 2.1 I moved my Http Client instances over to be created by the new HttpClientFactory. Now I have a single central place where my HttpClient objects are created and managed, an…

b30f5128-181e-11e6-8780-bc9e5b17685eLast week while upgrading my podcast site to ASP.NET Core 2.1 and .NET. Core 2.1 I moved my Http Client instances over to be created by the new HttpClientFactory. Now I have a single central place where my HttpClient objects are created and managed, and I can set policies as I like on each named client.

It really can't be overstated how useful a resilience framework for .NET Core like Polly is.

Take some code like this that calls a backend REST API:

public class SimpleCastClient

{
private HttpClient _client;
private ILogger<SimpleCastClient> _logger;
private readonly string _apiKey;

public SimpleCastClient(HttpClient client, ILogger<SimpleCastClient> logger, IConfiguration config)
{
_client = client;
_client.BaseAddress = new Uri($"https://api.simplecast.com");
_logger = logger;
_apiKey = config["SimpleCastAPIKey"];
}

public async Task<List<Show>> GetShows()
{
var episodesUrl = new Uri($"/v1/podcasts/shownum/episodes.json?api_key={_apiKey}", UriKind.Relative);
var res = await _client.GetAsync(episodesUrl);
return await res.Content.ReadAsAsync<List<Show>>();
}
}

Now consider what it takes to add things like

  • Retry n times - maybe it's a network blip
  • Circuit-breaker - Try a few times but stop so you don't overload the system.
  • Timeout - Try, but give up after n seconds/minutes
  • Cache - You asked before!
    • I'm going to do a separate blog post on this because I wrote a WHOLE caching system and I may be able to "refactor via subtraction."

If I want features like Retry and Timeout, I could end up littering my code. OR, I could put it in a base class and build a series of HttpClient utilities. However, I don't think I should have to do those things because while they are behaviors, they are really cross-cutting policies. I'd like a central way to manage HttpClient policy!

Enter Polly. Polly is an OSS library with a lovely Microsoft.Extensions.Http.Polly package that you can use to combine the goodness of Polly with ASP.NET Core 2.1.

As Dylan from the Polly Project says:

HttpClientFactory in ASPNET Core 2.1 provides a way to pre-configure instances of HttpClient which apply Polly policies to every outgoing call.

I just went into my Startup.cs and changed this

services.AddHttpClient<SimpleCastClient>();

to this (after adding "using Polly;" as a namespace)

services.AddHttpClient<SimpleCastClient>().

AddTransientHttpErrorPolicy(policyBuilder => policyBuilder.RetryAsync(2));

and now I've got Retries. Change it to this:

services.AddHttpClient<SimpleCastClient>().

AddTransientHttpErrorPolicy(policyBuilder => policyBuilder.CircuitBreakerAsync(
handledEventsAllowedBeforeBreaking: 2,
durationOfBreak: TimeSpan.FromMinutes(1)
));

And now I've got CircuitBreaker where it backs off for a minute if it's broken (hit a handled fault) twice!

I like AddTransientHttpErrorPolicy because it automatically handles Http5xx's and Http408s as well as the occasional System.Net.Http.HttpRequestException. I can have as many named or typed HttpClients as I like and they can have all kinds of specific policies with VERY sophisticated behaviors. If those behaviors aren't actual Business Logic (tm) then why not get them out of your code?

Go read up on Polly at https://githaub.com/App-vNext/Polly and check out the extensive samples at https://github.com/App-vNext/Polly-Samples/tree/master/PollyTestClient/Samples.

Even though it works great with ASP.NET Core 2.1 (best, IMHO) you can use Polly with .NET 4, .NET 4.5, or anything that's compliant with .NET Standard 1.1.

Gotchas

A few things to remember. If you are POSTing to an endpoint and applying retries, you want that operation to be idempotent.

"From a RESTful service standpoint, for an operation (or service call) to be idempotent, clients can make that same call repeatedly while producing the same result."

But everyone's API is different. What would happen if you applied a Polly Retry Policy to an HttpClient and it POSTed twice? Is that backend behavior compatible with your policies? Know what the behavior you expect is and plan for it. You may want to have a GET policy and a post one and use different HttpClients. Just be conscious.

Next, think about Timeouts. HttpClient's have a Timeout which is "all tries overall timeout" while a TimeoutPolicy inside a Retry is "timeout per try." Again, be aware.

Thanks to Dylan Reisenberger for his help on this post, along with Joel Hulen! Also read more about HttpClientFactory on Steve Gordon's blog and learn more about HttpClientFactory and Polly on the Polly project site.


Sponsor: Check out JetBrains Rider: a cross-platform .NET IDE. Edit, refactor, test and debug ASP.NET, .NET Framework, .NET Core, Xamarin or Unity applications. Learn more and download a 30-day trial!



© 2018 Scott Hanselman. All rights reserved.
     

HttpClientFactory for typed HttpClient instances in ASP.NET Core 2.1

I’m continuing to upgrade my podcast site https://www.hanselminutes.com to .NET Core 2.1 running ASP.NET Core 2.1. I’m using Razor Pages having converted my old Web Matrix Site (like 8 years old) and it’s gone very smoothly. I’ve got a ton of blog post…

THE HANSELMINUTES PODCASTI'm continuing to upgrade my podcast site https://www.hanselminutes.com to .NET Core 2.1 running ASP.NET Core 2.1. I'm using Razor Pages having converted my old Web Matrix Site (like 8 years old) and it's gone very smoothly. I've got a ton of blog posts queued up as I'm learning a ton. I've added Unit Testing for the Razor Pages as well as more complete Integration Testing for checking things "from the outside" like URL redirects.

My podcast has recently switched away from a custom database over to using SimpleCast and their REST API for the back end. There's a number of ways to abstract that API away as well as the HttpClient that will ultimately make the call to the SimpleCast backend. I am a fan of the Refit library for typed REST Clients and there are ways to integrate these two things but for now I'm going to use the new HttpClientFactory introduced in ASP.NET Core 2.1 by itself.

Next I'll look at implementing a Polly Handler for resilience policies to be used like Retry, WaitAndRetry, and CircuitBreaker, etc. (I blogged about Polly in 2015 - you should check it out) as it's just way to useful to not use.

HttpClient Factory lets you preconfigure named HttpClients with base addresses and default headers so you can just ask for them later by name.

public void ConfigureServices(IServiceCollection services)

{
services.AddHttpClient("SomeCustomAPI", client =>
{
client.BaseAddress = new Uri("https://someapiurl/");
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.DefaultRequestHeaders.Add("User-Agent", "MyCustomUserAgent");
});
services.AddMvc();
}

Then later you ask for it and you've got less to worry about.

using System.Threading.Tasks;

using Microsoft.AspNetCore.Mvc;

namespace MyApp.Controllers
{
public class HomeController : Controller
{
private readonly IHttpClientFactory _httpClientFactory;

public HomeController(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}

public Task<IActionResult> Index()
{
var client = _httpClientFactory.CreateClient("SomeCustomAPI");
return Ok(await client.GetStringAsync("/api"));
}
}
}

I prefer a TypedClient and I just add it by type in Startup.cs...just like above except:

services.AddHttpClient<SimpleCastClient>();

Note that I could put the BaseAddress in multiple places depending on if I'm calling my own API, a 3rd party, or some dev/test/staging version. I could also pull it from config:

services.AddHttpClient<SimpleCastClient>(client => client.BaseAddress = new Uri(Configuration["SimpleCastServiceUri"]));

Again, I'll look at ways to make this even simpler AND more robust (it has no retries, etc) with Polly soon.

public class SimpleCastClient

{
private HttpClient _client;
private ILogger<SimpleCastClient> _logger;
private readonly string _apiKey;

public SimpleCastClient(HttpClient client, ILogger<SimpleCastClient> logger, IConfiguration config)
{
_client = client;
_client.BaseAddress = new Uri($"https://api.simplecast.com"); //Could also be set in Startup.cs
_logger = logger;
_apiKey = config["SimpleCastAPIKey"];
}

public async Task<List<Show>> GetShows()
{
try
{
var episodesUrl = new Uri($"/v1/podcasts/shownum/episodes.json?api_key={_apiKey}", UriKind.Relative);
_logger.LogWarning($"HttpClient: Loading {episodesUrl}");
var res = await _client.GetAsync(episodesUrl);
res.EnsureSuccessStatusCode();
return await res.Content.ReadAsAsync<List<Show>>();
}
catch (HttpRequestException ex)
{
_logger.LogError($"An error occurred connecting to SimpleCast API {ex.ToString()}");
throw;
}
}
}

Once I have the client I can use it from another layer, or just inject it with [FromServices] whenever I have a method that needs one:

public class IndexModel : PageModel

{
public async Task OnGetAsync([FromServices]SimpleCastClient client)
{
var shows = await client.GetShows();
}
}

Or in the constructor:

public class IndexModel : PageModel

{
private SimpleCastClient _client;

public IndexModel(SimpleCastClient Client)
{
_client = Client;
}
public async Task OnGetAsync()
{
var shows = await _client.GetShows();
}
}

Another nice side effect is that HttpClients that are created from the HttpClientFactory give me free logging:

info: System.Net.Http.ShowsClient.LogicalHandler[100]

Start processing HTTP request GET https://api.simplecast.com/v1/podcasts/shownum/episodes.json?api_key=
System.Net.Http.ShowsClient.LogicalHandler:Information: Start processing HTTP request GET https://api.simplecast.com/v1/podcasts/shownum/episodes.json?api_key=
info: System.Net.Http.ShowsClient.ClientHandler[100]
Sending HTTP request GET https://api.simplecast.com/v1/podcasts/shownum/episodes.json?api_key=
System.Net.Http.ShowsClient.ClientHandler:Information: Sending HTTP request GET https://api.simplecast.com/v1/podcasts/shownum/episodes.json?api_key=
info: System.Net.Http.ShowsClient.ClientHandler[101]
Received HTTP response after 882.8487ms - OK
System.Net.Http.ShowsClient.ClientHandler:Information: Received HTTP response after 882.8487ms - OK
info: System.Net.Http.ShowsClient.LogicalHandler[101]
End processing HTTP request after 895.3685ms - OK
System.Net.Http.ShowsClient.LogicalHandler:Information: End processing HTTP request after 895.3685ms - OK

It was super easy to move my existing code over to this model, and I'll keep simplifying AND adding other features as I learn more.


Sponsor: Check out JetBrains Rider: a cross-platform .NET IDE. Edit, refactor, test and debug ASP.NET, .NET Framework, .NET Core, Xamarin or Unity applications. Learn more and download a 30-day trial!



© 2018 Scott Hanselman. All rights reserved.
     

Updating jQuery-based Lazy Image Loading to IntersectionObserver

Five years ago I implemented “lazy loading” of the 600+ images on my podcast’s archives page (I don’t like paging, as a rule) over here https://www.hanselminutes.com/episodes. I did it with jQuery and a jQuery Plugin. It was kind of messy and gross fro…

The Hanselminutes Tech PodcastFive years ago I implemented "lazy loading" of the 600+ images on my podcast's archives page (I don't like paging, as a rule) over here https://www.hanselminutes.com/episodes. I did it with jQuery and a jQuery Plugin. It was kind of messy and gross from a purist's perspective, but it totally worked and has easily saved me (and you) hundreds of dollars in bandwidth over the years. The page is like 9 or 10 megs if you load 600 images, not to mention you're loading 600 freaking images.

Fast-forward to 2018, and there's the "Intersection Observer API" that's supported everywhere but Safari and IE, well, because, Safari and IE, sigh. We will return to that issue in a moment.

Following Dean Hume's blog post on the topic, I start with my images like this. I don't populate src="", but instead hold the Image URL in the HTML5 data- bucket of data-src. For src, I can use the nothing grey.gif or just style and color the image grey.

<a href="http://feeds.hanselman.com/~/t/0/0/scotthanselman/~https://www.hanselman.com/626/christine-spangs-open-source-journey-from-teen-oss-contributor-to-cto-of-nylas" class="showCard">
    <img data-src="https://images.hanselminutes.com/images/626.jpg" 
         class="lazy" src="https://www.hanselman.com/images/grey.gif" width="212" height="212" alt="Christine Spang&#x27;s Open Source Journey from Teen OSS Contributor to CTO of Nylas" />
    <span class="shownumber">626</span>                
    <div class="overlay title">Christine Spang&#x27;s Open Source Journey from Teen OSS Contributor to CTO of Nylas</div>
</a>
<a href="http://feeds.hanselman.com/~/t/0/0/scotthanselman/~https://www.hanselman.com/625/a-new-sega-megadrivegenesis-game-in-2018-with-1995-tools-with-tanglewoods-matt-phillips" class="showCard">
    <img data-src="https://images.hanselminutes.com/images/625.jpg" 
         class="lazy" src="https://www.hanselman.com/images/grey.gif" width="212" height="212" alt="A new Sega Megadrive/Genesis Game in 2018 with 1995 Tools with Tanglewood&#x27;s Matt Phillips" />
    <span class="shownumber">625</span>                
    <div class="overlay title">A new Sega Megadrive/Genesis Game in 2018 with 1995 Tools with Tanglewood&#x27;s Matt Phillips</div>
</a>

Then, if the images get within 50px intersecting the viewPort (I'm scrolling down) then I load them:

// Get images of class lazy
const images = document.querySelectorAll('.lazy');
const config = {
  // If image gets within 50px go get it
  rootMargin: '50px 0px',
  threshold: 0.01
};
let observer = new IntersectionObserver(onIntersection, config);
  images.forEach(image => {
    observer.observe(image);
  });

Now that we are watching it, we need to do something when it's observed.

function onIntersection(entries) {
  // Loop through the entries
  entries.forEach(entry => {
    // Are we in viewport?
    if (entry.intersectionRatio > 0) {
      // Stop watching and load the image
      observer.unobserve(entry.target);
      preloadImage(entry.target);
    }
  });
}

If the browser (IE, Safari, Mobile Safari) doesn't support IntersectionObserver, we can do a few things. I *could* fall back to my old jQuery technique, although it would involve loading a bunch of extra scripts for those browsers, or I could just load all the images in a loop, regardless, like:

if (!('IntersectionObserver' in window)) {
    loadImagesImmediately(images);
} else {...}

Dean's examples are all "Vanilla JS" and require no jQuery, no plugins, no polyfills WITH browser support. There are also some IntersectionObserver helper libraries out there like Cory Dowdy's IOLazy. Cory's is a nice simple wrapper and is super easy to implement. Given I want to support iOS Safari as well, I am using a polyfill to get the support I want from browsers that don't have it natively.

<!-- intersection observer polyfill -->
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=IntersectionObserver"></script>

Pollyfill.io is a lovely site that gives you just the fills you need (or those you need AND request) tailored to your browser. Try GETting the URL above in Chrome. You'll see it's basically empty as you don't need it. Then hit it in IE, and you'll get the polyfill. The official IntersectionObserver polyfill is at the w3c.

At this point I've removed jQuery entirely from my site and I'm just using an optional polyfill plus browser support that didn't exist when I started my podcast site. Fewer moving parts means a cleaner, leaner, simpler site!

Go subscribe to the Hanselminutes Podcast today! We're on iTunes, Spotify, Google Play, and even Twitter!


Sponsor: Announcing Raygun APM! Now you can monitor your entire application stack, with your whole team, all in one place. Learn more!



© 2018 Scott Hanselman. All rights reserved.
     

Easier functional and integration testing of ASP.NET Core applications

In ASP.NET 2.1 (now in preview) there’s apparently a new package called Microsoft.AspNetCore.Mvc.Testing that’s meant to help streamline in-memory end-to-end testing of applications that use the MVC pattern. I’ve been re-writing my podcast site at http…

.NET Test ExplorerIn ASP.NET 2.1 (now in preview) there's apparently a new package called Microsoft.AspNetCore.Mvc.Testing that's meant to help streamline in-memory end-to-end testing of applications that use the MVC pattern. I've been re-writing my podcast site at https://hanselminutes.com in ASP.NET Core 2.1 lately, and recently added some unit testing and automatic unit testing with code coverage. Here's a couple of basic tests. Note that these call the Razor Pages directly and call their OnGet() methods directly. This shows how ASP.NET Core is nicely factored for Unit Testing but it doesn't do a "real" HTTP GET or perform true end-to-end testing.

These tests are testing if visiting URLs like /620 will automatically redirect to the correct full canonical path as they should.

[Fact]

public async void ShowDetailsPageIncompleteTitleUrlTest()
{
// FAKE HTTP GET "/620"
IActionResult result = await pageModel.OnGetAsync(id:620, path:"");

RedirectResult r = Assert.IsType<RedirectResult>(result);
Assert.NotNull(r);
Assert.True(r.Permanent); //HTTP 301?
Assert.Equal("/620/jessica-rose-and-the-worst-advice-ever",r.Url);
}

[Fact]
public async void SuperOldShowTest()
{
// FAKE HTTP GET "/default.aspx?showId=18602"
IActionResult result = await pageModel.OnGetOldShowId(18602);

RedirectResult r = Assert.IsType<RedirectResult>(result);
Assert.NotNull(r);
Assert.True(r.Permanent); //HTTP 301?
Assert.StartsWith("/615/developing-on-not-for-a-nokia-feature",r.Url);
}

I wanted to see how quickly and easily I could do these same two tests, except "from the outside" with an HTTP GET, thereby testing more of the stack.

I added a reference to Microsoft.AspNetCore.Mvc.Testing in my testing assembly using the command-line equivalanet of "Right Click | Add NuGet Package" in Visual Studio. This CLI command does the same thing as the UI and adds the package to the csproj file.

dotnet add package Microsoft.AspNetCore.Mvc.Testing -v 2.1.0-preview1-final

It includes a new WebApplicationTestFixture that I point to my app's Startup class. Note that I can take store the HttpClient the TestFixture makes for me.

public class TestingMvcFunctionalTests : IClassFixture<WebApplicationTestFixture<Startup>>

{
public HttpClient Client { get; }

public TestingMvcFunctionalTests(WebApplicationTestFixture<Startup> fixture)
{
Client = fixture.Client;
}
}

No tests yet, just setup. I'm using SSL redirection so I'll make sure the client knows that, and add a test:

public TestingMvcFunctionalTests(WebApplicationTestFixture<Startup> fixture)

{
Client = fixture.Client;
Client.BaseAddress = new Uri("https://localhost");
}

[Fact]
public async Task GetHomePage()
{
// Arrange & Act
var response = await Client.GetAsync("/");

// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}

This will fail, in fact. Because I have an API Key that is needed to call out to my backend system, and I store it in .NET's User Secrets system. My test will get an InternalServerError instead of OK.

Starting test execution, please wait...

[xUnit.net 00:00:01.2110048] Discovering: hanselminutes.core.tests
[xUnit.net 00:00:01.2690390] Discovered: hanselminutes.core.tests
[xUnit.net 00:00:01.2749018] Starting: hanselminutes.core.tests
[xUnit.net 00:00:08.1088832] hanselminutes_core_tests.TestingMvcFunctionalTests.GetHomePage [FAIL]
[xUnit.net 00:00:08.1102884] Assert.Equal() Failure
[xUnit.net 00:00:08.1103719] Expected: OK
[xUnit.net 00:00:08.1104377] Actual: InternalServerError
[xUnit.net 00:00:08.1114432] Stack Trace:
[xUnit.net 00:00:08.1124268] D:\github\hanselminutes-core\hanselminutes.core.tests\FunctionalTests.cs(29,0): at hanselminutes_core_tests.TestingMvcFunctionalTests.<GetHomePage>d__4.MoveNext()
[xUnit.net 00:00:08.1126872] --- End of stack trace from previous location where exception was thrown ---
[xUnit.net 00:00:08.1158250] Finished: hanselminutes.core.tests
Failed hanselminutes_core_tests.TestingMvcFunctionalTests.GetHomePage
Error Message:
Assert.Equal() Failure
Expected: OK
Actual: InternalServerError

Where do these secrets come from? In Development they come from user secrets.

public Startup(IHostingEnvironment env)

{
this.env = env;
var builder = new ConfigurationBuilder();

if (env.IsDevelopment())
{
builder.AddUserSecrets<Startup>();
}
Configuration = builder.Build();
}

But in Production they come from the ENVIRONMENT. Are these tests Development or Production...I must ask myself.  They are Production unless told otherwise. I can override the Fixture and tell it to use another Environment, like "Development." Here is a way (given this preview) to make my own TestFixture by deriving and grabbing and override to change the Environment. I think it's too hard and should be easier.

Either way, the real question here is for me - do I want my tests to be integration tests in development or in "production." Likely I need to make a new environment for myself - "testing."

public class MyOwnTextFixture<TStartup> : WebApplicationTestFixture<Startup> where TStartup : class

{
public MyOwnTextFixture() { }

protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseEnvironment("Development");
}
}

However, my User Secrets still aren't loading, and that's where the API Key is that I need.

BUG?: There is either a bug here, or I don't know what I'm doing. I'm loading User Secrets in builder.AddUserSecrets<Startup> and later injecting the IConfiguration instance from builder.Build() and going "_apiKey = config["SimpleCastAPIKey"];" but it's null. The config that's injected later in the app isn't the same one that's created in Startup.cs. It's empty. Not sure if this is an ASP.NE Core 2.0 thing or 2.1 thing but I'm going to bring it up with the team and update this blog post later. It might be a Razor Pages subtlety I'm missing.
For now, I'm going to put in a check and manually fix up my Config. However, when this is fixed (or I discover my error) this whole thing will be a pretty nice little set up for integration testing.

I will add another test, similar to the redirect Unit Test but a fuller integration test that actually uses HTTP and tests the result.

[Fact]

public async Task GetAShow()
{
// Arrange & Act
var response = await Client.GetAsync("/620");

// Assert
Assert.Equal(HttpStatusCode.MovedPermanently, response.StatusCode);
Assert.Equal("/620/jessica-rose-and-the-worst-advice-ever",response.Headers.Location.ToString());
}

There's another issue here that I don't understand. Because have to set Client.BaseAddress to https://localhost (because https) and the Client is passed into fixture.Client, I can't set the Base address twice or I'll get an exception, as the Test's Constructor runs twice, but the HttpClient that's passed in as a lifecycler that's longer. It's being reused, and it fails when setting its BaseAddress twice.

Error Message:

System.InvalidOperationException : This instance has already started one or more requests. Properties can only be modified before sending the first request.

BUG? So to work around it I check to see if I've done it before. Which is gross. I want to set the BaseAddress once, but I am not in charge of the creation of this HttpClient as it's passed in by the Fixture.

public TestingMvcFunctionalTests(MyOwnTextFixture<Startup> fixture)

{
Client = fixture.Client;
if (Client.BaseAddress.ToString().StartsWith("https://") == false)
Client.BaseAddress = new Uri("https://localhost");
}

Another option is that I create a new client every time, which is less efficient and perhaps a better idea as it avoids any side effects from other tests, but also feels weird that I should have to do this, as the new standard for ASP.NET Core sites is to be SSL/HTTPS by default..

public TestingMvcFunctionalTests(MyOwnTextFixture<Startup> fixture)

{
Client = fixture.CreateClient(new Uri(https://localhost));
}

I'm still learning about how it all fits together, but later I plan to add in Selenium tests to have a full, complete, test suite that includes the browser, CSS, JavaScript, end-to-end integration tests, and unit tests.

Let me know if you think I'm doing something wrong. This is preview stuff, so it's early days!


Sponsor: Get the latest JetBrains Rider for debugging third-party .NET code, Smart Step Into, more debugger improvements, C# Interactive, new project wizard, and formatting code in columns.


© 2018 Scott Hanselman. All rights reserved.
     

Setting up Application Insights took 10 minutes. It created two days of work for me.

I’ve been upgrading my podcast site from a 10 year old WebMatrix site to modern, open source ASP.NET Core with Razor Pages. It’s off IIS and now running cross-platform in Azure. I added Application Insights to the site in about 10 min just a few days a…

I've been upgrading my podcast site from a 10 year old WebMatrix site to modern, open source ASP.NET Core with Razor Pages. It's off IIS and now running cross-platform in Azure.

I added Application Insights to the site in about 10 min just a few days ago. It was super easy to setup and basically automatic in Visual Studio 2017 Community. I left the defaults, installed a bit of script on the client, and enabled the server side, and AppInsights already found a few interesting things.

It took 10 minutes to set up App Insights. It too two days (and work continues) to fix what it found. I love it. This tool has already give me a deeper insight into how my code runs and how it's behaving - and I'm just scratching the service. I'll need to do some videos and/or more blog posts to dig deeper. Truly, you need to try it.

Slow performance in other countries

I could fill this blog post with dozens of awesome screenshots of the useful charts, graphs, and filters that I got by just turning on AppInsights. But the most interesting part is that I turned it on really expecting nothing. I figured I'd get some "Google Analytics"-type behavior.

Then I got this email:

Browser Time is slow in Bangladesh

Huh. I had set up the Azure CDN at images.hanselminutes.com to handle all the faces for each episode. I then added lazy loading so that the webite only loads the images that enter the browser's viewport. I figured I was pretty much done.

However I didn't really think about the page itself as it loads for folks from around the world - given that it's hosted on Azure in the West US.

18.4 secs to load the page in Bangladesh

Ideally I'd want the site to load in less than a second, but this is my archives page with 600 shows so it's pretty heavy.

That's some long load times

Yuck. I have a few options. I could pay and load up another copy of the site in South Asia and then do some global load balancing. However, I'm hosting this on a single small (along with a dozen other sites) so I don't want to really pay much to fix this.

I ended up signing up for a free account at CloudFlare and set up caching for my HTML. The images stay the same. served by the Azure CDN.

Lots of requests from Cloudflare

Fixing Random and regular Server 500 errors

I left the site up for a while and came back later to a warning. You can see my site availability is just 93%. Note that there's "2 Servers?" That's because one is my local machine! Very cool that AppInsights also (optionally) tracks your local development server as well.

1 Alert!

When I dig in I see a VERY interesting sawtooth pattern.

Pro Tip - Recognizing that a Sawtooth Pattern is a Bad Thing (tm) is an important DevOps thing. Why is this happening regularly? Is it exactly regularly (like every 4 hours on a schedule?) or somewhat regularly (like a garbage collection issue?)

What do these operations have in common? Look closely.

scarygraph

It's not a GET it's a HEAD. Remember that HTTP Verbs are more than GET, POST, PUT, DELETE. There's also HEAD. It literally is a HEADer call. Like a GET, but no body.

HTTP HEAD - The HEAD method is identical to GET except that the server MUST NOT return a message-body in the response.

I installed HTTPie - which is like curl or wget for humans - and issue a HEAD command from my local machine while under the debugger.

C:>http --verify=no HEAD https://localhost:5001

HTTP/1.1 500 Internal Server Error
Content-Type: text/html; charset=utf-8
Date: Tue, 13 Mar 2018 03:41:51 GMT
Server: Kestrel

Ok that is bad. See the 500? I check out AppInsights and see it has the full call stack. See it's getting a NullReferenceException as it tries to Render() the Razor page?

Null Reference Exception

It turns out since I'm using Razor Pages, I have implemented "OnGet" where I do my data base work then pass a model to the pages to generate HTML. However, if someone issues a HEAD, then the pages still run but the local data work never happened (I have no OnHead() call). I have a few options here. I could handle HEAD myself. I could no-op it, but that'd be a lie.

THOUGHT: I think this behavior is sub-optimal. While GET and POST are distinct and it makes sense to require an OnGet() and OnPost(), I think that HEAD is special. It's basically a GET with a "don't return the body" flag set. So why not have Razor Pages automatically delegate OnHead to OnGet, unless there's an explicit OnHead() declared? I'll file an issue on GitHub because I don't like this behavior and I find it counter-intuitive. I could also register a global IPageFilter to make this work for all my site's pages.

The simplest thing to do is just to delegate the OnHead to to the OnGet handler.

public Task OnHeadAsync(int? id, string path) => OnGetAsync(id, path);

Then double check and test it with HTTPie:

C:\>http --verify=no HEAD https://localhost:5001

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Date: Tue, 13 Mar 2018 03:53:55 GMT
Request-Context: appId=cid-v1:e310025f-88e9-4133-bc15-e775513c67ac
Server: Kestrel

Bonus - Application Map

Since I have AppInsights enabled on both the client and the server, I can see this cool live Application Map. I'll check again in a few days to see if I have fewer errors. You can see where my Podcast Site calls into the backend data service at Simplecast.

An application map that shows all the components, both client and server

I saw a few failures in my call to SimpleCast's API as I was failing to consistently set my API key. Everything in this map can be drilled down into.

Bonus - Web Performance Testing

I figured while I was in the Azure Portal I would also take advantage of the free performance testing. I did a simulated aggressive 250 users beating on the site. Average response time is 1.22 seconds and I was doing over 600 req/second.

38097 successful calls

I am learning a ton of stuff. I have more things to fix, more improvements to make, and more insights to dig into. I LOVE that it's creating all this work for me because it's giving me a better application/website!

You can get a free Azure account at http://azure.com/free or check out Azure for Startups https://azure.microsoft.com/overview/startups/ and get a bunch of free Azure time. AppInsights works with Node, Docker, Java, ASP.NET, ASP.NET Core, and other platforms. It even supports telemetry in Electron or Windows Apps.


Sponsor: Get the latest JetBrains Rider for debugging third-party .NET code, Smart Step Into, more debugger improvements, C# Interactive, new project wizard, and formatting code in columns.



© 2017 Scott Hanselman. All rights reserved.
     

Upgrading a 10 year old site to ASP.NET Core’s Razor Pages using the URL Rewriting Middleware

My podcast has over 600 episodes (Every week for many years, you do the math! And subscribe!) website was written in ASP.NET Web Pages many years ago. “Web Pages” (horrible name) was it’s own thing. It wasn’t ASP.NET Web Forms, nor was it ASP.NET MVC. …

Visual Studio Code editing my new ASP.NET Core site using Razor PagesMy podcast has over 600 episodes (Every week for many years, you do the math! And subscribe!) website was written in ASP.NET Web Pages many years ago. "Web Pages" (horrible name) was it's own thing. It wasn't ASP.NET Web Forms, nor was it ASP.NET MVC. However, while open-source and cross-platform ASP.NET Core uses the "MVC" pattern, it includes an integrated architecture that supports pages created with the model-view-controller style, Web APIs that return JSON/whatever from controllers, and routing system that works across all of these. It also includes "Razor Pages."

On first blush, you'd think Razor Pages is "Web Pages" part two. I thought that, but it's not. It's an alternative model to MVC but it's built on MVC. Let me explain.

My podcast site has a home page, a single episode page, and and archives page. It's pretty basic. Back in the day I felt an MVC-style site would just be overkill, so I did it in a page model. However, the code ended up (no disrespect intended) very 90s style PHPy. Basically one super-page with too much state management to all the URL cracking happening at the top of the page.

What I wanted was a Page-focused model without the ceremony of MVC while still being able to dip down into the flexibility and power of MVC when appropriate. That's Razor Pages. Best of all worlds and simply another tool in my toolbox. And the Pages (.cshtml) are Razor so I could port 90% of my very old existing code. In fact, I just made a new site with .NET Core with "dotnet new razor," opened up Visual Studio Code, and started copying over from (gasp) my WebMatrix project. I updated the code to be cleaner (a lot has happened to C# since then) and had 80% of my site going in a few hours. I'll switch Hanselminutes.com over in the next few weeks. This will mean I'll have a proper git checkin/deploy process rather than my "publish from WebMatrix" system I use today. I can containerize the site, run it on Linux, and finally add Unit Testing as I've been able to use pervasive Dependency Injection that's built into ASP.NET.

Merging the old and the new with the ASP.NET Core's URL Rewriting Middleware

Here's the thing though, there's parts of my existing site that are 10 years old, sure, but they also WORK. For example, I have existing URL Rewrite Rules from IIS that have been around that long. I'm pretty obsessive about making old URLs work. Never break a URL. No excuses.

There are still links around that have horrible URLs in the VERY original format that (not my fault) used database ids, like https://hanselminutes.com/default.aspx?ShowID=18570. Well, that database doesn't exist anymore, but I don't break URLs. I have these old URLs store along site my new system, and along with dozens of existing rewrite URLs I have an "IISUrlRewrite.xml" file. This was IIS specific and used with the IIS URL Rewrite Module, but you have all seen these before with things like Apache's ModRewrite. Those files are often loved and managed and carried around for years. They work. A lot of work went into them. Sure, I could rewrite all these rules with ASP.NET Core's routing and custom middleware, but again, they already work. I just want them to continue to work. They can with ASP.NET Core's Url Rewriting Middleware that supports Apache Mod Rewrite AND IIS Url Rewrite without using Apache or IIS!

Here's a complex and very complete example of mixing and matching. Mine is far simpler.

public void Configure(IApplicationBuilder app)

{
using (StreamReader apacheModRewriteStreamReader =
File.OpenText("ApacheModRewrite.txt"))
using (StreamReader iisUrlRewriteStreamReader =
File.OpenText("IISUrlRewrite.xml"))
{
var options = new RewriteOptions()
.AddRedirect("redirect-rule/(.*)", "redirected/$1")
.AddRewrite(@"^rewrite-rule/(\d+)/(\d+)", "rewritten?var1=$1&var2=$2",
skipRemainingRules: true)
.AddApacheModRewrite(apacheModRewriteStreamReader)
.AddIISUrlRewrite(iisUrlRewriteStreamReader)
.Add(MethodRules.RedirectXMLRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));

app.UseRewriter(options);
}

app.Run(context => context.Response.WriteAsync(
$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
}

Remember I have URLs like default.aspx?ShowID=18570 but I don't use default.aspx any more (literally doesn't exist on disk) and I don't use those IDs (they are just stored as metadata in a new system.

NOTE: Just want to point out that last line above there, where it shows the rewritten URL. Putting that in the logs or bypassing everything and outputting it as text is a nice way to debug and developer with this middleware, then comment it out as you get things refined and working.

I have an IIS Rewrite URL that looks like this. It lives in an XML file along with dozens of other rules. Reminder - there's no IIS in this scenario. We are talking about the format and reusing that format. I load my rewrite rules in my Configure() method in Startup:

using (StreamReader iisUrlRewriteStreamReader = 

File.OpenText("IISUrlRewrite.xml"))
{
var options = new RewriteOptions()
.AddIISUrlRewrite(iisUrlRewriteStreamReader);

app.UseRewriter(options);
}

It lives in the "Microsoft.AspNetCore.Rewrite" package that I added to my csproj with "dotnet add package Microsoft.AspNetCore.Rewrite." And here's the rule I use (one of many in the old xml file):

<rule name="OldShowId">

<match url="^.*(?:Default.aspx).*$" />
<conditions>
<add input="{QUERY_STRING}" pattern="ShowID=(\d+)" />
</conditions>
<action type="Rewrite" url="/{C:1}?handler=oldshowid" appendQueryString="false" />
</rule>

I capture that show ID and I rewrite (not redirect...we rewrite and continue on to the next segment of the pipeline) it to /18570?handler=oldshowid. That handler is a magic internal part of Razor Pages. Usually if you have a page called foo.cshtml it will have a method called OnGet or OnPost or OnHTTPVERB. But if you want multiple handlers per page you'll have OnGetHANDLERNAME so I have OnGet() for regular stuff, and I have OnGetOldShowId for this rare but important URL type. But notice that my implementation isn't URL-style specific. Razor Pages doesn't even know about that URL format. It just knows that these weird IDs have their own handler.

public async Task<IActionResult> OnGetOldShowId(int id)

{
var allShows = await _db.GetShows();

string idAsString = id.ToString();
LastShow = allShows.Where(c => c.Guid.EndsWith(idAsString)).FirstOrDefault();
if (LastShow == null) return Redirect("/"); //catch all error case, 302 to home
return RedirectPermanent(LastShow.ShowNumber.ToString()); // 301 to /showid
}

That's it. I have a ton more to share as I keep upgrading my podcast site, coming soon.


Sponsor: Get the latest JetBrains Rider for debugging third-party .NET code, Smart Step Into, more debugger improvements, C# Interactive, new project wizard, and formatting code in columns.


© 2017 Scott Hanselman. All rights reserved.
     

Lightweight bundling, minifying, and compression, for CSS and JavaScript with ASP.NET Core and Smidge

Yesterday I blogged about WebOptimizer, a minifier that Mads Kristensen wrote for ASP.NET Core. A few people mentioned that Shannon Deminick also had a great minifier for ASP.NET Core. Shannon has a number of great libraries on his GitHub https://githu…

Yesterday I blogged about WebOptimizer, a minifier that Mads Kristensen wrote for ASP.NET Core. A few people mentioned that Shannon Deminick also had a great minifier for ASP.NET Core. Shannon has a number of great libraries on his GitHub https://github.com/Shazwazza including not just "Smidge" but also Examine, an indexing system, ClientDependency for managing all your client side assets, and Articulate, a blog engine built on Umbraco.

Often when there's more than one way to do things, but one of the ways is made by a Microsoft employee like Mads - even if it's in his spare time - it can feel like inside baseball or an unfair advantage. The same would apply if I made a node.js library but a node.js core committer also made a similar one. Many things can affect whether an open source library "pops," and it's not always merit. Sometimes it's locale/location, niceness of docs, marketing, word of mouth, website. Both Mads and Shannon and a dozen other people are all making great libraries and useful stuff. Sometimes people are aware of other projects and sometimes they aren't. At some point a community wants to "pick a winner" but even as I write this blog post, someone else we haven't met yet is likely making the next great bundler/minifier. And that's OK!

I'm going to take a look at Shannon Deminck's "Smidge" in this post. Smidge has been around as a runtime bundler since the beginning of ASP.NET Core even back when DNX was a thing, if you remember that. Shannon's been updating the library as ASP.NET Core has evolved, and it's under active development.

Smidge supports minification, combination, compression for JS/CSS files and features a fluent syntax for creating and configuring bundles

I'll start from "dotnet new mvc" and then:

C:\Users\scott\Desktop\smidgenweb>dotnet add package smidge

Writing C:\Users\scott\AppData\Local\Temp\tmp325B.tmp
info : Adding PackageReference for package 'smidge' into project 'C:\Users\scott\Desktop\smidgenweb\smidgenweb.csproj'.
log : Restoring packages for C:\Users\scott\Desktop\smidgenweb\smidgenweb.csproj...
...SNIP...
log : Installing Smidge 3.0.0.
info : Package 'smidge' is compatible with all the specified frameworks in project 'C:\Users\scott\Desktop\smidgenweb\smidgenweb.csproj'.
info : PackageReference for package 'smidge' version '3.0.0' added to file 'C:\Users\scott\Desktop\smidgenweb\smidgenweb.csproj'.

Then I'll update appSettings.json (where logging lives) and add Smidge's config:

{

"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
},
"smidge": {
"dataFolder" : "App_Data/Smidge",
"version" : "1"
}
}

Let me squish my CSS, so I'll make a bundle:

app.UseSmidge(bundles =>

{
bundles.CreateCss("my-css", "~/css/site.css");
});

I refer to the bundle by name and the Smidge tag helper turns this:

<link rel="stylesheet" href="http://feeds.hanselman.com/~/t/0/0/scotthanselman/~my-css" /> 

into this

<link href="http://feeds.hanselman.com/~/t/0/0/scotthanselman/~https://www.hanselman.com/sb/my-css.css.v1" rel="stylesheet" />

Notice the generated filename with version embedded. That bundle could be one or more files, a whole folder, whatever you need.

Her eyou can see Kestral handling the request. Smidge jumps in there and does its thing, then the bundle is cached for the next request!

info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]

Executing action method Smidge.Controllers.SmidgeController.Bundle (Smidge) with arguments (Smidge.Models.BundleRequestModel) - ModelState is Valid
dbug: Smidge.Controllers.SmidgeController[0]
Processing bundle 'my-css', debug? False ...
dbug: Smidge.FileProcessors.PreProcessManager[0]
Processing file '/css/site.css', type: Css, cacheFile: C:\Users\scott\Desktop\smidgenweb\App_Data\Smidge\Cache\SONOFHEXPOWER\1\bb8368ef.css, watching? False ...
dbug: Smidge.FileProcessors.PreProcessManager[0]
Processed file '/css/site.css' in 19ms
dbug: Smidge.Controllers.SmidgeController[0]
Processed bundle 'my-css' in 73ms
info: Microsoft.AspNetCore.Mvc.Internal.VirtualFileResultExecutor[1]
Executing FileResult, sending file

The minified results are cached wherever you want (remember I said App_Data):

Compressed JS and CSS

This is a SUPER simple example. You can use Smidge's fluent interface to affect how an individual bundle is created and behaves:

bundles.CreateJs("test-bundle-3", "~/Js/Bundle3")

.WithEnvironmentOptions(BundleEnvironmentOptions.Create()
.ForDebug(builder => builder
.EnableCompositeProcessing()
.EnableFileWatcher()
.SetCacheBusterType<AppDomainLifetimeCacheBuster>()
.CacheControlOptions(enableEtag: false, cacheControlMaxAge: 0))
.Build()
);

Smidge is unique in its Custom Pre-Processing Pipeline. Similar to ASP.NET Core itself, if there's anything you don't like or any behavior you want to change, you can.

I'm sure Shannon would appreciate help in Documentation and Open Issues, so go check out Smidge at https://github.com/Shazwazza/Smidge!


Sponsor: Check out JetBrains Rider: a new cross-platform .NET IDE. Edit, refactor, test and debug ASP.NET, .NET Framework, .NET Core, Xamarin or Unity applications. Learn more and download a 30-day trial!



© 2017 Scott Hanselman. All rights reserved.
     

WebOptimizer – a Bundler and Minifier for ASP.NET Core

ASP.NET Core didn’t have a runtime bundler like previous versions of ASP.NET. This was a bummer as I was a fan. Fortunately Mads Kristensen created one and put it on GitHub, called WebOptimizer. WebOptimizer – ASP.NET Core middleware for bundling and …

ASP.NET Core didn't have a runtime bundler like previous versions of ASP.NET. This was a bummer as I was a fan. Fortunately Mads Kristensen created one and put it on GitHub, called WebOptimizer.

WebOptimizer - ASP.NET Core middleware for bundling and minification of CSS and JavaScript files at runtime. With full server-side and client-side caching to ensure high performance.

I'll try it out on a default ASP.NET Core 2.0 app.

First, assuming I've installed http://dot.net I'll run

C:\Users\scott\Desktop> cd squishyweb


C:\Users\scott\Desktop\squishyweb> dotnet new mvc
The template "ASP.NET Core Web App (Model-View-Controller)" was created successfully.
This template contains technologies from parties other than Microsoft, see https://aka.ms/template-3pn for details.

SNIP

Restore succeeded.

Then I'll add a reference to the WebOptimizer package. Be sure to check the versioning and pick the one you want, or use the latest.

C:\Users\scott\Desktop\squishyweb> dotnet add package LigerShark.WebOptimizer.Core --version 1.0.178-beta 

Add the service in ConfigureServices and add it (I'll do it conditionally, only when in Production) in Configure. Notice I had to put it before UseStaticFiles() because I want it to get the first chance at those requests.

public void ConfigureServices(IServiceCollection services)

{
services.AddMvc();
services.AddWebOptimizer();
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseWebOptimizer();
app.UseExceptionHandler("/Home/Error");
}

app.UseStaticFiles();

app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}

After running "dotnet run" I'll request site.css as an example and see it's automatically minimized:

CSS minification automatically

You can control the pipeline with globbing like this:

public void ConfigureServices(IServiceCollection services)

{
services.AddMvc();
services.AddWebOptimizer(pipeline =>
{
pipeline.MinifyJsFiles("js/a.js", "js/b.js", "js/c.js");
});
}

If I wanted to combine some files into an output "file" that'll be held/cached only in memory, I can do that also. To be clear, it'll never touch the disk, it's just a URL. Then I can just refer to it with a <link> within my Razor Page or main Layout.

services.AddWebOptimizer(pipeline =>

{
pipeline.AddCssBundle("/css/mybundle.css", "css/*.css");
});

WebOptimizer also supports automatic "cache busting" with a ?v= query string created by a TagHelper. It can even compile Scss (Sass) files into CSS. There's plugins for TypeScript, Less, and Markdown too!

WebOptimizer is open source and lives at https://github.com/ligershark/WebOptimizer. Go check it out, kick the tires, and see if it meets your needs! Maybe get involved and make a fix or help with docs! There are already some open issues you could start helping with.


Sponsor: Check out JetBrains Rider: a new cross-platform .NET IDE. Edit, refactor, test and debug ASP.NET, .NET Framework, .NET Core, Xamarin or Unity applications. Learn more and download a 30-day trial!



© 2017 Scott Hanselman. All rights reserved.
     

Botwin offers an interesting alternative option for routing with ASP.NET Core

NancyFx is a great alternative to ASP.NET if you want to make elegant little web apis like this:public class SampleModule : Nancy.NancyModule
{
public SampleModule()
{
Get[“/”] = _ => “Hello World!”;
}
}

However, it may …

NancyFx is a great alternative to ASP.NET if you want to make elegant little web apis like this:

public class SampleModule : Nancy.NancyModule

{
public SampleModule()
{
Get["/"] = _ => "Hello World!";
}
}

However, it may be that you want a routing style - the way you define your routes - that is like NancyFx BUT you want to use ASP.NET. Botwin is a library that lets you do just that. They say:

This is not a framework, it simply builds on top of Microsoft.AspNetCore.Routing allowing you to have more elegant routing rather than have attribute routing, convention routing, ASP.Net Controllers or IRouteBuilder extensions.

You can plug Botwin into your existing ASP.NET Core application, or you can even add a basic started Botwin app to "dotnet new" like this:

C:\botwinexample> dotnet new -i BotwinTemplate

C:\botwinexample> dotnet new botwin -n MyBotwinApp
C:\botwinexample> dir
10/11/2017 10:14 PM 284 HomeModule.cs
10/11/2017 10:14 PM 470 MyBotwinApp.csproj
10/11/2017 10:14 PM 421 Program.cs
10/11/2017 10:14 PM 408 Startup.cs
4 File(s) 1,583 bytes

You add Botwin as a service to your ASP.NET Core app:

public class Startup

{ public void ConfigureServices(IServiceCollection services) { services.AddBotwin(); } public void Configure(IApplicationBuilder app) { app.UseBotwin(); }
}

And then add 'Modules' like this:

namespace MyBotwinApp
{
    using Botwin;
    using Microsoft.AspNetCore.Http;
    public class HomeModule : BotwinModule
    {
        public HomeModule()
        {
            Get("/", async(req, res, routeData) => await res.WriteAsync("Hello from Botwin!"));
        }
    }
}

That's a hello world. Let's try something more interesting. You can have Before and After hooks like this:

public class HooksModule : BotwinModule

{
public HooksModule()
{
this.Before = async (ctx) =>
{
ctx.Response.StatusCode = 402;
await ctx.Response.WriteAsync("Pay up you filthy animal");
return false;
};

this.Get("/hooks", async (req, res, routeData) => await res.WriteAsync("Can't catch me here"));

this.After = async (ctx) => await ctx.Response.WriteAsync("Don't forget you owe me big bucks!");
}
}

Here's a more complex example. See how they do a BindAndValidate in the Post() where they check for a valid Actor before working with it.

public class ActorsModule : BotwinModule

{
public ActorsModule(IActorProvider actorProvider)
{
this.Get("/actors", async (req, res, routeData) =>
{
var people = actorProvider.Get();
await res.AsJson(people);
});

this.Get("/actors/{id:int}", async (req, res, routeData) =>
{
var person = actorProvider.Get(routeData.As<int>("id"));
await res.Negotiate(person);
});

this.Put("/actors/{id:int}", async (req, res, routeData) =>
{
var result = req.BindAndValidate<Actor>();

if (!result.ValidationResult.IsValid)
{
res.StatusCode = 422;
await res.Negotiate(result.ValidationResult.GetFormattedErrors());
return;
}

//Update the user in your database

res.StatusCode = 204;
});

this.Post("/actors", async (req, res, routeData) =>
{
var result = req.BindAndValidate<Actor>();

if (!result.ValidationResult.IsValid)
{
res.StatusCode = 422;
await res.Negotiate(result.ValidationResult.GetFormattedErrors());
return;
}

//Save the user in your database
res.StatusCode = 201;
await res.Negotiate(result.Data);
});
}

What do you think about the choices you have with ASP.NET Core? Some people feel like the amount of plugability is overwhelming, but I find the flexibility heartening. Go check out Botwin and, hopefully, help out and contribute to open source!


Sponsor: Get the latest JetBrains Rider preview for .NET Core 2.0 support, Value Tracking and Call Tracking, MSTest runner, new code inspections and refactorings, and the Parallel Stacks view in debugger.



© 2017 Scott Hanselman. All rights reserved.
     

Botwin offers an interesting alternative option for routing with ASP.NET Core

NancyFx is a great alternative to ASP.NET if you want to make elegant little web apis like this:public class SampleModule : Nancy.NancyModule
{
public SampleModule()
{
Get[“/”] = _ => “Hello World!”;
}
}

However, it may …

NancyFx is a great alternative to ASP.NET if you want to make elegant little web apis like this:

public class SampleModule : Nancy.NancyModule

{
public SampleModule()
{
Get["/"] = _ => "Hello World!";
}
}

However, it may be that you want a routing style - the way you define your routes - that is like NancyFx BUT you want to use ASP.NET. Botwin is a library that lets you do just that. They say:

This is not a framework, it simply builds on top of Microsoft.AspNetCore.Routing allowing you to have more elegant routing rather than have attribute routing, convention routing, ASP.Net Controllers or IRouteBuilder extensions.

You can plug Botwin into your existing ASP.NET Core application, or you can even add a basic started Botwin app to "dotnet new" like this:

C:\botwinexample> dotnet new -i BotwinTemplate

C:\botwinexample> dotnet new botwin -n MyBotwinApp
C:\botwinexample> dir
10/11/2017 10:14 PM 284 HomeModule.cs
10/11/2017 10:14 PM 470 MyBotwinApp.csproj
10/11/2017 10:14 PM 421 Program.cs
10/11/2017 10:14 PM 408 Startup.cs
4 File(s) 1,583 bytes

You add Botwin as a service to your ASP.NET Core app:

public class Startup

{ public void ConfigureServices(IServiceCollection services) { services.AddBotwin(); } public void Configure(IApplicationBuilder app) { app.UseBotwin(); }
}

And then add 'Modules' like this:

namespace MyBotwinApp
{
    using Botwin;
    using Microsoft.AspNetCore.Http;
    public class HomeModule : BotwinModule
    {
        public HomeModule()
        {
            Get("/", async(req, res, routeData) => await res.WriteAsync("Hello from Botwin!"));
        }
    }
}

That's a hello world. Let's try something more interesting. You can have Before and After hooks like this:

public class HooksModule : BotwinModule

{
public HooksModule()
{
this.Before = async (ctx) =>
{
ctx.Response.StatusCode = 402;
await ctx.Response.WriteAsync("Pay up you filthy animal");
return false;
};

this.Get("/hooks", async (req, res, routeData) => await res.WriteAsync("Can't catch me here"));

this.After = async (ctx) => await ctx.Response.WriteAsync("Don't forget you owe me big bucks!");
}
}

Here's a more complex example. See how they do a BindAndValidate in the Post() where they check for a valid Actor before working with it.

public class ActorsModule : BotwinModule

{
public ActorsModule(IActorProvider actorProvider)
{
this.Get("/actors", async (req, res, routeData) =>
{
var people = actorProvider.Get();
await res.AsJson(people);
});

this.Get("/actors/{id:int}", async (req, res, routeData) =>
{
var person = actorProvider.Get(routeData.As<int>("id"));
await res.Negotiate(person);
});

this.Put("/actors/{id:int}", async (req, res, routeData) =>
{
var result = req.BindAndValidate<Actor>();

if (!result.ValidationResult.IsValid)
{
res.StatusCode = 422;
await res.Negotiate(result.ValidationResult.GetFormattedErrors());
return;
}

//Update the user in your database

res.StatusCode = 204;
});

this.Post("/actors", async (req, res, routeData) =>
{
var result = req.BindAndValidate<Actor>();

if (!result.ValidationResult.IsValid)
{
res.StatusCode = 422;
await res.Negotiate(result.ValidationResult.GetFormattedErrors());
return;
}

//Save the user in your database
res.StatusCode = 201;
await res.Negotiate(result.Data);
});
}

What do you think about the choices you have with ASP.NET Core? Some people feel like the amount of plugability is overwhelming, but I find the flexibility heartening. Go check out Botwin and, hopefully, help out and contribute to open source!


Sponsor: Get the latest JetBrains Rider preview for .NET Core 2.0 support, Value Tracking and Call Tracking, MSTest runner, new code inspections and refactorings, and the Parallel Stacks view in debugger.



© 2017 Scott Hanselman. All rights reserved.