Blazor singleton object recreated on navigation - c#

My singleton object does not keep its state when loading a new page in blazor.
I have this interface:
public interface IPreLaunchSession
{
bool IsPreLaunchAuthenticated { get; set; }
}
The class for this interface
public class PreLaunchSession : IPreLaunchSession
{
public bool IsPreLaunchAuthenticated { get; set; }
public PreLaunchSession()
{
}
}
I register it as a singleton
builder.Services.AddSingleton<IPreLaunchSession,PreLaunchSession>();
Then inject it into the page
#inject Services.IPreLaunchSession PreLaunchSession
In these pages where its injected, I test for the bool variable as well as set it.
In another section of code, I redirect using NavigationManager. Something like this:
Navigation.NavigateTo("/MyOtherPath");
This seems to work for a while but then a <NavLink/> is used to go to another page and as soon as this happens then the PreLaunchSession object seems to be recreated and the bool variable in it is reset to default (which is false)
Any help here would be great in understanding why a singleton object gets recreated with navigation happening.

Have a look at how to persist value in a singleton state container in blazor web assembly on page reload
When refreshing the app (F5 etc) the app is essentially turned off and turned on again.
This would cause the whole app to restart and all memory of persisted singleton objects would be lost.
I ended up using localstorage to persist between full refreshes or tab changes.

Related

Why is my scoped service being called as a new instance every time?

This is a practice ASP.NET project I'm using to better understand a few techniques, and while I've got Dependency Injection working, its not working quite as I want it to. I have a class that I want to use to store a history, so every time the user hits a submit button, it displays a result, and after the second time it starts displaying the history. Anyway I added the history to the DI as a scoped service, thinking that would mean it would be created and then remain the same instance for the duration of the session for that user. However according to the debugger it looks like the list never gets bigger than one, and thats at the point of adding the item to the list. So the code.
The object
{
public class RollHistory : IRollHistory
{
public List<IRollMessage> Entries { get; set; } = new List<IRollMessage>();
}
}
The DI
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddTransient<IDiceTray, DiceTray>();
services.AddTransient<IRollMessage, RollMessage>();
services.AddScoped<IRollHistory, RollHistory>();
}
The Controller constructor
public HomeController(ILogger<HomeController> logger, IDiceTray diceTray, IRollMessage rollMessage, IRollHistory rollHistory)
{
_logger = logger;
_diceTray = diceTray;
_rollMessage = rollMessage;
_rollHistory = rollHistory;
}
And the code for when the button gets clicked
[HttpPost]
public IActionResult Index(DiceRollModel diceRoll)
{
_diceTray.DiceRoll(diceRoll.DiceType, diceRoll.DiceCount, diceRoll.Bonus, diceRoll.VantageType);
_rollMessage.RollMessages(_diceTray);
diceRoll.RollResult = _rollMessage;
_rollHistory.Entries.Add(_rollMessage);
diceRoll.History = _rollHistory.Entries;
return View(diceRoll);
}
It's worth noting I've tried to code this at least 4 different ways with and without DI, the only way it works is if I use AddSingleton, while this might not be an issue because this app is unlikely to ever be live, its a poor excuse not to do it right.
I believe “scope” is by default per request which would explain that each submit gets is own service.
“Doing stuff right” is of course to some extend a matter of opinion. But my opinion would clearly be that I would avoid server-side session to avoid problems with scaling to more than one instance. There are also ways to support shared state, but this is difficult. To me singletons are not a code smell either, but they have their own problems.
Your problem might be solved by storing whatever state you need in the browser either in a cookie or localStorage. Your service would then have request scope, but it would read user state from browser causing “user scope” for the data. (But don’t rely on browser state to persist and remember it is modifiable to the user.)

Blazor Server Scoped Inititialized again on refreshing the page

I created a simple Blazor Server Application:
The class "UserSettings.cs" is injected in the components.
public class UserSettings
{
public Guid Id{ get; } = Guid.NewGuid();
}
services.AddScoped<UserSettings>();
I display the Id inside the UserSetting Service in different Components.
After switching the component, the Id stays the same, but when i refresh the page, it changes.
Why does the Service initialized again when refreshing the page?
Your service is scoped to the circuit connection...When you refresh the page, a new circuit object is created. You may use the AddSingleton instead, but in that case all your users may see the same ID.
Why not create the Id when your App is rendered for the first time, and save it in a session storage or local storage for use ?

Ninject Creating an Extra Instance when using NinjectHttpModule

For some reason Ninject is creating an additional instance of my object when I use NinjectHttpModule in my MVC 4 app.
If I use NinjectHttpModule (the Ninject.MVC3 default) but do not actually have any IHttpModule classes that require constructor injection, it works fine. But as soon as I create a class that implements IHttpModule and that requires constructor injection, Ninject for some reason creates two instances of my object.
I added some tracking code to the class that is being duplicated to verify that it was being duplicated. Every time an instance is created, the static count variable is incremented:
namespace Trigger.Events
{
public class TriggerEventRegistry : ITriggerRegistry
{
private static int count;
public TriggerEventRegistry()
{
TriggerEventRegistry.count++;
}
}
}
Here is my IHttpModule:
namespace TriggerDevelopment.ApplicationTriggers
{
public class RegisterTriggerComponentsHttpModule : IHttpModule
{
ITriggerEventRegistry eventRegistry;
public RegisterTriggerComponentsHttpModule(ITriggerEventRegistry eventRegistry)
{
this.eventRegistry = eventRegistry;
}
}
....
}
By the time a TriggerEventRegistry is injected into my controller (on the same request), the TriggerEventRegistry.count equals 2. If I comment out the constructor on RegisterTriggerComponentsHttpModule, then the value of TriggerEventRegistry.count equals 1 (which is should since there should only be one instance/request).
Here is the binding code:
Bind<ITriggerEventRegistry>().To<TriggerEventRegistry>().InRequestScope();
Any help on this would be greatly appreciated!
Note
I even made a request to my app using curl to avoid multiple HTTP requests being made by the browser looking for assets, a favicon or something like that. Still no joy.
Update
Upon further investigation, I'm also seeing that the ctor and the Init method of RegisterTriggerComponentsHttpModule is being called twice.
It's going to call your HttpModule as many times as there are requests. For instance, most web browsers submit at least two requests, the page request and a favicon request. Try adding something like an image to the page, and see if you get three requests...

Unity 2 is behaving strangely with generics

I am right now having big trouble with Unity 2 in my MVC 3 project.
I have created an abstract BaseViewPage that all viewpages inherits from. In this i have 2 dependencies. So far so good.
Now i have 3 different Viewpages that inherit from the BaseViewPage. These uses generics to deliver some specific data to the view. So far so good.
Now comes the problem.
In my homecontroller i use unity to resolve one of those viewpages. The viewpage gets loaded correctly when i debug it but right after my call to return view("index", model); unity makes a call to one of those dependencies inside the baseviewpage. This is done when the httpcontext is null.
Unity config (Loads all the viewpages):
container.RegisterType<IBackendWrapper, BackendWrapper.BackendWrapper>(new PerThreadLifetimeManager());
container.RegisterType<BaseViewPage, EmptyViewPage>("EmptyViewPage");
container.RegisterType(typeof(BaseViewPage), typeof(GenericViewPage<>), "GenericViewPage");
container.RegisterType(typeof(BaseViewPage), typeof(GenericIEnumerableViewPage<>), "GenericIEnumerableViewPage");
BaseViewPage and one generic view page (the other generic pages looks almost the same):
public abstract class BaseViewPage
{
[Dependency]
public IBackendWrapper Backend { get; set; }
....
}
public class GenericViewPage<T> : BaseViewPage
{
public T Model { get; set; }
public GenericViewPage(T model)
: base()
{
Model = model;
}
}
Now in my home controller i have first a dependency to Backend (to test that it works), then inside the Index i use unity to resolve one generic view page:
public class HomeController : Controller
{
[Dependency]
public IBackendWrapper Backend { get; set; }
public ActionResult Index(MvcLoginUser user)
{
var model = UnityGlobalContainer.Container.Resolve<GenericViewPage<MvcLoginUser>>("GenericViewPage");
return View("Index", model);
}
}
Now after the return, unity makes a call to the BackendWrapper object. More precisely BackendWrapper.UserIdent.TheLogin. TheLogin throws an error since there is no HttpContext present. Question is, why does unity try to access it? I have implemented a dispose inside the BackendWrapper and inside the UserIdent but unity ignores them and still calls TheLogin.
Inside the BackendWrapper i have a constructor that calls an external dll to create a new UserIdent. So its nothing that Unity is resolving. But unity still tries to access it.
Also, if i remove the dependency from the BaseViewPage it works perfectly. So only when BaseViewPage has a dependency to BackendWrapper is this problem occurring.
Did i configure it correctly?
I found out that this "Application_EndRequest" was hit BEFORE unity has cleaned up its objects. This in turn terminated our NHibernate session, so when unity then tried to dispose of all objects inside the created classes it would fail. Still have not figured out why it tries to dispose of non created classes. The wrapperclass was created by unity and the wrapper class in turn created the UserIdent. My guess is that the garbage collector or something ran and tried to go through all the created classes and their child objects. This was done outside the HttpContext which resulted in several exceptions.

Will the static public variables in my app get shared with other users in the same app?

For reasons I would rather not discuss, I need to create a custom authentication system for my app. I was just reviewing the system and am having some doubts if my solution is thread safe. My goal was to create a solution that would allow my app to authenticate a user one time and that users authentication info would be shared by all master pages, pages, classes, user controls, etc that are used. (But not share the same info between users)
Here is my setup:
PageHttpModule.cs - this is added to the web.config as a httpModule.
public class PageHttpModule : IHttpModule
{
public void Init(HttpApplication app)
{
app.AuthenticateRequest += new EventHandler(OnAuthenticateRequest);
}
public void OnAuthenticateRequest(Object s, EventArgs e)
{
CurrentUser.Initialize();
}
public void Dispose() { }
}
CurrentUser.cs
public static class CurrentUser
{
public static bool IsAuthenticated { get; private set; }
public static string Email {get; set;}
public static string RealName {get; set;
public static string UserId {get; set;}
public static void Initialize()
{
CurrentUser.AuthenticateUser();
}
Note: this is a scaled down version of my authentication code.
public static void AuthenticateUser()
{
UserAuthentication user = new UserAuthentication();
user.AuthenticateUser();
if (user.IsAuthenticated)
{
CurrentUser.IsAuthenticated = true;
CurrentUser.UserId = user.UserId;
CurrentUser.Email = user.Email;
CurrentUser.RealName = user.RealName;
}
}
}
UserAuthentication.cs
public class UserAuthentication
{
public string Email { get; set; }
public string RealName { get; set; }
public string UserId { get; set; }
public bool IsAuthenticated { get; private set; }
public UserAuthentication()
{
IsAuthenticated = false;
Email = String.Empty;
RealName = String.Empty;
UserId = String.Empty;
}
public void AuthenticateUser()
{
//do some logic here.. if the user is ok then
IsAuthenticated = true
Email = address from db
UserId = userid from db;
Realname = name from db;
}
}
I have tested between 3 different browsers and it seems to work fine, but I am still learning and don't want to make a huge mistake.
If my logic is totally wrong, then how should I do it so I dont have to put user lookups on every page directly?
No, this is not thread-safe. For instances of the application living in separate processes or AppDomains, this will be just fine. But if your ASP.NET server is going to serve multiple requests at once using threading, you are going to have some very bad side effects if two people try to use the application at the same time.
In the Init method, the HttpApplication parameter is described as:
An HttpApplication that provides access to the methods, properties, and events common to all application objects within an ASP.NET application
The key here is that there is one PageHttpModule for the lifetime of the app, and all static objects that exist in the lifetime of the app will share those variables.
BUT... the lifetime of CurrentUser is only within the scope of the OnAuthenticateRequest event, unless some other reference keeps the object alive. If it were a PageHttpModule member-level variable, you'd have issues that you would have noticed immediately. In your situation, however, you'll work fine so long as you don't get more than one simultaneously-processed OnAuthenticateRequest call.
The answer to your question is no, you're not guaranteed to be thread-safe. If two authentication requests come in simultaneously, you're not guaranteed to have one event complete before the other begins, in which case the second user can appear authenticated, when it's really the first user that was logged on.
Update
I think part of the problem is coming from a misunderstanding of AuthenticateRequest... By the time this event is called, the user has already been authenticated by either Windows or Forms authentication... you're just getting notified that it's happened. In fact, the property User.Identity.IsAuthenticated has already been set (I believe this event fires even if the user fails authentication, but I won't swear to that without double-checking).
If I understand what you are after, you're really trying to write your own custom membership provider. If you take this approach, you will have all the benefits of the built-in authentication... all of the standard properties related to authentication will be set and accessible, and will be isolated to a user's session in the manner you want.
Writing a custom provider is not a small feat, but it is doable, and you should be able to reuse a lot of the logic and code you're currently using for your classes.
Trying to completely re-write the authentication mechanism would be jumping through painful, complicated hoops.
Some links:
http://www.devx.com/asp/Article/29256/0/page/3
http://www.codeproject.com/KB/aspnet/WSSecurityProvider.aspx
http://msdn.microsoft.com/en-us/library/f1kyba5e%28v=VS.90%29.aspx
The properties you must implement may look daunting, but unless you need a specific functionality (such as ResetPassword), you can simply throw a NotImplementedException. Code only what you'll use.
Why not just do it the way microsoft recommends?
http://msdn.microsoft.com/en-us/library/9wff0kyh.aspx
I've done custom authentication this way and it works fine.
Here is another link which should prove useful:
Link
What you have done with IHttpModule seems like a good path to tackle this kind of issue. One of the purposes of the http module as stated by microsoft is to enable for any kind of special authentication. When http module intializes it uses the same instance for new requests. Since you dont have any global variables I am not so sure how to address your thread safe question. It seems like you are onlu reading some data out, so please elaborate!

Categories