Skip to content

Commit

Permalink
Merge pull request #630 from leancodepl/feature/local-execution
Browse files Browse the repository at this point in the history
Local execution of CQRS
  • Loading branch information
jakubfijalkowski authored Feb 9, 2024
2 parents 4a006f1 + 925267d commit 03b8bfc
Show file tree
Hide file tree
Showing 46 changed files with 2,340 additions and 13 deletions.
1 change: 0 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ dotnet_style_predefined_type_for_member_access = true
dotnet_style_readonly_field = true : suggestion

dotnet_style_require_accessibility_modifiers = always : warning
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async : warning

dotnet_style_object_initializer = true : suggestion
dotnet_style_collection_initializer = true : suggestion
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;

namespace LeanCode.CQRS.AspNetCore.Local.Context;

internal class LocalCallContext : HttpContext, IDisposable
{
private const int DefaultFeatureCollectionSize = 6; // 4 internal, 2 external (set in local executors)

private readonly FeatureCollection features;

private readonly ItemsFeature itemsFeature;
private readonly LocalCallServiceProvidersFeature serviceProvidersFeature;
private readonly LocalCallLifetimeFeature callLifetimeFeature;

public override IFeatureCollection Features => features;

public override ClaimsPrincipal User { get; set; }
public override string TraceIdentifier { get; set; }
public override HttpRequest Request { get; }
public override HttpResponse Response { get; }

public override IDictionary<object, object?> Items
{
get => itemsFeature.Items;
set => itemsFeature.Items = value;
}

public override IServiceProvider RequestServices
{
get => serviceProvidersFeature.RequestServices;
set => serviceProvidersFeature.RequestServices = value;
}

public override CancellationToken RequestAborted
{
get => callLifetimeFeature.RequestAborted;
set => callLifetimeFeature.RequestAborted = value;
}

public CancellationToken CallAborted => callLifetimeFeature.CallAborted;

public override ConnectionInfo Connection => NullConnectionInfo.Empty;
public override WebSocketManager WebSockets => NullWebSocketManager.Empty;
public override ISession Session
{
get => NullSession.Empty;
set { }
}

public LocalCallContext(
IServiceProvider requestServices,
ClaimsPrincipal user,
string? activityIdentifier,
IHeaderDictionary? headers,
CancellationToken cancellationToken
)
{
features = new(DefaultFeatureCollectionSize);

User = user;
TraceIdentifier = activityIdentifier ?? "";
Request = new LocalHttpRequest(this, headers);
Response = new NullHttpResponse(this);

itemsFeature = new();
serviceProvidersFeature = new(requestServices);
callLifetimeFeature = new(cancellationToken);

features.Set<IItemsFeature>(itemsFeature);
features.Set<IServiceProvidersFeature>(serviceProvidersFeature);
features.Set<IHttpRequestLifetimeFeature>(callLifetimeFeature);
features.Set<IEndpointFeature>(NullEndpointFeature.Empty);
}

public override void Abort() => callLifetimeFeature.Abort();

public void Dispose() => callLifetimeFeature.Dispose();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Microsoft.AspNetCore.Http.Features;

namespace LeanCode.CQRS.AspNetCore.Local.Context;

internal class LocalCallLifetimeFeature : IHttpRequestLifetimeFeature, IDisposable
{
private readonly CancellationTokenSource source;
private CancellationToken requestAborted;

public CancellationToken RequestAborted
{
get => requestAborted;
set => requestAborted = value;
}

public CancellationToken CallAborted => source.Token;

public LocalCallLifetimeFeature(CancellationToken cancellationToken)
{
source = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
requestAborted = source.Token;
}

public void Abort() => source.Cancel();

public void Dispose() => source.Dispose();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Microsoft.AspNetCore.Http.Features;

namespace LeanCode.CQRS.AspNetCore.Local.Context;

internal class LocalCallServiceProvidersFeature : IServiceProvidersFeature
{
public IServiceProvider RequestServices { get; set; }

public LocalCallServiceProvidersFeature(IServiceProvider requestServices)
{
RequestServices = requestServices;
}
}
104 changes: 104 additions & 0 deletions src/CQRS/LeanCode.CQRS.AspNetCore/Local/Context/LocalHttpRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
using Microsoft.AspNetCore.Http;

namespace LeanCode.CQRS.AspNetCore.Local.Context;

internal class LocalHttpRequest : HttpRequest
{
public override HttpContext HttpContext { get; }
public override IHeaderDictionary Headers { get; }

public override bool HasFormContentType => false;

public override string Method
{
get => "";
set { }
}

public override string Scheme
{
get => "";
set { }
}

public override bool IsHttps
{
get => false;
set { }
}

public override HostString Host
{
get => default;
set { }
}

public override PathString PathBase
{
get => PathString.Empty;
set { }
}

public override PathString Path
{
get => PathString.Empty;
set { }
}

public override QueryString QueryString
{
get => QueryString.Empty;
set { }
}

public override IQueryCollection Query
{
get => QueryCollection.Empty;
set { }
}

public override string Protocol
{
get => "";
set { }
}

public override IRequestCookieCollection Cookies
{
get => NullRequestCookieCollection.Empty;
set { }
}

public override long? ContentLength
{
get => null;
set { }
}

public override string? ContentType
{
get => null;
set { }
}

public override Stream Body
{
get => Stream.Null;
set { }
}

public override IFormCollection Form
{
get => FormCollection.Empty;
set { }
}

public LocalHttpRequest(HttpContext httpContext, IHeaderDictionary? headers)
{
HttpContext = httpContext;
Headers = headers ?? new HeaderDictionary();
}

public override Task<IFormCollection> ReadFormAsync(CancellationToken cancellationToken = default) =>
Task.FromResult<IFormCollection>(FormCollection.Empty);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Net;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Http;

namespace LeanCode.CQRS.AspNetCore.Local.Context;

internal class NullConnectionInfo : ConnectionInfo
{
public static readonly NullConnectionInfo Empty = new();

public override string Id
{
get => "";
set { }
}

public override IPAddress? RemoteIpAddress
{
get => null;
set { }
}

public override int RemotePort
{
get => 0;
set { }
}

public override IPAddress? LocalIpAddress
{
get => null;
set { }
}

public override int LocalPort
{
get => 0;
set { }
}

public override X509Certificate2? ClientCertificate
{
get => null;
set { }
}

private NullConnectionInfo() { }

public override Task<X509Certificate2?> GetClientCertificateAsync(CancellationToken cancellationToken = default) =>
Task.FromResult<X509Certificate2?>(null);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;

namespace LeanCode.CQRS.AspNetCore.Local.Context;

internal class NullEndpointFeature : IEndpointFeature
{
public static readonly NullEndpointFeature Empty = new();

public Endpoint? Endpoint
{
get => null;
set { }
}

private NullEndpointFeature() { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System.Collections;
using System.Collections.ObjectModel;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;

namespace LeanCode.CQRS.AspNetCore.Local.Context;

internal class NullHeaderDictionary : IHeaderDictionary
{
public static readonly NullHeaderDictionary Empty = new();

public StringValues this[string key]
{
get => StringValues.Empty;
set { }
}

public long? ContentLength
{
get => null;
set { }
}

public ICollection<string> Keys { get; } = new ReadOnlyCollection<string>([ ]);

public ICollection<StringValues> Values { get; } = new ReadOnlyCollection<StringValues>([ ]);

public int Count => 0;

public bool IsReadOnly => true;

private NullHeaderDictionary() { }

public void Add(string key, StringValues value) { }

public void Add(KeyValuePair<string, StringValues> item) { }

public void Clear() { }

public bool Contains(KeyValuePair<string, StringValues> item) => false;

public bool ContainsKey(string key) => false;

public void CopyTo(KeyValuePair<string, StringValues>[] array, int arrayIndex) { }

public bool Remove(string key) => false;

public bool Remove(KeyValuePair<string, StringValues> item) => false;

public bool TryGetValue(string key, out StringValues value)
{
value = StringValues.Empty;
return false;
}

public IEnumerator<KeyValuePair<string, StringValues>> GetEnumerator() =>
Enumerable.Empty<KeyValuePair<string, StringValues>>().GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
Loading

0 comments on commit 03b8bfc

Please sign in to comment.