Skip to content

Commit

Permalink
test
Browse files Browse the repository at this point in the history
  • Loading branch information
Gary Woodfine committed Sep 9, 2023
1 parent 16cf09f commit 522b4ca
Show file tree
Hide file tree
Showing 104 changed files with 6,495 additions and 28 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ This repository contains the code for a number of articles and blog posts on my
- [How to manage secrets with dotnet user secrets](https://garywoodfine.com/how-to-manage-secrets-in-dotnet/ "How to manage secrets with dotnet user secrets - Gary Woodfine")
- [How to use Azure Key Vault to manage secrets](https://garywoodfine.com/how-to-use-azure-key-vault-to-manage-secrets/ "How to use Azure Key Vault to manage secrets - Gary Woodfine")
- [How to use Azure Blob Storage](https://garywoodfine.com/how-to-use-azure-blob-storage/ "How to use Azure Blob Storage - Gary Woodfine")
- [How to create a websocket server in dotnet](https://garywoodfine.com/how-to-create-a-websocket-server-in-dotnet/ "How to create a websocket server in dotnet - Gary Woodfine")
32 changes: 32 additions & 0 deletions how-to-create-websocket-server-dotnet/SampleWebSocket.sln
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.
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";
}
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 how-to-create-websocket-server-dotnet/src/api/ApiSocket.csproj
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>
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;
}
}


}
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;
}
}
}
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; }
}
}
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)
{
}
}
}
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 }
};
}
}
}
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;
}
}
}
Loading

0 comments on commit 522b4ca

Please sign in to comment.