Fix gRPC serialization

This commit is contained in:
Sipke Schoorstra 2020-11-14 17:46:25 +01:00
parent be92fe7d74
commit 0038702dd8
33 changed files with 134 additions and 63 deletions

View file

@ -1,5 +1,4 @@
using System;
using System.Text.Json;
using Elsa.Client.Models;
using Newtonsoft.Json;
using JsonSerializer = Newtonsoft.Json.JsonSerializer;

View file

@ -21,6 +21,7 @@
<ItemGroup>
<PackageReference Include="NodaTime" Version="3.0.3" />
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.0.0" />
<PackageReference Include="protobuf-net.Core" Version="3.0.52" />
<PackageReference Include="Refit" Version="5.2.1" />
<PackageReference Include="Refit.HttpClientFactory" Version="5.2.1" />
</ItemGroup>

View file

@ -1,16 +1,17 @@
using System;
using ProtoBuf;
namespace Elsa.Client.Models
{
[ProtoContract]
public class ActivityDescriptor
{
public string Type { get; set; } = default!;
public string DisplayName { get; set; } = default!;
public string? Description { get; set; }
public string? RuntimeDescription { get; set; }
public string Category { get; set; } = default!;
public string? Icon { get; set; }
public string[] Outcomes { get; set; } = new string[0];
public ActivityPropertyDescriptor[] Properties { get; set; } = new ActivityPropertyDescriptor[0];
[ProtoMember(1)] public string Type { get; set; } = default!;
[ProtoMember(2)] public string DisplayName { get; set; } = default!;
[ProtoMember(3)] public string? Description { get; set; }
[ProtoMember(4)] public string? RuntimeDescription { get; set; }
[ProtoMember(5)] public string Category { get; set; } = default!;
[ProtoMember(6)] public string? Icon { get; set; }
[ProtoMember(7)] public string[] Outcomes { get; set; } = new string[0];
[ProtoMember(8)] public ActivityPropertyDescriptor[] Properties { get; set; } = new ActivityPropertyDescriptor[0];
}
}

View file

@ -1,13 +1,15 @@
using Newtonsoft.Json.Linq;
using ProtoBuf;
namespace Elsa.Client.Models
{
[ProtoContract(IgnoreListHandling = true)]
public class ActivityPropertyDescriptor
{
public string Name { get; } = default!;
public string Type { get; } = default!;
public string? Label { get; set; }
public string? Hint { get; set;}
public JObject? Options { get; set;}
[ProtoMember(1)] public string Name { get; } = default!;
[ProtoMember(2)] public string Type { get; } = default!;
[ProtoMember(3)] public string? Label { get; set; }
[ProtoMember(4)] public string? Hint { get; set; }
[ProtoMember(5)] public JObject? Options { get; set; }
}
}

View file

@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Linq;
namespace Elsa.Client.Models
{

View file

@ -11,6 +11,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ElsaDashboard.Application.WebAssembly\ElsaDashboard.Application.WebAssembly.csproj" />
<ProjectReference Include="..\ElsaDashboard.Application\ElsaDashboard.Application.csproj" />
<ProjectReference Include="..\ElsaDashboard.Backend\ElsaDashboard.Backend.csproj" />
</ItemGroup>

View file

@ -1,11 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
namespace ElsaDashboard.Application.Server.Pages
{

View file

@ -6,7 +6,7 @@ namespace ElsaDashboard.Application.Server
{
public class Program
{
public static bool UseBlazorServer = true;
public static bool UseBlazorServer = false;
public static bool UseBlazorWebAssembly => !UseBlazorServer;
public static RenderMode RenderMode => UseBlazorServer ? RenderMode.ServerPrerendered: RenderMode.WebAssemblyPrerendered;

View file

@ -44,18 +44,17 @@ namespace ElsaDashboard.Application.Server
app.UseHsts();
}
app.UseHttpsRedirection();
if(Program.UseBlazorWebAssembly)
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseRouting();
app.UserElsaGrpcServices();
app.UseElsaGrpcServices();
app.UseEndpoints(endpoints =>
{
if(Program.UseBlazorServer)
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}

View file

@ -1,4 +1,3 @@
@using ElsaDashboard.Application.Models
<Router AppAssembly="@typeof(MenuItem).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />

View file

@ -14,8 +14,8 @@ namespace ElsaDashboard.Application.Pages.Designer
protected override async Task OnInitializedAsync()
{
var activities = (await ActivityService.GetActivitiesAsync()).ToList();
ActivityGroupings = activities.GroupBy(x => x.Category).ToList();
var response = (await ActivityService.GetActivitiesAsync());
ActivityGroupings = response.Activities.GroupBy(x => x.Category).ToList();
}
}
}

View file

@ -1,4 +1,4 @@
<div x-show="$store.flyoutPanel.show" x-data="{ open: false }" x-on:keydown.window.escape="$store.flyoutPanel.show = false" class="fixed inset-0 overflow-hidden">
<div x-show="$store.flyoutPanel.show" x-data="{}" x-on:keydown.window.escape="$store.flyoutPanel.show = false" class="fixed inset-0 overflow-hidden">
<div class="absolute inset-0 overflow-hidden">
<section x-on:click.away="$store.flyoutPanel.show = false" class="absolute inset-y-0 right-0 pl-10 max-w-md md:max-w-full flex sm:pl-16">
<div class="w-screen max-w-2xl" x-description="Slide-over panel, show/hide based on slide-over state." x-show="$store.flyoutPanel.show == true" x-transition:enter="transform transition ease-in-out duration-500 sm:duration-700" x-transition:enter-start="translate-x-full" x-transition:enter-end="translate-x-0" x-transition:leave="transform transition ease-in-out duration-500 sm:duration-700" x-transition:leave-start="translate-x-0" x-transition:leave-end="translate-x-full">
@ -15,15 +15,9 @@
</h2>
}
<div class="h-7 flex items-center">
<button @click="open = false; setTimeout(() => open = true, 1000);"
aria-label="Close panel"
class="text-gray-400 hover:text-gray-500 transition ease-in-out duration-150">
<svg class="h-6 w-6" x-description="Heroicon name: x"
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M6 18L18 6M6 6l12 12">
</path>
<button x-on:click="$store.flyoutPanel.show = false" aria-label="Close panel" class="text-gray-400 hover:text-gray-500 transition ease-in-out duration-150">
<svg class="h-6 w-6" x-description="Heroicon name: x" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>

View file

@ -5,9 +5,10 @@ namespace ElsaDashboard.Backend.Extensions
{
public static class ApplicationBuilderExtensions
{
public static IApplicationBuilder UserElsaGrpcServices(this IApplicationBuilder app)
public static IApplicationBuilder UseElsaGrpcServices(this IApplicationBuilder app)
{
return app
.UseCors()
.UseGrpcWeb()
.UseEndpoints(endpoints => endpoints
.MapGrpcEndpoint<IActivityService>()

View file

@ -4,8 +4,10 @@ using Elsa.Client.Extensions;
using Elsa.Client.Options;
using ElsaDashboard.Backend.Rpc;
using ElsaDashboard.Shared.Rpc;
using ElsaDashboard.Shared.Surrogates;
using Microsoft.Extensions.DependencyInjection;
using ProtoBuf.Grpc.Server;
using ProtoBuf.Meta;
namespace ElsaDashboard.Backend.Extensions
{
@ -13,6 +15,8 @@ namespace ElsaDashboard.Backend.Extensions
{
public static IServiceCollection AddElsaDashboardBackend(this IServiceCollection services, Action<ElsaClientOptions>? configure = default)
{
RuntimeTypeModel.Default.AddElsaGrpcSurrogates();
services.AddCors();
services.AddElsaClient(configure);
services.AddCodeFirstGrpc(options => options.ResponseCompressionLevel = CompressionLevel.Optimal);
services.AddScoped<IActivityService, ActivityService>();

View file

@ -1,8 +1,5 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks;
using Elsa.Client;
using Elsa.Client.Models;
using ElsaDashboard.Shared.Rpc;
using ProtoBuf.Grpc;
@ -17,10 +14,10 @@ namespace ElsaDashboard.Backend.Rpc
_elsaClient = elsaClient;
}
public async Task<IEnumerable<ActivityDescriptor>> GetActivitiesAsync(CallContext callContext)
public async Task<GetActivitiesResponse> GetActivitiesAsync(CallContext callContext)
{
var result = await _elsaClient.Activities.ListAsync(callContext.CancellationToken);
return result.Items;
return new GetActivitiesResponse(result.Items);
}
}
}

View file

@ -5,6 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Api.CommonProtos" Version="2.1.0" />
<PackageReference Include="Grpc.Net.Client" Version="2.33.1" />
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.33.1" />
<PackageReference Include="protobuf-net" Version="3.0.52" />

View file

@ -1,5 +1,5 @@
using System.Collections.Generic;
using System.Threading;
using System.ServiceModel;
using System.Threading.Tasks;
using Elsa.Client.Models;
using ProtoBuf;
@ -8,9 +8,25 @@ using ProtoBuf.ServiceModel;
namespace ElsaDashboard.Shared.Rpc
{
[ProtoContract]
[ServiceContract]
public interface IActivityService
{
[ProtoBehavior]Task<IEnumerable<ActivityDescriptor>> GetActivitiesAsync(CallContext context = default);
[ProtoBehavior]
Task<GetActivitiesResponse> GetActivitiesAsync(CallContext context = default);
}
[ProtoContract]
public sealed class GetActivitiesResponse
{
public GetActivitiesResponse()
{
}
public GetActivitiesResponse(IEnumerable<ActivityDescriptor> activities)
{
Activities = activities;
}
[ProtoMember(1)] public IEnumerable<ActivityDescriptor> Activities { get; set; } = new System.Collections.Generic.List<ActivityDescriptor>();
}
}

View file

@ -0,0 +1,45 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NodaTime;
using NodaTime.Serialization.JsonNet;
using ProtoBuf;
namespace ElsaDashboard.Shared.Surrogates
{
/// <summary>
/// Surrogate for <see cref="JToken"/>.
/// </summary>
[ProtoContract(IgnoreListHandling = true)]
public class JTokenSurrogate
{
private static readonly JsonSerializerSettings SerializerSettings;
/// <summary>
/// Default constructor.
/// </summary>
static JTokenSurrogate()
{
SerializerSettings = new JsonSerializerSettings().ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
}
/// <summary>
/// Constructor overload.
/// </summary>
public JTokenSurrogate(JToken value) => Value = JsonConvert.SerializeObject(value, SerializerSettings);
/// <summary>
/// Stores the Google Protobuf Any value for serialization.
/// </summary>
public string Value { get; set; } = default!;
/// <summary>
/// Converts the surrogate to a <see cref="JToken"/>.
/// </summary>
public static implicit operator JToken(JTokenSurrogate surrogate) => JsonConvert.DeserializeObject<JToken>(surrogate.Value, SerializerSettings);
/// <summary>
/// Converts the <see cref="JToken"/> to a surrogate.
/// </summary>
public static implicit operator JTokenSurrogate(JToken source) => new(source);
}
}

View file

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using ProtoBuf.Meta;
namespace ElsaDashboard.Shared.Surrogates
{
/// <summary>
/// Provides an extension method to conveniently register surrogate types with the default <see cref="RuntimeTypeModel"/>.
/// </summary>
public static class RuntimeTypeModelExtensions
{
private static readonly IDictionary<Type, Type> SurrogateMapping = new Dictionary<Type, Type>
{
[typeof(JToken)] = typeof(JTokenSurrogate)
};
/// <summary>
/// Register all NodaTime surrogate types with the protobuf runtime model.
/// </summary>
public static RuntimeTypeModel AddElsaGrpcSurrogates(this RuntimeTypeModel runtimeTypeModel)
{
foreach (var map in SurrogateMapping)
runtimeTypeModel.Add(map.Key, false).SetSurrogate(map.Value);
return runtimeTypeModel;
}
}
}

View file

@ -1,5 +1,7 @@
using ElsaDashboard.Shared.Rpc;
using ElsaDashboard.Shared.Surrogates;
using Microsoft.Extensions.DependencyInjection;
using ProtoBuf.Meta;
namespace ElsaDashboard.WebAssembly.Extensions
{
@ -7,6 +9,7 @@ namespace ElsaDashboard.WebAssembly.Extensions
{
public static IServiceCollection AddElsaDashboardBackend(this IServiceCollection services)
{
RuntimeTypeModel.Default.AddElsaGrpcSurrogates();
return services
.AddGrpcClient<IActivityService>()
.AddGrpcClient<IWorkflowDefinitionService>();

View file

@ -1,14 +1,10 @@
using System.Collections.Generic;
using System.Linq;
using Elsa.Metadata;
using Elsa.Models;
using Elsa.Server.Api.Models;
using Elsa.Server.Api.Swagger;
using Elsa.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Swashbuckle.AspNetCore.Annotations;
using Swashbuckle.AspNetCore.Filters;
namespace Elsa.Server.Api.Endpoints.Activities
{

View file

@ -1,7 +1,6 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Models;
using Elsa.Server.Api.Models;
using Elsa.Server.Api.Swagger;
using Elsa.Services;
using Microsoft.AspNetCore.Http;

View file

@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Linq;
namespace Elsa.Server.Api.Models
{

View file

@ -2,7 +2,6 @@
using System.Threading.Tasks;
using Elsa.Core.IntegrationTests.Helpers;
using Elsa.Models;
using Elsa.Testing.Shared.Helpers;
using Elsa.Testing.Shared.Unit;
using Microsoft.Extensions.DependencyInjection;
using Storage.Net.Blobs;

View file

@ -1,6 +1,5 @@
using System.Threading.Tasks;
using Elsa.Models;
using Elsa.Testing.Shared.Helpers;
using Elsa.Testing.Shared.Unit;
using Xunit;
using Xunit.Abstractions;

View file

@ -1,7 +1,6 @@
using System.Threading.Tasks;
using AutoFixture;
using Elsa.Models;
using Elsa.Testing.Shared.Helpers;
using Elsa.Testing.Shared.Unit;
using Xunit;
using Xunit.Abstractions;

View file

@ -1,7 +1,6 @@
using System.Linq;
using System.Threading.Tasks;
using Elsa.Models;
using Elsa.Testing.Shared.Helpers;
using Elsa.Testing.Shared.Unit;
using Xunit;
using Xunit.Abstractions;

View file

@ -1,6 +1,5 @@
using System.Linq;
using System.Threading.Tasks;
using Elsa.Testing.Shared.Helpers;
using Elsa.Testing.Shared.Unit;
using Xunit;
using Xunit.Abstractions;

View file

@ -5,7 +5,6 @@ using Elsa.Activities.Signaling;
using Elsa.Activities.Signaling.Models;
using Elsa.Models;
using Elsa.Services.Models;
using Elsa.Testing.Shared.Helpers;
using Elsa.Testing.Shared.Unit;
using Xunit;
using Xunit.Abstractions;

View file

@ -1,6 +1,5 @@
using System.Linq;
using System.Threading.Tasks;
using Elsa.Testing.Shared.Helpers;
using Elsa.Testing.Shared.Unit;
using Xunit;
using Xunit.Abstractions;

View file

@ -1,7 +1,6 @@
using System.Linq;
using System.Threading.Tasks;
using Elsa.Models;
using Elsa.Testing.Shared.Helpers;
using Elsa.Testing.Shared.Unit;
using Xunit;
using Xunit.Abstractions;

View file

@ -1,6 +1,5 @@
using System.Linq;
using System.Threading.Tasks;
using Elsa.Testing.Shared.Helpers;
using Elsa.Testing.Shared.Unit;
using Xunit;
using Xunit.Abstractions;

View file

@ -1,6 +1,5 @@
using System.Linq;
using System.Threading.Tasks;
using Elsa.Testing.Shared.Helpers;
using Elsa.Testing.Shared.Unit;
using Xunit;
using Xunit.Abstractions;