Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge v4.3 changes into v5 main #1857

Merged
merged 54 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
7a4ed64
.NET Core projects updated to .NET 6
tomasherceg Jul 14, 2024
0e94135
Regenerated web.config files
tomasherceg Jul 15, 2024
1672661
Removed unnecessary publish profiles and upgraded remaining projects
tomasherceg Aug 1, 2024
d2b3cd1
Support for localizable routes
tomasherceg May 29, 2024
989f9b4
Reverted unnecessary file changes
tomasherceg May 29, 2024
9a66b29
Fixed build errors and unit tests
tomasherceg Jun 4, 2024
fed797c
Implemented partial route matching and handlers
tomasherceg Jul 12, 2024
3f6f134
Fixed binding redirects
tomasherceg Jul 12, 2024
de399fc
Review comments resolved
tomasherceg Jul 12, 2024
d304c1e
Fixed issues in tests
tomasherceg Jul 14, 2024
390bb32
Removed .NET 3.1 from GitHub Actions
tomasherceg Jul 14, 2024
76c9245
Merge branch 'tmp' into main-v4
exyi Aug 17, 2024
f4f2060
enable github actions on main-* branches
exyi Aug 17, 2024
6eaa46d
AlternateCultureLinks control added
tomasherceg Aug 1, 2024
9b96691
Fixed build warnings and test
tomasherceg Aug 1, 2024
491d246
view compiler: avoid blocking the ThreadPool in the AfterApplicationS…
exyi Aug 12, 2024
a8c784d
Add some missing doccomments
exyi Aug 12, 2024
7a434a0
Doccomments for TextBox types
exyi Aug 17, 2024
0370cd4
Added helper methods to build command binding expressions
tomasherceg Aug 1, 2024
cf32aec
Fixed nullability warning
tomasherceg Aug 1, 2024
89f5abe
Added helper methods for resource binding
tomasherceg Aug 16, 2024
de43b4a
Add memory barries to writes at the end of a lock
exyi Aug 12, 2024
54bc1a4
Fix near page indexes crashing on invalid page number
exyi Aug 12, 2024
81e9365
Merge pull request #1844 from riganti/doccomments
exyi Aug 18, 2024
795adec
command binding creation - link to CommandBindingExpression constructor
exyi Aug 18, 2024
97e10bc
Add binding missing redirects to OWIN samples
exyi Aug 18, 2024
8f97ea0
Merge pull request #1846 from riganti/fix/near-page-index-OOR-v4
exyi Aug 18, 2024
add46ba
Merge pull request #1845 from riganti/lock-mem-barriers
exyi Aug 18, 2024
50578ee
Merge pull request #1839 from riganti/feature/binding-service-cache-m…
exyi Aug 18, 2024
315cf5f
Merge pull request #1843 from riganti/view-precompilation-limit-para
exyi Aug 18, 2024
8a148e6
AlternateCultureLinks: doccomments
exyi Aug 17, 2024
6fb56a7
Merge pull request #1840 from riganti/feature/route-localized-versions
exyi Aug 18, 2024
795fbaf
js translations: TimeOnly, DateOnly and DateTime.Now
exyi Aug 16, 2024
bb8a249
Merge pull request #1849 from riganti/datetimeonly-translations
exyi Aug 18, 2024
3a3f38c
Fix view compilation on OWIN (optional log)
exyi Aug 18, 2024
75cb710
Fix and maybe optimize CopyProperty for inherited properties
exyi Aug 28, 2024
1f06059
Optimize html encoding using .NET IndexOf
exyi Sep 26, 2023
99cbaf1
Small performance improvements in binding formatting
exyi Sep 27, 2023
7d05167
small allocation optimization
exyi Aug 28, 2024
50a2431
perf: cleanup rejected/unused code
exyi Aug 28, 2024
264b8e8
Preserve other hisory.state properties when storing the viewmodel on …
exyi Aug 17, 2024
5f9f48a
JS: restore last viewmodel from history API
exyi Sep 7, 2024
05b9554
Add UI test for back navigaton VM restore
exyi Sep 7, 2024
5ed77a9
bit less instructions in GenerateConcurrencyModeHandler
exyi Sep 7, 2024
780c9d1
Fixed failing UI test
tomasherceg Sep 7, 2024
d1825a5
Merge pull request #1848 from riganti/fix-viewmodel-restore
tomasherceg Sep 7, 2024
b22dab3
Merge pull request #1850 from riganti/fix-CopyProperty-formcontrolsen…
tomasherceg Sep 7, 2024
c0a911e
Merge pull request #1851 from riganti/perf-htmlwriter-indexof
tomasherceg Sep 7, 2024
4746085
Block postbacks for 5s after redirects
exyi Sep 7, 2024
a600e96
HtmlWriter: remove upper attribute warning, as it is redundant
exyi Sep 7, 2024
93b238f
Merge pull request #1853 from riganti/redirect-queue-block
tomasherceg Sep 7, 2024
2977480
Merge pull request #1854 from riganti/htmlwriter-uppercase-warning
tomasherceg Sep 7, 2024
2f57498
Fixed warnings in compilation status page
tomasherceg Sep 7, 2024
b6cc7e8
Merge pull request #1855 from riganti/fix/compilation-page-warnings
exyi Sep 8, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ runs:
dotnet-version: |
8.0.x
6.0.x
3.1.x
- if: ${{ runner.os == 'Windows' }}
uses: microsoft/[email protected]

Expand Down
16 changes: 10 additions & 6 deletions .github/uitest/uitest.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -171,13 +171,17 @@ function Test-Sample {
write-host $log
get-content $log | write-host
}
foreach ($log in dir c:\inetpub\logs\logfiles\*\*.log) {
write-host $log
get-content $log | write-host
if (test-path c:\inetpub\logs\logfiles) {
foreach ($log in dir c:\inetpub\logs\logfiles\*\*.log) {
write-host $log
get-content $log | write-host
}
}
foreach ($log in dir $root\artifacts\**\*.log) {
write-host $log
get-content $log | write-host
if (test-path $root\artifacts) {
foreach ($log in dir $root\artifacts\**\*.log) {
write-host $log
get-content $log | write-host
}
}
throw "The sample '${sampleName}' failed to start."
}
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
branches:
- 'main'
- 'main-*'
- 'release/**'
pull_request:
workflow_dispatch:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ public DistanceNearPageIndexesProvider(int distance)
/// <param name="pagingOptions">The settings for paging.</param>
public IList<int> GetIndexes(IPagingOptions pagingOptions)
{
var firstIndex = Math.Max(pagingOptions.PageIndex - distance, 0);
var lastIndex = Math.Min(pagingOptions.PageIndex + distance + 1, pagingOptions.PagesCount);
var count = pagingOptions.PagesCount;
var index = Math.Max(0, Math.Min(count - 1, pagingOptions.PageIndex)); // clamp index to be a valid page
var firstIndex = Math.Max(index - distance, 0);
var lastIndex = Math.Min(index + distance + 1, count);

return Enumerable
.Range(firstIndex, lastIndex - firstIndex)
Expand Down
6 changes: 6 additions & 0 deletions src/Framework/Core/Storage/UploadedFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,22 @@ namespace DotVVM.Core.Storage
{
public class UploadedFile
{
/// <summary> A unique, randomly generated ID of the uploaded file. Use this ID to get the file from <see cref="IUploadedFileStorage.GetFileAsync(Guid)" /> </summary>
public Guid FileId { get; set; }

/// <summary> A user-specified name of the file. Use with caution, the user may specify this to be any string (for example <c>../../Web.config</c>). </summary>
public string? FileName { get; set; }

/// <summary> Length of the file in bytes. Use with caution, the user may manipulate with this property and it might not correspond to the file returned from <see cref="IUploadedFileStorage" />. </summary>
public FileSize FileSize { get; set; } = new FileSize();

/// <summary> If the file type matched one of type MIME types or extensions in <c>FileUpload.AllowedFileTypes</c>. Use with caution, the user may manipulate with this property. </summary>
public bool IsFileTypeAllowed { get; set; } = true;

/// <summary> If the file size is larger that the limit specified in <c>FileUpload.MaxFileSize</c>. Use with caution, the user may manipulate with this property. </summary>
public bool IsMaxSizeExceeded { get; set; } = false;

/// <summary> If the file satisfies both allowed file types and the size limit. Use with caution, the user may manipulate with this property. </summary>
public bool IsAllowed
=> IsFileTypeAllowed && !IsMaxSizeExceeded;
}
Expand Down
6 changes: 3 additions & 3 deletions src/Framework/Framework/Binding/BindingHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,11 @@ public static string FormatKnockoutScript(this ParametrizedCode code, DotvvmBind
/// Gets Internal.PathFragmentProperty or DataContext.KnockoutExpression. Returns null if none of these is set.
/// </summary>
public static string? GetDataContextPathFragment(this DotvvmBindableObject currentControl) =>
(string?)currentControl.GetValue(Internal.PathFragmentProperty, inherit: false) ??
(currentControl.GetBinding(DotvvmBindableObject.DataContextProperty, inherit: false) is IValueBinding binding ?
currentControl.properties.TryGet(Internal.PathFragmentProperty, out var pathFragment) && pathFragment is string pathFragmentStr ? pathFragmentStr :
currentControl.properties.TryGet(DotvvmBindableObject.DataContextProperty, out var dataContext) && dataContext is IValueBinding binding ?
binding.GetProperty<SimplePathExpressionBindingProperty>()
.Code.FormatKnockoutScript(currentControl, binding) :
null);
null;


// PERF: maybe safe last GetValue's target/binding to ThreadLocal variable, so the path does not have to be traversed twice
Expand Down
44 changes: 44 additions & 0 deletions src/Framework/Framework/Binding/DotvvmBindingCacheHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,50 @@ public ValueBindingExpression<TResult> CreateValueBinding<TResult>(string code,
}));
}

/// <summary> Compiles a new `{resource: ...code...}` binding which can be evaluated server-side. The result is cached. <see cref="ResourceBindingExpression.ResourceBindingExpression(BindingCompilationService, IEnumerable{object})" /> </summary>
public ResourceBindingExpression CreateResourceBinding(string code, DataContextStack dataContext, BindingParserOptions? parserOptions = null)
{
return CreateCachedBinding("ResourceBinding:" + code, new object?[] { dataContext, parserOptions }, () =>
new ResourceBindingExpression(compilationService, new object?[] {
dataContext,
new OriginalStringBindingProperty(code),
parserOptions
}));
}

/// <summary> Compiles a new `{resource: ...code...}` binding which can be evaluated server-side. The result is implicitly converted to <typeparamref name="TResult" />. The result is cached. </summary>
public ResourceBindingExpression<TResult> CreateResourceBinding<TResult>(string code, DataContextStack dataContext, BindingParserOptions? parserOptions = null)
{
return CreateCachedBinding($"ResourceBinding<{typeof(TResult).ToCode()}>:{code}", new object?[] { dataContext, parserOptions }, () =>
new ResourceBindingExpression<TResult>(compilationService, new object?[] {
dataContext,
new OriginalStringBindingProperty(code),
parserOptions
}));
}

/// <summary> Compiles a new `{command: ...code...}` binding which can be evaluated server-side and also client-side. The result is cached. Note that command bindings might be easier to create using the <see cref="CommandBindingExpression.CommandBindingExpression(BindingCompilationService, Func{object[], System.Threading.Tasks.Task}, string)" /> constructor. </summary>
public CommandBindingExpression CreateCommand(string code, DataContextStack dataContext, BindingParserOptions? parserOptions = null)
{
return CreateCachedBinding($"Command:{code}", new object?[] { dataContext, parserOptions }, () =>
new CommandBindingExpression(compilationService, new object?[] {
dataContext,
new OriginalStringBindingProperty(code),
parserOptions
}));
}

/// <summary> Compiles a new `{command: ...code...}` binding which can be evaluated server-side and also client-side. The result is implicitly converted to <typeparamref name="TResult" />. The result is cached. Note that command bindings might be easier to create using the <see cref="CommandBindingExpression.CommandBindingExpression(BindingCompilationService, Func{object[], System.Threading.Tasks.Task}, string)" /> constructor. </summary>
public CommandBindingExpression<TResult> CreateCommand<TResult>(string code, DataContextStack dataContext, BindingParserOptions? parserOptions = null)
{
return CreateCachedBinding($"Command<{typeof(TResult).ToCode()}>:{code}", new object?[] { dataContext, parserOptions }, () =>
new CommandBindingExpression<TResult>(compilationService, new object?[] {
dataContext,
new OriginalStringBindingProperty(code),
parserOptions
}));
}

/// <summary> Compiles a new `{staticCommand: ...code...}` binding which can be evaluated server-side and also client-side. The result is cached. </summary>
public StaticCommandBindingExpression CreateStaticCommand(string code, DataContextStack dataContext, BindingParserOptions? parserOptions = null)
{
Expand Down
6 changes: 5 additions & 1 deletion src/Framework/Framework/Binding/DotvvmProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,11 @@ internal void AddUsedInCapability(DotvvmCapabilityProperty? p)
if (p is object)
lock(this)
{
UsedInCapabilities = UsedInCapabilities.Add(p);
if (UsedInCapabilities.Contains(p)) return;

var newArray = UsedInCapabilities.Add(p);
Thread.MemoryBarrier(); // make sure the array is complete before we let other threads use it lock-free
UsedInCapabilities = newArray;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ protected void AddNullResolvers()



string? toStringValue;
volatile string? toStringValue;
public override string ToString()
{
if (toStringValue is null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ namespace DotVVM.Framework.Binding.Expressions
[Options]
public class CommandBindingExpression : BindingExpression, ICommandBinding
{
public CommandBindingExpression(BindingCompilationService service, IEnumerable<object> properties) : base(service, properties)
public CommandBindingExpression(BindingCompilationService service, IEnumerable<object?> properties) : base(service, properties)
{
AddNullResolvers();
}
Expand Down Expand Up @@ -172,6 +172,6 @@ public CommandBindingExpression(BindingCompilationService service, BindingDelega
public class CommandBindingExpression<T> : CommandBindingExpression, ICommandBinding<T>
{
public new BindingDelegate<T> BindingDelegate => base.BindingDelegate.ToGeneric<T>();
public CommandBindingExpression(BindingCompilationService service, IEnumerable<object> properties) : base(service, properties) { }
public CommandBindingExpression(BindingCompilationService service, IEnumerable<object?> properties) : base(service, properties) { }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace DotVVM.Framework.Binding.Expressions
[Options]
public class ResourceBindingExpression : BindingExpression, IStaticValueBinding
{
public ResourceBindingExpression(BindingCompilationService service, IEnumerable<object> properties) : base(service, properties) { }
public ResourceBindingExpression(BindingCompilationService service, IEnumerable<object?> properties) : base(service, properties) { }

public BindingDelegate BindingDelegate => this.bindingDelegate.GetValueOrThrow(this);

Expand All @@ -34,7 +34,7 @@ public class OptionsAttribute : BindingCompilationOptionsAttribute

public class ResourceBindingExpression<T> : ResourceBindingExpression, IStaticValueBinding<T>
{
public ResourceBindingExpression(BindingCompilationService service, IEnumerable<object> properties) : base(service, properties) { }
public ResourceBindingExpression(BindingCompilationService service, IEnumerable<object?> properties) : base(service, properties) { }

public new BindingDelegate<T> BindingDelegate => base.BindingDelegate.ToGeneric<T>();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using DotVVM.Framework.Utils;
using System.Runtime.CompilerServices;
using System.Collections.Immutable;
using System.Threading;

namespace DotVVM.Framework.Compilation.ControlTree
{
Expand Down Expand Up @@ -81,8 +82,11 @@ internal void AddUsedInCapability(DotvvmCapabilityProperty? p)
if (p is object)
lock(this)
{
if (!UsedInCapabilities.Contains(p))
UsedInCapabilities = UsedInCapabilities.Add(p);
if (UsedInCapabilities.Contains(p)) return;

var newArray = UsedInCapabilities.Add(p);
Thread.MemoryBarrier();
UsedInCapabilities = newArray;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using DotVVM.Framework.Utils;
using DotVVM.Framework.Binding.Properties;
using DotVVM.Framework.Testing;
using Microsoft.Extensions.Logging;

namespace DotVVM.Framework.Compilation
{
Expand All @@ -19,14 +20,16 @@ public class DotvvmViewCompilationService : IDotvvmViewCompilationService
private readonly IControlBuilderFactory controlBuilderFactory;
private readonly CompilationTracer tracer;
private readonly IMarkupFileLoader markupFileLoader;
private readonly ILogger<DotvvmViewCompilationService>? log;
private readonly DotvvmConfiguration dotvvmConfiguration;

public DotvvmViewCompilationService(DotvvmConfiguration dotvvmConfiguration, IControlBuilderFactory controlBuilderFactory, CompilationTracer tracer, IMarkupFileLoader markupFileLoader)
public DotvvmViewCompilationService(DotvvmConfiguration dotvvmConfiguration, IControlBuilderFactory controlBuilderFactory, CompilationTracer tracer, IMarkupFileLoader markupFileLoader, ILogger<DotvvmViewCompilationService>? log = null)
{
this.dotvvmConfiguration = dotvvmConfiguration;
this.controlBuilderFactory = controlBuilderFactory;
this.tracer = tracer;
this.markupFileLoader = markupFileLoader;
this.log = log;
masterPages = new Lazy<ConcurrentDictionary<string, DotHtmlFileInfo>>(InitMasterPagesCollection);
controls = new Lazy<ImmutableArray<DotHtmlFileInfo>>(InitControls);
routes = new Lazy<ImmutableArray<DotHtmlFileInfo>>(InitRoutes);
Expand Down Expand Up @@ -108,7 +111,13 @@ public async Task<bool> CompileAll(bool buildInParallel = true, bool forceRecomp
}
}
var discoveredMasterPages = new ConcurrentDictionary<string, DotHtmlFileInfo>();

var maxParallelism = buildInParallel ? Environment.ProcessorCount : 1;
if (!dotvvmConfiguration.Debug && dotvvmConfiguration.Markup.ViewCompilation.Mode != ViewCompilationMode.DuringApplicationStart)
{
// in production when compiling after application start, only use half of the CPUs to leave room for handling requests
maxParallelism = (int)Math.Ceiling(maxParallelism * 0.5);
}
var sw = ValueStopwatch.StartNew();

var compilationTaskFactory = (DotHtmlFileInfo t) => () => {
BuildView(t, forceRecompile, out var masterPage);
Expand All @@ -117,15 +126,19 @@ public async Task<bool> CompileAll(bool buildInParallel = true, bool forceRecomp
};

var compileTasks = filesToCompile.Select(compilationTaskFactory).ToArray();
await ExecuteCompileTasks(compileTasks, buildInParallel);
var totalCompiledFiles = compileTasks.Length;
await ExecuteCompileTasks(compileTasks, maxParallelism);

while (discoveredMasterPages.Any())
{
compileTasks = discoveredMasterPages.Values.Select(compilationTaskFactory).ToArray();
totalCompiledFiles += compileTasks.Length;
discoveredMasterPages = new ConcurrentDictionary<string, DotHtmlFileInfo>();

await ExecuteCompileTasks(compileTasks, buildInParallel);
await ExecuteCompileTasks(compileTasks, maxParallelism);
}

log?.LogInformation("Compiled {0} DotHTML files on {1} threads in {2} s", totalCompiledFiles, maxParallelism, sw.ElapsedSeconds);
}
finally
{
Expand All @@ -135,11 +148,22 @@ public async Task<bool> CompileAll(bool buildInParallel = true, bool forceRecomp
return !GetFilesWithFailedCompilation().Any();
}

private async Task ExecuteCompileTasks(Action[] compileTasks, bool buildInParallel)
private static async Task ExecuteCompileTasks(Action[] compileTasks, int maxParallelism)
{
if (buildInParallel)
if (maxParallelism > 1)
{
await Task.WhenAll(compileTasks.Select(Task.Run));
var semaphore = new SemaphoreSlim(maxParallelism);
await Task.WhenAll(compileTasks.Select(async t => {
await semaphore.WaitAsync();
try
{
await Task.Run(t);
}
finally
{
semaphore.Release();
}
}));
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ public IEnumerable<MethodInfo> GetExtensionsForNamespaces(string[] @namespaces)
// it's most likely the same namespaces, so it won't help at all - only run into lock contention in System.Reflection
lock (methodsCache)
{
results = namespaces.Select(x => methodsCache.GetValueOrDefault(x)).ToArray();
var missingNamespaces = namespaces.Where(x => !methodsCache.ContainsKey(x)).ToArray();

var createdNamespaces = CreateExtensionsForNamespaces(missingNamespaces);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ public static JsExpression Invoke(this JsExpression target, IEnumerable<JsExpres
public static JsExpression Invoke(this JsExpression target, params JsExpression?[] arguments) =>
new JsInvocationExpression(target, arguments);

public static JsExpression CallMethod(this JsExpression target, string methodName, params JsExpression?[] arguments) =>
target.Member(methodName).Invoke(arguments);

public static JsExpression Indexer(this JsExpression target, JsExpression argument)
{
return new JsIndexerExpression(target, argument);
Expand Down
Loading
Loading