Bald Bearded Builder
aspnetcore/building-custom-middleware-for-asp-net-core

Building Custom Middleware for ASP.NET Core

Queues, stacks, and dictionaries solve distinct C# collection problems involving fair ordering, last-in-first-out history, and fast typed lookups.

Loads from YouTube when you press play

Transcript 6 topics

Middleware basics

0:00

All kinds of wear are important. Think about it. Software, underwear, heck, middleware. Let's talk about what middleware looks like in an ASP dot NET Core application. Middleware is basically the building blocks of the ASP dot NET request pipeline.

And that's a lot of jargon. So let's talk about what they really are. Every time an ASP dot NET Core app receives a request, it fires up this process to handle that request. And to handle that request, it's gonna step through each piece of middleware that we've registered with it. Now you've seen a lot of these, like, use authentication, use authorization, use cores, use static files, use routing.

Those are all built in pieces of, middleware. We're gonna talk about creating our own. But, essentially, as it's stepping through those, maybe it's adding additional data. Maybe it's doing logging.

Most of the time, that middleware isn't aware of what the other middleware is doing. It's its own contained little piece of information.

Building custom middleware

0:58

Sometimes it matters. You need to register them in order. But at the end of the day, you're gonna get back a response of some kind because your request went through the middleware that's been registered for that application. So to see what that looks like in the real world, let's fire up a dot net new web API, let's say, and see what it gives us. It's gonna bake in some middleware by default.

And once it's there, we can look into program CS and we can register some services it's doing. And then it's using the app builder to build app use Swagger, use Swagger UI, use HTTPS redirection. Those are all pieces of middleware. I'm gonna get rid of all this stuff we don't really care about, like this weather forecast garbage. Let me get out the ones that they've got added, and we'll just we'll look at some ones that are very common.

You've likely seen them in apps you've used before. If you're using a web app instead of an API, you've probably seen use static files. You've probably seen use cookie policy, use routing, use cores, authentication, authorization session. There are tons of these baked in, but we wanna write our own. Now, why would we wanna do that?

Well, it could be that we have custom logging requirements. Maybe we wanna handle exceptions globally for our entire app. If you wanna learn more about how to do that, click on that video up in the cards to see a better explainer on how to build custom logging or custom exception handling for your asp.net Core app. In that, we're not going go into that detail here. For whatever reason, we're going to build one.

And let's just pretend we have a great reason. In this case, let's build one that just every request adds a transaction ID to the response header, that goes back to the client. So if they need to reference an error, they can say, well, when I got it back, I got this transaction ID. Very, very simplistic. I don't know that you ever wanna do this, but let's just pretend you do.

First thing we're gonna do is get out those, and I'm gonna paste this in. And if you look at it, this is the most simplistic way to to build middleware. And you probably don't wanna do this in the real world, but I just wanna show you it's gonna take in the context and the next. So we can run code here that happens before the response. We can invoke the next piece of middleware, and then we can run it, code run after response is going back.

We can do code there, but we should never do code there that's gonna modify the response because that can throw all kinds of problems. Like, you if you added things to a header or body, it could change the content size, all those sorts of don't do it. If you want to log there, great. If you a lot of things you can do, but don't don't try to write the response. If you want to write the response, do it up here.

And actually, you can short circuit this whole thing by, returning the response here, and not calling next. A great a great example of that would be use static files. That's usually one of the third first pieces of middleware you see registered. Why? Well, it hits that and says, okay, well, you're asking for this static file.

Do I have some kind of static file out here that I can serve up to you? Oh, yeah. Yeah. There's the CSS file. It can just hand it back without having to run different kinds of logging or authentication or authorization or routing middleware.

There's no reason to do all that processing if we're just handing you a file back and forth. Let's exit early. Now that works, and we could put code here and do it. But this isn't very modular.

It'd be hard to, like, run tests and that sort of stuff. It's if we wanna be a little more maintainable, let's go create a class.

Creating the transaction middleware

4:49

We'll call it, say, TransactionMiddleware. Let's go do that real quick. Let's go in. I'm gonna save that as transaction middleware, and I've pasted it in. We can go check it out.

Basically, when we construct it, we're gonna take that request delegate, which is the next, and we so that we can invoke it later if we need to. And then in our invoke, we're going say, give me the HTTP context of this request. So I'll get all my request information, the headers and all that sort of stuff. And then in there, I'm going to say, hey, go ahead and keep this going down the line because later on, we're going to build a minimal API endpoint to call. And we'll say, go ahead and keep passing it down so that routing will pick it up.

And then we can have it handle the actual request. But for now, go ahead and just add this to the response headers so that we'll have it when it goes back out. And we're just gonna add a x transaction ID with a new grid, included in that data. Right? And then we'll say, call the next piece of the the next piece of middle words are hard.

Call the next piece of middleware in the chain and keep this thing going until we respond back. And then the last thing, this is just a nice d that you should probably be doing, don't just manually register that middleware in your program CS. Create a nice, pretty, little, extension method. In this case, use subscribe, please. I don't know why I named it that.

It seems clever, and maybe you should. But regardless, all that's gonna do is register that middleware for us a little cleaner in the program CS. So with that file added, we can go back over to the program CS, and we'll remove that code. And we'll say app dot use what did I call it? Subscribe.

I forgot what I called it. Use subscribe, please. Like a boss. And then let's add a web API endpoint map get to the root. Just return result text, hello there.

So the responses, you can say hello there, but we'll look in the developer tools to see the headers to see if that transaction ID gets added. Let's go .net, run this. And then and then and then let's open it in a browser real quick. Okay. So there it is.

We're gonna f 12 so we can look at the headers. Go to the network tab. Let's refresh so we can get them fresh here. There we are. And if we look down through our x transaction ID is that GWID.

Can you see that pretty good? Can you see it? Can you see it? I'll zoom in a bit there for you. There you go.

So we got our GWID and our response header back in. But, Michael, what if I wanted to short circuit that request and send stuff back in the response? Maybe based on something, start responding now and and stop going beyond the pipeline. Well, let's try it. Let's, let's go back over to our code, and we'll go back over to our middleware that looks magnificent.

Short-circuiting requests

7:42

But, okay, I'll I'll change it for you just since you asked. Let's see. Let's close the Explorer real quick. We're gonna get in an API key from the query string. If we get a query string, the API key in, and it exists, we're gonna write back to the console or no.

If we don't get an API key back in, we're gonna write back API key is required and then just return. We're not gonna call that next, so we don't pass it up the middleware any further. We don't try to hit that endpoint and route it. We just respond back immediately and say, you didn't give us an API key. Now you'd probably want that API key to come in, like, via a header or something.

But look, I'm trying to be easy here. And then if we did have it, we'll respond back just normal. We'll add that transaction ID to the header and call next so that our our minimal API endpoint gets picked up and responded back. Right? So nothing else changes there.

So I'm gonna save that. And then if we go back to the terminal, stop that, and let's run it again. Do it. Alright. It's running.

I'm gonna hit refresh. And now I'm getting back API key is required. And if I look at the headers of that request, you'll notice the response headers do not have a transaction ID there. But if I add a query string and say API key equals, I don't know, did you subscribe yet? Seems appropriate.

Now we get back a hello there. And if we go look at the headers of this request, we did get that transaction ID. So it appends it and then sends it on up the line to the next piece of middleware to get a route to our middle API or middle whatever. Minimal API. The words, they fail me.

Anyway, we're not gonna normally do that kind of stuff in middleware. Right? That's pretty simplistic. Even if we were wanting to write back a transaction ID, we wouldn't be doing it just a new grid there.

We would probably be using some kind of service, right, that's that's registering those and recording them to a database somewhere so that we know what's what and who requested this. We'd probably record back transaction ID and API key so we know they are associated with each other.

Injecting services

9:59

So how would we inject a service into that middleware? Well, for one, you need to understand that that middleware gets wound up and constructed when the application starts. Think of it like a singleton. Now, if you wanna know more about dependency injection lifetime cycles, again, I've already done a video about this. Check it out in the cards up there to go into a more deep dive.

But understand that this middleware, when you construct it at the application start, that middleware lives throughout the entire application's life cycle. So as a whole, it doesn't know about each individual request. If you have services that are scoped scoped or transient, well, we need to pass those services in each time it's invoked. So if we look back at our code real quick, and specifically, let's look at our middleware, when we construct this object, if we need to pass any singleton services in, that's the place to do it. If we needed to inject some kind of scoped or transient service, we want to do that inside the invoke or invoke async method so that we can have those in their appropriate location, right, the appropriate scope.

So let's fake this up. I'm going to go and create a new iTransactionService. Let me save this. Let's call it transaction service. And then all it's going to have is a string that gets transaction ID.

We'll create a transaction service that implements that interface, and it has that method that just returns out the string. Right? So then we need to go modify our InvokeAsync to receive an iTransactionService. We'll call it transaction service. That seems appropriate.

And then instead of saying GUID, new GUID to string, let's say transaction service dot what was that method name? GetTransactionID. Yeah. Because I can't be expected to remember this stuff. It's not like I wrote this code.

Or did I? Okay. So now how do we get it going? Well, we've got it invoked into here, but we need to go register it in our program CS.

Registering scoped services

12:11

So let's go in here and say builder.servicesdot, add scoped because this is a scoped service. We want one for every request. If I'm typing this right, we'll see. And it's complaining because I'm gonna have to compile it before it's there. And, that should do us.

Now that you subscribe, please, should have its transaction service injected. Let's give it a go. Let's stop this and do .net or .net run again. And then, well, we'll go refresh our page. Let's take off that API key again.

Hit that API key as required. So our middleware is still working as far as that. But when we get it, we get a hello there. If we go look at that request, do we get it? We get our transaction ID that came out of our injected service.

So it's pretty easy to inject services and all that kind stuff as long as you remember to, register them. And you can see where, like, knowing that you can inject services into those, there are so many things you could do with middleware. Things like appending things to the response or checking things in the request or logging and exception handling like we mentioned. Though the world is your oyster, my friend. Does your team use middleware and ASP dot NET Core apps today?

Hopefully, you're at least exception checking in there, but maybe not. Let me know your war stories of things that have gotten you with that and and how it's worked out for you. Until next time.

Sign in to join in. Reading needs nothing.