Skip to content

Commit e549fa2

Browse files
Merge branch 'dev', remote-tracking branch 'origin' into main
2 parents e2a108e + 8b6c477 commit e549fa2

File tree

9 files changed

+191
-60
lines changed

9 files changed

+191
-60
lines changed

.github/workflows/publish-to-nuget.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
runs-on: ubuntu-latest
1212
strategy:
1313
matrix:
14-
folder: [Bones, Bones.Akka, Bones.Converters, Bones.Flow, Bones.Grpc, Bones.Tests, Bones.X509, Bones.Akka.Monitoring, Bones.Monitoring]
14+
folder: [Bones, Bones.Akka, Bones.AspNetCore, Bones.Converters, Bones.Flow, Bones.Grpc, Bones.Tests, Bones.X509, Bones.Akka.Monitoring, Bones.Monitoring]
1515
steps:
1616
# Checking out repository
1717
- name: Checkout 🛎️

dev/demo.flow/Demo.Flow.Console/Worker.cs

Lines changed: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -21,36 +21,48 @@ public Worker(IServiceProvider services)
2121
}
2222

2323
public async Task StartAsync(CancellationToken cancellationToken)
24-
{
25-
var logger = _services.GetRequiredService<ILogger<Program>>();
24+
{
25+
var logger = _services.GetRequiredService<ILogger<Program>>();
2626

27-
Activity.CurrentChanged += (sender, args) =>
27+
Activity.CurrentChanged += (sender, args) =>
28+
{
29+
var current = Activity.Current;
30+
if (current != null)
2831
{
29-
var current = Activity.Current;
30-
if (current != null)
31-
{
32-
logger.LogInformation($"Activity.CurrentChanged: {current.OperationName}");
33-
}
34-
};
35-
36-
ICommandHandler<HelloWorldCommand> helloCommandHandler = _services.GetRequiredService<ICommandHandler<HelloWorldCommand>>();
37-
IQueryHandler<BoolQuery, bool> boolQueryHandler = _services.GetRequiredService<IQueryHandler<BoolQuery, bool>>();
38-
39-
var command = new HelloWorldCommand();
40-
command.Message = "First Command";
41-
42-
var query = new BoolQuery();
43-
query.Message = "First Query";
44-
45-
// while(true)
46-
// {
47-
await helloCommandHandler.HandleAsync(command);
48-
// var res = await boolQueryHandler.HandleAsync(query);
49-
// System.Console.WriteLine(res);
50-
await Task.Delay(1000, cancellationToken);
51-
// }
52-
53-
// return Task.CompletedTask;
32+
logger.LogInformation($"Activity.CurrentChanged: {current.OperationName}");
33+
}
34+
};
35+
36+
for (var i = 0; i < 10_000_000; i++)
37+
{
38+
using var scope = _services.CreateScope();
39+
40+
var stopWatch = Stopwatch.StartNew();
41+
var handler = scope.ServiceProvider.GetRequiredService<ICommandHandler<HelloWorldCommand>>();
42+
stopWatch.Stop();
43+
44+
if(i % 10_000 == 0)
45+
logger.LogInformation($"Resolved ICommandHandler<HelloWorldCommand> in {stopWatch.ElapsedMilliseconds} ms");
46+
}
47+
48+
ICommandHandler<HelloWorldCommand> helloCommandHandler = _services.GetRequiredService<ICommandHandler<HelloWorldCommand>>();
49+
IQueryHandler<BoolQuery, bool> boolQueryHandler = _services.GetRequiredService<IQueryHandler<BoolQuery, bool>>();
50+
51+
var command = new HelloWorldCommand();
52+
command.Message = "First Command";
53+
54+
var query = new BoolQuery();
55+
query.Message = "First Query";
56+
57+
// while(true)
58+
// {
59+
await helloCommandHandler.HandleAsync(command);
60+
// var res = await boolQueryHandler.HandleAsync(query);
61+
// System.Console.WriteLine(res);
62+
await Task.Delay(1000, cancellationToken);
63+
// }
64+
65+
// return Task.CompletedTask;
5466
}
5567

5668
public Task StopAsync(CancellationToken cancellationToken)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<ItemGroup>
4+
<ProjectReference Include="..\Bones.Monitoring\Bones.Monitoring.csproj" />
5+
</ItemGroup>
6+
7+
<PropertyGroup>
8+
<TargetFramework>net7.0</TargetFramework>
9+
<Version>$(VERSION)</Version>
10+
<Authors>dative-gpi</Authors>
11+
<Company>dative-gpi</Company>
12+
</PropertyGroup>
13+
14+
</Project>
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Text.Json;
6+
using System.Threading.Tasks;
7+
8+
using Microsoft.AspNetCore.Http;
9+
using Microsoft.Extensions.Primitives;
10+
11+
using Bones.Monitoring.Core;
12+
13+
namespace Bones.AspNetCore
14+
{
15+
public class PostAsGetMiddleware : MonitoredMiddleware
16+
{
17+
public PostAsGetMiddleware(
18+
IServiceProvider serviceProvider,
19+
RequestDelegate next) : base(next, serviceProvider)
20+
{
21+
}
22+
23+
protected override async Task HandleAsync(HttpContext context, RequestDelegate next)
24+
{
25+
if (context.Request.Method == "POST"
26+
&& context.Request.Query.ContainsKey("_method")
27+
&& context.Request.Query["_method"] == "GET"
28+
&& context.Request.HasJsonContentType())
29+
{
30+
// Convert the request to a GET request
31+
context.Request.Method = "GET";
32+
var doc = await JsonSerializer.DeserializeAsync<Dictionary<string, JsonElement>>(context.Request.Body);
33+
if (doc != null)
34+
{
35+
var props = Flat(doc);
36+
var query = $"?{String.Join("&", props.Select(kv => $"{Uri.EscapeDataString(kv.Key)}={Uri.EscapeDataString(kv.Value)}"))}";
37+
context.Request.QueryString = new QueryString(query);
38+
context.Request.Body = Stream.Null;
39+
context.Request.ContentLength = 0;
40+
}
41+
}
42+
await next(context);
43+
return;
44+
}
45+
46+
private Dictionary<string, StringValues> Flat(Dictionary<string, JsonElement> payload)
47+
{
48+
Dictionary<string, StringValues> result = new Dictionary<string, StringValues>();
49+
foreach (var item in payload)
50+
{
51+
var flatted = DeepFlat(item.Key, item.Value);
52+
foreach (var flat in flatted)
53+
{
54+
result.Add(flat.Key, flat.Value);
55+
}
56+
}
57+
return result;
58+
}
59+
60+
private IEnumerable<KeyValuePair<string, string>> DeepFlat(string key, JsonElement value)
61+
{
62+
switch (value.ValueKind)
63+
{
64+
case JsonValueKind.Array when value.GetArrayLength() == 0:
65+
return new[] { new KeyValuePair<string, string>($"{key}[]", "") };
66+
case JsonValueKind.Array when value.GetArrayLength() != 0:
67+
return value.EnumerateArray().SelectMany((item, index) => DeepFlat($"{key}[{index}]", item));
68+
case JsonValueKind.Object:
69+
return value.EnumerateObject().SelectMany(prop => DeepFlat($"{key}.{prop.Name}", prop.Value));
70+
default:
71+
return new[] { new KeyValuePair<string, string>(key, value.ToString()) };
72+
}
73+
}
74+
}
75+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using Microsoft.AspNetCore.Builder;
2+
3+
namespace Bones.AspNetCore
4+
{
5+
public static class PostAsGetMiddlewareExtension
6+
{
7+
public static IApplicationBuilder UsePostAsGetMiddleware(this IApplicationBuilder builder)
8+
{
9+
return builder.UseMiddleware<PostAsGetMiddleware>();
10+
}
11+
}
12+
}

src/Bones.Monitoring/Metrics/MetricFactory.cs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,17 @@ public MetricFactory()
1414

1515
public ICounter<T> GetCounter<T>(string key, Meter meter, string unit = null, string description = null) where T : struct
1616
{
17-
var counter = counters.GetOrAdd(key, new CounterMetric<T>(meter, key, unit, description));
17+
object counter;
18+
if (!counters.ContainsKey(key))
19+
{
20+
counter = new CounterMetric<T>(meter, key, unit, description);
21+
counters.TryAdd(key, counter);
22+
}
23+
else
24+
{
25+
counter = counters[key];
26+
}
27+
1828
if(counter is CounterMetric<T> typedCounter)
1929
{
2030
return typedCounter;
@@ -27,8 +37,18 @@ public ICounter<T> GetCounter<T>(string key, Meter meter, string unit = null, st
2737

2838
public IHistogram<T> GetHistogram<T>(string key, Meter meter, string unit = null, string description = null) where T : struct
2939
{
30-
var counter = counters.GetOrAdd(key, new HistogramMetric<T>(meter, key, unit, description));
31-
if(counter is HistogramMetric<T> typedCounter)
40+
object counter;
41+
if (!counters.ContainsKey(key))
42+
{
43+
counter = new HistogramMetric<T>(meter, key, unit, description);
44+
counters.TryAdd(key, counter);
45+
}
46+
else
47+
{
48+
counter = counters[key];
49+
}
50+
51+
if (counter is HistogramMetric<T> typedCounter)
3252
{
3353
return typedCounter;
3454
}

src/Bones.UI/core/composableFactory.ts

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Ref, onUnmounted, ref, computed } from "vue";
1+
import { Ref, onUnmounted, ref, computed, computed } from "vue";
22

33
import { FilterFactory } from "./filterFactory";
44
import { INotifyService } from "../abstractions";
@@ -25,27 +25,6 @@ export class ComposableFactory {
2525
return ComposableFactory.customRemove(service.remove);
2626
}
2727

28-
public static subscribe<TDetails>(service: INotifyService<TDetails>) {
29-
return () => {
30-
let subscribersIds: number[] = [];
31-
32-
onUnmounted(() => {
33-
subscribersIds.forEach(id => service.unsubscribe(id));
34-
subscribersIds = [];
35-
});
36-
37-
const subscribe: INotifyService<TDetails>["subscribe"] = (ev: any, callback: any) => {
38-
const subscriberId = service.subscribe(ev, callback);
39-
subscribersIds.push(subscriberId);
40-
return subscriberId;
41-
}
42-
43-
return {
44-
subscribe
45-
}
46-
}
47-
}
48-
4928
public static custom<TDetails, TArgs extends any[]>(method: (...args: TArgs) => Promise<TDetails>, applyFactory?: () => (entity: Ref<TDetails>) => void) {
5029
return () => {
5130
const apply = applyFactory ? applyFactory() : () => { };

src/Bones.UI/core/serviceFactory.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,24 @@ export class ServiceFactory<TDetailsDTO, TDetails> {
3636
}
3737

3838
addGetMany<TInfosDTO, TInfos, TFilter>(url: string | (() => string), entity: new (dto: TInfosDTO) => TInfos)
39-
: { getMany: (filter?: TFilter) => Promise<TInfos[]> } {
39+
: { getMany: (filter?: TFilter, controller?: AbortController) => Promise<TInfos[]> } {
4040

41-
const getMany = async (filter?: TFilter) => {
41+
const getMany = async (filter?: TFilter, controller?: AbortController) => {
4242
const realUrl = typeof url === "string" ? url : url();
4343

4444
let response;
4545

4646
// If the service is configured to use GET as POST to prevent issues with large query strings,
4747
// we send the filter as a POST request with a "_method" parameter to indicate it's a GET request.
4848
if (ServiceFactory.getAsPost && filter) {
49-
response = await ServiceFactory.http.post(buildURL(realUrl, { "_method": "GET" }), filter);
49+
response = await ServiceFactory.http.post(buildURL(realUrl, { "_method": "GET" }), filter, {
50+
signal: controller?.signal
51+
});
5052
}
5153
else {
52-
response = await ServiceFactory.http.get(buildURL(realUrl, filter));
54+
response = await ServiceFactory.http.get(buildURL(realUrl, filter), {
55+
signal: controller?.signal
56+
});
5357
}
5458

5559
const dtos: TInfosDTO[] = response.data;
@@ -76,7 +80,7 @@ export class ServiceFactory<TDetailsDTO, TDetails> {
7680
return { get };
7781
}
7882

79-
static addCustom<T extends string, TArgs extends any[], TResultDTO, TResult>(name: T, call: (axios: AxiosInstance, ...args: TArgs) => Promise<AxiosResponse>, mapper: (dto: TResultDTO) => TResult): Record<T, (...args: TArgs) => Promise<TResult>> {
83+
static addCustom<T extends string, TArgs extends any[], TResultDTO, TResult>(name: T, call: (axios: AxiosInstance, ...args: TArgs) => Promise<AxiosResponse>, mapper: (dto: TResultDTO) => TResult) {
8084

8185
const fetch = async (...args: TArgs) => {
8286
const response = await call(ServiceFactory.http, ...args);
@@ -90,6 +94,21 @@ export class ServiceFactory<TDetailsDTO, TDetails> {
9094
return { [name]: fetch } as Record<T, (...args: TArgs) => Promise<TResult>>;
9195
}
9296

97+
98+
static addCustomCancellable<T extends string, TArgs extends any[], TResultDTO, TResult>(name: T, call: (axios: AxiosInstance, ...args: [...TArgs, AbortController]) => Promise<AxiosResponse>, mapper: (dto: TResultDTO) => TResult) {
99+
100+
const fetch = async (...args: [...TArgs, AbortController]) => {
101+
const response = await call(ServiceFactory.http, ...args);
102+
const dto: TResultDTO = response.data;
103+
104+
const result = mapper(dto);
105+
106+
return result;
107+
}
108+
109+
return { [name]: fetch } as Record<T, (...args: [...TArgs, AbortController]) => Promise<TResult>>;
110+
}
111+
93112
addCreate<TCreateDTO>(url: string | (() => string))
94113
: { create: (dto: TCreateDTO) => Promise<TDetails> } {
95114

tests/Bones.UI.Tests/services/testUserService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const AccountLoginFactory = new ServiceFactory<TestUserDetailsDTO, TestUserDetai
1717
ServiceFactory.addCustom("logout", axios => axios.get(TEST_USERS_URL), (dto: TestUserDetailsDTO) => new TestUserDetails(dto)),
1818
ServiceFactory.addCustom("current", axios => axios.get(TEST_USERS_URL), (dto: TestUserDetailsDTO) => new TestUserDetails(dto)),
1919
ServiceFactory.addCustom("complexCurrent", (axios, p1: string, p2: number) => axios.get(TEST_USERS_URL), (dto: TestUserDetailsDTO) => new TestUserDetails(dto)),
20-
ServiceFactory.addCustom("complexGetMany", (axios, p1: string, p2: number) => axios.get(TEST_USERS_URL), (dto: TestUserDetailsDTO) => new Array(5).map(a => new TestUserDetails(dto))),
20+
ServiceFactory.addCustomCancellable("complexGetMany", (axios, p1: string, p2: number, controller: AbortController) => axios.get(TEST_USERS_URL, { signal: controller.signal }), (dto: TestUserDetailsDTO) => new Array(5).map(a => new TestUserDetails(dto))),
2121
));
2222

2323
export const useTestUsersSync = ComposableFactory.sync<TestUserDetails, TestUserInfos>(testUserServiceFactory);

0 commit comments

Comments
 (0)