Skip to content

Commit

Permalink
Enables a custom expression serializer, and… (#98)
Browse files Browse the repository at this point in the history
 Enables a custom expression serializer, and also a baseclass for just adding known types to the Serialize.Linq JsonSerialize
  • Loading branch information
Tewr authored Feb 22, 2024
1 parent 88a5fa5 commit d11c650
Show file tree
Hide file tree
Showing 12 changed files with 384 additions and 37 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@page "/ComplexSerialization"
<BlazorWorker.Demo.SharedPages.Pages.ComplexSerialization />
133 changes: 133 additions & 0 deletions src/BlazorWorker.Demo/SharedPages/Pages/ComplexSerialization.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
@inject IWorkerFactory workerFactory

@using BlazorWorker.BackgroundServiceFactory
@using BlazorWorker.WorkerBackgroundService
@using BlazorWorker.Demo.Shared
@using BlazorWorker.Core
<div class="row">
<div class="col-6 col-xs-12">
<h1>.NET Worker Multithreading</h1>

Complex / Custom Serialization <br />
<br /><br />
<button @onclick="OnClick" class="btn btn-primary">Run Test</button><br />

<br />
<br />
<strong>Output:</strong>
<hr />
<pre>
@output
</pre>
</div>
<div class="col-6 col-xs-12">
<GithubSource RelativePath="Pages/BackgroundServiceMulti.razor" />
</div>
</div>
@code {

string output;

IWorker worker;
IWorkerBackgroundService<ComplexService> backgroundService;

string RunDisabled => Running ? "disabled" : null;
bool Running = false;


public class ComplexService
{
public ComplexServiceResponse ComplexCall(ComplexServiceArg arg)
{
return new ComplexServiceResponse
{
OriginalArg = arg,
OnlyInResponse = "This is only in response."
};
}
}

public class ComplexServiceArg
{
public string ThisIsJustAString { get; set; }
public OhLookARecord ARecord { get; set; }
public Dictionary<string, string> ADictionary { get; set; }
public ComplexServiceArg TypeRecursive { get; set; }
}

public class ComplexServiceResponse
{
public ComplexServiceArg OriginalArg { get; set; }
public string OnlyInResponse { get; set; }
}

public record OhLookARecord
{
public int Number { get; set; }
}

public async Task OnClick(EventArgs _)
{
Running = true;
await this.InvokeAsync(StateHasChanged);

output = "";
var rn = Environment.NewLine;
try
{
if (worker == null)
{
output += $"Starting worker...{rn}";
await this.InvokeAsync(StateHasChanged);
worker = await workerFactory.CreateAsync();
}
if (backgroundService == null)
{

output += $"Starting BackgroundService...{rn}";
await this.InvokeAsync(StateHasChanged);
/*
* have a look here. This is the essence of this example.
* */

backgroundService = await worker.CreateBackgroundServiceAsync<ComplexService>(options
=> options.UseCustomExpressionSerializer(typeof(CustomSerializeLinqExpressionJsonSerializer)));
}

var sw = System.Diagnostics.Stopwatch.StartNew();

var complexArgInstance = new ComplexServiceArg
{
ThisIsJustAString = "just a string",
ARecord = new OhLookARecord { Number = 5 },
ADictionary = new Dictionary<string, string>
{
{ "Test", "TestValue" }
},
TypeRecursive = new ComplexServiceArg
{
ThisIsJustAString = "SubString"
}
};

var result = await backgroundService.RunAsync(service => service.ComplexCall(complexArgInstance));
var elapsed = sw.ElapsedMilliseconds;
output += $"{rn}result: " + System.Text.Json.JsonSerializer.Serialize(result);
output += $"{rn}roundtrip to worker in {elapsed}ms";
}
catch (Exception e)
{
output += $"{rn}Error = {e}";
}
finally
{
Running = false;
output += $"{rn}Done.";
}
}

private string LogDate()
{
return DateTime.Now.ToString("HH:mm:ss:fff");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using BlazorWorker.WorkerBackgroundService;
using Serialize.Linq.Serializers;
using System;
using System.Linq.Expressions;
using static BlazorWorker.Demo.SharedPages.Pages.ComplexSerialization;

namespace BlazorWorker.Demo.SharedPages.Shared
{
/// <summary>
/// Example 1: Simple custom expression Serializer using <see cref="Serialize.Linq.ExpressionSerializer"/>
/// as base class, but explicitly adds complex types as known types.
/// </summary>
public class CustomSerializeLinqExpressionJsonSerializer : SerializeLinqExpressionJsonSerializerBase
{
public override Type[] GetKnownTypes() =>
[typeof(ComplexServiceArg), typeof(ComplexServiceResponse), typeof(OhLookARecord)];
}

/// <summary>
/// Fully custom Expression Serializer, which uses <see cref="Serialize.Linq.ExpressionSerializer"/> but you could use an alternative implementation.
/// </summary>
public class CustomExpressionSerializer : IExpressionSerializer
{
private readonly ExpressionSerializer serializer;

public CustomExpressionSerializer()
{
var specificSerializer = new JsonSerializer();
specificSerializer.AddKnownType(typeof(ComplexServiceArg));
specificSerializer.AddKnownType(typeof(ComplexServiceResponse));
specificSerializer.AddKnownType(typeof(OhLookARecord));

this.serializer = new ExpressionSerializer(specificSerializer);
}

public Expression Deserialize(string expressionString)
{
return serializer.DeserializeText(expressionString);
}

public string Serialize(Expression expression)
{
return serializer.SerializeText(expression);
}
}

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Components;
using BlazorWorker.Demo.SharedPages.Pages;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Routing;
using System;
using System.Collections.Generic;
Expand Down Expand Up @@ -34,6 +35,10 @@ public class NavMenuLinksModel
{
new() { Icon = "document", Href="IndexedDB", Text = "IndexedDB" }
},
{
new() { Icon = "document", Href="ComplexSerialization", Text = "ComplexSerialization" }
},

{
new() { Icon = "fork", Href="https://github.com/tewr/BlazorWorker", Text = "To the source!" }
},
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,17 @@ public static async Task<IWorkerBackgroundService<T>> CreateBackgroundServiceAsy
{
throw new ArgumentNullException(nameof(webWorkerProxy));
}

var proxy = new WorkerBackgroundServiceProxy<T>(webWorkerProxy, new WebWorkerOptions());
if (workerInitOptions == null)
{
workerInitOptions = new WorkerInitOptions();
}

var webWorkerOptions = new WebWorkerOptions();
webWorkerOptions.SetExpressionSerializerFromInitOptions(workerInitOptions);

var proxy = new WorkerBackgroundServiceProxy<T>(webWorkerProxy, webWorkerOptions);


await proxy.InitAsync(workerInitOptions);
return proxy;
}
Expand Down Expand Up @@ -68,14 +72,32 @@ public static async Task<IWorkerBackgroundService<TService>> CreateBackgroundSer
{
workerInitOptionsModifier(workerInitOptions);
}
var factoryProxy = new WorkerBackgroundServiceProxy<TFactory>(webWorkerProxy, new WebWorkerOptions());

if (workerInitOptions == null)
{
workerInitOptions = new WorkerInitOptions();
}

var webWorkerOptions = new WebWorkerOptions();
webWorkerOptions.SetExpressionSerializerFromInitOptions(workerInitOptions);


var factoryProxy = new WorkerBackgroundServiceProxy<TFactory>(webWorkerProxy, webWorkerOptions);
await factoryProxy.InitAsync(workerInitOptions);

var newProxy = await factoryProxy.InitFromFactoryAsync(factoryExpression);
newProxy.Disposables.Add(factoryProxy);
return newProxy;
}

private static void SetExpressionSerializerFromInitOptions(this WebWorkerOptions target, WorkerInitOptions workerInitOptions)
{
if (workerInitOptions.EnvMap?.TryGetValue(WebWorkerOptions.ExpressionSerializerTypeEnvKey, out var serializerType) == true)
{
target.ExpressionSerializerType = Type.GetType(serializerType);
}
}

/// <summary>
/// Creates a new background service using the specified <paramref name="factoryExpression"/>
/// </summary>
Expand Down
28 changes: 28 additions & 0 deletions src/BlazorWorker.ServiceFactory/WorkerInitExtension.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using BlazorWorker.Core;
using BlazorWorker.WorkerBackgroundService;
using System;

namespace BlazorWorker.BackgroundServiceFactory
{
public static class WorkerInitExtension
{

/// <summary>
/// Sets a custom ExpressionSerializer type. Must implement <see cref="IExpressionSerializer"/>..
/// </summary>
/// <param name="source"></param>
/// <param name="expressionSerializerType">A type that implements <see cref="IExpressionSerializer"/></param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static WorkerInitOptions UseCustomExpressionSerializer(this WorkerInitOptions source, Type expressionSerializerType)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}

source.SetEnv(WebWorkerOptions.ExpressionSerializerTypeEnvKey, expressionSerializerType.AssemblyQualifiedName);
return source;
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using Serialize.Linq.Serializers;
using System;
using System.Linq.Expressions;

namespace BlazorWorker.WorkerBackgroundService
{
/// <summary>
/// Base class for adding known types to a serializer using <see cref="Serialize.Linq.Serializers.JsonSerializer"/>.
/// </summary>
public abstract class SerializeLinqExpressionJsonSerializerBase : IExpressionSerializer
{
private ExpressionSerializer serializer;

private ExpressionSerializer Serializer
=> this.serializer ?? (this.serializer = GetSerializer());

/// <summary>
/// Automatically adds known types as array types. If set to <c>true</c>, sets <see cref="AutoAddKnownTypesAsListTypes"/> to <c>false</c>.
/// </summary>
public bool? AutoAddKnownTypesAsArrayTypes { get; set; }

/// <summary>
/// Automatically adds known types as list types. If set to <c>true</c>, sets <see cref="AutoAddKnownTypesAsArrayTypes"/> to <c>false</c>.
/// </summary>
public bool? AutoAddKnownTypesAsListTypes { get; set; }

public abstract Type[] GetKnownTypes();

private ExpressionSerializer GetSerializer()
{
var jsonSerializer = new JsonSerializer();
foreach (var type in GetKnownTypes())
{
jsonSerializer.AddKnownType(type);
}
if (this.AutoAddKnownTypesAsArrayTypes.HasValue) {
jsonSerializer.AutoAddKnownTypesAsArrayTypes = this.AutoAddKnownTypesAsArrayTypes.Value;
}
if (this.AutoAddKnownTypesAsListTypes.HasValue)
{
jsonSerializer.AutoAddKnownTypesAsListTypes = this.AutoAddKnownTypesAsListTypes.Value;
}

return new ExpressionSerializer(jsonSerializer);
}

public Expression Deserialize(string expressionString)
{
return this.Serializer.DeserializeText(expressionString);
}

public string Serialize(Expression expression)
{
return this.Serializer.SerializeText(expression);
}
}
}
Loading

0 comments on commit d11c650

Please sign in to comment.