Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
332 changes: 332 additions & 0 deletions aspnetcore-webapi/BasicWebApiSeries/BasicWebApiSeries.sln

Large diffs are not rendered by default.

38 changes: 38 additions & 0 deletions aspnetcore-webapi/BasicWebApiSeries/Database/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Database

Part 1 of the ASP.NET Core Web API series. This folder holds the SQL Server schema the
rest of the series reads and writes, and nothing else: there is no .NET project here.

`init.sql` creates the `AccountOwner` database, the `Owner` and `Account` tables, the
foreign key between them, and the sample data. It is re-runnable.

## Windows, on LocalDB

```
sqlcmd -S "(localdb)\MSSQLLocalDB" -i init.sql
```

LocalDB comes with the Visual Studio Installer's *Data storage and processing* workload
(and with SQL Server Express media). It is not part of the .NET SDK.

## Anywhere, in a container

```
docker compose up -d
sqlcmd -S localhost -U sa -P "YourStrong!Passw0rd" -C -i init.sql
```

`-C` trusts the container's self-signed certificate, which ODBC Driver 18 and the newer
`sqlcmd` builds require.

## Why the ids are fixed

The sample data hardcodes its GUIDs. Later parts of the series call endpoints with these
exact ids, so a reader who generates fresh ones cannot follow along.

## Why nothing calls EnsureCreated()

The schema comes from this script, with explicit lengths (`NVARCHAR(60)`, `NVARCHAR(100)`,
`NVARCHAR(45)`) and `DATE` columns. EF Core's default model for the same entities would
produce `nvarchar(max)` and `datetime2` instead. The script is the only thing that creates
the schema, in every folder of this series.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
services:
sqlserver:
image: mcr.microsoft.com/mssql/server:2022-latest
container_name: accountowner-sqlserver
environment:
ACCEPT_EULA: "Y"
MSSQL_SA_PASSWORD: "YourStrong!Passw0rd"
ports:
- "1433:1433"
76 changes: 76 additions & 0 deletions aspnetcore-webapi/BasicWebApiSeries/Database/init.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
AccountOwner sample database - ASP.NET Core Web API series, part 1.

Creates the AccountOwner database, the Owner and Account tables, the foreign key
between them, and the sample data the rest of the series calls its endpoints with.

The script is re-runnable: it drops the tables before it creates them, so running
it a second time rebuilds the schema instead of failing on names that already exist.

LocalDB (Windows):
sqlcmd -S "(localdb)\MSSQLLocalDB" -i init.sql

Container (any OS), started with the docker-compose.yml beside this file:
sqlcmd -S localhost -U sa -P "<YourStrong!Passw0rd>" -C -i init.sql
*/

IF DB_ID('AccountOwner') IS NULL
CREATE DATABASE [AccountOwner];
GO

USE [AccountOwner];
GO

-- Account first: it holds the foreign key, so it has to go before its parent.
DROP TABLE IF EXISTS [dbo].[Account];
DROP TABLE IF EXISTS [dbo].[Owner];
GO

CREATE TABLE [dbo].[Owner]
(
[OwnerId] UNIQUEIDENTIFIER NOT NULL,
[Name] NVARCHAR(60) NOT NULL,
[DateOfBirth] DATE NOT NULL,
[Address] NVARCHAR(100) NOT NULL,
CONSTRAINT [PK_Owner] PRIMARY KEY CLUSTERED ([OwnerId])
);
GO

CREATE TABLE [dbo].[Account]
(
[AccountId] UNIQUEIDENTIFIER NOT NULL,
[DateCreated] DATE NOT NULL,
[AccountType] NVARCHAR(45) NOT NULL,
[OwnerId] UNIQUEIDENTIFIER NOT NULL,
CONSTRAINT [PK_Account] PRIMARY KEY CLUSTERED ([AccountId]),
CONSTRAINT [FK_Account_Owner] FOREIGN KEY ([OwnerId])
REFERENCES [dbo].[Owner] ([OwnerId])
ON UPDATE CASCADE
ON DELETE NO ACTION
);
GO

CREATE INDEX [IX_Account_OwnerId] ON [dbo].[Account] ([OwnerId]);
GO

-- Owners first. Every Account row names an owner, and the foreign key rejects a row
-- whose owner does not exist yet.
INSERT INTO [dbo].[Owner] ([OwnerId], [Name], [DateOfBirth], [Address])
VALUES
('24fd81f8-d58a-4bcc-9f35-dc6cd5641906', N'John Keen', '1980-12-05', N'61 Wellfield Road'),
('261e1685-cf26-494c-b17c-3546e65f5620', N'Anna Bosh', '1974-11-14', N'27 Colored Row'),
('a3c1880c-674c-4d18-8f91-5d3608a2c937', N'Sam Query', '1990-04-22', N'91 Western Roads'),
('f98e4d74-0f68-4aac-89fd-047f1aaca6b6', N'Martin Miller', '1983-05-21', N'3 Edgar Buildings');
GO

INSERT INTO [dbo].[Account] ([AccountId], [DateCreated], [AccountType], [OwnerId])
VALUES
('03e91478-5608-4132-a753-d494dafce00b', '2003-12-15', N'Domestic', 'f98e4d74-0f68-4aac-89fd-047f1aaca6b6'),
('356a5a9b-64bf-4de0-bc84-5395a1fdc9c4', '1996-02-15', N'Domestic', '261e1685-cf26-494c-b17c-3546e65f5620'),
('371b93f2-f8c5-4a32-894a-fc672741aa5b', '1999-05-04', N'Domestic', '24fd81f8-d58a-4bcc-9f35-dc6cd5641906'),
('670775db-ecc0-4b90-a9ab-37cd0d8e2801', '1999-12-21', N'Savings', '24fd81f8-d58a-4bcc-9f35-dc6cd5641906'),
('a3fbad0b-7f48-4feb-8ac0-6d3bbc997bfc', '2010-05-28', N'Domestic', 'a3c1880c-674c-4d18-8f91-5d3608a2c937'),
('aa15f658-04bb-4f73-82af-82db49d0fbef', '1999-05-12', N'Foreign', '24fd81f8-d58a-4bcc-9f35-dc6cd5641906'),
('c6066eb0-53ca-43e1-97aa-3c2169eec659', '1996-02-16', N'Foreign', '261e1685-cf26-494c-b17c-3546e65f5620'),
('eccadf79-85fe-402f-893c-32d3f03ed9b1', '2010-06-20', N'Foreign', 'a3c1880c-674c-4d18-8f91-5d3608a2c937');
GO
37 changes: 37 additions & 0 deletions aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.1.32210.238
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AccountOwnerServer", "AccountOwnerServer\AccountOwnerServer.csproj", "{0213FB50-5C53-4C34-8B2C-E515C7DC2579}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Contracts", "Contracts\Contracts.csproj", "{B731D3C2-9A89-40E4-B96B-AA300B4195BB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoggerService", "LoggerService\LoggerService.csproj", "{7CEC76F5-80AF-4B37-B6F1-94A743E5C748}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0213FB50-5C53-4C34-8B2C-E515C7DC2579}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0213FB50-5C53-4C34-8B2C-E515C7DC2579}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0213FB50-5C53-4C34-8B2C-E515C7DC2579}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0213FB50-5C53-4C34-8B2C-E515C7DC2579}.Release|Any CPU.Build.0 = Release|Any CPU
{B731D3C2-9A89-40E4-B96B-AA300B4195BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B731D3C2-9A89-40E4-B96B-AA300B4195BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B731D3C2-9A89-40E4-B96B-AA300B4195BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B731D3C2-9A89-40E4-B96B-AA300B4195BB}.Release|Any CPU.Build.0 = Release|Any CPU
{7CEC76F5-80AF-4B37-B6F1-94A743E5C748}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7CEC76F5-80AF-4B37-B6F1-94A743E5C748}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7CEC76F5-80AF-4B37-B6F1-94A743E5C748}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7CEC76F5-80AF-4B37-B6F1-94A743E5C748}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F8E1F895-37AB-455B-8D05-ED495BE48F88}
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\LoggerService\LoggerService.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Contracts;
using Microsoft.AspNetCore.Mvc;

namespace AccountOwnerServer.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly ILoggerManager _logger;

public WeatherForecastController(ILoggerManager logger)
{
_logger = logger;
}

// One request produces one line per level in logs/<date>_logfile.txt.
[HttpGet]
public IEnumerable<string> Get()
{
_logger.LogInfo("Here is info message from the controller.");
_logger.LogDebug("Here is debug message from the controller.");
_logger.LogWarn("Here is warn message from the controller.");
_logger.LogError("Here is error message from the controller.");

return ["value1", "value2"];
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Microsoft.AspNetCore.Mvc;

namespace AccountOwnerServer.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastWithILoggerController : ControllerBase
{
private readonly ILogger<WeatherForecastWithILoggerController> _logger;

public WeatherForecastWithILoggerController(ILogger<WeatherForecastWithILoggerController> logger)
{
_logger = logger;
}

// The platform way: no registration, and the message is a template with a named
// placeholder. WeatherForecastController takes the ILoggerManager wrapper instead,
// and both reach the same file because NLog is registered as a provider.
[HttpGet]
public IEnumerable<string> Get()
{
_logger.LogInformation("Serving {Count} forecast values", 2);

return ["value1", "value2"];
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Contracts;
using LoggerService;

namespace AccountOwnerServer.Extensions
{
public static class ServiceExtensions
{
public static void ConfigureCors(this IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
});
}

public static void ConfigureLoggerService(this IServiceCollection services)
{
services.AddSingleton<ILoggerManager, LoggerManager>();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using AccountOwnerServer.Extensions;
using Microsoft.AspNetCore.HttpOverrides;
using NLog.Web;

var builder = WebApplication.CreateBuilder(args);

// NLog is registered as a logging provider, so ILogger<T> reaches the file targets in
// nlog.config as well as ILoggerManager. NLog finds nlog.config in the output directory
// on its own; there is nothing to load by hand.
builder.Logging.ClearProviders();
builder.Host.UseNLog();

builder.Services.ConfigureCors();
builder.Services.ConfigureLoggerService();

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();
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"$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"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace AccountOwnerServer
{
public class WeatherForecast
{
public DateTime Date { get; set; }

public int TemperatureC { get; set; }

public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

public string? Summary { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
autoReload="true">

<targets>
<target name="logfile" xsi:type="File"
fileName="logs/${shortdate}_logfile.txt"
layout="${longdate} ${level:uppercase=true} ${message}"/>
</targets>

<rules>
<logger name="*" minlevel="Debug" writeTo="logfile" />
</rules>
</nlog>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Contracts
{
public interface ILoggerManager
{
void LogInfo(string message);
void LogWarn(string message);
void LogDebug(string message);
void LogError(string message);
}
}
Loading
Loading