Blazor,这是单例滥用吗? (共享 iframe 值)
Blazor, Is this Singleton abuse? (Sharing iframe values)
首先,让我解释一下为什么我认为我需要一个单例。我正在将多个 Hosted Checkout 支付处理器集成到我的 Blazor Server 应用程序中。它们的工作原理如下;
Index.razor
有一个显示支付处理器的 Iframe url。
- 当客户完成付款时,iframe 重定向回我的应用程序
PaymentComplete.razor
指定的 url。
PaymentComplete.razor
使用 范围内的服务 HostedCheckoutService
向 Index.razor
发起包含付款响应的事件。
这就是问题所在。 PaymentComplete.razor
托管在 iframe 中,因此在单独的范围内处理。 HostedCheckoutService
在 PaymentComplete.razor
中引发的任何 属性 更改或事件都不会是 Index.razor
。这使得(几乎?)无法将 iframe 中的信息合并到 Index.razor
.
的范围内
显然可以解决此问题的方法是将 HostedCheckoutService
注册为 单例 。现在的问题是,当一个客户端从 PaymentComplete.razor
引发事件时, 所有 个客户端将不得不处理它。
为了解决这个问题,我创建了一个 IndexBase
,其中包含一个名为 EventTargetId
的唯一 属性。当iframe支付完成后,returnurl到PaymentComplete.razor
查询字符串中会包含EventTargetId
IndexBase.cs
<iframe style="width:100%;height:50vh" src="@HostedCheckoutFrameSrc " frameborder="0" ></iframe>
public class IndexBase : ComponentBase, IDisposable
{
[Inject] NavigationManager NavigationManager { get; set; }
[Inject] HostedCheckoutService HostedCheckoutService { get; set; }
[Inject] PaymentApi PaymentApi { get; set; }
public string HostedCheckoutFrameSrc { get; set; }
public string EventTargetId { get; set; } = Guid.NewGuid().ToString();
protected override void OnInitialized()
{
HostedCheckoutService.OnPaymentComplete += PaymentComplete;
}
public void Dispose()
{
HostedCheckoutService.OnPaymentComplete -= PaymentComplete;
}
private void PaymentComplete(string eventTargetId, string paymentJson)
{
// Hosted checkout iframe has returned a successfull payment.
// Do something, send order, notification, ect.
}
public async Task InitializePayment()
{
string returnUrl = NavigationManager.BaseUri + $"/PaymentComplete?eventTargetId={EventTargetId}";
InitializePaymentResponse response = await PaymentApi.CreatePaymentRequest(returnUrl);
// Set iframe src property to third party payment providers url.
// When customer completes third party payment url, the iframe redirects to PaymentComplete.razor (returnUrl).
HostedCheckoutFrameSrc = PaymentApi.baseUrl + response.PaymentId;
}
}
PaymentComplete.razor(从第三方 url 重定向,在 iframe 内托管)
此页面将从查询字符串中获取 EventTargetId
并在我们的单例服务上引发事件。
[Inject] NavigationManager NavigationManager { get; set; }
[Inject] PostFormService PostFormService { get; set; }
[Inject] HostedCheckoutService HostedCheckoutService { get; set; }
protected override async Task OnInitializedAsync()
{
// We face double render problem, but Form values will be null on secord render anyways.
if (PostFormService.Form != null)
{
NavigationManager.TryGetQueryString<string>("eventTargetId", out string eventTargetId);
string paymentJson = PostFormService.Form?["PaymentResponse"];
HostedCheckoutService.PaymentCompleted(eventTargetId, paymentJson);
}
}
在我的单例 HostedCheckoutService
中,我使用 EventTargetId
.
过滤掉所有订阅者
public class HostedCheckoutService
{
public event Action<string, string> OnPaymentComplete;
public void PaymentCompleted(string eventTargetId, string paymentJson)
{
// Instead of raising the event for every instance attached to this action
// only raise the event for the specified target.
var instance = OnPaymentComplete.GetInvocationList()
.Where(d => d.Target is IndexBase && ((IndexBase)d.Target).EventTargetId == eventTargetId)
.FirstOrDefault();
instance?.DynamicInvoke(eventTargetId, paymentJson);
}
}
终于有问题了!这看起来是不可接受的单例事件使用还是有人有更好的方法? 即使每个客户端都不会处理该事件,对 GetInvocationList()
的调用仍将包含每个订阅的列表 class.
注意: 每个事件订阅者实际上并不是完整的 IndexBase
class。这将是一个简单的支付组件(我简化了这个例子)。
我主要关心的是在事件上调用所有已注册方法的缩放。
因为我不知道 HostedCheckoutService
还能做什么,如果有一个单例 PaymentTransactionService
包含一个简单的 Guids against Actions 集合 - 可能还有操作超时系统的注册时间。 Index 在 PaymentTransactionService
上调用 Register
方法来注册它的 Guid 和 Action。 - 显然是 ReRegister
方法 Disposes
。 PaymentComplete
在 PaymentTransactionService
上调用了 TransactionComplete
方法。它会检查它的列表并执行已注册的操作(如果有)——如果没有则记录错误。您可以使用每个 PaymentComplete
调用来启动检查超时和删除过期注册的管理例程。
首先,让我解释一下为什么我认为我需要一个单例。我正在将多个 Hosted Checkout 支付处理器集成到我的 Blazor Server 应用程序中。它们的工作原理如下;
Index.razor
有一个显示支付处理器的 Iframe url。- 当客户完成付款时,iframe 重定向回我的应用程序
PaymentComplete.razor
指定的 url。PaymentComplete.razor
使用 范围内的服务HostedCheckoutService
向Index.razor
发起包含付款响应的事件。
这就是问题所在。 PaymentComplete.razor
托管在 iframe 中,因此在单独的范围内处理。 HostedCheckoutService
在 PaymentComplete.razor
中引发的任何 属性 更改或事件都不会是 Index.razor
。这使得(几乎?)无法将 iframe 中的信息合并到 Index.razor
.
显然可以解决此问题的方法是将 HostedCheckoutService
注册为 单例 。现在的问题是,当一个客户端从 PaymentComplete.razor
引发事件时, 所有 个客户端将不得不处理它。
为了解决这个问题,我创建了一个 IndexBase
,其中包含一个名为 EventTargetId
的唯一 属性。当iframe支付完成后,returnurl到PaymentComplete.razor
查询字符串中会包含EventTargetId
IndexBase.cs
<iframe style="width:100%;height:50vh" src="@HostedCheckoutFrameSrc " frameborder="0" ></iframe>
public class IndexBase : ComponentBase, IDisposable
{
[Inject] NavigationManager NavigationManager { get; set; }
[Inject] HostedCheckoutService HostedCheckoutService { get; set; }
[Inject] PaymentApi PaymentApi { get; set; }
public string HostedCheckoutFrameSrc { get; set; }
public string EventTargetId { get; set; } = Guid.NewGuid().ToString();
protected override void OnInitialized()
{
HostedCheckoutService.OnPaymentComplete += PaymentComplete;
}
public void Dispose()
{
HostedCheckoutService.OnPaymentComplete -= PaymentComplete;
}
private void PaymentComplete(string eventTargetId, string paymentJson)
{
// Hosted checkout iframe has returned a successfull payment.
// Do something, send order, notification, ect.
}
public async Task InitializePayment()
{
string returnUrl = NavigationManager.BaseUri + $"/PaymentComplete?eventTargetId={EventTargetId}";
InitializePaymentResponse response = await PaymentApi.CreatePaymentRequest(returnUrl);
// Set iframe src property to third party payment providers url.
// When customer completes third party payment url, the iframe redirects to PaymentComplete.razor (returnUrl).
HostedCheckoutFrameSrc = PaymentApi.baseUrl + response.PaymentId;
}
}
PaymentComplete.razor(从第三方 url 重定向,在 iframe 内托管)
此页面将从查询字符串中获取 EventTargetId
并在我们的单例服务上引发事件。
[Inject] NavigationManager NavigationManager { get; set; }
[Inject] PostFormService PostFormService { get; set; }
[Inject] HostedCheckoutService HostedCheckoutService { get; set; }
protected override async Task OnInitializedAsync()
{
// We face double render problem, but Form values will be null on secord render anyways.
if (PostFormService.Form != null)
{
NavigationManager.TryGetQueryString<string>("eventTargetId", out string eventTargetId);
string paymentJson = PostFormService.Form?["PaymentResponse"];
HostedCheckoutService.PaymentCompleted(eventTargetId, paymentJson);
}
}
在我的单例 HostedCheckoutService
中,我使用 EventTargetId
.
public class HostedCheckoutService
{
public event Action<string, string> OnPaymentComplete;
public void PaymentCompleted(string eventTargetId, string paymentJson)
{
// Instead of raising the event for every instance attached to this action
// only raise the event for the specified target.
var instance = OnPaymentComplete.GetInvocationList()
.Where(d => d.Target is IndexBase && ((IndexBase)d.Target).EventTargetId == eventTargetId)
.FirstOrDefault();
instance?.DynamicInvoke(eventTargetId, paymentJson);
}
}
终于有问题了!这看起来是不可接受的单例事件使用还是有人有更好的方法? 即使每个客户端都不会处理该事件,对 GetInvocationList()
的调用仍将包含每个订阅的列表 class.
注意: 每个事件订阅者实际上并不是完整的 IndexBase
class。这将是一个简单的支付组件(我简化了这个例子)。
我主要关心的是在事件上调用所有已注册方法的缩放。
因为我不知道 HostedCheckoutService
还能做什么,如果有一个单例 PaymentTransactionService
包含一个简单的 Guids against Actions 集合 - 可能还有操作超时系统的注册时间。 Index 在 PaymentTransactionService
上调用 Register
方法来注册它的 Guid 和 Action。 - 显然是 ReRegister
方法 Disposes
。 PaymentComplete
在 PaymentTransactionService
上调用了 TransactionComplete
方法。它会检查它的列表并执行已注册的操作(如果有)——如果没有则记录错误。您可以使用每个 PaymentComplete
调用来启动检查超时和删除过期注册的管理例程。