From 0edecbd7b6027a00dc78cdf54286545d5409695d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?=
Date: Sun, 27 Nov 2022 20:21:24 +0100
Subject: [PATCH 01/13] Prototype of calling C# web assembly implemented
---
src/DotVVM.sln | 30 ++++++++
.../HelperNamespace/CsharpBindingApi.cs | 16 ++++
.../Binding/ViewModuleReferenceInfo.cs | 13 +++-
.../ControlTree/BindingExtensionParameter.cs | 44 +++++++++++
.../ControlTree/ControlTreeResolverBase.cs | 12 ++-
.../IAbstractCsharpViewModuleDirective.cs | 7 ++
.../ControlTree/IAbstractTreeBuilder.cs | 1 +
.../ResolvedCsharpViewModuleDirective.cs | 18 +++++
.../Resolved/ResolvedTreeBuilder.cs | 3 +
.../CSharpViewModuleCompilationResult.cs | 6 ++
.../CsharpViewModuleDirectiveCompiler.cs | 77 +++++++++++++++++++
.../MarkupDirectiveCompilerPipeline.cs | 10 +++
.../Directives/MarkupPageMetadata.cs | 1 +
.../Directives/ViewModuleDirectiveCompiler.cs | 7 +-
.../JavascriptTranslatableMethodCollection.cs | 1 +
.../Compilation/Parser/ParserConstants.cs | 3 +-
.../Configuration/DotvvmConfiguration.cs | 7 ++
.../Framework/Controls/DotvvmMarkupControl.cs | 2 +-
src/Framework/Framework/Controls/Internal.cs | 3 +
.../Framework/DotVVM.Framework.csproj | 5 ++
.../ResourceManagement/ResourceConstants.cs | 1 +
.../ViewModuleImportResource.cs | 13 ++--
.../ViewModuleInitResource.cs | 22 +++++-
.../viewModules/dotnetWasmViewModule.ts | 67 ++++++++++++++++
.../Scripts/viewModules/viewModuleManager.ts | 13 +++-
src/Framework/Framework/package.json | 3 +-
...DotVVM.Framework.Interop.DotnetWasm.csproj | 11 +++
.../Interop.DotnetWasm/DotnetWasmInterop.cs | 69 +++++++++++++++++
.../DotvvmClientSerializer.cs | 16 ++++
.../Interop.DotnetWasm/IViewModuleCommand.cs | 7 ++
.../Interop.DotnetWasm/IViewModuleContext.cs | 11 +++
.../Interop.DotnetWasm/ViewModuleCommand.cs | 27 +++++++
.../Interop.DotnetWasm/ViewModuleContext.cs | 29 +++++++
...mples.BasicSamples.AspNetCoreLatest.csproj | 1 +
.../Properties/launchSettings.json | 2 +
src/Samples/AspNetCoreLatest/Startup.cs | 13 ++++
...M.Samples.BasicSamples.CSharpClient.csproj | 15 ++++
src/Samples/CSharpClient/Program.cs | 9 +++
src/Samples/CSharpClient/TestCsharpModule.cs | 42 ++++++++++
src/Samples/CSharpClient/main.js | 48 ++++++++++++
.../Common/DotVVM.Samples.Common.csproj | 1 +
.../CsharpClient/CSharpClientViewModel.cs | 20 +++++
.../CsharpClient/CSharpClient.dothtml | 34 ++++++++
43 files changed, 714 insertions(+), 26 deletions(-)
create mode 100644 src/Framework/Framework/Binding/HelperNamespace/CsharpBindingApi.cs
create mode 100644 src/Framework/Framework/Compilation/ControlTree/IAbstractCsharpViewModuleDirective.cs
create mode 100644 src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedCsharpViewModuleDirective.cs
create mode 100644 src/Framework/Framework/Compilation/Directives/CSharpViewModuleCompilationResult.cs
create mode 100644 src/Framework/Framework/Compilation/Directives/CsharpViewModuleDirectiveCompiler.cs
create mode 100644 src/Framework/Framework/Resources/Scripts/viewModules/dotnetWasmViewModule.ts
create mode 100644 src/Framework/Interop.DotnetWasm/DotVVM.Framework.Interop.DotnetWasm.csproj
create mode 100644 src/Framework/Interop.DotnetWasm/DotnetWasmInterop.cs
create mode 100644 src/Framework/Interop.DotnetWasm/DotvvmClientSerializer.cs
create mode 100644 src/Framework/Interop.DotnetWasm/IViewModuleCommand.cs
create mode 100644 src/Framework/Interop.DotnetWasm/IViewModuleContext.cs
create mode 100644 src/Framework/Interop.DotnetWasm/ViewModuleCommand.cs
create mode 100644 src/Framework/Interop.DotnetWasm/ViewModuleContext.cs
create mode 100644 src/Samples/CSharpClient/DotVVM.Samples.BasicSamples.CSharpClient.csproj
create mode 100644 src/Samples/CSharpClient/Program.cs
create mode 100644 src/Samples/CSharpClient/TestCsharpModule.cs
create mode 100644 src/Samples/CSharpClient/main.js
create mode 100644 src/Samples/Common/ViewModels/FeatureSamples/CsharpClient/CSharpClientViewModel.cs
create mode 100644 src/Samples/Common/Views/FeatureSamples/CsharpClient/CSharpClient.dothtml
diff --git a/src/DotVVM.sln b/src/DotVVM.sln
index 3542ef1634..dd4357ba49 100644
--- a/src/DotVVM.sln
+++ b/src/DotVVM.sln
@@ -123,6 +123,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotVVM.Framework.Controls.D
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotVVM.Framework.Controls.DynamicData", "DynamicData\DynamicData\DotVVM.Framework.Controls.DynamicData.csproj", "{9E19A537-E1B2-4D1E-A904-D99D4222474F}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotVVM.Samples.BasicSamples.CSharpClient", "Samples\CSharpClient\DotVVM.Samples.BasicSamples.CSharpClient.csproj", "{A51F80E0-DA25-476C-BBF5-FCF79E6C67D4}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotVVM.Framework.Interop.DotnetWasm", "Framework\Interop.DotnetWasm\DotVVM.Framework.Interop.DotnetWasm.csproj", "{D8898963-091F-4398-AFC6-CDB4ED6A98A2}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -697,6 +701,30 @@ Global
{9E19A537-E1B2-4D1E-A904-D99D4222474F}.Release|x64.Build.0 = Release|Any CPU
{9E19A537-E1B2-4D1E-A904-D99D4222474F}.Release|x86.ActiveCfg = Release|Any CPU
{9E19A537-E1B2-4D1E-A904-D99D4222474F}.Release|x86.Build.0 = Release|Any CPU
+ {A51F80E0-DA25-476C-BBF5-FCF79E6C67D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A51F80E0-DA25-476C-BBF5-FCF79E6C67D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A51F80E0-DA25-476C-BBF5-FCF79E6C67D4}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A51F80E0-DA25-476C-BBF5-FCF79E6C67D4}.Debug|x64.Build.0 = Debug|Any CPU
+ {A51F80E0-DA25-476C-BBF5-FCF79E6C67D4}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A51F80E0-DA25-476C-BBF5-FCF79E6C67D4}.Debug|x86.Build.0 = Debug|Any CPU
+ {A51F80E0-DA25-476C-BBF5-FCF79E6C67D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A51F80E0-DA25-476C-BBF5-FCF79E6C67D4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A51F80E0-DA25-476C-BBF5-FCF79E6C67D4}.Release|x64.ActiveCfg = Release|Any CPU
+ {A51F80E0-DA25-476C-BBF5-FCF79E6C67D4}.Release|x64.Build.0 = Release|Any CPU
+ {A51F80E0-DA25-476C-BBF5-FCF79E6C67D4}.Release|x86.ActiveCfg = Release|Any CPU
+ {A51F80E0-DA25-476C-BBF5-FCF79E6C67D4}.Release|x86.Build.0 = Release|Any CPU
+ {D8898963-091F-4398-AFC6-CDB4ED6A98A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D8898963-091F-4398-AFC6-CDB4ED6A98A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D8898963-091F-4398-AFC6-CDB4ED6A98A2}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D8898963-091F-4398-AFC6-CDB4ED6A98A2}.Debug|x64.Build.0 = Debug|Any CPU
+ {D8898963-091F-4398-AFC6-CDB4ED6A98A2}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D8898963-091F-4398-AFC6-CDB4ED6A98A2}.Debug|x86.Build.0 = Debug|Any CPU
+ {D8898963-091F-4398-AFC6-CDB4ED6A98A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D8898963-091F-4398-AFC6-CDB4ED6A98A2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D8898963-091F-4398-AFC6-CDB4ED6A98A2}.Release|x64.ActiveCfg = Release|Any CPU
+ {D8898963-091F-4398-AFC6-CDB4ED6A98A2}.Release|x64.Build.0 = Release|Any CPU
+ {D8898963-091F-4398-AFC6-CDB4ED6A98A2}.Release|x86.ActiveCfg = Release|Any CPU
+ {D8898963-091F-4398-AFC6-CDB4ED6A98A2}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -753,6 +781,8 @@ Global
{DB0AB0C3-DA5E-4B5A-9CD4-036D37B50AED} = {E57EE0B8-30FC-4702-B310-FB82C19D7473}
{3209E1B1-88BB-4A95-B234-950E89EFCEE0} = {CF90322D-63BC-4047-BFEA-EE87E45020AF}
{9E19A537-E1B2-4D1E-A904-D99D4222474F} = {CF90322D-63BC-4047-BFEA-EE87E45020AF}
+ {A51F80E0-DA25-476C-BBF5-FCF79E6C67D4} = {DC6E006E-EE9D-481D-B94C-8A53331BCBC1}
+ {D8898963-091F-4398-AFC6-CDB4ED6A98A2} = {F211156C-FEE6-464C-A7A7-317D16DD3D8B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {61F8A195-365E-47B1-A6F2-CD3534E918F8}
diff --git a/src/Framework/Framework/Binding/HelperNamespace/CsharpBindingApi.cs b/src/Framework/Framework/Binding/HelperNamespace/CsharpBindingApi.cs
new file mode 100644
index 0000000000..410a4e7425
--- /dev/null
+++ b/src/Framework/Framework/Binding/HelperNamespace/CsharpBindingApi.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using DotVVM.Framework.Compilation.Javascript;
+
+namespace DotVVM.Framework.Binding.HelperNamespace
+{
+ public class CsharpBindingApi
+ {
+ public static void RegisterJavascriptTranslations(JavascriptTranslatableMethodCollection collection)
+ {
+
+
+ }
+ }
+}
diff --git a/src/Framework/Framework/Binding/ViewModuleReferenceInfo.cs b/src/Framework/Framework/Binding/ViewModuleReferenceInfo.cs
index 924f37f743..780660b7e0 100644
--- a/src/Framework/Framework/Binding/ViewModuleReferenceInfo.cs
+++ b/src/Framework/Framework/Binding/ViewModuleReferenceInfo.cs
@@ -17,14 +17,17 @@ namespace DotVVM.Framework.Binding
[HandleAsImmutableObjectInDotvvmProperty]
public sealed class ViewModuleReferenceInfo
{
- public string[] ReferencedModules { get; }
+ public ViewModuleReferencedModule[] ReferencedModules { get; }
+
/// The modules are referenced under an Id to the dotvvm client-side runtime. The same ID must be used in the invocation from the _js literal.
public string ViewId { get; }
/// Whether control id should be used instead of ViewId to identify the modules.
public bool IsMarkupControl { get; }
- public ViewModuleReferenceInfo(string viewId, string[] referencedModules, bool isMarkupControl)
+ public string?[] ModuleInstanceArgs { get; }
+
+ public ViewModuleReferenceInfo(string viewId, ViewModuleReferencedModule[] referencedModules, bool isMarkupControl)
{
this.ViewId = viewId;
this.IsMarkupControl = isMarkupControl;
@@ -46,7 +49,7 @@ public ViewModuleReferenceInfo(string viewId, string[] referencedModules, bool i
internal (ViewModuleImportResource importResource, ViewModuleInitResource initResource) BuildResources(IDotvvmResourceRepository allResources)
{
var dependencies = ReferencedModules.SelectMany((moduleResourceName, index) => {
- var moduleResource = allResources.FindResource(moduleResourceName);
+ var moduleResource = allResources.FindResource(moduleResourceName.ModuleName);
if (moduleResource is null)
throw new Exception($"Cannot find resource named '{moduleResourceName}' referenced by the @js directive!");
if (!(moduleResource is ScriptModuleResource))
@@ -63,8 +66,10 @@ public ViewModuleReferenceInfo(string viewId, string[] referencedModules, bool i
private string GenerateModuleBatchUniqueId()
{
using var sha = SHA256.Create();
- return Convert.ToBase64String(sha.ComputeHash(Encoding.Unicode.GetBytes(string.Join("\0", this.ReferencedModules))))
+ return Convert.ToBase64String(sha.ComputeHash(Encoding.Unicode.GetBytes(string.Join("\0", this.ReferencedModules.Select(r => r.ModuleName + "\0" + r.InitArguments != null ? string.Join("\0", r.InitArguments) : "")))))
.Replace("/", "_").Replace("+", "-").Replace("=", "");
}
}
+
+ public record ViewModuleReferencedModule(string ModuleName, string[]? InitArguments = null);
}
diff --git a/src/Framework/Framework/Compilation/ControlTree/BindingExtensionParameter.cs b/src/Framework/Framework/Compilation/ControlTree/BindingExtensionParameter.cs
index 87fd7619c7..33104f2758 100644
--- a/src/Framework/Framework/Compilation/ControlTree/BindingExtensionParameter.cs
+++ b/src/Framework/Framework/Compilation/ControlTree/BindingExtensionParameter.cs
@@ -211,6 +211,50 @@ public ViewModuleAnnotation(string id, bool isMarkupControl)
}
}
+ public class CSharpExtensionParameter : BindingExtensionParameter
+ {
+ public string Id { get; }
+ public bool IsMarkupControl { get; }
+ public ITypeDescriptor Type { get; }
+
+ public CSharpExtensionParameter(string id, bool isMarkupControl, ITypeDescriptor type) : base("_csharp", type, true)
+ {
+ this.Id = id;
+ this.IsMarkupControl = isMarkupControl;
+ this.Type = type;
+ }
+
+ public override Expression GetServerEquivalent(Expression controlParameter)
+ {
+ var type = ResolvedTypeDescriptor.ToSystemType(this.Type);
+ var constructors = type.GetConstructors(BindingFlags.Public);
+ if (constructors.Length != 1 || constructors[0].GetParameters().Length != 1)
+ {
+ throw new DotvvmCompilationException($"The type {type} referenced in the @csharp directive must have exactly one public constructor with one parameter of IViewModuleContext!");
+ }
+ // TODO: check parameter type
+ return Expression.New(constructors[0], Expression.Constant(null, typeof(object)));
+ }
+
+ public override JsExpression GetJsTranslation(JsExpression dataContext)
+ {
+ return new JsIdentifierExpression("dotvvm").Member("viewModules").WithAnnotation(new ViewModuleAnnotation(Id, IsMarkupControl, ResolvedTypeDescriptor.ToSystemType(this.Type)));
+ }
+
+ public class ViewModuleAnnotation
+ {
+ public ViewModuleAnnotation(string id, bool isMarkupControl, Type type)
+ {
+ Id = id;
+ IsMarkupControl = isMarkupControl;
+ Type = type;
+ }
+ public string Id { get; }
+ public bool IsMarkupControl { get; }
+ public Type Type { get; }
+ }
+ }
+
public class CurrentUserExtensionParameter : BindingExtensionParameter
{
public CurrentUserExtensionParameter() : base("_user", new ResolvedTypeDescriptor(typeof(ClaimsPrincipal)), true)
diff --git a/src/Framework/Framework/Compilation/ControlTree/ControlTreeResolverBase.cs b/src/Framework/Framework/Compilation/ControlTree/ControlTreeResolverBase.cs
index aa9d9cae7a..ffad7bc1d2 100644
--- a/src/Framework/Framework/Compilation/ControlTree/ControlTreeResolverBase.cs
+++ b/src/Framework/Framework/Compilation/ControlTree/ControlTreeResolverBase.cs
@@ -56,7 +56,8 @@ public virtual IAbstractTreeRoot ResolveTree(DothtmlRootNode root, string fileNa
new BindingPageInfoExtensionParameter(),
new BindingApiExtensionParameter()
}.Concat(directiveMetadata.InjectedServices)
- .Concat(directiveMetadata.ViewModuleResult is null ? new BindingExtensionParameter[0] : new[] { directiveMetadata.ViewModuleResult.ExtensionParameter }).ToArray());
+ .Concat(directiveMetadata.ViewModuleResult is null ? new BindingExtensionParameter[0] : new[] { directiveMetadata.ViewModuleResult.ExtensionParameter })
+ .Concat(directiveMetadata.CSharpViewModuleResult is null ? new BindingExtensionParameter[0] : new[] { directiveMetadata.CSharpViewModuleResult.ExtensionParameter }).ToArray());
var view = treeBuilder.BuildTreeRoot(this, viewMetadata, root, dataContextTypeStack, directiveMetadata.Directives, directiveMetadata.MasterPage);
view.FileName = fileName;
@@ -70,6 +71,15 @@ out _
);
}
+ if (directiveMetadata.CSharpViewModuleResult is { })
+ {
+ treeBuilder.AddProperty(
+ view,
+ treeBuilder.BuildPropertyValue(Internal.ReferencedCSharpViewModuleInfoProperty, directiveMetadata.CSharpViewModuleResult.Reference, null),
+ out _
+ );
+ }
+
ResolveRootContent(root, view, viewMetadata);
return view;
diff --git a/src/Framework/Framework/Compilation/ControlTree/IAbstractCsharpViewModuleDirective.cs b/src/Framework/Framework/Compilation/ControlTree/IAbstractCsharpViewModuleDirective.cs
new file mode 100644
index 0000000000..b3f8822c24
--- /dev/null
+++ b/src/Framework/Framework/Compilation/ControlTree/IAbstractCsharpViewModuleDirective.cs
@@ -0,0 +1,7 @@
+namespace DotVVM.Framework.Compilation.ControlTree;
+
+public interface IAbstractCsharpViewModuleDirective : IAbstractDirective
+{
+ /// Full type name of the module specified
+ ITypeDescriptor ModuleType { get; }
+}
diff --git a/src/Framework/Framework/Compilation/ControlTree/IAbstractTreeBuilder.cs b/src/Framework/Framework/Compilation/ControlTree/IAbstractTreeBuilder.cs
index af6509e636..f5f867e9c7 100644
--- a/src/Framework/Framework/Compilation/ControlTree/IAbstractTreeBuilder.cs
+++ b/src/Framework/Framework/Compilation/ControlTree/IAbstractTreeBuilder.cs
@@ -28,6 +28,7 @@ public interface IAbstractTreeBuilder
IAbstractBaseTypeDirective BuildBaseTypeDirective(DothtmlDirectiveNode directive, BindingParserNode nameSyntax, ImmutableList imports);
IAbstractViewModuleDirective BuildViewModuleDirective(DothtmlDirectiveNode directiveNode, string modulePath, string resourceName);
+ IAbstractCsharpViewModuleDirective BuildCsharpViewModuleDirective(DothtmlDirectiveNode directiveNode, BindingParserNode moduleType, ImmutableList imports);
IAbstractPropertyDeclarationDirective BuildPropertyDeclarationDirective(DothtmlDirectiveNode directive, TypeReferenceBindingParserNode typeSyntax, SimpleNameBindingParserNode nameSyntax, BindingParserNode? initializer, IList resolvedAttributes, BindingParserNode valueSyntaxRoot, ImmutableList imports);
IAbstractDirectiveAttributeReference BuildPropertyDeclarationAttributeReference(DothtmlDirectiveNode directiveNode, IdentifierNameBindingParserNode propertyNameSyntax, ActualTypeReferenceBindingParserNode typeSyntax, LiteralExpressionBindingParserNode initializer, ImmutableList imports);
IAbstractPropertyBinding BuildPropertyBinding(IPropertyDescriptor property, IAbstractBinding binding, DothtmlAttributeNode? sourceAttributeNode);
diff --git a/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedCsharpViewModuleDirective.cs b/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedCsharpViewModuleDirective.cs
new file mode 100644
index 0000000000..bc903e02ca
--- /dev/null
+++ b/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedCsharpViewModuleDirective.cs
@@ -0,0 +1,18 @@
+using System.Collections.Immutable;
+using DotVVM.Framework.Compilation.Parser.Binding.Parser;
+using DotVVM.Framework.Compilation.Parser.Dothtml.Parser;
+
+namespace DotVVM.Framework.Compilation.ControlTree.Resolved;
+
+/// Represents the @csharp directive - import .NET WASM module on the client side
+public class ResolvedCsharpViewModuleDirective : ResolvedDirective, IAbstractCsharpViewModuleDirective
+{
+ /// Full .NET type of the module
+ public ITypeDescriptor ModuleType { get; }
+
+ public ResolvedCsharpViewModuleDirective(DirectiveCompilationService directiveCompilationService, DothtmlDirectiveNode node, BindingParserNode typeName, ImmutableList imports)
+ : base(node)
+ {
+ ModuleType = directiveCompilationService.ResolveType(node, typeName, imports);
+ }
+}
diff --git a/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedTreeBuilder.cs b/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedTreeBuilder.cs
index e0ea0beadf..6a065cccc9 100644
--- a/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedTreeBuilder.cs
+++ b/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedTreeBuilder.cs
@@ -97,6 +97,9 @@ public IAbstractBaseTypeDirective BuildBaseTypeDirective(DothtmlDirectiveNode di
public IAbstractViewModuleDirective BuildViewModuleDirective(DothtmlDirectiveNode directiveNode, string modulePath, string resourceName) =>
new ResolvedViewModuleDirective(directiveNode, modulePath, resourceName);
+ public IAbstractCsharpViewModuleDirective BuildCsharpViewModuleDirective(DothtmlDirectiveNode directiveNode, BindingParserNode typeName, ImmutableList imports) =>
+ new ResolvedCsharpViewModuleDirective(directiveService, directiveNode, typeName, imports);
+
public IAbstractPropertyDeclarationDirective BuildPropertyDeclarationDirective(
DothtmlDirectiveNode directive,
TypeReferenceBindingParserNode typeSyntax,
diff --git a/src/Framework/Framework/Compilation/Directives/CSharpViewModuleCompilationResult.cs b/src/Framework/Framework/Compilation/Directives/CSharpViewModuleCompilationResult.cs
new file mode 100644
index 0000000000..bc8e68e3d4
--- /dev/null
+++ b/src/Framework/Framework/Compilation/Directives/CSharpViewModuleCompilationResult.cs
@@ -0,0 +1,6 @@
+using DotVVM.Framework.Binding;
+using DotVVM.Framework.Compilation.ControlTree;
+
+namespace DotVVM.Framework.Compilation.Directives;
+
+public record CSharpViewModuleCompilationResult(CSharpExtensionParameter ExtensionParameter, ViewModuleReferenceInfo Reference);
diff --git a/src/Framework/Framework/Compilation/Directives/CsharpViewModuleDirectiveCompiler.cs b/src/Framework/Framework/Compilation/Directives/CsharpViewModuleDirectiveCompiler.cs
new file mode 100644
index 0000000000..1ef4a5a915
--- /dev/null
+++ b/src/Framework/Framework/Compilation/Directives/CsharpViewModuleDirectiveCompiler.cs
@@ -0,0 +1,77 @@
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using DotVVM.Framework.Binding;
+using DotVVM.Framework.Compilation.ControlTree;
+using DotVVM.Framework.Compilation.ControlTree.Resolved;
+using DotVVM.Framework.Compilation.Parser;
+using DotVVM.Framework.Compilation.Parser.Dothtml.Parser;
+using DotVVM.Framework.Compilation.ViewCompiler;
+using DotVVM.Framework.ResourceManagement;
+
+namespace DotVVM.Framework.Compilation.Directives;
+
+public class CsharpViewModuleDirectiveCompiler : DirectiveCompiler
+{
+ private readonly IAbstractControlBuilderDescriptor? masterPage;
+ private readonly bool isMarkupControl;
+ private readonly ImmutableList imports;
+
+ public CsharpViewModuleDirectiveCompiler(IReadOnlyDictionary> directiveNodesByName, IAbstractTreeBuilder treeBuilder, IAbstractControlBuilderDescriptor? masterPage, bool isMarkupControl, ImmutableList imports)
+ : base(directiveNodesByName, treeBuilder)
+ {
+ this.masterPage = masterPage;
+ this.isMarkupControl = isMarkupControl;
+ this.imports = imports;
+ }
+
+ public override string DirectiveName => ParserConstants.CsharpViewModuleDirective;
+
+ protected override CSharpViewModuleCompilationResult? CreateArtefact(IReadOnlyList resolvedDirectives)
+ {
+ var id = AssignViewModuleId(masterPage);
+ return ResolveImportedViewModules(resolvedDirectives, id);
+ }
+
+ private CSharpViewModuleCompilationResult? ResolveImportedViewModules(IReadOnlyList moduleDirectives, string id)
+ {
+ if (moduleDirectives.Count == 0)
+ {
+ return null;
+ }
+
+ if (moduleDirectives.Count > 1)
+ {
+ moduleDirectives[1].DothtmlNode!.AddError("There can be only one @csharp directive in the page!");
+ return null;
+ }
+
+ var x = moduleDirectives[0];
+ if (x.ModuleType == null)
+ {
+ moduleDirectives[0].DothtmlNode!.AddError($"The type {moduleDirectives[0].Value} was not found. Make sure the namespace and assembly name is correct.");
+ return null;
+ }
+
+ var info = new ViewModuleReferenceInfo(
+ id,
+ new[] { new ViewModuleReferencedModule(ResourceConstants.DotvvmDotnetWasmInteropResourceName, new[] { x.ModuleType.FullName + ", "+ x.ModuleType.Assembly }) },
+ isMarkupControl);
+
+ return new CSharpViewModuleCompilationResult(new CSharpExtensionParameter(id, isMarkupControl, moduleDirectives[0].ModuleType), info);
+ }
+
+ protected virtual string AssignViewModuleId(IAbstractControlBuilderDescriptor? masterPage)
+ {
+ var numberOfMasterPages = 0;
+ while (masterPage != null)
+ {
+ masterPage = masterPage.MasterPage;
+ numberOfMasterPages += 1;
+ }
+ return "p" + numberOfMasterPages;
+ }
+
+ protected override IAbstractCsharpViewModuleDirective Resolve(DothtmlDirectiveNode directiveNode) =>
+ TreeBuilder.BuildCsharpViewModuleDirective(directiveNode, ParseDirective(directiveNode, p => p.ReadDirectiveTypeName()), imports);
+}
diff --git a/src/Framework/Framework/Compilation/Directives/MarkupDirectiveCompilerPipeline.cs b/src/Framework/Framework/Compilation/Directives/MarkupDirectiveCompilerPipeline.cs
index 2475e84115..8ba5dcdddf 100644
--- a/src/Framework/Framework/Compilation/Directives/MarkupDirectiveCompilerPipeline.cs
+++ b/src/Framework/Framework/Compilation/Directives/MarkupDirectiveCompilerPipeline.cs
@@ -62,6 +62,15 @@ public MarkupPageMetadata Compile(DothtmlRootNode dothtmlRoot, string fileName)
var viewModuleResult = viewModuleDirectiveCompiler.Compile();
resolvedDirectives.AddIfAny(viewModuleDirectiveCompiler.DirectiveName, viewModuleResult.Directives);
+ var csharpViewModuleDirectiveCompiler = new CsharpViewModuleDirectiveCompiler(
+ directivesByName,
+ treeBuilder,
+ masterPage,
+ !baseType.IsEqualTo(ResolvedTypeDescriptor.Create(typeof(DotvvmView))),
+ imports);
+ var csharpViewModuleResult = csharpViewModuleDirectiveCompiler.Compile();
+ resolvedDirectives.AddIfAny(csharpViewModuleDirectiveCompiler.DirectiveName, csharpViewModuleResult.Directives);
+
var propertyDirectiveCompiler = new PropertyDeclarationDirectiveCompiler(directivesByName, treeBuilder, baseType, imports);
var propertyResult = propertyDirectiveCompiler.Compile();
resolvedDirectives.AddIfAny(propertyDirectiveCompiler.DirectiveName, propertyResult.Directives);
@@ -84,6 +93,7 @@ public MarkupPageMetadata Compile(DothtmlRootNode dothtmlRoot, string fileName)
baseType,
viewModelType.TypeDescriptor,
viewModuleResult.Artefact,
+ csharpViewModuleResult.Artefact,
propertyResult.Artefact);
}
}
diff --git a/src/Framework/Framework/Compilation/Directives/MarkupPageMetadata.cs b/src/Framework/Framework/Compilation/Directives/MarkupPageMetadata.cs
index 1908e89a10..c052d37503 100644
--- a/src/Framework/Framework/Compilation/Directives/MarkupPageMetadata.cs
+++ b/src/Framework/Framework/Compilation/Directives/MarkupPageMetadata.cs
@@ -14,5 +14,6 @@ public record MarkupPageMetadata(
ITypeDescriptor BaseType,
ITypeDescriptor? ViewModelType,
ViewModuleCompilationResult? ViewModuleResult,
+ CSharpViewModuleCompilationResult? CSharpViewModuleResult,
ImmutableList Properties);
}
diff --git a/src/Framework/Framework/Compilation/Directives/ViewModuleDirectiveCompiler.cs b/src/Framework/Framework/Compilation/Directives/ViewModuleDirectiveCompiler.cs
index fa642a9a42..115cd00259 100644
--- a/src/Framework/Framework/Compilation/Directives/ViewModuleDirectiveCompiler.cs
+++ b/src/Framework/Framework/Compilation/Directives/ViewModuleDirectiveCompiler.cs
@@ -23,7 +23,7 @@ public ViewModuleDirectiveCompiler(IReadOnlyDictionary ParserConstants.ViewModuleDirective;
+ public override string DirectiveName => ParserConstants.JsViewModuleDirective;
protected override ViewModuleCompilationResult? CreateArtefact(IReadOnlyList resolvedDirectives)
{
@@ -54,7 +54,7 @@ public ViewModuleDirectiveCompiler(IReadOnlyDictionary
TreeBuilder.BuildViewModuleDirective(directiveNode, modulePath: directiveNode.Value, resourceName: directiveNode.Value);
-}
-
+ }
}
diff --git a/src/Framework/Framework/Compilation/Javascript/JavascriptTranslatableMethodCollection.cs b/src/Framework/Framework/Compilation/Javascript/JavascriptTranslatableMethodCollection.cs
index e3bef8df6e..b1feafaa37 100644
--- a/src/Framework/Framework/Compilation/Javascript/JavascriptTranslatableMethodCollection.cs
+++ b/src/Framework/Framework/Compilation/Javascript/JavascriptTranslatableMethodCollection.cs
@@ -159,6 +159,7 @@ JsExpression dictionarySetIndexer(JsExpression[] args, MethodInfo method) =>
new GenericMethodCompiler(args => new JsBinaryExpression(args[0], BinaryOperatorType.NotEqual, new JsLiteral(null))));
JsBindingApi.RegisterJavascriptTranslations(this);
+ CsharpBindingApi.RegisterJavascriptTranslations(this);
BindingApi.RegisterJavascriptTranslations(this);
BindingPageInfo.RegisterJavascriptTranslations(this);
BindingCollectionInfo.RegisterJavascriptTranslations(this);
diff --git a/src/Framework/Framework/Compilation/Parser/ParserConstants.cs b/src/Framework/Framework/Compilation/Parser/ParserConstants.cs
index 145f5d54f4..a852c4041f 100644
--- a/src/Framework/Framework/Compilation/Parser/ParserConstants.cs
+++ b/src/Framework/Framework/Compilation/Parser/ParserConstants.cs
@@ -11,7 +11,8 @@ public class ParserConstants
public const string WrapperTagNameDirective = "wrapperTag";
public const string NoWrapperTagNameDirective = "noWrapperTag";
public const string ServiceInjectDirective = "service";
- public const string ViewModuleDirective = "js";
+ public const string JsViewModuleDirective = "js";
+ public const string CsharpViewModuleDirective = "csharp";
public const string ValueBinding = "value";
public const string CommandBinding = "command";
diff --git a/src/Framework/Framework/Configuration/DotvvmConfiguration.cs b/src/Framework/Framework/Configuration/DotvvmConfiguration.cs
index a3ca0cf583..2c803fec79 100644
--- a/src/Framework/Framework/Configuration/DotvvmConfiguration.cs
+++ b/src/Framework/Framework/Configuration/DotvvmConfiguration.cs
@@ -385,6 +385,13 @@ private static void RegisterResources(DotvvmConfiguration configuration)
typeof(DotvvmConfiguration).Assembly,
"DotVVM.Framework.Resources.Styles.DotVVM.Internal.css"));
+ configuration.Resources.RegisterScript(ResourceConstants.DotvvmDotnetWasmInteropResourceName,
+ new EmbeddedResourceLocation(
+ typeof(DotvvmConfiguration).Assembly,
+ "DotVVM.Framework.obj.javascript.dotvvmStaticResources.dotnetWasmViewModule.js",
+ debugName: "DotVVM.Framework.obj.javascript.dotvvmStaticResources.dotnetWasmViewModule.js"),
+ module: true);
+
RegisterGlobalizeResources(configuration);
}
diff --git a/src/Framework/Framework/Controls/DotvvmMarkupControl.cs b/src/Framework/Framework/Controls/DotvvmMarkupControl.cs
index a03388ce18..72be03e492 100644
--- a/src/Framework/Framework/Controls/DotvvmMarkupControl.cs
+++ b/src/Framework/Framework/Controls/DotvvmMarkupControl.cs
@@ -126,7 +126,7 @@ protected override void AddAttributesToRender(IHtmlWriter writer, IDotvvmRequest
{
var settings = DefaultSerializerSettingsProvider.Instance.GetSettingsCopy();
settings.StringEscapeHandling = StringEscapeHandling.EscapeHtml;
- var binding = $"{{ modules: {JsonConvert.SerializeObject(viewModule.ReferencedModules, settings)} }}";
+ var binding = JsonConvert.SerializeObject(viewModule.ReferencedModules.Select(m => new { module = m.ModuleName, args = m.InitArguments }), settings);
if (RendersHtmlTag)
writer.AddKnockoutDataBind("dotvvm-with-view-modules", binding);
else
diff --git a/src/Framework/Framework/Controls/Internal.cs b/src/Framework/Framework/Controls/Internal.cs
index 3dcdde8f8d..2f1d731fc1 100644
--- a/src/Framework/Framework/Controls/Internal.cs
+++ b/src/Framework/Framework/Controls/Internal.cs
@@ -62,6 +62,9 @@ public class Internal
public static DotvvmProperty ReferencedViewModuleInfoProperty =
DotvvmProperty.Register(() => ReferencedViewModuleInfoProperty);
+ public static DotvvmProperty ReferencedCSharpViewModuleInfoProperty =
+ DotvvmProperty.Register(() => ReferencedCSharpViewModuleInfoProperty);
+
public static DotvvmProperty UsedPropertiesInfoProperty =
DotvvmProperty.Register(() => UsedPropertiesInfoProperty);
diff --git a/src/Framework/Framework/DotVVM.Framework.csproj b/src/Framework/Framework/DotVVM.Framework.csproj
index 6b0bd5a75c..7bc66f08d6 100644
--- a/src/Framework/Framework/DotVVM.Framework.csproj
+++ b/src/Framework/Framework/DotVVM.Framework.csproj
@@ -30,6 +30,7 @@
+
@@ -120,6 +121,10 @@
+
+
+
+
diff --git a/src/Framework/Framework/ResourceManagement/ResourceConstants.cs b/src/Framework/Framework/ResourceManagement/ResourceConstants.cs
index bc8761a801..8519aedb84 100644
--- a/src/Framework/Framework/ResourceManagement/ResourceConstants.cs
+++ b/src/Framework/Framework/ResourceManagement/ResourceConstants.cs
@@ -18,5 +18,6 @@ public class ResourceConstants
public const string DotvvmFileUploadCssResourceName = "dotvvm.fileUpload-css";
public const string DotvvmInternalCssResourceName = "dotvvm.internal-css";
+ public const string DotvvmDotnetWasmInteropResourceName = "dotvvm.interop.dotnet-wasm";
}
}
diff --git a/src/Framework/Framework/ResourceManagement/ViewModuleImportResource.cs b/src/Framework/Framework/ResourceManagement/ViewModuleImportResource.cs
index 6e2235e696..12b14f18f1 100644
--- a/src/Framework/Framework/ResourceManagement/ViewModuleImportResource.cs
+++ b/src/Framework/Framework/ResourceManagement/ViewModuleImportResource.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
+using DotVVM.Framework.Binding;
using DotVVM.Framework.Controls;
using DotVVM.Framework.Hosting;
using Newtonsoft.Json;
@@ -15,7 +16,7 @@ public class ViewModuleImportResource : IResource
{
public ResourceRenderPosition RenderPosition => ResourceRenderPosition.Anywhere;
- public string[] ReferencedModules { get; }
+ public ViewModuleReferencedModule[] ReferencedModules { get; }
public string[] Dependencies { get; }
@@ -23,13 +24,13 @@ public class ViewModuleImportResource : IResource
private string registrationScript;
- public ViewModuleImportResource(string[] referencedModules, string name, string[] dependencies)
+ public ViewModuleImportResource(ViewModuleReferencedModule[] referencedModules, string name, string[] dependencies)
{
- this.ReferencedModules = referencedModules.ToArray();
+ this.ReferencedModules = referencedModules;
this.ResourceName = name;
this.Dependencies = new string[] { "dotvvm" }.Concat(dependencies).ToArray();
- this.registrationScript = $"dotvvm.viewModules.registerMany({{{string.Join(", ", this.ReferencedModules.Select((m, i) => JsonConvert.ToString(m, '\'', StringEscapeHandling.EscapeHtml) + ": m" + i))}}});";
+ this.registrationScript = $"dotvvm.viewModules.registerMany({{{string.Join(", ", this.ReferencedModules.Select((m, i) => JsonConvert.ToString(m.ModuleName, '\'', StringEscapeHandling.EscapeHtml) + ": m" + i))}}});";
}
public void Render(IHtmlWriter writer, IDotvvmRequestContext context, string resourceName)
@@ -39,13 +40,13 @@ public void Render(IHtmlWriter writer, IDotvvmRequestContext context, string res
int i = 0;
foreach (var r in this.ReferencedModules)
{
- var resource = context.ResourceManager.FindResource(r);
+ var resource = context.ResourceManager.FindResource(r.ModuleName);
if (resource is null)
throw new Exception($"Resource {r} does not exist.");
if (!(resource is ILinkResource linkResource))
throw new Exception($"Resource {r} is not a LinkResource.");
- var location = linkResource.GetLocations().FirstOrDefault()?.GetUrl(context, r);
+ var location = linkResource.GetLocations().FirstOrDefault()?.GetUrl(context, r.ModuleName);
if (location is null)
throw new Exception($"Could not get location of resource {r}");
diff --git a/src/Framework/Framework/ResourceManagement/ViewModuleInitResource.cs b/src/Framework/Framework/ResourceManagement/ViewModuleInitResource.cs
index bcb2965f2b..efe54c04d4 100644
--- a/src/Framework/Framework/ResourceManagement/ViewModuleInitResource.cs
+++ b/src/Framework/Framework/ResourceManagement/ViewModuleInitResource.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using DotVVM.Framework.Binding;
using DotVVM.Framework.Controls;
using DotVVM.Framework.Hosting;
using Newtonsoft.Json;
@@ -15,21 +16,34 @@ public class ViewModuleInitResource : IResource
public ResourceRenderPosition RenderPosition => ResourceRenderPosition.Anywhere;
- public string[] ReferencedModules { get; }
+ public ViewModuleReferencedModule[] ReferencedModules { get; }
public string[] Dependencies { get; }
public string ResourceName { get; }
- private string registrationScript;
+ private readonly string registrationScript;
- public ViewModuleInitResource(string[] referencedModules, string name, string viewId, string[] dependencies)
+ public ViewModuleInitResource(ViewModuleReferencedModule[] referencedModules, string name, string viewId, string[] dependencies)
{
this.ResourceName = name;
this.ReferencedModules = referencedModules.ToArray();
this.Dependencies = dependencies;
- this.registrationScript = string.Join("\r\n", this.ReferencedModules.Select(m => $"dotvvm.viewModules.init({KnockoutHelper.MakeStringLiteral(m)}, {KnockoutHelper.MakeStringLiteral(viewId)}, document.body);"));
+ this.registrationScript = string.Join("\r\n", this.ReferencedModules.Select(m =>
+ {
+ var args = new List()
+ {
+ KnockoutHelper.MakeStringLiteral(m.ModuleName),
+ KnockoutHelper.MakeStringLiteral(viewId),
+ "document.body"
+ };
+ if (m.InitArguments != null)
+ {
+ args.Add($"[{string.Join(", ", m.InitArguments.Select(a => KnockoutHelper.MakeStringLiteral(a)))}]");
+ }
+ return $"dotvvm.viewModules.init({string.Join(", ", args)});";
+ }));
}
public void Render(IHtmlWriter writer, IDotvvmRequestContext context, string resourceName)
diff --git a/src/Framework/Framework/Resources/Scripts/viewModules/dotnetWasmViewModule.ts b/src/Framework/Framework/Resources/Scripts/viewModules/dotnetWasmViewModule.ts
new file mode 100644
index 0000000000..f425979b3c
--- /dev/null
+++ b/src/Framework/Framework/Resources/Scripts/viewModules/dotnetWasmViewModule.ts
@@ -0,0 +1,67 @@
+// DO NOT IMPORT THIS MODULE - it is not a part of DotVVM bundle, it is distributed separately at /dotvvmStaticResource/dotnetWasmInterop.js
+
+// @ts-ignore
+import { dotnet } from "./dotnet.js";
+declare var dotvvm: any;
+
+let interop: any;
+async function initDotnet() {
+ const { setModuleImports, getAssemblyExports, getConfig } = await dotnet.withDiagnosticTracing(true).create();
+
+ setModuleImports("/dotvvmStaticResource/dotnetWasmInterop.js", {
+ callNamedCommand: async (typeName: string, instanceName: string, commandName: string, args: string[]) => {
+ const viewIdOrElement = instanceMap[instanceName];
+ const argValues = args.map(a => JSON.parse(a));
+ const result = await dotvvm.viewModules.call(viewIdOrElement, "dotnetWasmInvoke", argValues, true);
+ return JSON.stringify(result);
+ },
+ getViewModelSnapshot: () => {
+ return JSON.stringify(dotvvm.state);
+ },
+ patchViewModel: (patchJson: string) => {
+ dotvvm.patchState(JSON.parse(patchJson));
+ }
+ });
+
+ const config = getConfig();
+ const exports = await getAssemblyExports("DotVVM.Framework.Interop.DotnetWasm");
+ await dotnet.run();
+
+ interop = exports.DotVVM.Framework.Interop.DotnetWasm.DotnetWasmInterop;
+}
+const initPromise: Promise = initDotnet();
+
+let instanceCounter = 0;
+const instanceMap: { [id: string]: string | HTMLElement } = {};
+
+class DotnetWasmModule {
+ private readonly moduleType: string;
+ private readonly moduleInstanceId: string;
+
+ constructor(public readonly context: any) {
+ this.moduleType = this.context.instanceArgs![0];
+ this.moduleInstanceId = "dotnet-wasm-" + (instanceCounter++);
+ instanceMap[this.moduleInstanceId] = context.viewIdOrElement;
+ }
+
+ private async init() {
+ await initPromise;
+ interop.CreateViewModuleInstance(this.moduleType, this.moduleInstanceId, ["TestCommand"]);
+ }
+
+ async dotnetWasmInvoke(method: string, ...args: any[]) {
+ const argValues = args.map(a => JSON.stringify(a));
+
+ await initPromise;
+ const result = await interop.CallViewModuleCommand(this.moduleType, this.moduleInstanceId, method, argValues);
+
+ return JSON.parse(result);
+ }
+
+ $dispose() {
+ interop.DisposeViewModuleInstance(this.moduleType, this.moduleInstanceId);
+ delete instanceMap[this.moduleInstanceId];
+ }
+}
+
+export default (context: any) => new DotnetWasmModule(context);
diff --git a/src/Framework/Framework/Resources/Scripts/viewModules/viewModuleManager.ts b/src/Framework/Framework/Resources/Scripts/viewModules/viewModuleManager.ts
index 01bff8b926..49e4ea92b7 100644
--- a/src/Framework/Framework/Resources/Scripts/viewModules/viewModuleManager.ts
+++ b/src/Framework/Framework/Resources/Scripts/viewModules/viewModuleManager.ts
@@ -28,7 +28,7 @@ export function registerViewModules(modules: { [name: string]: any }) {
}
}
-export function initViewModule(name: string, viewIdOrElement: string | HTMLElement, rootElement: HTMLElement): ModuleContext {
+export function initViewModule(name: string, viewIdOrElement: string | HTMLElement, rootElement: HTMLElement, instanceArgs?: string[]): ModuleContext {
if (compileConstants.debug && rootElement == null) { throw new Error("rootElement has to have a value"); }
const handler = ensureModuleHandler(name);
@@ -49,11 +49,14 @@ export function initViewModule(name: string, viewIdOrElement: string | HTMLEleme
const context = new ModuleContext(
name,
[rootElement],
- elementContext && elementContext.$control ? { ...elementContext.$control } : {}
+ elementContext && elementContext.$control ? { ...elementContext.$control } : {},
+ viewIdOrElement,
+ instanceArgs
);
const moduleInstance = createModuleInstance(handler.module.default, context);
context.module = moduleInstance;
Object.freeze(context);
+ context.instanceArgs && Object.freeze(context.instanceArgs);
if (typeof viewIdOrElement === "string") {
handler.contexts[viewIdOrElement] = context;
@@ -254,13 +257,15 @@ function mapCommandResult(result: any) {
}
export class ModuleContext {
- private readonly namedCommands: { [name: string]: (...args: any[]) => Promise } = {};
+ public readonly namedCommands: { [name: string]: (...args: any[]) => Promise } = {};
public module: any;
constructor(
public readonly moduleName: string,
public readonly elements: HTMLElement[],
- public readonly properties: { [name: string]: any }) {
+ public readonly properties: { [name: string]: any },
+ public readonly viewIdOrElement: string | HTMLElement,
+ public readonly instanceArgs?: string[]) {
}
public registerNamedCommand = (name: string, command: (...args: any[]) => Promise) => {
diff --git a/src/Framework/Framework/package.json b/src/Framework/Framework/package.json
index da5367c90c..5ea2dfac60 100644
--- a/src/Framework/Framework/package.json
+++ b/src/Framework/Framework/package.json
@@ -16,7 +16,8 @@
"typescript": "4.7.4"
},
"scripts": {
- "build": "node ./build.js",
+ "build": "node ./build.js && npm run build-dotnet-wasm-module",
+ "build-dotnet-wasm-module": "tsc Resources/Scripts/viewModules/dotnetWasmViewModule.ts --outDir ./obj/javascript/dotvvmStaticResources --module es2020 --target es2020 --noResolve --typeRoots _",
"build-stats": "PRINT_STATS=true node ./build.js",
"build-development": "rollup -c && npm run tsc-types",
"build-rollup": "npm run build-production && npm run build-development",
diff --git a/src/Framework/Interop.DotnetWasm/DotVVM.Framework.Interop.DotnetWasm.csproj b/src/Framework/Interop.DotnetWasm/DotVVM.Framework.Interop.DotnetWasm.csproj
new file mode 100644
index 0000000000..9017eaebe8
--- /dev/null
+++ b/src/Framework/Interop.DotnetWasm/DotVVM.Framework.Interop.DotnetWasm.csproj
@@ -0,0 +1,11 @@
+
+
+
+ net7.0
+ enable
+ enable
+ 11
+ true
+
+
+
diff --git a/src/Framework/Interop.DotnetWasm/DotnetWasmInterop.cs b/src/Framework/Interop.DotnetWasm/DotnetWasmInterop.cs
new file mode 100644
index 0000000000..767b05d960
--- /dev/null
+++ b/src/Framework/Interop.DotnetWasm/DotnetWasmInterop.cs
@@ -0,0 +1,69 @@
+using System.Reflection;
+using System.Runtime.InteropServices.JavaScript;
+
+namespace DotVVM.Framework.Interop.DotnetWasm;
+
+internal static partial class DotnetWasmInterop
+{
+ private static Dictionary instances = new();
+ private static DotvvmClientSerializer serializer = new();
+
+ [JSExport]
+ internal static void CreateViewModuleInstance(string typeName, string instanceName, string[] namedCommandNames)
+ {
+ var type = Type.GetType(typeName, true);
+ var context = new ViewModuleContext(typeName, instanceName, namedCommandNames, serializer);
+
+ var instance = Activator.CreateInstance(type, context);
+ instances.Add(new ViewModuleInstanceKey(typeName, instanceName), instance);
+ }
+
+ [JSExport]
+ [return: JSMarshalAs]
+ internal static object? CallViewModuleCommand(string typeName, string instanceName, string methodName, string[] args)
+ {
+ var instance = GetInstance(typeName, instanceName);
+ var method = instance.GetType().GetMethod(methodName);
+ if (method == null)
+ {
+ throw new Exception($"The method {methodName} was not found!");
+ }
+
+ var parameters = method.GetParameters();
+ var argValues = args
+ .Select((json, index) => serializer.Deserialize(parameters[index].ParameterType, json))
+ .ToArray();
+
+ try
+ {
+ return method.Invoke(instance, argValues);
+ }
+ catch (TargetInvocationException ex)
+ {
+ throw ex.InnerException!;
+ }
+ }
+
+ [JSImport("callNamedCommand")]
+ internal static partial Task CallNamedCommand(string typeName, string instanceName, string commandName, string[] args);
+
+ [JSImport("getViewModelSnapshot")]
+ internal static partial string GetViewModelSnapshot();
+
+ [JSImport("patchViewModel")]
+ internal static partial void PatchViewModelSnapshot(string patchJson);
+
+ [JSExport]
+ internal static void DisposeViewModuleInstance(string typeName, string instanceName)
+ {
+ instances.Remove(new ViewModuleInstanceKey(typeName, instanceName));
+ }
+
+ private static object GetInstance(string typeName, string instanceName)
+ {
+ return instances[new ViewModuleInstanceKey(typeName, instanceName)];
+ }
+
+ record ViewModuleInstanceKey(string TypeName, string InstanceName);
+
+}
diff --git a/src/Framework/Interop.DotnetWasm/DotvvmClientSerializer.cs b/src/Framework/Interop.DotnetWasm/DotvvmClientSerializer.cs
new file mode 100644
index 0000000000..5a8e8ae35f
--- /dev/null
+++ b/src/Framework/Interop.DotnetWasm/DotvvmClientSerializer.cs
@@ -0,0 +1,16 @@
+using System.Text.Json;
+
+namespace DotVVM.Framework.Interop.DotnetWasm;
+
+public class DotvvmClientSerializer
+{
+ public string Serialize(object value)
+ {
+ return JsonSerializer.Serialize(value);
+ }
+
+ public object? Deserialize(Type type, string json)
+ {
+ return JsonSerializer.Deserialize(json, type);
+ }
+}
diff --git a/src/Framework/Interop.DotnetWasm/IViewModuleCommand.cs b/src/Framework/Interop.DotnetWasm/IViewModuleCommand.cs
new file mode 100644
index 0000000000..384cc7f082
--- /dev/null
+++ b/src/Framework/Interop.DotnetWasm/IViewModuleCommand.cs
@@ -0,0 +1,7 @@
+namespace DotVVM.Framework.Interop.DotnetWasm;
+
+public interface IViewModuleCommand
+{
+ Task InvokeAsync(params object[] args);
+ Task InvokeAsync(params object[] args);
+}
\ No newline at end of file
diff --git a/src/Framework/Interop.DotnetWasm/IViewModuleContext.cs b/src/Framework/Interop.DotnetWasm/IViewModuleContext.cs
new file mode 100644
index 0000000000..57994cccb7
--- /dev/null
+++ b/src/Framework/Interop.DotnetWasm/IViewModuleContext.cs
@@ -0,0 +1,11 @@
+namespace DotVVM.Framework.Interop.DotnetWasm;
+
+public interface IViewModuleContext
+{
+ T? GetViewModelSnapshot();
+
+ void PatchViewModel(object data);
+
+ IReadOnlyDictionary NamedCommands { get; }
+
+}
diff --git a/src/Framework/Interop.DotnetWasm/ViewModuleCommand.cs b/src/Framework/Interop.DotnetWasm/ViewModuleCommand.cs
new file mode 100644
index 0000000000..55b1f23c79
--- /dev/null
+++ b/src/Framework/Interop.DotnetWasm/ViewModuleCommand.cs
@@ -0,0 +1,27 @@
+namespace DotVVM.Framework.Interop.DotnetWasm;
+
+public class ViewModuleCommand : IViewModuleCommand
+{
+ private readonly string typeName;
+ private readonly string instanceName;
+ private readonly string commandName;
+ private readonly DotvvmClientSerializer serializer;
+
+ public ViewModuleCommand(string typeName, string instanceName, string commandName, DotvvmClientSerializer serializer)
+ {
+ this.typeName = typeName;
+ this.instanceName = instanceName;
+ this.commandName = commandName;
+ this.serializer = serializer;
+ }
+
+ public Task InvokeAsync(params object[] args) => InvokeAsync
-
- {{value: NamedCommandResult}}
+
+
+
+
+
From 13b73abab10340e2a0113df15317dca3c55c3c71 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?=
Date: Sun, 4 Dec 2022 13:44:57 +0100
Subject: [PATCH 03/13] Cleanup and refactoring
---
.../dotnetWasmViewModule.ts | 10 +++----
src/Framework/Framework/package.json | 2 +-
...DotVVM.Framework.Interop.DotnetWasm.csproj | 4 +++
.../Interop.DotnetWasm/DotnetWasmInterop.cs | 16 +++++++----
.../DotvvmClientSerializer.cs | 2 +-
.../Interop.DotnetWasm/IViewModuleCommand.cs | 7 -----
.../Interop.DotnetWasm/IViewModuleContext.cs | 4 ++-
.../Interop.DotnetWasm/ViewModuleCommand.cs | 27 -------------------
.../Interop.DotnetWasm/ViewModuleContext.cs | 20 +++++++++-----
src/Samples/CSharpClient/TestCsharpModule.cs | 4 +--
10 files changed, 41 insertions(+), 55 deletions(-)
rename src/Framework/Framework/Resources/Scripts/{viewModules => _standalone}/dotnetWasmViewModule.ts (87%)
delete mode 100644 src/Framework/Interop.DotnetWasm/IViewModuleCommand.cs
delete mode 100644 src/Framework/Interop.DotnetWasm/ViewModuleCommand.cs
diff --git a/src/Framework/Framework/Resources/Scripts/viewModules/dotnetWasmViewModule.ts b/src/Framework/Framework/Resources/Scripts/_standalone/dotnetWasmViewModule.ts
similarity index 87%
rename from src/Framework/Framework/Resources/Scripts/viewModules/dotnetWasmViewModule.ts
rename to src/Framework/Framework/Resources/Scripts/_standalone/dotnetWasmViewModule.ts
index 93d7725fb6..943c68dbf3 100644
--- a/src/Framework/Framework/Resources/Scripts/viewModules/dotnetWasmViewModule.ts
+++ b/src/Framework/Framework/Resources/Scripts/_standalone/dotnetWasmViewModule.ts
@@ -1,4 +1,4 @@
-// DO NOT IMPORT THIS MODULE - it is not a part of DotVVM bundle, it is distributed separately at /dotvvmStaticResource/dotnetWasmInterop.js
+// This module is not a part of the DotVVM bundle, it is distributed separately at /dotvvmResource/dotnetWasmInterop.js
// @ts-ignore
import { dotnet } from "./dotnet.js";
@@ -12,8 +12,8 @@ async function initDotnet() {
callNamedCommand: async (typeName: string, instanceName: string, commandName: string, args: string[]) => {
const viewIdOrElement = instanceMap[instanceName];
const argValues = args.map(a => JSON.parse(a));
- const result = await dotvvm.viewModules.call(viewIdOrElement, "callNamedCommand", [commandName, ...argValues], true);
- return JSON.stringify(result);
+ const result = await dotvvm.viewModules.call(viewIdOrElement, "dotnetWasmCallNamedCommand", [commandName, ...argValues], true);
+ return JSON.stringify(result || null);
},
getViewModelSnapshot: () => {
return JSON.stringify(dotvvm.state);
@@ -46,7 +46,7 @@ class DotnetWasmModule {
private async init() {
await initPromise;
- interop.CreateViewModuleInstance(this.moduleType, this.moduleInstanceId, ["TestCommand"]);
+ interop.CreateViewModuleInstance(this.moduleType, this.moduleInstanceId);
}
async dotnetWasmInvoke(method: string, ...args: any[]) {
@@ -58,7 +58,7 @@ class DotnetWasmModule {
return JSON.parse(result);
}
- async callNamedCommand(name: string, ...args: any[]) {
+ async dotnetWasmCallNamedCommand(name: string, ...args: any[]) {
const command = this.context.namedCommands[name];
if (!command) {
throw `NamedCommand control with name '${name}' not found.`;
diff --git a/src/Framework/Framework/package.json b/src/Framework/Framework/package.json
index 5ea2dfac60..836f873da3 100644
--- a/src/Framework/Framework/package.json
+++ b/src/Framework/Framework/package.json
@@ -17,7 +17,7 @@
},
"scripts": {
"build": "node ./build.js && npm run build-dotnet-wasm-module",
- "build-dotnet-wasm-module": "tsc Resources/Scripts/viewModules/dotnetWasmViewModule.ts --outDir ./obj/javascript/dotvvmStaticResources --module es2020 --target es2020 --noResolve --typeRoots _",
+ "build-dotnet-wasm-module": "tsc Resources/Scripts/_standalone/dotnetWasmViewModule.ts --outDir ./obj/javascript/dotvvmStaticResources --module es2020 --target es2020 --noResolve --typeRoots _",
"build-stats": "PRINT_STATS=true node ./build.js",
"build-development": "rollup -c && npm run tsc-types",
"build-rollup": "npm run build-production && npm run build-development",
diff --git a/src/Framework/Interop.DotnetWasm/DotVVM.Framework.Interop.DotnetWasm.csproj b/src/Framework/Interop.DotnetWasm/DotVVM.Framework.Interop.DotnetWasm.csproj
index 9017eaebe8..68b30011cb 100644
--- a/src/Framework/Interop.DotnetWasm/DotVVM.Framework.Interop.DotnetWasm.csproj
+++ b/src/Framework/Interop.DotnetWasm/DotVVM.Framework.Interop.DotnetWasm.csproj
@@ -8,4 +8,8 @@
true
+
+
+
+
diff --git a/src/Framework/Interop.DotnetWasm/DotnetWasmInterop.cs b/src/Framework/Interop.DotnetWasm/DotnetWasmInterop.cs
index eda3dab2d9..31999c710d 100644
--- a/src/Framework/Interop.DotnetWasm/DotnetWasmInterop.cs
+++ b/src/Framework/Interop.DotnetWasm/DotnetWasmInterop.cs
@@ -1,5 +1,6 @@
using System.Reflection;
using System.Runtime.InteropServices.JavaScript;
+using DotVVM.Framework.Utils;
namespace DotVVM.Framework.Interop.DotnetWasm;
@@ -9,18 +10,17 @@ internal static partial class DotnetWasmInterop
private static DotvvmClientSerializer serializer = new();
[JSExport]
- internal static void CreateViewModuleInstance(string typeName, string instanceName, string[] namedCommandNames)
+ internal static void CreateViewModuleInstance(string typeName, string instanceName)
{
var type = Type.GetType(typeName, true);
- var context = new ViewModuleContext(typeName, instanceName, namedCommandNames, serializer);
+ var context = new ViewModuleContext(typeName, instanceName, serializer);
var instance = Activator.CreateInstance(type, context);
instances.Add(new ViewModuleInstanceKey(typeName, instanceName), instance);
}
[JSExport]
- [return: JSMarshalAs]
- internal static object? CallViewModuleCommand(string typeName, string instanceName, string methodName, string[] args)
+ internal static async Task CallViewModuleCommand(string typeName, string instanceName, string methodName, string[] args)
{
var instance = GetInstance(typeName, instanceName);
var method = instance.GetType().GetMethod(methodName);
@@ -36,7 +36,13 @@ internal static void CreateViewModuleInstance(string typeName, string instanceNa
try
{
- return method.Invoke(instance, argValues);
+ var result = method.Invoke(instance, argValues);
+ if (result is Task taskResult)
+ {
+ await taskResult;
+ result = TaskUtils.GetResult(taskResult);
+ }
+ return serializer.Serialize(result);
}
catch (TargetInvocationException ex)
{
diff --git a/src/Framework/Interop.DotnetWasm/DotvvmClientSerializer.cs b/src/Framework/Interop.DotnetWasm/DotvvmClientSerializer.cs
index 5a8e8ae35f..92d0bf69f0 100644
--- a/src/Framework/Interop.DotnetWasm/DotvvmClientSerializer.cs
+++ b/src/Framework/Interop.DotnetWasm/DotvvmClientSerializer.cs
@@ -4,7 +4,7 @@ namespace DotVVM.Framework.Interop.DotnetWasm;
public class DotvvmClientSerializer
{
- public string Serialize(object value)
+ public string Serialize(object? value)
{
return JsonSerializer.Serialize(value);
}
diff --git a/src/Framework/Interop.DotnetWasm/IViewModuleCommand.cs b/src/Framework/Interop.DotnetWasm/IViewModuleCommand.cs
deleted file mode 100644
index 384cc7f082..0000000000
--- a/src/Framework/Interop.DotnetWasm/IViewModuleCommand.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace DotVVM.Framework.Interop.DotnetWasm;
-
-public interface IViewModuleCommand
-{
- Task InvokeAsync(params object[] args);
- Task InvokeAsync(params object[] args);
-}
\ No newline at end of file
diff --git a/src/Framework/Interop.DotnetWasm/IViewModuleContext.cs b/src/Framework/Interop.DotnetWasm/IViewModuleContext.cs
index 57994cccb7..ef848bd030 100644
--- a/src/Framework/Interop.DotnetWasm/IViewModuleContext.cs
+++ b/src/Framework/Interop.DotnetWasm/IViewModuleContext.cs
@@ -6,6 +6,8 @@ public interface IViewModuleContext
void PatchViewModel(object data);
- IReadOnlyDictionary NamedCommands { get; }
+ Task InvokeNamedCommandAsync(string commandName, params object[] args);
+
+ Task InvokeNamedCommandAsync(string commandName, params object[] args);
}
diff --git a/src/Framework/Interop.DotnetWasm/ViewModuleCommand.cs b/src/Framework/Interop.DotnetWasm/ViewModuleCommand.cs
deleted file mode 100644
index 55b1f23c79..0000000000
--- a/src/Framework/Interop.DotnetWasm/ViewModuleCommand.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-namespace DotVVM.Framework.Interop.DotnetWasm;
-
-public class ViewModuleCommand : IViewModuleCommand
-{
- private readonly string typeName;
- private readonly string instanceName;
- private readonly string commandName;
- private readonly DotvvmClientSerializer serializer;
-
- public ViewModuleCommand(string typeName, string instanceName, string commandName, DotvvmClientSerializer serializer)
- {
- this.typeName = typeName;
- this.instanceName = instanceName;
- this.commandName = commandName;
- this.serializer = serializer;
- }
-
- public Task InvokeAsync(params object[] args) => InvokeAsync