move projects to src folder

This commit is contained in:
Matt Parker
2025-05-18 14:04:50 +10:00
parent 4310f63f86
commit 4bd972f905
49 changed files with 7 additions and 5 deletions

View File

@@ -0,0 +1,8 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="DotNetSolutionTools.App.App"
RequestedThemeVariant="Default">
<Application.Styles>
<FluentTheme />
</Application.Styles>
</Application>

View File

@@ -0,0 +1,25 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using DotNetSolutionTools.App.ViewModels;
using DotNetSolutionTools.App.Views;
namespace DotNetSolutionTools.App;
public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow() { DataContext = new MainWindowViewModel() };
}
base.OnFrameworkInitializationCompleted();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

View File

@@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**"/>
</ItemGroup>
<ItemGroup>
<ProjectCapability Include="Avalonia"/>
<TrimmerRootAssembly Include="Avalonia.Themes.Fluent"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.3.0" />
<PackageReference Include="Avalonia.Desktop" Version="11.3.0" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.0" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.0" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.3.0" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DotNetSolutionTools.Core\DotNetSolutionTools.Core.csproj"/>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,8 @@
namespace DotNetSolutionTools.App.Models;
public class LocalStateDto
{
public string SolutionFolderPath { get; set; } = string.Empty;
public string SolutionFilePath { get; set; } = string.Empty;
public string CsprojFilePath { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,16 @@
using Avalonia;
namespace DotNetSolutionTools.App;
class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp() =>
AppBuilder.Configure<App>().UsePlatformDetect().WithInterFont().LogToTrace();
}

View File

@@ -0,0 +1,56 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform.Storage;
namespace DotNetSolutionTools.App.Services;
public class FileService
{
private readonly FilePickerFileType _csprojFileType = new("C# Project File") { Patterns = ["*.csproj"] };
private readonly FilePickerFileType _slnFileType = new("C# Solution File") { Patterns = ["*.sln", "*.slnx"] };
public async Task<IStorageFile?> DoOpenFilePickerCsprojAsync()
{
return await DoOpenFilePickerAsync(_csprojFileType);
}
public async Task<IStorageFile?> DoOpenFilePickerSlnAsync()
{
return await DoOpenFilePickerAsync(_slnFileType);
}
public async Task<IStorageFile?> DoOpenFilePickerAsync(FilePickerFileType fileType)
{
if (
Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop
|| desktop.MainWindow?.StorageProvider is not { } provider
)
throw new NullReferenceException("Missing StorageProvider instance.");
var files = await provider.OpenFilePickerAsync(
new FilePickerOpenOptions()
{
Title = "Open File",
AllowMultiple = false,
FileTypeFilter = [fileType]
}
);
return files?.Count >= 1 ? files[0] : null;
}
public async Task<IStorageFolder?> DoOpenFolderPickerAsync()
{
if (
Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop
|| desktop.MainWindow?.StorageProvider is not { } provider
)
throw new NullReferenceException("Missing StorageProvider instance.");
var folder = await provider.OpenFolderPickerAsync(
new FolderPickerOpenOptions() { Title = "Select Folder", AllowMultiple = false }
);
return folder?.Count >= 1 ? folder[0] : null;
}
}

View File

@@ -0,0 +1,416 @@
using System.Collections.ObjectModel;
using System.Text.Json;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using DotNetSolutionTools.App.Models;
using DotNetSolutionTools.App.Services;
using DotNetSolutionTools.Core;
using DotNetSolutionTools.Core.Common;
using NuGet.Packaging;
namespace DotNetSolutionTools.App.ViewModels;
public partial class MainWindowViewModel : ObservableObject
{
private readonly FileService _fileService = new();
[ObservableProperty]
private string _solutionFolderPath = string.Empty;
[ObservableProperty]
private string _solutionFilePath = string.Empty;
[ObservableProperty]
private string _csprojFilePath = string.Empty;
[ObservableProperty]
private ObservableCollection<string> _operationResults = new() { };
[ObservableProperty]
private string _resultsLabel = "Ready";
[ObservableProperty]
private IImmutableSolidColorBrush _resultsLabelColor = new ImmutableSolidColorBrush(Colors.Black);
public MainWindowViewModel()
{
LoadSavedState().ConfigureAwait(false);
}
private void SetBeginCommandState()
{
OperationResults.Clear();
ResultsLabel = "Running...";
ResultsLabelColor = new ImmutableSolidColorBrush(Colors.Black);
}
private void SetCommandSuccessState(string message, IEnumerable<string>? results = null)
{
ResultsLabel = message;
ResultsLabelColor = new ImmutableSolidColorBrush(Colors.Green);
if (results is not null)
OperationResults.AddRange(results);
}
private void SetCommandFailureState(string message, Exception e)
{
ResultsLabel = message;
ResultsLabelColor = new ImmutableSolidColorBrush(Colors.DarkRed);
OperationResults?.Add(e.Message);
OperationResults?.Add(e.ToString());
}
[RelayCommand]
private async Task ExecuteParityChecker(CancellationToken token)
{
SetBeginCommandState();
try
{
await Task.Run(
() =>
{
var results = SolutionProjectParity.CompareSolutionAndCSharpProjects(
SolutionFolderPath,
SolutionFilePath
);
SetCommandSuccessState($"{results.Count} Projects in folder missing from solution", results);
},
token
);
}
catch (Exception e)
{
SetCommandFailureState("Failed to compare solution and csharp projects", e);
}
}
[RelayCommand]
private async Task FormatCsProjFile(CancellationToken token)
{
SetBeginCommandState();
try
{
await Task.Run(
() =>
{
FormatCsproj.FormatCsprojFile(CsprojFilePath);
},
token
);
SetCommandSuccessState("Successfully formatted csproj file");
}
catch (Exception e)
{
SetCommandFailureState("Failed to format csproj file", e);
}
}
[RelayCommand]
private async Task FormatAllCsprojFilesInSolutionFile(CancellationToken token)
{
SetBeginCommandState();
try
{
await Task.Run(
() =>
{
var csprojList = CsprojHelper.RetrieveAllCSharpProjectFullPathsFromSolution(SolutionFilePath);
foreach (var csproj in csprojList)
{
FormatCsproj.FormatCsprojFile(csproj);
}
},
token
);
SetCommandSuccessState("Successfully formatted all csproj files in solution file");
}
catch (Exception e)
{
SetCommandFailureState("Failed to format all csproj files in solution file", e);
}
}
[RelayCommand]
private async Task FormatAllCsprojFilesInSolutionFolder(CancellationToken token)
{
SetBeginCommandState();
try
{
await Task.Run(
() =>
{
var csprojList = CsprojHelper.RetrieveAllCSharpProjectFullPathsFromFolder(SolutionFolderPath);
foreach (var csproj in csprojList)
{
FormatCsproj.FormatCsprojFile(csproj);
}
},
token
);
SetCommandSuccessState("Successfully formatted all csproj files in folder");
}
catch (Exception e)
{
SetCommandFailureState("Failed to format all csproj files in folder", e);
}
}
[RelayCommand]
private async Task ResolveInconsistentNugetPackageVersionsInSolutionFile(CancellationToken token)
{
SetBeginCommandState();
try
{
await Task.Run(
() =>
{
PackageVersionConsistency.ResolvePackageVersionsToLatestInstalled(SolutionFilePath);
SetCommandSuccessState("Resolved all packages with inconsistent versions");
},
token
);
}
catch (Exception e)
{
SetCommandFailureState("Failed to resolve inconsistent package versions in solution file", e);
}
}
[RelayCommand]
private async Task CheckForInconsistentNugetPackageVersionsInSolutionFile(CancellationToken token)
{
SetBeginCommandState();
try
{
await Task.Run(
() =>
{
var results = PackageVersionConsistency.FindInconsistentNugetPackageVersions(SolutionFilePath);
SetCommandSuccessState($"{results.Count} packages with inconsistent versions", results);
},
token
);
}
catch (Exception e)
{
SetCommandFailureState("Failed to check for inconsistent package versions in solution file", e);
}
}
[RelayCommand]
private async Task CheckForMissingImplicitUsingsInSolutionFile(CancellationToken token)
{
SetBeginCommandState();
try
{
await Task.Run(
() =>
{
var results = ImplicitUsings.FindCSharpProjectsMissingImplicitUsings(SolutionFilePath);
SetCommandSuccessState($"{results.Count} Projects missing ImplicitUsings", results);
},
token
);
}
catch (Exception e)
{
SetCommandFailureState("Failed to check for missing implicit usings in solution file", e);
}
}
[RelayCommand]
private async Task CheckForMissingTreatWarningsAsErrorsInSolutionFile(CancellationToken token)
{
SetBeginCommandState();
try
{
await Task.Run(
() =>
{
var results = WarningsAsErrors.FindCSharpProjectsMissingTreatWarningsAsErrors(SolutionFilePath);
SetCommandSuccessState($"{results.Count} Projects missing TreatWarningsAsErrors", results);
},
token
);
}
catch (Exception e)
{
SetCommandFailureState("Failed to check for missing treat warnings as errors in solution file", e);
}
}
[RelayCommand]
private async Task DeleteBinAndObjFoldersInFolder(CancellationToken token)
{
SetBeginCommandState();
try
{
await Task.Run(
() =>
{
CleanFolder.DeleteBinObjAndNodeModulesFoldersInFolder(SolutionFolderPath);
},
token
);
SetCommandSuccessState("Successfully deleted bin and obj folders");
}
catch (Exception e)
{
SetCommandFailureState("Failed to delete bin and obj folders", e);
}
}
[RelayCommand]
private async Task UpdateAllProjectsToNet80(CancellationToken token)
{
SetBeginCommandState();
try
{
await Task.Run(
async () =>
{
await DotNetUpgrade.UpdateProjectsInSolutionToNet80(SolutionFilePath);
},
token
);
SetCommandSuccessState("Successfully updated all projects in solution to .NET 8");
}
catch (Exception e)
{
SetCommandFailureState("Failed to update all projects in solution to .NET 8", e);
}
}
[RelayCommand]
private async Task UpdateProjectToNet80(CancellationToken token)
{
SetBeginCommandState();
try
{
await Task.Run(
async () =>
{
await DotNetUpgrade.UpdateProjectAtPathToNet80(CsprojFilePath);
},
token
);
SetCommandSuccessState("Successfully updated project to .NET 8");
}
catch (Exception e)
{
SetCommandFailureState("Failed to update project to .NET 8", e);
}
}
[RelayCommand]
private async Task LoadSolutionFile(CancellationToken token)
{
try
{
var file = await _fileService.DoOpenFilePickerSlnAsync();
if (file is null)
return;
SolutionFilePath = file.Path.AbsolutePath;
await SaveLoadedState();
}
catch (Exception e)
{
ResultsLabel = "Error";
OperationResults?.Add(e.Message);
}
}
[RelayCommand]
private async Task ClearSolutionFile(CancellationToken token)
{
SolutionFilePath = string.Empty;
await SaveLoadedState();
}
[RelayCommand]
private async Task LoadSolutionFolder(CancellationToken token)
{
try
{
var folder = await _fileService.DoOpenFolderPickerAsync();
if (folder is null)
return;
SolutionFolderPath = folder.Path.AbsolutePath;
await SaveLoadedState();
}
catch (Exception e)
{
ResultsLabel = "Error";
OperationResults?.Add(e.Message);
}
}
[RelayCommand]
private async Task ClearSolutionFolder(CancellationToken token)
{
SolutionFolderPath = string.Empty;
await SaveLoadedState();
}
[RelayCommand]
private async Task LoadCsprojFile(CancellationToken token)
{
try
{
var folder = await _fileService.DoOpenFilePickerCsprojAsync();
if (folder is null)
return;
CsprojFilePath = folder.Path.AbsolutePath;
await SaveLoadedState();
}
catch (Exception e)
{
ResultsLabel = "Error";
OperationResults?.Add(e.Message);
}
}
[RelayCommand]
private async Task ClearCsprojFile(CancellationToken token)
{
CsprojFilePath = string.Empty;
await SaveLoadedState();
}
private async Task SaveLoadedState()
{
var dto = new LocalStateDto
{
SolutionFolderPath = SolutionFolderPath,
SolutionFilePath = SolutionFilePath,
CsprojFilePath = CsprojFilePath
};
var json = JsonSerializer.Serialize(dto);
await File.WriteAllTextAsync("./localState.json", json);
}
private async Task LoadSavedState()
{
try
{
var json = await File.ReadAllTextAsync("./localState.json");
if (string.IsNullOrEmpty(json))
return;
var dto = JsonSerializer.Deserialize<LocalStateDto>(json);
if (dto is null)
return;
SolutionFolderPath = dto.SolutionFolderPath;
SolutionFilePath = dto.SolutionFilePath;
CsprojFilePath = dto.CsprojFilePath;
}
catch
{
// ignored
}
}
}

View File

@@ -0,0 +1,146 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:DotNetSolutionTools.App.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="650"
Width="800" Height="650"
x:Class="DotNetSolutionTools.App.Views.MainWindow"
x:DataType="viewModels:MainWindowViewModel"
Icon="/Assets/avalonia-logo.ico"
Title="DotNetSolutionTools">
<Design.DataContext>
<!-- This only sets the DataContext for the previewer in an IDE,
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
<viewModels:MainWindowViewModel />
</Design.DataContext>
<DockPanel Width="{Binding $parent.Bounds.Width}" Height="{Binding $parent.Bounds.Height}">
<StackPanel Margin="5 10 5 5" Orientation="Vertical" DockPanel.Dock="Top">
<Grid Margin="5 0 5 0" ColumnDefinitions="Auto,*,Auto">
<Button Grid.Column="0" HorizontalContentAlignment="Center" Width="200" Command="{Binding LoadSolutionFolderCommand}">Select Solution Folder</Button>
<TextBlock Grid.Column="1" VerticalAlignment="Center" TextAlignment="Center" Name="SolutionFolderPath2" Text="{Binding SolutionFolderPath}" />
<Button Grid.Column="2" HorizontalContentAlignment="Center" Width="60" Command="{Binding ClearSolutionFolderCommand}">Clear</Button>
</Grid>
<Grid Margin="5 0 5 0" ColumnDefinitions="Auto,*,Auto">
<Button Grid.Column="0" Width="200" HorizontalContentAlignment="Center" Command="{Binding LoadSolutionFileCommand}">Select Solution File</Button>
<TextBlock Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center" Name="SolutionFilePath" Text="{Binding SolutionFilePath}" />
<Button Grid.Column="2" Width="60" HorizontalContentAlignment="Center" Command="{Binding ClearSolutionFileCommand}">Clear</Button>
</Grid>
<Grid Margin="5 0 5 0" ColumnDefinitions="Auto,*,Auto">
<Button Grid.Column="0" Width="200" HorizontalContentAlignment="Center" Command="{Binding LoadCsprojFileCommand}">Select CSharp Project File</Button>
<TextBlock Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center" Name="CsprojFilePath" Text="{Binding CsprojFilePath}" />
<Button Grid.Column="2" Width="60" HorizontalContentAlignment="Center" Command="{Binding ClearCsprojFileCommand}">Clear</Button>
</Grid>
<Grid Margin="0 5 0 0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" ShowGridLines="False" RowDefinitions="*,*,*,*" ColumnDefinitions="*,*,*">
<Button Grid.Row="0" Grid.Column="0" MinHeight="100" Padding="10" Margin="5"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
IsEnabled="{Binding SolutionFolderPath, Mode=OneWay, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Command="{Binding ExecuteParityCheckerCommand}">
<TextBlock TextWrapping="Wrap">
Check Solution Parity
</TextBlock>
</Button>
<Button Grid.Row="0" Grid.Column="1" MinHeight="100" Padding="10" Margin="5"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
IsEnabled="{Binding CsprojFilePath, Mode=OneWay, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Command="{Binding FormatCsProjFileCommand}">
<TextBlock TextWrapping="Wrap">
Format CSharp Project File
</TextBlock>
</Button>
<Button Grid.Row="0" Grid.Column="2" MinHeight="100" Padding="10" Margin="5"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
IsEnabled="{Binding SolutionFilePath, Mode=OneWay, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Command="{Binding FormatAllCsprojFilesInSolutionFileCommand}">
<TextBlock TextWrapping="Wrap">
Format All CSharp Project Files in Solution
</TextBlock>
</Button>
<Button Grid.Row="1" Grid.Column="0" MinHeight="100" Padding="10" Margin="5"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
IsEnabled="{Binding SolutionFolderPath, Mode=OneWay, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Command="{Binding FormatAllCsprojFilesInSolutionFolderCommand}">
<TextBlock TextWrapping="Wrap">
Format All CSharp Project Files in Folder
</TextBlock>
</Button>
<Button Grid.Row="1" Grid.Column="1" MinHeight="100" Padding="10" Margin="5"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
IsEnabled="{Binding SolutionFilePath, Mode=OneWay, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Command="{Binding CheckForMissingImplicitUsingsInSolutionFileCommand}">
<TextBlock TextWrapping="Wrap">
Check For Missing Implicit Usings
</TextBlock>
</Button>
<Button Grid.Row="1" Grid.Column="2" MinHeight="100" Padding="10" Margin="5"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
IsEnabled="{Binding SolutionFilePath, Mode=OneWay, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Command="{Binding CheckForMissingTreatWarningsAsErrorsInSolutionFileCommand}">
<TextBlock TextWrapping="Wrap">
Check For Missing Treat Warnings as Errors
</TextBlock>
</Button>
<Button Grid.Row="2" Grid.Column="0" MinHeight="100" Padding="10" Margin="5"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
IsEnabled="{Binding SolutionFolderPath, Mode=OneWay, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Command="{Binding DeleteBinAndObjFoldersInFolderCommand}">
<StackPanel>
<TextBlock TextWrapping="Wrap">
Clear bin and obj folders
</TextBlock>
<TextBlock TextWrapping="Wrap">
(and node_modules)
</TextBlock>
</StackPanel>
</Button>
<Button Grid.Row="2" Grid.Column="1" MinHeight="100" Padding="10" Margin="5"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
IsEnabled="{Binding SolutionFilePath, Mode=OneWay, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Command="{Binding UpdateAllProjectsToNet80Command}">
<TextBlock TextWrapping="Wrap">
Update all projects in Solution to .NET 9.0
</TextBlock>
</Button>
<Button Grid.Row="2" Grid.Column="2" MinHeight="100" Padding="10" Margin="5"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
IsEnabled="{Binding CsprojFilePath, Mode=OneWay, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Command="{Binding UpdateProjectToNet80Command}">
<TextBlock TextWrapping="Wrap">
Update C# Project to .NET 9.0
</TextBlock>
</Button>
<Button Grid.Row="3" Grid.Column="0" MinHeight="100" Padding="10" Margin="5"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
IsEnabled="{Binding SolutionFilePath, Mode=OneWay, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Command="{Binding CheckForInconsistentNugetPackageVersionsInSolutionFileCommand}">
<TextBlock TextWrapping="Wrap">
Check for Inconsistent Nuget Package Versions
</TextBlock>
</Button>
<Button Grid.Row="3" Grid.Column="1" MinHeight="100" Padding="10" Margin="5"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
IsEnabled="{Binding SolutionFilePath, Mode=OneWay, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Command="{Binding ResolveInconsistentNugetPackageVersionsInSolutionFileCommand}">
<TextBlock TextWrapping="Wrap">
Resolve Inconsistent Nuget Package Versions
</TextBlock>
</Button>
</Grid>
<Label Width="{Binding $parent.Bounds.Width}" Background="{Binding ResultsLabelColor}" HorizontalAlignment="Center" HorizontalContentAlignment="Center" Name="ResultsLabel" Content="{Binding ResultsLabel}"></Label>
</StackPanel>
<ScrollViewer Width="{Binding $parent.Bounds.Width}">
<ListBox ScrollViewer.VerticalScrollBarVisibility="Visible" Name="Results" ItemsSource="{Binding OperationResults}" />
</ScrollViewer>
</DockPanel>
</Window>

View File

@@ -0,0 +1,17 @@
using Avalonia.Controls;
using Microsoft.Build.Locator;
namespace DotNetSolutionTools.App.Views;
public partial class MainWindow : Window
{
public MainWindow()
{
var instance = MSBuildLocator
.QueryVisualStudioInstances()
.OrderByDescending(instance => instance.Version)
.First();
MSBuildLocator.RegisterInstance(instance);
InitializeComponent();
}
}

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embedded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="SolutionParityChecker.App.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>

View File

@@ -0,0 +1,29 @@
using DotNetSolutionTools.Core;
using Spectre.Console.Cli;
namespace DotNetSolutionTools.CLI.Commands;
public class ClearBinObjCommand : Command<ClearBinObjCommand.Settings>
{
public sealed class Settings : CommandSettings
{
[CommandArgument(1, "<FolderPath>")]
public required string FolderPath { get; set; }
}
public override int Execute(CommandContext context, Settings settings)
{
// validate a real folder path was passed in
if (!Directory.Exists(settings.FolderPath))
{
Console.WriteLine("Invalid folder path");
return 1;
}
Console.WriteLine("Deleting bin, obj, and node_modules folders");
CleanFolder.DeleteBinObjAndNodeModulesFoldersInFolder(settings.FolderPath);
Console.WriteLine("==================================================");
Console.WriteLine("Done!");
return 0;
}
}

View File

@@ -0,0 +1,69 @@
using System.ComponentModel;
using DotNetSolutionTools.Core;
using DotNetSolutionTools.Core.Common;
using Spectre.Console.Cli;
namespace DotNetSolutionTools.CLI.Commands;
public class CompareCommand : Command<CompareCommand.Settings>
{
public sealed class Settings : CommandSettings
{
[CommandArgument(0, "<SolutionFolderPath>")]
public required string SolutionFolderPath { get; set; }
[CommandArgument(1, "<SolutionFilePath>")]
public required string SolutionFilePath { get; set; }
[CommandOption("-l|--logprojectfiles")]
[Description("true to enable log output of all project files found in folder. Default is false.")]
[DefaultValue(false)]
public bool LogAllProjectFileNames { get; set; } = false;
}
public override int Execute(CommandContext context, Settings settings)
{
var folderDirectory = settings.SolutionFolderPath; // Include the trailing slash
var pathToSolutionFile = settings.SolutionFilePath;
Console.WriteLine($"Retrieving C# Projects from {folderDirectory}");
var csprojList = CsprojHelper.RetrieveAllCSharpProjectFullPathsFromFolder(folderDirectory);
if (settings.LogAllProjectFileNames)
{
foreach (var project in csprojList)
{
Console.WriteLine(project);
}
}
Console.WriteLine($"Retrieved {csprojList.Length} C# Projects");
Console.WriteLine("==================================================");
Console.WriteLine($"Parsing Solution File: {pathToSolutionFile}");
// Load the solution file
var solutionFile = SlnHelper.ParseSolutionFileFromPath(pathToSolutionFile);
if (solutionFile == null)
{
Console.WriteLine("Failed to parse solution file. The file was either not found or malformed.");
return 1;
}
// Get the list of projects
var projectsMissingFromSolution = SolutionProjectParity.FindProjectsMissingFromSolution(
csprojList,
solutionFile
);
Console.WriteLine("==================================================");
Console.WriteLine($"Missing {projectsMissingFromSolution.Count} C# Projects from Solution File");
foreach (var project in projectsMissingFromSolution)
{
Console.WriteLine(project);
}
Console.WriteLine("==================================================");
Console.WriteLine("Done!");
return 0;
}
}

View File

@@ -0,0 +1,72 @@
using DotNetSolutionTools.Core;
using DotNetSolutionTools.Core.Common;
using Spectre.Console.Cli;
namespace DotNetSolutionTools.CLI.Commands;
public class FormatCsprojCommand : Command<FormatCsprojCommand.Settings>
{
public sealed class Settings : CommandSettings
{
[CommandOption("--project <CsprojFilePath>")]
public string? CsprojFilePath { get; set; }
[CommandOption("--folder <SolutionFolderPath>")]
public string? SolutionFolderPath { get; set; }
[CommandOption("--sln <SolutionFilePath>")]
public string? SolutionFilePath { get; set; }
}
public override int Execute(CommandContext context, Settings settings)
{
if (!string.IsNullOrWhiteSpace(settings.CsprojFilePath))
{
var pathToCsprojFile = settings.CsprojFilePath;
Console.WriteLine($"Retrieving C# Project from {pathToCsprojFile}");
FormatCsproj.FormatCsprojFile(pathToCsprojFile);
Console.WriteLine("Done!");
return 0;
}
else if (!string.IsNullOrWhiteSpace(settings.SolutionFilePath))
{
var test = SlnHelper.ParseSolutionFileFromPath(settings.SolutionFilePath);
if (test == null)
{
Console.WriteLine("Failed to parse solution file. The file was either not found or malformed.");
return 1;
}
var cSharpProjects = SlnHelper.GetCSharpProjectObjectsFromSolutionFile(test);
Console.WriteLine($"Found {cSharpProjects.Count} C# Projects");
Console.WriteLine("==================================================");
foreach (var project in cSharpProjects)
{
FormatCsproj.FormatCsprojFile(project.FullPath);
}
Console.WriteLine("Done!");
return 0;
}
else if (!string.IsNullOrWhiteSpace(settings.SolutionFolderPath))
{
var folderDirectory = settings.SolutionFolderPath; // Include the trailing slash
Console.WriteLine($"Retrieving C# Projects from {folderDirectory}");
var csprojList = CsprojHelper.RetrieveAllCSharpProjectFullPathsFromFolder(folderDirectory);
Console.WriteLine($"Retrieved {csprojList.Length} C# Projects");
Console.WriteLine("==================================================");
foreach (var project in csprojList)
{
FormatCsproj.FormatCsprojFile(project);
}
Console.WriteLine("Done!");
return 0;
}
else
{
Console.WriteLine("No arguments were provided. Please provide a csproj, folder, or solution file.");
return 1;
}
}
}

View File

@@ -0,0 +1,85 @@
using System.ComponentModel;
using DotNetSolutionTools.Core;
using DotNetSolutionTools.Core.Common;
using Spectre.Console.Cli;
namespace DotNetSolutionTools.CLI.Commands;
public class ImplicitUsingsCommand : Command<ImplicitUsingsCommand.Settings>
{
public sealed class Settings : CommandSettings
{
[CommandArgument(1, "<SolutionFilePath>")]
public required string SolutionFilePath { get; set; }
[CommandOption("-m|--add-missing")]
[Description("Add Implicit Usings=true to projects missing them. Default is false.")]
[DefaultValue(false)]
public bool AddMissing { get; set; } = false;
[CommandOption("-d|--enable-disabled")]
[Description("Sets Implicit Usings to true for any projects with it disabled. Default is false.")]
[DefaultValue(false)]
public bool EnableDisabled { get; set; } = false;
[CommandOption("-a|--enable-all")]
[Description("Enables Implicit Usings for all projects. Default is false.")]
[DefaultValue(false)]
public bool EnableAll { get; set; } = false;
}
public override int Execute(CommandContext context, Settings settings)
{
var pathToSolutionFile = settings.SolutionFilePath;
Console.WriteLine($"Retrieving Solution from {pathToSolutionFile}");
var solutionFile = SlnHelper.ParseSolutionFileFromPath(pathToSolutionFile);
if (solutionFile == null)
{
Console.WriteLine("Failed to parse solution file. The file was either not found or malformed.");
return 1;
}
var cSharpProjects = SlnHelper.GetCSharpProjectObjectsFromSolutionFile(solutionFile);
Console.WriteLine($"Found {cSharpProjects.Count} C# Projects");
Console.WriteLine("==================================================");
// Get the list of projects
var projectsMissingImplicitUsings = ImplicitUsings.FindCSharpProjectsMissingImplicitUsings(cSharpProjects);
Console.WriteLine(
$"{projectsMissingImplicitUsings.Count} C# Projects have missing or disabled implicit usings"
);
foreach (var project in projectsMissingImplicitUsings)
{
Console.WriteLine(project.DirectoryPath);
}
if (settings.AddMissing)
{
Console.WriteLine("==================================================");
Console.WriteLine("Adding missing implicit usings");
ImplicitUsings.AddMissingImplicitUsings(projectsMissingImplicitUsings);
var updatedProjects = SlnHelper.GetCSharpProjectObjectsFromSolutionFile(solutionFile);
var projectsWithMissing = ImplicitUsings.FindCSharpProjectsMissingImplicitUsings(updatedProjects);
Console.WriteLine(
$"There are now {projectsWithMissing.Count} C# Projects missing/disabled implicit usings"
);
}
if (settings.EnableDisabled)
{
Console.WriteLine("==================================================");
Console.WriteLine("Enabling disabled implicit usings");
ImplicitUsings.EnableDisabledImplicitUsings(projectsMissingImplicitUsings);
}
if (settings.EnableAll)
{
Console.WriteLine("==================================================");
Console.WriteLine("Enabling all implicit usings");
ImplicitUsings.EnableAllImplicitUsings(projectsMissingImplicitUsings);
}
Console.WriteLine("==================================================");
Console.WriteLine("Done!");
return 0;
}
}

View File

@@ -0,0 +1,59 @@
using System.ComponentModel;
using DotNetSolutionTools.Core;
using DotNetSolutionTools.Core.Common;
using Spectre.Console.Cli;
namespace DotNetSolutionTools.CLI.Commands;
public class TreatWarningsAsErrorsCommand : Command<TreatWarningsAsErrorsCommand.Settings>
{
public sealed class Settings : CommandSettings
{
[CommandArgument(1, "<SolutionFilePath>")]
public required string SolutionFilePath { get; set; }
[CommandOption("-m|--add-missing")]
[DefaultValue(false)]
public bool AddMissing { get; set; } = false;
}
public override int Execute(CommandContext context, Settings settings)
{
var pathToSolutionFile = settings.SolutionFilePath;
Console.WriteLine($"Retrieving Solution from {pathToSolutionFile}");
var solutionFile = SlnHelper.ParseSolutionFileFromPath(pathToSolutionFile);
if (solutionFile == null)
{
Console.WriteLine("Failed to parse solution file. The file was either not found or malformed.");
return 1;
}
var cSharpProjects = SlnHelper.GetCSharpProjectObjectsFromSolutionFile(solutionFile);
Console.WriteLine($"Found {cSharpProjects.Count} C# Projects");
Console.WriteLine("==================================================");
// Get the list of projects
var projectsMissingTreatWarningsAsErrors = WarningsAsErrors.FindCSharpProjectsMissingTreatWarningsAsErrors(
cSharpProjects
);
Console.WriteLine(
$"{projectsMissingTreatWarningsAsErrors.Count} C# Projects have missing Treat Warnings As Errors"
);
if (settings.AddMissing)
{
Console.WriteLine("==================================================");
Console.WriteLine("Adding missing Warnings As Errors");
WarningsAsErrors.AddMissingTreatWarningsAsErrors(projectsMissingTreatWarningsAsErrors);
var updatedProjects = SlnHelper.GetCSharpProjectObjectsFromSolutionFile(solutionFile);
var projectsWithMissing = WarningsAsErrors.FindCSharpProjectsMissingTreatWarningsAsErrors(updatedProjects);
Console.WriteLine(
$"There are now {projectsWithMissing.Count} C# Projects missing Treat Warnings As Errors"
);
}
Console.WriteLine("==================================================");
Console.WriteLine("Done!");
return 0;
}
}

View File

@@ -0,0 +1,28 @@
using DotNetSolutionTools.Core;
using Spectre.Console.Cli;
namespace DotNetSolutionTools.CLI.Commands;
public class UpdateProjectToNet80Command : AsyncCommand<UpdateProjectToNet80Command.Settings>
{
public sealed class Settings : CommandSettings
{
[CommandArgument(1, "<CsprojFilePath>")]
public required string CsprojFilePath { get; set; }
}
public override async Task<int> ExecuteAsync(CommandContext context, Settings settings)
{
if (!Directory.Exists(settings.CsprojFilePath))
{
Console.WriteLine("Invalid file path. Please pass in a valid file path to a .csproj file.");
return 1;
}
Console.WriteLine("Upgrading project to .NET 9.0");
await DotNetUpgrade.UpdateProjectAtPathToNet80(settings.CsprojFilePath);
Console.WriteLine("==================================================");
Console.WriteLine("Done!");
return 0;
}
}

View File

@@ -0,0 +1,28 @@
using DotNetSolutionTools.Core;
using Spectre.Console.Cli;
namespace DotNetSolutionTools.CLI.Commands;
public class UpdateSlnToNet80Command : AsyncCommand<UpdateSlnToNet80Command.Settings>
{
public sealed class Settings : CommandSettings
{
[CommandArgument(1, "<SolutionFilePath>")]
public required string SolutionFilePath { get; set; }
}
public override async Task<int> ExecuteAsync(CommandContext context, Settings settings)
{
if (!File.Exists(settings.SolutionFilePath))
{
Console.WriteLine("Invalid file path. Please pass in a valid file path to a .csproj file.");
return 1;
}
Console.WriteLine("Upgrading project to .NET 9.0");
await DotNetUpgrade.UpdateProjectsInSolutionToNet80(settings.SolutionFilePath);
Console.WriteLine("==================================================");
Console.WriteLine("Done!");
return 0;
}
}

View File

@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Spectre.Console" Version="0.50.0" />
<PackageReference Include="Spectre.Console.Cli" Version="0.50.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DotNetSolutionTools.Core\DotNetSolutionTools.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,23 @@
using DotNetSolutionTools.CLI.Commands;
using Microsoft.Build.Locator;
using Spectre.Console.Cli;
var app = new CommandApp();
app.Configure(config =>
{
config.SetApplicationName("SolutionParityChecker");
config.ValidateExamples();
config.AddCommand<CompareCommand>("compare");
config.AddCommand<ImplicitUsingsCommand>("implicit-usings");
config.AddCommand<FormatCsprojCommand>("format-csproj");
config.AddCommand<TreatWarningsAsErrorsCommand>("warnings-as-errors");
config.AddCommand<ClearBinObjCommand>("clear-bin-obj");
config.AddCommand<UpdateProjectToNet80Command>("update-csproj-net80");
config.AddCommand<UpdateSlnToNet80Command>("update-sln-net80");
});
var instance = MSBuildLocator.QueryVisualStudioInstances().OrderByDescending(instance => instance.Version).First();
MSBuildLocator.RegisterInstance(instance);
return await app.RunAsync(args);

View File

@@ -0,0 +1,35 @@
namespace DotNetSolutionTools.Core;
public static class CleanFolder
{
public static void DeleteBinObjAndNodeModulesFoldersInFolder(string folderPath)
{
var nodeModulesFolders = Directory
.GetDirectories(folderPath, "*", SearchOption.AllDirectories)
.Where(x => x.EndsWith(Path.DirectorySeparatorChar + "node_modules"))
.ToList();
foreach (var folder in nodeModulesFolders)
{
if (Directory.Exists(folder))
{
Directory.Delete(folder, true);
}
}
var binAndObjFolders = Directory
.GetDirectories(folderPath, "*", SearchOption.AllDirectories)
.Where(x =>
x.EndsWith(Path.DirectorySeparatorChar + "bin") || x.EndsWith(Path.DirectorySeparatorChar + "obj")
)
.ToList();
foreach (var folder in binAndObjFolders)
{
if (Directory.Exists(folder))
{
Directory.Delete(folder, true);
}
}
}
}

View File

@@ -0,0 +1,24 @@
namespace DotNetSolutionTools.Core.Common;
public static class CsprojHelper
{
public static string[] RetrieveAllCSharpProjectFullPathsFromFolder(string solutionFolderPath)
{
// if solutionFolderPath does not end with a slash, add one
if (solutionFolderPath[^1] != Path.DirectorySeparatorChar)
{
solutionFolderPath += Path.DirectorySeparatorChar;
}
var csprojList = Directory.GetFiles(solutionFolderPath, "*.csproj", SearchOption.AllDirectories);
return csprojList;
}
public static string[] RetrieveAllCSharpProjectFullPathsFromSolution(string solutionFilePath)
{
var solutionFile = SlnHelper.ParseSolutionFileFromPath(solutionFilePath);
ArgumentNullException.ThrowIfNull(solutionFile);
var result = SlnHelper.GetCSharpProjectObjectsFromSolutionFile(solutionFile);
var csprojList = result.Select(x => x.FullPath).ToArray();
return csprojList;
}
}

View File

@@ -0,0 +1,23 @@
using Microsoft.Build.Construction;
namespace DotNetSolutionTools.Core.Common;
public static class SlnHelper
{
public static SolutionFile? ParseSolutionFileFromPath(string solutionFilePath)
{
var solutionFile = SolutionFile.Parse(solutionFilePath);
return solutionFile;
}
public static List<ProjectRootElement> GetCSharpProjectObjectsFromSolutionFile(SolutionFile solutionFile)
{
var projectList = solutionFile
.ProjectsByGuid.Where(x => x.Value.ProjectType == SolutionProjectType.KnownToBeMSBuildFormat)
.Select(s => ProjectRootElement.Open(s.Value.AbsolutePath))
.ToList();
return projectList;
}
}

View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build" Version="17.14.8" ExcludeAssets="runtime" />
<PackageReference Include="Microsoft.Build.Locator" Version="1.9.1" />
<PackageReference Include="NuGet.Protocol" Version="6.14.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,96 @@
using DotNetSolutionTools.Core.Common;
using DotNetSolutionTools.Core.Infrastructure;
using Microsoft.Build.Construction;
using NuGet.Versioning;
namespace DotNetSolutionTools.Core;
public static class DotNetUpgrade
{
public static async Task UpdateProjectsInSolutionToNet80(string solutionFilePath)
{
var solutionFile = SolutionFile.Parse(solutionFilePath);
var csprojList = SlnHelper.GetCSharpProjectObjectsFromSolutionFile(solutionFile);
await UpdateProjectsToNet80(csprojList);
}
public static async Task UpdateProjectAtPathToNet80(string csprojFilePath)
{
var csproj = ProjectRootElement.Open(csprojFilePath);
await UpdateProjectToNet80(csproj!);
}
private static async Task UpdateProjectsToNet80(List<ProjectRootElement> projects)
{
foreach (var project in projects)
{
await UpdateProjectToNet80(project);
}
}
private static async Task UpdateProjectToNet80(ProjectRootElement project)
{
var targetFramework = project
.PropertyGroups.SelectMany(x => x.Properties)
.FirstOrDefault(x => x.Name == "TargetFramework");
if (targetFramework?.Value is "net9.0" or "net8.0" or "net7.0" or "net6.0" or "net5.0")
{
if (targetFramework.Value is not "net9.0")
{
targetFramework.Value = "net9.0";
project.Save();
FormatCsproj.FormatCsprojFile(project.FullPath);
}
await UpdatePackagesToLatest(project);
}
}
private static async Task UpdatePackagesToLatest(ProjectRootElement project)
{
try
{
var packages = project
.Items.Where(x =>
x.ItemType == "PackageReference"
&& x.Metadata.Any(s => s.Name == "Version")
&& x.Include.StartsWith("Microsoft.")
)
.ToList();
var packageNameAndVersion = packages
.Select(x => new
{
Package = x,
Name = x.Include,
NugetVersion = NuGetVersion.Parse(x.Metadata.First(s => s.Name == "Version").Value)
})
.ToList();
var shouldSave = false;
foreach (var package in packageNameAndVersion)
{
var latestNugetVersion = await NugetLookup.FetchPackageMetadataAsync(
package.Name,
package.NugetVersion.IsPrerelease
);
if (latestNugetVersion > package.NugetVersion)
{
shouldSave = true;
package.Package.Metadata.First(s => s.Name == "Version").Value = latestNugetVersion.ToString();
}
}
if (shouldSave)
{
project.Save();
FormatCsproj.FormatCsprojFile(project.FullPath);
}
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
}

View File

@@ -0,0 +1,36 @@
using System.Text;
using System.Xml;
namespace DotNetSolutionTools.Core;
public static class FormatCsproj
{
public static void FormatCsprojFile(string csprojFilePath)
{
using TextReader rd = new StreamReader(csprojFilePath, Encoding.Default);
XmlDocument doc = new XmlDocument();
doc.Load(rd);
if (rd != Console.In)
{
rd.Close();
}
using var wr = new StreamWriter(csprojFilePath, false, Encoding.Default);
var settings = new XmlWriterSettings
{
Indent = true,
IndentChars = "\t",
NewLineOnAttributes = false,
OmitXmlDeclaration = true
};
using (var writer = XmlWriter.Create(wr, settings))
{
doc.WriteContentTo(writer);
writer.Close();
}
}
}

View File

@@ -0,0 +1,89 @@
using System.Diagnostics;
using Microsoft.Build.Construction;
namespace DotNetSolutionTools.Core;
public static class GenerateSln
{
private const string BasePath = @"C:\Users\Matthew\Documents\Git\folder";
public static async Task GenerateSlnWithDependenciesFromProjects(List<string> csprojPathList)
{
csprojPathList = [$"""{BasePath}/src/project1/project1.csproj"""];
// Get csproj objects
var projectObjects = csprojPathList.Select(ProjectRootElement.Open).ToList();
// Initialize dependencies with the root projects
var allDependencies = new HashSet<string>(csprojPathList);
// Recursively find all dependencies
FindAllDependenciesAsync(projectObjects, allDependencies);
// At this point, allDependencies contains paths to all projects and their dependencies
// Further processing to generate the solution file can be done here
Console.WriteLine(allDependencies.Count);
// delete the solution file if it exists
if (File.Exists(BasePath + "\\Global.sln"))
{
File.Delete(BasePath + "\\Global.sln");
}
// powershell
await RunPowerShellCommandAsync($"dotnet new sln -n Global -o {BasePath}");
foreach (var project in allDependencies)
{
await RunPowerShellCommandAsync($"dotnet sln {BasePath}/Global.sln add {project}");
}
}
private static void FindAllDependenciesAsync(List<ProjectRootElement> projects, HashSet<string> allDependencies)
{
var newDependencies = new List<ProjectRootElement>();
foreach (var project in projects)
{
var dependencyPaths = project
.Items.Where(x => x.ItemType == "ProjectReference")
.Select(s => s.Include)
.ToList();
// projectrootelement can't handle paths with ../ so we need to remove them, but resolve them correctly
dependencyPaths = dependencyPaths.Select(x => Path.GetFullPath(x, project.DirectoryPath)).ToList();
foreach (var path in dependencyPaths)
{
// Add to the HashSet to prevent duplicates and check if the path was already processed
if (allDependencies.Add(path))
{
newDependencies.Add(ProjectRootElement.Open(path));
}
}
}
if (newDependencies.Any())
{
// Recursively process new dependencies found
FindAllDependenciesAsync(newDependencies, allDependencies);
}
}
private static async Task RunPowerShellCommandAsync(string command)
{
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "powershell",
Arguments = command,
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
}
};
process.Start();
await process.WaitForExitAsync();
Console.WriteLine(await process.StandardOutput.ReadToEndAsync());
}
}

View File

@@ -0,0 +1,71 @@
using DotNetSolutionTools.Core.Common;
using Microsoft.Build.Construction;
namespace DotNetSolutionTools.Core;
public static class ImplicitUsings
{
public static List<string> FindCSharpProjectsMissingImplicitUsings(string solutionFilePath)
{
var solutionFile = SolutionFile.Parse(solutionFilePath);
var csprojList = SlnHelper.GetCSharpProjectObjectsFromSolutionFile(solutionFile);
var projectsMissingImplicitUsings = FindCSharpProjectsMissingImplicitUsings(csprojList);
var projectsMissingImplicitUsingsStringList = projectsMissingImplicitUsings.Select(x => x.FullPath).ToList();
return projectsMissingImplicitUsingsStringList;
}
public static List<ProjectRootElement> FindCSharpProjectsMissingImplicitUsings(List<ProjectRootElement> projectList)
{
var projectsMissingImplicitUsings = new List<ProjectRootElement>();
foreach (var project in projectList)
{
var implicitUsings = project
.PropertyGroups.SelectMany(x => x.Properties)
.FirstOrDefault(x => x.Name == "ImplicitUsings");
if (implicitUsings is null || implicitUsings.Value is not "enable")
{
projectsMissingImplicitUsings.Add(project);
}
}
return projectsMissingImplicitUsings;
}
public static void AddMissingImplicitUsings(List<ProjectRootElement> projectsMissingImplicitUsings)
{
foreach (var project in projectsMissingImplicitUsings)
{
if (ProjectIsMissingImplicitUsings(project))
{
project.AddProperty("ImplicitUsings", "enable");
project.Save();
FormatCsproj.FormatCsprojFile(project.FullPath);
}
}
}
public static void EnableDisabledImplicitUsings(List<ProjectRootElement> projectsMissingImplicitUsings)
{
throw new NotImplementedException();
}
public static void EnableAllImplicitUsings(List<ProjectRootElement> projectsMissingImplicitUsings)
{
throw new NotImplementedException();
}
public static bool ProjectIsMissingImplicitUsings(ProjectRootElement project)
{
var implicitUsings = project
.PropertyGroups.SelectMany(x => x.Properties)
.FirstOrDefault(x => x.Name == "ImplicitUsings");
if (implicitUsings is null)
{
return true;
}
return false;
}
}

View File

@@ -0,0 +1,66 @@
using System.Diagnostics.CodeAnalysis;
using NuGet.Common;
using NuGet.Protocol;
using NuGet.Protocol.Core.Types;
using NuGet.Versioning;
namespace DotNetSolutionTools.Core.Infrastructure;
public static class NugetLookup
{
public static async Task<NuGetVersion> FetchPackageMetadataAsync(string packageId, bool isCurrentlyPrerelease)
{
var cache = new SourceCacheContext();
var repositories = Repository.Factory.GetCoreV3("https://api.nuget.org/v3/index.json");
var logger = NullLogger.Instance;
var resource = await repositories.GetResourceAsync<FindPackageByIdResource>();
var versions = await resource.GetAllVersionsAsync(packageId, cache, logger, CancellationToken.None);
var latestPrereleaseVersion = versions.LastOrDefault(s => s.IsPrerelease == true);
var latestStableVersion = versions.LastOrDefault(s => s.IsPrerelease == false);
if (latestStableVersion is null && latestPrereleaseVersion is null)
{
Throw(packageId);
}
if (latestStableVersion is null)
{
if (isCurrentlyPrerelease && latestPrereleaseVersion is not null)
{
return latestPrereleaseVersion;
}
else
{
Throw(packageId);
}
}
if (latestPrereleaseVersion is null)
{
return latestStableVersion;
}
if (latestStableVersion > latestPrereleaseVersion)
{
return latestStableVersion;
}
if (isCurrentlyPrerelease)
{
return latestPrereleaseVersion;
}
return latestStableVersion;
}
[DoesNotReturn]
private static void Throw(string packageId)
{
throw new ArgumentNullException(
"latestVersion",
$"No latest stable or prerelease Nuget package version found for {packageId}"
);
}
}

View File

@@ -0,0 +1,8 @@
namespace DotNetSolutionTools.Core.Models;
public class Project
{
public required string FullPath { get; set; }
public required string Name { get; set; }
public List<Project> DependsOn { get; set; } = [];
}

View File

@@ -0,0 +1,93 @@
using DotNetSolutionTools.Core.Common;
using Microsoft.Build.Construction;
using NuGet.Versioning;
namespace DotNetSolutionTools.Core;
public class PackageVersionConsistency
{
public static void ResolvePackageVersionsToLatestInstalled(string solutionFilePath)
{
var result = GetInconsistentNugetPackageVersions(solutionFilePath);
var inconsistentPackages = result
.GroupBy(s => s.packageName)
.Select(s =>
(s.Key, s.Select(x => (x.version, x.metadataElement)).OrderByDescending(x => x.version).ToList())
)
.ToList();
foreach (var package in inconsistentPackages)
{
var latestInstalledVersion = package.Item2.First().version;
foreach (var (version, metadataElement) in package.Item2.Skip(1))
{
metadataElement.Value = latestInstalledVersion.ToString();
metadataElement.ContainingProject.Save();
FormatCsproj.FormatCsprojFile(metadataElement.ContainingProject.FullPath);
}
}
}
public static List<string> FindInconsistentNugetPackageVersions(string solutionFilePath)
{
var result = GetInconsistentNugetPackageVersions(solutionFilePath);
return result.Select(s => s.packageName).Distinct().ToList();
}
private static List<(
string packageName,
NuGetVersion version,
ProjectMetadataElement metadataElement
)> GetInconsistentNugetPackageVersions(string solutionFilePath)
{
var solutionFile = SolutionFile.Parse(solutionFilePath);
var csprojList = SlnHelper.GetCSharpProjectObjectsFromSolutionFile(solutionFile);
var packageList = new List<(string, NuGetVersion, ProjectMetadataElement)>();
var outOfSyncPackages = new List<(string packageName, NuGetVersion version, ProjectMetadataElement element)>();
foreach (var project in csprojList)
{
var projectPackages = GetPackageVersions(project);
packageList.AddRange(projectPackages);
}
var groupedByPackage = packageList.GroupBy(s => s.Item1).ToList();
foreach (var grouping in groupedByPackage)
{
var versions = grouping.Select(s => (s.Item2, s.Item3)).DistinctBy(s => s.Item1).ToList();
if (versions.Count > 1)
{
//outOfSyncPackages.AddRange(versions.Select(s => (grouping.Key, s.Item1, s.Item2)));
outOfSyncPackages.AddRange(grouping.Select(s => (s.Item1, s.Item2, s.Item3)));
}
}
return outOfSyncPackages.ToList();
}
private static List<(string, NuGetVersion, ProjectMetadataElement)> GetPackageVersions(ProjectRootElement project)
{
try
{
var packages = project
.Items.Where(x => x.ItemType == "PackageReference" && x.Metadata.Any(s => s.Name == "Version"))
.ToList();
var packageNameAndVersion = packages
.Select(x =>
(
x.Include,
NuGetVersion.Parse(x.Metadata.First(s => s.Name == "Version").Value),
x.Metadata.First(s => s.Name == "Version")
)
)
.ToList();
return packageNameAndVersion;
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
}

View File

@@ -0,0 +1,52 @@
using DotNetSolutionTools.Core.Common;
using DotNetSolutionTools.Core.Models;
using Microsoft.Build.Construction;
namespace DotNetSolutionTools.Core;
public static class SolutionBuildOrder
{
public static List<ProjectRootElement> Projects { get; set; } = [];
public static List<Project> GetBuildOrder(string solutionFilePath)
{
var solutionFile = SlnHelper.ParseSolutionFileFromPath(solutionFilePath);
ArgumentNullException.ThrowIfNull(solutionFile);
var projects = SlnHelper.GetCSharpProjectObjectsFromSolutionFile(solutionFile);
Projects = projects;
List<Project> projects2 = [];
foreach (var project in projects)
{
var projectName = Path.GetFileNameWithoutExtension(project.FullPath);
var project2 = new Project { FullPath = project.FullPath, Name = projectName };
project2.DependsOn = GetDependencies(project2);
projects2.Add(project2);
}
return projects2;
}
public static List<Project> GetDependencies(Project project)
{
var projectReferences = Projects
.Single(s => s.FullPath == project.FullPath)
.AllChildren.OfType<ProjectItemElement>()
.Where(x => x.ElementName == "ProjectReference")
.ToList();
List<Project> dependencies = [];
foreach (var projectReference in projectReferences)
{
var fullPath = Path.Combine(Path.GetDirectoryName(project.FullPath)!, projectReference.Include);
fullPath = Path.GetFullPath(fullPath);
var dependency = Projects.Single(s => s.FullPath == fullPath);
var projectName = Path.GetFileNameWithoutExtension(dependency.FullPath);
var subProject = new Project { FullPath = dependency.FullPath, Name = projectName };
subProject.DependsOn = GetDependencies(subProject);
dependencies.Add(subProject);
}
return dependencies;
}
}

View File

@@ -0,0 +1,43 @@
using DotNetSolutionTools.Core.Common;
using Microsoft.Build.Construction;
namespace DotNetSolutionTools.Core;
public static class SolutionProjectParity
{
public static List<string> CompareSolutionAndCSharpProjects(string solutionFolderPath, string solutionFilePath)
{
var csprojList = CsprojHelper.RetrieveAllCSharpProjectFullPathsFromFolder(solutionFolderPath);
var solutionFile = SlnHelper.ParseSolutionFileFromPath(solutionFilePath);
ArgumentNullException.ThrowIfNull(solutionFile);
var projectsMissingFromSolution = FindProjectsMissingFromSolution(csprojList, solutionFile);
return projectsMissingFromSolution;
}
public static List<string> FindProjectsMissingFromSolution(string[] csprojList, SolutionFile solutionFile)
{
var projects = solutionFile.ProjectsInOrder;
var projectsMissingFromSolution = new List<string>();
foreach (var project in csprojList)
{
var projectInSolution = projects.FirstOrDefault(x =>
NormalizePath(x.AbsolutePath) == NormalizePath(project)
);
if (projectInSolution == null)
{
projectsMissingFromSolution.Add(project);
}
}
return projectsMissingFromSolution;
}
private static string NormalizePath(string path)
{
return Path.GetFullPath(new Uri(path).LocalPath)
.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
}
}

View File

@@ -0,0 +1,103 @@
using DotNetSolutionTools.Core.Common;
using Microsoft.Build.Construction;
namespace DotNetSolutionTools.Core;
public static class WarningsAsErrors
{
public static List<string> FindCSharpProjectsMissingTreatWarningsAsErrors(string solutionFilePath)
{
var solutionFile = SolutionFile.Parse(solutionFilePath);
var csprojList = SlnHelper.GetCSharpProjectObjectsFromSolutionFile(solutionFile);
var projectsMissingImplicitUsings = FindCSharpProjectsMissingTreatWarningsAsErrors(csprojList);
var projectsMissingImplicitUsingsStringList = projectsMissingImplicitUsings.Select(x => x.FullPath).ToList();
return projectsMissingImplicitUsingsStringList;
}
public static List<ProjectRootElement> FindCSharpProjectsMissingTreatWarningsAsErrors(
List<ProjectRootElement> projectList
)
{
var projectsMissingTreatWarningsAsErrors = new List<ProjectRootElement>();
foreach (var project in projectList)
{
var treatWarningsAsErrors = project
.PropertyGroups.SelectMany(x => x.Properties)
.FirstOrDefault(x => x.Name == "TreatWarningsAsErrors");
if (treatWarningsAsErrors is null || treatWarningsAsErrors.Value is not "true")
{
projectsMissingTreatWarningsAsErrors.Add(project);
}
}
return projectsMissingTreatWarningsAsErrors;
}
public static void AddMissingTreatWarningsAsErrorsToSolution(string solutionFilePath)
{
var solutionFile = SolutionFile.Parse(solutionFilePath);
var csprojList = SlnHelper.GetCSharpProjectObjectsFromSolutionFile(solutionFile);
var projectsMissingImplicitUsings = FindCSharpProjectsMissingTreatWarningsAsErrors(csprojList);
AddMissingTreatWarningsAsErrors(projectsMissingImplicitUsings);
}
public static void RemoveAllTreatWarningsAsErrorsInSolution(string solutionFilePath)
{
var solutionFile = SolutionFile.Parse(solutionFilePath);
var csprojList = SlnHelper.GetCSharpProjectObjectsFromSolutionFile(solutionFile);
RemoveTreatWarningsAsErrors(csprojList);
}
public static void AddMissingTreatWarningsAsErrors(List<ProjectRootElement> projectsMissingImplicitUsings)
{
foreach (var project in projectsMissingImplicitUsings)
{
if (ProjectIsMissingTreatWarningsAsErrors(project))
{
project.AddProperty("TreatWarningsAsErrors", "true");
project.Save();
FormatCsproj.FormatCsprojFile(project.FullPath);
}
}
}
public static void RemoveTreatWarningsAsErrors(List<ProjectRootElement> projectList)
{
foreach (var project in projectList)
{
var treatWarningsAsErrors = project
.PropertyGroups.SelectMany(x => x.Properties)
.FirstOrDefault(x => x.Name == "TreatWarningsAsErrors");
if (treatWarningsAsErrors is not null)
{
treatWarningsAsErrors.Parent.RemoveChild(treatWarningsAsErrors);
project.Save();
FormatCsproj.FormatCsprojFile(project.FullPath);
}
}
}
private static bool ProjectIsMissingTreatWarningsAsErrors(ProjectRootElement project)
{
var implicitUsings = project
.PropertyGroups.SelectMany(x => x.Properties)
.FirstOrDefault(x => x.Name == "TreatWarningsAsErrors");
if (implicitUsings is null)
{
return true;
}
return false;
}
private static bool ProjectBuildSuccessfully(ProjectRootElement project)
{
// build the project
var buildProject = new Microsoft.Build.Evaluation.Project(project);
// retrieve warnings
var buildResult = buildProject.Build();
return buildResult;
}
}

View File

@@ -0,0 +1,17 @@
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
@code
{
}

View File

@@ -0,0 +1,15 @@
using MudBlazor;
namespace DotNetSolutionTools.Photino;
public static class AppThemeProvider
{
public static MudTheme GetTheme()
{
var theme = new MudTheme();
theme.Typography.H5.FontSize = "1.4rem";
theme.Typography.H5.FontWeight = "500";
theme.Typography.H6.FontSize = "1.1rem";
return theme;
}
}

View File

@@ -0,0 +1,61 @@
@using Blazor.Diagrams
@using Blazor.Diagrams.Core.Anchors
@using Blazor.Diagrams.Core.Geometry
@using Blazor.Diagrams.Core.Models
@using DotNetSolutionTools.Core.Models
<h3>BuildOrderDiagram</h3>
<div style="width: 100%; height: 400px; border: black">
<CascadingValue Value="Diagram" IsFixed="true">
<DiagramCanvas />
</CascadingValue>
</div>
@code {
[Parameter, EditorRequired]
public List<Project> Projects { get; set; }
private BlazorDiagram Diagram { get; set; } = null!;
protected override void OnInitialized()
{
Diagram = new BlazorDiagram();
foreach (var project in Projects)
{
var node = Diagram.Nodes.Add(new NodeModel()
{
Title = project.Name,
});
foreach (var dependency in project.DependsOn)
{
var dependencyNode = Diagram.Nodes.Add(new NodeModel()
{
Title = dependency.Name
});
Diagram.Links.Add(new LinkModel(node, dependencyNode));
}
}
}
protected void Example()
{
Diagram = new BlazorDiagram();
var firstNode = Diagram.Nodes.Add(new NodeModel(position: new Point(50, 50))
{
Title = "Node 1"
});
var secondNode = Diagram.Nodes.Add(new NodeModel(position: new Point(200, 100))
{
Title = "Node 2"
});
var leftPort = secondNode.AddPort(PortAlignment.Left);
var rightPort = secondNode.AddPort(PortAlignment.Right);
// The connection point will be the intersection of
// a line going from the target to the center of the source
var sourceAnchor = new ShapeIntersectionAnchor(firstNode);
// The connection point will be the port's position
var targetAnchor = new SinglePortAnchor(leftPort);
var link = Diagram.Links.Add(new LinkModel(sourceAnchor, targetAnchor));
}
}

View File

@@ -0,0 +1,48 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<OutputType>WinExe</OutputType> <!-- Doesn't actually mean Windows Exe, 'Exe' = console entrypoint, 'WinExe' = application entry point -->
<TargetFramework>net9.0</TargetFramework>
<ApplicationIcon>favicon.ico</ApplicationIcon>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>false</SelfContained>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
</PropertyGroup>
<ItemGroup>
<SupportedPlatform Include="browser" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Ardalis.GuardClauses" Version="5.0.0" />
<PackageReference Include="MudBlazor" Version="8.6.0" />
<PackageReference Include="Photino.Blazor" Version="4.0.13" />
<PackageReference Include="Z.Blazor.Diagrams" Version="3.0.3" />
</ItemGroup>
<ItemGroup>
<Content Update="wwwroot\**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<None Update="favicon.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DotNetSolutionTools.Core\DotNetSolutionTools.Core.csproj" />
</ItemGroup>
<Target Name="RemoveConflictingAssets" BeforeTargets="ResolveStaticWebAssetsConfiguration">
<ItemGroup>
<StaticWebAsset Remove="@(StaticWebAsset)" Condition="'%(Extension)' == '.gz'" />
</ItemGroup>
</Target>
</Project>

View File

@@ -0,0 +1,34 @@
@inherits LayoutComponentBase
<MudThemeProvider Theme="@AppThemeProvider.GetTheme()" />
<MudPopoverProvider/>
<MudDialogProvider/>
<MudSnackbarProvider/>
<MudLayout>
<MudAppBar Dense="true">
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="@((e) => DrawerToggle())" />
</MudAppBar>
<MudDrawer @bind-Open="@_drawerOpen">
<MudDrawerHeader>
<MudStack Row="false" Justify="Justify.Center">
<MudText Typo="Typo.h5">DotNetSolutionTools.Photino</MudText>
</MudStack>
</MudDrawerHeader>
<NavMenu/>
</MudDrawer>
<MudMainContent>
<MudContainer Class="px-8 py-6">
@Body
</MudContainer>
</MudMainContent>
</MudLayout>
@code {
bool _drawerOpen = true;
void DrawerToggle()
{
_drawerOpen = !_drawerOpen;
}
}

View File

@@ -0,0 +1,3 @@
<MudNavMenu>
<MudNavLink Href="/" Icon="@Icons.Material.Filled.Home" Match="NavLinkMatch.All">Home</MudNavLink>
</MudNavMenu>

View File

@@ -0,0 +1,8 @@
namespace DotNetSolutionTools.Photino.Models;
public class AppState
{
public required string? SolutionFolderPath { get; set; }
public required string? SolutionFilePath { get; set; }
public required string? CsprojFilePath { get; set; }
}

View File

@@ -0,0 +1,42 @@
@page "/"
@using Ardalis.GuardClauses
@using DotNetSolutionTools.Core
@using DotNetSolutionTools.Core.Models
@inject AppState AppState
<MudStack>
<MudInput T="string" Label="Solution File Path" @bind-Value="_solutionFilePath" @bind-Value:after="SetToAppState" />
<MudButton OnClick="@Populate">Populate project dependencies</MudButton>
</MudStack>
@if (_projects.Count > 0)
{
<BuildOrderDiagram Projects="_projects"/>
}
@foreach(var project in _projects)
{
<MudText>@project.Name</MudText>
}
@code {
private string? _solutionFilePath;
private List<Project> _projects = [];
protected override void OnInitialized()
{
_solutionFilePath = AppState.SolutionFilePath;
}
private void Populate()
{
Guard.Against.Null(_solutionFilePath);
var result = SolutionBuildOrder.GetBuildOrder(_solutionFilePath);
_projects = result;
}
private void SetToAppState()
{
AppState.SolutionFilePath = _solutionFilePath;
}
}

View File

@@ -0,0 +1,91 @@
using System.Text.Json;
using DotNetSolutionTools.Photino.Models;
using Microsoft.Build.Locator;
using Microsoft.Extensions.DependencyInjection;
using MudBlazor.Services;
using Photino.Blazor;
namespace DotNetSolutionTools.Photino;
public class Program
{
[STAThread]
public static void Main(string[] args)
{
var appBuilder = PhotinoBlazorAppBuilder.CreateDefault(args);
appBuilder.Services.AddLogging();
appBuilder.Services.AddMudServices();
appBuilder.Services.AddSingleton<AppState>();
appBuilder.RootComponents.Add<App>("app");
var app = appBuilder.Build();
app.MainWindow.SetSize(1400, 800)
.SetDevToolsEnabled(true)
.SetLogVerbosity(0)
//.SetIconFile("favicon.ico")
.SetTitle("DotNetSolutionTools.Photino");
AppDomain.CurrentDomain.UnhandledException += (sender, error) =>
{
app.MainWindow.ShowMessage("Fatal exception", error.ExceptionObject.ToString());
};
var instance = MSBuildLocator
.QueryVisualStudioInstances()
.OrderByDescending(instance => instance.Version)
.First();
MSBuildLocator.RegisterInstance(instance);
var configFilePath = GetConfigFilePath();
using var scope = app.Services.CreateScope();
var appState = scope.ServiceProvider.GetRequiredService<AppState>();
LoadAppStateFromConfigFile(appState, configFilePath);
app.MainWindow.RegisterWindowClosingHandler(
(sender, eventArgs) =>
{
using var stream = File.Create(configFilePath);
JsonSerializer.Serialize(stream, appState);
stream.Flush();
return false;
}
);
app.Run();
}
private static string GetConfigFilePath()
{
var folder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
var configFolder = Path.Combine(folder, "DotNetSolutionTools.Photino");
Directory.CreateDirectory(configFolder);
var configFilePath = Path.Combine(configFolder, "config.json");
return configFilePath;
}
private static void LoadAppStateFromConfigFile(AppState appState, string configFilePath)
{
if (File.Exists(configFilePath) is false)
{
File.WriteAllText(configFilePath, string.Empty);
}
using var stream = File.OpenRead(configFilePath);
if (stream.Length is 0)
{
return;
}
var deserializedAppState = JsonSerializer.Deserialize<AppState>(stream);
if (deserializedAppState is not null)
{
appState.SolutionFolderPath = deserializedAppState.SolutionFolderPath;
appState.SolutionFilePath = deserializedAppState.SolutionFilePath;
appState.CsprojFilePath = deserializedAppState.CsprojFilePath;
}
}
}

View File

@@ -0,0 +1,22 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"Run": {
"commandName": "Project",
"dotnetRunMessages": true,
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development"
}
},
"(Watch)": {
"commandName": "Executable",
"executablePath": "dotnet",
"workingDirectory": "$(ProjectDir)",
"commandLineArgs": "watch run",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_WATCH_RESTART_ON_RUDE_EDIT": "true"
}
}
}
}

View File

@@ -0,0 +1,15 @@
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using Microsoft.Extensions.Logging
@using MudBlazor
@using DotNetSolutionTools.Photino.Layout
@using DotNetSolutionTools.Photino
@using DotNetSolutionTools.Photino.Models
@using DotNetSolutionTools.Photino.Components
@using Blazor.Diagrams.Components

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<base href="/" />
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
<link href="css/app.css" rel="stylesheet" />
<link href="DotNetSolutionTools.Photino.styles.css" rel="stylesheet" />
<link href="_content/Z.Blazor.Diagrams/style.min.css" rel="stylesheet" />
<link href="_content/Z.Blazor.Diagrams/default.styles.min.css" rel="stylesheet" />
</head>
<body>
<app>Loading...</app>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.webview.js"></script>
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
<script src="_content/Z.Blazor.Diagrams/script.min.js"></script>
</body>
</html>