1
0

feat: Finate some details

This commit is contained in:
Ca2didi 2025-04-02 06:11:39 +08:00
parent c80b2759a9
commit 91097940fc
22 changed files with 295 additions and 81 deletions

View File

@ -8,6 +8,7 @@
<entry key="Flawless.Client/Theme/ToggleSwitch.axaml" value="Flawless.Client/Flawless.Client.csproj" />
<entry key="Flawless.Client/Views/HelloSetup/LoginPageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
<entry key="Flawless.Client/Views/HelloSetup/RegisterPageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
<entry key="Flawless.Client/Views/HelloSetup/ServerSetupPageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
<entry key="Flawless.Client/Views/HelloView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
<entry key="Flawless.Client/Views/HelloWindowView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
<entry key="Flawless.Client/Views/HomeView.axaml" value="Flawless.Client/Flawless.Client.csproj" />

View File

@ -106,6 +106,12 @@ public static class WorkPath
return sb.ToString();
}
public static string FormatPathDirectorySeparator(string path)
{
return path.Replace(Path.DirectorySeparatorChar, DirectorySeparatorChar)
.Replace(Path.AltDirectorySeparatorChar, DirectorySeparatorChar);
}
/// <summary>
/// Split work path into path vector.
/// </summary>

View File

@ -1,11 +0,0 @@
using System;
using Avalonia.Controls.Notifications;
namespace Flawless.Client;
public static class ErrorGUIHandler
{
public static void OnError(Exception ex)
{
}
}

View File

@ -99,7 +99,7 @@ public class Api : BaseService<Api>
public bool RequireRefreshToken()
{
if (_token.Value == null) return true;
if (DateTime.UtcNow.AddMinutes(1) > _token.Value.Expiration) return true;
if (DateTime.UtcNow > _token.Value.Expiration!.Value.UtcDateTime.AddMinutes(-2)) return true;
return false;
}

View File

@ -115,6 +115,7 @@ public class RepositoryService : BaseService<RepositoryService>
}
catch (Exception e)
{
UIHelper.NotifyError(e);
Console.WriteLine(e);
return null;
}
@ -164,6 +165,7 @@ public class RepositoryService : BaseService<RepositoryService>
}
catch (Exception e)
{
UIHelper.NotifyError(e);
Console.WriteLine(e);
return false;
}
@ -226,6 +228,7 @@ public class RepositoryService : BaseService<RepositoryService>
}
catch (Exception e)
{
UIHelper.NotifyError(e);
Console.WriteLine(e);
return false;
}
@ -328,6 +331,7 @@ public class RepositoryService : BaseService<RepositoryService>
}
catch (Exception e)
{
UIHelper.NotifyError(e);
Console.WriteLine(e);
await DeleteFromDiskAsync(repo);
return false;
@ -361,6 +365,7 @@ public class RepositoryService : BaseService<RepositoryService>
}
catch (Exception e)
{
UIHelper.NotifyError(e);
Console.WriteLine(e);
return false;
}
@ -417,6 +422,7 @@ public class RepositoryService : BaseService<RepositoryService>
}
catch (Exception e)
{
UIHelper.NotifyError(e);
Console.WriteLine(e);
return false;
}
@ -435,7 +441,6 @@ public class RepositoryService : BaseService<RepositoryService>
if (manifest == null) return null;
// Prepare folders
var path = PathUtility.GetWorkspacePath(repo.OwnerName, repo.Name);
var depotsRoot = PathUtility.GetWorkspaceDepotCachePath(repo.OwnerName, repo.Name);
Directory.CreateDirectory(depotsRoot);
@ -464,18 +469,22 @@ public class RepositoryService : BaseService<RepositoryService>
}
// Create mapping dictionary
var mappingDict = downloadedDepots.ToDictionary(i => i.Item1, i => i.Item2!);
var streamMap = downloadedDepots.ToDictionary(i => i.Item1, i => i.Item2!);
foreach (var dl in mainDepotLabel)
{
if (mappingDict.ContainsKey(dl.Id)) continue;
// If this file is not being opened, open it from file system
if (!streamMap.ContainsKey(dl.Id))
{
var dst = Path.Combine(depotsRoot, dl.Id.ToString());
mappingDict.Add(dl.Id, new FileStream(dst, FileMode.Create));
streamMap.Add(dl.Id, new FileStream(dst, FileMode.Open, FileAccess.Read, FileShare.Read));
}
}
return new RepositoryFileTreeAccessor(mappingDict, manifest.Value);
return new RepositoryFileTreeAccessor(streamMap, manifest.Value);
}
catch (Exception e)
{
UIHelper.NotifyError(e);
if (downloadedDepots != null)
foreach (var t in downloadedDepots)
{
@ -537,6 +546,7 @@ public class RepositoryService : BaseService<RepositoryService>
}
catch (Exception e)
{
UIHelper.NotifyError(e);
Console.WriteLine(e);
return null;
}
@ -561,6 +571,7 @@ public class RepositoryService : BaseService<RepositoryService>
}
catch (Exception e)
{
UIHelper.NotifyError(e);
Console.WriteLine(e);
return null;
}
@ -575,6 +586,7 @@ public class RepositoryService : BaseService<RepositoryService>
}
catch (Exception e)
{
UIHelper.NotifyError(e);
Console.WriteLine(e);
return false;
}
@ -630,8 +642,15 @@ public class RepositoryService : BaseService<RepositoryService>
if (accessor == null) return null; //todo this is a really fatal issue...
if (localDb.RepoAccessor != null)
{
try { await localDb.RepoAccessor.DisposeAsync(); }
catch (Exception e) { Console.WriteLine(e); }
try
{
await localDb.RepoAccessor.DisposeAsync();
}
catch (Exception e)
{
UIHelper.NotifyError(e);
Console.WriteLine(e);
}
}
// Point to newest state.
@ -644,6 +663,7 @@ public class RepositoryService : BaseService<RepositoryService>
}
catch (Exception e)
{
UIHelper.NotifyError(e);
Console.WriteLine(e);
return null;
}
@ -735,6 +755,7 @@ public class RepositoryService : BaseService<RepositoryService>
}
catch (Exception e)
{
UIHelper.NotifyError(e);
Directory.Delete(repoWs, true);
Console.WriteLine(e);
return null;

View File

@ -50,6 +50,7 @@ public partial class UserService : BaseService<UserService>
}
catch (Exception e)
{
UIHelper.NotifyError(e);
Console.WriteLine(e);
return null;
}
@ -80,6 +81,7 @@ public partial class UserService : BaseService<UserService>
}
catch (Exception e)
{
UIHelper.NotifyError(e);
Console.WriteLine(e);
return false;
}

View File

@ -0,0 +1,96 @@
using System;
using System.Threading.Tasks;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls.Notifications;
using Flawless.Client.ViewModels.ModalBox;
using Flawless.Client.Views.ModalBox;
using Ursa.Controls;
using Notification = Ursa.Controls.Notification;
using WindowNotificationManager = Ursa.Controls.WindowNotificationManager;
namespace Flawless.Client;
public static class UIHelper
{
private static WindowNotificationManager _notificationManager = null!;
public static WindowNotificationManager Notify
{
get
{
if (_notificationManager != null) return _notificationManager!;
var lf = ((IClassicDesktopStyleApplicationLifetime)App.Current.ApplicationLifetime);
if (!WindowNotificationManager.TryGetNotificationManager(lf.MainWindow!, out _notificationManager))
throw new Exception("Can not get notification manager");
_notificationManager!.Position = NotificationPosition.TopCenter;
return _notificationManager!;
}
}
public static void NotifyError(Exception ex)
{
try
{
var content = ex.ToString();
if (content.Length > 100) content = content.Substring(0, 100) + "...";
var nf = new Notification(ex.GetType().Name, content, NotificationType.Error);
Notify.Show(nf);
}
catch (Exception e)
{
Console.WriteLine("Can not notify error to users: " + e);
}
}
public static void NotifyError(string title, string content)
{
try
{
var nf = new Notification(title, content, NotificationType.Error);
Notify.Show(nf);
}
catch (Exception e)
{
Console.WriteLine($"Can not notify error to users: {title} - {content}, {e}");
}
}
public static Task<DialogResult> SimpleAskAsync(string content, DialogMode mode = DialogMode.None)
{
var opt = new OverlayDialogOptions
{
FullScreen = false,
Buttons = DialogButton.YesNo,
CanResize = false,
CanDragMove = false,
IsCloseButtonVisible = true,
CanLightDismiss = true,
Mode = mode
};
var vm = new SimpleMessageDialogViewModel(content);
return OverlayDialog.ShowModal<SimpleMessageDialogView, SimpleMessageDialogViewModel>(vm, AppDefaultValues.HostId, opt);
}
public static Task SimpleAlert(string content, DialogMode mode = DialogMode.Error)
{
var opt = new OverlayDialogOptions
{
FullScreen = false,
Buttons = DialogButton.YesNo,
CanResize = false,
CanDragMove = false,
IsCloseButtonVisible = true,
CanLightDismiss = true,
Mode = mode
};
var vm = new SimpleMessageDialogViewModel(content);
return OverlayDialog.ShowModal<SimpleMessageDialogView, SimpleMessageDialogViewModel>(vm, AppDefaultValues.HostId, opt);
}
}

View File

@ -21,8 +21,6 @@ public partial class LoginPageViewModel : ViewModelBase, IRoutableViewModel
[Reactive] private string _password = "4453A2b33";
[Reactive(SetModifier = AccessModifier.Protected)] private string _issue = String.Empty;
public IObservable<bool> CanLogin;
public IObservable<bool> CanRegister => Api.C.Status.Select(s => s != null && s.AllowPublicRegister);
@ -56,12 +54,12 @@ public partial class LoginPageViewModel : ViewModelBase, IRoutableViewModel
catch (ApiException ex)
{
await Console.Error.WriteLineAsync($"Login as '{Username}' Failed: {ex.Content}");
Issue = ex.Content ?? String.Empty;
UIHelper.NotifyError(ex);
}
catch (Exception ex)
{
await Console.Error.WriteLineAsync($"Login as '{Username}' Failed: {ex}");
Issue = ex.Message;
UIHelper.NotifyError(ex);
}
Console.WriteLine($"Login as '{Username}' success!");

View File

@ -23,8 +23,6 @@ public partial class RegisterPageViewModel : ViewModelBase, IRoutableViewModel
[Reactive] private string _password;
[Reactive(SetModifier = AccessModifier.Protected)] private string _issue;
public RegisterPageViewModel(IScreen hostScreen)
{
HostScreen = hostScreen;
@ -49,12 +47,12 @@ public partial class RegisterPageViewModel : ViewModelBase, IRoutableViewModel
catch (ApiException ex)
{
await Console.Error.WriteLineAsync($"Register as '{Username}' Failed: {ex.Content}");
Issue = ex.Content ?? String.Empty;
UIHelper.NotifyError(ex);
}
catch (Exception ex)
{
await Console.Error.WriteLineAsync($"Register as '{Username}' Failed: {ex}");
Issue = ex.Message;
UIHelper.NotifyError(ex);
}
Console.WriteLine($"Register as '{Username}' success!");

View File

@ -10,6 +10,7 @@ using Avalonia.Controls;
using Avalonia.Controls.Models.TreeDataGrid;
using DynamicData;
using DynamicData.Binding;
using Flawless.Abstraction;
using Flawless.Client.Models;
using Flawless.Client.Service;
using Flawless.Core.Modal;
@ -106,13 +107,14 @@ public partial class RepositoryViewModel : RoutableViewModelBase
public HierarchicalTreeDataGridSource<LocalChangesNode> LocalChange { get; }
public HierarchicalTreeDataGridSource<LocalChangesNode> FileTree { get; }
public FlatTreeDataGridSource<CommitTransitNode> Commits { get; }
public ObservableCollection<LocalChangesNode> LocalChangeSetRaw { get; } = new();
public ObservableCollection<LocalChangesNode> CurrentCommitFileTreeRaw { get; } = new();
public UserModel User { get; }
[Reactive] private bool _autoDetectChanges = true;
@ -181,9 +183,48 @@ public partial class RepositoryViewModel : RoutableViewModelBase
}
};
DetectLocalChangesAsyncCommand.Execute();
FileTree = new HierarchicalTreeDataGridSource<LocalChangesNode>(CurrentCommitFileTreeRaw)
{
Columns =
{
new HierarchicalExpanderColumn<LocalChangesNode>(
new TextColumn<LocalChangesNode, string>(
"Name",
n => Path.GetFileName(n.FullPath)),
n => n.Contents),
new TextColumn<LocalChangesNode, string>(
"File Type",
n => n.Contents != null ? "Folder" : Path.GetExtension(n.FullPath)),
new TextColumn<LocalChangesNode, ulong>(
"Size",
n => 0),
new TextColumn<LocalChangesNode, DateTime?>(
"ModifiedTime", n => n.ModifiedTime.HasValue ? n.ModifiedTime.Value.ToLocalTime() : null),
}
};
_ = StartupTasksAsync();
}
private async Task StartupTasksAsync()
{
await DetectLocalChangesAsyncCommand.Execute();
await RendererFileTreeAsync();
}
private async ValueTask RendererFileTreeAsync()
{
if (LocalDatabase.RepoAccessor == null) return;
var accessor = LocalDatabase.RepoAccessor;
var nodes = await CalculateFileTreeOfChangesNodeAsync(accessor.Select(
f => new LocalFileTreeAccessor.ChangeRecord(ChangeType.Add, f)));
CurrentCommitFileTreeRaw.Clear();
CurrentCommitFileTreeRaw.AddRange(nodes);
}
private void CollectChanges(List<LocalFileTreeAccessor.ChangeRecord> store, IEnumerable<LocalChangesNode> changesNode)
{
@ -201,21 +242,72 @@ public partial class RepositoryViewModel : RoutableViewModelBase
}
}
private Task<List<LocalChangesNode>> CalculateFileTreeOfChangesNodeAsync(IEnumerable<LocalFileTreeAccessor.ChangeRecord> changesNode)
{
return Task.Run(() =>
{
// Generate a map of all folders
var folderMap = new Dictionary<string, LocalChangesNode>();
var nodes = new List<LocalChangesNode>();
foreach (var file in changesNode)
{
var n = LocalChangesNode.FromWorkspaceFile(file);
var parentNode = AddParentToMap(file.File.WorkPath);
if (parentNode == null) nodes.Add(n);
else parentNode.Contents!.Add(n);
}
return nodes;
LocalChangesNode? AddParentToMap(string path)
{
path = WorkPath.FormatPathDirectorySeparator(Path.GetDirectoryName(path) ?? string.Empty);
// 如果为空,则其直接文件夹就是根目录
if (string.IsNullOrEmpty(path)) return null;
// 如果直接文件夹已经存在,则不再生成,直接返回即可
if (folderMap.TryGetValue(path, out var node)) return node;
// 生成当前文件夹,并先找到这个文件夹的直接文件夹
node = LocalChangesNode.FromFolder(path);
var parent = AddParentToMap(path);
folderMap.Add(path, node);
if (parent != null) parent.Contents!.Add(node);
else nodes.Add(node);
// 将新建的文件夹告知调用方
return node;
}
});
}
[ReactiveCommand]
private async Task CommitSelectedChangesAsync()
{
var changes = new List<LocalFileTreeAccessor.ChangeRecord>();
CollectChanges(changes, LocalChangeSetRaw);
if (changes.Count == 0) return;
var manifest = await RepositoryService.C.CommitWorkspaceAsBaselineAsync(Repository, changes,
LocalDatabase.CommitMessage ?? string.Empty);
if (changes.Count == 0)
{
await UIHelper.SimpleAlert("You haven't choose any changes yet!");
return;
}
if (string.IsNullOrWhiteSpace(LocalDatabase.CommitMessage))
{
await UIHelper.SimpleAlert("Commit message can not be empty!");
return;
}
var manifest = await RepositoryService.C.CommitWorkspaceAsBaselineAsync(Repository, changes, LocalDatabase.CommitMessage!);
if (manifest == null) return;
LocalDatabase.LocalAccessor.SetBaseline(manifest.Value.FilePaths);
LocalDatabase.CommitMessage = string.Empty;
await DetectLocalChangesAsyncCommand.Execute();
await RendererFileTreeAsync();
}
[ReactiveCommand]
@ -242,37 +334,28 @@ public partial class RepositoryViewModel : RoutableViewModelBase
[ReactiveCommand]
private async ValueTask DetectLocalChangesAsync()
{
var ns = await Task.Run(() =>
var ns = await Task.Run(async () =>
{
LocalDatabase.LocalAccessor.Refresh();
// Generate a map of all folders
var folderMap = new Dictionary<string, LocalChangesNode>();
foreach (var k in LocalDatabase.LocalAccessor.Changes.Keys)
AddParentToMap(k);
var nodes = new List<LocalChangesNode>();
foreach (var file in LocalDatabase.LocalAccessor.Changes.Values)
{
var directory = Path.GetDirectoryName(file.File.WorkPath);
var n = LocalChangesNode.FromWorkspaceFile(file);
if (string.IsNullOrEmpty(directory)) nodes.Add(n);
else folderMap[directory].Contents!.Add(n);
}
nodes.AddRange(folderMap.Values);
return nodes;
void AddParentToMap(string path)
{
var parent = Path.GetDirectoryName(path);
if (string.IsNullOrEmpty(parent) || folderMap.ContainsKey(parent)) return;
folderMap.Add(parent, LocalChangesNode.FromFolder(parent));
}
return await CalculateFileTreeOfChangesNodeAsync(LocalDatabase.LocalAccessor.Changes.Values);
});
LocalChangeSetRaw.Clear();
LocalChangeSetRaw.AddRange(ns);
}
[ReactiveCommand]
private void SelectAllChanges()
{
foreach (var n in LocalChangeSetRaw)
n.Included = true;
}
[ReactiveCommand]
private void DeselectAllChanges()
{
foreach (var n in LocalChangeSetRaw)
n.Included = false;
}
}

View File

@ -14,8 +14,6 @@ public partial class ServerSetupPageViewModel : RoutableViewModelBase
[Reactive] private string _host = "http://localhost:5256/";
[Reactive(SetModifier = AccessModifier.Protected)] private string? _issue;
public IObservable<bool> CanSetHost { get; }
public ServerSetupPageViewModel(IScreen hostScreen) : base(hostScreen)
@ -33,19 +31,18 @@ public partial class ServerSetupPageViewModel : RoutableViewModelBase
{
try
{
Issue = string.Empty;
await Api.C.SetGatewayAsync(Host);
HostScreen.Router.Navigate.Execute(new LoginPageViewModel(HostScreen));
}
catch (ApiException ex)
{
await Console.Error.WriteLineAsync("Can not connect to server: " + ex.ToString());
Issue = ex.Content;
UIHelper.NotifyError(ex);
}
catch (Exception ex)
{
await Console.Error.WriteLineAsync("Can not connect to server: " + ex.ToString());
Issue = ex.Message;
UIHelper.NotifyError(ex);
}
}

View File

@ -20,6 +20,5 @@
<Button Content="Login" Command="{Binding LoginCommand}"/>
<Button Content="Register" Command="{Binding RegisterCommand}"/>
</StackPanel>
<Label Content="{Binding Issue}" Foreground="Red"/>
</StackPanel>
</UserControl>

View File

@ -20,6 +20,5 @@
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="{DynamicResource Semi}">
<Button Content="Register" Command="{Binding RegisterCommand}"/>
</StackPanel>
<Label Content="{Binding Issue}" Foreground="Red"/>
</StackPanel>
</UserControl>

View File

@ -16,6 +16,5 @@
<StackPanel Spacing="10" HorizontalAlignment="Stretch" VerticalAlignment="Center">
<TextBox Watermark="Host" Text="{Binding Host, Mode=TwoWay}"/>
<Button Content="Connect" Command="{Binding SetHostCommand}"/>
<Label Content="{Binding Issue}" Foreground="Red"/>
</StackPanel>
</UserControl>

View File

@ -21,5 +21,6 @@
<Panel>
<rxui:RoutedViewHost Router="{Binding Router}"/>
<ursa:OverlayDialogHost HostId="Overlay"/>
<ursa:WindowNotificationManager/>
</Panel>
</ursa:UrsaWindow>

View File

@ -10,15 +10,9 @@
<Grid ColumnDefinitions="2*, *">
<Border Grid.Column="0" Classes="Shadow" Theme="{StaticResource CardBorder}">
<Grid RowDefinitions="Auto, *">
<StackPanel Orientation="Horizontal" Spacing="8"
IsVisible="{Binding IsOwnerRole}">
<u:IconButton Icon="{StaticResource SemiIconEdit}" Content="Edit"/>
</StackPanel>
<ScrollViewer Grid.Row="1">
<SelectableTextBlock Text="{Binding Repository.Description}"/>
</ScrollViewer>
</Grid>
</Border>
<ScrollViewer Grid.Column="1" Margin="36 20">
<StackPanel Orientation="Vertical">

View File

@ -7,7 +7,7 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Flawless.Client.Views.RepositoryPage.RepoFileTreePageView">
<Grid ColumnDefinitions="2*, *">
<TreeDataGrid Grid.Column="0">
<TreeDataGrid Grid.Column="0" Grid.ColumnSpan="2" Source="{Binding FileTree}">
</TreeDataGrid>
<!-- <Border Grid.Column="1" Classes="Shadow" Theme="{StaticResource CardBorder}"> -->
<!-- <ScrollViewer> -->

View File

@ -6,7 +6,26 @@
x:DataType="vm:RepositoryViewModel"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Flawless.Client.Views.RepositoryPage.RepoSettingPageView">
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<Label Content="Sit down and wait patience."></Label>
<TabControl TabStripPlacement="Left">
<TabItem Header="Members">
<StackPanel Width="400" HorizontalAlignment="Stretch">
</StackPanel>
</TabItem>
<TabItem Header="Statics" IsVisible="{Binding IsDeveloperRole}">
<StackPanel Width="400" HorizontalAlignment="Stretch">
</StackPanel>
</TabItem>
<TabItem Header="Admin Area" IsVisible="{Binding IsOwnerRole}">
<StackPanel Width="400" HorizontalAlignment="Stretch">
</StackPanel>
</TabItem>
<TabItem Header="Hooks" IsVisible="{Binding IsOwnerRole}">
<StackPanel Width="400" HorizontalAlignment="Stretch">
</StackPanel>
</TabItem>
</TabControl>
</UserControl>

View File

@ -12,6 +12,8 @@
<u:IconButton Icon="{StaticResource SemiIconRefresh}" Content="Detect"
Command="{Binding DetectLocalChangesAsyncCommand}"/>
<u:IconButton Icon="{StaticResource SemiIconDownload}" Content="Pull"/>
<u:IconButton Icon="{StaticResource SemiIconCheckList}" Content="Select"/>
<u:IconButton Icon="{StaticResource SemiIconList}" Content="Deselect"/>
<!-- <ToggleButton Content="Auto Refresh" IsChecked="{Binding AutoDetectChanges}"/> -->
</StackPanel>
<TreeDataGrid Grid.Row="1" Grid.Column="0" Source="{Binding LocalChange}" CanUserSortColumns="True"/>

View File

@ -382,6 +382,7 @@ public class RepositoryInnieController(
}
[HttpPost("create_commit")]
[RequestSizeLimit(long.MaxValue)]
[ProducesResponseType<CommitSuccessResponse>(200)]
public async Task<IActionResult> CommitAsync(string userName, string repositoryName, [FromForm] FormCommitRequest req)
{

View File

@ -8,6 +8,7 @@ using Flawless.Server.Services;
using Flawless.Server.Utility;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
@ -38,6 +39,14 @@ public static class Program
builder.WebHost.ConfigureKestrel(opt =>
{
opt.Limits.MaxRequestBodySize = long.MaxValue; // As big as possible...
opt.Limits.MaxRequestHeaderCount = int.MaxValue; // As big as possible...
});
builder.Services.Configure<FormOptions>(opt =>
{
opt.MultipartBodyLengthLimit = long.MaxValue;
opt.ValueLengthLimit = int.MaxValue;
opt.MultipartHeadersLengthLimit = int.MaxValue;
});
// Api related