前言
上次,我们虽然用代码实现了“异步 Request-Reply 模式”,但是需要为每一个长时间操作 API 实现一个对应的 AsyncXXX 操作。
其实,可以尝试用 Source Generators 减少这种重复性劳动。.
实现思路
-
Controller 类必须是
partial
,这样才能为它额外增加新方法; -
为每个长时间操作 API 声明一个 AsyncMethodAttribute,这样 Source Generators 才知道为谁实现对应的同步操作;
-
遍历所有声明了 AsyncMethodAttribute 的方法,为其编写实现方法。
具体代码
1.添加 AsyncMethodAttribute
向待编译项目加入 AsyncMethodAttribute 代码:
const string asyncMethodAttributeText = @"using System;
namespace AsyncMethodGenerator
{
public sealed class AsyncMethodAttribute : Attribute
{
public AsyncMethodAttribute()
{
}
}
}
";
context.AddSource("AsyncMethodAttribute", SourceText.From(asyncMethodAttributeText, Encoding.UTF8));
2.遍历 AsyncMethodAttribute 声明方法
找到声明了 AsyncMethodAttribute 的所有方法:
private string GenerateMethods(SyntaxList<MemberDeclarationSyntax> members)
{
StringBuilder stringBuilder = new StringBuilder();
foreach (var member in members)
{
if(member is MethodDeclarationSyntax method && HasAsyncMethodAttribute(method))
{
stringBuilder.Append(GenerateAsyncMethod(method));
}
}
return stringBuilder.ToString();
}
private bool HasAsyncMethodAttribute(MethodDeclarationSyntax method)
{
var hasAttribute = false;
foreach (var attributeList in method.AttributeLists)
{
foreach (var attribute in attributeList.Attributes)
{
if (attribute.Name.ToString().Equals("AsyncMethod"))
{
hasAttribute = true;
}
}
}
return hasAttribute;
}
3.生成 AsyncMethod 代码
根据原方法定义,生成 AsyncMethod 方法:
private string GenerateAsyncMethod(MethodDeclarationSyntax method)
{
var stringBuilder = new StringBuilder();
foreach (var attributeList in method.AttributeLists)
{
foreach (var attribute in attributeList.Attributes)
{
if (attribute.Name.ToString().Equals("Route"))
{
stringBuilder.Append($@"[Route(""async/{attribute.ArgumentList.Arguments[0].ToString().Trim('"')}"")]");
}
else
{
stringBuilder.Append($"[{attribute}]");
stringBuilder.Append("\r\n");
}
}
}
stringBuilder.Append($"public async Task<IActionResult> Async{method.Identifier} ");
stringBuilder.Append($"({method.ParameterList.Parameters})");
stringBuilder.Append("\r\n");
stringBuilder.Append($@"
{{
string id = Guid.NewGuid().ToString();
string responseValue = $@""/status/{{id}}"";
_cache.SetString(id, responseValue);
Task.Factory.StartNew(() =>
{{
var result = {GenerateCallingMethod(method)}.Result;
_cache.SetString(id + ""_result"", JsonConvert.SerializeObject(result));
}});
return Accepted(responseValue);
}}
");
return stringBuilder.ToString();
}
4.使用
现在,就可以在目标项目中使用 AsyncMethod 方法了:
[ApiController]
[Route("[controller]")]
public partial class WeatherForecastController : ControllerBase
{
...
[HttpGet]
[Route("get")]
[AsyncMethod]
public async Task<IEnumerable<WeatherForecast>> Get()
{
...
}
}
注意 WeatherForecastController 是 partial class
结论
有了 Source Generators,可以让编译器帮我们自动实现“异步 Request-Reply 模式”了!