forked from KevinDockx/AspNetCore6WebAPIFundamentals
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
479fa27
commit 0d17b53
Showing
39 changed files
with
2,580 additions
and
0 deletions.
There are no files selected for viewing
671 changes: 671 additions & 0 deletions
671
ASP.NET Core 6 Web API Fundamentals.postman_collection.json
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
using CityInfo.API.Models; | ||
|
||
namespace CityInfo.API | ||
{ | ||
public class CitiesDataStore | ||
{ | ||
public List<CityDto> Cities { get; set; } | ||
// public static CitiesDataStore Current { get; } = new CitiesDataStore(); | ||
|
||
public CitiesDataStore() | ||
{ | ||
// init dummy data | ||
Cities = new List<CityDto>() | ||
{ | ||
new CityDto() | ||
{ | ||
Id = 1, | ||
Name = "New York City", | ||
Description = "The one with that big park.", | ||
PointsOfInterest = new List<PointOfInterestDto>() | ||
{ | ||
new PointOfInterestDto() { | ||
Id = 1, | ||
Name = "Central Park", | ||
Description = "The most visited urban park in the United States." }, | ||
new PointOfInterestDto() { | ||
Id = 2, | ||
Name = "Empire State Building", | ||
Description = "A 102-story skyscraper located in Midtown Manhattan." }, | ||
} | ||
}, | ||
new CityDto() | ||
{ | ||
Id = 2, | ||
Name = "Antwerp", | ||
Description = "The one with the cathedral that was never really finished.", | ||
PointsOfInterest = new List<PointOfInterestDto>() | ||
{ | ||
new PointOfInterestDto() { | ||
Id = 3, | ||
Name = "Cathedral of Our Lady", | ||
Description = "A Gothic style cathedral, conceived by architects Jan and Pieter Appelmans." }, | ||
new PointOfInterestDto() { | ||
Id = 4, | ||
Name = "Antwerp Central Station", | ||
Description = "The the finest example of railway architecture in Belgium." }, | ||
} | ||
}, | ||
new CityDto() | ||
{ | ||
Id= 3, | ||
Name = "Paris", | ||
Description = "The one with that big tower.", | ||
PointsOfInterest = new List<PointOfInterestDto>() | ||
{ | ||
new PointOfInterestDto() { | ||
Id = 5, | ||
Name = "Eiffel Tower", | ||
Description = "A wrought iron lattice tower on the Champ de Mars, named after engineer Gustave Eiffel." }, | ||
new PointOfInterestDto() { | ||
Id = 6, | ||
Name = "The Louvre", | ||
Description = "The world's largest museum." }, | ||
} | ||
} | ||
}; | ||
|
||
} | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
<Project Sdk="Microsoft.NET.Sdk.Web"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net6.0</TargetFramework> | ||
<Nullable>enable</Nullable> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<GenerateDocumentationFile>True</GenerateDocumentationFile> | ||
<DocumentationFile> CityInfo.API.xml</DocumentationFile> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.1.1" /> | ||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.0" /> | ||
<PackageReference Include="Microsoft.AspNetCore.JsonPatch" Version="6.0.1" /> | ||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.1" /> | ||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="5.0.0" /> | ||
<PackageReference Include="microsoft.entityframeworkcore" Version="6.0.0" /> | ||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.0" /> | ||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.0"> | ||
<PrivateAssets>all</PrivateAssets> | ||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||
</PackageReference> | ||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.15.0" /> | ||
<PackageReference Include="Serilog.AspNetCore" Version="4.1.0" /> | ||
<PackageReference Include="serilog.sinks.console" Version="4.0.1" /> | ||
<PackageReference Include="serilog.sinks.file" Version="5.0.0" /> | ||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" /> | ||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.15.0" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<None Update="getting-started-with-rest-slides.pdf"> | ||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | ||
</None> | ||
</ItemGroup> | ||
|
||
</Project> |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Microsoft.IdentityModel.Tokens; | ||
using System.IdentityModel.Tokens.Jwt; | ||
using System.Security.Claims; | ||
using System.Text; | ||
|
||
namespace CityInfo.API.Controllers | ||
{ | ||
[Route("api/authentication")] | ||
[ApiController] | ||
public class AuthenticationController : ControllerBase | ||
{ | ||
private readonly IConfiguration _configuration; | ||
|
||
// we won't use this outside of this class, so we can scope it to this namespace | ||
public class AuthenticationRequestBody | ||
{ | ||
public string? UserName { get; set; } | ||
public string? Password { get; set; } | ||
} | ||
|
||
private class CityInfoUser | ||
{ | ||
public int UserId { get; set; } | ||
public string UserName { get; set; } | ||
public string FirstName { get; set; } | ||
public string LastName { get; set; } | ||
public string City { get; set; } | ||
|
||
public CityInfoUser( | ||
int userId, | ||
string userName, | ||
string firstName, | ||
string lastName, | ||
string city) | ||
{ | ||
UserId = userId; | ||
UserName = userName; | ||
FirstName = firstName; | ||
LastName = lastName; | ||
City = city; | ||
} | ||
|
||
} | ||
|
||
public AuthenticationController(IConfiguration configuration) | ||
{ | ||
_configuration = configuration ?? | ||
throw new ArgumentNullException(nameof(configuration)); | ||
} | ||
|
||
[HttpPost("authenticate")] | ||
public ActionResult<string> Authenticate( | ||
AuthenticationRequestBody authenticationRequestBody) | ||
{ | ||
// Step 1: validate the username/password | ||
var user = ValidateUserCredentials( | ||
authenticationRequestBody.UserName, | ||
authenticationRequestBody.Password); | ||
|
||
if (user == null) | ||
{ | ||
return Unauthorized(); | ||
} | ||
|
||
// Step 2: create a token | ||
var securityKey = new SymmetricSecurityKey( | ||
Encoding.ASCII.GetBytes(_configuration["Authentication:SecretForKey"])); | ||
var signingCredentials = new SigningCredentials( | ||
securityKey, SecurityAlgorithms.HmacSha256); | ||
|
||
var claimsForToken = new List<Claim>(); | ||
claimsForToken.Add(new Claim("sub", user.UserId.ToString())); | ||
claimsForToken.Add(new Claim("given_name", user.FirstName)); | ||
claimsForToken.Add(new Claim("family_name", user.LastName)); | ||
claimsForToken.Add(new Claim("city", user.City)); | ||
|
||
var jwtSecurityToken = new JwtSecurityToken( | ||
_configuration["Authentication:Issuer"], | ||
_configuration["Authentication:Audience"], | ||
claimsForToken, | ||
DateTime.UtcNow, | ||
DateTime.UtcNow.AddHours(1), | ||
signingCredentials); | ||
|
||
var tokenToReturn = new JwtSecurityTokenHandler() | ||
.WriteToken(jwtSecurityToken); | ||
|
||
return Ok(tokenToReturn); | ||
} | ||
|
||
private CityInfoUser ValidateUserCredentials(string? userName, string? password) | ||
{ | ||
// we don't have a user DB or table. If you have, check the passed-through | ||
// username/password against what's stored in the database. | ||
// | ||
// For demo purposes, we assume the credentials are valid | ||
|
||
// return a new CityInfoUser (values would normally come from your user DB/table) | ||
return new CityInfoUser( | ||
1, | ||
userName ?? "", | ||
"Kevin", | ||
"Dockx", | ||
"Antwerp"); | ||
|
||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
using AutoMapper; | ||
using CityInfo.API.Models; | ||
using CityInfo.API.Services; | ||
using Microsoft.AspNetCore.Authorization; | ||
using Microsoft.AspNetCore.Mvc; | ||
using System.Text.Json; | ||
|
||
namespace CityInfo.API.Controllers | ||
{ | ||
[ApiController] | ||
[Authorize] | ||
[ApiVersion("1.0")] | ||
[ApiVersion("2.0")] | ||
[Route("api/v{version:apiVersion}/cities")] | ||
public class CitiesController : ControllerBase | ||
{ | ||
private readonly ICityInfoRepository _cityInfoRepository; | ||
private readonly IMapper _mapper; | ||
const int maxCitiesPageSize = 20; | ||
|
||
public CitiesController(ICityInfoRepository cityInfoRepository, | ||
IMapper mapper) | ||
{ | ||
_cityInfoRepository = cityInfoRepository ?? | ||
throw new ArgumentNullException(nameof(cityInfoRepository)); | ||
_mapper = mapper ?? | ||
throw new ArgumentNullException(nameof(mapper)); | ||
} | ||
|
||
[HttpGet] | ||
public async Task<ActionResult<IEnumerable<CityWithoutPointsOfInterestDto>>> GetCities( | ||
string? name, string? searchQuery, int pageNumber = 1, int pageSize = 10) | ||
{ | ||
if (pageSize > maxCitiesPageSize) | ||
{ | ||
pageNumber = maxCitiesPageSize; | ||
} | ||
|
||
var (cityEntities, paginationMetadata) = await _cityInfoRepository | ||
.GetCitiesAsync(name, searchQuery, pageNumber, pageSize); | ||
|
||
Response.Headers.Add("X-Pagination", | ||
JsonSerializer.Serialize(paginationMetadata)); | ||
|
||
return Ok(_mapper.Map<IEnumerable<CityWithoutPointsOfInterestDto>>(cityEntities)); | ||
} | ||
|
||
/// <summary> | ||
/// Get a city by id | ||
/// </summary> | ||
/// <param name="id">The id of the city to get</param> | ||
/// <param name="includePointsOfInterest">Whether or not to include the points of interest</param> | ||
/// <returns>An IActionResult</returns> | ||
/// <response code="200">Returns the requested city</response> | ||
[HttpGet("{id}")] | ||
[ProducesResponseType(StatusCodes.Status200OK)] | ||
[ProducesResponseType(StatusCodes.Status404NotFound)] | ||
[ProducesResponseType(StatusCodes.Status400BadRequest)] | ||
public async Task<IActionResult> GetCity( | ||
int id, bool includePointsOfInterest = false) | ||
{ | ||
var city = await _cityInfoRepository.GetCityAsync(id, includePointsOfInterest); | ||
if (city == null) | ||
{ | ||
return NotFound(); | ||
} | ||
|
||
if (includePointsOfInterest) | ||
{ | ||
return Ok(_mapper.Map<CityDto>(city)); | ||
} | ||
|
||
return Ok(_mapper.Map<CityWithoutPointsOfInterestDto>(city)); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
using Microsoft.AspNetCore.Authorization; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Microsoft.AspNetCore.StaticFiles; | ||
|
||
namespace CityInfo.API.Controllers | ||
{ | ||
[Route("api/files")] | ||
[Authorize] | ||
[ApiController] | ||
public class FilesController : ControllerBase | ||
{ | ||
|
||
private readonly FileExtensionContentTypeProvider _fileExtensionContentTypeProvider; | ||
|
||
public FilesController( | ||
FileExtensionContentTypeProvider fileExtensionContentTypeProvider) | ||
{ | ||
_fileExtensionContentTypeProvider = fileExtensionContentTypeProvider | ||
?? throw new System.ArgumentNullException( | ||
nameof(fileExtensionContentTypeProvider)); | ||
} | ||
|
||
[HttpGet("{fileId}")] | ||
public ActionResult GetFile(string fileId) | ||
{ | ||
// look up the actual file, depending on the fileId... | ||
// demo code | ||
var pathToFile = "getting-started-with-rest-slides.pdf"; | ||
|
||
// check whether the file exists | ||
if (!System.IO.File.Exists(pathToFile)) | ||
{ | ||
return NotFound(); | ||
} | ||
|
||
if (!_fileExtensionContentTypeProvider.TryGetContentType( | ||
pathToFile, out var contentType)) | ||
{ | ||
contentType = "application/octet-stream"; | ||
} | ||
|
||
var bytes = System.IO.File.ReadAllBytes(pathToFile); | ||
return File(bytes, contentType, Path.GetFileName(pathToFile)); | ||
} | ||
} | ||
} |
Oops, something went wrong.