-
Notifications
You must be signed in to change notification settings - Fork 4
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
Gary Woodfine
committed
Sep 9, 2023
1 parent
16cf09f
commit 522b4ca
Showing
104 changed files
with
6,495 additions
and
28 deletions.
There are no files selected for viewing
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
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,32 @@ | ||
|
||
Microsoft Visual Studio Solution File, Format Version 12.00 | ||
# Visual Studio Version 17 | ||
VisualStudioVersion = 17.0.31903.59 | ||
MinimumVisualStudioVersion = 10.0.40219.1 | ||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{35129844-B706-4D21-94C7-1804FCB89278}" | ||
EndProject | ||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiSocket", "src\api\ApiSocket.csproj", "{C6BD428E-71DE-465C-ACDD-330526B0788E}" | ||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|Any CPU = Debug|Any CPU | ||
Release|Any CPU = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(SolutionProperties) = preSolution | ||
HideSolutionNode = FALSE | ||
EndGlobalSection | ||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
{8E629729-6B2E-4FA8-B7B7-DB4B6AF8F8E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{8E629729-6B2E-4FA8-B7B7-DB4B6AF8F8E4}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{8E629729-6B2E-4FA8-B7B7-DB4B6AF8F8E4}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{8E629729-6B2E-4FA8-B7B7-DB4B6AF8F8E4}.Release|Any CPU.Build.0 = Release|Any CPU | ||
{C6BD428E-71DE-465C-ACDD-330526B0788E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{C6BD428E-71DE-465C-ACDD-330526B0788E}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{C6BD428E-71DE-465C-ACDD-330526B0788E}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{C6BD428E-71DE-465C-ACDD-330526B0788E}.Release|Any CPU.Build.0 = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(NestedProjects) = preSolution | ||
{8E629729-6B2E-4FA8-B7B7-DB4B6AF8F8E4} = {F463714D-D27D-4B61-8E76-FB2C089053BA} | ||
{C6BD428E-71DE-465C-ACDD-330526B0788E} = {35129844-B706-4D21-94C7-1804FCB89278} | ||
EndGlobalSection | ||
EndGlobal |
Empty file.
5 changes: 5 additions & 0 deletions
5
how-to-create-websocket-server-dotnet/src/api/Activities/Sockets/Routes.cs
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,5 @@ | ||
namespace Api.Activities; | ||
internal static partial class Routes | ||
{ | ||
internal const string Sockets = "Sockets"; | ||
} |
66 changes: 66 additions & 0 deletions
66
...to-create-websocket-server-dotnet/src/api/Activities/Sockets/SampleSocket/SampleSocket.cs
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,66 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Net; | ||
using System.Net.WebSockets; | ||
using System.Text; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Api.Activities; | ||
using Ardalis.ApiEndpoints; | ||
using MediatR; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Microsoft.EntityFrameworkCore; | ||
using Swashbuckle.AspNetCore.Annotations; | ||
using Threenine.ApiResponse; | ||
|
||
namespace Api.Activities.Sockets.Queries.SampleSocket; | ||
|
||
[ApiController] | ||
[Route("ws")] | ||
public class WebSocketsController : ControllerBase | ||
{ | ||
private new const int BadRequest = ((int)HttpStatusCode.BadRequest); | ||
private readonly ILogger<WebSocketsController> _logger; | ||
|
||
public WebSocketsController(ILogger<WebSocketsController> logger) | ||
{ | ||
_logger = logger; | ||
} | ||
|
||
[HttpGet] | ||
public async Task Get() | ||
{ | ||
if (HttpContext.WebSockets.IsWebSocketRequest) | ||
{ | ||
using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); | ||
_logger.Log(LogLevel.Information, "WebSocket connection established"); | ||
await Echo(webSocket); | ||
} | ||
else | ||
{ | ||
HttpContext.Response.StatusCode = BadRequest; | ||
} | ||
} | ||
|
||
private async Task Echo(WebSocket webSocket) | ||
{ | ||
var buffer = new byte[1024 * 4]; | ||
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None); | ||
_logger.Log(LogLevel.Information, "Message received from Client"); | ||
|
||
while (!result.CloseStatus.HasValue) | ||
{ | ||
var serverMsg = Encoding.UTF8.GetBytes($"Client Message: {Encoding.UTF8.GetString(buffer)}"); | ||
await webSocket.SendAsync(new ArraySegment<byte>(serverMsg, 0, serverMsg.Length), result.MessageType, result.EndOfMessage, CancellationToken.None); | ||
_logger.Log(LogLevel.Information, "Message sent to Client"); | ||
|
||
buffer = new byte[1024 * 4]; | ||
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None); | ||
_logger.Log(LogLevel.Information, "Message received from Client"); | ||
} | ||
|
||
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); | ||
_logger.Log(LogLevel.Information, "WebSocket connection closed"); | ||
} | ||
} |
36 changes: 36 additions & 0 deletions
36
how-to-create-websocket-server-dotnet/src/api/ApiSocket.csproj
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,36 @@ | ||
<Project Sdk="Microsoft.NET.Sdk.Web"> | ||
<PropertyGroup> | ||
<TargetFramework>net7.0</TargetFramework> | ||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> | ||
<IsPackable>false</IsPackable> | ||
<ImplicitUsings>true</ImplicitUsings> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>disable</Nullable> | ||
<RootNamespace>ApiSocket</RootNamespace> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<PackageReference Include="Ardalis.ApiEndpoints" Version="4.1.0" /> | ||
<PackageReference Include="AutoMapper" Version="12.0.1" /> | ||
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" /> | ||
<PackageReference Include="Microsoft.AspNetCore.JsonPatch" Version="7.0.5" /> | ||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="7.0.5" /> | ||
<PackageReference Include="FluentValidation" Version="11.7.1" /> | ||
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" /> | ||
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.7.1" /> | ||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.10"/> | ||
<PackageReference Include="Npgsql" Version="7.0.4" /> | ||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="7.0.4" /> | ||
<PackageReference Include="Serilog.Exceptions" Version="8.4.0" /> | ||
<PackageReference Include="Serilog.Settings.Configuration" Version="7.0.1" /> | ||
<PackageReference Include="Serilog.Sinks.Seq" Version="5.2.2" /> | ||
<PackageReference Include="Threenine.DataService" Version="0.2.1" /> | ||
<PackageReference Include="MediatR" Version="12.1.1" /> | ||
<PackageReference Include="Serilog" Version="3.0.1" /> | ||
<PackageReference Include="Serilog.AspNetCore" Version="7.0.0" /> | ||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" /> | ||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.5.0" /> | ||
<PackageReference Include="Threenine.ApiResponse" Version="1.0.26" /> | ||
<PackageReference Include="Threenine.Data" Version="5.0.0" /> | ||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Common" Version="7.0.10" /> | ||
</ItemGroup> | ||
</Project> |
41 changes: 41 additions & 0 deletions
41
how-to-create-websocket-server-dotnet/src/api/Behaviours/LoggingBehaviour.cs
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,41 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Reflection; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using MediatR; | ||
using Serilog; | ||
using ILogger = Serilog.ILogger; | ||
|
||
namespace ApiSocket.Behaviours | ||
{ | ||
public class LoggingBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> | ||
where TRequest : IRequest<TResponse> | ||
{ | ||
private readonly ILogger _logger; | ||
public LoggingBehaviour(ILogger logger) | ||
{ | ||
_logger = logger; | ||
} | ||
|
||
|
||
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken) | ||
{ | ||
//Query | ||
_logger.Information($"Handling {typeof(TRequest).Name}"); | ||
Type myType = request.GetType(); | ||
IList<PropertyInfo> props = new List<PropertyInfo>(myType.GetProperties()); | ||
foreach (PropertyInfo prop in props) | ||
{ | ||
object propValue = prop.GetValue(request, null); | ||
_logger.Information("{Property} : {@Value}", prop.Name, propValue); | ||
} | ||
var response = await next(); | ||
//FilterResponse | ||
_logger.Information($"Handled {typeof(TResponse).Name}"); | ||
return response; | ||
} | ||
} | ||
|
||
|
||
} |
50 changes: 50 additions & 0 deletions
50
how-to-create-websocket-server-dotnet/src/api/Behaviours/ValidationBehaviour.cs
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,50 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using FluentValidation; | ||
using MediatR; | ||
using Serilog; | ||
using ILogger = Serilog.ILogger; | ||
|
||
namespace ApiSocket.Behaviours | ||
{ | ||
public class ValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest,TResponse> | ||
where TResponse : class where TRequest : IRequest<TResponse> | ||
{ | ||
private readonly IEnumerable<IValidator<TRequest>> _validators; | ||
private readonly ILogger _logger; | ||
|
||
public ValidationBehaviour(IEnumerable<IValidator<TRequest>> validators, ILogger logger) | ||
{ | ||
_validators = validators; | ||
_logger = logger; | ||
} | ||
|
||
|
||
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken) | ||
{ | ||
if (!typeof(TResponse).IsGenericType) return await next(); | ||
if (!_validators.Any()) return await next(); | ||
|
||
var context = new ValidationContext<TRequest>(request); | ||
var validationResults = | ||
await Task.WhenAll(_validators.Select(v => v.ValidateAsync(context, cancellationToken))); | ||
var failures = validationResults.SelectMany(r => r.Errors) | ||
.Where(f => f != null) | ||
.GroupBy(x => x.PropertyName, | ||
x => x.ErrorMessage, | ||
(propertyName, errorMessages) => new | ||
{ | ||
Key = propertyName, | ||
Values = errorMessages.Distinct().ToArray() | ||
}) | ||
.ToDictionary(x => x.Key, x => x.Values); | ||
|
||
if (!failures.Any()) return await next(); | ||
|
||
return Activator.CreateInstance(typeof(TResponse), null, failures.ToList()) as TResponse; | ||
} | ||
} | ||
} |
10 changes: 10 additions & 0 deletions
10
how-to-create-websocket-server-dotnet/src/api/Exceptions/ApiSocketException.cs
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,10 @@ | ||
using System; | ||
|
||
namespace ApiSocket.Exceptions | ||
{ | ||
public class ApiSocketException : Exception | ||
{ | ||
public ApiSocketException(string title, string message) : base(message) => Title = title; | ||
public string Title { get; set; } | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
how-to-create-websocket-server-dotnet/src/api/Exceptions/NotFoundException.cs
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,13 @@ | ||
using System; | ||
|
||
|
||
namespace ApiSocket.Exceptions | ||
{ | ||
[Serializable] | ||
public class NotFoundException : ApiSocketException | ||
{ | ||
public NotFoundException(string title, string message) : base(title, message) | ||
{ | ||
} | ||
} | ||
} |
57 changes: 57 additions & 0 deletions
57
how-to-create-websocket-server-dotnet/src/api/Helpers/JsonPatchDocumentFilter.cs
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,57 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using Microsoft.OpenApi.Models; | ||
using Swashbuckle.AspNetCore.SwaggerGen; | ||
|
||
namespace ApiSocket.Helpers; | ||
|
||
public class JsonPatchDocumentFilter : IDocumentFilter | ||
{ | ||
private const string JsonPatchDocument = "JsonPatchDocument"; | ||
private const string JsonPatchApplication = "application/json-patch+json"; | ||
private const string Operation = "Operation"; | ||
|
||
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) | ||
{ | ||
var schemas = swaggerDoc.Components.Schemas.ToList(); | ||
foreach (var item in schemas) | ||
if (item.Key.StartsWith(Operation) || item.Key.StartsWith(JsonPatchDocument)) | ||
swaggerDoc.Components.Schemas.Remove(item.Key); | ||
|
||
swaggerDoc.Components.Schemas.Add(Operation, new OpenApiSchema | ||
{ | ||
Type = "object", | ||
Properties = new Dictionary<string, OpenApiSchema> | ||
{ | ||
{ "op", new OpenApiSchema { Type = "string" } }, | ||
{ "value", new OpenApiSchema { Type = "string" } }, | ||
{ "path", new OpenApiSchema { Type = "string" } } | ||
} | ||
}); | ||
|
||
swaggerDoc.Components.Schemas.Add(JsonPatchDocument, new OpenApiSchema | ||
{ | ||
Type = nameof(Array).ToLower(), | ||
Items = new OpenApiSchema | ||
{ | ||
Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = Operation } | ||
}, | ||
Description = "Array of operations to perform" | ||
}); | ||
|
||
foreach (var path in swaggerDoc.Paths.SelectMany(p => p.Value.Operations) | ||
.Where(p => p.Key == OperationType.Patch)) | ||
{ | ||
foreach (var item in path.Value.RequestBody.Content.Where(c => c.Key != JsonPatchApplication)) | ||
path.Value.RequestBody.Content.Remove(item.Key); | ||
|
||
var response = path.Value.RequestBody.Content.SingleOrDefault(c => c.Key == JsonPatchApplication); | ||
|
||
response.Value.Schema = new OpenApiSchema | ||
{ | ||
Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = JsonPatchDocument } | ||
}; | ||
} | ||
} | ||
} |
67 changes: 67 additions & 0 deletions
67
how-to-create-websocket-server-dotnet/src/api/Middleware/ExceptionHandlingMiddleware.cs
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,67 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Text.Json; | ||
using System.Threading.Tasks; | ||
using ApiSocket.Exceptions; | ||
using Microsoft.AspNetCore.Http; | ||
|
||
namespace ApiSocket.Middleware | ||
{ | ||
internal class ExceptionHandlingMiddleware : IMiddleware | ||
{ | ||
public async Task InvokeAsync(HttpContext context, RequestDelegate next) | ||
{ | ||
try | ||
{ | ||
await next(context); | ||
} | ||
catch (Exception e) | ||
{ | ||
await HandleException(context, e); | ||
} | ||
} | ||
|
||
private static async Task HandleException(HttpContext httpContext, Exception exception) | ||
{ | ||
var statusCode = GetStatusCode(exception); | ||
|
||
var response = new | ||
{ | ||
title = GetTitle(exception), | ||
status = statusCode, | ||
detail = exception.Message, | ||
errors = GetErrors(exception) | ||
}; | ||
|
||
httpContext.Response.ContentType = "application/json"; | ||
|
||
httpContext.Response.StatusCode = statusCode; | ||
|
||
await httpContext.Response.WriteAsync(JsonSerializer.Serialize(response)); | ||
} | ||
|
||
private static string GetTitle(Exception exception) => | ||
exception switch | ||
{ | ||
NotFoundException nf => nf.Title, | ||
|
||
_ => "Server Error" | ||
}; | ||
|
||
private static int GetStatusCode(Exception exception) => | ||
exception switch | ||
{ | ||
|
||
NotFoundException => StatusCodes.Status404NotFound, | ||
_ => StatusCodes.Status500InternalServerError | ||
}; | ||
|
||
|
||
private static IReadOnlyDictionary<string, string[]> GetErrors(Exception exception) | ||
{ | ||
IReadOnlyDictionary<string, string[]> errors = null; | ||
|
||
return errors; | ||
} | ||
} | ||
} |
Oops, something went wrong.