├── DemoApi ├── Demo.txt ├── appsettings.Development.json ├── WeatherForecast.cs ├── Data │ └── DefaultDbContext.cs ├── appsettings.json ├── Entities │ ├── Post.cs │ └── User.cs ├── Controllers │ ├── DemoController.cs │ ├── ErrorsController.cs │ ├── TasksController.cs │ ├── StatusController.cs │ └── UsersController.cs ├── DemoApi.csproj ├── Core │ ├── ClaimsTransformationService.cs │ └── UserService.cs ├── Properties │ └── launchSettings.json ├── Program.cs └── Auth │ └── BasicAuthenticationHandler.cs ├── .gitignore ├── .dockerignore ├── Dockerfile ├── DemoApi.sln └── cloudbuild.yaml /DemoApi/Demo.txt: -------------------------------------------------------------------------------- 1 | 1 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | /packages/ 4 | riderModule.iml 5 | /_ReSharper.Caches/ 6 | .idea -------------------------------------------------------------------------------- /DemoApi/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /DemoApi/WeatherForecast.cs: -------------------------------------------------------------------------------- 1 | namespace DemoApi; 2 | 3 | public class WeatherForecast 4 | { 5 | public DateOnly Date { get; set; } 6 | 7 | public int TemperatureC { get; set; } 8 | 9 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); 10 | 11 | public string? Summary { get; set; } 12 | } -------------------------------------------------------------------------------- /DemoApi/Data/DefaultDbContext.cs: -------------------------------------------------------------------------------- 1 | using DemoApi.Entities; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace DemoApi.Data; 5 | 6 | public class DefaultDbContext : DbContext 7 | { 8 | public DbSet Users { get; set; } 9 | 10 | public DefaultDbContext(DbContextOptions options) : base(options) 11 | { 12 | 13 | } 14 | } -------------------------------------------------------------------------------- /DemoApi/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*", 9 | "ConnectionStrings": { 10 | "DemoContext": "Host=localhost;Port=5432;Database=demodatabase;Username=postgres;Password=DemoAdmin123" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.dockerignore 2 | **/.env 3 | **/.git 4 | **/.gitignore 5 | **/.project 6 | **/.settings 7 | **/.toolstarget 8 | **/.vs 9 | **/.vscode 10 | **/.idea 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /DemoApi/Entities/Post.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace DemoApi.Entities; 5 | 6 | [Table("Post")] 7 | public class Post 8 | { 9 | [Key] 10 | public int Id { get; set; } 11 | 12 | public int UserId { get; set; } 13 | 14 | [Required] 15 | [MaxLength(4000)] 16 | public string Content { get; set; } 17 | 18 | [Required] 19 | public DateTime PostDate { get; set; } 20 | 21 | public User User { get; set; } 22 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base 2 | WORKDIR /app 3 | EXPOSE 8080 4 | EXPOSE 443 5 | 6 | FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build 7 | WORKDIR /src 8 | COPY ["DemoApi/DemoApi.csproj", "DemoApi/"] 9 | RUN dotnet restore "DemoApi/DemoApi.csproj" 10 | COPY . . 11 | WORKDIR "/src/DemoApi" 12 | RUN dotnet build "DemoApi.csproj" -c Release -o /app/build 13 | 14 | FROM build AS publish 15 | RUN dotnet publish "DemoApi.csproj" -c Release -o /app/publish 16 | 17 | FROM base AS final 18 | ENV ASPNETCORE_URLS=http://+:8080 19 | WORKDIR /app 20 | COPY --from=publish /app/publish . 21 | ENTRYPOINT ["dotnet", "DemoApi.dll"] 22 | -------------------------------------------------------------------------------- /DemoApi/Entities/User.cs: -------------------------------------------------------------------------------- 1 | namespace DemoApi.Entities; 2 | 3 | using System; 4 | using System.ComponentModel.DataAnnotations; 5 | using System.ComponentModel.DataAnnotations.Schema; 6 | 7 | [Table("User", Schema = "public")] 8 | public class User 9 | { 10 | [Key] 11 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 12 | public int Id { get; set; } 13 | 14 | [Required] 15 | [MaxLength(100)] 16 | public string Name { get; set; } 17 | 18 | [Required] 19 | [MaxLength(255)] 20 | [EmailAddress] 21 | public string Email { get; set; } 22 | 23 | [Required] 24 | [DataType(DataType.DateTime)] 25 | public DateTime DateOfBirth { get; set; } 26 | 27 | public List Posts { get; set; } 28 | } 29 | -------------------------------------------------------------------------------- /DemoApi.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DemoApi", "DemoApi\DemoApi.csproj", "{CA55409D-D6E6-4946-82D0-6F21D3AFF065}" 4 | EndProject 5 | Global 6 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 7 | Debug|Any CPU = Debug|Any CPU 8 | Release|Any CPU = Release|Any CPU 9 | EndGlobalSection 10 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 11 | {CA55409D-D6E6-4946-82D0-6F21D3AFF065}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 12 | {CA55409D-D6E6-4946-82D0-6F21D3AFF065}.Debug|Any CPU.Build.0 = Debug|Any CPU 13 | {CA55409D-D6E6-4946-82D0-6F21D3AFF065}.Release|Any CPU.ActiveCfg = Release|Any CPU 14 | {CA55409D-D6E6-4946-82D0-6F21D3AFF065}.Release|Any CPU.Build.0 = Release|Any CPU 15 | EndGlobalSection 16 | EndGlobal 17 | -------------------------------------------------------------------------------- /cloudbuild.yaml: -------------------------------------------------------------------------------- 1 | steps: 2 | - name: gcr.io/cloud-builders/docker 3 | args: 4 | - build 5 | - '-t' 6 | - '$_AR_HOSTNAME/$PROJECT_ID/$_AR_REPO/$_SERVICE_NAME:$COMMIT_SHA' 7 | - . 8 | - name: gcr.io/cloud-builders/docker 9 | args: 10 | - push 11 | - '$_AR_HOSTNAME/$PROJECT_ID/$_AR_REPO/$_SERVICE_NAME:$COMMIT_SHA' 12 | - name: gcr.io/google.com/cloudsdktool/cloud-sdk 13 | args: 14 | - run 15 | - deploy 16 | - $_SERVICE_NAME 17 | - '--image' 18 | - '$_AR_HOSTNAME/$PROJECT_ID/$_AR_REPO/$_SERVICE_NAME:$COMMIT_SHA' 19 | - '--region' 20 | - $_DEPLOY_REGION 21 | - '--platform' 22 | - $_PLATFORM 23 | entrypoint: gcloud 24 | timeout: 1200s 25 | images: 26 | - '$_AR_HOSTNAME/$PROJECT_ID/$_AR_REPO/$_SERVICE_NAME:$COMMIT_SHA' 27 | options: 28 | logging: CLOUD_LOGGING_ONLY -------------------------------------------------------------------------------- /DemoApi/Controllers/DemoController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.Mvc; 3 | 4 | namespace DemoApi.Controllers; 5 | 6 | [Route("api/[controller]")] 7 | [ApiController] 8 | [Authorize] 9 | public class DemoController : ControllerBase 10 | { 11 | [HttpGet(Name = "Demo")] 12 | public async Task Get() 13 | { 14 | return Ok("Hello from the Demo API"); 15 | } 16 | 17 | [HttpGet("admin", Name = "AdminDemo")] 18 | [AllowAnonymous] 19 | public async Task GetAdminProtected() 20 | { 21 | return Ok("Hello from the Admin Only route"); 22 | } 23 | 24 | [HttpGet("user", Name = "UserDemo")] 25 | [Authorize(Roles = "User")] 26 | public async Task GetUserProtected() 27 | { 28 | return Ok("Hello from the User Route"); 29 | } 30 | } -------------------------------------------------------------------------------- /DemoApi/Controllers/ErrorsController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace DemoApi.Controllers 4 | { 5 | [Route("api/[controller]")] 6 | [ApiController] 7 | public class ErrorsController : ControllerBase 8 | { 9 | [HttpGet("401", Name = "Unauthorized")] 10 | public IActionResult UnAuthorized401() 11 | { 12 | return Unauthorized(); 13 | } 14 | 15 | [HttpGet("403", Name = "Forbidden")] 16 | public IActionResult Forbidden403() 17 | { 18 | return Forbid(); 19 | } 20 | 21 | [HttpGet("404", Name = "NotFound")] 22 | public IActionResult NotFound404() 23 | { 24 | return StatusCode(404); 25 | } 26 | 27 | [HttpGet("500", Name = "InternalServerError")] 28 | public IActionResult InternalServerError500() 29 | { 30 | return StatusCode(StatusCodes.Status500InternalServerError); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /DemoApi/DemoApi.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | Linux 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | .dockerignore 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /DemoApi/Core/ClaimsTransformationService.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | using Microsoft.AspNetCore.Authentication; 3 | 4 | namespace DemoApi.Core; 5 | 6 | public class ClaimsTransformationService : IClaimsTransformation 7 | { 8 | private readonly UserService userService; 9 | 10 | public ClaimsTransformationService(UserService userService) 11 | { 12 | this.userService = userService; 13 | } 14 | 15 | public async Task TransformAsync(ClaimsPrincipal principal) 16 | { 17 | if (principal.Identity?.IsAuthenticated != true) 18 | { 19 | return principal; 20 | } 21 | 22 | var userId = principal.FindFirstValue(ClaimTypes.NameIdentifier); 23 | 24 | var roles = await userService.UserRoles(userId); 25 | 26 | if (roles.Count == 0) 27 | { 28 | return principal; 29 | } 30 | 31 | foreach (var role in roles) 32 | { 33 | if (principal.HasClaim(ClaimTypes.Role, role)) 34 | { 35 | continue; 36 | } 37 | ((ClaimsIdentity)principal.Identity).AddClaim(new Claim(ClaimTypes.Role, role)); 38 | } 39 | 40 | return principal; 41 | } 42 | } -------------------------------------------------------------------------------- /DemoApi/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:10302", 8 | "sslPort": 44302 9 | } 10 | }, 11 | "profiles": { 12 | "http": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "launchUrl": "swagger", 17 | "applicationUrl": "http://localhost:5012", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | }, 22 | "https": { 23 | "commandName": "Project", 24 | "dotnetRunMessages": true, 25 | "launchBrowser": true, 26 | "launchUrl": "swagger", 27 | "applicationUrl": "https://localhost:7276;http://localhost:5012", 28 | "environmentVariables": { 29 | "ASPNETCORE_ENVIRONMENT": "Development" 30 | } 31 | }, 32 | "IIS Express": { 33 | "commandName": "IISExpress", 34 | "launchBrowser": true, 35 | "launchUrl": "swagger", 36 | "environmentVariables": { 37 | "ASPNETCORE_ENVIRONMENT": "Development" 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /DemoApi/Controllers/TasksController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace DemoApi.Controllers; 4 | 5 | [Route("api/[controller]")] 6 | [ApiController] 7 | public class TasksController : ControllerBase 8 | { 9 | [HttpGet(Name = "WhenAllVsParallel")] 10 | public async Task Test1() 11 | { 12 | var ids = Enumerable.Range(1, 10).ToList(); 13 | 14 | await WhenAll(ids); 15 | 16 | await ParallelForeach(ids); 17 | 18 | 19 | return Ok(new { Message = "Test1 completed" }); 20 | } 21 | 22 | private async Task Process(int id) 23 | { 24 | await Task.Delay(2000); 25 | Console.WriteLine($"Processed id: {id}"); 26 | } 27 | 28 | // Good option for batches of I/O that WON'T throttle/limit the destination (api, db, etc). 29 | private async Task WhenAll(List ids) 30 | { 31 | Console.WriteLine("Before WhenAll: " + DateTime.Now); 32 | await Task.WhenAll(ids.Select(id => Process(id))); 33 | Console.WriteLine("After WhenAll: " + DateTime.Now); 34 | } 35 | 36 | // Good option for processing that COULD throttle/limit the destination 37 | // Also good for light/medium CPU bound in small batches 38 | private async Task ParallelForeach(List ids) 39 | { 40 | // Parallel.ForEach 41 | Console.WriteLine("Before ParallelForeach: " + DateTime.Now); 42 | ParallelOptions options = new() 43 | { 44 | MaxDegreeOfParallelism = 2 45 | }; 46 | 47 | await Parallel.ForEachAsync(ids, options, async (id, token) => 48 | { 49 | await Process(id); 50 | }); 51 | Console.WriteLine("After ParallelForeach: " + DateTime.Now); 52 | } 53 | } -------------------------------------------------------------------------------- /DemoApi/Core/UserService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Caching.Memory; 2 | 3 | namespace DemoApi.Core; 4 | 5 | public class UserService 6 | { 7 | private readonly IMemoryCache cache; 8 | 9 | public UserService(IMemoryCache cache) 10 | { 11 | this.cache = cache; 12 | } 13 | 14 | // non-working demo just to show how to remove the cache 15 | public void SetRoles(string email, List roles) 16 | { 17 | // go update the database 18 | cache.Remove("UserRoles" + email); 19 | } 20 | 21 | public async Task> UserRoles(string email) 22 | { 23 | var roles = cache.Get>("UserRoles" + email); 24 | 25 | if (roles != null) 26 | { 27 | Console.WriteLine("Roles found in cache."); 28 | return roles; 29 | } 30 | 31 | Console.WriteLine("Roles not found in cache. Fetching from database."); 32 | roles = await DbCallGetRoles(email); 33 | 34 | cache.Set("UserRoles" + email, roles, TimeSpan.FromHours(5)); 35 | Console.WriteLine("Roles added to cache."); 36 | 37 | return roles; 38 | } 39 | 40 | async Task> DbCallGetRoles(string email) 41 | { 42 | var emailRoles = new Dictionary>() 43 | { 44 | { "test@fake.com", ["Admin"] }, 45 | { "test2@fake.com", ["User", "Publisher"] }, 46 | { "example@sample.com", ["Editor", "Contributor"] }, 47 | { "user3@domain.com", ["Viewer"] }, 48 | { "admin@example.com", ["Admin", "Editor", "User"] } 49 | }; 50 | 51 | // Simulate DB call delay 52 | await Task.Delay(200); 53 | 54 | return emailRoles.TryGetValue(email, out var roles) ? roles : new List(); 55 | } 56 | } -------------------------------------------------------------------------------- /DemoApi/Controllers/StatusController.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | using Microsoft.AspNetCore.Authorization; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | namespace DemoApi.Controllers 6 | { 7 | [Route("api/[controller]")] 8 | [ApiController] 9 | public class StatusController : ControllerBase 10 | { 11 | [HttpGet(Name = "Status")] 12 | public ActionResult Get() 13 | { 14 | // Returning anonymous object. You can return anything you want. 15 | var response = new 16 | { 17 | Message = "All good!", 18 | ServerTime = DateTime.Now 19 | }; 20 | 21 | return Ok(response); 22 | } 23 | 24 | [HttpGet("db", Name = "DbStatus")] 25 | public ActionResult DbStatus() 26 | { 27 | // Here you would make a call to your database. 28 | // I like to just select the date: "select getdate()" 29 | var date = DateTime.Now; // Actually call the DB here... 30 | 31 | // If DB call is successful: 32 | var response = new 33 | { 34 | Message = "DB is up!", 35 | ServerTime = DateTime.Now, 36 | DatabaseTime = date 37 | }; 38 | 39 | return Ok(response); 40 | } 41 | 42 | [Authorize(Roles = "Admin")] 43 | [HttpGet("auth", Name = "AuthStatus")] 44 | public ActionResult AuthStatus() 45 | { 46 | var username = HttpContext.User.Claims 47 | .FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)? 48 | .Value; 49 | // If it even gets to here it means auth is working because of the [Authorize] tag 50 | var response = new 51 | { 52 | Message = "Auth is up!", 53 | ServerTime = DateTime.Now, 54 | Username = username 55 | }; 56 | 57 | return Ok(response); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /DemoApi/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using DemoApi.Auth; 3 | using DemoApi.Core; 4 | using DemoApi.Data; 5 | using Microsoft.AspNetCore.Authentication; 6 | using Microsoft.EntityFrameworkCore; 7 | using Microsoft.OpenApi.Models; 8 | 9 | var builder = WebApplication.CreateBuilder(args); 10 | 11 | // Add services to the container. 12 | 13 | builder.Services.AddDbContext(options => 14 | { 15 | options.UseNpgsql(builder.Configuration.GetConnectionString("DemoContext")); 16 | }); 17 | 18 | builder.Services.AddControllers() 19 | .AddJsonOptions(o => {o.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;});; 20 | // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle 21 | builder.Services.AddEndpointsApiExplorer(); 22 | builder.Services.AddSwaggerGen(c => 23 | { 24 | c.SwaggerDoc("v1", new OpenApiInfo { Title = "Demo API", Version = "v1" }); 25 | c.AddSecurityDefinition("basic", new OpenApiSecurityScheme 26 | { 27 | Name = "Authorization", 28 | Type = SecuritySchemeType.Http, 29 | Scheme = "basic", 30 | In = ParameterLocation.Header, 31 | Description = "Basic Authorization header." 32 | }); 33 | c.AddSecurityRequirement(new OpenApiSecurityRequirement 34 | { 35 | { 36 | new OpenApiSecurityScheme 37 | { 38 | Reference = new OpenApiReference 39 | { 40 | Type = ReferenceType.SecurityScheme, 41 | Id = "basic" 42 | } 43 | }, 44 | new string[] {} 45 | } 46 | }); 47 | }); 48 | 49 | builder.Services.AddScoped(); 50 | builder.Services.AddMemoryCache(); 51 | 52 | builder.Services.AddAuthentication("BasicAuthentication") 53 | .AddScheme("BasicAuthentication", null); 54 | 55 | builder.Services.AddTransient(); 56 | 57 | var app = builder.Build(); 58 | 59 | // Configure the HTTP request pipeline. 60 | 61 | app.UseSwagger(); 62 | app.UseSwaggerUI(); 63 | 64 | app.UseHttpsRedirection(); 65 | 66 | app.UseAuthorization(); 67 | 68 | app.MapControllers(); 69 | 70 | app.Run(); -------------------------------------------------------------------------------- /DemoApi/Auth/BasicAuthenticationHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | using System.Text; 3 | using System.Text.Encodings.Web; 4 | using Microsoft.AspNetCore.Authentication; 5 | using Microsoft.Extensions.Options; 6 | 7 | namespace DemoApi.Auth; 8 | 9 | public class BasicAuthenticationHandler : AuthenticationHandler 10 | { 11 | public BasicAuthenticationHandler( 12 | IOptionsMonitor options, 13 | ILoggerFactory logger, 14 | UrlEncoder encoder, 15 | ISystemClock clock) 16 | : base(options, logger, encoder, clock) 17 | { 18 | } 19 | 20 | protected override async Task HandleAuthenticateAsync() 21 | { 22 | if (!Request.Headers.ContainsKey("Authorization")) 23 | { 24 | return AuthenticateResult.Fail("Unauthorized"); 25 | } 26 | 27 | string authorizationHeader = Request.Headers["Authorization"]; 28 | if (string.IsNullOrEmpty(authorizationHeader)) 29 | { 30 | return AuthenticateResult.Fail("Unauthorized"); 31 | } 32 | 33 | if (!authorizationHeader.StartsWith("basic ", StringComparison.OrdinalIgnoreCase)) 34 | { 35 | return AuthenticateResult.Fail("Unauthorized"); 36 | } 37 | 38 | var token = authorizationHeader.Substring(6); 39 | var credentialAsString = Encoding.UTF8.GetString(Convert.FromBase64String(token)); 40 | 41 | var credentials = credentialAsString.Split(":"); 42 | if (credentials?.Length != 2) 43 | { 44 | return AuthenticateResult.Fail("Unauthorized"); 45 | } 46 | 47 | var username = credentials[0]; 48 | var password = credentials[1]; 49 | 50 | if (username != "test@fake.com" && password != "subscribe") 51 | { 52 | return AuthenticateResult.Fail("Authentication failed"); 53 | } 54 | 55 | var claims = new[] 56 | { 57 | new Claim(ClaimTypes.NameIdentifier, username) 58 | }; 59 | var identity = new ClaimsIdentity(claims, "Basic"); 60 | var claimsPrincipal = new ClaimsPrincipal(identity); 61 | return AuthenticateResult.Success(new AuthenticationTicket(claimsPrincipal, Scheme.Name)); 62 | } 63 | } -------------------------------------------------------------------------------- /DemoApi/Controllers/UsersController.cs: -------------------------------------------------------------------------------- 1 | using DemoApi.Core; 2 | using DemoApi.Data; 3 | using DemoApi.Entities; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.EntityFrameworkCore; 6 | 7 | namespace DemoApi.Controllers; 8 | 9 | [Route("api/[controller]")] 10 | [ApiController] 11 | public class UsersController : ControllerBase 12 | { 13 | private readonly DefaultDbContext context; 14 | private readonly UserService userService; 15 | 16 | public UsersController(DefaultDbContext context, UserService userService) 17 | { 18 | this.context = context; 19 | this.userService = userService; 20 | } 21 | 22 | [HttpGet(Name = "AllUsers")] 23 | public async Task Get( 24 | string name = "", 25 | DateTime? birthStart = null, 26 | DateTime? birthEnd = null, 27 | int page = 1, 28 | int pageSize = 50 29 | ) 30 | { 31 | var query = context.Users.AsQueryable(); 32 | 33 | if (!string.IsNullOrEmpty(name)) 34 | { 35 | query = query.Where(u => u.Name == name); 36 | } 37 | 38 | if (birthStart.HasValue) 39 | { 40 | birthStart = DateTime.SpecifyKind(birthStart.Value, DateTimeKind.Utc); 41 | query = query.Where(u => u.DateOfBirth >= birthStart); 42 | } 43 | 44 | if (birthEnd.HasValue) 45 | { 46 | birthEnd = DateTime.SpecifyKind(birthEnd.Value, DateTimeKind.Utc); 47 | query = query.Where(u => u.DateOfBirth <= birthEnd); 48 | } 49 | 50 | var results = await query.Skip((page - 1) * pageSize).Take(pageSize).ToListAsync(); 51 | 52 | return Ok(results); 53 | } 54 | 55 | [HttpGet("{id:int}",Name = "GetUser")] 56 | public async Task GetUser(int id) 57 | { 58 | var user = await context.Users 59 | .Select(u => new 60 | { 61 | Id = u.Id, 62 | Name = u.Name, 63 | Posts = u.Posts 64 | }) 65 | .FirstOrDefaultAsync(u => u.Id == id); 66 | 67 | return user == null ? NotFound() : Ok(user); 68 | } 69 | 70 | [HttpPut("{id:int}", Name = "UpdateUser")] 71 | public async Task UpdateUser([FromBody] User user) 72 | { 73 | var existing = await context.Users.FindAsync(user.Id); 74 | 75 | if (existing == null) 76 | { 77 | return NotFound(); 78 | } 79 | 80 | existing.Name = user.Name; 81 | existing.DateOfBirth = user.DateOfBirth; 82 | existing.Email = user.Email; 83 | 84 | context.ChangeTracker.DetectChanges(); 85 | await context.SaveChangesAsync(); 86 | 87 | return Ok(existing); 88 | 89 | 90 | // Just for reference since I didn't make actual endpoints for these, the code for adding and deleting: 91 | // context.AddAsync(user); 92 | // await context.SaveChangesAsync(); 93 | 94 | // context.Remove(existing); 95 | // await context.SaveChangesAsync() 96 | } 97 | 98 | [HttpGet("roles", Name = "UserRoles")] 99 | public async Task GetRoles(string email) 100 | { 101 | var roles = await userService.UserRoles(email); 102 | 103 | return Ok(roles); 104 | } 105 | } --------------------------------------------------------------------------------