Blazor, Is this Singleton abuse? (Sharing iframe values) - c#

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 service HostedCheckoutService to raise an event to Index.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 + $"/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).

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 singleton PaymentTransactionService that contains a simple collection of Guids against Actions - and probably registration times to operate a timeout system . Index calls a Register method on PaymentTransactionService to register it's Guid and it's Action. - and obviously a ReRegister method when it Disposes. PaymentComplete calls a TransactionComplete method on PaymentTransactionService. 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 each PaymentComplete call to also kick off a managment routine that checks timeouts and removed expired registrations.

Related

Blazor: Awaiting a task of type string within a foreach loop [duplicate]

I have a server-side blazor client and I'm trying to modify the MainLayout razor page by having a Login check. I'm currently using Blazored for localstorage saving, and I'm currently using to see if a token is saved to see if user is logged in, however I'm not sure how I translate this in the if statement in razor page because it wants async method.
My login check is pretty simple as shown below.
public async Task<bool> IsLoggedIn()
{
return await m_localStorage.ContainKeyAsync("token").ConfigureAwait(false);
}
In my Razor page I'm doing this statement check - which obvious doesn't work as there's no async modifier
#if (!await AppState.IsLoggedIn()) //Requires async modifier
{
Login
}
I've also tried doing it using the .Result property, but this results in an exception thrown: (System.AggregateException: 'Information: Executed an implicit handler method, returned result Microsoft.AspNetC)' with an inner-exception -> NullReferenceException: Object reference not set to an instance of an object.
But from what I can see AppState is injected correctly and the local storage seems to be injected correctly in AppState.
#if (!AppState.IsLoggedIn().Result)
{
Login
}
So my question is what is the correct way to approach this, is there a way to execute async methods in razor pages?
is there a way to execute async methods in razor pages?
No, there isn't a way to use await in a Razor component. This is because you can't do async work as part of the rendering of the component.
Incidentally, the local storage mechanism provided by the Blazor team supports data protection, and is recommended for use by Steve Sanderson.
Note: The async Lifecycle methods of the component are where async work is done, and thus you can design your code accordingly, as for instance, calling AppState.IsLoggedIn() from OnInitializedAsync, and assigning the returned value to a local variable which can be accessed from your views.
AsyncComponent.razor
#typeparam TResult
#typeparam TInput
#if (Result != null)
{
#DataReadyFragment(Result)
}
else if (DataMissingFragment != null)
{
#DataMissingFragment
}
#code {
[Parameter] public RenderFragment<TResult> DataReadyFragment { get; set; }
[Parameter] public RenderFragment DataMissingFragment { get; set; }
[Parameter] public Func<TInput, Task<TResult>> AsyncOperation { get; set; }
[Parameter] public TInput Input { get; set; }
TResult Result { get; set; }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if(firstRender)
AsyncOperation.Invoke(Input).ContinueWith(t => { Result = t.Result; InvokeAsync(StateHasChanged); });
}
}
Usage
<AsyncComponent TResult="User" TInput="string" Input="Pa$$W0rd" AsyncOperation="#AsyncMethodName">
<DataReadyFragment Context="result">
#if(result.IsLoggedIn)
{
<h3>Logged-In , Username:#result.Name</h3>
}
else
{
<h3>Wrong Password</h3>
}
</DataReadyFragment>
<DataMissingFragment>
<h3>Please Login :)</h3>
</DataMissingFragment>
</AsyncComponent>
Based on LazZiya example, worked for me. In my case an event was not async as supported by a component, but the call I had required await. Using this example I could return data from the call based on a model.
public string Name => Task.Run(() => Service.GetNameAsync()).GetAwaiter().GetResult();
I ended up with this and it worked like a charm.
Task<AssessmentResponsesModel> task = Task.Run(() => _IAssessmentResponse.CreateAsync(APIPathD, argsItem, token).GetAwaiter().GetResult());

Can't get SignalR Hub variables to stop losing value (static variables not the issue)

I have a simple SignalR server, which I have almost no experience with.
My hub class is:
public class MyHub : Hub
{
public List<Player> players { get; set; }
public MyHub()
{
players = new List<Player>();
}
public void Searching(Player player)
{
players.Add(player);
//Clients.All.a
}
}
Here is where I call the Searching method and the variables in my code
public String UserName { get; set; }
public IHubProxy HubProxy { get; set; }
const string ServerURI = "http://localhost:5051/signalr";
public HubConnection Connection { get; set; }
public Player User { get; set; }
public MainWindow()
{
InitializeComponent();
User = new Player(100);
ConnectAsync();
}
private void btnSearch_Click(object sender, RoutedEventArgs e)
{
HubProxy.Invoke("Searching", User);
}
In MyHub class, I would like the players list to (for now) keep track of every time someone clicks the btnSearch on the client, and by doing this I am simply adding a member of the Player class to the players list in MyHub.
For some reason, every time I call this, using the debugger I can see that the list is null again, the constructor is called again, and the players list is empty again.
I've seen in other stack overflow questions that the problem is static variables, however nothing in mine is static. (Nothing in the Player class is static either)
Basically, hubs are transient, meaning you cannot know when an instance will be recycled or discarded. One way to solve this would be using a Singleton for the state, but there are many options. This is straight from MSDN [1]:
"You don't instantiate the Hub class or call its methods from your own
code on the server; all that is done for you by the SignalR Hubs
pipeline. SignalR creates a new instance of your Hub class each time
it needs to handle a Hub operation such as when a client connects,
disconnects, or makes a method call to the server.
Because instances of the Hub class are transient, you can't use them
to maintain state from one method call to the next. Each time the
server receives a method call from a client, a new instance of your
Hub class processes the message. To maintain state through multiple
connections and method calls, use some other method such as a
database, or a static variable on the Hub class, or a different class
that does not derive from Hub. If you persist data in memory, using a
method such as a static variable on the Hub class, the data will be
lost when the app domain recycles.
If you want to send messages to clients from your own code that runs
outside the Hub class, you can't do it by instantiating a Hub class
instance, but you can do it by getting a reference to the SignalR
context object for your Hub class. For more information, see How to
call client methods and manage groups from outside the Hub class later
in this topic."
[1]:
https://www.asp.net/signalr/overview/guide-to-the-api/hubs-api-guide-server
"SignalR documentation"

Using the subdomain as a parameter

I’ve got an ASP.net MVC (5.2) site that runs using several subdomains, where the name of the subdomain is the name of a client in my database. Basically what I want to do is use the subdomain as a variable within my action methods to allow me to get the correct data from my database.
I did something similar a few years back, but it’s messy and not intuitive, so was wondering if there’s a better way to do it than I was using before. Here’s what I did before:
protected override void OnActionExecuting(ActionExecutingContext filterContext) {
Session["subdomain"] = GetSubDomain(Request.Url);
}
private static string GetSubDomain(Uri url) {
string host = url.Host;
if (host.Split('.').Length > 1) {
int index = host.IndexOf(".");
string subdomain = host.Substring(0, index);
if (subdomain != "www") {
return subdomain;
}
}
return null;
}
Which basically assigned a key to the session variable if the subdomain was anything other than "www", but I’m really not happy with this way of doing it as it relies on me knowing that the session might contain this magic value!
Ideally I’d like to be able to create an attribute that I can decorate my classes/methods with that would extract the subdomain and then allow me to include a "subdomain" parameter in my action method that would contain the value extracted by the attribute. Is that even possible?
If that can’t be done, is there a better way of doing what I’m doing now without having to rely on the session?
Thanks,
Dylan
Your right this doesn't need to be stored in Session and IMHO shouldn't be, I would refactor this out into its own class and use HttpContext.Current.
public interface ISubDomainProvider
{
string SubDomain { get; set; }
}
public class SubDomainProvider : ISubDomainProvider
{
public SubDomainProvider()
{
string host = HttpContext.Current.Request.Url.Host; // not checked (off the top of my head
if (host.Split('.').Length > 1)
{
int index = host.IndexOf(".");
string subdomain = host.Substring(0, index);
if (subdomain != "www")
{
SubDomain = subdomain;
}
}
}
public string SubDomain { get; set; }
}
You choose how to use it, if your using an IoC container it would just be a case of injecting this class into your controller via the constructor, I like this because it is easier to Mock and Unit Test. Of course you can still do this:
public class SomeController : Controller
{
private readonly ISubDomainProvider _subDomainProvider;
public SomeController()
{
_subDomainProvider = new SubDomainProvider();
}
}
You could even create you own abstract Controller Class:
public abstract class MyAbstractController : Controller
{
public MyAbstractController()
{
SubDomain = new SubDomainProvider();
}
protected string SubDomain {get; set; }
}
public class SomeController : MyAbstractController
{
public ActionResult SomeAction()
{
// access the subdomain by calling the base base.SubDomain
}
}
You could set the name in the Session on the Session_Start event in the global.asax, this means it would only happen one time and would persist for the duration of the users' session
public void Session_Start(object sender, EventArgs e)
{
Session["subdomain"] = GetSubDomain(Request.Url);
}
Looks like there’s a good way of doing what I’m after at:
ASP.NET MVC Pass object from Custom Action Filter to Action
It essentially uses the route data to pass a custom parameter to the action, and can also pass objects other than simple strings etc.
On the plus side it avoids using the session and relying on magic values, but on the downside it means processing the URL for every request, which probably isn’t a good idea if a database is involved.

Multiple Ajax Requests per MVC 4 View

I'm using the repository pattern with a context and ninject as the IOC. I have a service which handles getting and setting page properties in the database.
public class MyContext : DbContext
{
public MyContext() : base ("DefaultConnection")
{
}
public DbSet<PageProperty> PageProperties { get; set; }
public DbSet<Contact> Contacts { get; set; }
}
public class DefaultRepository : IRepository
{
MyContext _context;
public DefaultRepository(MyContext context)
{
_context = context;
}
public IQueryable<PageProperty> PageProperties { get { return _context.PageProperties; } }
public IQueryable<Contact> Contacts { get { return _context.Contacts; } }
}
public class ModuleLoader : NinjectModule
{
public ModuleLoader()
{
}
public override void Load()
{
var context = new MyContext();
context.Database.Initialize(false);
Bind<MyContext>().ToConstant(context).InSingletonScope();
Bind<IRepository>().To<DefaultRepository>();
Bind<IPagePropertyProvider>().To<DefaultPagePropertyProvider>().InSingletonScope();
}
}
public class DefaultPagePropertyProvider : IPagePropertyProvider
{
IRepository _repository;
object _syncLock = new object();
public DefaultPagePropertyProvider (IRepository repository)
{
_repository = repository;
}
public string GetValue(string pageName, string propertyName
{
lock (_syncLock)
{
var prop = page.PageProperties.FirstOrDefault(x => x.Property.Equals(propertyName) && x.PageName.Equals(pageName)).Value;
return prop;
}
}
public void SetValue(string pageName, string propertyName, string value)
{
var pageProp = _repository.PageProperties.FirstOrDefault(x => x.Property.Equals(propertyName) && x.PageName.Equals(pageName));
pageProp.Value = value;
_repository.SaveSingleEntity(pageProp);
}
}
In my view I am doing 3 ajax calls, one to get a list from contacts to fill out a table, one ajax call to determine how many pages i have depending on the page size I'm using, and one ajax call to set the page size that I want to use. so a select box changes the page size (How many contacts per page: [ 30]) , a table that displays the contacts (generated from jquery which decifers json), and finally a div containing a list of page numbers to click. The workflow is, call GetContacts(), this function then queries the PageProperties to find out the page size to use, then call GetPages(), this function also queries PageProperties to find out what page size to use, SetPageSize() which sets the page size. So GetContacts() and GetPages() is used when a page is selected from the div, SetPageSize() then GetContacts() and GetPages() is called when the select box change event is fired. GetContacts() and GetPages() is only called when the first SetPageSize() $.ajax request is done() and there is a success from that function.
Now, before I added lock(syncLock) in the DefaultPageProperty service and before I added InSingletonScope to both that service and the context, I was getting two errors.
The connection was not closed. The connection's current state is connecting.
An EdmType cannot be mapped to CLR classes multiple times
I assumed because the connection was in a connecting state, that the context was being reused and reused and reused, so I thought putting that to SingletonScope() would mean that only one connection was made, then I thought the same about DefaultPageProperty and then because I was making async calls to that service, I should put a lock over the database querying.
It works, and the problems don't exist. But I don't know if what I have done is correct within the pattern I'm using, I'm wondering if I've missed something fundamental? My question is, is this a proper/viable solution which won't create any caveats later down the road? Have I actually solved the issue or just created more?
I redesigned the way I do my context now.
I have my context then I implement IDbContextFactory<TContext> called DefaultContextFactory<MyContext> and I inject them.
In the Repository I have in the public constructor _context = contextFactory.Create();.
Then throughout the repository i just use _context.WhatEver() and its fine.
I also did in the ModuleLoader Bind<IRepository>().To<DefaultRepository>().InTransientScope() in order to make every call to it create a new repository!
I don't need a repository factory because I only have one repository!

StructureMap doesn't appear to be ready during HttpModule constructor -- is this correct?

This question is more to confirm my diagnosis of an issue we encountered--or to find alternative explanations.
We have an HTTPModule which intercepts every request made to our webforms application. It's job is to translate specific querystring parameters which our integration partners send.
More importantly, it was wired to StructureMap like this:
public class SomeModule : IHttpModule
{
public SomeModule()
{
ObjectFactory.BuildUp(this);
}
public IDependency Dependency { get; set; }
}
During a previous release it appeared that the module wasn't being injected by the time it executed it's request-processing. That led to some (ugly) defensive check being added like:
public class SomeModule : IHttpModule
{
public SomeModule()
{
ObjectFactory.BuildUp(this);
if (SomeDependency == null)
{
// HACK: Not sure why this corrects the issue!
Dependency = ObjectFactory.GetInstance<ISomeDependency>();
}
}
public IDependency Dependency { get; set; }
}
You'll notice the HACK comment -- it resolved the issue but without good reason.
Well, this same module has been re-purposed on another site--and the previous hack no longer worked. After looking at it for some time I made the change to move the StructureMap call outside the constructor, and lo-and-behold, it works.
public class SomeModule : IHttpModule
{
public IDependency Dependency { get; set; }
public void IHttpModule.Init(HttpApplication context)
{
Initialize();
// the rest of the code
}
private bool _initialized;
private void Initialize()
{
if (_initialized)
{
return;
}
ObjectFactory.BuildUp(this);
_initialized = true;
}
}
So, my I have a few questions around this behavior:
My suspicion is that StructureMap was not fully initialized/configured when the HttpModule constructor was being called -- agree/disagree, any insight?
I haven't found any reference materials that state when to expect StructureMap to be initialized and ready to service requests. Is there any such documentation?
I wouldn't expect the behavior to be very predictable when you're trying to build up a type in its constructor. Many actions that are normally considered safe are not considered safe in a constructor (like using a virtual property in a class).
Where you moved the code to looks much better and I would leave it there. If you can't have the container create the instance itself (and therefore are forced to build it up) some sort of Initialize method is the preferred place to do the build up action.
To answer the question you have at the end of your post:
It is up to the developer to determine when StructureMap is initialized. In a web application, this is almost always done in the Global.asax in Application_Start(). In that case, I would expect the container to be ready when your module is called.

Categories

Resources