11 Commits

Author SHA1 Message Date
Meik
de9a226272 build: bump m42 package to 1.4.0.17 2026-06-30 18:57:21 +02:00
Meik
a9a9e28924 fix: avoid direct journal service method binding 2026-06-30 18:55:58 +02:00
Meik
5e9ad806d6 fix: remove obsolete alert activity relation for 26.1
Matrix42 26.1 no longer provides UsedInTypeSPSActivityTypeAlert, so ticket overview activity type resolution must not query or evaluate that attribute.
2026-06-30 18:48:30 +02:00
Meik
b9060d3ca1 fix: return role memberships payload directly 2026-06-30 16:04:32 +02:00
Meik
592d0e109f fix: bind role memberships sid first 2026-06-30 15:49:53 +02:00
Meik
92c41d3aa0 fix: align web api parameter binding with m42 example 2026-06-30 15:27:53 +02:00
Meik
01589d617e fix: read GET parameters from request query 2026-06-30 14:34:32 +02:00
Meik
c59acbd30c fix: bind GET query parameters from URI 2026-06-30 14:30:25 +02:00
Meik
942473501e fix: restore 26eac54 behavior for 26.1 port 2026-06-30 14:17:55 +02:00
Meik
12ebd1fce9 fix: compile helper from checked-in FASD base 2026-06-30 14:00:13 +02:00
Meik
063098808f feat: migrate 26eac54 state to Matrix42 26.1 2026-06-30 13:26:07 +02:00
772 changed files with 12558 additions and 2628 deletions

95
.gitignore vendored
View File

@@ -1,81 +1,14 @@
# --------------------------------------------------------------------------------------------------
# Core Visual Studio / .NET build outputs
# --------------------------------------------------------------------------------------------------
bin/
obj/
[Bb]uild/
[Dd]ebug*/
[Rr]elease*/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
_ReSharper.*/
*.user
*.userosscache
*.suo
*.cache
*.pdb
*.mpdb
*.opendb
*.VC.db
*.vscode/
# --------------------------------------------------------------------------------------------------
# Package artefacts (NuGet, npm, etc.)
# --------------------------------------------------------------------------------------------------
/packages/
*.nupkg
*.snupkg
*.nuspec
package-lock.json
packages.config
npm-debug.log*
yarn-error.log
# --------------------------------------------------------------------------------------------------
# TFS / TFVC specific files and folders
# --------------------------------------------------------------------------------------------------
$tf/
*$tf/
*.tf
*.tfs
.tfignore
# --------------------------------------------------------------------------------------------------
# IDE tooling and workspace settings
# --------------------------------------------------------------------------------------------------
.vs/
.vscode/
*.swp
*.sln.docstates
*.VC.VC.opendb
.idea/
.DS_Store
Thumbs.db
# --------------------------------------------------------------------------------------------------
# Test results and profiling data
# --------------------------------------------------------------------------------------------------
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
*.trx
*.coverage
*.coveragexml
*.opencover.xml
*.dotCover
BenchmarkDotNet.Artifacts/
# --------------------------------------------------------------------------------------------------
# Miscellaneous recommended ignores
# --------------------------------------------------------------------------------------------------
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
*.tmp
*.temp
*.log
*.bak
*.orig
*.scc
bin/
obj/
.vs/
*.user
packages/
F4SDHelper/packages.config
F4SDM42WebApi/packages.config
F4SDM42WebApi/BasePackage/Assemblies/
F4SDM42WebApi/Root/InstalledPackages/
F4SDHelper/bin/
F4SDHelper/obj/
F4SDM42WebApi/bin/
F4SDM42WebApi/obj/
artifacts/

View File

@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.33424.131
# Visual Studio Version 18
VisualStudioVersion = 18.7.11919.86 stable
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "F4SD - M42WebApi", "F4SDM42WebApi\F4SD - M42WebApi.csproj", "{D8CBFFCA-0B43-4ACC-80EA-C944E7420CEE}"
EndProject
@@ -47,25 +47,6 @@ Global
EndGlobalSection
GlobalSection(SharedMSBuildProjectFiles) = preSolution
M42F4SDUUXWorkspace\M42F4SDUUXWorkspace.projitems*{c3d52a04-2461-4f35-8edf-de7226add677}*SharedItemsImports = 13
M42F4SDUUXWorkspace\M42F4SDUUXWorkspace.projitems*{d8cbffca-0b43-4acc-80ea-c944e7420cee}*SharedItemsImports = 4
EndGlobalSection
GlobalSection(TeamFoundationVersionControl) = preSolution
SccNumberOfProjects = 5
SccEnterpriseProvider = {4CA58AB2-18FA-4F8D-95D4-32DDF27D184C}
SccTeamFoundationServer = https://consulting4it.visualstudio.com/
SccLocalPath0 = .
SccProjectUniqueName1 = F4SDHelper\\F4SD\u0020-\u0020Helper.csproj
SccProjectName1 = F4SDHelper
SccLocalPath1 = F4SDHelper
SccProjectUniqueName2 = F4SDM42WebApi\\F4SD\u0020-\u0020M42WebApi.csproj
SccProjectName2 = F4SDM42WebApi
SccLocalPath2 = F4SDM42WebApi
SccProjectUniqueName3 = M42F4SDUUXWorkspace\\F4SD\u0020-\u0020M42\u0020UUX\u0020Workspace.shproj
SccProjectName3 = M42F4SDUUXWorkspace
SccLocalPath3 = M42F4SDUUXWorkspace
SccProjectUniqueName4 = M42F4SDUUXWorkspace\\M42F4SDUUXWorkspace.projitems
SccProjectTopLevelParentUniqueName4 = M42F4SDUUXWorkspace\\F4SD\u0020-\u0020M42\u0020UUX\u0020Workspace.shproj
SccProjectName4 = M42F4SDUUXWorkspace
SccLocalPath4 = M42F4SDUUXWorkspace
M42F4SDUUXWorkspace\M42F4SDUUXWorkspace.projitems*{d8cbffca-0b43-4acc-80ea-c944e7420cee}*SharedItemsImports = 5
EndGlobalSection
EndGlobal

View File

@@ -1,75 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{EB95E1FB-7A9E-4894-BD28-2D0BE2715EBE}</ProjectGuid>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>C4IT.F4SDM</RootNamespace>
<AssemblyName>C4ITF4SDM42WebApiHelper</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
<SccProjectName>SAK</SccProjectName>
<SccLocalPath>SAK</SccLocalPath>
<SccAuxPath>SAK</SccAuxPath>
<SccProvider>SAK</SccProvider>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release_and_copy|AnyCPU'">
<OutputPath>bin\Release_and_copy\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<Nullable>disable</Nullable>
<ImplicitUsings>disable</ImplicitUsings>
<LangVersion>latestmajor</LangVersion>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
<Reference Include="Newtonsoft.Json">
<HintPath>..\F4SDM42WebApi\M42Libraries\26.1\Newtonsoft.Json.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="..\..\..\Common Code\Logging\C4IT.Logging.LogManager.cs">
<Link>Common\C4IT.Logging.LogManager.cs</Link>
</Compile>
<Compile Include="Common\C4IT.FASD.Base.cs" />
<Compile Include="..\SharedAssemblyInfo.cs">
<Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile>
<Compile Include="Common\C4IT.FASD.Base.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
</Project>

View File

@@ -0,0 +1,73 @@
param(
[Parameter(Mandatory = $true)]
[string]$TemplateDir,
[Parameter(Mandatory = $true)]
[string]$AssembliesDir,
[Parameter(Mandatory = $true)]
[string]$OutputRoot,
[Parameter(Mandatory = $true)]
[string]$PackageName,
[Parameter(Mandatory = $true)]
[string]$PackageVersion
)
$ErrorActionPreference = 'Stop'
function Normalize-FullPath {
param([string]$Path)
return [System.IO.Path]::GetFullPath($Path)
}
function Normalize-TextFileLineEndings {
param([string]$Path)
$content = [System.IO.File]::ReadAllText($Path)
$content = [regex]::Replace($content, '(?<!\r)\n', "`r`n")
[System.IO.File]::WriteAllText($Path, $content, [System.Text.UTF8Encoding]::new($true))
}
$templateDirFull = Normalize-FullPath $TemplateDir
$assembliesDirFull = Normalize-FullPath $AssembliesDir
$outputRootFull = Normalize-FullPath $OutputRoot
$packageDir = Join-Path $outputRootFull "$PackageName v$PackageVersion"
$zipPath = "$packageDir.zip"
if (-not (Test-Path -LiteralPath (Join-Path $templateDirFull 'package.json'))) {
throw "Package template not found: $templateDirFull"
}
if (-not (Test-Path -LiteralPath (Join-Path $assembliesDirFull 'C4ITF4SDM42WebApi.dll'))) {
throw "Package assemblies not found: $assembliesDirFull"
}
if (Test-Path -LiteralPath $packageDir) {
Remove-Item -LiteralPath $packageDir -Recurse -Force
}
if (Test-Path -LiteralPath $zipPath) {
Remove-Item -LiteralPath $zipPath -Force
}
New-Item -ItemType Directory -Path $packageDir -Force | Out-Null
Copy-Item -Path (Join-Path $templateDirFull '*') -Destination $packageDir -Recurse -Force
Copy-Item -Path $assembliesDirFull -Destination (Join-Path $packageDir 'Assemblies') -Recurse -Force
$packageJsonPath = Join-Path $packageDir 'package.json'
$packageJson = [System.IO.File]::ReadAllText($packageJsonPath)
$packageJson = [regex]::Replace($packageJson, '"Version"\s*:\s*"[^"]+"', "`"Version`": `"$PackageVersion`"")
$packageJson = [regex]::Replace($packageJson, '"LastUpdatedDate"\s*:\s*"[^"]+"', "`"LastUpdatedDate`": `"$(Get-Date -Format 'yyyy-MM-ddTHH:mm:ss')`"")
[System.IO.File]::WriteAllText($packageJsonPath, $packageJson, [System.Text.UTF8Encoding]::new($true))
$textFileExtensions = @('.json', '.xml', '.dat', '.type', '.class', '.config', '.host')
Get-ChildItem -LiteralPath $packageDir -Recurse -File |
Where-Object { $textFileExtensions -contains $_.Extension.ToLowerInvariant() } |
ForEach-Object { Normalize-TextFileLineEndings $_.FullName }
Compress-Archive -Path (Join-Path $packageDir '*') -DestinationPath $zipPath -Force
Write-Host "Matrix42 package folder: $packageDir"
Write-Host "Matrix42 package zip: $zipPath"

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8" ?>
<host xmlns="urn:m42/host.config">
<modules>
<module assembly="Matrix42.DataLayer.Persistence" />
</modules>
<sections></sections>
</host>

View File

@@ -1,143 +1,99 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{D8CBFFCA-0B43-4ACC-80EA-C944E7420CEE}</ProjectGuid>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>C4IT.F4SDM</RootNamespace>
<AssemblyName>C4ITF4SDM42WebApi</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
<SccProjectName>SAK</SccProjectName>
<SccLocalPath>SAK</SccLocalPath>
<SccAuxPath>SAK</SccAuxPath>
<SccProvider>SAK</SccProvider>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>
</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug_and_copy|AnyCPU'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\Debug_and_copy\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release_and_copy|AnyCPU'">
<OutputPath>bin\Release_and_copy\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<RuntimeIdentifiers>win-x64;linux-x64</RuntimeIdentifiers>
<AppendRuntimeIdentifierToOutputPath>true</AppendRuntimeIdentifierToOutputPath>
<Nullable>disable</Nullable>
<ImplicitUsings>disable</ImplicitUsings>
<LangVersion>latestmajor</LangVersion>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<M42ExtensionId>6673a25d-33d6-4072-91d9-abed4be1a966</M42ExtensionId>
<M42AssemblyPattern>C4ITF4SD*.*</M42AssemblyPattern>
<M42PackageVersion>1.4.0.17</M42PackageVersion>
<M42BuildPackage>true</M42BuildPackage>
</PropertyGroup>
<ItemGroup>
<Reference Include="Matrix42.Common, Version=12.0.3.3096, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>M42Libraries\Matrix42.Common.dll</HintPath>
<Reference Include="Matrix42.Common">
<HintPath>M42Libraries\26.1\Matrix42.Common.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Matrix42.Contracts.Common, Version=12.0.3.3096, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>M42Libraries\Matrix42.Contracts.Common.dll</HintPath>
<Reference Include="Matrix42.Contracts.Common">
<HintPath>M42Libraries\26.1\Matrix42.Contracts.Common.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Matrix42.Contracts.Platform, Version=12.0.3.3096, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>M42Libraries\Matrix42.Contracts.Platform.dll</HintPath>
<Reference Include="Matrix42.Contracts.Platform">
<HintPath>M42Libraries\26.1\Matrix42.Contracts.Platform.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Matrix42.Contracts.ServiceManagement, Version=12.0.3.3096, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>M42Libraries\Matrix42.Contracts.ServiceManagement.dll</HintPath>
<Reference Include="Matrix42.Contracts.ServiceManagement">
<HintPath>M42Libraries\26.1\Matrix42.Contracts.ServiceManagement.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Matrix42.Pandora.Contracts, Version=12.0.3.3096, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>M42Libraries\Matrix42.Pandora.Contracts.dll</HintPath>
<Reference Include="Matrix42.Hosting.Contracts">
<HintPath>M42Libraries\26.1\Matrix42.Hosting.Contracts.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Matrix42.Services.Description.Contracts, Version=12.0.3.3096, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>M42Libraries\Matrix42.Services.Description.Contracts.dll</HintPath>
<Reference Include="Matrix42.Configuration.Global">
<HintPath>M42Libraries\26.1\Matrix42.Configuration.Global.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Microsoft.QualityTools.Testing.Fakes, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<Reference Include="Matrix42.Pandora.Contracts">
<HintPath>M42Libraries\26.1\Matrix42.Pandora.Contracts.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
<Reference Include="Matrix42.Persistence.Contracts">
<HintPath>M42Libraries\26.1\Matrix42.Persistence.Contracts.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.IO.Compression" />
<Reference Include="System.Web" />
<Reference Include="System.Web.Http, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>M42Libraries\System.Web.Http.dll</HintPath>
<Reference Include="Matrix42.Services.Description.Contracts">
<HintPath>M42Libraries\26.1\Matrix42.Services.Description.Contracts.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="System.Web.OData, Version=5.9.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>M42Libraries\System.Web.OData.dll</HintPath>
<Reference Include="Matrix42.WebApi.Contracts">
<HintPath>M42Libraries\26.1\Matrix42.WebApi.Contracts.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<Reference Include="update4u.SPS.DataLayer, Version=12.0.3.3096, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>M42Libraries\update4u.SPS.DataLayer.dll</HintPath>
<Reference Include="Newtonsoft.Json">
<HintPath>M42Libraries\26.1\Newtonsoft.Json.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="update4u.SPS.Utility, Version=12.0.3.3096, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>M42Libraries\update4u.SPS.Utility.dll</HintPath>
<Reference Include="System.Net.Http.Formatting">
<HintPath>M42Libraries\26.1\System.Net.Http.Formatting.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="update4u.SPS.DataLayer">
<HintPath>M42Libraries\26.1\update4u.SPS.DataLayer.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="update4u.SPS.Utility">
<HintPath>M42Libraries\26.1\update4u.SPS.Utility.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\F4SDHelper\F4SD - Helper.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\SharedAssemblyInfo.cs">
<Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile>
<Compile Include="cM42LogEntry.cs" />
<Compile Include="F4SDHelperService.cs" />
<Compile Include="F4SDM42WebApiController.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="..\SharedAssemblyInfo.cs">
<Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\F4SDHelper\F4SD - Helper.csproj">
<Project>{eb95e1fb-7a9e-4894-bd28-2d0be2715ebe}</Project>
<Name>F4SD - Helper</Name>
<Private>True</Private>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="deploy.ps1" />
<None Include="packages.config" />
<None Include="SignSourceFiles.cmd" />
<None Update="C4ITF4SDM42WebApi.dll.host.config" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<Import Project="..\M42F4SDUUXWorkspace\M42F4SDUUXWorkspace.projitems" Label="Shared" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>@echo powershell -ExecutionPolicy Unrestricted $(ProjectDir)deploy.ps1 -ProjectDir $(ProjectDir) -SolutionDir $(SolutionDir) -OutDir $(OutDir) -ConfigurationName $(ConfigurationName)</PostBuildEvent>
</PropertyGroup>
</Project>
<Import Project="M42SandboxedExtension.targets" Condition="Exists('M42SandboxedExtension.targets')" />
</Project>

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,3 @@
using C4IT.F4SDM;
using C4IT.FASD.Base;
using C4IT.Logging;
using Matrix42.Common;
using Matrix42.Contracts.ServiceManagement.ServiceContracts;
using Matrix42.Pandora.Contracts;
using Matrix42.Services.Description.Contracts;
using System;
using System.Collections.Generic;
using System.Data;
@@ -12,13 +5,24 @@ using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.OData;
using System.Web.OData.Query;
using Matrix42.Common;
using Matrix42.Contracts.Common.Security;
using Matrix42.Contracts.ServiceManagement.ServiceContracts;
using Matrix42.Hosting.Contracts;
using Matrix42.Pandora.Contracts;
using Matrix42.Persistence.Contracts;
using Matrix42.Services.Description.Contracts;
using Matrix42.WebApi.Contracts;
using Matrix42.WebApi.Contracts.OData;
using update4u.SPS.Utility.GlobalConfiguration;
using C4IT.F4SDM;
using C4IT.FASD.Base;
using C4IT.Logging;
using static C4IT.FASD.Base.cF4SDTicket;
using static C4IT.Logging.cLogManager;
@@ -31,41 +35,33 @@ namespace C4IT.F4SD
public static F4SDM42WebApiController defaultInstance;
//private readonly IIncidentService _incidentService;
internal readonly GlobalConfigurationProvider _globalConfigurationProvider;
public readonly IJournalService _journalService;
//public readonly IObjectService _objectService;
//public readonly IFragmentService _fragmentService;
internal readonly IEntityDataService _entityDataService;
internal readonly IPandoraUserProfile _userProfile;
private readonly IDependencyResolver _resolver;
private readonly IEnumerationProvider _enumerationProvider;
private readonly F4SDHelperService _f4stHelperService;
public string BaseUrl => $"{Request.RequestUri.Scheme}://{Request.RequestUri.Host}";
public string BaseUrl => Request?.RequestUri == null ? string.Empty : $"{Request.RequestUri.Scheme}://{Request.RequestUri.Host}";
public string EndpointBaseUrl => $"{BaseUrl}/m42Services/api/c4itf4sdwebapi";
public F4SDM42WebApiController(
//IObjectService objectService,
//IIncidentService incidentService,
IJournalService journalService
//IFragmentService fragmentService,
, IEntityDataService entityDataService
, IPandoraUserProfile userProfile
)
public F4SDM42WebApiController(IDependencyResolver resolver, IEnumerationProvider enumerationProvider)
{
_resolver = resolver ?? throw new ArgumentNullException(nameof(resolver));
_enumerationProvider = enumerationProvider ?? throw new ArgumentNullException(nameof(enumerationProvider));
defaultInstance = this;
//_objectService = objectService;
//_fragmentService = fragmentService;
//_incidentService = Guard.NullArgument(incidentService, "incidentService");
_entityDataService = entityDataService;
_journalService = journalService;
_globalConfigurationProvider = GlobalConfigurationProvider.Instance;
_f4stHelperService = new F4SDHelperService();
_userProfile = userProfile;
EnsureInitialized();
}
private static object initLock = new object();
protected override void Initialize(HttpControllerContext controllerContext)
private static readonly object initLock = new object();
private static void EnsureInitialized()
{
base.Initialize(controllerContext);
try
{
//System.Diagnostics.Debugger.Launch();
@@ -85,6 +81,20 @@ namespace C4IT.F4SD
catch { };
}
internal IJournalService GetJournalService()
{
return GetRequiredService<IJournalService>();
}
private T GetRequiredService<T>() where T : class
{
var service = _resolver.TryGet<T>();
if (service != null)
return service;
throw new InvalidOperationException($"Required Matrix42 service is not registered: {typeof(T).FullName}");
}
[Route("getDirectLinkCreateTicket"), HttpGet]
public async Task<F4SDHelperService.DirectLink> getDirectLinkCreateTicket(string sid = "", string assetname = "")
{
@@ -98,20 +108,20 @@ namespace C4IT.F4SD
}
[Route("getTicketList"), HttpGet]
public async Task<List<cF4SDTicketSummary>> getTicketList(
string sid,
int hours,
int queueoption = 0,
string queues = ""
)
{
var decodedPairs = ParseQueues(queues);
// Nun weiterreichen an Service
return await _f4stHelperService.getTicketListByUser(
sid,
hours,
queueoption,
public async Task<List<cF4SDTicketSummary>> getTicketList(
string sid,
int hours,
int queueoption = 0,
string queues = ""
)
{
var decodedPairs = ParseQueues(queues);
// Nun weiterreichen an Service
return await _f4stHelperService.getTicketListByUser(
sid,
hours,
queueoption,
decodedPairs
);
}
@@ -127,73 +137,73 @@ namespace C4IT.F4SD
return null;
}
[Route("getTicketHistory"), HttpGet]
public async Task<List<cTicketJournalItem>> getTicketHistory(Guid objectId)
{
return await _f4stHelperService.GetJournalEntries(objectId);
}
[Route("getTicketOverviewCounts"), HttpGet]
public async Task<F4SDHelperService.TicketOverviewCountsResult> getTicketOverviewCounts(
string sid,
string scope = "personal",
string keys = "",
int queueoption = 0,
string queues = ""
)
{
var parsedKeys = (keys ?? string.Empty)
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(key => key.Trim())
.Where(key => !string.IsNullOrWhiteSpace(key))
.ToList();
var decodedQueues = ParseQueues(queues);
return await _f4stHelperService.getTicketOverviewCounts(sid, scope, parsedKeys, queueoption, decodedQueues);
}
[Route("getTicketOverviewCountsByRoles"), HttpPost]
public async Task<F4SDHelperService.TicketOverviewCountsByRoleResult> getTicketOverviewCountsByRoles([FromBody] TicketOverviewCountsByRolesRequest request)
{
var parsedKeys = (request?.Keys ?? new List<string>())
.Where(key => !string.IsNullOrWhiteSpace(key))
.Select(key => key.Trim())
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
var roleGuids = (request?.RoleGuids ?? new List<Guid>())
.Where(roleId => roleId != Guid.Empty)
.Distinct()
.ToList();
var decodedQueues = ParseQueues(request?.Queues ?? string.Empty);
return await _f4stHelperService.getTicketOverviewCountsByRoles(
request?.Sid,
roleGuids,
parsedKeys,
request?.QueueOption ?? 0,
decodedQueues
);
}
[Route("getTicketOverviewRelations"), HttpGet]
public async Task<List<F4SDHelperService.TicketOverviewRelationDto>> getTicketOverviewRelations(
string sid,
string scope = "personal",
string key = "",
int count = 0,
int queueoption = 0,
string queues = ""
)
{
var decodedQueues = ParseQueues(queues);
return await _f4stHelperService.getTicketOverviewRelations(sid, scope, key, count, queueoption, decodedQueues);
}
/*
[Route("updateActivitySolution/{objectId}"), HttpPost]
public async Task<HttpResponseMessage> updateActivitySolution(Guid objectId, [FromBody] string SolutionHtml)
{
return new HttpResponseMessage
[Route("getTicketHistory"), HttpGet]
public async Task<List<cTicketJournalItem>> getTicketHistory(Guid objectId)
{
return await _f4stHelperService.GetJournalEntries(objectId);
}
[Route("getTicketOverviewCounts"), HttpGet]
public async Task<F4SDHelperService.TicketOverviewCountsResult> getTicketOverviewCounts(
string sid,
string scope = "personal",
string keys = "",
int queueoption = 0,
string queues = ""
)
{
var parsedKeys = (keys ?? string.Empty)
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(key => key.Trim())
.Where(key => !string.IsNullOrWhiteSpace(key))
.ToList();
var decodedQueues = ParseQueues(queues);
return await _f4stHelperService.getTicketOverviewCounts(sid, scope, parsedKeys, queueoption, decodedQueues);
}
[Route("getTicketOverviewCountsByRoles"), HttpPost]
public async Task<F4SDHelperService.TicketOverviewCountsByRoleResult> getTicketOverviewCountsByRoles([FromBody] TicketOverviewCountsByRolesRequest request)
{
var parsedKeys = (request?.Keys ?? new List<string>())
.Where(key => !string.IsNullOrWhiteSpace(key))
.Select(key => key.Trim())
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
var roleGuids = (request?.RoleGuids ?? new List<Guid>())
.Where(roleId => roleId != Guid.Empty)
.Distinct()
.ToList();
var decodedQueues = ParseQueues(request?.Queues ?? string.Empty);
return await _f4stHelperService.getTicketOverviewCountsByRoles(
request?.Sid,
roleGuids,
parsedKeys,
request?.QueueOption ?? 0,
decodedQueues
);
}
[Route("getTicketOverviewRelations"), HttpGet]
public async Task<List<F4SDHelperService.TicketOverviewRelationDto>> getTicketOverviewRelations(
string sid,
string scope = "personal",
string key = "",
int count = 0,
int queueoption = 0,
string queues = ""
)
{
var decodedQueues = ParseQueues(queues);
return await _f4stHelperService.getTicketOverviewRelations(sid, scope, key, count, queueoption, decodedQueues);
}
/*
[Route("updateActivitySolution/{objectId}"), HttpPost]
public async Task<HttpResponseMessage> updateActivitySolution(Guid objectId, [FromBody] string SolutionHtml)
{
return new HttpResponseMessage
{
StatusCode = await _f4stHelperService.updateActivitySolution(objectId, SolutionHtml) ? HttpStatusCode.NoContent : HttpStatusCode.BadRequest,
};
@@ -201,10 +211,10 @@ namespace C4IT.F4SD
*/
[Route("getPickup/{name}"), HttpGet]
//[CacheOutput(UseETAG = true)]
public async Task<HttpResponseMessage> getPickup(string name, [FromUri] EntityEnumerationVisibilityMode mode = EntityEnumerationVisibilityMode.None, [FromUri] Int32 group = -1)
public async Task<HttpResponseMessage> getPickup(string name, EntityEnumerationVisibilityMode mode = EntityEnumerationVisibilityMode.None, Int32 group = -1)
{
await Task.Delay(0);
EntityEnumeration enumerationTemp = _entityDataService.GetEnumeration(name, mode);
EntityEnumeration enumerationTemp = GetEnumeration(name, mode);
var vals = enumerationTemp.Values;
if (group > -1)
@@ -227,35 +237,38 @@ namespace C4IT.F4SD
return Request.CreateResponse(HttpStatusCode.OK, enumeration);
}
[Route("getMyRoleMemberships", Order = 2), HttpGet]
public async Task<HttpResponseMessage> getMyRoleMemberships()
[Route("getMyRoleMemberships"), HttpGet]
public async Task<object> getMyRoleMemberships()
{
var User = _userProfile.GetInteractiveUserInfo();
var filter = AsqlHelper.BuildInCondition("ID", new Guid[] { User.Id });
return Request.CreateResponse(HttpStatusCode.OK, await _f4stHelperService.UserPermissionsInfo(filter));
var userId = GetCurrentUserId();
if (userId == Guid.Empty)
throw new UnauthorizedAccessException("Cannot determine the interactive Matrix42 user.");
var filter = AsqlHelper.BuildInCondition("ID", new Guid[] { userId });
return await _f4stHelperService.UserPermissionsInfo(filter);
}
[HttpGet]
[Route("getRoleMemberships", Order = 1)]
public async Task<HttpResponseMessage> getRoleMemberships([FromUri] GetRoleMembershipsRequest req)
[Route("getRoleMemberships")]
public async Task<object> getRoleMemberships(string sid = "", string upn = "", Guid? id = null)
{
var filter = "";
if (req.Id != null && req.Id.Value != Guid.Empty)
if (id != null && id.Value != Guid.Empty)
{
filter = AsqlHelper.BuildInCondition("ID", new Guid[] { req.Id.Value });
filter = AsqlHelper.BuildInCondition("ID", new Guid[] { id.Value });
}
else if (!string.IsNullOrEmpty(req.Sid))
else if (!string.IsNullOrEmpty(sid))
{
filter = AsqlHelper.BuildInCondition("Accounts.T(SPSAccountClassAD).Sid", new string[] { req.Sid });
filter = AsqlHelper.BuildInCondition("Accounts.T(SPSAccountClassAD).Sid", new string[] { sid });
}
else if (!string.IsNullOrEmpty(req.Upn))
else if (!string.IsNullOrEmpty(upn))
{
filter = AsqlHelper.BuildInCondition("Accounts.T(SPSAccountClassAD).UserPrincipalName", new string[] { req.Upn });
filter = AsqlHelper.BuildInCondition("Accounts.T(SPSAccountClassAD).UserPrincipalName", new string[] { upn });
}
if (!string.IsNullOrEmpty(filter))
{
return Request.CreateResponse(HttpStatusCode.OK, await _f4stHelperService.UserPermissionsInfo(filter));
return await _f4stHelperService.UserPermissionsInfo(filter);
}
else
{
@@ -263,43 +276,124 @@ namespace C4IT.F4SD
}
}
public class GetRoleMembershipsRequest
{
public Guid? Id { get; set; }
public string Sid { get; set; }
public string Upn { get; set; }
public GetRoleMembershipsRequest() { }
}
public class TicketOverviewCountsByRolesRequest
{
public string Sid { get; set; }
public List<Guid> RoleGuids { get; set; } = new List<Guid>();
public List<string> Keys { get; set; } = new List<string>();
public int? QueueOption { get; set; }
public string Queues { get; set; }
}
private static List<cApiM42TicketQueueInfo> ParseQueues(string queues)
{
return (queues ?? string.Empty)
.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
.Select(part =>
{
var segments = part.Split(':');
if (segments.Length != 2)
return null;
var name = HttpUtility.UrlDecode(segments[0]);
var idStr = HttpUtility.UrlDecode(segments[1]);
return Guid.TryParse(idStr, out var guid)
? new cApiM42TicketQueueInfo { QueueName = name, QueueID = guid }
: null;
})
.Where(q => q != null)
.ToList();
}
public class TicketOverviewCountsByRolesRequest
{
public string Sid { get; set; }
public List<Guid> RoleGuids { get; set; } = new List<Guid>();
public List<string> Keys { get; set; } = new List<string>();
public int? QueueOption { get; set; }
public string Queues { get; set; }
}
private EntityEnumeration GetEnumeration(string name, EntityEnumerationVisibilityMode mode)
{
name = name?.Trim();
var dataTable = _enumerationProvider.GetEnumeration(name, GetVisibilityFilter(mode));
if (dataTable == null)
throw new InvalidOperationException($"Enumeration '{name}' was not found.");
var valueColumn = FindColumn(dataTable, "Value") ?? FindNumericColumn(dataTable);
if (string.IsNullOrEmpty(valueColumn))
throw new InvalidOperationException($"Enumeration '{name}' does not contain a numeric value column.");
var displayColumn = FindColumn(dataTable, "DisplayString")
?? FindColumn(dataTable, "DisplayExpression")
?? FindColumn(dataTable, "Name")
?? valueColumn;
var hiddenColumn = FindColumn(dataTable, "Hidden");
var values = dataTable.Rows.Cast<DataRow>()
.Select(row => new EntityEnumerationValue
{
Value = ConvertHelper.ParseInt(row[valueColumn], 0),
DisplayString = Convert.ToString(row[displayColumn]),
Hidden = hiddenColumn != null && ConvertHelper.ParseInt(row[hiddenColumn], 0) == 1,
Extentions = dataTable.Columns.Cast<DataColumn>()
.ToDictionary(column => column.ColumnName, column => row[column])
})
.ToArray();
return new EntityEnumeration
{
Name = name,
Values = values
};
}
private static bool? GetVisibilityFilter(EntityEnumerationVisibilityMode mode)
{
switch (mode)
{
case EntityEnumerationVisibilityMode.Visible:
return true;
case EntityEnumerationVisibilityMode.Hidden:
return false;
default:
return null;
}
}
private static string FindColumn(DataTable dataTable, string columnName)
{
return dataTable.Columns.Cast<DataColumn>()
.FirstOrDefault(column => string.Equals(column.ColumnName, columnName, StringComparison.OrdinalIgnoreCase))
?.ColumnName;
}
private static string FindNumericColumn(DataTable dataTable)
{
var excludedNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"Hidden",
"Position",
"StateGroup"
};
return dataTable.Columns.Cast<DataColumn>()
.FirstOrDefault(column => !excludedNames.Contains(column.ColumnName) && IsNumericType(column.DataType))
?.ColumnName;
}
private static bool IsNumericType(Type type)
{
return type == typeof(byte)
|| type == typeof(short)
|| type == typeof(int)
|| type == typeof(long)
|| type == typeof(sbyte)
|| type == typeof(ushort)
|| type == typeof(uint)
|| type == typeof(ulong);
}
private static Guid GetCurrentUserId()
{
var principal = Thread.CurrentPrincipal as IM42Principal;
return principal?.InteractivePrincipal?.M42Identity?.UserFragmentID
?? principal?.M42Identity?.UserFragmentID
?? Guid.Empty;
}
private static List<cApiM42TicketQueueInfo> ParseQueues(string queues)
{
return (queues ?? string.Empty)
.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
.Select(part =>
{
var segments = part.Split(':');
if (segments.Length != 2)
return null;
var name = WebUtility.UrlDecode(segments[0]);
var idStr = WebUtility.UrlDecode(segments[1]);
return Guid.TryParse(idStr, out var guid)
? new cApiM42TicketQueueInfo { QueueName = name, QueueID = guid }
: null;
})
.Where(q => q != null)
.ToList();
}
[Route("isAlive"), HttpGet]
public HttpResponseMessage isAlive()
@@ -350,10 +444,9 @@ namespace C4IT.F4SD
public static bool IsInitialized { get; private set; } = false;
private static object initLock = new object();
protected override void Initialize(HttpControllerContext controllerContext)
private static readonly object initLock = new object();
private static void EnsureInitialized()
{
base.Initialize(controllerContext);
try
{
lock (initLock)
@@ -377,20 +470,15 @@ namespace C4IT.F4SD
public F4SDM42LogsWebApiController()
{
_f4stHelperService = new F4SDHelperService();
EnsureInitialized();
}
[Route(""), HttpGet]
[EnableQuery]
public IEnumerable<cM42LogEntry> getLog2(ODataQueryOptions<cM42LogEntry> queryOptions)
{
try
{
IQueryable<cM42LogEntry> queryable = _f4stHelperService.privGetLog2().AsQueryable();
if (queryOptions.Filter != null)
{
queryable = queryOptions.Filter.ApplyTo(queryable, new ODataQuerySettings()).Cast<cM42LogEntry>();
}
return queryable;
return ApplyLogFilter(_f4stHelperService.privGetLog2(), queryOptions);
}
catch (Exception E)
{
@@ -403,12 +491,7 @@ namespace C4IT.F4SD
[HttpGet]
public int Log2Count(ODataQueryOptions<cM42LogEntry> queryOptions)
{
IQueryable<cM42LogEntry> queryable = _f4stHelperService.privGetLog2().AsQueryable();
if (queryOptions.Filter != null)
{
queryable = queryOptions.Filter.ApplyTo(queryable, new ODataQuerySettings()).Cast<cM42LogEntry>();
}
return queryable.Count();
return ApplyLogFilter(_f4stHelperService.privGetLog2(), queryOptions).Count();
}
[Route("{id}")]
@@ -417,6 +500,22 @@ namespace C4IT.F4SD
{
return _f4stHelperService.privGetLog2(id);
}
private static IEnumerable<cM42LogEntry> ApplyLogFilter(IEnumerable<cM42LogEntry> entries, ODataQueryOptions<cM42LogEntry> queryOptions)
{
var result = entries ?? Enumerable.Empty<cM42LogEntry>();
var filter = queryOptions?.Filter;
if (!string.IsNullOrWhiteSpace(filter))
{
result = result.Where(entry =>
(entry.Message?.IndexOf(filter, StringComparison.OrdinalIgnoreCase) ?? -1) >= 0 ||
(entry.logLvl?.IndexOf(filter, StringComparison.OrdinalIgnoreCase) ?? -1) >= 0 ||
(entry.Theme?.IndexOf(filter, StringComparison.OrdinalIgnoreCase) ?? -1) >= 0);
}
return result;
}
}
public class cGetPropertyBody

View File

@@ -0,0 +1,28 @@
.idea/
.vs/
.vscode/
node_modules/
dist/
package-lock.json
workspace.json
.vs/
.libs/
.build/
bin/
obj/
*.user
/**/AssemblyInfoProduct.cs
/**/_Resharper.Caches
/Solutions/*.sln.DotSettings.user
/Solutions/**/*.csproj.user
/Solutions/**/packages/*.*
/Solutions/**/**/AssemblyInfoProduct.cs
/Solutions/TestResult/*.*
BasePackage/Assemblies/

View File

@@ -0,0 +1,14 @@
# M42ExtensionScaffolder
## Description
Template project acting as an extension scaffolder. Automatically generate extension structure including provided projects and runtimes
## Owner
Name: Titan
Email: dmytro.romanii@matrix42.com
## Product
M42 Enterprise
## Documentation
- [Service Information](./service-info.yml)

View File

@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Matrix42.Extensions.Scaffolder.Core\Matrix42.Extensions.Scaffolder.Core.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,61 @@
using Matrix42.Extensions.Scaffolder.Core;
using Matrix42.Extensions.Scaffolder.Core.Project;
using Microsoft.Extensions.Configuration;
namespace Matrix42.Extensions.Scaffolder.ConsoleApp
{
internal class Program
{
static void Main(string[] args)
{
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory()) // required for appsettings.json
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.Build();
var extensionPath = config["Scaffolder:ExtensionPath"];
var extensionIdStr = config["Scaffolder:ExtensionId"];
var extensionNamespace = config["Scaffolder:ExtensionNamespace"];
if (string.IsNullOrWhiteSpace(extensionPath))
throw new Exception("Scaffolder:ExtensionPath is missing in appsettings.json");
var extensionId = Guid.TryParse(extensionIdStr, out var gid) ? gid : Guid.NewGuid();
var options = new ExtensionScaffoldingOptions
{
ExtensionPath = extensionPath,
ExtensionId = extensionId,
ExtensionNamespace = extensionNamespace,
Projects = [
new ProjectDefinition
{
Prefix = "Services",
Runtimes = RuntimeTargets.WinCore | RuntimeTargets.LinuxCore,
Functionality = ProjectFunctionality.WebApi
},
new ProjectDefinition
{
Prefix = "Contracts",
Runtimes = RuntimeTargets.All,
Functionality = ProjectFunctionality.Custom
},
new ProjectDefinition
{
Prefix = "BizLogic",
Runtimes = RuntimeTargets.All,
Functionality = ProjectFunctionality.Custom
}
]
};
var scaffolder = new ExtensionScaffolder();
var projects = scaffolder.Generate(options);
Console.WriteLine("Generated projects:");
foreach (var p in projects)
Console.WriteLine(" - " + p);
}
}
}

View File

@@ -0,0 +1,7 @@
{
"Scaffolder": {
"ExtensionPath": "C:\\Projects\\Matrix42\\Titan_Linux\\ESM-FieldServiceManagement-Extension_demo",
"ExtensionId": "c55407f9-9b46-4483-8b65-1bc1c3827efc",
"ExtensionNamespace": "Matrix42.FieldServiceManagement"
}
}

View File

@@ -0,0 +1,206 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!--
Layout assumptions
This .targets file lives in:
{ExtensionPath}\Extension.Assemblies.targets
We want to copy to:
BasePackage\Assemblies\
→ {ExtensionPath}\BasePackage\Assemblies\
Root\InstalledPackages\Assemblies\{ExtensionId}\
→ {ExtensionPath}\..\Root\InstalledPackages\Assemblies\{ExtensionId}\
Properties:
- M42BasePackageAssembliesDir (can override in csproj)
- M42RootAssembliesDir (needs M42ExtensionId)
- M42AssemblyPattern (default: $(MSBuildProjectName)*.*)
- M42DefaultRids (default: win-x64;linux-x64)
- M42ExtensionId (set in csproj)
- M42EnableRidBuild (true for net8.0 w/ RIDs, false for netstandard)
-->
<PropertyGroup>
<!-- BasePackage under extension root -->
<M42BasePackageAssembliesDir Condition="'$(M42BasePackageAssembliesDir)' == ''">$(MSBuildThisFileDirectory)BasePackage\Assemblies\</M42BasePackageAssembliesDir>
<!-- Root one level above extension root -->
<M42RootAssembliesDir Condition="'$(M42RootAssembliesDir)' == '' AND '$(M42ExtensionId)' != ''">$(MSBuildThisFileDirectory)..\Root\InstalledPackages\Assemblies\$(M42ExtensionId)\</M42RootAssembliesDir>
<!-- Pattern & default RIDs -->
<M42AssemblyPattern Condition="'$(M42AssemblyPattern)' == ''">$(MSBuildProjectName)*.*</M42AssemblyPattern>
<M42DefaultRids Condition="'$(M42DefaultRids)' == ''">win-x64;linux-x64</M42DefaultRids>
<!-- Enable multi-RID builds per project; can be set to false in csproj -->
<M42EnableRidBuild Condition="'$(M42EnableRidBuild)' == ''">true</M42EnableRidBuild>
</PropertyGroup>
<!--
M42_BuildAllRids
Local only: if no RID was specified AND we're not in Azure DevOps,
build all default RIDs (M42DefaultRids) by re-invoking MSBuild
with RuntimeIdentifier and AppendRuntimeIdentifierToOutputPath=true.
This prevents the outer RID-less build by setting SkipBuild=true.
Skipped when M42EnableRidBuild=false (e.g. netstandard projects).
-->
<Target Name="M42_BuildAllRids"
BeforeTargets="Build"
Condition="'$(RuntimeIdentifier)' == ''
AND ( '$(TF_BUILD)' != 'True' OR '$(M42BuildAllRidsOnCI)' == 'true' )
AND '$(M42EnableRidBuild)' == 'true'">
<ItemGroup>
<_M42RidList Include="$(M42DefaultRids)" />
</ItemGroup>
<MSBuild Projects="$(MSBuildProjectFullPath)"
Targets="Restore;Build"
Properties="RuntimeIdentifier=%(_M42RidList.Identity);Configuration=$(Configuration);AppendRuntimeIdentifierToOutputPath=true"
BuildInParallel="true" />
<PropertyGroup>
<SkipBuild>true</SkipBuild>
</PropertyGroup>
</Target>
<!--
M42_PostBuild_CopyRidless
For RID-less builds (RuntimeIdentifier == ''), copy top-level output files
that match M42AssemblyPattern directly from $(TargetDir) to:
1) BasePackage\Assemblies\
2) Root\InstalledPackages\Assemblies\{ExtId}\ (if configured)
Useful for projects that don't support RID-specific builds (e.g. netstandard),
or when M42EnableRidBuild=false.
-->
<Target Name="M42_PostBuild_CopyRidless"
AfterTargets="Build"
Condition="'$(RuntimeIdentifier)' == ''">
<Message Text="M42: RID-less copy from $(TargetDir) → $(M42BasePackageAssembliesDir)" Importance="high" />
<ItemGroup>
<_M42FlatRootRidless Include="$(TargetDir)$(M42AssemblyPattern)" />
</ItemGroup>
<!-- BasePackage flat copy -->
<MakeDir Directories="$(M42BasePackageAssembliesDir)"
Condition="!Exists('$(M42BasePackageAssembliesDir)')" />
<Copy
SourceFiles="@(_M42FlatRootRidless)"
DestinationFolder="$(M42BasePackageAssembliesDir)"
SkipUnchangedFiles="true"
Condition="Exists('$(TargetDir)')" />
<!-- Root flat copy (if configured) -->
<Message Text="M42: RID-less copy to Root → $(M42RootAssembliesDir)" Importance="high"
Condition="'$(M42RootAssembliesDir)' != ''" />
<MakeDir Directories="$(M42RootAssembliesDir)"
Condition="'$(M42RootAssembliesDir)' != '' AND !Exists('$(M42RootAssembliesDir)')" />
<Copy
SourceFiles="@(_M42FlatRootRidless)"
DestinationFolder="$(M42RootAssembliesDir)"
SkipUnchangedFiles="true"
Condition="'$(M42RootAssembliesDir)' != '' AND Exists('$(TargetDir)')" />
</Target>
<!--
M42_PostBuild_CopyRID
After each RID-specific build, copy assemblies to:
1) BasePackage\Assemblies\ (FLAT, from RID-less bin\...\netX\)
2) BasePackage\Assemblies\<RID>\... (RECURSIVE, from current RID build)
And, if M42RootAssembliesDir is set:
3) Root\InstalledPackages\Assemblies\{ExtId}\ (FLAT)
4) Root\InstalledPackages\Assemblies\{ExtId}\<RID>\ (RECURSIVE)
-->
<Target Name="M42_PostBuild_CopyRID"
AfterTargets="Build"
Condition="'$(RuntimeIdentifier)' != ''">
<Message Text="M42: RID=$(RuntimeIdentifier) TargetDir=$(TargetDir)" Importance="high" />
<!-- Parent of ...\netX\<rid>\ -->
<PropertyGroup>
<M42_RidlessTargetDir>$([System.IO.Path]::GetFullPath('$(TargetDir)..\'))</M42_RidlessTargetDir>
</PropertyGroup>
<!-- 1) FLAT copy from rid-less netX to BasePackage\Assemblies -->
<Message Text="M42: flat copy from $(M42_RidlessTargetDir) → $(M42BasePackageAssembliesDir)" Importance="high" />
<ItemGroup>
<_M42FlatRoot Include="$(M42_RidlessTargetDir)$(M42AssemblyPattern)" />
</ItemGroup>
<MakeDir Directories="$(M42BasePackageAssembliesDir)"
Condition="!Exists('$(M42BasePackageAssembliesDir)')" />
<Copy
SourceFiles="@(_M42FlatRoot)"
DestinationFolder="$(M42BasePackageAssembliesDir)"
SkipUnchangedFiles="true"
Condition="Exists('$(M42_RidlessTargetDir)')" />
<!-- 1b) FLAT copy to Root if configured -->
<Message Text="M42: flat copy to Root → $(M42RootAssembliesDir)" Importance="high"
Condition="'$(M42RootAssembliesDir)' != ''" />
<MakeDir Directories="$(M42RootAssembliesDir)"
Condition="'$(M42RootAssembliesDir)' != '' AND !Exists('$(M42RootAssembliesDir)')" />
<Copy
SourceFiles="@(_M42FlatRoot)"
DestinationFolder="$(M42RootAssembliesDir)"
SkipUnchangedFiles="true"
Condition="'$(M42RootAssembliesDir)' != '' AND Exists('$(M42_RidlessTargetDir)')" />
<!-- 2) RID copy to BasePackage\Assemblies\<RID>\... -->
<Message Text="M42: RID copy → $(M42BasePackageAssembliesDir)$(RuntimeIdentifier)\" Importance="high" />
<ItemGroup>
<_M42Rid Include="$(TargetDir)**\$(M42AssemblyPattern)" />
</ItemGroup>
<MakeDir Directories="$(M42BasePackageAssembliesDir)$(RuntimeIdentifier)"
Condition="!Exists('$(M42BasePackageAssembliesDir)$(RuntimeIdentifier)')" />
<Copy
SourceFiles="@(_M42Rid)"
DestinationFolder="$(M42BasePackageAssembliesDir)$(RuntimeIdentifier)\%(RecursiveDir)"
SkipUnchangedFiles="true" />
<!-- 2b) RID copy to Root\...\<RID>\... -->
<Message Text="M42: RID copy to Root → $(M42RootAssembliesDir)$(RuntimeIdentifier)\" Importance="high"
Condition="'$(M42RootAssembliesDir)' != ''" />
<MakeDir Directories="$(M42RootAssembliesDir)$(RuntimeIdentifier)"
Condition="'$(M42RootAssembliesDir)' != '' AND !Exists('$(M42RootAssembliesDir)$(RuntimeIdentifier)')" />
<Copy
SourceFiles="@(_M42Rid)"
DestinationFolder="$(M42RootAssembliesDir)$(RuntimeIdentifier)\%(RecursiveDir)"
SkipUnchangedFiles="true"
Condition="'$(M42RootAssembliesDir)' != ''" />
</Target>
</Project>

View File

@@ -0,0 +1,232 @@
using Matrix42.Extensions.Scaffolder.Core.ProjectGenerators;
using System;
using System.Collections.Generic;
using System.IO;
namespace Matrix42.Extensions.Scaffolder.Core;
public sealed class ExtensionScaffolder : IExtensionScaffolder
{
private readonly DefaultProjectGenerator _projectGenerator = new();
public IReadOnlyList<string> Generate(ExtensionScaffoldingOptions options)
{
if (options is null)
throw new ArgumentNullException(nameof(options));
if (string.IsNullOrWhiteSpace(options.ExtensionPath)) throw new ArgumentException("ExtensionPath must be set.", nameof(options));
if (string.IsNullOrWhiteSpace(options.ExtensionNamespace)) throw new ArgumentException("ExtensionNamespace must be set.", nameof(options));
if (options.Projects == null) throw new ArgumentException("Projects must be set.", nameof(options));
var result = new List<string>();
PrepareRootStructure(options);
PrepareBasePackage(options);
var solutionDir = PrepareSolutionFolder(options);
PrepareRootArtifacts(options);
PrepareSolutionFiles(options, solutionDir);
foreach (var def in options.Projects)
{
var csprojPath = _projectGenerator.GenerateProject(options, def);
result.Add(csprojPath);
}
GenerateSolutionFile(options, solutionDir, result);
return result;
}
private void PrepareRootStructure(ExtensionScaffoldingOptions options)
{
Directory.CreateDirectory(options.ExtensionPath);
}
private void PrepareBasePackage(ExtensionScaffoldingOptions options)
{
var basePackageDir = Path.Combine(options.ExtensionPath, "BasePackage");
Directory.CreateDirectory(basePackageDir);
var packageJsonPath = Path.Combine(basePackageDir, "package.json");
if (!File.Exists(packageJsonPath))
{
var json = GetDefaultPackageJson(options);
File.WriteAllText(packageJsonPath, json);
}
}
/// <summary>
/// Creates /Solutions/{ns} and returns that path (solution-level folder).
/// </summary>
private string PrepareSolutionFolder(ExtensionScaffoldingOptions options)
{
var solutionDir = Path.Combine(options.ExtensionPath, "Solutions", options.ExtensionNamespace);
Directory.CreateDirectory(solutionDir);
return solutionDir;
}
private void PrepareRootArtifacts(ExtensionScaffoldingOptions options)
{
/*var azurePath = Path.Combine(options.ExtensionPath, "azure-pipelines-ci.yml");
if (!File.Exists(azurePath))
{
File.WriteAllText(azurePath, Templates.AzurePipelinesYml);
}*/
var azurePath = Path.Combine(options.ExtensionPath, "azure-pipelines-ci.yaml");
if (!File.Exists(azurePath))
{
var repoName = new DirectoryInfo(options.ExtensionPath).Name;
var content = Templates.GetAzurePiplineCi();
var replacedContent = content.Replace("{repoName}", repoName).Replace("{ns}", options.ExtensionNamespace);
File.WriteAllText(azurePath, replacedContent);
}
var gitignorePath = Path.Combine(options.ExtensionPath, ".gitignore");
if (!File.Exists(gitignorePath))
{
File.WriteAllText(gitignorePath, Templates.Gitignore);
}
/*var veracodePath = Path.Combine(options.ExtensionPath, "PrepareVeracodeScan.cmd");
if (!File.Exists(veracodePath))
{
var content = Templates.PrepareVeracodeScanCmd.Replace("{ns}", options.ExtensionNamespace);
File.WriteAllText(veracodePath, content);
}*/
var targetsPath = Path.Combine(options.ExtensionPath, "Extension.Assemblies.targets");
if (!File.Exists(targetsPath))
{
var content = Templates.GetMatrix42AssembliesTargets();
File.WriteAllText(targetsPath, content);
}
}
/// <summary>
/// Solution-level files that should live next to the .sln (e.g. BuildEvent.cmd).
/// </summary>
private void PrepareSolutionFiles(ExtensionScaffoldingOptions options, string solutionDir)
{
var buildEventPath = Path.Combine(solutionDir, "BuildEvent.cmd");
if (!File.Exists(buildEventPath))
{
var content = Templates.BuildEventCmdTemplate.Replace("{ns}", options.ExtensionNamespace);
File.WriteAllText(buildEventPath, content);
}
// If later you want to actually create the .sln here (for console scenario),
// this is the right place as well.
}
private static string GetDefaultPackageJson(ExtensionScaffoldingOptions options) =>
$@"{{
""Id"": ""{options.ExtensionId}"",
""Vendor"": ""Not Specified"",
""Name"": ""{options.ExtensionNamespace}"",
""Version"": ""1.0"",
""Prerequisites"": {{
""MinimalRequiredProductVersion"": ""26.1.0"",
""DependentPackages"": []
}},
""SetupDirectives"": {{
""MaintenanceMode"": false,
""RecycleWebApplication"": false,
""RestartM42WindowsServices"": false
}},
""Sandboxed"": true
}}";
/// <summary>
/// Creates {ns}.sln under solutionDir and adds all csproj files to it.
/// </summary>
private void GenerateSolutionFile(
ExtensionScaffoldingOptions options,
string solutionDir,
IReadOnlyList<string> projectPaths)
{
if (projectPaths == null || projectPaths.Count == 0)
return;
var solutionName = options.ExtensionNamespace;
var slnPath = Path.Combine(solutionDir, solutionName + ".sln");
// Basic VS solution header
var lines = new List<string>();
lines.Add("Microsoft Visual Studio Solution File, Format Version 12.00");
lines.Add("# Visual Studio Version 17");
lines.Add("VisualStudioVersion = 17.0.31912.275");
lines.Add("MinimumVisualStudioVersion = 10.0.40219.1");
// C# project type GUID
const string csProjTypeGuid = "{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}";
// Collect project GUIDs
var projectEntries = new List<Tuple<string, string, string>>();
// (projGuid, projName, relativePath)
foreach (var projPath in projectPaths)
{
var projName = Path.GetFileNameWithoutExtension(projPath);
var relativePath = GetRelativePath(solutionDir, projPath);
var projGuid = "{" + Guid.NewGuid().ToString().ToUpperInvariant() + "}";
projectEntries.Add(Tuple.Create(projGuid, projName, relativePath));
lines.Add(string.Format(
"Project(\"{0}\") = \"{1}\", \"{2}\", \"{3}\"",
csProjTypeGuid,
projName,
relativePath,
projGuid));
lines.Add("EndProject");
}
// Global section (configs + project configs)
lines.Add("Global");
lines.Add("\tGlobalSection(SolutionConfigurationPlatforms) = preSolution");
lines.Add("\t\tDebug|Any CPU = Debug|Any CPU");
lines.Add("\t\tRelease|Any CPU = Release|Any CPU");
lines.Add("\tEndGlobalSection");
lines.Add("\tGlobalSection(ProjectConfigurationPlatforms) = postSolution");
foreach (var entry in projectEntries)
{
var guid = entry.Item1;
lines.Add(string.Format("\t\t{0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU", guid));
lines.Add(string.Format("\t\t{0}.Debug|Any CPU.Build.0 = Debug|Any CPU", guid));
lines.Add(string.Format("\t\t{0}.Release|Any CPU.ActiveCfg = Release|Any CPU", guid));
lines.Add(string.Format("\t\t{0}.Release|Any CPU.Build.0 = Release|Any CPU", guid));
}
lines.Add("\tEndGlobalSection");
lines.Add("\tGlobalSection(SolutionProperties) = preSolution");
lines.Add("\t\tHideSolutionNode = FALSE");
lines.Add("\tEndGlobalSection");
lines.Add("EndGlobal");
File.WriteAllLines(slnPath, lines);
}
/// <summary>
/// Simple helper to get relative path from one folder to a file.
/// </summary>
private static string GetRelativePath(string basePath, string fullPath)
{
var baseUri = new Uri(AppendDirectorySeparatorChar(basePath));
var fullUri = new Uri(fullPath);
var relative = baseUri.MakeRelativeUri(fullUri).ToString();
return relative.Replace('/', Path.DirectorySeparatorChar);
}
private static string AppendDirectorySeparatorChar(string path)
{
return !path.EndsWith(Path.DirectorySeparatorChar.ToString()) ? path + Path.DirectorySeparatorChar : path;
}
}

View File

@@ -0,0 +1,30 @@
using Matrix42.Extensions.Scaffolder.Core.Project;
using System;
using System.Collections.Generic;
namespace Matrix42.Extensions.Scaffolder.Core
{
public sealed class ExtensionScaffoldingOptions
{
/// <summary>
/// Absolute path where scaffolding should be created.
/// Typically solution directory from VS or some folder from CLI.
/// </summary>
public string ExtensionPath { get; set; }
/// <summary>
/// Extension id (Guid) used in package.json or other metadata.
/// </summary>
public Guid ExtensionId { get; set; }
/// <summary>
/// Root namespace, e.g. "Matrix42.MyAwesomeExtension".
/// </summary>
public string ExtensionNamespace { get; set; }
/// <summary>
/// Projects to create.
/// </summary>
public IReadOnlyList<ProjectDefinition> Projects { get; set; } = Array.Empty<ProjectDefinition>();
}
}

View File

@@ -0,0 +1,20 @@
using Matrix42.Extensions.Scaffolder.Core.Project;
using System.Collections.Generic;
namespace Matrix42.Extensions.Scaffolder.Core.Functionality
{
public interface IProjectFunctionalityDefinition
{
ProjectFunctionality Kind { get; }
/// <summary>
/// Files to create for this functionality (relative paths, template content).
/// </summary>
IEnumerable<TemplateFile> GetFiles(ExtensionScaffoldingOptions options, ProjectDefinition project);
/// <summary>
/// References to add to the csproj.
/// </summary>
IEnumerable<ProjectReferenceDefinition> GetReferences(ExtensionScaffoldingOptions options, ProjectDefinition project);
}
}

View File

@@ -0,0 +1,65 @@
using Matrix42.Extensions.Scaffolder.Core.Project;
using System.Collections.Generic;
namespace Matrix42.Extensions.Scaffolder.Core.Functionality
{
public sealed class WebApiFunctionalityDefinition : IProjectFunctionalityDefinition
{
public ProjectFunctionality Kind => ProjectFunctionality.WebApi;
public IEnumerable<TemplateFile> GetFiles(
ExtensionScaffoldingOptions options,
ProjectDefinition project)
{
var ns = options.ExtensionNamespace;
yield return new TemplateFile
{
RelativePath = @"Controllers\TestController.cs",
Content = WebApi_TestControllerTemplate.Replace("{ns}", ns),
CreateIfMissingOnly = true
};
}
public IEnumerable<ProjectReferenceDefinition> GetReferences(ExtensionScaffoldingOptions options, ProjectDefinition project)
{
yield return new ProjectReferenceDefinition
{
Include = "Matrix42.WebApi.Contracts",
HintPath = @"..\..\..\..\Root\bin\Matrix42.WebApi.Contracts.dll"
};
yield return new ProjectReferenceDefinition
{
Include = "Matrix42.Hosting.Contracts",
HintPath = @"..\..\..\..\Root\bin\Matrix42.Hosting.Contracts.dll"
};
}
private const string WebApi_TestControllerTemplate =
@"
using Matrix42.WebApi.Contracts;
using Matrix42.Hosting.Contracts;
namespace {ns}.Services
{
[RoutePrefix(""api/test"")]
public class TestController : ApiController
{
private readonly IDependencyResolver _resolver;
public TestController(IDependencyResolver resolver)
{
_resolver = resolver;
}
[HttpPost, Route(""test"")]
public string TestMethod()
{
// TODO: implement your test method logic
return ""OK"";
}
}
}";
}
}

View File

@@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace Matrix42.Extensions.Scaffolder.Core;
public interface IExtensionScaffolder
{
/// <summary>
/// Generates all files/folders according to options and returns the list of generated csproj paths.
/// </summary>
IReadOnlyList<string> Generate(ExtensionScaffoldingOptions options);
}

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>11.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Extension.Assemblies.targets" />
<EmbeddedResource Include="azure-pipelines-ci.yaml" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,54 @@
using System.Collections.Generic;
namespace Matrix42.Extensions.Scaffolder.Core.Project;
public class ProjectDefinition
{
/// <summary>
/// E.g. "Services", "BizLogic", etc.
/// If empty, project name will be equal to ExtensionNamespace.
/// </summary>
public string Prefix { get; set; } = string.Empty;
public RuntimeTargets Runtimes { get; set; }
/// <summary>
/// Combination of functionality flags: WebApi, Engine, Behavior, Connector, Custom.
/// </summary>
public ProjectFunctionality Functionality { get; set; } = ProjectFunctionality.None;
public string Display => $"Prefix: {Prefix} - Runtimes: {Runtimes} - Functionality: {Functionality}";
public string GetProjectName(string extensionNamespace)
{
var pfx = Prefix?.Trim();
return string.IsNullOrWhiteSpace(pfx)
? extensionNamespace
: $"{extensionNamespace}.{pfx}";
}
public string GetNamespace(string extensionNamespace) => GetProjectName(extensionNamespace);
public bool DoesSupportAllRuntimes() => Runtimes.HasFlag(RuntimeTargets.All);
public string GetRuntimeIdentifiers()
{
if (DoesSupportAllRuntimes())
{
return "netstandard2.0";
}
var list = new List<string>();
if (Runtimes.HasFlag(RuntimeTargets.Win) || Runtimes.HasFlag(RuntimeTargets.WinCore))
{
list.Add("win-x64");
}
if (Runtimes.HasFlag(RuntimeTargets.LinuxCore))
{
list.Add("linux-x64");
}
return string.Join(";", list);
}
}

View File

@@ -0,0 +1,23 @@
using System;
namespace Matrix42.Extensions.Scaffolder.Core.Project;
[Flags]
public enum ProjectFunctionality
{
None = 0,
WebApi = 1,
Engine = 2,
Behavior = 4,
Connector = 8,
Custom = 16
}
[Flags]
public enum RuntimeTargets
{
Win = 1,
WinCore = 2,
LinuxCore = 4,
All = 8
}

View File

@@ -0,0 +1,11 @@
namespace Matrix42.Extensions.Scaffolder.Core.Project
{
/// <summary>
/// Describes a single extra reference to put in the csproj.
/// </summary>
public sealed class ProjectReferenceDefinition
{
public string Include { get; set; }
public string HintPath { get; set; }
}
}

View File

@@ -0,0 +1,200 @@
using Matrix42.Extensions.Scaffolder.Core.Functionality;
using Matrix42.Extensions.Scaffolder.Core.Project;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace Matrix42.Extensions.Scaffolder.Core.ProjectGenerators
{
internal class DefaultProjectGenerator
{
private readonly IReadOnlyList<IProjectFunctionalityDefinition> _functionalityDefinitions;
public DefaultProjectGenerator()
{
_functionalityDefinitions = new IProjectFunctionalityDefinition[]
{
new WebApiFunctionalityDefinition(),
// new EngineFunctionalityDefinition(),
// new BehaviorFunctionalityDefinition(),
// new ConnectorFunctionalityDefinition(),
// new CustomFunctionalityDefinition(),
};
}
public string GenerateProject(ExtensionScaffoldingOptions options, ProjectDefinition def)
{
var ns = options.ExtensionNamespace;
var projectName = def.GetProjectName(ns); // e.g. Matrix42.MyExt.Services
var solutionDir = Path.Combine(options.ExtensionPath, "Solutions", options.ExtensionNamespace);
Directory.CreateDirectory(solutionDir);
var projectDir = Path.Combine(solutionDir, projectName);
Directory.CreateDirectory(projectDir);
var csprojPath = Path.Combine(projectDir, $"{projectName}.csproj");
GenerateCsproj(csprojPath, def, options, projectName, ns, CollectFunctionalityReferences(options, def));
GenerateHostFile(projectDir, projectName);
GenerateFiles(projectDir, CollectFunctionalityFiles(options, def));
return csprojPath;
}
#region Csproj / Host
protected virtual void GenerateCsproj(
string csprojPath,
ProjectDefinition def,
ExtensionScaffoldingOptions options,
string projectName,
string rootNamespace,
IReadOnlyList<ProjectReferenceDefinition> extraReferences)
{
var allRuntimes = def.DoesSupportAllRuntimes();
var tfm = allRuntimes ? def.GetRuntimeIdentifiers() : "net8.0"; // Todo: dynamic based on runtimes
var runtimeIds = def.GetRuntimeIdentifiers();
var anyRids = !allRuntimes && !string.IsNullOrWhiteSpace(runtimeIds);
var ridBlock = anyRids ? $"<RuntimeIdentifiers>{runtimeIds}</RuntimeIdentifiers>{Environment.NewLine}" : string.Empty;
var referencesXml = BuildReferencesXml(extraReferences);
var content =
$@"<Project Sdk=""Microsoft.NET.Sdk"">
<PropertyGroup>
<TargetFramework>{tfm}</TargetFramework>
<OutputType>Library</OutputType>
<RootNamespace>{rootNamespace}</RootNamespace>
{ridBlock}
<AppendRuntimeIdentifierToOutputPath>true</AppendRuntimeIdentifierToOutputPath>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>latestmajor</LangVersion>
<BasePackageAssembliesDir>$(MSBuildProjectDirectory)\..\..\..\BasePackage\Assemblies\</BasePackageAssembliesDir>
</PropertyGroup>
{referencesXml}
<PropertyGroup>
<M42ExtensionId>{options.ExtensionId}</M42ExtensionId>
<M42AssemblyPattern>$(MSBuildProjectName)*.*</M42AssemblyPattern>
<M42EnableRidBuild>{anyRids.ToString().ToLowerInvariant()}</M42EnableRidBuild>
</PropertyGroup>
<Import Project=""..\..\..\Extension.Assemblies.targets"" Condition=""Exists('..\..\..\Extension.Assemblies.targets')"" />
</Project>
";
File.WriteAllText(csprojPath, content);
}
protected virtual void GenerateHostFile(string projectDir, string projectName)
{
var hostFileName = $"{projectName}.dll.host";
var hostPath = Path.Combine(projectDir, hostFileName);
if (!File.Exists(hostPath))
{
File.WriteAllText(hostPath, Templates.DefaultHostFileContent);
}
}
private static string BuildReferencesXml(IEnumerable<ProjectReferenceDefinition> refs)
{
var list = refs?.ToList() ?? new List<ProjectReferenceDefinition>();
if (list.Count == 0)
return string.Empty;
var sb = new StringBuilder();
sb.AppendLine(" <ItemGroup>");
foreach (var r in list)
{
sb.AppendLine($@" <Reference Include=""{r.Include}"">");
if (!string.IsNullOrWhiteSpace(r.HintPath))
{
sb.AppendLine($@" <HintPath>{r.HintPath}</HintPath>");
}
sb.AppendLine(" </Reference>");
}
sb.AppendLine(" </ItemGroup>");
return sb.ToString();
}
#endregion
#region Functionality integration (files + references)
/// <summary>
/// Collects files to generate from all enabled functionalities.
/// </summary>
protected virtual IReadOnlyList<TemplateFile> CollectFunctionalityFiles(
ExtensionScaffoldingOptions options, ProjectDefinition def)
{
var result = new List<TemplateFile>();
var flags = def.Functionality;
foreach (var defn in _functionalityDefinitions)
{
if (!flags.HasFlag(defn.Kind))
{
continue;
}
result.AddRange(defn.GetFiles(options, def));
}
return result;
}
/// <summary>
/// Collects references to add to csproj from all enabled functionalities.
/// </summary>
protected virtual IReadOnlyList<ProjectReferenceDefinition> CollectFunctionalityReferences(
ExtensionScaffoldingOptions options, ProjectDefinition def)
{
var result = new List<ProjectReferenceDefinition>();
var flags = def.Functionality;
foreach (var defn in _functionalityDefinitions)
{
if (!flags.HasFlag(defn.Kind))
{
continue;
}
result.AddRange(defn.GetReferences(options, def));
}
return result;
}
protected virtual void GenerateFiles(string projectDir, IReadOnlyList<TemplateFile> files)
{
foreach (var file in files)
{
var targetPath = Path.Combine(projectDir, file.RelativePath);
var targetDir = Path.GetDirectoryName(targetPath);
if (!string.IsNullOrEmpty(targetDir))
{
Directory.CreateDirectory(targetDir);
}
if (file.CreateIfMissingOnly && File.Exists(targetPath))
{
continue;
}
File.WriteAllText(targetPath, file.Content);
}
}
#endregion
}
}

View File

@@ -0,0 +1,12 @@
using Matrix42.Extensions.Scaffolder.Core.Project;
using System.Collections.Generic;
namespace Matrix42.Extensions.Scaffolder.Core.ProjectGenerators;
internal interface IProjectGenerator
{
/// <summary>
/// Generates the project and returns the path to the created .csproj file.
/// </summary>
string GenerateProject(ExtensionScaffoldingOptions options, ProjectDefinition def);
}

View File

@@ -0,0 +1,26 @@
namespace Matrix42.Extensions.Scaffolder.Core
{
/// <summary>
/// Describes a single file to generate from a template.
/// </summary>
public sealed class TemplateFile
{
/// <summary>
/// Path relative to project folder (for project files)
/// or ExtensionPath (for root-level files).
/// Example: "Controllers\\TestController.cs" or "BuildEvent.cmd".
/// </summary>
public string RelativePath { get; set; }
/// <summary>
/// Template content (with tokens like {ns}, {projectName} etc.).
/// </summary>
public string Content { get; set; }
/// <summary>
/// If true, file is only created if it doesn't exist.
/// If false, it will be overwritten.
/// </summary>
public bool CreateIfMissingOnly { get; set; } = true;
}
}

View File

@@ -0,0 +1,91 @@
using System.IO;
namespace Matrix42.Extensions.Scaffolder.Core
{
internal static class Templates
{
public const string Gitignore = @"
.idea/
.vs/
.vscode/
node_modules/
dist/
package-lock.json
workspace.json
.vs/
.libs/
.build/
bin/
obj/
*.user
/**/AssemblyInfoProduct.cs
/**/_Resharper.Caches
/Solutions/*.sln.DotSettings.user
/Solutions/**/*.csproj.user
/Solutions/**/packages/*.*
/Solutions/**/**/AssemblyInfoProduct.cs
/Solutions/TestResult/*.*
BasePackage/Assemblies/
";
public const string PrepareVeracodeScanCmd = @"set arch=""C:\Program Files\7-Zip\7z.exe""
echo [*] Building backend archive...
%arch% a -tzip -mx5 -x!*.config %Build_ArtifactStagingDirectory%\{ns}.zip %BaseDir%\Solutions\{ns}\.build\*.*
";
public const string DefaultHostFileContent =
@"<?xml version=""1.0"" encoding=""utf-8"" ?>
<host xmlns=""urn:m42/host.config"">
<modules></modules>
<sections></sections>
</host>
";
public const string BuildEventCmdTemplate =
@"call %~dps0..\..\TeamBuildEvent.cmd %1 %2 %3 ""/Solutions/{ns}""";
internal static string GetMatrix42AssembliesTargets()
{
var asm = typeof(Templates).Assembly;
const string resourceName = "Matrix42.Extensions.Scaffolder.Core.Extension.Assemblies.targets";
using (var stream = asm.GetManifestResourceStream(resourceName))
{
if (stream == null)
{
throw new FileNotFoundException($"Embedded resource '{resourceName}' not found in assembly '{asm.FullName}'.");
}
using (var reader = new StreamReader(stream))
{
return reader.ReadToEnd();
}
}
}
internal static string GetAzurePiplineCi()
{
var asm = typeof(Templates).Assembly;
const string resourceName = "Matrix42.Extensions.Scaffolder.Core.azure-pipelines-ci.yaml";
using (var stream = asm.GetManifestResourceStream(resourceName))
{
if (stream == null)
{
throw new FileNotFoundException($"Embedded resource '{resourceName}' not found in assembly '{asm.FullName}'.");
}
using (var reader = new StreamReader(stream))
{
return reader.ReadToEnd();
}
}
}
}
}

View File

@@ -0,0 +1,123 @@
# azure-pipelines.yml
pr: none
trigger:
branches:
include:
- release_26_1_0
paths:
include:
- 'Solutions/**'
parameters:
- name: BaseDir
type: string
default: $(Build.SourcesDirectory)
- name: SuiteRarShare
type: string
default: '\\m42-sto-ffm01.matrix42.de\ServiceStore\Builds\TitanLinux Continuous'
- name: RepoName
type: string
default: '{repoName}'
- name: SolutionName
type: string
default: '{ns}'
variables:
- name: BaseDir
value: ${{ parameters.BaseDir }}
- name: ExtensionTarget
value: ${{ parameters.BaseDir }}/${{ parameters.RepoName }}
- name: SolutionDir
value: $(ExtensionTarget)/Solutions/{ns}
- name: SolutionPath
value: $(SolutionDir)/{ns}.sln
# Tests
- name: TestsDir
value: $(SolutionDir)
- name: TestProject
value: '{ns}.Tests/{ns}.Tests.csproj'
stages:
- stage: Build
displayName: 'Build ${{ parameters.RepoName }}'
jobs:
- job: Build_Backend
displayName: 'Build, Test, Package'
pool:
name: M42BPM
demands:
- DotNetFramework
- vstest
steps:
- task: CopyFiles@2
displayName: 'Copy extension $(ExtensionTarget) source files'
inputs:
SourceFolder: '${{ parameters.BaseDir }}'
Contents: '**'
TargetFolder: '$(ExtensionTarget)'
- task: CopyFiles@2
displayName: 'Copy suite.rar'
inputs:
SourceFolder: '${{ parameters.SuiteRarShare }}'
Contents: 'suite.rar'
TargetFolder: '${{ parameters.BaseDir }}'
OverWrite: true
- task: ExtractFiles@1
displayName: 'Extract suite.rar'
inputs:
archiveFilePatterns: '${{ parameters.BaseDir }}/suite.rar'
destinationFolder: '${{ parameters.BaseDir }}'
cleanDestinationFolder: false
overwriteExistingFiles: true
- task: DotNetCoreCLI@2
displayName: 'Restore $(ExtensionTarget) solution'
inputs:
command: 'restore'
projects: '$(SolutionPath)'
# Optional: override default RIDs globally if needed: -p:M42DefaultRids=win-x64;linux-x64
- task: DotNetCoreCLI@2
displayName: 'Build {ns} solution'
inputs:
command: 'build'
projects: '$(SolutionPath)'
arguments: >
-c Release
--no-restore
-p:SolutionDir=$(SolutionDir)\
-p:M42BuildAllRidsOnCI=true
- task: DotNetCoreCLI@2
displayName: 'Test (net8.0)'
inputs:
command: 'test'
projects: '$(TestsDir)/$(TestProject)'
arguments: '-c Release -f net8.0 --no-build'
- task: ArchiveFiles@2
displayName: 'Zip $(ExtensionTarget) BasePackage'
inputs:
rootFolderOrFile: '$(ExtensionTarget)/BasePackage'
includeRootFolder: false
archiveType: 'zip'
sevenZipCompression: 'normal'
archiveFile: '$(Build.ArtifactStagingDirectory)/${{ parameters.RepoName }}_1.0.zip'
replaceExistingArchive: true
- task: PublishPipelineArtifact@1
displayName: 'Publish ${{ parameters.RepoName }}_1.0.zip'
inputs:
targetPath: '$(Build.ArtifactStagingDirectory)/${{ parameters.RepoName }}_1.0.zip'
artifact: '${{ parameters.RepoName }}'

View File

@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MinimumVisualStudioVersion>17.0</MinimumVisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<SchemaVersion>2.0</SchemaVersion>
<ProjectTypeGuids>{82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<ProjectGuid>{50F8DCAA-48B9-47D5-ABB8-F8D33BEF1BD1}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Matrix42.Extensions.Scaffolder.Vsix</RootNamespace>
<AssemblyName>Matrix42.Extensions.Scaffolder.Vsix</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<GeneratePkgDefFile>true</GeneratePkgDefFile>
<UseCodebase>true</UseCodebase>
<IncludeAssemblyInVSIXContainer>true</IncludeAssemblyInVSIXContainer>
<IncludeDebugSymbolsInVSIXContainer>false</IncludeDebugSymbolsInVSIXContainer>
<IncludeDebugSymbolsInLocalVSIXDeployment>false</IncludeDebugSymbolsInLocalVSIXDeployment>
<CopyBuildOutputToOutputDirectory>true</CopyBuildOutputToOutputDirectory>
<CopyOutputSymbolsToOutputDirectory>true</CopyOutputSymbolsToOutputDirectory>
<StartAction>Program</StartAction>
<StartProgram Condition="'$(DevEnvDir)' != ''">$(DevEnvDir)devenv.exe</StartProgram>
<StartArguments>/rootsuffix Exp</StartArguments>
<GeneratePkgDefFile>false</GeneratePkgDefFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Wizard\ExtensionProjectsWizard.cs" />
<Compile Include="Wizard\ProjectDefinition.cs" />
<Compile Include="Wizard\RuntimeTargets.cs" />
<Compile Include="Wizard\WizardForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Wizard\WizardForm.Designer.cs">
<DependentUpon>WizardForm.cs</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="ProjectTemplates\Directory.Build.props.template" />
<None Include="ProjectTemplates\Placeholder.csproj" />
<None Include="source.extension.vsixmanifest">
<SubType>Designer</SubType>
</None>
</ItemGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.SDK" Version="17.0.32112.339" ExcludeAssets="runtime" NoWarn="NU1604" />
<PackageReference Include="Microsoft.VisualStudio.TemplateWizardInterface">
<Version>17.10.40170</Version>
</PackageReference>
<PackageReference Include="Microsoft.VSSDK.BuildTools" Version="17.14.2096" NoWarn="NU1604" />
</ItemGroup>
<ItemGroup>
<Content Include="ProjectTemplates\ExtensionSolution.vstemplate" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(VSToolsPath)\VSSDK\Microsoft.VsSDK.targets" Condition="'$(VSToolsPath)' != ''" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -0,0 +1,37 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.14.36310.24 d17.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Matrix42.Extensions.Scaffolder.Vsix", "Matrix42.Extensions.Scaffolder.Vsix.csproj", "{50F8DCAA-48B9-47D5-ABB8-F8D33BEF1BD1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|arm64 = Debug|arm64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|arm64 = Release|arm64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{50F8DCAA-48B9-47D5-ABB8-F8D33BEF1BD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{50F8DCAA-48B9-47D5-ABB8-F8D33BEF1BD1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{50F8DCAA-48B9-47D5-ABB8-F8D33BEF1BD1}.Debug|arm64.ActiveCfg = Debug|arm64
{50F8DCAA-48B9-47D5-ABB8-F8D33BEF1BD1}.Debug|arm64.Build.0 = Debug|arm64
{50F8DCAA-48B9-47D5-ABB8-F8D33BEF1BD1}.Debug|x86.ActiveCfg = Debug|x86
{50F8DCAA-48B9-47D5-ABB8-F8D33BEF1BD1}.Debug|x86.Build.0 = Debug|x86
{50F8DCAA-48B9-47D5-ABB8-F8D33BEF1BD1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{50F8DCAA-48B9-47D5-ABB8-F8D33BEF1BD1}.Release|Any CPU.Build.0 = Release|Any CPU
{50F8DCAA-48B9-47D5-ABB8-F8D33BEF1BD1}.Release|arm64.ActiveCfg = Release|arm64
{50F8DCAA-48B9-47D5-ABB8-F8D33BEF1BD1}.Release|arm64.Build.0 = Release|arm64
{50F8DCAA-48B9-47D5-ABB8-F8D33BEF1BD1}.Release|x86.ActiveCfg = Release|x86
{50F8DCAA-48B9-47D5-ABB8-F8D33BEF1BD1}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {ECBF8D8B-3FBE-4CDA-BE52-C37D76054B09}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,7 @@
<Project>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,21 @@
<VSTemplate Version="3.0.0" Type="Project">
<TemplateData>
<Name>Matrix42 Extension Solution</Name>
<Description>Create a new Matrix42 extension solution (WebApi / Contracts / BizLogic / Custom) using Matrix42.Extensions.SDK.</Description>
<ProjectType>CSharp</ProjectType>
<SortOrder>1000</SortOrder>
<DefaultName>Matrix42.Extension</DefaultName>
<CreateNewFolder>true</CreateNewFolder>
<ProvideDefaultName>true</ProvideDefaultName>
<LocationField>Enabled</LocationField>
</TemplateData>
<TemplateContent>
<Project File="Placeholder.csproj" ReplaceParameters="true" TargetFileName="$safeprojectname$.csproj" />
</TemplateContent>
<WizardExtension>
<Assembly>Matrix42.Extensions.Scaffolder.Vsix, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null</Assembly>
<FullClassName>Matrix42.Extensions.Scaffolder.Wizard.ExtensionProjectsWizard</FullClassName>
</WizardExtension>
</VSTemplate>

View File

@@ -0,0 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Library</OutputType>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,33 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Matrix42.Extensions.Scaffolder.Vsix")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Matrix42.Extensions.Scaffolder.Vsix")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -0,0 +1,231 @@
using System;
using System.Collections.Generic;
using System.IO;
using EnvDTE;
using EnvDTE80;
using Microsoft.VisualStudio.TemplateWizard;
namespace Matrix42.Extensions.Scaffolder.Wizard
{
public class ExtensionProjectsWizard : IWizard
{
private DTE2 _dte;
private Project _placeholderProject;
private string _extensionNamespace = string.Empty;
private List<ProjectDefinition> _projects = new List<ProjectDefinition>();
private string _solutionDirectory = string.Empty;
public void RunStarted(
object automationObject,
Dictionary<string, string> replacementsDictionary,
WizardRunKind runKind,
object[] customParams)
{
_dte = automationObject as DTE2
?? throw new InvalidOperationException("DTE2 not available.");
using (var form = new WizardForm())
{
var result = form.ShowDialog();
if (result != System.Windows.Forms.DialogResult.OK)
throw new WizardBackoutException();
_extensionNamespace = form.ExtensionNamespace;
_projects = new List<ProjectDefinition>(form.Projects);
}
}
// 🔹 This is the method that was missing
public void BeforeOpeningFile(ProjectItem projectItem)
{
// We don't need to do anything before a file is opened.
// Leave empty on purpose.
}
public void ProjectFinishedGenerating(Project project)
{
_placeholderProject = project;
}
public void ProjectItemFinishedGenerating(ProjectItem projectItem)
{
}
public void RunFinished()
{
var solution = _dte.Solution;
_solutionDirectory = Path.GetDirectoryName(solution.FullName);
if (string.IsNullOrEmpty(_solutionDirectory))
return;
// Remove placeholder project from solution and disk
if (_placeholderProject != null)
{
var placeholderPath = _placeholderProject.FullName;
solution.Remove(_placeholderProject);
TryDeleteDirectorySafe(Path.GetDirectoryName(placeholderPath));
}
// Drop Directory.Build.props
var propsTemplatePath = Path.Combine(
GetExtensionInstallPath(),
"ProjectTemplates",
"Directory.Build.props.template");
if (File.Exists(propsTemplatePath))
{
var destProps = Path.Combine(_solutionDirectory, "Directory.Build.props");
if (!File.Exists(destProps))
File.Copy(propsTemplatePath, destProps, overwrite: false);
}
foreach (var proj in _projects)
{
CreateProjectFromDefinition(solution, proj);
}
}
public bool ShouldAddProjectItem(string filePath) => true;
private void CreateProjectFromDefinition(Solution solution, ProjectDefinition def)
{
var projectName = def.GetProjectName(_extensionNamespace);
var projectNamespace = def.GetNamespace(_extensionNamespace);
var projectDir = Path.Combine(_solutionDirectory, projectName);
Directory.CreateDirectory(projectDir);
var csprojPath = Path.Combine(projectDir, projectName + ".csproj");
var runtimeIds = def.GetRuntimeIdentifiers();
var tfm = "net8.0"; // default target framework
var csprojContent = GenerateCsproj(def.Type, tfm, runtimeIds, projectNamespace);
File.WriteAllText(csprojPath, csprojContent);
var codeFilePath = Path.Combine(
projectDir,
def.Type == ProjectType.WebApi ? "Program.cs" : "Class1.cs");
var codeContent = GenerateCodeFile(def.Type, projectNamespace);
File.WriteAllText(codeFilePath, codeContent);
solution.AddFromFile(csprojPath);
}
private string GenerateCsproj(
ProjectType type,
string tfm,
string runtimeIds,
string rootNamespace)
{
var outputType = type == ProjectType.WebApi ? "Exe" : "Library";
var sdk = type == ProjectType.WebApi ? "Microsoft.NET.Sdk.Web" : "Microsoft.NET.Sdk";
var ridBlock = string.IsNullOrWhiteSpace(runtimeIds)
? string.Empty
: $" <RuntimeIdentifiers>{runtimeIds}</RuntimeIdentifiers>{Environment.NewLine}";
return
$@"<Project Sdk=""{sdk}"">
<PropertyGroup>
<TargetFramework>{tfm}</TargetFramework>
<OutputType>{outputType}</OutputType>
<RootNamespace>{rootNamespace}</RootNamespace>
</PropertyGroup>
<PropertyGroup>
{ridBlock} </PropertyGroup>
<ItemGroup>
<PackageReference Include=""Matrix42.Extensions.SDK"" Version=""1.0.0"" />
</ItemGroup>
</Project>
";
}
private string GenerateCodeFile(ProjectType type, string ns)
{
switch (type)
{
case ProjectType.WebApi:
return
$@"using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Matrix42.Extensions.SDK;
namespace {ns}
{{
public class Program
{{
public static void Main(string[] args)
{{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
// TODO: configure Matrix42.Extensions.SDK services
var app = builder.Build();
app.MapControllers();
app.Run();
}}
}}
}}";
case ProjectType.Contracts:
return
$@"namespace {ns}
{{
// Place your contracts (DTOs, interfaces) here
public class SampleContract
{{
public string Id {{ get; set; }} = string.Empty;
}}
}}";
case ProjectType.BizLogic:
return
$@"using Matrix42.Extensions.SDK;
namespace {ns}
{{
// Place your business logic services here
public class SampleService
{{
public string SayHello() => ""Hello from {ns}"";
}}
}}";
case ProjectType.Custom:
default:
return
$@"namespace {ns}
{{
// Custom project for your extension
public class CustomEntry
{{
public string Name {{ get; set; }} = ""Custom"";
}}
}}";
}
}
private string GetExtensionInstallPath()
{
var asmPath = typeof(ExtensionProjectsWizard).Assembly.Location;
return Path.GetDirectoryName(asmPath);
}
private void TryDeleteDirectorySafe(string path)
{
try
{
if (!string.IsNullOrWhiteSpace(path) && Directory.Exists(path))
Directory.Delete(path, recursive: true);
}
catch
{
// ignore
}
}
}
}

View File

@@ -0,0 +1,66 @@
using System.Collections.Generic;
namespace Matrix42.Extensions.Scaffolder.Wizard
{
public enum ProjectType
{
WebApi,
Contracts,
BizLogic,
Custom
}
public class ProjectDefinition
{
public ProjectType Type { get; set; }
public string Prefix { get; set; } = string.Empty; // custom for Custom type, otherwise derived
public RuntimeTargets Runtimes { get; set; }
public string EffectivePrefix
{
get
{
if (Type == ProjectType.Custom)
return Prefix;
switch (Type)
{
case ProjectType.WebApi:
return string.IsNullOrWhiteSpace(Prefix) ? "Services" : Prefix;
case ProjectType.Contracts:
return string.IsNullOrWhiteSpace(Prefix) ? "Contracts" : Prefix;
case ProjectType.BizLogic:
return string.IsNullOrWhiteSpace(Prefix) ? "BizLogic" : Prefix;
default:
return Prefix;
}
}
}
public string Display => $"{Type} - Prefix: {EffectivePrefix} - Runtimes: {Runtimes}";
public string GetProjectName(string extensionNamespace)
{
var pfx = EffectivePrefix?.Trim();
return string.IsNullOrWhiteSpace(pfx)
? extensionNamespace
: $"{extensionNamespace}.{pfx}";
}
public string GetNamespace(string extensionNamespace) => GetProjectName(extensionNamespace);
public string GetRuntimeIdentifiers()
{
if (Runtimes.HasFlag(RuntimeTargets.All))
return "win-x64;linux-x64";
var list = new List<string>();
if (Runtimes.HasFlag(RuntimeTargets.Win) || Runtimes.HasFlag(RuntimeTargets.WinCore))
list.Add("win-x64");
if (Runtimes.HasFlag(RuntimeTargets.LinuxCore))
list.Add("linux-x64");
return string.Join(";", list);
}
}
}

View File

@@ -0,0 +1,12 @@
namespace Matrix42.Extensions.Scaffolder.Wizard
{
[System.Flags]
public enum RuntimeTargets
{
None = 0,
Win = 1,
WinCore = 2,
LinuxCore = 4,
All = 8
}
}

View File

@@ -0,0 +1,236 @@
namespace Matrix42.Extensions.Scaffolder.Wizard
{
partial class WizardForm
{
private System.ComponentModel.IContainer components = null;
private System.Windows.Forms.Label lblTitle;
private System.Windows.Forms.Label lblExtensionNamespace;
private System.Windows.Forms.TextBox txtExtensionNamespace;
private System.Windows.Forms.Label lblHint;
private System.Windows.Forms.Label lblAddProjects;
private System.Windows.Forms.Label lblProjectType;
private System.Windows.Forms.ComboBox cboProjectType;
private System.Windows.Forms.Label lblProjectPrefix;
private System.Windows.Forms.TextBox txtProjectPrefix;
private System.Windows.Forms.Label lblRuntimes;
private System.Windows.Forms.CheckedListBox clbRuntimes;
private System.Windows.Forms.Button btnAddProject;
private System.Windows.Forms.ListBox lstProjects;
private System.Windows.Forms.Button btnRemoveSelected;
private System.Windows.Forms.Button btnOK;
private System.Windows.Forms.Button btnCancel;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
components.Dispose();
base.Dispose(disposing);
}
private void InitializeComponent()
{
this.lblTitle = new System.Windows.Forms.Label();
this.lblExtensionNamespace = new System.Windows.Forms.Label();
this.txtExtensionNamespace = new System.Windows.Forms.TextBox();
this.lblHint = new System.Windows.Forms.Label();
this.lblAddProjects = new System.Windows.Forms.Label();
this.lblProjectType = new System.Windows.Forms.Label();
this.cboProjectType = new System.Windows.Forms.ComboBox();
this.lblProjectPrefix = new System.Windows.Forms.Label();
this.txtProjectPrefix = new System.Windows.Forms.TextBox();
this.lblRuntimes = new System.Windows.Forms.Label();
this.clbRuntimes = new System.Windows.Forms.CheckedListBox();
this.btnAddProject = new System.Windows.Forms.Button();
this.lstProjects = new System.Windows.Forms.ListBox();
this.btnRemoveSelected = new System.Windows.Forms.Button();
this.btnOK = new System.Windows.Forms.Button();
this.btnCancel = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// lblTitle
//
this.lblTitle.AutoSize = true;
this.lblTitle.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Bold);
this.lblTitle.Location = new System.Drawing.Point(12, 9);
this.lblTitle.Name = "lblTitle";
this.lblTitle.Size = new System.Drawing.Size(120, 20);
this.lblTitle.TabIndex = 0;
this.lblTitle.Text = "Projects Wizard";
//
// lblExtensionNamespace
//
this.lblExtensionNamespace.AutoSize = true;
this.lblExtensionNamespace.Location = new System.Drawing.Point(13, 44);
this.lblExtensionNamespace.Name = "lblExtensionNamespace";
this.lblExtensionNamespace.Size = new System.Drawing.Size(113, 15);
this.lblExtensionNamespace.TabIndex = 1;
this.lblExtensionNamespace.Text = "Extension Namespace";
//
// txtExtensionNamespace
//
this.txtExtensionNamespace.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.txtExtensionNamespace.Location = new System.Drawing.Point(16, 62);
this.txtExtensionNamespace.Name = "txtExtensionNamespace";
this.txtExtensionNamespace.Size = new System.Drawing.Size(444, 23);
this.txtExtensionNamespace.TabIndex = 2;
//
// lblHint
//
this.lblHint.AutoSize = true;
this.lblHint.Location = new System.Drawing.Point(466, 65);
this.lblHint.Name = "lblHint";
this.lblHint.Size = new System.Drawing.Size(121, 15);
this.lblHint.TabIndex = 3;
this.lblHint.Text = "aka Matrix42.MyExtension";
//
// lblAddProjects
//
this.lblAddProjects.AutoSize = true;
this.lblAddProjects.Location = new System.Drawing.Point(13, 103);
this.lblAddProjects.Name = "lblAddProjects";
this.lblAddProjects.Size = new System.Drawing.Size(71, 15);
this.lblAddProjects.TabIndex = 4;
this.lblAddProjects.Text = "Add Projects";
//
// lblProjectType
//
this.lblProjectType.AutoSize = true;
this.lblProjectType.Location = new System.Drawing.Point(13, 129);
this.lblProjectType.Name = "lblProjectType";
this.lblProjectType.Size = new System.Drawing.Size(70, 15);
this.lblProjectType.TabIndex = 5;
this.lblProjectType.Text = "Project Type";
//
// cboProjectType
//
this.cboProjectType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cboProjectType.FormattingEnabled = true;
this.cboProjectType.Location = new System.Drawing.Point(16, 147);
this.cboProjectType.Name = "cboProjectType";
this.cboProjectType.Size = new System.Drawing.Size(150, 23);
this.cboProjectType.TabIndex = 6;
//
// lblProjectPrefix
//
this.lblProjectPrefix.AutoSize = true;
this.lblProjectPrefix.Location = new System.Drawing.Point(179, 129);
this.lblProjectPrefix.Name = "lblProjectPrefix";
this.lblProjectPrefix.Size = new System.Drawing.Size(75, 15);
this.lblProjectPrefix.TabIndex = 7;
this.lblProjectPrefix.Text = "Project Prefix";
//
// txtProjectPrefix
//
this.txtProjectPrefix.Location = new System.Drawing.Point(182, 147);
this.txtProjectPrefix.Name = "txtProjectPrefix";
this.txtProjectPrefix.Size = new System.Drawing.Size(150, 23);
this.txtProjectPrefix.TabIndex = 8;
//
// lblRuntimes
//
this.lblRuntimes.AutoSize = true;
this.lblRuntimes.Location = new System.Drawing.Point(345, 129);
this.lblRuntimes.Name = "lblRuntimes";
this.lblRuntimes.Size = new System.Drawing.Size(121, 15);
this.lblRuntimes.TabIndex = 9;
this.lblRuntimes.Text = "Runtimes (multiselect)";
//
// clbRuntimes
//
this.clbRuntimes.CheckOnClick = true;
this.clbRuntimes.FormattingEnabled = true;
this.clbRuntimes.Location = new System.Drawing.Point(348, 147);
this.clbRuntimes.Name = "clbRuntimes";
this.clbRuntimes.Size = new System.Drawing.Size(150, 76);
this.clbRuntimes.TabIndex = 10;
//
// btnAddProject
//
this.btnAddProject.Location = new System.Drawing.Point(504, 147);
this.btnAddProject.Name = "btnAddProject";
this.btnAddProject.Size = new System.Drawing.Size(75, 23);
this.btnAddProject.TabIndex = 11;
this.btnAddProject.Text = "Add";
this.btnAddProject.UseVisualStyleBackColor = true;
this.btnAddProject.Click += new System.EventHandler(this.btnAddProject_Click);
//
// lstProjects
//
this.lstProjects.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.lstProjects.FormattingEnabled = true;
this.lstProjects.ItemHeight = 15;
this.lstProjects.Location = new System.Drawing.Point(16, 236);
this.lstProjects.Name = "lstProjects";
this.lstProjects.Size = new System.Drawing.Size(563, 139);
this.lstProjects.TabIndex = 12;
//
// btnRemoveSelected
//
this.btnRemoveSelected.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.btnRemoveSelected.Location = new System.Drawing.Point(16, 385);
this.btnRemoveSelected.Name = "btnRemoveSelected";
this.btnRemoveSelected.Size = new System.Drawing.Size(120, 23);
this.btnRemoveSelected.TabIndex = 13;
this.btnRemoveSelected.Text = "Remove selected";
this.btnRemoveSelected.UseVisualStyleBackColor = true;
this.btnRemoveSelected.Click += new System.EventHandler(this.btnRemoveSelected_Click);
//
// btnOK
//
this.btnOK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnOK.Location = new System.Drawing.Point(423, 385);
this.btnOK.Name = "btnOK";
this.btnOK.Size = new System.Drawing.Size(75, 23);
this.btnOK.TabIndex = 14;
this.btnOK.Text = "Create";
this.btnOK.UseVisualStyleBackColor = true;
this.btnOK.Click += new System.EventHandler(this.btnOK_Click);
//
// btnCancel
//
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnCancel.Location = new System.Drawing.Point(504, 385);
this.btnCancel.Name = "btnCancel";
this.btnCancel.Size = new System.Drawing.Size(75, 23);
this.btnCancel.TabIndex = 15;
this.btnCancel.Text = "Cancel";
this.btnCancel.UseVisualStyleBackColor = true;
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
//
// WizardForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(592, 420);
this.Controls.Add(this.btnCancel);
this.Controls.Add(this.btnOK);
this.Controls.Add(this.btnRemoveSelected);
this.Controls.Add(this.lstProjects);
this.Controls.Add(this.btnAddProject);
this.Controls.Add(this.clbRuntimes);
this.Controls.Add(this.lblRuntimes);
this.Controls.Add(this.txtProjectPrefix);
this.Controls.Add(this.lblProjectPrefix);
this.Controls.Add(this.cboProjectType);
this.Controls.Add(this.lblProjectType);
this.Controls.Add(this.lblAddProjects);
this.Controls.Add(this.lblHint);
this.Controls.Add(this.txtExtensionNamespace);
this.Controls.Add(this.lblExtensionNamespace);
this.Controls.Add(this.lblTitle);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "WizardForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "Matrix42 Extension Solution Wizard";
this.Load += new System.EventHandler(this.WizardForm_Load);
this.ResumeLayout(false);
this.PerformLayout();
}
}
}

View File

@@ -0,0 +1,164 @@
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace Matrix42.Extensions.Scaffolder.Wizard
{
public partial class WizardForm : Form
{
private readonly List<ProjectDefinition> _projects = new List<ProjectDefinition>();
public string ExtensionNamespace => txtExtensionNamespace.Text.Trim();
public IReadOnlyList<ProjectDefinition> Projects => _projects;
public WizardForm()
{
InitializeComponent();
cboProjectType.DataSource = Enum.GetValues(typeof(ProjectType));
clbRuntimes.Items.Add("Win");
clbRuntimes.Items.Add("WinCore");
clbRuntimes.Items.Add("LinuxCore");
clbRuntimes.Items.Add("ALL");
cboProjectType.SelectedIndexChanged += CboProjectType_SelectedIndexChanged;
}
private void WizardForm_Load(object sender, EventArgs e)
{
lblHint.Text = "aka Matrix42.MyExtension";
}
private void CboProjectType_SelectedIndexChanged(object sender, EventArgs e)
{
var type = (ProjectType)cboProjectType.SelectedItem;
switch (type)
{
case ProjectType.WebApi:
txtProjectPrefix.Text = "Services";
txtProjectPrefix.ReadOnly = false;
break;
case ProjectType.Contracts:
txtProjectPrefix.Text = "Contracts";
txtProjectPrefix.ReadOnly = false;
break;
case ProjectType.BizLogic:
txtProjectPrefix.Text = "BizLogic";
txtProjectPrefix.ReadOnly = false;
break;
case ProjectType.Custom:
txtProjectPrefix.Text = string.Empty;
txtProjectPrefix.ReadOnly = false;
break;
}
}
private void btnAddProject_Click(object sender, EventArgs e)
{
if (!ValidateProjectInputs())
return;
var def = new ProjectDefinition
{
Type = (ProjectType)cboProjectType.SelectedItem,
Prefix = txtProjectPrefix.Text.Trim(),
Runtimes = GetSelectedRuntimes()
};
_projects.Add(def);
lstProjects.Items.Add(def.Display);
txtProjectPrefix.Clear();
for (int i = 0; i < clbRuntimes.Items.Count; i++)
clbRuntimes.SetItemChecked(i, false);
}
private RuntimeTargets GetSelectedRuntimes()
{
RuntimeTargets result = RuntimeTargets.None;
foreach (var item in clbRuntimes.CheckedItems)
{
switch (item.ToString())
{
case "Win":
result |= RuntimeTargets.Win;
break;
case "WinCore":
result |= RuntimeTargets.WinCore;
break;
case "LinuxCore":
result |= RuntimeTargets.LinuxCore;
break;
case "ALL":
result |= RuntimeTargets.All;
break;
}
}
return result;
}
private bool ValidateProjectInputs()
{
if (cboProjectType.SelectedItem == null)
{
MessageBox.Show("Please select project type.");
return false;
}
var type = (ProjectType)cboProjectType.SelectedItem;
if (string.IsNullOrWhiteSpace(txtProjectPrefix.Text))
{
if (type == ProjectType.Custom)
{
MessageBox.Show("Project prefix is required for Custom project type.");
return false;
}
}
if (clbRuntimes.CheckedItems.Count == 0)
{
MessageBox.Show("Please select at least one runtime.");
return false;
}
return true;
}
private void btnRemoveSelected_Click(object sender, EventArgs e)
{
int idx = lstProjects.SelectedIndex;
if (idx < 0 || idx >= _projects.Count)
return;
_projects.RemoveAt(idx);
lstProjects.Items.RemoveAt(idx);
}
private void btnOK_Click(object sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(ExtensionNamespace))
{
MessageBox.Show("Extension Namespace is required.");
return;
}
if (_projects.Count == 0)
{
MessageBox.Show("Please add at least one project.");
return;
}
DialogResult = DialogResult.OK;
Close();
}
private void btnCancel_Click(object sender, EventArgs e)
{
DialogResult = DialogResult.Cancel;
Close();
}
}
}

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011" xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011">
<Metadata>
<Identity Id="Matrix42.Extensions.Scaffolder.Vsix.7c0f30ed-547f-4e1d-a046-8120ae21fc8b" Version="1.0" Language="en-US" Publisher="Matrix42" />
<DisplayName>Matrix42.Extensions.Scaffolder.Vsix</DisplayName>
<Description xml:space="preserve">Creates a new Matrix42 extension solution with multiple projects based on Matrix42.Extensions.SDK.</Description>
</Metadata>
<Installation>
<InstallationTarget Id="Microsoft.VisualStudio.Community" Version="[17.0, 18.0)">
<ProductArchitecture>amd64</ProductArchitecture>
</InstallationTarget>
</Installation>
<Dependencies>
<Dependency Id="Microsoft.Framework.NDP" DisplayName="Microsoft .NET Framework" d:Source="Manual" Version="[4.7,)" />
</Dependencies>
<Prerequisites>
<Prerequisite Id="Microsoft.VisualStudio.Component.CoreEditor" Version="[17.0,18.0)" DisplayName="Visual Studio core editor" />
</Prerequisites>
<Assets>
<Asset Type="Microsoft.VisualStudio.ProjectTemplate" Path="ProjectTemplates\ExtensionSolution.vstemplate" />
</Assets>
</PackageManifest>

View File

@@ -0,0 +1,37 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.14.36310.24
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Matrix42.Extensions.Scaffolder.Core", "Matrix42.Extensions.Scaffolder.Core\Matrix42.Extensions.Scaffolder.Core.csproj", "{43E82070-2941-4589-8BF5-1FD1D427BDAE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Matrix42.Extensions.Scaffolder.Console", "Matrix42.Extensions.Scaffolder.Console\Matrix42.Extensions.Scaffolder.Console.csproj", "{F7979A70-0D89-46B7-814C-1E7D0B0DF441}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Matrix42.Extensions.Scaffolder.Vsix", "Matrix42.Extensions.Scaffolder.Vsix\Matrix42.Extensions.Scaffolder.Vsix.csproj", "{50F8DCAA-48B9-47D5-ABB8-F8D33BEF1BD1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{43E82070-2941-4589-8BF5-1FD1D427BDAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{43E82070-2941-4589-8BF5-1FD1D427BDAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{43E82070-2941-4589-8BF5-1FD1D427BDAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{43E82070-2941-4589-8BF5-1FD1D427BDAE}.Release|Any CPU.Build.0 = Release|Any CPU
{F7979A70-0D89-46B7-814C-1E7D0B0DF441}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F7979A70-0D89-46B7-814C-1E7D0B0DF441}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F7979A70-0D89-46B7-814C-1E7D0B0DF441}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F7979A70-0D89-46B7-814C-1E7D0B0DF441}.Release|Any CPU.Build.0 = Release|Any CPU
{50F8DCAA-48B9-47D5-ABB8-F8D33BEF1BD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{50F8DCAA-48B9-47D5-ABB8-F8D33BEF1BD1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{50F8DCAA-48B9-47D5-ABB8-F8D33BEF1BD1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{50F8DCAA-48B9-47D5-ABB8-F8D33BEF1BD1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3329054A-94A3-4953-8650-68C9B450515A}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,17 @@
product: "M42 Enterprise" # The project or platform this service belongs to
service_name: "Extensions Scaffolder" # Name of the service
description: "Template project acting as an extension scaffolder. Automatically generate extension structure including provided projects and runtimes" # A brief description of the service
owner:
name: "Titan" # Owner of the service (could be an individual or team)
email: "dmytro.romanii@matrix42.com" # Contact email for the owner
pipelines:
pull_request_pipeline: "N/A" # URL to the pipeline that runs for pull requests
ci_pipeline: "N/A" # URL to the continuous integration (CI) pipeline
cd_pipeline: "N/A" # URL to the continuous delivery (CD) pipeline
links:
repository: "https://dev.azure.com/matrix42/DWP/_git/M42ExtensionScaffolder"
documentation: "https://dev.azure.com/matrix42/DWP/_git/M42ExtensionScaffolder?path=/README.md"
backlog: "https://dev.azure.com/matrix42/BPM/_backlogs/backlog/Titan"

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More