>> Hi everyone. I'm Stephen Halter. I'm an engineer on ASP.NET. >> I'm Safia Abdalla. I'm also
an engineer on the ASP.NET team. >> We're both very excited to
show you Minimal APIs in.NET 6. I'm going to start out with a demo. Today I'm moonlighting as an
engineer at Contoso Construction. I have been working on an app, a backend to manage our construction job sites and I've already written
this app using.NET 5..NET 5 templates you
may be familiar with have program.cs where
you set up your host, startup CS where you set up
your application and then you might have some controller
if you're doing a Web API. Now, I want to update this to.NET 6. First, if we take a look
at this edit project file, it's nice that we don't have
to unload projects anymore. If I wanted to upgrade to.NET 6, I could change the TFM, the Target Framework, and go and this would just run. But I wanted to do something
a little more fun. I heard that.NET 6
introduced Minimal APIs. Here I will create a new project that does the
same thing and try this out. Without further ado, let's try
out the new project templates. I'm going to add a project. I'm going to start out with the
empty ASP.NET Core project. I'm going to call this
my minimal server and will choose all the defaults. This template is quite a bit different than what you
might have seen before. You'll notice instead of a
program class and a startup class and these controllers, this
is the empty template. When in fact controllers before,
but what about start-up? Now we just have four lines of code. You've probably seen
a bit of this before. This is using our new
web application host, and this is using the new C
Sharp features you heard about. If we take a look at this CSPROJ, you'll see that we've started
enabling nullability, implicit usings, and
that allows us to really get down to the business
logic of our application. Now, since reporting
an existing app, I'm going to copy these package
references that we already have. We can install packages
just like before, but it's pretty easy just to copy. Then from there I'm going to go
and I'm going to define our DTOs. I'm going to add some jobs. Then we're going to be using EF
Core to manage your database, and I'm going to be connecting
to SQL Server and Azure. To do this, just like we did
in our original application, we're going to add Azure Key Vault to
your configuration so we can pull down
our connection string. This is one of the new things in this presentation that
you might not have seen. In the past you had this
host configuration and app configuration and your configuration builders
and IConfiguration, and you had to figure out
where to use each thing. Now in.NET 6, our web application builder
has a configuration manager. This allows us to
both reconfiguration. In this case, it's
reading a vault URI from my environment variable and then
add new configuration sources, so we're now reading connection
strings from Azure Key Vault. You'll notice that we're using the same configuration object to both read the vault
URI and add sources. Now that we have our
connection strings, we can add the DbContext for our jobs and we're going to use
the Azure SQL connection string. I have defined in Key Vault. Now, instead of creating a new
class to define a controller, I can use these new map methods. Now you might have seen MapGet, MapPost, MapDelete, things like that before, but this isn't like what
you've seen before. Instead of always
taking an HTTP context and always returning a task, you have a lot more flexibility
and a lot more options, a lot like you would in a
Web API or MVC controller. If you want to define
some endpoints, we're going to define
the exact same list of endpoints that we previously
had in our jobs controller. Before we were taking
our job site DB, which is the DbContext from Entity Framework as a
constructor parameter. But now we're just taking
these as a parameter to our request handlers. This we're calling them,
but this is Minimal APIs. You'll see that if we want to
get all endpoints so you can get our normal ToList async. Let's first set this is our
startup project and make sure that we're good so far. We're going to start
this project and I'm going to be testing this
out with HTTP ruffle. We still have our
hello world endpoints, so at least you started,
that's a good sign. We're listening on 7204. That's another change
in our templates. We now are listening on
basically random port numbers. This means when you
do file new project, you don't have port conflicts. You don't have to go into your
launch settings and change everything just to
avoid port conflicts. This is great when you're trying to microservices where you're
running a bunch of apps at once. Let's quickly get the jobs endpoint and let's see if we
get some results. We're connecting to Azure
service database has already populated and we see a little bunch of jobs
we have in progress. That's a good start. But let's look at some of the
new features we have. You'll notice that we're pulling in some attributes for when
we're posting job bodies. We've posting jobs as JSON here, so anytime you have a
parameter that's from the body that's being read as JSON. Here, the JobSiteDb
is from services. You'll notice before we took
that without the attribute. The attribute can be inferred. Basically, Minimal APIs knows that the JobSiteDb is in
your service provider, so there's no need to attribute it unless you prefer
that for better clarity. Another thing to note is that these attributes are
coming from MVC, so these are the existing
attributes you might use today in your MVC controllers. They continue to work with
these callback handlers. >> Got it. Stephen, you've shown how we can resolve
the job type from body. What if we wanted to
resolve some custom detail, say we wanted to do a search for a job sites
based on the coordinates? >> Excellent question.
We already have this search based on a string that we had in our
original jobs controller. We're searching the
job site by name. But if I want to search
in a specific area, we can use a new feature
in Minimal APIs that allow you to basically define custom DTOs and determine
how they're parsed. Let's see how this would look
for a latitude-longitude pair, which we'll call a coordinate. We're going to define
our coordinate DTO. Here we're using records, so it's easy comparisons that
defines all equals methods. We can define latitude,
longitude right here. I really enjoy this. Then the key here is that we have
this static tryparse method. This takes input from either the router
query string and then allows us to output
our coordinate type. In this case, we're reading two
doubles separated by a comma, and that will be our latitude-longitude pair
which we can search by. Now, to use this in an endpoint, it's going to be pretty
similar to our name search. But if we're going to define
our coordinate endpoint, we'll see we have a
coordinate here instead of a query string and we're taking
this coordinate as a parameter. Now we're searching for
within one degree of latitude and longitude of
the place that I search. >> Nice. >> If we start this again, we got to restart the entire application because a re-running the
program main method. To rerun main, we're going to restart the application
even if we could reload the code, it's not going to be called
again unless we restart. Then let's see if we can
search for a location, and then let's search for around. Let's see what we have here. We have basically around 47, negative 122. Maybe
we'll find a few. >> Yeah. >> We need a comma,
otherwise it won't parse. We might get a 400. Good thing. We can go back in my REPL
and do we have results? Nope because I basically
searched in the wrong place. But maybe it's not within a degree. We just deleted a
bunch of endpoints. >> It definitely work. >> Yeah. You got to trust us. This is what happens sometimes
when you have a live demo. Now, this works great for query
string and route parameters. But say you want to read
something from the body, that might need something
that's asynchronous. You don't want to block
your requests thread. That's a big no-no in ASP.NET. We also have the option of
defining a bind async method. If both of these static
methods exist on a given type, Minimal APIs will try calling the bind async method
since it's more flexible. But because it's more than
routing query string parameters, bind async doesn't just provide
the string for you to parse. It just gives you the
entire HTTP context, but you've got some details
about the parameter that the coordinate thing was, so we can take the coordinate name, pull it out there out via value
call tryparse, things work. That's not all we have. This is teaching us how
we can basically handle different kinds of
inputs in Minimal APIs. You'll see we also have
some other special inputs, like in this case, you're
taking HTTP response. This could also be an HTTP context, this could be a ClaimsPrincipal,
things of that nature. You have these special parameters. That's nice. That
allows me to basically in this case write to
one created response. But I also have the option of
doing results dot created. Then we can put in our
job or whatever, job ID. I'm not going to write that all out. Now we have some other examples, say here and here, that basically are a lot like the actual results
that you would have seen in the jobs
controller in the past. Unlike with the attributes that
are supported on the parameters, the action results
that we're returning actually object resulted not
found object result in MVC, those will not work in Minimal APIs, so we designed this new
results static class with these methods and these
return new IO result types. >> This is new to minimal APIs. >> Cool. I see there's a ton of
extension methods for returning 404 results or 200OK or a
variety of other result types. What if I wanted to add a customer's
all type for our application? Like say we need to return XML
from one of our endpoints? >> XML? >> Yeah. >> What are we, in the year 2000? Fortunately, we did
prepare for this. I think we can do just that. Let's try defining an XML result and seeing what that
would look like. Here we're implementing that
IResult type that we had before. We're using an XML serializer. One thing that we're doing, serializer is an async. We're serializing the
student memory stream because we want better scale. If you want even better
efficiency, you'll pull this, but this is taking an
HttpContext and then writing this result which is passed into the constructor to
the response body. It's that simple. It's
doing that asynchronously. There's one other thing
that I added here sneakily. Is this extension methods
to iresults extension. I'm not even using this parameter, but all it's doing is calling
this constructor with a t. This is allowing us to discover
these new results types. Let me show you what that means. Here we have this endpoint that
is returning all of our jobs. Right now it's just returning
the object directly. Just like in MVC, you're
allowed to do that. But if you have these
custom results types, it allows you to do interesting
things to HTTP response, like change the status code, change how things are serialized. If you return an object
directly, it's always JSON. But in this case we can do results. In this case I'm going
to go extensions. I resolved extensions type there. I was extending with my Extension
Methods and then do XML. Because this is a method
in our constructor, I don't need to pass the
t. If all goes well, it's going to tell me
that I need to restart the entire application
which is true. Anytime that we find an endpoint, we got to rerun main at this point. Certainly if you're adding an
endpoint that's necessary. Now it's running again. If I do again before we were looking at all the end
points and that is JSON. Now we have XML. Yes, so perfect for all of you
guys traveling back in time. This is what I wanted to show
you as far as how to deal with the new minimal host parameter
binding and this new results types. I hear that you've got
some stuff to show. >> Yes, I do. We're going to
switch over to me really quickly. I loved everything that
you did in your demo. You showed a ton of
interesting endpoints. I want to take it a little
bit further and try and document some of the endpoints
that you have created here. I'm going to go ahead and
jump into my local clone of our application that we've been
moonlighting and building, and show you how I'm
going to add support for open API integration to this app. Now, open API is a specification
for documenting APIs. We can leverage that within
our minimal apps the same way we would in our controller
based applications. To do this, I'm going to set up the groundwork with
a couple of things. First things first, I have
registered the services that are needed to support generating open API definitions
in my application, so two lines of code. Again, I'm using that web
application builder type that Stephen was talking
about a little bit earlier. Then I'm going to add some
middlewares that are going to render a swagger user interface. The API that I'm going to document, it's going to be printed
out and displayed in this swagger based
UI where I can explore all of the types and even
send out requests to those endpoints the same way
you were doing with HttpRepl. I've set up the groundwork with
my services and my middlewares. Now it's time for me to
get into the fun part and actually start annotating
some of these APIs. Here I've got that, GetAllJobs in point that we were
just looking at. I've got the version that
returns a JSON payload. I'm going to go ahead
and add some of the documentation that
this endpoint needs. Now, if you're familiar with
building controller based apps, you probably are familiar with using the produces attribute
to document your APIs. You'll notice here that I'm
using the extension methods that are coming in with minimal
APIs to document the API. If you want to use attributes, those will still work, but I prefer the extension methods. It reads a little
easier on the eyes. Here what I'm saying
is that might get all jobs endpoint is going to produce a list of jobs with a 200 status code.
That makes sense to me. I'm uniquely identifying this
endpoint as a GetAllJobs end point. Then I'm tagging it as one of the
many getters in our application. We've got a few of those that we're going to see in just a little bit. Now, similar to what
was supported in MVC, in addition to having
one produces attribute, you can have multiple
if you want to convey something like an endpoint that can return a 404 if it didn't find any data in your
database or an object, if it did find something
in your database. Which is what I've done here
for my GetJob by the endpoint. I've got two producers, one for the 404 and one for
the 200OK. Pretty nifty. Now, getters are fun and nifty, but we've also got endpoints
that take in data. An example of this is this post
jobs endpoint that I have. Now this post jobs endpoint
accepts a, jobs Dto. I want to go ahead
and document that. I'm going to show you what it looks
like to annotate and say that a particular endpoint accepts a
certain type of endpoint as data. As you might expect, I'm using the accepts
extension method to say that this endpoint
is going to take a job Dto and it's going to
consume it as a JSON payload. There you go. We've got
accepts to define what our post based endpoints consume or take in as input and of course, produces with name get tags. All of that fun stuff.
Last but not least, let's talk a little bit about endpoints that we don't
want to be documented. We've got this search by query
endpoint that I'm wanting to omit. I was just playing around with
something and I don't want this to show up in our
open API description. Well, I could use the exclude
from description marker to say, wherever we're generating
an open API specification to document this API, just omit this endpoint. What does that all look like when
everything is said and done? I'm going to hop over to
my browser and show you. We're going to go here and you'll see that I've got
the swagger based UI. I have got my creators which was that post jobs endpoint
under one category. I've got my getters under another. We can pop in and see that
it's been documented that our push jobs endpoint
is going to take a JSON based object
that contains the id, latitude, longitude, and name of the object that we want
inserted into the database. Pretty cool. We can
actually try and execute against some of these
endpoints from the swagger UI. But I'm not going to do that exactly because I've got an
even better idea, which is I want to take
a stab at building a user interface for
our application here. I'm going to do that
by leaning into what my favorite UI technologies, which is Blazor WebAssembly. Through the power of preparing
ahead and time for my demo, I have set up my application
with a Blazor interface. I'm going to show you some of the
changes that I needed to make to my program CS in order
support hosting Blazor. Now if you're starting off
with any of the templates, you're not going to need to make
any of these modifications, but they're helpful to know about. Here I'm registering the services
that enable Blazor WASM hosting, same model that I did with open API. The next thing I'm going
to do is show you-all of the middlewares that I'm
registering in order to be able to serve all of
the static assets that are associated with my Blazor
WebAssembly application. When all is said and done, I'm going to give you a sneak
peek of what I've built, which is right over here. Sweet. I have used a map-based UI to lay out a map of all of the points that are
currently in our database. You can see we've got all of the different job
sites that have been registered displayed
on this wonderful map. I'm going to show you a little
bit of what that looks like from the Blazor end
behind the scenes. You'll be happy to know that
it'll look pretty familiar. If we hop over to our
index.razor page, which is the homepage
for our application, you'll notice that I'm using
the HTTP client to send a JSON request using a get to the jobs endpoint that
we were just looking at. I'm going to take the result of
that request and populate it into a list of jobs stored locally in my Blazor
WebAssembly application. Then I'm just going to display
those jobs as MapMarkers in my UI. Now, getting data from a
database that's pretty nifty, but I want to show
something else which is sending data to the database. What I want to do is add a feature that when a
user clicks on a map, they are able to add a new job site. I think this is going
to be really helpful for some of our workers. I've done that here. I have my
own map click event handler. What this is going to do,
is it's going to take the information about
the current latitude and longitude that was
clicked on the map, the name that the user has picked, and it's going to send
that as a post request to the API that we're using and it's going to add it to the
database behind the scenes. Let me show you what that
looks like right now. Going to pop over
back to my UI here. Let's go ahead and add a new site. Let's say in Kenwick. I'm just going to call
it Kenwick apartments because developers are not known
for creativity with names. Cool. We'll see that it's
been added to our UI. If we refresh here, we
should be able to see also added into our map. That was pretty nifty,
don't you think? >> I think so, but are we just
letting anyone add job sites? >> Good point. We
should probably add some security authentication
into our application. Now, let me show you how to do that. We'll start in the same framework. We'll talk about the
changes that I'm going to be making to my minimal app, and then some of the
corresponding changes in my Blazor application. I'm going to hop over
to my program CS here. You'll notice that I've
done a couple of things. First things first, I've registered all of the authentication
services that I've needed using an Azure AD based
configuration in my app. I'm using Azure AD and
Microsoft Identity Web to provide authentication
in my application. The other thing I've done is I've registered all the
way at the bottom, the UseAuthentication
and UseAuthorization extension methods to add in all of the authentication and authorization related
middlewares into my application. With those two things, I've
been configured to support requiring authorized access
to particular endpoints. The endpoint that I
care about locking down is the
RequireAuthorization endpoint, which is right here. Or the post jobs on point
which I've already added, the RequireAuthorization
extension Method 2. If you've used controller
based applications, you might be familiar
with the authorize attribute on an
action or controller. This is the exact same thing. I'm saying that if
you're going to be sending a post request to jobs, you'd better come
with a bearer token. >> Could I use the authorize
attribute if I wanted to? >> Yes, you can. Wu, flexibility. I love it. Cool. Now that we've got
the logic written up in our back end that requires
authorization to our endpoint. We're going to go ahead and modify
our front end a little bit. This is all done in Blazor, and it's actually
pretty nifty to do. All I've done is added a try catch
to my post request that says, ''try and send the post request, and if it fails because there's no access token that the
user has registered, then go ahead and
redirect them to login.'' Let me show you what
that looks like. I'm going to do that in
my hidden window here, so I'm not logged in. I'm an incognito mode. I should not be able
to add any endpoints, and I'm going to try
and do that right now. Now, let me call this hacky
endpoint because I'm a hacker. You're going to see that
I am prompted to log in via Azure AD into my application. To save time, I'm not
going to do that here. Also, no one needs to know
my password on the internet. I've already actually logged in to this app that I've
had running before. You can see that I've got my
user account already registered, and I'll show you that
if we try and add here, let's call this Nez
Perce Apartments. Nifty, we're able to
add the endpoint now, and we've actually done it by
providing a bearer token in the outbound request we sent from the client to our
minimal API server. It's pretty neat. I like the
track that you've set me down. With making sure
that my fundamentals like security are covered
in my application. I think there's one
thing we want to do before shipping this
out to production, which is making sure
that we test our app. I'm going to jump in and
talk a little bit about testing minimal apps in DotNet 6. Now, there's two ways you can
test minimal apps and DotNet 6. I'm going to start by talking
about how you can approach integration testing
for minimal apps. I created a new test
project and I have this integration test class. I'm using this class
to validate that my much beloved post jobs endpoint works the way that I
might expect it to. This is a basic
three-step test setup. I'm going to create a
new API application. We'll talk a little bit about
what's under the hood of this API application
here in just a second. I'm setting up some data that
I'm going to be sending, which is this test job. I'm sending out what I expect
is to be the response body, which when we send a post
request with the job, we're going to get a
response containing the information for
that exact same job. Now, I want to act. I initialize an HTTP client associated
with my application. I send that post request
with the job that I created, and then I read the response
and assert on the status. I should be getting a
created status code, which is what our post
endpoint returns, and I should be getting
a string containing the JSON representation of the job that I tried to
insert into the database, so cool, nice test setup here. What is API application? Well, this integration
test is leveraging the web application
factory infrastructure. Here, I've got this API application, which is an implementation
of a web application factory with a little bit of bootstrapping
done under the hood, to support using an in-memory
database instead of the Azure SQL database you were talking about a little bit earlier. Also to support Azure AD, instead of supporting
Azure AD Authentication, we're actually using
a test OP handler. Instead of having to go to the Cloud and hit the intent and all that, I'm mocking out a little bit of the external factors
in my application. That's integration testing. Now, it's a little bit heavy duty. After all, you have to test
all of your middlewares and services are mock them out
if you're not able to. You're actually running
an application server in-memory in your app. Wonder if I could try something
a little bit more lightweight, and it turns out
that I can actually. We can do this by leveraging unit
testing for our application. I've gone ahead and I've
created a unit test class here. This unit test class is a
typical unit test class. I've done some stuff to set myself
up for success here though. I've created this job service, which contains all of the endpoints that I
initialized in my application. I've got get all jobs, which we've been talking
about quite a bit. We've got get job ID, create job. All of these have been
factored out into core method. All my business logic
is in this job service, and this is what I'm
going to be testing in my application. How do I test it? Well, pretty much the same way I showed you earlier
for the few changes. I'm going to instantiate
a job service instance, act by invoking the business
logic in the create job method. Then I'm going to assert on the result type that
is returned from that. We can see here that I'm
asserting that it's returning a minimal APIs
extensions result type with the status code that I expect, which is 201 Created, and the expected value. Pretty Nico? >> Yeah, but I don't think I've
seen that created site before. Do we sneak that in
right before release? >> You have got a
sharp eye, Stephen. Stephen is pointing out this minimal APIs
extensions results type, that I'm using to assert that my create job endpoint
return the right thing. It's not that our result
type that we were talking about a little bit earlier. It turns out I'm leveraging an
ecosystem library here to make it a little bit easier to test our endpoints that return
high salt objects. There were some gaps in
the initial high results API that we're planning on
addressing in DOTNet 7. This extensions library
takes care of that. That's why I'm leveraging it, makes my unit tests a
whole lot easier to write. Now you've set me on a
pretty good track Stephen, which is talking about the future
of minimal APIs and DotNet 7. We've talked about all of the things that minimal APIs currently do. OpenAPI support, Blazor
integration, Auth integration, and support for integration and unit testing that we're going
to be continuing to improve. We've got more coming
up in DotNet 7, and we want you to
help us build it out. The best way to do that
is to start playing with minimal APIs and using it
to build your applications. You can check out the
official minimal API stocks at the links here and check
out some of our sample apps, including the demo
code for this app, and share your feedback
with us on GitHub. With that, we're going to say
thank you for joining us. We hope you enjoy minimal APIs
and we'll take any questions. >> Thanks everyone. >> Good stuff. Thank you
very much for your time. My apologies from
saying your name wrong, Stephanie. A correction there. One quick quick question
in the time that we have here from Javier who is asking, does minimal API supports
versioning via attributes or other forms or what
are the options of different ways you could potentially
version one of these APIs? >> There's no special
built-in versioning support. You can add metadata using
those extension methods, that Sofy was demonstrating. You can of course prefix
your APIs how you choose. But this is the type of feedback
that we're looking for. If this is something that you want, please go to GitHub and also if you want to try
making your own library, we'd love to see what the
community has to offer. >> Cool. Good answer that
makes sense on its own. No real opinion about
versioning yet, but certainly it's early days. >> Right. >> Fantastic.