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 _projects = new List(); private string _solutionDirectory = string.Empty; public void RunStarted( object automationObject, Dictionary 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(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 : $" {runtimeIds}{Environment.NewLine}"; return $@" {tfm} {outputType} {rootNamespace} {ridBlock} "; } 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 } } } }