Skip to content

Commit

Permalink
Merge pull request #1 from redbridge-uk/webapicore
Browse files Browse the repository at this point in the history
WebApi Core Port
  • Loading branch information
binarysenator authored Oct 26, 2022
2 parents c81094f + 987a601 commit 634735a
Show file tree
Hide file tree
Showing 12 changed files with 341 additions and 3 deletions.
32 changes: 32 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,35 @@
/Redbridge.WebApi/obj
/Redbridge.WebApi.Tests/bin/Debug/net472
/Redbridge.WebApi.Tests/obj
/.vs/ProjectEvaluation/redbridge.webapiframework.projects.v5.1
/.vs/ProjectEvaluation/redbridge.webapiframework.projects.v2
/.vs/ProjectEvaluation/redbridge.webapiframework.metadata.v5.1
/.vs/ProjectEvaluation/redbridge.webapiframework.metadata.v2
/__azurite_db_table__.json
/__azurite_db_queue__.json
/__azurite_db_queue_extent__.json
/__azurite_db_blob__.json
/__azurite_db_blob_extent__.json
/__blobstorage__/289dd10e-63cb-41f9-ac37-d811b89ca871
/Redbridge.WebApiCore/obj/Redbridge.WebApiCore.csproj.nuget.g.targets
/Redbridge.WebApiCore/obj/Redbridge.WebApiCore.csproj.nuget.g.props
/Redbridge.WebApiCore/obj/Redbridge.WebApiCore.csproj.nuget.dgspec.json
/Redbridge.WebApiCore/obj/project.nuget.cache
/Redbridge.WebApiCore/obj/project.assets.json
/Redbridge.WebApiCore/obj/Debug/net6.0/_IsIncrementalBuild
/Redbridge.WebApiCore/obj/Debug/net6.0/refint/Redbridge.WebApiCore.dll
/Redbridge.WebApiCore/obj/Debug/net6.0/ref/Redbridge.WebApiCore.dll
/Redbridge.WebApiCore/obj/Debug/net6.0/Redbridge.WebApiCore.pdb
/Redbridge.WebApiCore/obj/Debug/net6.0/Redbridge.WebApiCore.GlobalUsings.g.cs
/Redbridge.WebApiCore/obj/Debug/net6.0/Redbridge.WebApiCore.GeneratedMSBuildEditorConfig.editorconfig
/Redbridge.WebApiCore/obj/Debug/net6.0/Redbridge.WebApiCore.dll
/Redbridge.WebApiCore/obj/Debug/net6.0/Redbridge.WebApiCore.csproj.FileListAbsolute.txt
/Redbridge.WebApiCore/obj/Debug/net6.0/Redbridge.WebApiCore.csproj.CoreCompileInputs.cache
/Redbridge.WebApiCore/obj/Debug/net6.0/Redbridge.WebApiCore.csproj.AssemblyReference.cache
/Redbridge.WebApiCore/obj/Debug/net6.0/Redbridge.WebApiCore.assets.cache
/Redbridge.WebApiCore/obj/Debug/net6.0/Redbridge.WebApiCore.AssemblyInfoInputs.cache
/Redbridge.WebApiCore/obj/Debug/net6.0/Redbridge.WebApiCore.AssemblyInfo.cs
/Redbridge.WebApiCore/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs
/Redbridge.WebApiCore/bin/Debug/net6.0/Redbridge.WebApiCore.pdb
/Redbridge.WebApiCore/bin/Debug/net6.0/Redbridge.WebApiCore.dll
/Redbridge.WebApiCore/bin/Debug/net6.0/Redbridge.WebApiCore.deps.json
5 changes: 2 additions & 3 deletions Redbridge.WebApi/Filters/LoggingExceptionFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ public class LoggingExceptionFilter : ExceptionFilterAttribute

public LoggingExceptionFilter(ILogger logger)
{
if (logger == null) throw new ArgumentNullException(nameof(logger));
_logger = logger;
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

public override void OnException(HttpActionExecutedContext actionExecutedContext)
Expand All @@ -23,7 +22,7 @@ public override void OnException(HttpActionExecutedContext actionExecutedContext
{
_logger.WriteInfo($"Logging that an exception has occurred in LoggingExceptionFilter: {actionExecutedContext.Exception.Message}...");
var messagePhrase = actionExecutedContext.Exception.Message ?? "Internal server error - no additional detail supplied";
messagePhrase = string.Join(",", messagePhrase.Split(new[] { Environment.NewLine }, StringSplitOptions.None).Where(s => !string.IsNullOrWhiteSpace(s))); // Cariage returns are not permitted in reason phrases.
messagePhrase = string.Join(",", messagePhrase.Split(new[] { Environment.NewLine }, StringSplitOptions.None).Where(s => !string.IsNullOrWhiteSpace(s))); // Carriage returns are not permitted in reason phrases.

var response = new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Expand Down
15 changes: 15 additions & 0 deletions Redbridge.WebApiCore/Controllers/ApiFactoryController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Microsoft.AspNetCore.Mvc;
using Redbridge.ApiManagement;

namespace Redbridge.WebApiCore.Controllers
{
public abstract class ApiFactoryController : ControllerBase
{
protected ApiFactoryController(IApiFactory apiFactory)
{
ApiFactory = apiFactory ?? throw new ArgumentNullException(nameof(apiFactory));
}

protected IApiFactory ApiFactory { get; }
}
}
63 changes: 63 additions & 0 deletions Redbridge.WebApiCore/FileDownloadDataExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System.IO.Compression;
using System.Net;
using System.Net.Http.Headers;
using Redbridge.Data;
using Redbridge.IO;

namespace Redbridge.WebApiCore
{
public static class FileDownloadDataExtensions
{
public static HttpResponseMessage AsFileResult(this FileDownloadData downloadData)
{
var result = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(downloadData.FileStream)
};

result.Content.Headers.ContentType = new MediaTypeHeaderValue(downloadData.ContentType);
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = downloadData.FileName
};

return result;
}

public static HttpResponseMessage AsZippedFileResult(this FileDownloadsData downloadData)
{
using (var archiveStream = new MemoryStream())
{
using (var archive = new ZipArchive(archiveStream, ZipArchiveMode.Create, true))
{
foreach (var download in downloadData.Files)
{
var zipArchiveEntry = archive.CreateEntry(download.FileName, CompressionLevel.Fastest);
using var zipStream = zipArchiveEntry.Open();
zipStream.Write(download.FileStream.ToByteArray(), 0, (int)download.FileStream.Length);
}
}

var archiveFile = archiveStream.ToArray();
return ZipContentResult(archiveFile, downloadData.FileName);
}
}

private static HttpResponseMessage ZipContentResult( byte[] archiveFile, string filename)
{
var pushStreamContent = new PushStreamContent((stream, content, context) =>
{
stream.Write(archiveFile);
stream.Close(); // After save we close the stream to signal that we are done writing.
}, "application/zip");

var response = new HttpResponseMessage(HttpStatusCode.OK) { Content = pushStreamContent };
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/zip");
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = filename
};
return response;
}
}
}
20 changes: 20 additions & 0 deletions Redbridge.WebApiCore/Filters/LoggingExceptionFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;

namespace Redbridge.WebApiCore.Filters
{
public class LoggingExceptionFilter : ExceptionFilterAttribute
{
private readonly ILogger _logger;

public LoggingExceptionFilter(ILogger<LoggingExceptionFilter> logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

public override void OnException(ExceptionContext actionExecutedContext)
{
_logger.LogError(actionExecutedContext.Exception, actionExecutedContext.Exception.Message);
}
}
}
27 changes: 27 additions & 0 deletions Redbridge.WebApiCore/Filters/UnknownEntityExceptionFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using Redbridge.Data;

namespace Redbridge.WebApiCore.Filters
{
public class UnknownEntityExceptionFilter : ExceptionFilterAttribute
{
private readonly ILogger _logger;

public UnknownEntityExceptionFilter(ILogger<UnknownEntityExceptionFilter> logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

public override void OnException(ExceptionContext actionExecutedContext)
{
if (actionExecutedContext.Exception is UnknownEntityException unknownEntityException)
{
_logger.LogInformation($"Unknown entity exception processing with message {unknownEntityException.Message}");
var errorMessageError = new { error = unknownEntityException.Message};
actionExecutedContext.Result = new JsonResult(errorMessageError) { StatusCode = 422 };
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using Redbridge.Exceptions;

namespace Redbridge.WebApiCore.Filters
{
public class UserNotAuthenticatedExceptionFilter : ExceptionFilterAttribute
{
readonly ILogger _logger;

public UserNotAuthenticatedExceptionFilter(ILogger<UserNotAuthenticatedExceptionFilter> logger)
{
_logger = logger ?? throw new System.ArgumentNullException(nameof(logger));
}

public override void OnException(ExceptionContext actionExecutedContext)
{
if (actionExecutedContext.Exception is UserNotAuthenticatedException exception)
{
_logger.LogDebug("Processing user not authenticated exception filtering.");
actionExecutedContext.Result = new UnauthorizedResult();
actionExecutedContext.ExceptionHandled = true;
}
}
}
}
29 changes: 29 additions & 0 deletions Redbridge.WebApiCore/Filters/UserNotAuthorizedExceptionFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using Redbridge.Exceptions;

namespace Redbridge.WebApiCore.Filters
{
public class UserNotAuthorizedExceptionFilter : ExceptionFilterAttribute
{
readonly ILogger _logger;

public UserNotAuthorizedExceptionFilter(ILogger<UserNotAuthorizedExceptionFilter> logger)
{
_logger = logger ?? throw new System.ArgumentNullException(nameof(logger));
}

public override void OnException(ExceptionContext actionExecutedContext)
{
_logger.LogInformation("Checking exception for user not authorized exception filtering....");

if (actionExecutedContext.Exception is UserNotAuthorizedException exception)
{
actionExecutedContext.ExceptionHandled = true;
actionExecutedContext.Result = new JsonResult(null) { StatusCode = 403 };
}
}
}
}
57 changes: 57 additions & 0 deletions Redbridge.WebApiCore/Filters/ValidationExceptionFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Redbridge.Exceptions;
using Redbridge.Validation;

namespace Redbridge.WebApiCore.Filters
{
public class ValidationExceptionFilter : ExceptionFilterAttribute
{
readonly ILogger _logger;

public ValidationExceptionFilter(ILogger<ValidationExceptionFilter> logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

public override void OnException(ExceptionContext actionExecutedContext)
{
// Convert ValidationResultsException results or a ValidationException into ValidationResults
_logger.LogInformation("Checking exception for validation exception filtering....");

if (actionExecutedContext.Exception is ValidationResultsException ||
actionExecutedContext.Exception is ValidationException)
{
ValidationResult[] results = Array.Empty<ValidationResult>();

if (actionExecutedContext.Exception is ValidationResultsException validationResultsException)
{
_logger.LogInformation(
"Validation exception filtering being applied to a multi-results exception...");
results = validationResultsException.Results?.Results != null
? validationResultsException.Results.Results.ToArray()
: new[] { new ValidationResult(false, validationResultsException.Message) };
}
else if (actionExecutedContext.Exception is ValidationException validationException)
{
_logger.LogInformation(
"Validation exception filtering being applied to a single result validation exception...");
results = new[] { new ValidationResult(false, validationException.Message) };
}

_logger.LogInformation("Serializing results into JSON for transmission...");
var rawJson = JsonConvert.SerializeObject(results, new JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});

actionExecutedContext.Result = new JsonResult(rawJson) { StatusCode = 422 };
actionExecutedContext.ExceptionHandled = true;
}
}

}
}
45 changes: 45 additions & 0 deletions Redbridge.WebApiCore/QueryStringAuthenticationExtension.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using Microsoft.AspNetCore.Builder;
using Redbridge.Web;
using Redbridge.Web.Messaging;

namespace Redbridge.WebApiCore
{
public static class QueryStringAuthenticationExtension
{
public static void UseQueryStringAuthentication(this IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
if (context.Request.QueryString.HasValue)
{
if (context.Request.Headers.TryGetValue(HeaderNames.Authorization, out var authorizationHeader))
{
if (string.IsNullOrWhiteSpace(authorizationHeader))
{
var queryString = HttpUtility.ParseQueryString(context.Request.QueryString.Value);
if (queryString.ContainsKey(QueryStringParts.Authentication))
{
var token = queryString[QueryStringParts.Authentication];

if (!string.IsNullOrWhiteSpace(token))
{
context.Request.Headers.Add(HeaderNames.Authorization,
new[] { BearerTokenFormatter.CreateToken(token) });
}
}
}
}
}

try
{
await next.Invoke();
}
catch (OperationCanceledException)
{
// Do not propagate this exception.
}
});
}
}
}
18 changes: 18 additions & 0 deletions Redbridge.WebApiCore/Redbridge.WebApiCore.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>true</IsPackable>
<PackageIconUrl>https://github.com/redbridge-uk/resources/blob/master/redbridge_32.png</PackageIconUrl>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.9" />
<PackageReference Include="Microsoft.AspNetCore.Owin" Version="6.0.10" />
<PackageReference Include="Redbridge.ApiManagement" Version="2.2.1" />
<PackageReference Include="Redbridge.SDK" Version="2.2.1" />
</ItemGroup>

</Project>
6 changes: 6 additions & 0 deletions Redbridge.WebApiFramework.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Redbridge.WebApi", "Redbrid
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Redbridge.WebApi.Tests", "Redbridge.WebApi.Tests\Redbridge.WebApi.Tests.csproj", "{8D21277A-50CB-4300-A32F-0518E68D84B2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Redbridge.WebApiCore", "Redbridge.WebApiCore\Redbridge.WebApiCore.csproj", "{CC19B56C-15E4-4CD7-9ACE-4AA6CA22A214}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -21,6 +23,10 @@ Global
{8D21277A-50CB-4300-A32F-0518E68D84B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8D21277A-50CB-4300-A32F-0518E68D84B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8D21277A-50CB-4300-A32F-0518E68D84B2}.Release|Any CPU.Build.0 = Release|Any CPU
{CC19B56C-15E4-4CD7-9ACE-4AA6CA22A214}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CC19B56C-15E4-4CD7-9ACE-4AA6CA22A214}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CC19B56C-15E4-4CD7-9ACE-4AA6CA22A214}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CC19B56C-15E4-4CD7-9ACE-4AA6CA22A214}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down

0 comments on commit 634735a

Please sign in to comment.