Dashboard hosting model improvements

This commit is contained in:
Sipke Schoorstra 2021-03-18 12:18:22 +01:00
parent 832afc1110
commit c62cac9ecb
21 changed files with 85 additions and 58 deletions

View file

@ -1,4 +1,5 @@
using System.Runtime.Serialization;
using Newtonsoft.Json.Linq;
namespace Elsa.Client.Models
{
@ -9,6 +10,6 @@ namespace Elsa.Client.Models
[DataMember(Order = 2)] public string UIHint { get; set; } = default!;
[DataMember(Order = 3)] public string? Label { get; set; }
[DataMember(Order = 4)] public string? Hint { get; set; }
[DataMember(Order = 5)] public object? Options { get; set; }
[DataMember(Order = 5)] public JToken? Options { get; set; }
}
}

View file

@ -27,7 +27,7 @@ namespace ElsaDashboard.Shared.Surrogates
Type = value.UIHint;
Label = value.Label;
Hint = value.Hint;
Options = value.Options != null ? JsonConvert.SerializeObject(value.Options, SerializerSettings) : default;
Options = value.Options?.ToString(Formatting.None);
}
[ProtoMember(1)] public string? Name { get; }
@ -44,7 +44,7 @@ namespace ElsaDashboard.Shared.Surrogates
UIHint = surrogate.Type!,
Hint = surrogate.Hint,
Label = surrogate.Label,
Options = !string.IsNullOrEmpty(surrogate.Options) ? JsonConvert.DeserializeObject<JObject>(surrogate.Options) : new JObject()
Options = surrogate.Options is null or "" ? default : JToken.Parse(surrogate.Options)
}
: default;

View file

@ -20,7 +20,7 @@ namespace ElsaDashboard.Shared.Surrogates
};
/// <summary>
/// Register all NodaTime surrogate types with the protobuf runtime model.
/// Register all surrogate types with the protobuf runtime model.
/// </summary>
public static RuntimeTypeModel AddElsaGrpcSurrogates(this RuntimeTypeModel runtimeTypeModel)
{

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\common.props" />
<Import Project="..\..\..\..\configureawait.props" />
@ -15,8 +15,6 @@
<ItemGroup>
<PackageReference Include="Grpc.Net.Client.Web" Version="2.36.0" />
<PackageReference Include="Microsoft.AspNetCore.Components" Version="5.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="5.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="5.0.2" PrivateAssets="all" />
<PackageReference Include="protobuf-net.NodaTime" Version="3.0.73" />
</ItemGroup>

View file

@ -1,10 +1,12 @@
using System;
using System.Net.Http;
using ElsaDashboard.WebAssembly.Options;
using Grpc.Core;
using Grpc.Net.Client;
using Grpc.Net.Client.Web;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using ProtoBuf.Grpc.Client;
namespace ElsaDashboard.WebAssembly.Extensions
@ -17,8 +19,10 @@ namespace ElsaDashboard.WebAssembly.Extensions
private static GrpcChannel CreateGrpcChannel(IServiceProvider sp)
{
var navigationManager = sp.GetRequiredService<NavigationManager>();
var backendUrl = navigationManager.BaseUri;
var options = sp.GetRequiredService<IOptions<ElsaDashboardWebAssemblyOptions>>();
var backendUrl = options.Value.BackendUrl?.ToString() ?? navigationManager.BaseUri;
var httpClient = new HttpClient(new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler()));
//var tokenManager = sp.GetRequiredService<ITokenManager>();
var credentials = CallCredentials.FromInterceptor(

View file

@ -1,5 +1,7 @@
using ElsaDashboard.Shared.Rpc;
using System;
using ElsaDashboard.Shared.Rpc;
using ElsaDashboard.Shared.Surrogates;
using ElsaDashboard.WebAssembly.Options;
using Microsoft.Extensions.DependencyInjection;
using ProtoBuf.Meta;
@ -7,8 +9,13 @@ namespace ElsaDashboard.WebAssembly.Extensions
{
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddElsaDashboardBackend(this IServiceCollection services)
public static IServiceCollection AddElsaDashboardBackend(this IServiceCollection services, Action<ElsaDashboardWebAssemblyOptions>? configure = default)
{
if (configure != null)
services.Configure(configure);
else
services.AddOptions<ElsaDashboardWebAssemblyOptions>();
RuntimeTypeModel.Default.AddElsaGrpcSurrogates();
RuntimeTypeModel.Default.AddNodaTime();
return services

View file

@ -0,0 +1,14 @@
using System;
namespace ElsaDashboard.WebAssembly.Options
{
public class ElsaDashboardWebAssemblyOptions
{
/// <summary>
/// The URL of the dashboard's backend.
/// When the web assembly project is hosted from an ASP.NET Core host, that host will be used by default.
/// When the web assembly project on the other hand is hosted directly from e.g. blob storage, the URL to the gRPC backend needs to be specified explicitly.
/// </summary>
public Uri? BackendUrl { get; set; }
}
}

View file

@ -3,6 +3,6 @@
public enum BlazorRuntimeModel
{
Server,
Browser
WebAssembly
}
}

View file

@ -19,9 +19,9 @@
<ProjectReference Include="..\..\..\core\Elsa\Elsa.csproj" />
<ProjectReference Include="..\..\..\dashboards\blazor\ElsaDashboard.Application\ElsaDashboard.Application.csproj" />
<ProjectReference Include="..\..\..\dashboards\blazor\ElsaDashboard.Backend\ElsaDashboard.Backend.csproj" />
<ProjectReference Include="..\..\..\dashboards\blazor\ElsaDashboard.WebAssembly\ElsaDashboard.WebAssembly.csproj" />
<ProjectReference Include="..\..\..\persistence\Elsa.Persistence.EntityFramework\Elsa.Persistence.EntityFramework.Sqlite\Elsa.Persistence.EntityFramework.Sqlite.csproj" />
<ProjectReference Include="..\..\..\server\Elsa.Server.Api\Elsa.Server.Api.csproj" />
<ProjectReference Include="..\ElsaDashboard.Samples.WebAssembly\ElsaDashboard.Samples.WebAssembly.csproj" />
</ItemGroup>
</Project>

View file

@ -24,23 +24,17 @@
<!-- Until I figure out why the ESM loader doesn't work properly, we need to include these scripts manually. -->
<script src="https://unpkg.com/monaco-editor@latest/min/vs/loader.js"></script>
<script>
require.config({paths: {'vs': 'https://unpkg.com/monaco-editor@latest/min/vs'}});
window.MonacoEnvironment = {getWorkerUrl: () => proxy};
let proxy = URL.createObjectURL(new Blob([`
self.MonacoEnvironment = {
baseUrl: 'https://unpkg.com/monaco-editor@latest/min/'
};
importScripts('https://unpkg.com/monaco-editor@latest/min/vs/base/worker/workerMain.js');
`], {type: 'text/javascript'}));
</script>
</head>
<body class="antialiased font-sans bg-gray-200">
<component type="typeof(App)" render-mode="@Program.RenderMode"/>
@if (Program.RuntimeModel == BlazorRuntimeModel.Server)
{
<component type="typeof(App)" render-mode="@Program.RenderMode"/>
}
else
{
<div id="app">Loading...</div>
}
<div id="blazor-error-ui">
<environment include="Staging,Production">

View file

@ -3,8 +3,8 @@
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:54938/",
"sslPort": 44399
"applicationUrl": "https://localhost:11000/",
"sslPort": 11000
}
},
"profiles": {
@ -21,7 +21,7 @@
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:5001;http://localhost:5000"
"applicationUrl": "https://localhost:11000"
}
}
}

View file

@ -71,7 +71,7 @@ namespace ElsaDashboard.Samples.Monolith
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Elsa"));
if (Program.RuntimeModel == BlazorRuntimeModel.Browser)
if (Program.RuntimeModel == BlazorRuntimeModel.WebAssembly)
app.UseWebAssemblyDebugging();
}
else
@ -80,7 +80,7 @@ namespace ElsaDashboard.Samples.Monolith
app.UseHsts();
}
if (Program.RuntimeModel == BlazorRuntimeModel.Browser)
if (Program.RuntimeModel == BlazorRuntimeModel.WebAssembly)
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();

View file

@ -9,7 +9,7 @@
"AllowedHosts": "*",
"Elsa": {
"Http": {
"BaseUrl": "https://localhost:3956"
"BaseUrl": "https://localhost:11000"
},
"Smtp": {
"Host": "localhost",
@ -18,7 +18,7 @@
}
},
"Dashboard": {
"ElsaServerUrl": "https://localhost:3956"
"ElsaServerUrl": "https://localhost:11000"
},
"Telnyx": {
"ApiUrl": " https://api.telnyx.com",

View file

@ -3,6 +3,6 @@
public enum BlazorRuntimeModel
{
Server,
Browser
WebAssembly
}
}

View file

@ -17,7 +17,7 @@
<ProjectReference Include="..\..\..\activities\Elsa.Activities.Http\Elsa.Activities.Http.csproj" />
<ProjectReference Include="..\..\..\dashboards\blazor\ElsaDashboard.Application\ElsaDashboard.Application.csproj" />
<ProjectReference Include="..\..\..\dashboards\blazor\ElsaDashboard.Backend\ElsaDashboard.Backend.csproj" />
<ProjectReference Include="..\..\..\dashboards\blazor\ElsaDashboard.WebAssembly\ElsaDashboard.WebAssembly.csproj" />
<ProjectReference Include="..\ElsaDashboard.Samples.WebAssembly\ElsaDashboard.Samples.WebAssembly.csproj" />
</ItemGroup>
</Project>

View file

@ -25,22 +25,17 @@
<!-- Until I figure out why the ESM loader doesn't work properly, we need to include these scripts manually. -->
<script src="https://unpkg.com/monaco-editor@latest/min/vs/loader.js"></script>
<script>
require.config({paths: {'vs': 'https://unpkg.com/monaco-editor@latest/min/vs'}});
window.MonacoEnvironment = {getWorkerUrl: () => proxy};
let proxy = URL.createObjectURL(new Blob([`
self.MonacoEnvironment = {
baseUrl: 'https://unpkg.com/monaco-editor@latest/min/'
};
importScripts('https://unpkg.com/monaco-editor@latest/min/vs/base/worker/workerMain.js');
`], {type: 'text/javascript'}));
</script>
</head>
<body class="antialiased font-sans bg-gray-200">
<component type="typeof(App)" render-mode="@Program.RenderMode"/>
@if (Program.RuntimeModel == BlazorRuntimeModel.Server)
{
<component type="typeof(App)" render-mode="@Program.RenderMode"/>
}
else
{
<div id="app">Loading...</div>
}
<div id="blazor-error-ui">
<environment include="Staging,Production">

View file

@ -6,7 +6,7 @@ namespace ElsaDashboard.Samples.Server
{
public class Program
{
public static BlazorRuntimeModel RuntimeModel => BlazorRuntimeModel.Browser;
public static BlazorRuntimeModel RuntimeModel => BlazorRuntimeModel.WebAssembly;
public static RenderMode RenderMode => RuntimeModel == BlazorRuntimeModel.Server ? RenderMode.ServerPrerendered : RenderMode.WebAssemblyPrerendered;
public static void Main(string[] args)

View file

@ -5,7 +5,7 @@
"dotnetRunMessages": "true",
"launchBrowser": false,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "https://localhost:53451",
"applicationUrl": "https://localhost:54938",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}

View file

@ -1,5 +1,6 @@
using System;
using ElsaDashboard.Backend.Extensions;
using MediatR;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
@ -22,9 +23,13 @@ namespace ElsaDashboard.Samples.Server
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddElsaDashboardUI();
services.AddElsaDashboardBackend(options => options.ServerUrl = Configuration.GetValue<Uri>("Elsa:HostUrl"));
var elsaServerUrl = Configuration.GetValue<Uri>("Elsa:HostUrl");
services
.AddElsaDashboardUI(options => options.ElsaServerUrl = elsaServerUrl)
.AddElsaDashboardBackend(options => options.ServerUrl = elsaServerUrl);
if (Program.RuntimeModel == BlazorRuntimeModel.Server)
services.AddServerSideBlazor(options =>
{
@ -39,7 +44,7 @@ namespace ElsaDashboard.Samples.Server
{
app.UseDeveloperExceptionPage();
if (Program.RuntimeModel == BlazorRuntimeModel.Browser)
if (Program.RuntimeModel == BlazorRuntimeModel.WebAssembly)
app.UseWebAssemblyDebugging();
}
else
@ -48,17 +53,19 @@ namespace ElsaDashboard.Samples.Server
app.UseHsts();
}
if (Program.RuntimeModel == BlazorRuntimeModel.Browser)
if (Program.RuntimeModel == BlazorRuntimeModel.WebAssembly)
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseElsaGrpcServices();
app.UseEndpoints(endpoints =>
{
if (Program.RuntimeModel == BlazorRuntimeModel.Server)
endpoints.MapBlazorHub();
endpoints.MapRazorPages();
endpoints.MapFallbackToPage("/_Host");
});
}

View file

@ -3,6 +3,7 @@ using Microsoft.Extensions.DependencyInjection;
using System;
using System.Net.Http;
using System.Threading.Tasks;
using ElsaDashboard.Application;
using ElsaDashboard.WebAssembly.Extensions;
namespace ElsaDashboard.Samples.WebAssembly
@ -14,9 +15,15 @@ namespace ElsaDashboard.Samples.WebAssembly
var builder = WebAssemblyHostBuilder.CreateDefault(args);
var services = builder.Services;
//builder.RootComponents.Add<App>("#app");
services.AddScoped(sp => new HttpClient {BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)});
services.AddElsaDashboardUI();
builder.RootComponents.Add<App>("#app");
// Change the Elsa Server URL to point to wherever you have hosted Elsa. In this example, Elsa is either hosted by "ElsaDashboard.Samples.Monolith" or "Elsa.Samples.Server.Host"
services.AddElsaDashboardUI(options => options.ElsaServerUrl = new Uri("https://localhost:11000"));
// When the web assembly project is hosted directly from e.g. blob storage, the URL to the gRPC backend needs to be specified explicitly.
// When hosted from ElsaDashboard.Samples.Monolith or ElsaDashboard.Samples.Server, we don't need to specify a backend URL.
//services.AddElsaDashboardBackend(options => options.BackendUrl = new Uri("https://localhost:11000"));
services.AddElsaDashboardBackend();
await builder.Build().RunAsync();

View file

@ -35,7 +35,7 @@ namespace Elsa.Server.Api.Endpoints.WorkflowDefinitions
OperationId = "WorkflowDefinitions.Post",
Tags = new[] { "WorkflowDefinitions" })
]
public async Task<ActionResult<WorkflowDefinition>> Handle(SaveRequest request, ApiVersion apiVersion, CancellationToken cancellationToken)
public async Task<ActionResult<WorkflowDefinition>> Handle([FromBody]SaveRequest request, [FromRoute]ApiVersion apiVersion, CancellationToken cancellationToken)
{
var workflowDefinitionId = request.WorkflowDefinitionId;
var workflowDefinition = !string.IsNullOrWhiteSpace(workflowDefinitionId) ? await _workflowPublisher.GetDraftAsync(workflowDefinitionId, cancellationToken) : default;