大火,这是辛格尔顿的滥用吗? (共享iframe值)
首先,让我解释为什么我认为我需要一个单身人士。我将几个托管的结帐支付处理器集成到我的Blazor Server应用程序中。他们所有人的工作如下;
index.razor
具有显示付款处理器URL的iFrame。- 当客户完成付款时,iframe重定向到我的应用程序指定的URL,
paymentcomplete.razor
。paymentcomplete.razor
使用 scoped服务hostedCheckoutservice
将事件提高到index.razor
包含付款响应。
这是问题出现的地方。 paymentcomplete.razor
托管在 iframe 中,因此用单独的范围处理。 hostedCheckoutservice
从paymentcomplete.razor
内部提出的任何属性更改或事件, be index.razor
。这使得(几乎?)无法从iframe内部合并到index.razor
的范围。
显然解决此问题的是将hostedcheckoutservice
注册为 singleton 。现在的问题是,当一个客户从paymentscomplete.razor
中提出事件时,所有所有客户都必须处理它。
为了解决这个问题,我创建了一个indexBase
,其中一个名为eventTargetID
的唯一属性。当iFrame付款完成后,返回到peayscomplete.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
从查询字符串中升级并升高我们的Singleton服务的活动。
[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);
}
}
在我的singleton 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);
}
}
最后,问题!这似乎是对Singleton事件的不可接受的使用,还是有人使用更好的方法?即使每个客户都无法处理事件,呼叫getInvocationList()
仍然包含一个列表每个订阅的班级。
注意:每个事件订户实际上并不是完整的indexBase
类。这将是一个简单的付款组件(我简化了此示例)。
First of all, let me explain why I think I need a singleton. I'm integrating several Hosted Checkout payment processors into my Blazor Server application. All of them work as follows;
Index.razor
has an Iframe that displays a payment processors url.- When the customer completes the payment the iframe redirects back to a url specified by my application,
PaymentComplete.razor
.PaymentComplete.razor
uses a scoped serviceHostedCheckoutService
to raise an event toIndex.razor
containing the payment response.
This is where the problem comes in. PaymentComplete.razor
is hosted inside an iframe therefore is treated with a separate scope. Any property changes or events raised by HostedCheckoutService
from within PaymentComplete.razor
wont be Index.razor
. This makes it (nearly?) impossible to merge information from within the iframe to the scope of Index.razor
.
What obviously solves this issue is registering HostedCheckoutService
as a singleton. Now the problem is that when one client raises an event from PaymentComplete.razor
, all clients will have to handle it.
To solve this I created an IndexBase
with a unique property named EventTargetId
. When the iframe payment is completed, the return url to PaymentComplete.razor
will contain the EventTargetId
in the query string.
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 + quot;/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 (redirected from third party url, hosted inside iframe)
This page will grab the EventTargetId
from the query string and raise an event on our singleton service.
[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);
}
}
In my singleton HostedCheckoutService
, I filter out any subscribers using the 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);
}
}
Finally, the question! Does this seem like an unacceptable use of singleton events or does anyone have a better approach? Even though every client wont be handling the event, a call to GetInvocationList()
would still contain a list of every subscribed class.
Note: Each event subscriber would not actually be a full IndexBase
class. It would be a simple payment component (I simplified this example).
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我主要关注的是扩展事件中的所有注册方法。
由于我不知道还有什么
hostedcheckoutservice
做了什么,让Singletonpeays paymentstransactionservice
包含一个简单的针对操作的GUID集合 - 并且可能需要注册时间以操作超时系统。索引调用寄存器
payment> paymentstransactionservice
注册它的GUID及其动作。 - 显然,当处置时,reregister
方法。paymentcomplete
调用transactionComplete
payment> Payment Transactionservice
的方法。它检查其列表并执行注册操作,如果没有一个列表 - 并记录一个错误。您可以使用每个paymentcomplete
调用还可以启动一个管理例程,以检查超时并删除过期的注册。My main concern would be scaling on calling all the registered methods on the event.
As I don't know what else
HostedCheckoutService
does, what about having a singletonPaymentTransactionService
that contains a simple collection of Guids against Actions - and probably registration times to operate a timeout system . Index calls aRegister
method onPaymentTransactionService
to register it's Guid and it's Action. - and obviously aReRegister
method when itDisposes
.PaymentComplete
calls aTransactionComplete
method onPaymentTransactionService
. It checks it's list and executes the registered action if there is one - and logs an error if there isn't. You can use eachPaymentComplete
call to also kick off a managment routine that checks timeouts and removed expired registrations.