The 1.0 release of PingPongr was pushed to NuGet a while ago. Since its initial release the framework has been reworked to use .NET Standard 2.0 which has simplified and standardized the basic use cases.

What is it? PingPongr is a lightweight web framework built around the Mediator or Command pattern. Use it as an alternative to ASP.NET for sites that only require simple web services.
If you just want to get started, checkout the github repository for the simple and complex examples of using the framework.
Build status NuGet

The framework’s goals are still the same from beta:

  • The code should be minimal both in size and in scope. The entire framework should fit into the mind of the developer and not add much of a maintenance burden.
  • Minimal dependencies. Again, as little of a maintenance burden as possible.
  • It should use POCO (Plain Old CSharp Objects) requests and responses that are processed by handlers.
  • Any handler dependencies should be injected using standard dependency injection containers.
  • It should be easy to get started and simple to extend.

The new additions and releases of .NET have made these goals easier to achieve. So easy, you might not even need PingPongr at all!

.NET Core and .NET Standard

When PingPongr was first created, the .NET Core framework was still in its infancy and many of the full framework APIs were missing. ASP.NET “Next” (as it was called then), was still in flux and the APIs hadn’t stabilized.

Because of that, the initial release used OWIN as a standard interface for HTTP requests and setting up the dependency injection container required custom setup code.

Since .NET Standard 2.0, all the APIs needed for PingPongr have been added and stabilized in the the various platform frameworks.

On a related note: I’ve found .NET Core to be quite stable in production. It’s fast, easy to maintain, and a general pleasure to work with. Good job.

To abstract away from requiring HTTP as the base for all requests, PingPongr uses a IRequestContext interface that’s implemented by the AspNetCore extension. The interface itself, though, intentionally mirrors the Microsoft.AspNetCore.Http.HttpContext class from the Microsoft.AspNetCore.Http.Abstractions library. The new HttpContext replaces the old OWIN and makes it much easier to work with.

Here’s the part of the context that PingPongr cares about:

1
2
3
4
5
6
7
8
9
10
11
public interface IRequestContext
{
string Path { get; }
bool IsHandled { get; set; } // represents the HTTP status code
string RequestContentType { get; }
Stream RequestBody { get; }
string ResponseContentType { get; set; }
Stream ResponseBody { get; }
CancellationToken CancellationToken { get; }
T GetService<T>();
}

This is very similar to the RequestContext in the beta release of PingPongr, but has one major difference with T GetService<T>();. The IServiceProvider in the System namespace is now part of .NET Standard and provides a standardized way of working with a dependency injection container. If you use any of the ASP.NET hosting solutions, it also automatically creates new scopes per request, and makes it available on the incoming context.

What this means to the end users of PingPongr is dependency injection no longer has to be setup ‘custom’ and can now just use the standard extensions that the container projects provide or even just use the Microsoft library.

The new PingPongr.Extensions.AspNetCore package contains all the implementations and extensions need to work with these new abstractions easy for the default cases, and simply extensible for more complex implementations. Chances are you can just use the three main packages with defaults and go.

An entire self hosted example is on the main page of the repo: https://github.com/decoy/PingPongr.

What PingPongr does for you and why you might not need it

The core of the PingPongr is the Router and Route. The router simply matches an incoming request path to a given route, while a route handles the four steps below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public async Task Send(IMediaTypeHandler mediaHandler, IRequestContext context)
{
// JSON from the Context.Request.Body
var req = await mediaHandler.Read<TRequest>(context);

// retrieve the handler from the IServicesProvider
var handler = context.GetService<IRouteRequestHandler<TRequest, TResponse>>();

// run the retrieved handler class and get the response
var resp = await handler.Handle(req, context.CancellationToken);

// serialize the response object to JSON and write to Context.Response.Body
await mediaHandler.Write(context, resp);
}

Route.cs

That’s not a lot. By reading the source code of PingPongr, you can see how to recreate this functionality pretty easily yourself. Most of the complexity in PingPongr is the abstractions, generic type handling, and dependency injection.

If you wanted to write something more straight forward - just functions that return results - you could write something like this instead:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using System.IO;
using System.Threading.Tasks;

public class Ping
{
public string Message { get; set; }
}

public class Pong
{
public string Reply { get; set; }
}

public class Startup
{
public static Pong HandlePing(Ping request)
{
return new Pong { Reply = "Re: " + request.Message };
}

public void Configure(IApplicationBuilder app)
{
// we can use 'map' in place of the PingPongr router.
app.Map("/ping", (a) =>
{
// and manually define the context handling in place of the route
a.Run(async (ctx) =>
{
// deserialize the body
// note: We needed a quick IO helper to ready the body stream
var body = await ReadAsString(ctx.Request.Body);
var ping = JsonConvert.DeserializeObject<Ping>(body);

// run the handler
var pong = HandlePing(ping);

// serialize the results to the stream
// note: we can use the write async helper here
var t = JsonConvert.SerializeObject(pong);
await ctx.Response.WriteAsync(t);
});

});
}

public static async Task<string> ReadAsString(Stream stream)
{
using (var reader = new StreamReader(stream))
{
return await reader.ReadToEndAsync();
}
}
}

public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}

public static IWebHost BuildWebHost(string[] args) => new WebHostBuilder()
.UseKestrel()
.UseStartup<Startup>()
.Build();
}

This is a fully functional webservice example using only the Newtonsoft.Json and Microsoft.AspNetCore.Server.Kestrel packages. I intentionally kept the code simple and naive, but there’s nothing keeping you from expanding it into your own reusable framework.

One of the best parts of this example and the stabilized ASP.NET API is how close the app.Use patterns match node’s Express or Go’s Iris framework APIs. They have all adopt a simple middleware style that makes it easier to transfer your knowledge and skills from one framework to another.

Conclusion

I still use PingPongr in simple projects, but I also have started writing more customized middleware that does the same thing for larger projects. Use what makes sense to you and keeps your future technical debt down!

If you have any questions or notice anything incorrect or missing, please post an issue or contact me.