diff --git a/src/Cnblogs.Architecture.Tool/GenerateOptions.cs b/src/Cnblogs.Architecture.Tool/GenerateOptions.cs
index 669c86b..d2b37d4 100644
--- a/src/Cnblogs.Architecture.Tool/GenerateOptions.cs
+++ b/src/Cnblogs.Architecture.Tool/GenerateOptions.cs
@@ -1 +1 @@
-internal sealed record GenerateOptions(string ApiProject, string Output, string Namespace, bool Clean);
+internal sealed record GenerateOptions(string ApiProject, string Output, string Namespace, bool Clean, string? BaseUrl);
diff --git a/src/Cnblogs.Architecture.Tool/Generation/ServiceAgentEmitter.cs b/src/Cnblogs.Architecture.Tool/Generation/ServiceAgentEmitter.cs
index d21943b..707bb85 100644
--- a/src/Cnblogs.Architecture.Tool/Generation/ServiceAgentEmitter.cs
+++ b/src/Cnblogs.Architecture.Tool/Generation/ServiceAgentEmitter.cs
@@ -39,6 +39,13 @@ internal sealed class ServiceAgentEmitter
///
public string ApiVersion { get; init; } = "1";
+ ///
+ /// When set, the generated DI extensions bake this base URL into each AddXxxService call and the methods
+ /// take no baseUri argument. Supplied via the tool's --base-url option; null keeps the
+ /// parameterized form (the caller passes baseUri).
+ ///
+ public string? BaseUrl { get; init; }
+
/// Emit all source files for the manifest, using for the generated types.
public List Emit(EndpointManifest manifest, string @namespace)
{
@@ -809,24 +816,40 @@ private static string InjectErrorType(string returnType, ManifestGroup group, Cl
private string RenderExtensions(EndpointManifest manifest, string @namespace)
{
+ // When --base-url was supplied, bake the URL into each call and drop the baseUri argument; otherwise keep the
+ // parameterized form so the caller supplies the base URI at registration time.
+ var bakeBaseUrl = BaseUrl is not null;
+ var signatureTail = bakeBaseUrl ? string.Empty : ", string baseUri";
+ var callArg = bakeBaseUrl ? RenderStringLiteral(BaseUrl!) : "baseUri";
+
var sb = new StringBuilder();
sb.Append(AutoGeneratedBanner);
sb.Append("using Cnblogs.Architecture.Ddd.Cqrs.ServiceAgent;\n");
sb.Append("using Microsoft.Extensions.DependencyInjection;\n\n");
sb.Append("namespace ").Append(@namespace).Append(";\n\n");
sb.Append("public static partial class ServiceAgentExtensions\n{\n");
- sb.Append(
- " public static IServiceCollection AddServiceAgents(this IServiceCollection services, string baseUri)\n {\n");
+ // One registration extension per group (e.g. AddCorpService), so callers can register each agent independently.
foreach (var group in manifest.Groups)
{
+ sb.Append(" public static IServiceCollection Add").Append(group.Name)
+ .Append("Service(this IServiceCollection services").Append(signatureTail).Append(")\n {\n");
sb.Append(" services.AddServiceAgent(baseUri);\n");
+ .Append("Service>(").Append(callArg).Append(");\n");
+ sb.Append(" return services;\n }\n");
}
- sb.Append(" return services;\n }\n}\n");
+ sb.Append("}\n");
return sb.ToString();
}
+ private static string RenderStringLiteral(string value)
+ {
+ // Escape for a regular C# string literal. URLs rarely contain quotes/backslashes, but guard against it so a
+ // value with such characters still compiles.
+ var escaped = value.Replace("\\", "\\\\").Replace("\"", "\\\"");
+ return "\"" + escaped + "\"";
+ }
+
///
/// Emit one POCO per payload contract: a mutable class mirroring the command's settable properties, so the
/// generated client references this generated type instead of the command type's assembly.
diff --git a/src/Cnblogs.Architecture.Tool/Program.cs b/src/Cnblogs.Architecture.Tool/Program.cs
index dbe4aad..24f3f2e 100644
--- a/src/Cnblogs.Architecture.Tool/Program.cs
+++ b/src/Cnblogs.Architecture.Tool/Program.cs
@@ -50,7 +50,9 @@ static void PrintUsage()
serviceagent Generate strongly-typed CQRS service agents.
serviceagent generate:
- dotnet cnb serviceagent generate --api-project --output --namespace [--clean]
+ dotnet cnb serviceagent generate --api-project --output --namespace [--base-url ] [--clean]
+
+ --base-url Bake this base URL into the generated AddXxxService extensions (otherwise each takes a baseUri argument).
Requires the API project to reference the Cnblogs.Architecture.ServiceAgent.Design package.
""");
@@ -60,6 +62,7 @@ Requires the API project to reference the Cnblogs.Architecture.ServiceAgent.Desi
{
string? apiProject = null;
string? output = null;
+ string? baseUrl = null;
var ns = "Generated.ServiceAgents";
var clean = false;
for (var i = 0; i < args.Length; i++)
@@ -75,6 +78,9 @@ Requires the API project to reference the Cnblogs.Architecture.ServiceAgent.Desi
case "--namespace":
ns = Next(args, ref i) ?? "Generated.ServiceAgents";
break;
+ case "--base-url":
+ baseUrl = Next(args, ref i);
+ break;
case "--clean":
clean = true;
break;
@@ -90,7 +96,7 @@ Requires the API project to reference the Cnblogs.Architecture.ServiceAgent.Desi
return null;
}
- return new GenerateOptions(apiProject, output, ns, clean);
+ return new GenerateOptions(apiProject, output, ns, clean, baseUrl);
}
static string? Next(string[] args, ref int i)
@@ -147,7 +153,7 @@ static async Task RunGenerateAsync(GenerateOptions options)
CleanGeneratedFiles(options.Output);
}
- var emitter = new ServiceAgentEmitter();
+ var emitter = new ServiceAgentEmitter { BaseUrl = options.BaseUrl };
var files = emitter.Emit(manifest, options.Namespace);
foreach (var diagnostic in emitter.Diagnostics)
{
diff --git a/test/Cnblogs.Architecture.UnitTests/Cqrs/ServiceAgentEmitterTests.cs b/test/Cnblogs.Architecture.UnitTests/Cqrs/ServiceAgentEmitterTests.cs
index 3c09a41..2dc060d 100644
--- a/test/Cnblogs.Architecture.UnitTests/Cqrs/ServiceAgentEmitterTests.cs
+++ b/test/Cnblogs.Architecture.UnitTests/Cqrs/ServiceAgentEmitterTests.cs
@@ -65,9 +65,14 @@ private static string EmitClass(params ManifestGroup[] groups)
}
private static string EmitExtensions(params ManifestGroup[] groups)
+ {
+ return EmitExtensions(new ServiceAgentEmitter(), groups);
+ }
+
+ private static string EmitExtensions(ServiceAgentEmitter emitter, params ManifestGroup[] groups)
{
var manifest = new EndpointManifest { Groups = groups.ToList() };
- var files = new ServiceAgentEmitter().Emit(manifest, "Cnblogs.Vip.ServiceAgent");
+ var files = emitter.Emit(manifest, "Cnblogs.Vip.ServiceAgent");
return files.First(f => f.IsExtensionsFile).Content;
}
@@ -212,9 +217,24 @@ public void Emit_Extensions_RegistersAllGroups()
new ManifestGroup { Name = "Vip", ErrorType = Error("VipError"), Endpoints = [] },
new ManifestGroup { Name = "Store", ErrorType = Error("StoreError"), Endpoints = [] });
+ // One AddXxxService method per group, each taking a baseUri argument (no --base-url supplied).
+ Assert.Contains("public static IServiceCollection AddVipService(this IServiceCollection services, string baseUri)", ext);
+ Assert.Contains("public static IServiceCollection AddStoreService(this IServiceCollection services, string baseUri)", ext);
Assert.Contains("AddServiceAgent(baseUri)", ext);
Assert.Contains("AddServiceAgent(baseUri)", ext);
- Assert.Contains("public static IServiceCollection AddServiceAgents(this IServiceCollection services, string baseUri)", ext);
+ Assert.DoesNotContain("AddServiceAgents", ext);
+ }
+
+ [Fact]
+ public void Emit_Extensions_BaseUrl_BakesUrlAndDropsParam()
+ {
+ var ext = EmitExtensions(
+ new ServiceAgentEmitter { BaseUrl = "http://corp_api" },
+ new ManifestGroup { Name = "Corp", ErrorType = Error("CorpError"), Endpoints = [] });
+
+ Assert.Contains("public static IServiceCollection AddCorpService(this IServiceCollection services)", ext);
+ Assert.Contains("AddServiceAgent(\"http://corp_api\")", ext);
+ Assert.DoesNotContain("baseUri", ext);
}
[Fact]