Skip to content

Commit

Permalink
Commit finished codebase
Browse files Browse the repository at this point in the history
  • Loading branch information
KevinDockx committed Dec 23, 2021
1 parent 479fa27 commit 0d17b53
Show file tree
Hide file tree
Showing 39 changed files with 2,580 additions and 0 deletions.
671 changes: 671 additions & 0 deletions ASP.NET Core 6 Web API Fundamentals.postman_collection.json

Large diffs are not rendered by default.

71 changes: 71 additions & 0 deletions CityInfo.API/CitiesDataStore.cs
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." },
}
}
};

}

}
}
37 changes: 37 additions & 0 deletions CityInfo.API/CityInfo.API.csproj
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>
37 changes: 37 additions & 0 deletions CityInfo.API/CityInfo.API.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added CityInfo.API/CityInfo.db
Binary file not shown.
110 changes: 110 additions & 0 deletions CityInfo.API/Controllers/AuthenticationController.cs
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");

}
}
}
76 changes: 76 additions & 0 deletions CityInfo.API/Controllers/CitiesController.cs
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));
}
}
}
47 changes: 47 additions & 0 deletions CityInfo.API/Controllers/FilesController.cs
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));
}
}
}
Loading

0 comments on commit 0d17b53

Please sign in to comment.