Blazor,这是对独生子女的虐待吗?(共享iframe值)



首先,让我解释一下为什么我认为我需要一个单例。我正在将几个托管Checkout支付处理器集成到我的Blazor服务器应用程序中。它们的工作方式如下:

  • Index.razor有一个Iframe显示支付处理器的url。
  • 当客户完成支付时,iframe重定向回我的应用程序指定的url,PaymentComplete.razor
    • PaymentComplete.razor使用作用域服务HostedCheckoutServiceIndex.razor触发一个包含支付响应的事件。

这就是问题所在PaymentComplete.razor托管在iframe中因此用单独的作用域来处理。HostedCheckoutServicePaymentComplete.razor中引发的任何属性更改或事件都不会是Index.razor。这使得(几乎)不可能将iframe内的信息合并到Index.razor的范围。

显然解决这个问题的是将HostedCheckoutService注册为单例. 现在的问题是,当一个客户端从PaymentComplete.razor引发事件时,所有客户端必须处理它。

为了解决这个问题,我创建了一个IndexBase,它有一个名为EventTargetId的唯一属性。当iframe支付完成后,返回PaymentComplete.razor的url将在查询字符串中包含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()的调用仍然包含每个订阅类的列表。

注意:每个事件订阅者实际上并不是一个完整的IndexBase类。它将是一个简单的支付组件(我简化了这个例子)。

我主要关心的是在调用事件上的所有注册方法时进行缩放。

因为我不知道HostedCheckoutService还能做什么,如果有一个单例PaymentTransactionService,它包含一个简单的针对动作的Guids集合——可能还有注册时间来操作超时系统。Index在PaymentTransactionService上调用Register方法来注册它的Guid和Action。-当它是Disposes时,显然是ReRegister方法。PaymentCompletePaymentTransactionService上调用TransactionComplete的方法。它检查它的列表,如果有,就执行注册的动作——如果没有,就记录一个错误。您可以使用每个PaymentComplete调用来启动检查超时和删除过期注册的管理例程。

最新更新