inversionhourglass / Rougamo

Compile-time AOP component. Works with any method, whether it is async or sync, instance or static. Uses an aspectj-like pattern to match methods.
MIT License
393 stars 47 forks source link

关于 MoAttribute 中获取 IServiceProvider 对象的问题 #57

Closed 2881099 closed 8 months ago

2881099 commented 8 months ago

最近接触 blazor SSR,使用 AsyncLocal 的方式存储 IServiceProvider 不再凑效了。

blazor SSR 的特点是长连接,ioc Scoped 正常使用没问题,AsyncLocal 在这种模式下很容易丢失上下文,原因是长链接各种异步 UI 操作,一言难尽。

希望肉夹馍提供与 IServiceProvider 设置有关的方式。

2881099 commented 8 months ago

例如按照约定方法:

public class xxx
{
    IServiceProvider Service; // Rougamo 内部获取这个字段或属性

    [XxxMo]
    public void xxx() {}
}

class XxxMo : MoAttrite
{
    public override void OnEntry(MethodContext context)
    {
        var service = context.GetService(); //按照约定方式
    }
}
2881099 commented 8 months ago
class XxxMo : MoAttrite
{
    public override void OnEntry(MethodContext context)
    {
        var prop = context.TargetType.GetProperty("ServiceProvider", BindingFlags.Public | BindingFlags.NonPublic);
        if (prop == null) throw new Exception($"{context.TargetType.DisplayCsharp()} 未定义 IServiceProvider ServiceProvider {{ get; set; }}");
        var service = prop.GetValue(context.Target);
    }
}

已解决,谢谢。

2881099 commented 8 months ago

反馈一下问题,关于泛型下 TargetType 使用 Mo 的处理:

new MethodContext(this, typeof(Xxx<>)

TargetType 使用的不是 typeof(Xxx\<int>)

inversionhourglass commented 8 months ago

感谢反馈,稍后将进行修复

inversionhourglass commented 8 months ago

回顾了一下代码,TargetType这样的表现不是bug,TargetType是编译时获取的定义的类型本身,无法得知使用时的泛型类型参数,如果需要获取包含泛型类型参数的实际类型,需要通过Target.GetType()来获取了。需要注意的是,如果方法时静态方法,Targetnull

inversionhourglass commented 1 month ago

老哥现在对blazor ssr的IServiceProvider的获取有没有什么好的方式。前些天封装了AspNetCore和通用主机获取IServiceProvider,然后有人反馈希望能支持blazor,我看了下blazor的scope关系,感觉有点难整,也没有找到比较好的blazor生命周期事件进行操作。

2881099 commented 1 month ago

https://github.com/2881099/AdminBlazor/blob/master/AdminBlazor/DataAnnotations/TransactionalAttribute.cs

public override void OnEntry(MethodContext context)
{
    var targetType = context.Target.GetType();
    var service = targetType.GetPropertyOrFieldValue(context.Target, 
        "ServiceProvider") as IServiceProvider;
    if (service == null)
        throw new Exception($"_Imports.razor 未使用 @inject IServiceProvider ServiceProvider");
}

我目前是这样处理的,前提要在 _Imports.razor 定义

https://github.com/2881099/AdminBlazor/blob/master/AdminBlazor/_Imports.razor

inversionhourglass commented 1 month ago

感谢老哥的分享。这两天看blazor源码看得头疼,感觉目前blazor还是太封闭了,在各个生命周期节点没有提供切面点,关键的类型也全是internal sealed的,甚至连注册到ServiceProvider的接口都是internal的,完全不给自定义扩展留口子

2881099 commented 1 month ago

ssr ioc scoped 是整个 websocket,和传统 webapi 不一样。

inversionhourglass commented 1 month ago

嗯,不仅如此,他的scope还是穿插使用的,与websocket一对一关联scope是在创建CircuitHost时创建的

https://github.com/dotnet/aspnetcore/blob/4bc867dee200dffb10c9e3693b875311407bdfc6/src/Components/Server/src/Circuits/CircuitFactory.cs#L46-L48

而在界面上点击按钮时,服务端执行点击事件对应方法时又会在DefaultHubDispatcher中再创建一个scope,这时就是两个不同scope中的对象穿插使用了

https://github.com/dotnet/aspnetcore/blob/4bc867dee200dffb10c9e3693b875311407bdfc6/src/SignalR/server/Core/src/Internal/DefaultHubDispatcher.cs#L318-L325

这么做其实也好理解,点击事件中产生的部分对象只需要在本次点击事件中存活,不需要关联到整个websocket生命周期。

其实他的scope怎么处理问题都不大,最大的问题就是做得太封闭,我目前知道的只有可以通过CircuitHandler在websocket连接和断开时做额外处理,像每次界面点击的这种回调没找到相关扩展方式。

2881099 commented 1 month ago

我没有研究这么深入,怕后续版本改动。

inversionhourglass commented 1 month ago

确实,这么多internal类型和接口,可能随时都会改掉内部实现,这么费力可能也只适用于当前版本

inversionhourglass commented 1 month ago

我还是用AsyncLocal来实现的,通过重写ComponentBaseIHandleEvent接口方法来实现。

https://github.com/inversionhourglass/DependencyInjection.StaticAccessor/blob/master/src/DependencyInjection.StaticAccessor.Blazor/DependencyInjection/StaticAccessor/Blazor/PinnedScopeComponentBase.cs

我现在已经针对Blazor编写了对应的肉夹馍DI扩展,可以直接引用NuGet,参考:https://github.com/inversionhourglass/Rougamo.DI/tree/master/samples/BlazorServerApp