Updated on
Every service an ASP.NET Core Web API uses is registered in Program.cs before the application starts. Left alone, that file grows into a wall of registration calls that says nothing about what the application is.
Extension methods on IServiceCollection are the fix. Each one groups the registrations for a single concern behind a name, so Program.cs reads as a list of decisions rather than a list of calls. We build a ServiceExtensions class here and use it to configure CORS, and the parts that follow add their own registrations to the same class.
If you want to see all the basic instructions and complete navigation for this series, please check the Introduction page for this tutorial.
How Do We Create the Web API Project?
After we have finished creating and populating the database in SQL Server With ASP.NET Core: Creating the Database, we are going to create a Visual Studio project for the server part of our application.
Let’s open Visual Studio and create a new ASP.NET Core Web API project and in the next window name it AccountOwnerServer.
Then in the next window, we have to provide a couple of information:
- Framework – for this series, we are going to choose .NET 10.0, but you can choose any previous version as well if you want to
- Authentication type – we’ll leave it at None
- Configure for HTTPS – we will leave it checked
- We are not going to enable Docker
- Use controllers – we want to check this since we will use controllers in our API
- Enable OpenAPI support – we are going to uncheck this since we don’t want OpenAPI or the Swagger implementation now (depending on which .NET version you are using)
After the project creation, we are going to modify the launchSettings.json file which is quite an important file for the .NET configuration. Let’s change the applicationUrl property to a new value and the launchBrowser property to false, to prevent a web browser from starting when the project starts.
In the Solution Explorer, let’s expand the Properties and double-click on the launchSettings.json file:
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Both profiles run the project directly on Kestrel. For the sake of simplicity, we are going to use the http profile and http://localhost:5000 throughout this series, but the https profile is there whenever we want the HTTPS address on 5001 as well.
What Does Program.cs Do in a .NET Web API?
Program.cs is where an ASP.NET Core application is assembled and started. It does two things, in order, and the file is laid out to make the split visible.
First it builds. WebApplication.CreateBuilder(args) returns a builder, and every builder.Services.Add… call registers a service the application will be able to inject later. Nothing is running yet.
Then it runs. builder.Build() produces the application, each app.Use… call adds a step to the request pipeline, and app.Run() starts listening.
Everything above Build() is registration; everything below it is request handling. That single line is the seam, and most confusion about this file comes from missing it.
There is no Startup class and no Main method here. Top-level statements let the file be the program, and implicit usings supply the namespaces a web project always needs, so what remains on screen is only the decisions this application makes.
This is the file the template generates for us:
var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); var app = builder.Build(); // Configure the HTTP request pipeline. app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run();
For readers who last saw ASP.NET Core before minimal hosting, here is the shape this replaced, and it is not what we write in a new project today:
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
We can see three main changes:
- Top-level statements
- Implicit using directives
- And there is no usage of the Startup class
“Top-level statements” means the compiler generates the namespace, class, and method elements for the main program in our application. You can read more about this in our Top Level Statements article.
“Implicit using directives” means the compiler automatically adds a different set of using directives based on a project type, so we don’t have to do that manually. We can find those using directives in the generated file under obj/Debug/net10.0, named after our project with a .GlobalUsings.g.cs suffix. The folder name always tracks the target framework, so it follows the project rather than staying on one version.
Finally, we can see that now in the Program class, we can add services to the service collection right below the comment that states exactly that. In the Startup-class model that registration went in the ConfigureServices() method. Also, we have a section (also marked with a comment) where we can add different middleware components to the application’s pipeline. The Startup class had Configure() for that.
How Do Extension Methods Keep Program.cs Readable?
An extension method is a static method that is called as if it belonged to the type it extends. Marking the first parameter with this is the whole mechanism.
In a Web API that matters because service registration accumulates. Each feature needs a handful of calls on IServiceCollection, and after a few features Program.cs is a wall of registration with no structure.
Writing ConfigureCors(this IServiceCollection services) in a static class turns that handful into one named call. Program.cs then reads as a list of concerns: configure CORS, add the controllers, and build the application.
The grouping is the point, not the syntax. A reader scanning Program.cs sees what the application is made of; a reader who wants the detail opens ServiceExtensions.
Nothing is hidden by this. The same registrations run in the same order, one indirection further away.
Let’s create a new folder Extensions in the main project and a new class inside with the name ServiceExtensions. We are going to make that class static and it will consist of our service extension methods, so it has to live in a static class:
namespace AccountOwnerServer.Extensions
{
public static class ServiceExtensions
{
}
}
How Do We Configure CORS in ASP.NET Core?
CORS is the browser rule that stops a page on one origin from reading a response from another. The server decides which origins are allowed, and ASP.NET Core expresses that decision as a named policy.
Two steps register it. AddCors() defines the policy in the service collection, and UseCors() applies it in the request pipeline. Both are needed, and the pipeline call has to sit before authorization.
The policy here allows any origin, method, and header, which is right for a sample and wrong for anything else. In production we name the origins the client actually uses.
One combination is rejected before the application starts: any origin together with credentials. ASP.NET Core throws while building the policy, so the API does not run at all until the origins are listed explicitly.
This series needs CORS because the Angular client runs on a different port from the API, and a different port is a different origin.
Our article on enabling CORS in ASP.NET Core covers the subject on its own, and the one on why any-origin and credentials cannot be combined walks through the startup failure that combination produces. So, let’s add this code to the ServiceExtensions class:
public static void ConfigureCors(this IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
});
}
We are using the basic settings for adding CORS policy because for this project allowing any origin, method, and header is quite enough. But we can be more restrictive with those settings if we want. Instead of the AllowAnyOrigin() method that allows requests from any source, we could use the WithOrigins("http://www.something.com") method that will allow requests just from the specified source. Also, instead of AllowAnyMethod() allowing all HTTP methods, we can use the WithMethods("POST", "GET") one that will allow only specified HTTP methods. Furthermore, we can make the same changes for the AllowAnyHeader() method by using, for example, the WithHeaders("accept", "content-type") method to allow only specified headers.
To call this extension method, we are going to modify the Program class:
using AccountOwnerServer.Extensions;
using Microsoft.AspNetCore.HttpOverrides;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.ConfigureCors();
builder.Services.AddControllers();
var app = builder.Build();
// Configure the HTTP request pipeline.
// UseForwardedHeaders goes first: every component after it has to see the client's
// scheme and address, not the proxy's, or an HTTPS redirect behind a proxy loops.
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.All
});
if (!app.Environment.IsDevelopment())
app.UseHsts();
app.UseHttpsRedirection();
app.UseCors("CorsPolicy");
app.UseAuthorization();
app.MapControllers();
app.Run();
The ASP.NET Core middleware documentation states the same sequence: “CORS middleware (UseCors), authentication middleware (UseAuthentication), and authorization middleware (UseAuthorization) must appear in the order shown”.
There is one more call worth a word:
app.UseForwardedHeaders()rewrites the scheme and the client address of the current request from the proxy’s headers, which is what we need during a Linux deployment. Note that we register it beforeapp.UseHsts()andapp.UseHttpsRedirection(). Nginx forwards requests to Kestrel over plain HTTP, so until these headers are read, our app sees every request as HTTP. IfUseHttpsRedirection()runs first, it redirects requests that already arrived over HTTPS, and the browser ends up in a redirect loop.
In What Order Do Middleware Components Run?
Middleware runs in registration order, and each component decides whether the next one runs at all. The app.Use… calls in Program.cs are not configuration, they are the pipeline itself, written top to bottom.
That makes order behaviour. Moving one line changes what the application does, and the failures it produces look like bugs somewhere else entirely.
Three orderings carry most of the pain. Proxy headers are read first, or the application never learns the request arrived over HTTPS. Routing runs before anything that needs to know which endpoint was matched. Authentication precedes authorization, because deciding whether a caller may proceed requires knowing who they are.
Static files short-circuit. A matched file is served immediately and the rest of the pipeline is skipped, unless routing has already picked an endpoint for that path.
The table below is the order to start from. Departures from it are worth a comment in the code explaining why.
| Order | Middleware | Why it sits here |
|---|---|---|
| 1 | UseForwardedHeaders() | Rewrites the scheme and client IP from proxy headers. It must run before anything that reads the scheme or the client IP |
| 2 | UseExceptionHandler() / UseHsts() | Error handling wraps everything downstream; it can only catch what runs after it. In Development the framework adds the developer exception page here for us |
| 3 | UseHttpsRedirection() | Redirects plain HTTP away before any work is done on the request |
| 4 | UseStaticFiles() | Serves a matched file and short-circuits the rest of the pipeline. In minimal hosting the framework has already run routing above this line, so a path that has an endpoint goes to the endpoint and the file is not served |
| 5 | UseRouting() | Selects the endpoint. Anything that needs to know which endpoint was matched runs after this |
| 6 | UseCors() | Needs the matched endpoint to read its per-endpoint CORS policy, and must precede authorization |
| 7 | UseAuthentication() / UseAuthorization() | Establishes who the caller is, then whether they may proceed |
| 8 | MapControllers() | Runs the endpoint that routing selected |
Two of those rows are written out for reference rather than typed by us. In minimal hosting, WebApplication inserts routing at the very beginning of the pipeline and endpoint execution at the end, so our sample never calls UseRouting() and still routes correctly. Our sample does not register UseStaticFiles() either, because a controllers-only Web API has no wwwroot to serve. If we want to watch this pipeline from the inside, our article on logging from the Program class shows how to get messages out of it before the application is even running.
Conclusion
We now know how to modify the launchSettings.json file, what Program.cs actually does, how to group service registrations behind extension methods, and where each middleware component belongs in the pipeline.
From here, configuration itself is the natural next subject: where configuration values come from and how to go about binding configuration to strongly typed options.
Thank you for reading and do check out NLog in ASP.NET Core Web API: Logging to a File, in which we create our first service, the logger service, and use it for logging our messages. Two parts later we add the database, and the connection string the next parts need is worth a look before we get there.
The Ultimate ASP.NET Core Web API course builds the same project out with the configuration this series does not reach: environment-specific settings, options binding and validation, and the middleware a production API needs.
Tested with .NET 10.

How to configure Cors in .net 6
I don’t understand? This example is .NET 6, so do it exactly as it is shown in the article.
HI, I like your book on Ultimate Asp.net cor Web Api.
it’s very readable and straight to the point. the content is excellent and up to date.
I would like to know if a similar book will be released for Blazor web assembly or server
Hello Samuel. Thanks for the kind words. Since we already have a video course on Blazor WASM, I am not sure that we are going to create a book on the same topic. That said, I see you’ve used blazor discount to purchase the book, which means that you already have the video course. Is there something not fitting your needs inside the course when you are asking for the book on the same topic?
I got an error in Startup.cs.Please Solve This Problem.
I don’t see the entire code, but based on the error message, you are creating an extension method in nonstatic class, and that is not allowed.
I got an other error that is shown in the attached Image.
Thanks, Brother These Problems are solved.But I got an error in Startup.cs class. on Startup.
I got an error that is shown in the attached Image.
Well, your method is outside of the static class. It has to be inside. That’s why we have provided our source code so you can always compare it. It is a quicker way of solving problems rather than waiting for us to reply since sometimes we can’t reply right away.
Done Sir, Thanks a Lot.
Hello, I got an Error in ServicesExtension.cs on ConfigureCors and ConfigureIISIntegration.
public static void ConfigureCors(this IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy(“CorsPolicy”,
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
});
}
public static void ConfigureIISIntegration(this IServiceCollection services)
{
services.Configure<IISOptions>(options =>
{
});
}
.And Also I got an error when adding services.ConfigureCors(); and services.ConfigureIISIntegration(); to startup.cs
“Error CS7036 There is no argument given that corresponds to the required formal parameter”.
Thanks
/Danny
Hi, I have a problem with my project. I have written ConfigureCors according to you but there is nothing in Developer Tools->Network->Response Headers.
Thank you for your answer.
Hello Pavel. When configuring CORS you are allowing your client apps that are hosted on different URI to access your API. Since we consume this API with an Angular app that runs on different URI, we had to enable it. You can read more about CORS here: https://code-maze.com/enabling-cors-in-asp-net-core/
Hi
Do you have source codes on ASP.NET Core Web Api that I can download? It would be easier to read through your article with complete source codes.
Thanks for your great article – they answer lots of my questions confused me before!
Peter
https://github.com/CodeMazeBlog/.NET-Core-Series It is strange that we didn’t link it here. We did that though for the next article.
For IServiceCollection to be found, you need to add the following using-directive:
Perhaps it would be useful to mention it somewhere in the article? I’m very confused how nobody has stumbled upon any errors relating to this, 🤔
Or actually, maybe my editor was just being weird. After I tried removing the using-directive and hovering over IServiceCollection again, it actually suggested I add the using-directive. It didn’t do that at first.
Hello, Zero. Well, this is the main reason why we don’t specify using statements in our code samples. IDE should give you that option right away. When there is a need for specifying the using statement, we do that in the explanation.
thanks for the thorough explanations, great post!
You are very welcome. I hope you will find helpful the rest of the series as well.
I’m looking forward to going through all these posts you’ve written. Do you happen to have anything on web development, css styling in particular?
No, sorry. I have to stick to what I know the best 🙂
Np, thanks 🙂
My copy is saying I need to do something with the Startup class in Startup.cs. Says it must be defined as a non-generic static class but when I change to ‘public static class Startup’ then I get other errors on the page.
Never mind. I just backed everything out and tried again and it seems to be fine.
Hi Marinko, yes, this works for .NET Core 3.1 – now I’m trying with .NET 5.0. I’m using an Angular project to hit one end point in the API which, like I said, works with the .NET Core 3.1 project. I created a new .NET 5.0 project with your example and try to connect to an endpoint from the same Angular project and get a CORS error:
Access to XMLHttpRequest at ‘https://localhost:44342/register’ from origin ‘http://localhost:4200’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.
Any ideas what I can try? I spent a few hours on this and I can’t get it. Any help is much appreciated!
Hello Kevin. I must say this is really strange. Right after I red your comment, I took our .NET Core project and updated it to the version 5. Then started the Angular project and hit the action without a problem. Of course, I had to update the Pomelo version to preview 5.0 (it is in prerelease version) and had to change the UseMySql method a bit, because they changed the implementation a bit. But other than that, everything works as it was before the update. Your error states the CORS problem, but I am not really sure why is that. If you can’t work it out, you can send your server project to me (link it on GitHub) and once I find some time, I will look at it.
Hi Marinko, OK, I have it working – I guess I was mixing examples. I started a new 5.0 project and exactly followed your article above and it works, pretty much as you say in your comment. I do apologize for not starting a new project and closely following the article before messaging you.
What I was trying to get working yesterday is the Identity example from your series, “Introducing Identity to the ASP.NET Core Project”. (Is that working for you in 5.0?) That will be my next attempt – start a new 5.0 project and get the Identity example working with registration and login working from Angular. The database creating part (including the User object) was working perfectly. I was encountering the CORS problem when trying to hit the registration end point. Like I said, I’ll start over and see if I can get it to work.
Hi Kevin. I’m glad you have it solved. Regarding your question, yes it works with .Net 5. Pretty much everything from 3.1 works in .Net 5, there were no big code changes in version 5. Every project I updated from 3.1 to 5 was working without a single problem or with pretty small modifications. Just pay attention that article you mentioned is for MVC project. We also have Angular with ASP.NET Core Identity series, so this one is better for you to follow if you are working with Angular and Web API. You can find the series in the Guides/Security menu.
Noticing the differences between .NET Core 2 and 3. I do like the idea of service extension approach. But I did do a double take when
we were able to reference ‘AccountOwnerServer.Extensions’ and it’s seamless in Startup.cs ConfigureServices using one IServiceCollection object. Not complaining about relearning so long as its a smarter design.
Hello Brian. Well, I am a little bit struggling to undarstend why is this a problem for you. ASP.NET Core provides us with a single IServiceCollection object to use it for the service registration. All we do here is placing that logic in a separate class with extension methods to clear the ConfigureServices method. It has proven as a great solution in larg projects we have been working on. Especially, when you have to configure Swagger with authentication, Identity with so many rules, etc… As long as I am concerned, this would be my prefered way for service registration.
The application that I’m writing has stopped working correctly and I can’t put a finger on what went wrong.
Previously, it would load the configuration, show it was listening to the ips and ports and block the command prompt as it was running.
Now, it’s only showing:
Config file is: *
Connection String: server=localhost; database=…; userid=…; password=…
Then it exits code 0
Can you point me in a direction to see how to fix this?
Why are you using MVC? I thought this was an API for an angular front end?
Hello Alexander. No we are not using MVC this is the Web API project. As you said it is an API for an angular front end. I believe you saw the AddMVC and UseMVC methods and thus your question. But in .NET Core 2.2 this is the way to configure your project. In ASP.NET Core 3.0 you don’t have that. Instead we have AddControllers() and UseEndpoints. Of course, you could use AddMvc and UseMvc but for the API project, it is not recommended.
When I move my server off my development machine, when I need to access it via an ip address, does it still need to listen on localhost?
Please have look at our articles about iis or linux deployment. You will find them in this series as well. You will find there how to configure deployment environment.
I tried looking over it but couldn’t find anything that helps.
I’m not using IIS. I’m running ASP.NET as a local application. It’s not meant to be a website but a web-based interface.
When I try to access the IP address of the computer, it says it can’t be reached. Is there any configuration I need to set?
Figured it out by adding “.UseUrls(“http://localhost:5000”, “http://ipaddress:5000″)” to the kestrel.
Perfect !
Where do you set that you’re using the MySQL driver and not the MSSQL driver?
Hello Eran. In this article nowhere. But in the Repository article I have stated that we need to instal a third party library Pomelo.EntityFrameworkCore.MySql to enable communication between .net core and mysql. And then in Startup class I have added:
var connectionString = config["mysqlconnection:connectionString"];(o => o.UseMySql(connectionString)); services.AddDbContext
As you can see, I am using o.UseMySql method here.
Isn’t the MySQL library enough?
You can use either MySQL Connector or Pomelo. I have used Pomelo because I worked with it before and it has a great support for .net core.
Ok. Right now, I’m getting “MySql.Data.MySqlClient.MySqlException (0x80004005): Unable to connect to any of the specified MySQL hosts.”
I just tried to mess up my MySQL connection string, and I get the same error when executing any query. So it might be related to your connection string or for any reason your MySQL server refuses connection, I can’t be sure. Check your server name or username and password and check wether you have spaces between parameters…
I checked again and removed the port specification from the connection string. That worked.
I am glad I could help, if I helped at all. Nice to hear you solved the problem.
If you using this tutorial with VS2017 than the latest .NET SDK you can go with is v.2.2.107. It can be downloaded from here: https://dotnet.microsoft.com/download/dotnet-core/2.2#sdk-2.2.107
looks like there isn’t part 2 available anymore on the provided github repo link
Hi Marinko
I’m working with part 2 of your series of tutorials. I’m getting the error “The name does not exist in the current context” for the ForwardHeaders.all and Path variables in the startup.cs page. Please advise what I’m missing.
Thanks for your excellent tutorial.
Matty
Hi Matty.
I would really want to help you, but it would be much easier if you could send me your solution. Or you could download my solution (provided in the post) and compare for differences. Again, I want to help but right now (based on your error message) I can’t figure it out. Upload the solution and post a link here, then I am going to check it out and for sure find what is missing.
One more time, thank you very much for reading the posts.
Hi Marinko
Thanks for that speedy response.
I think I managed to sort the problem out. Two missing references at the top of the page, one to system.io and one to microsoft.aspnetcore.httpoverrides. Hope that’s all it is.
Regards
Matt
Excellent Matty, I am glad you could sort it out by yourself. I thought that could be the problem but wasn’t that much sure. About a quick response, well it was 7 hours late, so not that quick, but in my deffence I was sleeping and just woke up an hour ago 😀
All the best,
Marinko.
Hello, I got error when add services.ConfigureCors(); to startup.cs
“Error CS7036 There is no argument given that corresponds to the required formal parameter”
Thanks
/Danny
Hello Danny, thank you a lot for reading this post. About your error, please check your extension method ConfigureCors for the missing “this” keyword in front of your “IServiceCollection services” parameter.
I think that you wrote your method like this “public static void ConfigureCors(IServiceCollection services)”
but it needs to be like this “public static void ConfigureCors(this IServiceCollection services)”
When I remove “this” keyword I get the same error as you are. So just check ConfigureCors method.
Thanks Marinko! Yes It’s working now
You are welcome Danny. It’s my pleasure to help.
Hi Marinko, this was greate article that help me to learn .NET Core from beginning.
But i still get same error. Could you please help me?what i’ve missed?
Hello Peter. If you have the same error as Danny did “Error CS7036” with the extension method, then you are missing “this” keyword in your extension method. If not, then please give me more information about your error because I can’t help you otherwise.
Hi Marinko, now it works. Thanks. 🙂