Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mrpmorris/564434c1133911f041c02c5753e6c092 to your computer and use it in GitHub Desktop.
Save mrpmorris/564434c1133911f041c02c5753e6c092 to your computer and use it in GitHub Desktop.
Prototype. Add references to SLN files without including them in your Aspire solution

NOTE: Aspire does not yet support loading projects by path alone. This is for if/when it does...

1: Add a reference to this library from your AspireHost app 2: Make sure the reference in your csproj is in the following format

3: Right-click your AppHost project and select "Add -> Existing item" 4: Browse to find the SLN file you want to use, then select it 5: Click the drop-down icon at the right side of the Add button, select "Add As Link" 6: Click the added SLN file in your solution explorer 7: View the properties for that file (F4) 8: Set the Build Action to "C# analyzer additional file"

If the name of your solution is MicroService1 and it contains projects Web.Api.csproj and MyFunctions.csproj then you can do this in your AppHost Program.cs file

builder.AddProject<Solution_MicroService1.Web_Api>("some-unique-name"); builder.AddProject<Solution_MicroService1.MyFunctions>("some-unique-name");

// If you have added multiple sln files then you can use those too builder.AddProject<Solution_MicroService2.SomeOtherApp>("some-unique-name");

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Title>Morris.Extensions.Aspire.ExternalSolutionSupport</Title>
<PackageId>Morris.Extensions.Aspire.ExternalSolutionSupport</PackageId>
<Description>Referenced Solutions support for Aspire</Description>
<Nullable>enable</Nullable>
<ImplicitUsings>true</ImplicitUsings>
<IncludeBuildOutput>false</IncludeBuildOutput>
<IsRoslynComponent>true</IsRoslynComponent>
<TargetFramework>netstandard2.0</TargetFramework>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<LangVersion>11</LangVersion>
</PropertyGroup>
<PropertyGroup>
<NoWarn>NU5118;NU5128</NoWarn>
</PropertyGroup>
<ItemGroup>
<Compile Remove="bin\**" />
<EmbeddedResource Remove="bin\**" />
<None Remove="bin\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<None Remove="bin\Debug\netstandard2.0\\Morris.Extensions.Aspire.ExternalSolutionSupport.dll" />
</ItemGroup>
<ItemGroup>
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
</ItemGroup>
<ItemGroup>
<!-- Generator package dependencies -->
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" PrivateAssets="all" GeneratePathProperty="true" />
</ItemGroup>
<PropertyGroup>
<GetTargetPathDependsOn>$(GetTargetPathDependsOn);GetDependencyTargetPaths</GetTargetPathDependsOn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>portable</DebugType>
<Optimize>False</Optimize>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebugType>portable</DebugType>
<Optimize>True</Optimize>
</PropertyGroup>
<Target Name="GetDependencyTargetPaths">
<ItemGroup>
<TargetPathWithTargetPlatformMoniker Include="$(PKGMicrosoft_CSharp)\lib\netstandard2.0\Microsoft.CSharp.dll" IncludeRuntimeDependency="false" />
</ItemGroup>
</Target>
</Project>
using Microsoft.CodeAnalysis;
using System.Text;
using System.Text.RegularExpressions;
namespace Morris.Extensions.Aspire.ExternalSolutionSupport;
[Generator]
public partial class RoslynIncrementalGenerator : IIncrementalGenerator
{
private static char Tab = '\t';
private static string Tab2 = "\t\t";
private static readonly Regex ProjectRegex = new Regex(
@"^\s*Project\(s*""{\w{8}-\w{4}-\w{4}-\w{4}-\w{12}}""\s*\)\s*=\s*""(.*?)""\s*,\s*""(.*?)""\s*,\s*""{\w{8}-\w{4}-\w{4}-\w{4}-\w{12}}""",
RegexOptions.Multiline | RegexOptions.Compiled);
public void Initialize(IncrementalGeneratorInitializationContext context)
{
IncrementalValuesProvider<KeyValuePair<string, string>> solutionsToScan =
context
.AdditionalTextsProvider
.Where(static x => x.Path.EndsWith(".sln", StringComparison.OrdinalIgnoreCase))
.Select((file, cancellationToken) =>
new KeyValuePair<string, string>(
key: file.Path,
value: file.GetText(cancellationToken)!.ToString()
)
);
context.RegisterSourceOutput(
source: solutionsToScan,
static (productionContext, input) =>
{
MatchCollection matches = ProjectRegex.Matches(input.Value);
if (matches.Count == 0)
return;
string slnFileDirectoryPath = Path.GetDirectoryName(input.Key);
string slnClassName = Path
.GetFileNameWithoutExtension(input.Key)
.Replace('.', '_');
var builder = new StringBuilder();
builder.AppendLine("namespace Projects;");
builder.AppendLine();
builder.AppendLine($"public static class Solution_{slnClassName}");
builder.AppendLine("{");
foreach (Match match in matches)
{
string projectRelativePath = match.Groups[2].Value;
if (projectRelativePath.EndsWith(".csproj", StringComparison.InvariantCultureIgnoreCase))
{
string projectFileName = match.Groups[1].Value;
string projectClassName = Path
.GetFileName(projectFileName)
.Replace('.', '_');
builder.AppendLine($$"""{{Tab}}[global::System.Diagnostics.DebuggerDisplay("Type = {GetType().Name,nq}, ProjectPath = {ProjectPath}")]""");
builder.AppendLine($$"""{{Tab}}public class {{projectClassName}} : global::Aspire.Hosting.IProjectMetadata""");
builder.AppendLine($$"""{{Tab}}{""");
builder.AppendLine($$""""{{Tab2}}public string ProjectPath => """{{slnFileDirectoryPath}}\{{projectRelativePath}}""";"""");
builder.AppendLine($$"""{{Tab}}}""");
builder.AppendLine();
}
}
builder.AppendLine("}");
productionContext.AddSource($"{slnClassName}.g.cs", builder.ToString());
}
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment