Skip to content

Commit

Permalink
Merge branch 'main' into fix/array-and-nullable-type-declarations
Browse files Browse the repository at this point in the history
  • Loading branch information
acizmarik committed Jun 11, 2021
2 parents f0303cb + 73ea46d commit 9f779b1
Show file tree
Hide file tree
Showing 73 changed files with 2,790 additions and 777 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ Component-based MVVM framework for ASP.NET
[![license](https://img.shields.io/github/license/riganti/dotvvm.svg?maxAge=2592000?style=plastic)]()
[![Join the chat at https://gitter.im/riganti/dotvvm](https://badges.gitter.im/riganti/dotvvm.svg)](https://gitter.im/riganti/dotvvm)

| ASP.NET Core 2.2 | ASP.NET Core 3.0 | OWIN |
| ASP.NET Core 2.1 | .NET 5.0 | OWIN |
|---------------------------|---------------------------|---------------------|
| [![Build status: ASP.NET Core 2.2](https://dev.azure.com/rigantitfs/DotVVM/_apis/build/status/CI/%5B2.1.0%5D%20DotVVM%20Framework%20-%20AspNet%20Core)](https://dev.azure.com/rigantitfs/DotVVM/_build/latest?definitionId=122) | [![Build status: ASP.NET Core 3.0](https://dev.azure.com/rigantitfs/DotVVM/_apis/build/status/CI/%5B2.1.0%5D%20DotVVM%20Framework%20-%20AspNet%20Core%20Latest)](https://dev.azure.com/rigantitfs/DotVVM/_build/latest?definitionId=208) | [![Build status: OWIN](https://dev.azure.com/rigantitfs/DotVVM/_apis/build/status/CI/%5B2.1.0%5D%20DotVVM%20Framework%20-%20Owin)](https://dev.azure.com/rigantitfs/DotVVM/_build/latest?definitionId=181) |
| [![Build status: ASP.NET Core 2.1](https://dev.azure.com/rigantitfs/DotVVM/_apis/build/status/CI/%5B2.1.0%5D%20DotVVM%20Framework%20-%20AspNet%20Core)](https://dev.azure.com/rigantitfs/DotVVM/_build/latest?definitionId=122) | [![Build status: .NET 5.0](https://dev.azure.com/rigantitfs/DotVVM/_apis/build/status/CI/%5B2.1.0%5D%20DotVVM%20Framework%20-%20AspNet%20Core%20Latest)](https://dev.azure.com/rigantitfs/DotVVM/_build/latest?definitionId=208) | [![Build status: OWIN](https://dev.azure.com/rigantitfs/DotVVM/_apis/build/status/CI/%5B2.1.0%5D%20DotVVM%20Framework%20-%20Owin)](https://dev.azure.com/rigantitfs/DotVVM/_build/latest?definitionId=181) |

[DotVVM](https://www.dotvvm.com) lets you build interactive web UIs with **just C# and HTML** using the **MVVM** approach.

Expand Down Expand Up @@ -90,7 +90,7 @@ There is also [dotnet new template](https://www.dotvvm.com/docs/tutorials/how-to

| | ASP.NET Core | OWIN |
|-------------------------|-----------------------------|-----------------------|
| Current stable version | `DotVVM.AspNetCore 3.0.1` | `DotVVM.Owin 3.0.1` |
| Current stable version | `DotVVM.AspNetCore 3.0.4` | `DotVVM.Owin 3.0.4` |
| Minimum runtime version | `.NET Core 2.1` | `.NET 4.5.1` |
| Minimum ASP.NET version | `ASP.NET Core 2.1` | `OWIN 3.0.1` |

Expand Down
27 changes: 27 additions & 0 deletions src/DotVVM.Framework.Tests/Binding/BindingCompilationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,22 @@ public void BindingCompiler_MemberAccessIdentifierMissing_Throws()
var result = ExecuteBinding("StringProp.", new[] { vm });
}

[TestMethod]
public void BindingCompiler_DictionaryIndexer_Get()
{
TestViewModel5 vm = new TestViewModel5();
var result = ExecuteBinding("Dictionary[2]", new[] { vm });
Assert.AreEqual(22, result);
}

[TestMethod]
public void BindingCompiler_DictionaryIndexer_Set()
{
TestViewModel5 vm = new TestViewModel5();
ExecuteBinding("Dictionary[1] = 123", new[] { vm }, null, expectedType: typeof(void));
Assert.AreEqual(123, vm.Dictionary[1]);
}

[TestMethod]
public void BindingCompiler_MultiBlockExpression_EnumAtEnd_CorrectResult()
{
Expand Down Expand Up @@ -906,6 +922,7 @@ class TestViewModel
public long LongProperty { get; set; }

public long[] LongArray => new long[] { 1, 2, long.MaxValue };
public List<long> LongList => new List<long>() { 1, 2, long.MaxValue };
public string[] StringArray => new string[] { "Hello ", "DotVVM" };
public TestViewModel2[] VmArray => new TestViewModel2[] { new TestViewModel2() };
public int[] IntArray { get; set; }
Expand Down Expand Up @@ -1016,6 +1033,16 @@ public Task Multiply()
}
}

class TestViewModel5
{
public Dictionary<int, int> Dictionary { get; set; } = new Dictionary<int, int>()
{
{ 1, 11 },
{ 2, 22 },
{ 3, 33 }
};
}

struct TestStruct
{
public int Int { get; set; }
Expand Down
292 changes: 225 additions & 67 deletions src/DotVVM.Framework.Tests/Binding/JavascriptCompilationTests.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@
resolve(r_0);
}, reject);
});
}($element, ko.contextFor($element)))}.bind(this),$element,[],[])} } -->
}($element, ko.contextFor($element)))}.bind(this),$element,[],[],undefined,undefined,undefined)} } -->
<input type="button" value="" onclick="dotvvm.applyPostbackHandlers(function(options){return (function(a, b) {
return new Promise(function(resolve, reject) {
Promise.resolve((b = a.$control.Click()) &amp;&amp; b()).then(function(r_0) {
resolve(r_0);
}, reject);
});
}(ko.contextFor(this)))}.bind(this),this,[],[]).catch(dotvvm.log.logPostBackScriptError);event.stopPropagation();return false;">
}(ko.contextFor(this)))}.bind(this),this,[],[],undefined,undefined,undefined).catch(dotvvm.log.logPostBackScriptError);event.stopPropagation();return false;">
<!-- /ko -->
</div>
<!-- /ko -->
Expand All @@ -29,14 +29,14 @@
resolve(r_0);
}, reject);
});
}($element, ko.contextFor($element)))}.bind(this),$element,[],[])} } -->
}($element, ko.contextFor($element)))}.bind(this),$element,[],[],undefined,undefined,undefined)} } -->
<input type="button" value="" onclick="dotvvm.applyPostbackHandlers(function(options){return (function(a, b) {
return new Promise(function(resolve, reject) {
Promise.resolve((b = a.$control.Click()) &amp;&amp; b()).then(function(r_0) {
resolve(r_0);
}, reject);
});
}(ko.contextFor(this)))}.bind(this),this,[],[]).catch(dotvvm.log.logPostBackScriptError);event.stopPropagation();return false;">
}(ko.contextFor(this)))}.bind(this),this,[],[],undefined,undefined,undefined).catch(dotvvm.log.logPostBackScriptError);event.stopPropagation();return false;">
<!-- /ko -->
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
<!-- ko dotvvm-with-view-modules: { viewIdOrElement: $element, modules: ["controlModule"] } -->
<input type="button" value="" onclick="dotvvm.applyPostbackHandlers(function(options){return (function(a) {
return Promise.resolve(dotvvm.viewModules.call(a, &quot;name&quot;, [1]));
}(this))}.bind(this),this,[],[]).catch(dotvvm.log.logPostBackScriptError);event.stopPropagation();return false;">
}(this))}.bind(this),this,[],[],undefined,undefined,undefined).catch(dotvvm.log.logPostBackScriptError);event.stopPropagation();return false;">
<!-- /ko -->
<!-- /ko -->
</div>
Expand All @@ -40,7 +40,7 @@
<!-- ko dotvvm-with-view-modules: { viewIdOrElement: $element, modules: ["controlModule"] } -->
<input type="button" value="" onclick="dotvvm.applyPostbackHandlers(function(options){return (function(a) {
return Promise.resolve(dotvvm.viewModules.call(a, &quot;name&quot;, [1]));
}(this))}.bind(this),this,[],[]).catch(dotvvm.log.logPostBackScriptError);event.stopPropagation();return false;">
}(this))}.bind(this),this,[],[],undefined,undefined,undefined).catch(dotvvm.log.logPostBackScriptError);event.stopPropagation();return false;">
<!-- /ko -->
<!-- /ko -->
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,13 @@ public void ViewModelTypeMetadata_TypeMetadata()
var checker = new OutputChecker("testoutputs");
checker.CheckJsonObject(result);
}

[Flags]
enum SampleEnum
{
Zero = 0,
One = 1,
Two = 2
Two = 2, // the order is mismatched intentionally - the serializer should fix it
One = 1
}

class TestViewModel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
},
"Naz5KfxmZJ+FNKch08f+TL+36i4=": {
"type": "enum",
"isFlags": true,
"values": {
"Zero": 0,
"One": 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ public ExpectedTypeBindingProperty GetExpectedType(AssignedPropertyBindingProper
public static CodeSymbolicParameter OptionalKnockoutContextParameter = new CodeSymbolicParameter("CommandBindingExpression.OptionalKnockoutContextParameter", CodeParameterAssignment.FromIdentifier("null"));
public static CodeSymbolicParameter PostbackHandlersParameter = new CodeSymbolicParameter("CommandBindingExpression.PostbackHandlersParameter");
public static CodeSymbolicParameter CommandArgumentsParameter = new CodeSymbolicParameter("CommandBindingExpression.CommandArgumentsParameter");
public static CodeSymbolicParameter AbortSignalParameter = new CodeSymbolicParameter("CommandBindingExpression.AbortSignalParameter");

private static ParametrizedCode createJavascriptPostbackInvocation(JsExpression? commandArgs) =>
new JsIdentifierExpression("dotvvm").Member("postBack").Invoke(
Expand All @@ -84,7 +85,8 @@ public ExpectedTypeBindingProperty GetExpectedType(AssignedPropertyBindingProper
new JsSymbolicParameter(ControlUniqueIdParameter),
new JsSymbolicParameter(OptionalKnockoutContextParameter),
new JsSymbolicParameter(PostbackHandlersParameter),
commandArgs
commandArgs ?? new JsLiteral(new object[] { }),
new JsSymbolicParameter(AbortSignalParameter)
).FormatParametrizedScript();

private static ParametrizedCode javascriptPostbackInvocation = createJavascriptPostbackInvocation(
Expand Down
51 changes: 51 additions & 0 deletions src/DotVVM.Framework/Binding/HelperNamespace/ListExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DotVVM.Framework.Binding.HelperNamespace
{
public static class ListExtensions
{
public static void AddOrUpdate<T>(this List<T> list, T element, Func<T,bool> matcher, Func<T,T> updater)
{
var found = false;
for (var index = 0; index < list.Count; index++)
{
if (!matcher(list[index]))
continue;

found = true;
list[index] = updater(list[index]);
}

if (!found)
list.Add(element);
}

public static void RemoveFirst<T>(this List<T> list, Func<T,bool> predicate)
{
for (var index = 0; index < list.Count; index++)
{
if (predicate(list[index]))
{
list.RemoveAt(index);
return;
}
}
}

public static void RemoveLast<T>(this List<T> list, Func<T, bool> predicate)
{
for (var index = list.Count - 1; index >= 0; index--)
{
if (predicate(list[index]))
{
list.RemoveAt(index);
return;
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using DotVVM.Framework.Compilation.Parser.Binding.Parser.Annotations;

namespace DotVVM.Framework.Compilation.Binding
{
Expand Down Expand Up @@ -163,12 +164,7 @@ protected override Expression VisitUnaryOperator(UnaryOperatorBindingParserNode

protected override Expression VisitBinaryOperator(BinaryOperatorBindingParserNode node)
{
var left = HandleErrors(node.FirstExpression, Visit);
var right = HandleErrors(node.SecondExpression, Visit);
ThrowOnErrors();

ExpressionType eop;

switch (node.Operator)
{
case BindingTokenType.AddOperator:
Expand Down Expand Up @@ -220,12 +216,24 @@ protected override Expression VisitBinaryOperator(BinaryOperatorBindingParserNod
eop = ExpressionType.OrElse;
break;
case BindingTokenType.AssignOperator:
node.FirstExpression.Annotations.Add(WriteAccessAnnotation.Instance);
eop = ExpressionType.Assign;
break;
default:
throw new NotSupportedException($"unary operator { node.Operator } is not supported");
}

var left = HandleErrors(node.FirstExpression, Visit);
var right = HandleErrors(node.SecondExpression, Visit);
ThrowOnErrors();

if (eop == ExpressionType.Assign && left is IndexExpression indexExpression)
{
// Convert to explicit method call `set_{Indexer}(index, value)`
var setMethod = indexExpression.Indexer.SetMethod;
return Expression.Call(indexExpression.Object, setMethod, indexExpression.Arguments.Concat(new[] { right }));
}

return memberExpressionFactory.GetBinaryOperator(left, right, eop);
}

Expand All @@ -235,7 +243,15 @@ protected override Expression VisitArrayAccess(ArrayAccessBindingParserNode node
var index = HandleErrors(node.ArrayIndexExpression, Visit);
ThrowOnErrors();

return ExpressionHelper.GetIndexer(target, index);
var expression = ExpressionHelper.GetIndexer(target, index);
if (expression is IndexExpression indexExpression && !node.Annotations.Contains(WriteAccessAnnotation.Instance))
{
// Convert to get_{Indexer}(index, value) call
var getMethod = indexExpression.Indexer.GetMethod;
return Expression.Call(indexExpression.Object, getMethod, indexExpression.Arguments);
}

return expression;
}

protected override Expression VisitFunctionCall(FunctionCallBindingParserNode node)
Expand Down
79 changes: 35 additions & 44 deletions src/DotVVM.Framework/Compilation/DefaultControlBuilderFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,11 @@ public class DefaultControlBuilderFactory : IControlBuilderFactory

public Func<IViewCompiler> ViewCompilerFactory { get; private set; }

private ConcurrentDictionary<MarkupFile, (ControlBuilderDescriptor, Lazy<IControlBuilder>)> controlBuilders = new ConcurrentDictionary<MarkupFile, (ControlBuilderDescriptor, Lazy<IControlBuilder>)>();
private ConcurrentDictionary<MarkupFile, Lazy<(ControlBuilderDescriptor, Lazy<IControlBuilder>)>> controlBuilders = new ConcurrentDictionary<MarkupFile, Lazy<(ControlBuilderDescriptor, Lazy<IControlBuilder>)>>();


public DefaultControlBuilderFactory(DotvvmConfiguration configuration, IMarkupFileLoader markupFileLoader, CompiledAssemblyCache compiledAssemblyCache)
{
for (int i = 0; i < compilationLocks.Length; i++)
{
compilationLocks[i] = new object();
}

this.configuration = configuration;

// WORKAROUND: there is a circular dependency
Expand All @@ -54,58 +49,54 @@ public DefaultControlBuilderFactory(DotvvmConfiguration configuration, IMarkupFi
public (ControlBuilderDescriptor descriptor, Lazy<IControlBuilder> builder) GetControlBuilder(string virtualPath)
{
var markupFile = markupFileLoader.GetMarkup(configuration, virtualPath) ?? throw new DotvvmCompilationException($"File '{virtualPath}' was not found. This exception is possibly caused because of incorrect route registration.");
return controlBuilders.GetOrAdd(markupFile, CreateControlBuilder);
return controlBuilders.GetOrAdd(
markupFile,
// use lazy - do not compile the same view multiple times
file => new Lazy<(ControlBuilderDescriptor, Lazy<IControlBuilder>)>(() => CreateControlBuilder(file))
).Value;
}

object[] compilationLocks = new object[Environment.ProcessorCount * 2];

/// <summary>
/// Creates the control builder.
/// </summary>
private (ControlBuilderDescriptor, Lazy<IControlBuilder>) CreateControlBuilder(MarkupFile file)
{
var lockId = (file.GetHashCode() & 0x7fffffff) % compilationLocks.Length;
// do not compile the same view multiple times
lock (compilationLocks[lockId])
var namespaceName = GetNamespaceFromFileName(file.FileName, file.LastWriteDateTimeUtc);
var assemblyName = namespaceName;
var className = GetClassFromFileName(file.FileName) + "ControlBuilder";
void editCompilationException(DotvvmCompilationException ex)
{
if (controlBuilders.ContainsKey(file)) return controlBuilders[file];
if (ex.FileName == null)
ex.FileName = file.FullPath;
else if (!Path.IsPathRooted(ex.FileName))
ex.FileName = Path.Combine(
file.FullPath.Remove(file.FullPath.Length - file.FileName.Length),
ex.FileName);
}
try
{
var (descriptor, factory) = ViewCompilerFactory().CompileView(file.ContentsReaderFactory(), file.FileName, assemblyName, namespaceName, className);

var namespaceName = GetNamespaceFromFileName(file.FileName, file.LastWriteDateTimeUtc);
var assemblyName = namespaceName;
var className = GetClassFromFileName(file.FileName) + "ControlBuilder";
void editCompilationException(DotvvmCompilationException ex)
if (descriptor.ViewModuleReference != null)
{
if (ex.FileName == null)
ex.FileName = file.FullPath;
else if (!Path.IsPathRooted(ex.FileName))
ex.FileName = Path.Combine(
file.FullPath.Remove(file.FullPath.Length - file.FileName.Length),
ex.FileName);
var (import, init) = descriptor.ViewModuleReference.BuildResources(configuration.Resources);
configuration.Resources.RegisterViewModuleResources(import, init);
}
try
{
var (descriptor, factory) = ViewCompilerFactory().CompileView(file.ContentsReaderFactory(), file.FileName, assemblyName, namespaceName, className);

if (descriptor.ViewModuleReference != null)
return (descriptor, new Lazy<IControlBuilder>(() => {
try { return factory(); }
catch (DotvvmCompilationException ex)
{
var (import, init) = descriptor.ViewModuleReference.BuildResources(configuration.Resources);
configuration.Resources.RegisterViewModuleResources(import, init);
editCompilationException(ex);
throw;
}

return (descriptor, new Lazy<IControlBuilder>(() => {
try { return factory(); }
catch (DotvvmCompilationException ex)
{
editCompilationException(ex);
throw;
}
}));
}
catch (DotvvmCompilationException ex)
{
editCompilationException(ex);
throw;
}
}));
}
catch (DotvvmCompilationException ex)
{
editCompilationException(ex);
throw;
}
}

Expand Down Expand Up @@ -213,7 +204,7 @@ public void RegisterControlBuilder(string file, IControlBuilder builder)
{
var markup = markupFileLoader.GetMarkup(configuration, file) ??
throw new Exception($"Could not load markup file {file}.");
controlBuilders.TryAdd(markup, (builder.Descriptor, new Lazy<IControlBuilder>(() => builder)));
controlBuilders.TryAdd(markup, new Lazy<(ControlBuilderDescriptor, Lazy<IControlBuilder>)>(() => (builder.Descriptor, new Lazy<IControlBuilder>(() => builder))));
}

private static readonly HashSet<string> csharpKeywords = new HashSet<string>(new[]
Expand Down
Loading

0 comments on commit 9f779b1

Please sign in to comment.