Skip to content

Adding HTTP endpoint generation#21

Closed
ejsmith wants to merge 6 commits intomainfrom
endpoints
Closed

Adding HTTP endpoint generation#21
ejsmith wants to merge 6 commits intomainfrom
endpoints

Conversation

@ejsmith
Copy link
Contributor

@ejsmith ejsmith commented Dec 3, 2025

This pull request introduces significant enhancements to the ConsoleSample project, adding first-class support for Minimal APIs and OpenAPI documentation, as well as improving the codebase for better maintainability and extensibility. The changes include the addition of a new endpoint generator, improved message documentation, and updates to dependencies to enable web API features.

Minimal API and OpenAPI integration:

  • Added new dependencies (Microsoft.AspNetCore.OpenApi, Scalar.AspNetCore) and a framework reference to Microsoft.AspNetCore.App in ConsoleSample.csproj to enable Minimal API and OpenAPI support. Removed now-unnecessary Microsoft.Extensions.* packages.
  • Updated Program.cs to detect a --web argument and launch a Minimal API host with OpenAPI and Scalar API reference endpoints. Added endpoint registration via MapMediatorEndpoints. [1] [2]

Endpoint generation and handler analysis:

  • Introduced a new EndpointGenerator in src/Foundatio.Mediator/EndpointGenerator.cs that generates Minimal API endpoints for mediator messages following CRUD naming conventions. Endpoints are grouped by category, support OpenAPI, and map mediator results to HTTP responses.
  • Enhanced handler analysis in HandlerAnalyzer.cs to extract category information and pass it to the endpoint generator, enabling logical grouping of endpoints. [1] [2] [3]

Message documentation improvements:

  • Added XML documentation comments to mediator message records in Messages.cs, improving generated API documentation and endpoint summaries. [1] [2]

Other improvements:

  • Updated transaction middleware to handle nullable transactions safely, preventing possible runtime errors.
  • Suppressed NuGet warnings related to TFM mismatches in the source generator project file.

These changes collectively modernize the sample project, providing a robust foundation for building web APIs with mediator-based message handling, complete with discoverable and well-documented endpoints.

Comment on lines +265 to +269
foreach (var prefix in SupportedPrefixes)
{
if (handler.MessageType.Name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
return true;
}
@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ ejsmith
❌ Copilot
You have signed the CLA already but the status is still pending? Let us recheck it.

Comment on lines +253 to +295
foreach (var syntaxRef in symbol.DeclaringSyntaxReferences)
{
if (syntaxRef.GetSyntax() is not CSharpSyntaxNode syntaxNode)
continue;

var docTrivia = syntaxNode.GetLeadingTrivia()
.Select(t => t.GetStructure())
.OfType<DocumentationCommentTriviaSyntax>()
.FirstOrDefault();

if (docTrivia == null)
{
var summaryFromSourceText = ParseSummaryFromSourceText(syntaxRef);
if (!String.IsNullOrWhiteSpace(summaryFromSourceText))
return summaryFromSourceText;

continue;
}

var summaryElement = docTrivia.Content
.OfType<XmlElementSyntax>()
.FirstOrDefault(e => String.Equals(e.StartTag?.Name.ToString(), "summary", StringComparison.Ordinal));

if (summaryElement != null)
{
var summaryText = String.Join(" ", summaryElement.Content
.OfType<XmlTextSyntax>()
.SelectMany(t => t.TextTokens)
.Select(tt => tt.Text.Trim())
.Where(t => t.Length > 0));

if (!String.IsNullOrWhiteSpace(summaryText))
return summaryText;
}

var summaryFromRaw = ParseSummaryFromRaw(docTrivia.ToFullString());
if (!String.IsNullOrWhiteSpace(summaryFromRaw))
return summaryFromRaw;

var summaryFromSource = ParseSummaryFromSourceText(syntaxRef);
if (!String.IsNullOrWhiteSpace(summaryFromSource))
return summaryFromSource;
}
Comment on lines +305 to +330
foreach (var attribute in symbol.GetAttributes())
{
if (attribute.AttributeClass is not { } attributeClass)
continue;

if (!IsCategoryAttribute(attributeClass))
continue;

if (attribute.ConstructorArguments.Length > 0)
{
foreach (var arg in attribute.ConstructorArguments)
{
if (arg.Value is string category && !String.IsNullOrWhiteSpace(category))
return category;
}
}

if (attribute.NamedArguments.Length > 0)
{
foreach (var arg in attribute.NamedArguments)
{
if (arg.Value.Value is string namedCategory && !String.IsNullOrWhiteSpace(namedCategory))
return namedCategory;
}
}
}
Comment on lines +315 to +319
foreach (var arg in attribute.ConstructorArguments)
{
if (arg.Value is string category && !String.IsNullOrWhiteSpace(category))
return category;
}
Comment on lines +324 to +328
foreach (var arg in attribute.NamedArguments)
{
if (arg.Value.Value is string namedCategory && !String.IsNullOrWhiteSpace(namedCategory))
return namedCategory;
}
Comment on lines +398 to +406
foreach (var line in lines)
{
var trimmed = line.Trim();
if (!trimmed.StartsWith("///", StringComparison.Ordinal))
continue;

var content = trimmed.Length > 3 ? trimmed.Substring(3).TrimStart() : String.Empty;
builder.AppendLine(content);
}
Comment on lines +439 to +442
catch
{
return null;
}
Comment on lines +396 to +405
if (needsAsync)
{
methodReturnType = returnsValue
? $"System.Threading.Tasks.ValueTask<{defaultReturnType}>"
: "System.Threading.Tasks.ValueTask";
}
else
{
methodReturnType = returnsValue ? defaultReturnType : "void";
}
@ejsmith ejsmith closed this Jan 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants