I use Visual Studio Code because it's faster at startup and has a well integrated terminal with the PowerShell console. It is also easier to reproduce the commands by copy/paste than showing the steps to use the graphical UI.
To start a new ASP.NET Core project you need to have the .NET 6 SDK installed. Check if you have the latest version installed with: dotnet --version
, currently it's version 6.0.101 called .NET 6.0.
Create and start a project by using templates, here web
:
dotnet new web -o TemplateWeb
cd TemplateWeb
dotnet run
Other common templates are webapp webapi nunit classlib
. List all available templates with dotnet new
ASP.NET Backend
Create a webapi
solution with multiple projects and use MySql as database for Entity Framework:
dotnet new webapi -f net6.0 -o Teach.WebApi
dotnet new nunit -f net6.0 -o Teach.WebApi.Tests
dotnet new classlib -f net6.0 -o Teach.Business
dotnet new nunit -f net6.0 -o Teach.Business.Tests
dotnet new classlib -f net6.0 -o Teach.DataAccess
dotnet new classlib -f net6.0 -o Teach.Domain
cd Teach.WebApi
dotnet add reference ../Teach.Business/Teach.Business.csproj
dotnet add reference ../Teach.DataAccess/Teach.DataAccess.csproj
dotnet add package Pomelo.EntityFrameworkCore.MySql
// for IEmailSender
dotnet add package Microsoft.AspNetCore.Identity.UI --version 6.0.4
cd ../Teach.Business
dotnet add reference ../Teach.DataAccess/Teach.DataAccess.csproj
dotnet add reference ../Teach.Domain/Teach.Domain.csproj
dotnet add package AutoMapper.Extensions.Microsoft.DependencyInjection
cd ../Teach.DataAccess
dotnet add reference ../Teach.Domain/Teach.Domain.csproj
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Pomelo.EntityFrameworkCore.MySql
cd ../Teach.Domain
dotnet add package Microsoft.AspNetCore.Identity --version 2.2.0
cd ../Teach.Business.Tests
dotnet add reference ../Teach.Business/Teach.Business.csproj
dotnet add reference ../Teach.Domain/Teach.Domain.csproj
dotnet add package Moq --version 4.18.0
cd ../Teach.WebApi.Tests
dotnet add reference ../Teach.WebApi/Teach.WebApi.csproj
dotnet add reference ../Teach.Business/Teach.Business.csproj
dotnet add reference ../Teach.Domain/Teach.Domain.csproj
cd ..
dotnet new sln -n Teach
dotnet sln add Teach.Business/Teach.Business.csproj
dotnet sln add Teach.DataAccess/Teach.DataAccess.csproj
dotnet sln add Teach.Domain/Teach.Domain.csproj
dotnet sln add Teach.WebApi/Teach.WebApi.csproj
dotnet sln add Teach.Business.Tests/Teach.Business.Tests.csproj
dotnet sln add Teach.WebApi.Tests/Teach.WebApi.Tests.csproj
dotnet build
Alternative: dotnet add package MySql.Data.EntityFrameworkCore
dotnet tool install -g Microsoft.Tye --version "0.11.0-alpha.22111.1"
tye run
todo:
Install ASP.NET code generator for scaffolding code:
dotnet tool install -g dotnet-aspnet-codegenerator
Update ASP.NET code generator:
dotnet tool update -g dotnet-aspnet-codegenerator
dotnet aspnet-codegenerator
ASP.NET Web-API
Create a new project:
dotnet new web --name AuthorizationServer
Add dependencies:
cd AuthorizationServer
dotnet add package OpenIddict
dotnet add package OpenIddict.AspNetCore
dotnet add package OpenIddict.EntityFrameworkCore
dotnet add package Pomelo.EntityFrameworkCore.MySql
dotnet add package Microsoft.EntityFrameworkCore.Relational
Add ConnectionString
to AuthorizationServer/appsettings.json
:
{
"ConnectionStrings": {
"ConnectionString": "server=localhost;user id=wisecards;password=YouKnowPassword123;database=wisecards"
}
}
dotnet ef dbcontext scaffold "server=localhost;user id=wisecards;password=YouKnowPassword123;database=wisecards" "Pomelo.EntityFrameworkCore.MySql"
Add Kestrel DEV endpoint ports:
{
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://localhost:5000"
},
"Https": {
"Url": "https://localhost:7000"
}
}
}
}
Run:
dotnet run --project AuthorizationServer.csproj
Change ASP.NET Core Identity Table Naming
public class TeachDbContext : IdentityDbContext<User, UserUserRole, int>
{
// ...
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<User>().ToTable("Users").HasKey(x => x.Id);
builder.Entity<UserUserRole>().ToTable("UserUserRole").HasKey(x => x.Id);
builder.Entity<IdentityUserLogin<int>>().ToTable("UserLogins").HasKey(x => x.UserId);
builder.Entity<IdentityUserClaim<int>>().ToTable("UserClaims").HasKey(x => x.Id);
builder.Entity<IdentityRole>().ToTable("Roles").HasKey(x => x.Id);
builder.Entity<IdentityRoleClaim<int>>().ToTable("RoleClaims").HasKey(x => x.Id);
builder.Entity<IdentityUserRole<int>>().ToTable("UserRoles").HasKey(x => new { x.UserId, x.RoleId });
builder.Entity<IdentityUserToken<int>>().ToTable("UserTokens").HasKey(x => new { x.UserId, x.LoginProvider, x.Name });
}
}
Cross-Site Request Forgery
Add support for SPA in ASP.NET Core
Progarm.cs
var services = builder.Services;
services.AddMvcCore().AddViews();
services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
// ... after builder.Build();
var antiforgery = app.Services.GetRequiredService<IAntiforgery>();
app.Use((context, next) =>
{
var path = context.Request.Path.Value;
if (string.Equals(path, "/", StringComparison.OrdinalIgnoreCase) ||
string.Equals(path, "/index.html", StringComparison.OrdinalIgnoreCase))
{
var tokens = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken!, new CookieOptions() { HttpOnly = false });
}
return next(context);
});
In controllers:
[ValidateAntiForgeryToken]
public class AccountController : Controller
{
// ...
}
In SPA:
Get anti-forgerey-token:
GET https://tea.ch/
Grab the cookie 'XSRF-TOKEN' and for each subsequent request, put that in the header X-XSRF-TOKEN
.
Like the login-request:
POST https://tea.ch/account/login
Content-Type: application/json
X-XSRF-TOKEN: CfDJ8LJyEjnZE3tJsmURR5M-E9OYcbPWm6YTTbbqLbAgv7FQhSkhhj870sl9SvYwvWSZv6fiJWGR_ZiL18J-JkdnUmgnkJ_RtmrhVTaUugDJ1Tb0VCiAsCr6qt019UJqjq9gJ4jEX5m3RL9OypcuuDYNm8eYwwqiQWbSW71Lx0aG75xmCEyPH8a089L6bK3UOExA_A
{
"username": "5a",
"password": "1.12",
"returnUrl": "https://tea.ch"
}
Unit Test
Test a Controller using Moq to mock data-access.
using Microsoft.AspNetCore.Mvc;
using Moq;
using NUnit.Framework;
using System.Threading.Tasks;
using Wisecards.Business.Services;
using Wisecards.DataAccess;
using Wisecards.Domain;
using Wisecards.WebApi.Controllers;
namespace Wisecards.WebApi.Tests;
public class DecksControllerTest
{
private Mock<IDeckAccess> deckAccessMock = new Mock<IDeckAccess>();
[SetUp]
public void Setup()
{
}
[Test]
public async Task DeleteDeck()
{
// arrange
var deck = new Deck();
deckAccessMock.Setup(m => m.GetById(It.IsAny<int>())).Returns(Task.FromResult<Deck?>(deck));
deckAccessMock.Setup(m => m.Update(It.IsAny<Deck>())).Returns(Task.FromResult<int>(1));
var deckService = new DeckService(null!, deckAccessMock.Object);
var controller = new DecksController(deckService);
// act
var result = await controller.DeleteDeckById(1);
// assert
Assert.NotNull(result);
Assert.IsTrue(deck.IsDeleted);
Assert.AreEqual(202, (result?.Result as OkResult)?.StatusCode);
}
}
[1] https://github.com/openiddict/openiddict-core
[2] https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql
[3] https://docs.microsoft.com/en-us/aspnet/core/security/authentication/?view=aspnetcore-6.0