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(args); + + public async Task InvokeAsync(params object[] args) + { + var argValues = args.Select(serializer.Serialize).ToArray(); + var json = await DotnetWasmInterop.CallNamedCommand(typeName, instanceName, commandName, argValues); + return (T?)serializer.Deserialize(typeof(T), json); + } + +} diff --git a/src/Framework/Interop.DotnetWasm/ViewModuleContext.cs b/src/Framework/Interop.DotnetWasm/ViewModuleContext.cs new file mode 100644 index 0000000000..77e2d114e5 --- /dev/null +++ b/src/Framework/Interop.DotnetWasm/ViewModuleContext.cs @@ -0,0 +1,29 @@ +namespace DotVVM.Framework.Interop.DotnetWasm +{ + public class ViewModuleContext : IViewModuleContext + { + private readonly DotvvmClientSerializer serializer; + + public IReadOnlyDictionary NamedCommands { get; } + + public T? GetViewModelSnapshot() + { + var json = DotnetWasmInterop.GetViewModelSnapshot(); + return (T?)serializer.Deserialize(typeof(T), json); + } + + public void PatchViewModel(object data) + { + var json = serializer.Serialize(data); + DotnetWasmInterop.PatchViewModelSnapshot(json); + } + + public ViewModuleContext(string typeName, string instanceName, IEnumerable namedCommandNames, DotvvmClientSerializer serializer) + { + this.serializer = serializer; + + NamedCommands = namedCommandNames + .ToDictionary(c => c, c => (IViewModuleCommand)new ViewModuleCommand(typeName, instanceName, c, serializer)); + } + } +} diff --git a/src/Samples/AspNetCoreLatest/DotVVM.Samples.BasicSamples.AspNetCoreLatest.csproj b/src/Samples/AspNetCoreLatest/DotVVM.Samples.BasicSamples.AspNetCoreLatest.csproj index 2b5baa8657..b9d36e56d6 100644 --- a/src/Samples/AspNetCoreLatest/DotVVM.Samples.BasicSamples.AspNetCoreLatest.csproj +++ b/src/Samples/AspNetCoreLatest/DotVVM.Samples.BasicSamples.AspNetCoreLatest.csproj @@ -10,6 +10,7 @@ + diff --git a/src/Samples/AspNetCoreLatest/Properties/launchSettings.json b/src/Samples/AspNetCoreLatest/Properties/launchSettings.json index 5cf93a20d2..07be086aca 100644 --- a/src/Samples/AspNetCoreLatest/Properties/launchSettings.json +++ b/src/Samples/AspNetCoreLatest/Properties/launchSettings.json @@ -11,6 +11,7 @@ "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } @@ -18,6 +19,7 @@ "DotVVM.Samples.BasicSamples": { "commandName": "Project", "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", "launchUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" diff --git a/src/Samples/AspNetCoreLatest/Startup.cs b/src/Samples/AspNetCoreLatest/Startup.cs index cf0affbee1..c5ece327f5 100644 --- a/src/Samples/AspNetCoreLatest/Startup.cs +++ b/src/Samples/AspNetCoreLatest/Startup.cs @@ -12,7 +12,9 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Localization; +using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging; namespace DotVVM.Samples.BasicSamples @@ -88,6 +90,17 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerF app.UseEndpoints(endpoints => { endpoints.MapHealthChecks("/health"); }); + + var contentTypeProvider = new FileExtensionContentTypeProvider(); + contentTypeProvider.Mappings.Add(".dll", "application/octet-stream"); + contentTypeProvider.Mappings.Add(".symbols", "application/octet-stream"); + contentTypeProvider.Mappings.Add(".blat", "application/octet-stream"); + contentTypeProvider.Mappings.Add(".dat", "application/octet-stream"); + app.UseStaticFiles(new StaticFileOptions() { + RequestPath = new PathString("/dotvvmStaticResource"), + FileProvider = new PhysicalFileProvider("D:\\Work\\Dotvvm\\src\\Samples\\CSharpClient\\bin\\Debug\\net7.0\\browser-wasm\\AppBundle"), + ContentTypeProvider = contentTypeProvider + }); app.UseStaticFiles(); app.UseEndpoints(endpoints => { diff --git a/src/Samples/CSharpClient/DotVVM.Samples.BasicSamples.CSharpClient.csproj b/src/Samples/CSharpClient/DotVVM.Samples.BasicSamples.CSharpClient.csproj new file mode 100644 index 0000000000..4bcd18ddbb --- /dev/null +++ b/src/Samples/CSharpClient/DotVVM.Samples.BasicSamples.CSharpClient.csproj @@ -0,0 +1,15 @@ + + + + net7.0 + browser-wasm + Exe + main.js + enable + + + + + + + diff --git a/src/Samples/CSharpClient/Program.cs b/src/Samples/CSharpClient/Program.cs new file mode 100644 index 0000000000..b6cdc0f7df --- /dev/null +++ b/src/Samples/CSharpClient/Program.cs @@ -0,0 +1,9 @@ +using System; +using System.Runtime.InteropServices.JavaScript; + +if (!OperatingSystem.IsBrowser()) +{ + throw new PlatformNotSupportedException("This application is expected to run on browser platform."); +} + +//await JSHost.ImportAsync("dotvvm-interop-dotnet", "/wasm/main.js"); diff --git a/src/Samples/CSharpClient/TestCsharpModule.cs b/src/Samples/CSharpClient/TestCsharpModule.cs new file mode 100644 index 0000000000..58dc4be278 --- /dev/null +++ b/src/Samples/CSharpClient/TestCsharpModule.cs @@ -0,0 +1,42 @@ +using System; +using System.Threading.Tasks; +using DotVVM.Framework.Interop.DotnetWasm; + +namespace DotVVM.Samples.BasicSamples.CSharpClient; + +public class TestCsharpModule +{ + private readonly IViewModuleContext context; + + public TestCsharpModule(IViewModuleContext context) + { + this.context = context; + } + + public void Hello() + { + Console.WriteLine("Hello world"); + } + + public int TestViewModelAccess() + { + var vm = context.GetViewModelSnapshot(); + return vm.Value; + } + + public void PatchViewModel(int newValue) + { + context.PatchViewModel(new { Value = newValue }); + } + + public async Task CallNamedCommand(int value) + { + return await context.NamedCommands["TestCommand"].InvokeAsync(value); + } + +} + +public class TestViewModelShadow +{ + public int Value { get; set; } +} diff --git a/src/Samples/CSharpClient/main.js b/src/Samples/CSharpClient/main.js new file mode 100644 index 0000000000..9d6be11879 --- /dev/null +++ b/src/Samples/CSharpClient/main.js @@ -0,0 +1,48 @@ +import { dotnet } from './dotnet.js' + +const is_browser = typeof window != "undefined"; +if (!is_browser) throw new Error(`Expected to be running in a browser`); + +const { setModuleImports, getAssemblyExports, getConfig } = await dotnet.withDiagnosticTracing(true).create(); + +setModuleImports("/wasm/main.js", { + ready: () => { + console.log("DotVVM interop is ready."); + }, + callNamedCommand: (typeName, instanceName, commandName, args) => { + console.log(typeName, instanceName, commandName, args); + }, + getViewModelSnapshot: () => { + return JSON.stringify(dotvvm.state); + }, + patchViewModel: (patchJson) => { + dotvvm.patchState(JSON.parse(patchJson)); + } +}); + +const config = getConfig(); +const exports = await getAssemblyExports("DotVVM.Framework.Interop.DotnetWasm"); + +await dotnet.run(); + +const interop = exports.DotVVM.Framework.Interop.DotnetWasm.DotnetWasmInterop; + +const type = "DotVVM.Samples.BasicSamples.CSharpClient.TestCsharpModule, DotVVM.Samples.BasicSamples.CSharpClient"; + +interop.CreateViewModuleInstance(type, "p0", ["TestCommand"]); +console.log("Created"); + +interop.CallViewModuleCommand(type, "p0", "Hello", []); +console.log("Command called"); + +var viewModelValue = interop.CallViewModuleCommand(type, "p0", "TestViewModelAccess", []); +console.log("View model value: " + viewModelValue); + +interop.CallViewModuleCommand(type, "p0", "PatchViewModel", ["15"]); +console.log("New view model value: " + dotvvm.state.Value); + +var commandResult = interop.CallViewModuleCommand(type, "p0", "CallNamedCommand", ["30"]); +console.log("Command result: " + commandResult); + +interop.DisposeViewModuleInstance(type, "p0"); +console.log("Disposed"); diff --git a/src/Samples/Common/DotVVM.Samples.Common.csproj b/src/Samples/Common/DotVVM.Samples.Common.csproj index 23cee1922b..2c0f2ae8a2 100644 --- a/src/Samples/Common/DotVVM.Samples.Common.csproj +++ b/src/Samples/Common/DotVVM.Samples.Common.csproj @@ -81,6 +81,7 @@ + diff --git a/src/Samples/Common/ViewModels/FeatureSamples/CsharpClient/CSharpClientViewModel.cs b/src/Samples/Common/ViewModels/FeatureSamples/CsharpClient/CSharpClientViewModel.cs new file mode 100644 index 0000000000..8d34ffd1d7 --- /dev/null +++ b/src/Samples/Common/ViewModels/FeatureSamples/CsharpClient/CSharpClientViewModel.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DotVVM.Framework.ViewModel; + +namespace DotVVM.Samples.Common.ViewModels.FeatureSamples.CsharpClient +{ + public class CSharpClientViewModel : DotvvmViewModelBase + { + public int Value { get; set; } = 1; + + public int? ReadResult { get; set; } + + public int? NamedCommandResult { get; set; } + + } +} + diff --git a/src/Samples/Common/Views/FeatureSamples/CsharpClient/CSharpClient.dothtml b/src/Samples/Common/Views/FeatureSamples/CsharpClient/CSharpClient.dothtml new file mode 100644 index 0000000000..25aa7d0448 --- /dev/null +++ b/src/Samples/Common/Views/FeatureSamples/CsharpClient/CSharpClient.dothtml @@ -0,0 +1,34 @@ +@viewModel DotVVM.Samples.Common.ViewModels.FeatureSamples.CsharpClient.CSharpClientViewModel, DotVVM.Samples.Common +@csharp DotVVM.Samples.BasicSamples.CSharpClient.TestCsharpModule, DotVVM.Samples.BasicSamples.CSharpClient + + + + + + + + + + + +

+ Value: +

+ +

+ +

+ +

+ + {{value: ReadResult}} +

+ +

+ + {{value: NamedCommandResult}} +

+ + + + From 1f15eb0a9b3df0f211ef3c3474d3db11dd96c54e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Wed, 30 Nov 2022 13:51:01 +0100 Subject: [PATCH 02/13] Calling wasm imlemented - basic scenarios work --- .../HelperNamespace/CsharpBindingApi.cs | 16 ----- .../Binding/ViewModuleReferenceInfo.cs | 2 - .../CsharpViewModuleMethodTranslator.cs | 72 +++++++++++++++++++ .../ControlTree/ControlTreeResolverBase.cs | 21 +++--- .../JavascriptTranslatableMethodCollection.cs | 1 - .../Javascript/JavascriptTranslator.cs | 2 + src/Framework/Framework/Controls/Internal.cs | 5 +- .../Framework/Controls/NamedCommand.cs | 2 +- .../Resources/Scripts/dotvvm-root.ts | 3 +- .../viewModules/dotnetWasmViewModule.ts | 19 +++-- .../Interop.DotnetWasm/DotnetWasmInterop.cs | 8 +-- src/Samples/AspNetCoreLatest/Startup.cs | 2 +- src/Samples/CSharpClient/Program.cs | 2 - .../CsharpClient/CSharpClientViewModel.cs | 2 - .../CsharpClient/CSharpClient.dothtml | 8 ++- 15 files changed, 113 insertions(+), 52 deletions(-) delete mode 100644 src/Framework/Framework/Binding/HelperNamespace/CsharpBindingApi.cs create mode 100644 src/Framework/Framework/Compilation/Binding/CsharpViewModuleMethodTranslator.cs diff --git a/src/Framework/Framework/Binding/HelperNamespace/CsharpBindingApi.cs b/src/Framework/Framework/Binding/HelperNamespace/CsharpBindingApi.cs deleted file mode 100644 index 410a4e7425..0000000000 --- a/src/Framework/Framework/Binding/HelperNamespace/CsharpBindingApi.cs +++ /dev/null @@ -1,16 +0,0 @@ -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 780660b7e0..099e1ba40a 100644 --- a/src/Framework/Framework/Binding/ViewModuleReferenceInfo.cs +++ b/src/Framework/Framework/Binding/ViewModuleReferenceInfo.cs @@ -25,8 +25,6 @@ public sealed class ViewModuleReferenceInfo /// Whether control id should be used instead of ViewId to identify the modules. public bool IsMarkupControl { get; } - public string?[] ModuleInstanceArgs { get; } - public ViewModuleReferenceInfo(string viewId, ViewModuleReferencedModule[] referencedModules, bool isMarkupControl) { this.ViewId = viewId; diff --git a/src/Framework/Framework/Compilation/Binding/CsharpViewModuleMethodTranslator.cs b/src/Framework/Framework/Compilation/Binding/CsharpViewModuleMethodTranslator.cs new file mode 100644 index 0000000000..d1b54131ee --- /dev/null +++ b/src/Framework/Framework/Compilation/Binding/CsharpViewModuleMethodTranslator.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System.Transactions; +using DotVVM.Framework.Compilation.ControlTree; +using DotVVM.Framework.Compilation.Javascript; +using DotVVM.Framework.Compilation.Javascript.Ast; +using DotVVM.Framework.Utils; + +namespace DotVVM.Framework.Compilation.Binding +{ + public class CsharpViewModuleMethodTranslator : IJavascriptMethodTranslator + { + public JsExpression? TryTranslateCall(LazyTranslatedExpression? context, LazyTranslatedExpression[] arguments, MethodInfo method) + { + // ignore static methods + if (context == null) + { + return null; + } + + // check whether we have the annotation - otherwise the type is not used in the _csharp context and will not be translated + var target = context.JsExpression(); + if (target.Annotation() is not { } annotation) + { + return null; + } + + // check that the method is callable + if (!method.IsPublic) + { + throw new DotvvmCompilationException($"Cannot call non-public method {method.DeclaringType.FullName}.{method.Name} on a @csharp module!"); + } + if (method.IsAbstract) + { + throw new DotvvmCompilationException($"Cannot call abstract method {method.DeclaringType.FullName}.{method.Name} on a @csharp module!"); + } + if (method.IsGenericMethod || method.IsGenericMethodDefinition) + { + throw new DotvvmCompilationException($"Cannot call generic method {method.DeclaringType.FullName}.{method.Name} on a @csharp module!"); + } + + // check that there are not more overloads + var allOverloads = context.NotNull().OriginalExpression.Type + .GetMethods() + .Where(m => m.Name == method.Name && m.IsPublic); + if (allOverloads.Count() > 1) + { + throw new DotvvmCompilationException($"There are multiple methods named {method.Name} on a @csharp module {context.OriginalExpression.Type}! Overloads are not supported on @csharp modules."); + } + + // translate the method + var viewIdOrElementExpr = annotation.IsMarkupControl ? new JsSymbolicParameter(JavascriptTranslator.CurrentElementParameter) : (JsExpression)new JsLiteral(annotation.Id); + + return new JsIdentifierExpression("dotvvm").Member("viewModules").Member("call") + .Invoke( + viewIdOrElementExpr, + new JsLiteral("dotnetWasmInvoke"), + new JsArrayExpression( + new[] { new JsLiteral(method.Name) } + .Concat(arguments.Select(a => a.JsExpression())) + .ToArray()), + new JsLiteral(true) + ) + .WithAnnotation(new ResultIsPromiseAnnotation(e => e)); + } + + } +} diff --git a/src/Framework/Framework/Compilation/ControlTree/ControlTreeResolverBase.cs b/src/Framework/Framework/Compilation/ControlTree/ControlTreeResolverBase.cs index ffad7bc1d2..d7b731230a 100644 --- a/src/Framework/Framework/Compilation/ControlTree/ControlTreeResolverBase.cs +++ b/src/Framework/Framework/Compilation/ControlTree/ControlTreeResolverBase.cs @@ -62,20 +62,21 @@ public virtual IAbstractTreeRoot ResolveTree(DothtmlRootNode root, string fileNa var view = treeBuilder.BuildTreeRoot(this, viewMetadata, root, dataContextTypeStack, directiveMetadata.Directives, directiveMetadata.MasterPage); view.FileName = fileName; - if (directiveMetadata.ViewModuleResult is { }) - { - treeBuilder.AddProperty( - view, - treeBuilder.BuildPropertyValue(Internal.ReferencedViewModuleInfoProperty, directiveMetadata.ViewModuleResult.Reference, null), - out _ + if (directiveMetadata.ViewModuleResult is { } || directiveMetadata.CSharpViewModuleResult is { }) + { + var firstReference = directiveMetadata.ViewModuleResult?.Reference ?? directiveMetadata.CSharpViewModuleResult?.Reference; + var reference = new ViewModuleReferenceInfo( + firstReference!.ViewId, + Enumerable.Concat( + directiveMetadata.ViewModuleResult?.Reference.ReferencedModules ?? Enumerable.Empty(), + directiveMetadata.CSharpViewModuleResult?.Reference.ReferencedModules ?? Enumerable.Empty() + ).ToArray(), + firstReference.IsMarkupControl ); - } - if (directiveMetadata.CSharpViewModuleResult is { }) - { treeBuilder.AddProperty( view, - treeBuilder.BuildPropertyValue(Internal.ReferencedCSharpViewModuleInfoProperty, directiveMetadata.CSharpViewModuleResult.Reference, null), + treeBuilder.BuildPropertyValue(Internal.ReferencedViewModuleInfoProperty, reference, null), out _ ); } diff --git a/src/Framework/Framework/Compilation/Javascript/JavascriptTranslatableMethodCollection.cs b/src/Framework/Framework/Compilation/Javascript/JavascriptTranslatableMethodCollection.cs index b1feafaa37..e3bef8df6e 100644 --- a/src/Framework/Framework/Compilation/Javascript/JavascriptTranslatableMethodCollection.cs +++ b/src/Framework/Framework/Compilation/Javascript/JavascriptTranslatableMethodCollection.cs @@ -159,7 +159,6 @@ 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/Javascript/JavascriptTranslator.cs b/src/Framework/Framework/Compilation/Javascript/JavascriptTranslator.cs index ab4145bb40..61016e2323 100644 --- a/src/Framework/Framework/Compilation/Javascript/JavascriptTranslator.cs +++ b/src/Framework/Framework/Compilation/Javascript/JavascriptTranslator.cs @@ -8,6 +8,7 @@ using DotVVM.Framework.Binding; using DotVVM.Framework.Binding.Expressions; using DotVVM.Framework.Binding.HelperNamespace; +using DotVVM.Framework.Compilation.Binding; using DotVVM.Framework.Compilation.ControlTree; using DotVVM.Framework.Compilation.Javascript.Ast; using DotVVM.Framework.Controls; @@ -248,6 +249,7 @@ public JavascriptTranslatorConfiguration() { Translators.Add(MethodCollection = new JavascriptTranslatableMethodCollection()); Translators.Add(new DelegateInvokeMethodTranslator()); + Translators.Add(new CsharpViewModuleMethodTranslator()); } public JsExpression? TryTranslateCall(LazyTranslatedExpression? context, LazyTranslatedExpression[] arguments, MethodInfo method) => diff --git a/src/Framework/Framework/Controls/Internal.cs b/src/Framework/Framework/Controls/Internal.cs index 2f1d731fc1..9c6f7170ad 100644 --- a/src/Framework/Framework/Controls/Internal.cs +++ b/src/Framework/Framework/Controls/Internal.cs @@ -61,10 +61,7 @@ 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/Controls/NamedCommand.cs b/src/Framework/Framework/Controls/NamedCommand.cs index a95d464c18..0d7ef92113 100644 --- a/src/Framework/Framework/Controls/NamedCommand.cs +++ b/src/Framework/Framework/Controls/NamedCommand.cs @@ -77,7 +77,7 @@ public static IEnumerable ValidateUsage(ResolvedControl contr { if (!control.TreeRoot.TryGetProperty(Internal.ReferencedViewModuleInfoProperty, out var _)) { - yield return new ControlUsageError("The NamedCommand control can be used only in pages or controls that have the @js directive."); + yield return new ControlUsageError("The NamedCommand control can be used only in pages or controls that have the @js or @csharp directive."); } } diff --git a/src/Framework/Framework/Resources/Scripts/dotvvm-root.ts b/src/Framework/Framework/Resources/Scripts/dotvvm-root.ts index b9096d3777..1cf3945da6 100644 --- a/src/Framework/Framework/Resources/Scripts/dotvvm-root.ts +++ b/src/Framework/Framework/Resources/Scripts/dotvvm-root.ts @@ -1,4 +1,4 @@ -import { initCore, getViewModel, getViewModelObservable, initBindings, getCulture, getState, getStateManager } from "./dotvvm-base" +import { initCore, getViewModel, getViewModelObservable, initBindings, getCulture, getState, getStateManager, getVirtualDirectory } from "./dotvvm-base" import * as events from './events' import * as spa from "./spa/spa" import * as validation from './validation/validation' @@ -107,6 +107,7 @@ const dotvvmExports = { call: viewModuleManager.callViewModuleCommand, registerMany: viewModuleManager.registerViewModules }, + get virtualDirectory() { return getVirtualDirectory() }, resourceLoader: { notifyModuleLoaded }, diff --git a/src/Framework/Framework/Resources/Scripts/viewModules/dotnetWasmViewModule.ts b/src/Framework/Framework/Resources/Scripts/viewModules/dotnetWasmViewModule.ts index f425979b3c..93d7725fb6 100644 --- a/src/Framework/Framework/Resources/Scripts/viewModules/dotnetWasmViewModule.ts +++ b/src/Framework/Framework/Resources/Scripts/viewModules/dotnetWasmViewModule.ts @@ -8,11 +8,11 @@ let interop: any; async function initDotnet() { const { setModuleImports, getAssemblyExports, getConfig } = await dotnet.withDiagnosticTracing(true).create(); - setModuleImports("/dotvvmStaticResource/dotnetWasmInterop.js", { + setModuleImports("dotvvmResource/dotvvm--interop--dotnet-wasm/dotvvm--interop--dotnet-wasm", { 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); + const result = await dotvvm.viewModules.call(viewIdOrElement, "callNamedCommand", [commandName, ...argValues], true); return JSON.stringify(result); }, getViewModelSnapshot: () => { @@ -25,8 +25,7 @@ async function initDotnet() { 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(); @@ -42,6 +41,7 @@ class DotnetWasmModule { this.moduleType = this.context.instanceArgs![0]; this.moduleInstanceId = "dotnet-wasm-" + (instanceCounter++); instanceMap[this.moduleInstanceId] = context.viewIdOrElement; + this.init(); } private async init() { @@ -58,7 +58,16 @@ class DotnetWasmModule { return JSON.parse(result); } - $dispose() { + async callNamedCommand(name: string, ...args: any[]) { + const command = this.context.namedCommands[name]; + if (!command) { + throw `NamedCommand control with name '${name}' not found.`; + } + return await command(...args); + } + + async $dispose() { + await initPromise; interop.DisposeViewModuleInstance(this.moduleType, this.moduleInstanceId); delete instanceMap[this.moduleInstanceId]; } diff --git a/src/Framework/Interop.DotnetWasm/DotnetWasmInterop.cs b/src/Framework/Interop.DotnetWasm/DotnetWasmInterop.cs index 767b05d960..eda3dab2d9 100644 --- a/src/Framework/Interop.DotnetWasm/DotnetWasmInterop.cs +++ b/src/Framework/Interop.DotnetWasm/DotnetWasmInterop.cs @@ -7,7 +7,7 @@ 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) { @@ -44,13 +44,13 @@ internal static void CreateViewModuleInstance(string typeName, string instanceNa } } - [JSImport("callNamedCommand")] + [JSImport("callNamedCommand", "dotvvmResource/dotvvm--interop--dotnet-wasm/dotvvm--interop--dotnet-wasm")] internal static partial Task CallNamedCommand(string typeName, string instanceName, string commandName, string[] args); - [JSImport("getViewModelSnapshot")] + [JSImport("getViewModelSnapshot", "dotvvmResource/dotvvm--interop--dotnet-wasm/dotvvm--interop--dotnet-wasm")] internal static partial string GetViewModelSnapshot(); - [JSImport("patchViewModel")] + [JSImport("patchViewModel", "dotvvmResource/dotvvm--interop--dotnet-wasm/dotvvm--interop--dotnet-wasm")] internal static partial void PatchViewModelSnapshot(string patchJson); [JSExport] diff --git a/src/Samples/AspNetCoreLatest/Startup.cs b/src/Samples/AspNetCoreLatest/Startup.cs index c5ece327f5..385b11068a 100644 --- a/src/Samples/AspNetCoreLatest/Startup.cs +++ b/src/Samples/AspNetCoreLatest/Startup.cs @@ -97,7 +97,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerF contentTypeProvider.Mappings.Add(".blat", "application/octet-stream"); contentTypeProvider.Mappings.Add(".dat", "application/octet-stream"); app.UseStaticFiles(new StaticFileOptions() { - RequestPath = new PathString("/dotvvmStaticResource"), + RequestPath = new PathString("/dotvvmResource/dotvvm--interop--dotnet-wasm"), FileProvider = new PhysicalFileProvider("D:\\Work\\Dotvvm\\src\\Samples\\CSharpClient\\bin\\Debug\\net7.0\\browser-wasm\\AppBundle"), ContentTypeProvider = contentTypeProvider }); diff --git a/src/Samples/CSharpClient/Program.cs b/src/Samples/CSharpClient/Program.cs index b6cdc0f7df..cf1d2bc8f9 100644 --- a/src/Samples/CSharpClient/Program.cs +++ b/src/Samples/CSharpClient/Program.cs @@ -5,5 +5,3 @@ { throw new PlatformNotSupportedException("This application is expected to run on browser platform."); } - -//await JSHost.ImportAsync("dotvvm-interop-dotnet", "/wasm/main.js"); diff --git a/src/Samples/Common/ViewModels/FeatureSamples/CsharpClient/CSharpClientViewModel.cs b/src/Samples/Common/ViewModels/FeatureSamples/CsharpClient/CSharpClientViewModel.cs index 8d34ffd1d7..7ac47989f5 100644 --- a/src/Samples/Common/ViewModels/FeatureSamples/CsharpClient/CSharpClientViewModel.cs +++ b/src/Samples/Common/ViewModels/FeatureSamples/CsharpClient/CSharpClientViewModel.cs @@ -13,8 +13,6 @@ public class CSharpClientViewModel : DotvvmViewModelBase public int? ReadResult { get; set; } - public int? NamedCommandResult { get; set; } - } } diff --git a/src/Samples/Common/Views/FeatureSamples/CsharpClient/CSharpClient.dothtml b/src/Samples/Common/Views/FeatureSamples/CsharpClient/CSharpClient.dothtml index 25aa7d0448..5c70bd87eb 100644 --- a/src/Samples/Common/Views/FeatureSamples/CsharpClient/CSharpClient.dothtml +++ b/src/Samples/Common/Views/FeatureSamples/CsharpClient/CSharpClient.dothtml @@ -7,7 +7,6 @@ - @@ -25,8 +24,11 @@

- - {{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(args); - - public async Task InvokeAsync(params object[] args) - { - var argValues = args.Select(serializer.Serialize).ToArray(); - var json = await DotnetWasmInterop.CallNamedCommand(typeName, instanceName, commandName, argValues); - return (T?)serializer.Deserialize(typeof(T), json); - } - -} diff --git a/src/Framework/Interop.DotnetWasm/ViewModuleContext.cs b/src/Framework/Interop.DotnetWasm/ViewModuleContext.cs index 77e2d114e5..ff13e928f3 100644 --- a/src/Framework/Interop.DotnetWasm/ViewModuleContext.cs +++ b/src/Framework/Interop.DotnetWasm/ViewModuleContext.cs @@ -2,10 +2,10 @@ { public class ViewModuleContext : IViewModuleContext { + private readonly string typeName; + private readonly string instanceName; private readonly DotvvmClientSerializer serializer; - public IReadOnlyDictionary NamedCommands { get; } - public T? GetViewModelSnapshot() { var json = DotnetWasmInterop.GetViewModelSnapshot(); @@ -18,12 +18,20 @@ public void PatchViewModel(object data) DotnetWasmInterop.PatchViewModelSnapshot(json); } - public ViewModuleContext(string typeName, string instanceName, IEnumerable namedCommandNames, DotvvmClientSerializer serializer) + public Task InvokeNamedCommandAsync(string commandName, params object[] args) => InvokeNamedCommandAsync(commandName, args); + + public async Task InvokeNamedCommandAsync(string commandName, params object[] args) { - this.serializer = serializer; + var argValues = args.Select(serializer.Serialize).ToArray(); + var json = await DotnetWasmInterop.CallNamedCommand(typeName, instanceName, commandName, argValues); + return (T?)serializer.Deserialize(typeof(T), json); + } - NamedCommands = namedCommandNames - .ToDictionary(c => c, c => (IViewModuleCommand)new ViewModuleCommand(typeName, instanceName, c, serializer)); + public ViewModuleContext(string typeName, string instanceName, DotvvmClientSerializer serializer) + { + this.typeName = typeName; + this.instanceName = instanceName; + this.serializer = serializer; } } } diff --git a/src/Samples/CSharpClient/TestCsharpModule.cs b/src/Samples/CSharpClient/TestCsharpModule.cs index 58dc4be278..1e22d375a7 100644 --- a/src/Samples/CSharpClient/TestCsharpModule.cs +++ b/src/Samples/CSharpClient/TestCsharpModule.cs @@ -29,9 +29,9 @@ public void PatchViewModel(int newValue) context.PatchViewModel(new { Value = newValue }); } - public async Task CallNamedCommand(int value) + public async Task CallNamedCommand(int value) { - return await context.NamedCommands["TestCommand"].InvokeAsync(value); + await context.InvokeNamedCommandAsync("TestCommand", value); } } From 7d685dd4a3e6155a6cf3fbb10f7161e0ea234dd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Sun, 4 Dec 2022 14:24:28 +0100 Subject: [PATCH 04/13] Csharp directive renamed to Dotnet to be more generic --- ...cs => DotnetViewModuleMethodTranslator.cs} | 14 ++++----- .../ControlTree/BindingExtensionParameter.cs | 6 ++-- .../ControlTree/ControlTreeResolverBase.cs | 31 +++++++++++-------- ... => IAbstractDotnetViewModuleDirective.cs} | 2 +- .../ControlTree/IAbstractTreeBuilder.cs | 2 +- ...s => ResolvedDotnetViewModuleDirective.cs} | 6 ++-- .../Resolved/ResolvedTreeBuilder.cs | 4 +-- ...s => DotnetViewModuleCompilationResult.cs} | 2 +- ...s => DotnetViewModuleDirectiveCompiler.cs} | 16 +++++----- .../MarkupDirectiveCompilerPipeline.cs | 8 ++--- .../Directives/MarkupPageMetadata.cs | 2 +- .../Javascript/JavascriptTranslator.cs | 2 +- .../Compilation/Parser/ParserConstants.cs | 2 +- .../CsharpClient/CSharpClient.dothtml | 10 +++--- 14 files changed, 56 insertions(+), 51 deletions(-) rename src/Framework/Framework/Compilation/Binding/{CsharpViewModuleMethodTranslator.cs => DotnetViewModuleMethodTranslator.cs} (86%) rename src/Framework/Framework/Compilation/ControlTree/{IAbstractCsharpViewModuleDirective.cs => IAbstractDotnetViewModuleDirective.cs} (73%) rename src/Framework/Framework/Compilation/ControlTree/Resolved/{ResolvedCsharpViewModuleDirective.cs => ResolvedDotnetViewModuleDirective.cs} (73%) rename src/Framework/Framework/Compilation/Directives/{CSharpViewModuleCompilationResult.cs => DotnetViewModuleCompilationResult.cs} (75%) rename src/Framework/Framework/Compilation/Directives/{CsharpViewModuleDirectiveCompiler.cs => DotnetViewModuleDirectiveCompiler.cs} (79%) diff --git a/src/Framework/Framework/Compilation/Binding/CsharpViewModuleMethodTranslator.cs b/src/Framework/Framework/Compilation/Binding/DotnetViewModuleMethodTranslator.cs similarity index 86% rename from src/Framework/Framework/Compilation/Binding/CsharpViewModuleMethodTranslator.cs rename to src/Framework/Framework/Compilation/Binding/DotnetViewModuleMethodTranslator.cs index d1b54131ee..e54a218bdc 100644 --- a/src/Framework/Framework/Compilation/Binding/CsharpViewModuleMethodTranslator.cs +++ b/src/Framework/Framework/Compilation/Binding/DotnetViewModuleMethodTranslator.cs @@ -12,7 +12,7 @@ namespace DotVVM.Framework.Compilation.Binding { - public class CsharpViewModuleMethodTranslator : IJavascriptMethodTranslator + public class DotnetViewModuleMethodTranslator : IJavascriptMethodTranslator { public JsExpression? TryTranslateCall(LazyTranslatedExpression? context, LazyTranslatedExpression[] arguments, MethodInfo method) { @@ -22,9 +22,9 @@ public class CsharpViewModuleMethodTranslator : IJavascriptMethodTranslator return null; } - // check whether we have the annotation - otherwise the type is not used in the _csharp context and will not be translated + // check whether we have the annotation - otherwise the type is not used in the _dotnet context and will not be translated var target = context.JsExpression(); - if (target.Annotation() is not { } annotation) + if (target.Annotation() is not { } annotation) { return null; } @@ -32,15 +32,15 @@ public class CsharpViewModuleMethodTranslator : IJavascriptMethodTranslator // check that the method is callable if (!method.IsPublic) { - throw new DotvvmCompilationException($"Cannot call non-public method {method.DeclaringType.FullName}.{method.Name} on a @csharp module!"); + throw new DotvvmCompilationException($"Cannot call non-public method {method.DeclaringType.FullName}.{method.Name} on a @dotnet module!"); } if (method.IsAbstract) { - throw new DotvvmCompilationException($"Cannot call abstract method {method.DeclaringType.FullName}.{method.Name} on a @csharp module!"); + throw new DotvvmCompilationException($"Cannot call abstract method {method.DeclaringType.FullName}.{method.Name} on a @dotnet module!"); } if (method.IsGenericMethod || method.IsGenericMethodDefinition) { - throw new DotvvmCompilationException($"Cannot call generic method {method.DeclaringType.FullName}.{method.Name} on a @csharp module!"); + throw new DotvvmCompilationException($"Cannot call generic method {method.DeclaringType.FullName}.{method.Name} on a @dotnet module!"); } // check that there are not more overloads @@ -49,7 +49,7 @@ public class CsharpViewModuleMethodTranslator : IJavascriptMethodTranslator .Where(m => m.Name == method.Name && m.IsPublic); if (allOverloads.Count() > 1) { - throw new DotvvmCompilationException($"There are multiple methods named {method.Name} on a @csharp module {context.OriginalExpression.Type}! Overloads are not supported on @csharp modules."); + throw new DotvvmCompilationException($"There are multiple methods named {method.Name} on a @dotnet module {context.OriginalExpression.Type}! Overloads are not supported on @dotnet modules."); } // translate the method diff --git a/src/Framework/Framework/Compilation/ControlTree/BindingExtensionParameter.cs b/src/Framework/Framework/Compilation/ControlTree/BindingExtensionParameter.cs index 33104f2758..e9ab8d456c 100644 --- a/src/Framework/Framework/Compilation/ControlTree/BindingExtensionParameter.cs +++ b/src/Framework/Framework/Compilation/ControlTree/BindingExtensionParameter.cs @@ -211,13 +211,13 @@ public ViewModuleAnnotation(string id, bool isMarkupControl) } } - public class CSharpExtensionParameter : BindingExtensionParameter + public class DotnetExtensionParameter : 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) + public DotnetExtensionParameter(string id, bool isMarkupControl, ITypeDescriptor type) : base("_dotnet", type, true) { this.Id = id; this.IsMarkupControl = isMarkupControl; @@ -230,7 +230,7 @@ public override Expression GetServerEquivalent(Expression controlParameter) 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!"); + throw new DotvvmCompilationException($"The type {type} referenced in the @dotnet 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))); diff --git a/src/Framework/Framework/Compilation/ControlTree/ControlTreeResolverBase.cs b/src/Framework/Framework/Compilation/ControlTree/ControlTreeResolverBase.cs index d7b731230a..e789e23e15 100644 --- a/src/Framework/Framework/Compilation/ControlTree/ControlTreeResolverBase.cs +++ b/src/Framework/Framework/Compilation/ControlTree/ControlTreeResolverBase.cs @@ -57,23 +57,14 @@ public virtual IAbstractTreeRoot ResolveTree(DothtmlRootNode root, string fileNa new BindingApiExtensionParameter() }.Concat(directiveMetadata.InjectedServices) .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()); + .Concat(directiveMetadata.DotnetViewModuleResult is null ? new BindingExtensionParameter[0] : new[] { directiveMetadata.DotnetViewModuleResult.ExtensionParameter }).ToArray()); var view = treeBuilder.BuildTreeRoot(this, viewMetadata, root, dataContextTypeStack, directiveMetadata.Directives, directiveMetadata.MasterPage); view.FileName = fileName; - if (directiveMetadata.ViewModuleResult is { } || directiveMetadata.CSharpViewModuleResult is { }) + if (directiveMetadata.ViewModuleResult is { } || directiveMetadata.DotnetViewModuleResult is { }) { - var firstReference = directiveMetadata.ViewModuleResult?.Reference ?? directiveMetadata.CSharpViewModuleResult?.Reference; - var reference = new ViewModuleReferenceInfo( - firstReference!.ViewId, - Enumerable.Concat( - directiveMetadata.ViewModuleResult?.Reference.ReferencedModules ?? Enumerable.Empty(), - directiveMetadata.CSharpViewModuleResult?.Reference.ReferencedModules ?? Enumerable.Empty() - ).ToArray(), - firstReference.IsMarkupControl - ); - + var reference = BuildViewModuleReferenceInfo(directiveMetadata); treeBuilder.AddProperty( view, treeBuilder.BuildPropertyValue(Internal.ReferencedViewModuleInfoProperty, reference, null), @@ -84,7 +75,21 @@ out _ ResolveRootContent(root, view, viewMetadata); return view; - } + } + + private static ViewModuleReferenceInfo BuildViewModuleReferenceInfo(MarkupPageMetadata directiveMetadata) + { + var firstReference = directiveMetadata.ViewModuleResult?.Reference ?? directiveMetadata.DotnetViewModuleResult?.Reference; + var reference = new ViewModuleReferenceInfo( + firstReference!.ViewId, + Enumerable.Concat( + directiveMetadata.ViewModuleResult?.Reference.ReferencedModules ?? Enumerable.Empty(), + directiveMetadata.DotnetViewModuleResult?.Reference.ReferencedModules ?? Enumerable.Empty() + ).ToArray(), + firstReference.IsMarkupControl + ); + return reference; + } /// /// Resolves the content of the root node. diff --git a/src/Framework/Framework/Compilation/ControlTree/IAbstractCsharpViewModuleDirective.cs b/src/Framework/Framework/Compilation/ControlTree/IAbstractDotnetViewModuleDirective.cs similarity index 73% rename from src/Framework/Framework/Compilation/ControlTree/IAbstractCsharpViewModuleDirective.cs rename to src/Framework/Framework/Compilation/ControlTree/IAbstractDotnetViewModuleDirective.cs index b3f8822c24..e1a741ff07 100644 --- a/src/Framework/Framework/Compilation/ControlTree/IAbstractCsharpViewModuleDirective.cs +++ b/src/Framework/Framework/Compilation/ControlTree/IAbstractDotnetViewModuleDirective.cs @@ -1,6 +1,6 @@ namespace DotVVM.Framework.Compilation.ControlTree; -public interface IAbstractCsharpViewModuleDirective : IAbstractDirective +public interface IAbstractDotnetViewModuleDirective : 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 f5f867e9c7..abb366ffa2 100644 --- a/src/Framework/Framework/Compilation/ControlTree/IAbstractTreeBuilder.cs +++ b/src/Framework/Framework/Compilation/ControlTree/IAbstractTreeBuilder.cs @@ -28,7 +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); + IAbstractDotnetViewModuleDirective BuildDotnetViewModuleDirective(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/ResolvedDotnetViewModuleDirective.cs similarity index 73% rename from src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedCsharpViewModuleDirective.cs rename to src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedDotnetViewModuleDirective.cs index bc903e02ca..13ad1c267b 100644 --- a/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedCsharpViewModuleDirective.cs +++ b/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedDotnetViewModuleDirective.cs @@ -4,13 +4,13 @@ namespace DotVVM.Framework.Compilation.ControlTree.Resolved; -/// Represents the @csharp directive - import .NET WASM module on the client side -public class ResolvedCsharpViewModuleDirective : ResolvedDirective, IAbstractCsharpViewModuleDirective +/// Represents the @dotnet directive - import .NET WASM module on the client side +public class ResolvedDotnetViewModuleDirective : ResolvedDirective, IAbstractDotnetViewModuleDirective { /// Full .NET type of the module public ITypeDescriptor ModuleType { get; } - public ResolvedCsharpViewModuleDirective(DirectiveCompilationService directiveCompilationService, DothtmlDirectiveNode node, BindingParserNode typeName, ImmutableList imports) + public ResolvedDotnetViewModuleDirective(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 6a065cccc9..6ae53bbe83 100644 --- a/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedTreeBuilder.cs +++ b/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedTreeBuilder.cs @@ -97,8 +97,8 @@ 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 IAbstractDotnetViewModuleDirective BuildDotnetViewModuleDirective(DothtmlDirectiveNode directiveNode, BindingParserNode typeName, ImmutableList imports) => + new ResolvedDotnetViewModuleDirective(directiveService, directiveNode, typeName, imports); public IAbstractPropertyDeclarationDirective BuildPropertyDeclarationDirective( DothtmlDirectiveNode directive, diff --git a/src/Framework/Framework/Compilation/Directives/CSharpViewModuleCompilationResult.cs b/src/Framework/Framework/Compilation/Directives/DotnetViewModuleCompilationResult.cs similarity index 75% rename from src/Framework/Framework/Compilation/Directives/CSharpViewModuleCompilationResult.cs rename to src/Framework/Framework/Compilation/Directives/DotnetViewModuleCompilationResult.cs index bc8e68e3d4..68c417de80 100644 --- a/src/Framework/Framework/Compilation/Directives/CSharpViewModuleCompilationResult.cs +++ b/src/Framework/Framework/Compilation/Directives/DotnetViewModuleCompilationResult.cs @@ -3,4 +3,4 @@ namespace DotVVM.Framework.Compilation.Directives; -public record CSharpViewModuleCompilationResult(CSharpExtensionParameter ExtensionParameter, ViewModuleReferenceInfo Reference); +public record DotnetViewModuleCompilationResult(DotnetExtensionParameter ExtensionParameter, ViewModuleReferenceInfo Reference); diff --git a/src/Framework/Framework/Compilation/Directives/CsharpViewModuleDirectiveCompiler.cs b/src/Framework/Framework/Compilation/Directives/DotnetViewModuleDirectiveCompiler.cs similarity index 79% rename from src/Framework/Framework/Compilation/Directives/CsharpViewModuleDirectiveCompiler.cs rename to src/Framework/Framework/Compilation/Directives/DotnetViewModuleDirectiveCompiler.cs index 1ef4a5a915..89cc3d80c5 100644 --- a/src/Framework/Framework/Compilation/Directives/CsharpViewModuleDirectiveCompiler.cs +++ b/src/Framework/Framework/Compilation/Directives/DotnetViewModuleDirectiveCompiler.cs @@ -11,13 +11,13 @@ namespace DotVVM.Framework.Compilation.Directives; -public class CsharpViewModuleDirectiveCompiler : DirectiveCompiler +public class DotnetViewModuleDirectiveCompiler : 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) + public DotnetViewModuleDirectiveCompiler(IReadOnlyDictionary> directiveNodesByName, IAbstractTreeBuilder treeBuilder, IAbstractControlBuilderDescriptor? masterPage, bool isMarkupControl, ImmutableList imports) : base(directiveNodesByName, treeBuilder) { this.masterPage = masterPage; @@ -27,13 +27,13 @@ public CsharpViewModuleDirectiveCompiler(IReadOnlyDictionary ParserConstants.CsharpViewModuleDirective; - protected override CSharpViewModuleCompilationResult? CreateArtefact(IReadOnlyList resolvedDirectives) + protected override DotnetViewModuleCompilationResult? CreateArtefact(IReadOnlyList resolvedDirectives) { var id = AssignViewModuleId(masterPage); return ResolveImportedViewModules(resolvedDirectives, id); } - private CSharpViewModuleCompilationResult? ResolveImportedViewModules(IReadOnlyList moduleDirectives, string id) + private DotnetViewModuleCompilationResult? ResolveImportedViewModules(IReadOnlyList moduleDirectives, string id) { if (moduleDirectives.Count == 0) { @@ -42,7 +42,7 @@ public CsharpViewModuleDirectiveCompiler(IReadOnlyDictionary 1) { - moduleDirectives[1].DothtmlNode!.AddError("There can be only one @csharp directive in the page!"); + moduleDirectives[1].DothtmlNode!.AddError("There can be only one @dotnet directive in the page!"); return null; } @@ -58,7 +58,7 @@ public CsharpViewModuleDirectiveCompiler(IReadOnlyDictionary - TreeBuilder.BuildCsharpViewModuleDirective(directiveNode, ParseDirective(directiveNode, p => p.ReadDirectiveTypeName()), imports); + protected override IAbstractDotnetViewModuleDirective Resolve(DothtmlDirectiveNode directiveNode) => + TreeBuilder.BuildDotnetViewModuleDirective(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 8ba5dcdddf..92b044dfc3 100644 --- a/src/Framework/Framework/Compilation/Directives/MarkupDirectiveCompilerPipeline.cs +++ b/src/Framework/Framework/Compilation/Directives/MarkupDirectiveCompilerPipeline.cs @@ -62,14 +62,14 @@ public MarkupPageMetadata Compile(DothtmlRootNode dothtmlRoot, string fileName) var viewModuleResult = viewModuleDirectiveCompiler.Compile(); resolvedDirectives.AddIfAny(viewModuleDirectiveCompiler.DirectiveName, viewModuleResult.Directives); - var csharpViewModuleDirectiveCompiler = new CsharpViewModuleDirectiveCompiler( + var dotnetViewModuleDirectiveCompiler = new DotnetViewModuleDirectiveCompiler( directivesByName, treeBuilder, masterPage, !baseType.IsEqualTo(ResolvedTypeDescriptor.Create(typeof(DotvvmView))), imports); - var csharpViewModuleResult = csharpViewModuleDirectiveCompiler.Compile(); - resolvedDirectives.AddIfAny(csharpViewModuleDirectiveCompiler.DirectiveName, csharpViewModuleResult.Directives); + var dotnetViewModuleResult = dotnetViewModuleDirectiveCompiler.Compile(); + resolvedDirectives.AddIfAny(dotnetViewModuleDirectiveCompiler.DirectiveName, dotnetViewModuleResult.Directives); var propertyDirectiveCompiler = new PropertyDeclarationDirectiveCompiler(directivesByName, treeBuilder, baseType, imports); var propertyResult = propertyDirectiveCompiler.Compile(); @@ -93,7 +93,7 @@ public MarkupPageMetadata Compile(DothtmlRootNode dothtmlRoot, string fileName) baseType, viewModelType.TypeDescriptor, viewModuleResult.Artefact, - csharpViewModuleResult.Artefact, + dotnetViewModuleResult.Artefact, propertyResult.Artefact); } } diff --git a/src/Framework/Framework/Compilation/Directives/MarkupPageMetadata.cs b/src/Framework/Framework/Compilation/Directives/MarkupPageMetadata.cs index c052d37503..16ca1aff70 100644 --- a/src/Framework/Framework/Compilation/Directives/MarkupPageMetadata.cs +++ b/src/Framework/Framework/Compilation/Directives/MarkupPageMetadata.cs @@ -14,6 +14,6 @@ public record MarkupPageMetadata( ITypeDescriptor BaseType, ITypeDescriptor? ViewModelType, ViewModuleCompilationResult? ViewModuleResult, - CSharpViewModuleCompilationResult? CSharpViewModuleResult, + DotnetViewModuleCompilationResult? DotnetViewModuleResult, ImmutableList Properties); } diff --git a/src/Framework/Framework/Compilation/Javascript/JavascriptTranslator.cs b/src/Framework/Framework/Compilation/Javascript/JavascriptTranslator.cs index 61016e2323..6df29a8ddb 100644 --- a/src/Framework/Framework/Compilation/Javascript/JavascriptTranslator.cs +++ b/src/Framework/Framework/Compilation/Javascript/JavascriptTranslator.cs @@ -249,7 +249,7 @@ public JavascriptTranslatorConfiguration() { Translators.Add(MethodCollection = new JavascriptTranslatableMethodCollection()); Translators.Add(new DelegateInvokeMethodTranslator()); - Translators.Add(new CsharpViewModuleMethodTranslator()); + Translators.Add(new DotnetViewModuleMethodTranslator()); } public JsExpression? TryTranslateCall(LazyTranslatedExpression? context, LazyTranslatedExpression[] arguments, MethodInfo method) => diff --git a/src/Framework/Framework/Compilation/Parser/ParserConstants.cs b/src/Framework/Framework/Compilation/Parser/ParserConstants.cs index a852c4041f..91f23f9ce8 100644 --- a/src/Framework/Framework/Compilation/Parser/ParserConstants.cs +++ b/src/Framework/Framework/Compilation/Parser/ParserConstants.cs @@ -12,7 +12,7 @@ public class ParserConstants public const string NoWrapperTagNameDirective = "noWrapperTag"; public const string ServiceInjectDirective = "service"; public const string JsViewModuleDirective = "js"; - public const string CsharpViewModuleDirective = "csharp"; + public const string CsharpViewModuleDirective = "dotnet"; public const string ValueBinding = "value"; public const string CommandBinding = "command"; diff --git a/src/Samples/Common/Views/FeatureSamples/CsharpClient/CSharpClient.dothtml b/src/Samples/Common/Views/FeatureSamples/CsharpClient/CSharpClient.dothtml index 5c70bd87eb..0054b82c1f 100644 --- a/src/Samples/Common/Views/FeatureSamples/CsharpClient/CSharpClient.dothtml +++ b/src/Samples/Common/Views/FeatureSamples/CsharpClient/CSharpClient.dothtml @@ -1,5 +1,5 @@ @viewModel DotVVM.Samples.Common.ViewModels.FeatureSamples.CsharpClient.CSharpClientViewModel, DotVVM.Samples.Common -@csharp DotVVM.Samples.BasicSamples.CSharpClient.TestCsharpModule, DotVVM.Samples.BasicSamples.CSharpClient +@dotnet DotVVM.Samples.BasicSamples.CSharpClient.TestCsharpModule, DotVVM.Samples.BasicSamples.CSharpClient @@ -15,20 +15,20 @@

- +

- + {{value: ReadResult}}

- +

- +

From d53795ac7758c9c89e81225f248354580762a717 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Sun, 4 Dec 2022 16:38:20 +0100 Subject: [PATCH 05/13] UI tests for web assembly added --- .github/uitest/uitest.ps1 | 2 + .../Interop.DotnetWasm/DotnetWasmInterop.cs | 7 +- .../DotvvmClientSerializer.cs | 19 +- .../ApplicationInsights.Owin/Web.config | 4 +- ...mples.BasicSamples.AspNetCoreLatest.csproj | 2 +- src/Samples/AspNetCoreLatest/Startup.cs | 6 +- .../CSharpClient/TypeMarshallingModule.cs | 82 ++++++++ .../Common/DotVVM.Samples.Common.csproj | 12 +- .../TextFileDiagnosticsInformationSender.cs | 2 +- .../ButtonInMarkupControl/EnabledViewModel.cs | 3 +- .../ActionFilterErrorHandlingViewModel.cs | 4 +- .../ActionFilterPageErrorHandlingViewModel.cs | 2 +- .../NastyChildViewModel.cs | 8 +- .../CsharpClient/CSharpClientViewModel.cs | 1 + .../CsharpClient/MarshallingViewModel.cs | 62 ++++++ .../CsharpClient/CSharpClient.dothtml | 24 ++- .../CsharpClient/Marshalling.dothtml | 197 ++++++++++++++++++ .../Abstractions/SamplesRouteUrls.designer.cs | 2 + .../Tests/Tests/Feature/CsharpClientTests.cs | 108 ++++++++++ 19 files changed, 521 insertions(+), 26 deletions(-) create mode 100644 src/Samples/CSharpClient/TypeMarshallingModule.cs create mode 100644 src/Samples/Common/ViewModels/FeatureSamples/CsharpClient/MarshallingViewModel.cs create mode 100644 src/Samples/Common/Views/FeatureSamples/CsharpClient/Marshalling.dothtml create mode 100644 src/Samples/Tests/Tests/Feature/CsharpClientTests.cs diff --git a/.github/uitest/uitest.ps1 b/.github/uitest/uitest.ps1 index d54c0846f7..04a53bcd9c 100644 --- a/.github/uitest/uitest.ps1 +++ b/.github/uitest/uitest.ps1 @@ -216,6 +216,8 @@ try { "$testDir", ` "--configuration", ` "$config", ` + "--filter", ` + "Category!=aspnetcore-only", ` "--no-restore", ` "--logger", ` "trx;LogFileName=$TrxName", ` diff --git a/src/Framework/Interop.DotnetWasm/DotnetWasmInterop.cs b/src/Framework/Interop.DotnetWasm/DotnetWasmInterop.cs index 31999c710d..3c4e4c06b0 100644 --- a/src/Framework/Interop.DotnetWasm/DotnetWasmInterop.cs +++ b/src/Framework/Interop.DotnetWasm/DotnetWasmInterop.cs @@ -15,7 +15,12 @@ internal static void CreateViewModuleInstance(string typeName, string instanceNa var type = Type.GetType(typeName, true); var context = new ViewModuleContext(typeName, instanceName, serializer); - var instance = Activator.CreateInstance(type, context); + var constructor = type.GetConstructor(BindingFlags.Public | BindingFlags.Instance, new[] { typeof(IViewModuleContext) }); + if (constructor == null) + { + throw new Exception($"The type {type} referenced in the @dotnet directive must have one public constructor accepting a parameter of type {typeof(IViewModuleContext)}."); + } + var instance = constructor.Invoke(new object[] { context }); instances.Add(new ViewModuleInstanceKey(typeName, instanceName), instance); } diff --git a/src/Framework/Interop.DotnetWasm/DotvvmClientSerializer.cs b/src/Framework/Interop.DotnetWasm/DotvvmClientSerializer.cs index 92d0bf69f0..a6b5d08e9f 100644 --- a/src/Framework/Interop.DotnetWasm/DotvvmClientSerializer.cs +++ b/src/Framework/Interop.DotnetWasm/DotvvmClientSerializer.cs @@ -1,16 +1,31 @@ using System.Text.Json; +using System.Text.Json.Serialization; namespace DotVVM.Framework.Interop.DotnetWasm; public class DotvvmClientSerializer { + private readonly JsonSerializerOptions options; + + public DotvvmClientSerializer() + { + this.options = GetDefaultOptions(); + } + + private JsonSerializerOptions GetDefaultOptions() + { + var options = new JsonSerializerOptions(); + options.Converters.Add(new JsonStringEnumConverter()); + return options; + } + public string Serialize(object? value) { - return JsonSerializer.Serialize(value); + return JsonSerializer.Serialize(value, options); } public object? Deserialize(Type type, string json) { - return JsonSerializer.Deserialize(json, type); + return JsonSerializer.Deserialize(json, type, options); } } diff --git a/src/Samples/ApplicationInsights.Owin/Web.config b/src/Samples/ApplicationInsights.Owin/Web.config index caad38616d..a7b2698ee1 100644 --- a/src/Samples/ApplicationInsights.Owin/Web.config +++ b/src/Samples/ApplicationInsights.Owin/Web.config @@ -59,8 +59,8 @@ - - + + diff --git a/src/Samples/AspNetCoreLatest/DotVVM.Samples.BasicSamples.AspNetCoreLatest.csproj b/src/Samples/AspNetCoreLatest/DotVVM.Samples.BasicSamples.AspNetCoreLatest.csproj index b9d36e56d6..31b5e308b2 100644 --- a/src/Samples/AspNetCoreLatest/DotVVM.Samples.BasicSamples.AspNetCoreLatest.csproj +++ b/src/Samples/AspNetCoreLatest/DotVVM.Samples.BasicSamples.AspNetCoreLatest.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/Samples/AspNetCoreLatest/Startup.cs b/src/Samples/AspNetCoreLatest/Startup.cs index 385b11068a..1d2319c4e2 100644 --- a/src/Samples/AspNetCoreLatest/Startup.cs +++ b/src/Samples/AspNetCoreLatest/Startup.cs @@ -91,14 +91,16 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerF endpoints.MapHealthChecks("/health"); }); + var wasmOutputPath = Path.GetFullPath(Path.Combine(env.ContentRootPath, "../Samples/CSharpClient/bin/Debug/net7.0/browser-wasm/AppBundle")); var contentTypeProvider = new FileExtensionContentTypeProvider(); contentTypeProvider.Mappings.Add(".dll", "application/octet-stream"); contentTypeProvider.Mappings.Add(".symbols", "application/octet-stream"); contentTypeProvider.Mappings.Add(".blat", "application/octet-stream"); contentTypeProvider.Mappings.Add(".dat", "application/octet-stream"); - app.UseStaticFiles(new StaticFileOptions() { + app.UseStaticFiles(new StaticFileOptions() + { RequestPath = new PathString("/dotvvmResource/dotvvm--interop--dotnet-wasm"), - FileProvider = new PhysicalFileProvider("D:\\Work\\Dotvvm\\src\\Samples\\CSharpClient\\bin\\Debug\\net7.0\\browser-wasm\\AppBundle"), + FileProvider = new PhysicalFileProvider(wasmOutputPath), ContentTypeProvider = contentTypeProvider }); app.UseStaticFiles(); diff --git a/src/Samples/CSharpClient/TypeMarshallingModule.cs b/src/Samples/CSharpClient/TypeMarshallingModule.cs new file mode 100644 index 0000000000..ad6ac56a3f --- /dev/null +++ b/src/Samples/CSharpClient/TypeMarshallingModule.cs @@ -0,0 +1,82 @@ +using System; +using System.Linq; +using System.Runtime.Serialization; +using System.Threading.Tasks; +using DotVVM.Framework.Interop.DotnetWasm; + +namespace DotVVM.Samples.BasicSamples.CSharpClient +{ + public class TypeMarshallingModule + { + public byte MarshallByte(byte a) => (byte)(a * 2); + public byte? MarshallNullableByte(byte? a) => (byte?)(a * 2); + public sbyte MarshallSByte(sbyte a) => (sbyte)(a * 2); + public sbyte? MarshallNullableSByte(sbyte? a) => (sbyte?)(a * 2); + public short MarshallShort(short a) => (short)(a * 2); + public short? MarshallNullableShort(short? a) => (short?)(a * 2); + public ushort MarshallUShort(ushort a) => (ushort)(a * 2); + public ushort? MarshallNullableUShort(ushort? a) => (ushort?)(a * 2); + public int MarshallInt(int a) => a * 2; + public int? MarshallNullableInt(int? a) => a * 2; + public uint MarshallUInt(uint a) => a * 2; + public uint? MarshallNullableUInt(uint? a) => a * 2; + public long MarshallLong(long a) => a * 2; + public long? MarshallNullableLong(long? a) => a * 2; + public ulong MarshallULong(ulong a) => a * 2; + public ulong? MarshallNullableULong(ulong? a) => a * 2; + public float MarshallFloat(float a) => a * 2; + public float? MarshallNullableFloat(float? a) => a * 2; + public double MarshallDouble(double a) => a * 2; + public double? MarshallNullableDouble(double? a) => a * 2; + public decimal MarshallDecimal(decimal a) => a * 2; + public decimal? MarshallNullableDecimal(decimal? a) => a * 2; + public DateTime MarshallDateTime(DateTime a) => a.AddDays(1); + public DateTime? MarshallNullableDateTime(DateTime? a) => a?.AddDays(1); + public DateOnly MarshallDateOnly(DateOnly a) => a.AddDays(1); + public DateOnly? MarshallNullableDateOnly(DateOnly? a) => a?.AddDays(1); + public TimeOnly MarshallTimeOnly(TimeOnly a) => a.AddHours(1); + public TimeOnly? MarshallNullableTimeOnly(TimeOnly? a) => a?.AddHours(1); + public TimeSpan MarshallTimeSpan(TimeSpan a) => a + TimeSpan.FromHours(1); + public TimeSpan? MarshallNullableTimeSpan(TimeSpan? a) => a + TimeSpan.FromHours(1); + public char MarshallChar(char a) => char.ToUpper(a); + public char? MarshallNullableChar(char? a) => a == null ? null : char.ToUpper(a.Value); + public Guid MarshallGuid(Guid a) => new Guid("C286C18D-ECD8-47E0-BFC6-6CE709C5D498"); + public Guid? MarshallNullableGuid(Guid? a) => a == null ? null : new Guid("C286C18D-ECD8-47E0-BFC6-6CE709C5D498"); + public string MarshallString(string a) => a.ToUpper(); + public ChildEnum MarshallEnum(ChildEnum a) => (ChildEnum)(4 - a); + public ChildEnum? MarshallNullableEnum(ChildEnum? a) => (ChildEnum?)(4 - a); + public ChildObject MarshallObject(ChildObject child) => new ChildObject() { Int = child.Int + 1, String = child.String.ToUpper() }; + public ChildRecord MarshallRecord(ChildRecord child) => new ChildRecord(child.Int + 1, child.String.ToUpper()); + public ChildObject[] MarshallObjectArray(ChildObject[] a) => a.Reverse().ToArray(); + public ChildRecord[] MarshallRecordArray(ChildRecord[] a) => a.Reverse().ToArray(); + public int[] MarshallIntArray(int[] a) => a.Reverse().ToArray(); + public double?[] MarshallNullableDoubleArray(double?[] a) => a.Reverse().ToArray(); + public string[] MarshallStringArray(string[] a) => a.Reverse().ToArray(); + public async Task MarshallTask(ChildRecord[] a) + { + await Task.Delay(1000); + return a.Reverse().ToArray(); + } + public Exception MarshallException() => throw new Exception("Test exception"); + + public TypeMarshallingModule(IViewModuleContext context) + { + } + } + + public class ChildObject + { + public int Int { get; set; } + + public string String { get; set; } + } + + public record ChildRecord(int Int, string String); + + public enum ChildEnum + { + One = 1, + Two = 2, + Three = 3 + } +} diff --git a/src/Samples/Common/DotVVM.Samples.Common.csproj b/src/Samples/Common/DotVVM.Samples.Common.csproj index ba0079f8c8..1f9380ef68 100644 --- a/src/Samples/Common/DotVVM.Samples.Common.csproj +++ b/src/Samples/Common/DotVVM.Samples.Common.csproj @@ -1,6 +1,6 @@  - $(DefaultTargetFrameworks) + $(DefaultTargetFrameworks);net7.0 DotVVM.Samples.Common @@ -85,6 +85,7 @@ + @@ -215,7 +216,14 @@ TRACE;DEBUG - + + CSHARP_CLIENT + + + + + + diff --git a/src/Samples/Common/TextFileDiagnosticsInformationSender.cs b/src/Samples/Common/TextFileDiagnosticsInformationSender.cs index 549cf03941..a0ab48a2ac 100644 --- a/src/Samples/Common/TextFileDiagnosticsInformationSender.cs +++ b/src/Samples/Common/TextFileDiagnosticsInformationSender.cs @@ -39,7 +39,7 @@ public Task SendInformationAsync(DiagnosticsInformation information) File.AppendAllText(logFilePath, messages); } - return TaskUtils.GetCompletedTask(); + return Task.CompletedTask; } private string FormatUnwrittenMessages(DiagnosticsInformation information) diff --git a/src/Samples/Common/ViewModels/ComplexSamples/ButtonInMarkupControl/EnabledViewModel.cs b/src/Samples/Common/ViewModels/ComplexSamples/ButtonInMarkupControl/EnabledViewModel.cs index d2ce250296..88df17babb 100644 --- a/src/Samples/Common/ViewModels/ComplexSamples/ButtonInMarkupControl/EnabledViewModel.cs +++ b/src/Samples/Common/ViewModels/ComplexSamples/ButtonInMarkupControl/EnabledViewModel.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using DotVVM.Framework.Utils; namespace DotVVM.Samples.BasicSamples.ViewModels.ComplexSamples.ButtonInMarkupControl { @@ -16,7 +15,7 @@ public class EnabledViewModel public Task Flip() { Enabled = !Enabled; - return TaskUtils.GetCompletedTask(); + return Task.CompletedTask; } public class TestDto diff --git a/src/Samples/Common/ViewModels/FeatureSamples/ActionFilterErrorHandling/ActionFilterErrorHandlingViewModel.cs b/src/Samples/Common/ViewModels/FeatureSamples/ActionFilterErrorHandling/ActionFilterErrorHandlingViewModel.cs index 655a11c9e8..1930d950b0 100644 --- a/src/Samples/Common/ViewModels/FeatureSamples/ActionFilterErrorHandling/ActionFilterErrorHandlingViewModel.cs +++ b/src/Samples/Common/ViewModels/FeatureSamples/ActionFilterErrorHandling/ActionFilterErrorHandlingViewModel.cs @@ -29,7 +29,7 @@ protected override Task OnCommandExceptionAsync(IDotvvmRequestContext context, A { ((ActionFilterErrorHandlingViewModel)context.ViewModel).Result = "error was handled"; context.IsCommandExceptionHandled = true; - return TaskUtils.GetCompletedTask(); + return Task.CompletedTask; } } -} \ No newline at end of file +} diff --git a/src/Samples/Common/ViewModels/FeatureSamples/ActionFilterErrorHandling/ActionFilterPageErrorHandlingViewModel.cs b/src/Samples/Common/ViewModels/FeatureSamples/ActionFilterErrorHandling/ActionFilterPageErrorHandlingViewModel.cs index bb74eaff91..d128678292 100644 --- a/src/Samples/Common/ViewModels/FeatureSamples/ActionFilterErrorHandling/ActionFilterPageErrorHandlingViewModel.cs +++ b/src/Samples/Common/ViewModels/FeatureSamples/ActionFilterErrorHandling/ActionFilterPageErrorHandlingViewModel.cs @@ -22,7 +22,7 @@ protected override Task OnPageExceptionAsync(IDotvvmRequestContext context, Exce { context.IsPageExceptionHandled = true; context.RedirectToUrl("/error500"); - return TaskUtils.GetCompletedTask(); + return Task.CompletedTask; } } } diff --git a/src/Samples/Common/ViewModels/FeatureSamples/ChildViewModelInvokeMethods/NastyChildViewModel.cs b/src/Samples/Common/ViewModels/FeatureSamples/ChildViewModelInvokeMethods/NastyChildViewModel.cs index 1884ab6e2c..4f9cc0847f 100644 --- a/src/Samples/Common/ViewModels/FeatureSamples/ChildViewModelInvokeMethods/NastyChildViewModel.cs +++ b/src/Samples/Common/ViewModels/FeatureSamples/ChildViewModelInvokeMethods/NastyChildViewModel.cs @@ -13,19 +13,19 @@ public class NastyChildViewModel : DotvvmViewModelBase public override Task Init() { InitCount++; - return TaskUtils.GetCompletedTask(); + return Task.CompletedTask; } public override Task Load() { LoadCount++; - return TaskUtils.GetCompletedTask(); + return Task.CompletedTask; } public override Task PreRender() { PreRenderCount++; - return TaskUtils.GetCompletedTask(); + return Task.CompletedTask; } } -} \ No newline at end of file +} diff --git a/src/Samples/Common/ViewModels/FeatureSamples/CsharpClient/CSharpClientViewModel.cs b/src/Samples/Common/ViewModels/FeatureSamples/CsharpClient/CSharpClientViewModel.cs index 7ac47989f5..6f70d03ea5 100644 --- a/src/Samples/Common/ViewModels/FeatureSamples/CsharpClient/CSharpClientViewModel.cs +++ b/src/Samples/Common/ViewModels/FeatureSamples/CsharpClient/CSharpClientViewModel.cs @@ -13,6 +13,7 @@ public class CSharpClientViewModel : DotvvmViewModelBase public int? ReadResult { get; set; } + public string LastConsole { get; set; } } } diff --git a/src/Samples/Common/ViewModels/FeatureSamples/CsharpClient/MarshallingViewModel.cs b/src/Samples/Common/ViewModels/FeatureSamples/CsharpClient/MarshallingViewModel.cs new file mode 100644 index 0000000000..f67794b9ef --- /dev/null +++ b/src/Samples/Common/ViewModels/FeatureSamples/CsharpClient/MarshallingViewModel.cs @@ -0,0 +1,62 @@ +#if CSHARP_CLIENT +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DotVVM.Framework.ViewModel; +using DotVVM.Samples.BasicSamples.CSharpClient; + +namespace DotVVM.Samples.Common.ViewModels.FeatureSamples.CsharpClient +{ + public class MarshallingViewModel : DotvvmViewModelBase + { + + public byte ByteValue { get; set; } = 0; + public byte? NullableByteValue { get; set; } = null; + public sbyte SByteValue { get; set; } = 1; + public sbyte? NullableSByteValue { get; set; } = 2; + public short ShortValue { get; set; } = 3; + public short? NullableShortValue { get; set; } = 4; + public ushort UShortValue { get; set; } = 5; + public ushort? NullableUShortValue { get; set; } = 6; + public int IntValue { get; set; } = 7; + public int? NullableIntValue { get; set; } = 8; + public uint UIntValue { get; set; } = 9; + public uint? NullableUIntValue { get; set; } = 10; + public long LongValue { get; set; } = 11; + public long? NullableLongValue { get; set; } = 12; + public ulong ULongValue { get; set; } = 13; + public ulong? NullableULongValue { get; set; } = 14; + public float FloatValue { get; set; } = 1.23f; + public float? NullableFloatValue { get; set; } = null; + public double DoubleValue { get; set; } = 4.5678; + public double? NullableDoubleValue { get; set; } = 9999; + public decimal DecimalValue { get; set; } = 1000000m; + public decimal? NullableDecimalValue { get; set; } = 1000001m; + public DateTime DateTimeValue { get; set; } = new DateTime(2020, 1, 2, 3, 4, 5); + public DateTime? NullableDateTimeValue { get; set; } = null; + public DateOnly DateOnlyValue { get; set; } = new DateOnly(2020, 10, 11); + public DateOnly? NullableDateOnlyValue { get; set; } = new DateOnly(2020, 11, 12); + public TimeOnly TimeOnlyValue { get; set; } = new TimeOnly(6, 0, 5); + public TimeOnly? NullableTimeOnlyValue { get; set; } = null; + public TimeSpan TimeSpanValue { get; set; } = new TimeSpan(2, 3, 4, 5); + public TimeSpan? NullableTimeSpanValue { get; set; } = null; + public char CharValue { get; set; } = 'b'; + public char? NullableCharValue { get; set; } = null; + public Guid GuidValue { get; set; } = new Guid("2EF427B2-889C-42A6-B6C8-781839A46825"); + public Guid? NullableGuidValue { get; set; } = null; + public string StringValue { get; set; } = "bababa"; + public ChildEnum EnumValue { get; set; } = ChildEnum.One; + public ChildEnum? NullableEnumValue { get; set; } = null; + public ChildObject ObjectValue { get; set; } = new ChildObject() { Int = 1, String = "abc" }; + public ChildRecord RecordValue { get; set; } = new ChildRecord(2, "def"); + public ChildObject[] ObjectArrayValue { get; set; } = new[] { new ChildObject() { Int = 1, String = "abc" }, new ChildObject() { Int = 3, String = "ghi" } }; + public ChildRecord[] RecordArrayValue { get; set; } = new[] { new ChildRecord(2, "def"), new ChildRecord(4, "jkl") }; + public int[] IntArrayValue { get; set; } = new[] { 2, 5 }; + public double?[] NullableDoubleArrayValue { get; set; } = new[] { 3.0, (double?)null }; + public string[] StringArrayValue { get; set; } = new[] { "abc", "def" }; + public ChildRecord[] TaskValue { get; set; } = new[] { new ChildRecord(6, "mno"), new ChildRecord(8, "pqr") }; + } +} +#endif diff --git a/src/Samples/Common/Views/FeatureSamples/CsharpClient/CSharpClient.dothtml b/src/Samples/Common/Views/FeatureSamples/CsharpClient/CSharpClient.dothtml index 0054b82c1f..24151979d8 100644 --- a/src/Samples/Common/Views/FeatureSamples/CsharpClient/CSharpClient.dothtml +++ b/src/Samples/Common/Views/FeatureSamples/CsharpClient/CSharpClient.dothtml @@ -11,26 +11,38 @@

- Value: + Value:

- +

- - {{value: ReadResult}} + + {{value: ReadResult}}

- +

- +

+

+ Last console entry: {{value: LastConsole}} +

+ + + diff --git a/src/Samples/Common/Views/FeatureSamples/CsharpClient/Marshalling.dothtml b/src/Samples/Common/Views/FeatureSamples/CsharpClient/Marshalling.dothtml new file mode 100644 index 0000000000..422bedb1c0 --- /dev/null +++ b/src/Samples/Common/Views/FeatureSamples/CsharpClient/Marshalling.dothtml @@ -0,0 +1,197 @@ +@viewModel DotVVM.Samples.Common.ViewModels.FeatureSamples.CsharpClient.MarshallingViewModel, DotVVM.Samples.Common +@dotnet DotVVM.Samples.BasicSamples.CSharpClient.TypeMarshallingModule, DotVVM.Samples.BasicSamples.CSharpClient + + + + + + + + +

+ + {{value: ByteValue}} +

+

+ + {{value: NullableByteValue}} +

+

+ + {{value: SByteValue}} +

+

+ + {{value: NullableSByteValue}} +

+

+ + {{value: ShortValue}} +

+

+ + {{value: NullableShortValue}} +

+

+ + {{value: UShortValue}} +

+

+ + {{value: NullableUShortValue}} +

+

+ + {{value: IntValue}} +

+

+ + {{value: NullableIntValue}} +

+

+ + {{value: UIntValue}} +

+

+ + {{value: NullableUIntValue}} +

+

+ + {{value: LongValue}} +

+

+ + {{value: NullableLongValue}} +

+

+ + {{value: ULongValue}} +

+

+ + {{value: NullableULongValue}} +

+

+ + {{value: FloatValue}} +

+

+ + {{value: NullableFloatValue}} +

+

+ + {{value: DoubleValue}} +

+

+ + {{value: NullableDoubleValue}} +

+

+ + {{value: DecimalValue}} +

+

+ + {{value: NullableDecimalValue}} +

+

+ + {{value: DateTimeValue}} +

+

+ + {{value: NullableDateTimeValue}} +

+

+ + {{value: DateOnlyValue}} +

+

+ + {{value: NullableDateOnlyValue}} +

+

+ + {{value: TimeOnlyValue}} +

+

+ + {{value: NullableTimeOnlyValue}} +

+

+ + {{value: TimeSpanValue}} +

+

+ + {{value: NullableTimeSpanValue}} +

+

+ + {{value: CharValue}} +

+

+ + {{value: NullableCharValue}} +

+

+ + {{value: GuidValue}} +

+

+ + {{value: NullableGuidValue}} +

+

+ + {{value: StringValue}} +

+

+ + {{value: EnumValue}} +

+

+ + {{value: NullableEnumValue}} +

+

+ + {{value: ObjectValue.Int}}, {{value: ObjectValue.String}} +

+

+ + {{value: RecordValue.Int}}, {{value: RecordValue.String}} +

+

+ + {{value: ObjectArrayValue[0].Int}}, {{value: ObjectArrayValue[0].String}}; {{value: ObjectArrayValue[1].Int}}, {{value: ObjectArrayValue[1].String}} +

+

+ + {{value: RecordArrayValue[0].Int}}, {{value: RecordArrayValue[0].String}}; {{value: RecordArrayValue[1].Int}}, {{value: RecordArrayValue[1].String}} +

+

+ + {{value: IntArrayValue[0]}}; {{value: IntArrayValue[1]}} +

+

+ + {{value: NullableDoubleArrayValue[0]}}; {{value: NullableDoubleArrayValue[1]}} +

+

+ + {{value: StringArrayValue[0]}}; {{value: StringArrayValue[1]}} +

+

+ + {{value: TaskValue[0].Int}}, {{value: TaskValue[0].String}}; {{value: TaskValue[1].Int}}, {{value: TaskValue[1].String}} +

+

+ +

+ + + + diff --git a/src/Samples/Tests/Abstractions/SamplesRouteUrls.designer.cs b/src/Samples/Tests/Abstractions/SamplesRouteUrls.designer.cs index 936bcaa3d2..8908d72a3f 100644 --- a/src/Samples/Tests/Abstractions/SamplesRouteUrls.designer.cs +++ b/src/Samples/Tests/Abstractions/SamplesRouteUrls.designer.cs @@ -227,6 +227,8 @@ public partial class SamplesRouteUrls public const string FeatureSamples_CompositeControls_BasicSample = "FeatureSamples/CompositeControls/BasicSample"; public const string FeatureSamples_CompositeControls_ControlPropertyNamingConflict = "FeatureSamples/CompositeControls/ControlPropertyNamingConflict"; public const string FeatureSamples_ConditionalCssClasses_ConditionalCssClasses = "FeatureSamples/ConditionalCssClasses/ConditionalCssClasses"; + public const string FeatureSamples_CsharpClient_CSharpClient = "FeatureSamples/CsharpClient/CSharpClient"; + public const string FeatureSamples_CsharpClient_Marshalling = "FeatureSamples/CsharpClient/Marshalling"; public const string FeatureSamples_CustomResponseProperties_SimpleExceptionFilter = "FeatureSamples/CustomResponseProperties/SimpleExceptionFilter"; public const string FeatureSamples_DateTimeSerialization_DateTimeSerialization = "FeatureSamples/DateTimeSerialization/DateTimeSerialization"; public const string FeatureSamples_DependencyInjection_ViewModelScopedService = "FeatureSamples/DependencyInjection/ViewModelScopedService"; diff --git a/src/Samples/Tests/Tests/Feature/CsharpClientTests.cs b/src/Samples/Tests/Tests/Feature/CsharpClientTests.cs new file mode 100644 index 0000000000..fbfee7aaba --- /dev/null +++ b/src/Samples/Tests/Tests/Feature/CsharpClientTests.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DotVVM.Samples.Tests.Base; +using DotVVM.Testing.Abstractions; +using Riganti.Selenium.Core; +using Xunit; +using Xunit.Abstractions; + +namespace DotVVM.Samples.Tests.Feature +{ + public class CsharpClientTests : AppSeleniumTest + { + [Fact] + [Trait("Category", "aspnetcore-only")] + public void Feature_CsharpClient_CSharpClient() + { + RunInAllBrowsers(browser => { + browser.NavigateToUrl(SamplesRouteUrls.FeatureSamples_CsharpClient_CSharpClient); + + var value = browser.Single("value", SelectByDataUi); + AssertUI.Value(value, "1"); + + // check console access + browser.Single("hello", SelectByDataUi).Click(); + AssertUI.TextEquals(browser.Single("console", SelectByDataUi), "Hello world"); + + // read VM + browser.Single("read-vm", SelectByDataUi).Click(); + AssertUI.TextEquals(browser.Single("read-vm-result", SelectByDataUi), "1"); + + // patch VM + browser.Single("patch-vm", SelectByDataUi).Click(); + AssertUI.TextEquals(value, "30"); + browser.Single("read-vm", SelectByDataUi).Click(); + AssertUI.TextEquals(browser.Single("read-vm-result", SelectByDataUi), "30"); + + // call command + browser.Single("named-command", SelectByDataUi).Click(); + AssertUI.TextEquals(value, "60"); + }); + } + + [Theory] + [InlineData("MarshallByte", "0")] + [InlineData("MarshallNullableByte", "")] + [InlineData("MarshallSByte", "2")] + [InlineData("MarshallNullableSByte", "4")] + [InlineData("MarshallShort", "6")] + [InlineData("MarshallNullableShort", "8")] + [InlineData("MarshallUShort", "10")] + [InlineData("MarshallNullableUShort", "12")] + [InlineData("MarshallInt", "14")] + [InlineData("MarshallNullableInt", "16")] + [InlineData("MarshallUInt", "18")] + [InlineData("MarshallNullableUInt", "20")] + [InlineData("MarshallLong", "22")] + [InlineData("MarshallNullableLong", "24")] + [InlineData("MarshallULong", "26")] + [InlineData("MarshallNullableULong", "28")] + [InlineData("MarshallFloat", "2.46")] + [InlineData("MarshallNullableFloat", "")] + [InlineData("MarshallDouble", "9.1356")] + [InlineData("MarshallNullableDouble", "19998")] + [InlineData("MarshallDecimal", "2000000")] + [InlineData("MarshallNullableDecimal", "2000002")] + [InlineData("MarshallDateTime", "1/3/2020 3:04:05 AM")] + [InlineData("MarshallNullableDateTime", "")] + [InlineData("MarshallDateOnly", "Monday, October 12, 2020")] + [InlineData("MarshallNullableDateOnly", "Friday, November 13, 2020")] + [InlineData("MarshallTimeOnly", "7:00:05 AM")] + [InlineData("MarshallNullableTimeOnly", "")] + [InlineData("MarshallTimeSpan", "2.04:04:05")] + [InlineData("MarshallNullableTimeSpan", "")] + [InlineData("MarshallChar", "B")] + [InlineData("MarshallNullableChar", "")] + [InlineData("MarshallGuid", "c286c18d-ecd8-47e0-bfc6-6ce709c5d498")] + [InlineData("MarshallNullableGuid", "")] + [InlineData("MarshallString", "BABABA")] + [InlineData("MarshallEnum", "Three")] + [InlineData("MarshallNullableEnum", "")] + [InlineData("MarshallObject", "2, ABC")] + [InlineData("MarshallRecord", "3, DEF")] + [InlineData("MarshallObjectArray", "3, ghi; 1, abc")] + [InlineData("MarshallRecordArray", "4, jkl; 2, def")] + [InlineData("MarshallIntArray", "5; 2")] + [InlineData("MarshallNullableDoubleArray", "; 3")] + [InlineData("MarshallStringArray", "def; abc")] + [InlineData("MarshallTask", "8, pqr; 6, mno")] + [Trait("Category", "aspnetcore-only")] + public void Feature_CsharpClient_Marshalling(string section, string expected) + { + RunInAllBrowsers(browser => { + browser.NavigateToUrl(SamplesRouteUrls.FeatureSamples_CsharpClient_Marshalling); + + var p = browser.Single(section, SelectByDataUi); + p.Single("input[type=button]").Click(); + AssertUI.TextEquals(p.Single("span"), expected); + }); + } + + public CsharpClientTests(ITestOutputHelper output) : base(output) + { + } + } +} From 9608f85504fd04aa9919b7237f52a20c3457f19b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Sun, 4 Dec 2022 16:39:15 +0100 Subject: [PATCH 06/13] Fixed startup path --- src/Samples/AspNetCoreLatest/Startup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Samples/AspNetCoreLatest/Startup.cs b/src/Samples/AspNetCoreLatest/Startup.cs index 1d2319c4e2..a537f04972 100644 --- a/src/Samples/AspNetCoreLatest/Startup.cs +++ b/src/Samples/AspNetCoreLatest/Startup.cs @@ -91,7 +91,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerF endpoints.MapHealthChecks("/health"); }); - var wasmOutputPath = Path.GetFullPath(Path.Combine(env.ContentRootPath, "../Samples/CSharpClient/bin/Debug/net7.0/browser-wasm/AppBundle")); + var wasmOutputPath = Path.GetFullPath(Path.Combine(env.ContentRootPath, "../CSharpClient/bin/Debug/net7.0/browser-wasm/AppBundle")); var contentTypeProvider = new FileExtensionContentTypeProvider(); contentTypeProvider.Mappings.Add(".dll", "application/octet-stream"); contentTypeProvider.Mappings.Add(".symbols", "application/octet-stream"); From 4bd3e0abf5a799aae7396350a01c8dd9b73dbfc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Sun, 4 Dec 2022 19:55:38 +0100 Subject: [PATCH 07/13] Updated pipeline for wasm tooling --- .github/setup/action.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/setup/action.yml b/.github/setup/action.yml index cecbf62a0f..3d96973f5b 100644 --- a/.github/setup/action.yml +++ b/.github/setup/action.yml @@ -43,3 +43,7 @@ runs: - if: ${{ runner.os != 'Windows' }} run: dotnet restore ${{ inputs.sln }} shell: bash + + # install workloads + - run: | + dotnet workload install wasm-tools From 8124aeb996b4ccf00bbc58d02da0d60b55f0a8a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Sun, 4 Dec 2022 20:01:28 +0100 Subject: [PATCH 08/13] Fix CI --- .github/setup/action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/setup/action.yml b/.github/setup/action.yml index 46c879262e..7b900ca0bc 100644 --- a/.github/setup/action.yml +++ b/.github/setup/action.yml @@ -49,5 +49,5 @@ runs: shell: bash # install workloads - - run: | - dotnet workload install wasm-tools + - run: dotnet workload install wasm-tools + shell: pwsh From 20adfaf8f8abcb5711c4768c1162030c919b19de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Sun, 4 Dec 2022 20:03:51 +0100 Subject: [PATCH 09/13] Fix CI --- .github/setup/action.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/setup/action.yml b/.github/setup/action.yml index 7b900ca0bc..f7a284e7d9 100644 --- a/.github/setup/action.yml +++ b/.github/setup/action.yml @@ -37,6 +37,10 @@ runs: - if: ${{ runner.os == 'Windows' }} run: choco install dotnetcore-3.1-windowshosting -y shell: pwsh + + # install workloads + - run: dotnet workload install wasm-tools + shell: pwsh # restore packages - if: ${{ runner.os == 'Windows' }} @@ -47,7 +51,3 @@ runs: - if: ${{ runner.os != 'Windows' }} run: dotnet restore ${{ inputs.sln }} shell: bash - - # install workloads - - run: dotnet workload install wasm-tools - shell: pwsh From 6ba764968a97e7a115d1a702adfde59fd03e11d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Sun, 4 Dec 2022 20:17:05 +0100 Subject: [PATCH 10/13] Fix CI --- src/Framework/Framework/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Framework/Framework/package.json b/src/Framework/Framework/package.json index 836f873da3..17a0ac1784 100644 --- a/src/Framework/Framework/package.json +++ b/src/Framework/Framework/package.json @@ -18,7 +18,7 @@ "scripts": { "build": "node ./build.js && npm run build-dotnet-wasm-module", "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-stats": "PRINT_STATS=true npm run build", "build-development": "rollup -c && npm run tsc-types", "build-rollup": "npm run build-production && npm run build-development", "build-production": "rollup -c --environment BUILD:production", From f71bb3aa28d0971845d93d008e3644a2bd2ddb62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Sun, 4 Dec 2022 20:33:18 +0100 Subject: [PATCH 11/13] Fixed nullability problems --- src/Framework/Framework/Binding/ViewModuleReferenceInfo.cs | 2 +- .../Compilation/Binding/DotnetViewModuleMethodTranslator.cs | 6 +++--- .../ControlTree/IAbstractDotnetViewModuleDirective.cs | 2 +- .../Resolved/ResolvedDotnetViewModuleDirective.cs | 2 +- .../Directives/DotnetViewModuleDirectiveCompiler.cs | 2 +- src/Framework/Interop.DotnetWasm/DotnetWasmInterop.cs | 2 +- src/Framework/Interop.DotnetWasm/IViewModuleContext.cs | 2 +- src/Framework/Interop.DotnetWasm/ViewModuleContext.cs | 4 ++-- src/Samples/CSharpClient/TypeMarshallingModule.cs | 2 +- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Framework/Framework/Binding/ViewModuleReferenceInfo.cs b/src/Framework/Framework/Binding/ViewModuleReferenceInfo.cs index 099e1ba40a..36411e4a43 100644 --- a/src/Framework/Framework/Binding/ViewModuleReferenceInfo.cs +++ b/src/Framework/Framework/Binding/ViewModuleReferenceInfo.cs @@ -64,7 +64,7 @@ public ViewModuleReferenceInfo(string viewId, ViewModuleReferencedModule[] refer private string GenerateModuleBatchUniqueId() { using var sha = SHA256.Create(); - 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) : ""))))) + 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("=", ""); } } diff --git a/src/Framework/Framework/Compilation/Binding/DotnetViewModuleMethodTranslator.cs b/src/Framework/Framework/Compilation/Binding/DotnetViewModuleMethodTranslator.cs index e54a218bdc..92c7004c91 100644 --- a/src/Framework/Framework/Compilation/Binding/DotnetViewModuleMethodTranslator.cs +++ b/src/Framework/Framework/Compilation/Binding/DotnetViewModuleMethodTranslator.cs @@ -32,15 +32,15 @@ public class DotnetViewModuleMethodTranslator : IJavascriptMethodTranslator // check that the method is callable if (!method.IsPublic) { - throw new DotvvmCompilationException($"Cannot call non-public method {method.DeclaringType.FullName}.{method.Name} on a @dotnet module!"); + throw new DotvvmCompilationException($"Cannot call non-public method {method.DeclaringType!.FullName}.{method.Name} on a @dotnet module!"); } if (method.IsAbstract) { - throw new DotvvmCompilationException($"Cannot call abstract method {method.DeclaringType.FullName}.{method.Name} on a @dotnet module!"); + throw new DotvvmCompilationException($"Cannot call abstract method {method.DeclaringType!.FullName}.{method.Name} on a @dotnet module!"); } if (method.IsGenericMethod || method.IsGenericMethodDefinition) { - throw new DotvvmCompilationException($"Cannot call generic method {method.DeclaringType.FullName}.{method.Name} on a @dotnet module!"); + throw new DotvvmCompilationException($"Cannot call generic method {method.DeclaringType!.FullName}.{method.Name} on a @dotnet module!"); } // check that there are not more overloads diff --git a/src/Framework/Framework/Compilation/ControlTree/IAbstractDotnetViewModuleDirective.cs b/src/Framework/Framework/Compilation/ControlTree/IAbstractDotnetViewModuleDirective.cs index e1a741ff07..920d5f6ff3 100644 --- a/src/Framework/Framework/Compilation/ControlTree/IAbstractDotnetViewModuleDirective.cs +++ b/src/Framework/Framework/Compilation/ControlTree/IAbstractDotnetViewModuleDirective.cs @@ -3,5 +3,5 @@ public interface IAbstractDotnetViewModuleDirective : IAbstractDirective { /// Full type name of the module specified - ITypeDescriptor ModuleType { get; } + ITypeDescriptor? ModuleType { get; } } diff --git a/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedDotnetViewModuleDirective.cs b/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedDotnetViewModuleDirective.cs index 13ad1c267b..68e2098b1b 100644 --- a/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedDotnetViewModuleDirective.cs +++ b/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedDotnetViewModuleDirective.cs @@ -8,7 +8,7 @@ namespace DotVVM.Framework.Compilation.ControlTree.Resolved; public class ResolvedDotnetViewModuleDirective : ResolvedDirective, IAbstractDotnetViewModuleDirective { /// Full .NET type of the module - public ITypeDescriptor ModuleType { get; } + public ITypeDescriptor? ModuleType { get; } public ResolvedDotnetViewModuleDirective(DirectiveCompilationService directiveCompilationService, DothtmlDirectiveNode node, BindingParserNode typeName, ImmutableList imports) : base(node) diff --git a/src/Framework/Framework/Compilation/Directives/DotnetViewModuleDirectiveCompiler.cs b/src/Framework/Framework/Compilation/Directives/DotnetViewModuleDirectiveCompiler.cs index 89cc3d80c5..4496997c2c 100644 --- a/src/Framework/Framework/Compilation/Directives/DotnetViewModuleDirectiveCompiler.cs +++ b/src/Framework/Framework/Compilation/Directives/DotnetViewModuleDirectiveCompiler.cs @@ -58,7 +58,7 @@ public DotnetViewModuleDirectiveCompiler(IReadOnlyDictionary(); + T GetViewModelSnapshot(); void PatchViewModel(object data); diff --git a/src/Framework/Interop.DotnetWasm/ViewModuleContext.cs b/src/Framework/Interop.DotnetWasm/ViewModuleContext.cs index ff13e928f3..b7c37cad7f 100644 --- a/src/Framework/Interop.DotnetWasm/ViewModuleContext.cs +++ b/src/Framework/Interop.DotnetWasm/ViewModuleContext.cs @@ -6,10 +6,10 @@ public class ViewModuleContext : IViewModuleContext private readonly string instanceName; private readonly DotvvmClientSerializer serializer; - public T? GetViewModelSnapshot() + public T GetViewModelSnapshot() { var json = DotnetWasmInterop.GetViewModelSnapshot(); - return (T?)serializer.Deserialize(typeof(T), json); + return (T)serializer.Deserialize(typeof(T), json)!; } public void PatchViewModel(object data) diff --git a/src/Samples/CSharpClient/TypeMarshallingModule.cs b/src/Samples/CSharpClient/TypeMarshallingModule.cs index ad6ac56a3f..cd2a95e44d 100644 --- a/src/Samples/CSharpClient/TypeMarshallingModule.cs +++ b/src/Samples/CSharpClient/TypeMarshallingModule.cs @@ -68,7 +68,7 @@ public class ChildObject { public int Int { get; set; } - public string String { get; set; } + public string String { get; set; } = null!; } public record ChildRecord(int Int, string String); From f526386beadafd4974b9eda767f712a30de4c7f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Sun, 4 Dec 2022 20:56:13 +0100 Subject: [PATCH 12/13] Fixed tests --- .../Framework/Binding/ViewModuleReferenceInfo.cs | 2 +- .../Binding/DotnetViewModuleMethodTranslator.cs | 7 ++++--- .../ControlTree/BindingExtensionParameter.cs | 6 ++++-- ...trolTests.MarkupControl_InternalProperty.html | 2 +- ...arkupControlTests.MarkupControl_JsInvoke.html | 2 +- ...ModulesServerSideTests.IncludeViewModule.html | 8 ++++---- ...rverSideTests.IncludeViewModuleInControl.html | 16 ++++++++-------- ...erializationTests.SerializeDefaultConfig.json | 11 +++++++++++ 8 files changed, 34 insertions(+), 20 deletions(-) diff --git a/src/Framework/Framework/Binding/ViewModuleReferenceInfo.cs b/src/Framework/Framework/Binding/ViewModuleReferenceInfo.cs index 36411e4a43..9129fef56b 100644 --- a/src/Framework/Framework/Binding/ViewModuleReferenceInfo.cs +++ b/src/Framework/Framework/Binding/ViewModuleReferenceInfo.cs @@ -64,7 +64,7 @@ public ViewModuleReferenceInfo(string viewId, ViewModuleReferencedModule[] refer private string GenerateModuleBatchUniqueId() { using var sha = SHA256.Create(); - 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!) : ""))))) + 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("=", ""); } } diff --git a/src/Framework/Framework/Compilation/Binding/DotnetViewModuleMethodTranslator.cs b/src/Framework/Framework/Compilation/Binding/DotnetViewModuleMethodTranslator.cs index 92c7004c91..cb237247a3 100644 --- a/src/Framework/Framework/Compilation/Binding/DotnetViewModuleMethodTranslator.cs +++ b/src/Framework/Framework/Compilation/Binding/DotnetViewModuleMethodTranslator.cs @@ -5,6 +5,7 @@ using System.Text; using System.Threading.Tasks; using System.Transactions; +using DotVVM.Framework.Binding; using DotVVM.Framework.Compilation.ControlTree; using DotVVM.Framework.Compilation.Javascript; using DotVVM.Framework.Compilation.Javascript.Ast; @@ -23,8 +24,8 @@ public class DotnetViewModuleMethodTranslator : IJavascriptMethodTranslator } // check whether we have the annotation - otherwise the type is not used in the _dotnet context and will not be translated - var target = context.JsExpression(); - if (target.Annotation() is not { } annotation) + var annotation = context.OriginalExpression.GetParameterAnnotation(); + if (annotation is null || annotation.ExtensionParameter is not DotnetExtensionParameter extensionParameter) { return null; } @@ -53,7 +54,7 @@ public class DotnetViewModuleMethodTranslator : IJavascriptMethodTranslator } // translate the method - var viewIdOrElementExpr = annotation.IsMarkupControl ? new JsSymbolicParameter(JavascriptTranslator.CurrentElementParameter) : (JsExpression)new JsLiteral(annotation.Id); + var viewIdOrElementExpr = extensionParameter.IsMarkupControl ? new JsSymbolicParameter(JavascriptTranslator.CurrentElementParameter) : (JsExpression)new JsLiteral(extensionParameter.Id); return new JsIdentifierExpression("dotvvm").Member("viewModules").Member("call") .Invoke( diff --git a/src/Framework/Framework/Compilation/ControlTree/BindingExtensionParameter.cs b/src/Framework/Framework/Compilation/ControlTree/BindingExtensionParameter.cs index e9ab8d456c..a35d642094 100644 --- a/src/Framework/Framework/Compilation/ControlTree/BindingExtensionParameter.cs +++ b/src/Framework/Framework/Compilation/ControlTree/BindingExtensionParameter.cs @@ -233,12 +233,14 @@ public override Expression GetServerEquivalent(Expression controlParameter) throw new DotvvmCompilationException($"The type {type} referenced in the @dotnet 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))); + return Expression.New(constructors[0], Expression.Constant(null, typeof(object))) + .AddParameterAnnotation(new BindingParameterAnnotation(extensionParameter: this)); } public override JsExpression GetJsTranslation(JsExpression dataContext) { - return new JsIdentifierExpression("dotvvm").Member("viewModules").WithAnnotation(new ViewModuleAnnotation(Id, IsMarkupControl, ResolvedTypeDescriptor.ToSystemType(this.Type))); + return new JsIdentifierExpression("dotvvm").Member("viewModules") + .WithAnnotation(new ViewModuleAnnotation(Id, IsMarkupControl, ResolvedTypeDescriptor.ToSystemType(this.Type))); } public class ViewModuleAnnotation diff --git a/src/Tests/ControlTests/testoutputs/MarkupControlTests.MarkupControl_InternalProperty.html b/src/Tests/ControlTests/testoutputs/MarkupControlTests.MarkupControl_InternalProperty.html index e84fa989d5..bf16e52e70 100644 --- a/src/Tests/ControlTests/testoutputs/MarkupControlTests.MarkupControl_InternalProperty.html +++ b/src/Tests/ControlTests/testoutputs/MarkupControlTests.MarkupControl_InternalProperty.html @@ -1,7 +1,7 @@ -
+
diff --git a/src/Tests/ControlTests/testoutputs/MarkupControlTests.MarkupControl_JsInvoke.html b/src/Tests/ControlTests/testoutputs/MarkupControlTests.MarkupControl_JsInvoke.html index 960a66701e..4a9d2eeb0e 100644 --- a/src/Tests/ControlTests/testoutputs/MarkupControlTests.MarkupControl_JsInvoke.html +++ b/src/Tests/ControlTests/testoutputs/MarkupControlTests.MarkupControl_JsInvoke.html @@ -1,7 +1,7 @@ -
+
diff --git a/src/Tests/ControlTests/testoutputs/ViewModulesServerSideTests.IncludeViewModule.html b/src/Tests/ControlTests/testoutputs/ViewModulesServerSideTests.IncludeViewModule.html index 46df5cdf5a..92f9879ed3 100644 --- a/src/Tests/ControlTests/testoutputs/ViewModulesServerSideTests.IncludeViewModule.html +++ b/src/Tests/ControlTests/testoutputs/ViewModulesServerSideTests.IncludeViewModule.html @@ -11,10 +11,10 @@ - + - + @@ -29,8 +29,8 @@ "dotvvm.internal", "dotvvm", "dotvvm.debug", - "viewModule.import.6DLlOTYMqGV5yPAJoz5k_moKDBgEmZ3_HLLQ2zKDo74", - "viewModule.init.6DLlOTYMqGV5yPAJoz5k_moKDBgEmZ3_HLLQ2zKDo74" + "viewModule.import.txdc3tmh_O-C0Z6NbwaMDcdUBct4MPc2ESN2w2mvtZU", + "viewModule.init.txdc3tmh_O-C0Z6NbwaMDcdUBct4MPc2ESN2w2mvtZU" ], "typeMetadata": {} }"> diff --git a/src/Tests/ControlTests/testoutputs/ViewModulesServerSideTests.IncludeViewModuleInControl.html b/src/Tests/ControlTests/testoutputs/ViewModulesServerSideTests.IncludeViewModuleInControl.html index c897ea466f..31b07e946b 100644 --- a/src/Tests/ControlTests/testoutputs/ViewModulesServerSideTests.IncludeViewModuleInControl.html +++ b/src/Tests/ControlTests/testoutputs/ViewModulesServerSideTests.IncludeViewModuleInControl.html @@ -8,26 +8,26 @@ - + - + - + -
+
-
+
@@ -49,10 +49,10 @@ "knockout", "dotvvm.internal", "dotvvm", - "viewModule.import.lIquTPFe3a42GboPePTmYZ4Xc1S4ok-JXmZud9HUybE", + "viewModule.import.-X0xRoPX49kw-adwrbsYQEJD_niSRyAgf2xH3URtlgw", "dotvvm.debug", - "viewModule.import.HIj399FcjVwbyaIvDKFctn7Ghr0UMajY-haUgZypcHs", - "viewModule.init.HIj399FcjVwbyaIvDKFctn7Ghr0UMajY-haUgZypcHs" + "viewModule.import.Zi0iKhaMCabp2tHzbqfe2sm6goFucgC1ZwGOP_Pli5k", + "viewModule.init.Zi0iKhaMCabp2tHzbqfe2sm6goFucgC1ZwGOP_Pli5k" ], "typeMetadata": { "RPyCWRg9lOhkRJyH": { diff --git a/src/Tests/Runtime/config-tests/ConfigurationSerializationTests.SerializeDefaultConfig.json b/src/Tests/Runtime/config-tests/ConfigurationSerializationTests.SerializeDefaultConfig.json index 37417c9dd9..2090a52421 100644 --- a/src/Tests/Runtime/config-tests/ConfigurationSerializationTests.SerializeDefaultConfig.json +++ b/src/Tests/Runtime/config-tests/ConfigurationSerializationTests.SerializeDefaultConfig.json @@ -68,6 +68,17 @@ "knockout" ], "RenderPosition": "Anywhere" + }, + "dotvvm.interop.dotnet-wasm": { + "Defer": true, + "Location": { + "$type": "DotVVM.Framework.ResourceManagement.EmbeddedResourceLocation, DotVVM.Framework", + "Assembly": "DotVVM.Framework, Version=***, Culture=neutral, PublicKeyToken=23f3607db32275da", + "Name": "DotVVM.Framework.obj.javascript.dotvvmStaticResources.dotnetWasmViewModule.js", + "DebugName": "DotVVM.Framework.obj.javascript.dotvvmStaticResources.dotnetWasmViewModule.js" + }, + "MimeType": "text/javascript", + "RenderPosition": "Anywhere" } }, "scripts": { From b990ff4933e50c9c41c781c426fe4c260148c847 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Sun, 4 Dec 2022 21:08:28 +0100 Subject: [PATCH 13/13] Fixed tests --- src/Tests/ControlTests/ViewModulesServerSideTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tests/ControlTests/ViewModulesServerSideTests.cs b/src/Tests/ControlTests/ViewModulesServerSideTests.cs index 1b7616967e..26732e3dc2 100644 --- a/src/Tests/ControlTests/ViewModulesServerSideTests.cs +++ b/src/Tests/ControlTests/ViewModulesServerSideTests.cs @@ -41,14 +41,14 @@ public async Task NamedCommandWithoutViewModule_StaticCommand() { var r = await Assert.ThrowsExceptionAsync(() => cth.RunPage(typeof(object), @" ")); - Assert.AreEqual("Validation error in NamedCommand at line 7: The NamedCommand control can be used only in pages or controls that have the @js directive.", r.Message); + Assert.AreEqual("Validation error in NamedCommand at line 7: The NamedCommand control can be used only in pages or controls that have the @js or @csharp directive.", r.Message); } [TestMethod] public async Task NamedCommandWithoutViewModule_Command() { var r = await Assert.ThrowsExceptionAsync(() => cth.RunPage(typeof(object), @" ")); - Assert.AreEqual("Validation error in NamedCommand at line 7: The NamedCommand control can be used only in pages or controls that have the @js directive.", r.Message); + Assert.AreEqual("Validation error in NamedCommand at line 7: The NamedCommand control can be used only in pages or controls that have the @js or @csharp directive.", r.Message); } [TestMethod]