how to properly use Scoped service from c# code - c#

im using blazor server side and are therefore trying to use AddScoped instead of AddSingleton as the object is used on a per-user basis. I try to split the razor pages and the c# code as much as posible as i find this to be cleaner.
I add a scoped service using
Services.AddScoped<Services.ManageUserService>();
in the ConfigureServices function of the Startup class.
my problem now is to properly access the service from any .cs file (holding the logic of my .razor pages)
I have tried to do an injection like this:
[Inject]
public Services.ManageUserService manageUserService { get; set; }
and then accesed the scoped object using (username for example):
manageUserService.User
and this works. My problem is that if I add a print that is supose to only run once within the scoped service it runs every time the page is reloaded or changed.
for example, lets say i do this:
public class ManageUserService
{
public string User { get; private set; }
private bool LoadedStartup = false;
public ManageUserService() => SetupUser();
private void SetupUser()
{
if (!LoadedStartup)
{
User = "me";
LoadedStartup = true;
System.Diagnostics.Debug.Print("User:" + User);
}
}
}
I then access the class from multiply .cs files using:
[Inject]
public Services.ManageUserService manageUserService { get; set; }
The print "User:me" is supposed to only happen once as the locking bool LoadedStartup is changed, problem is that I get the print every time the Inject is happening (on change page, etc)
What am I doing wrong? aren't the AddScoped() suppose to add a "singelton" instance for every client? am I accessing it wrongly?
I can't find any examples of using AddScoped from separated .cs and .razor pages, only directly from the .razor page, and then it is done using #inject.

I was in the same situation:
1.- Add the scoped services:
Services.AddScoped<Services.ManageUserService>();
2.- Then, in order to have really the scoped instances once per user, in _Hosts.cshtml:
<app>
<component type="typeof(App)" render-mode="Server" />
</app>
3.- Now the trick I found by myself, instance the scoped services in App.razor
#inject Examples.ViewModels.MainViewModel Main;
#inject Examples.ViewModels.ChildViewModel Child;
#inject Examples.ViewModels.LayoutViewModel Layout;
#inject Examples.ViewModels.TreeViewModel Tree;
#{
Child.Main = Main;
Tree.LayoutViewModel = Layout;
}
4.- And if you have in the constructor something like:
public class MainViewModel
{
public static MainViewModel Instance;
public MainViewModel()
{
Instance = this;
}
}
You can access to any class you define as service from anywhere in your code.
MainViewModel.Instance...
I post about it at my blog: https://expediteapps.net/2020/02/18/scoped-viewmodels-instanced-once-on-start/

Related

Pass a value globally

Im new to xamarin/c#, im trying to make an application with login , and Im trying to pass the logged in userid inside the application, the question is , how do I pass or make the user id keeps floating inside after the login page? Should I keep passing it in every page using queryproperty or there's better way to keep it , like a specific file to to put it so that every page can call it?
You can use the Application.Properties collection to store things that need to be accessible to the entire application.
To store the user ID you would use
Application.Current.Properties("UserID") = UserID;
and to retrieve it you would use
UserID = Application.Current.Properties("UserID");
In C# it's not possible to define true global variables (meaning that they don't belong to any class). using a static class is a valid alternative option so you can create something like this:
public static class Globals
{
public Dictionary<int, UserObject> Users = new Dictionary<int, UserObject>();
}
Now, you'll be able to access The Users's dictionary property and add/remove/modify login users
Following Hans Kesting comment, Please note that An Xamarin app servers a single user at at time, so you can refactor the above from a dictionary to UserObject
Static classes - bad practic for contains info. You can use IoC-container. I don't know xamarin, if you have startup-class (how WPF), you can make ioc-container:
Install Autofac Install-Package Autofac -Version 5.0.0
Rigster user content:
class StartupClass
{
public static IContainer Container { get; set; }
public void OnStartup()
{
var containerBuilder = new ContainerBuilder();
var userContent = smthMethodForGiveUserContent();
containerBuilder.RegisterInstance<User>(userContent); //register UserType!
Container = containerBuilder.Build();
}
}
Resolve user content:
class SmthClass
{
public void Method()
{
using(var scope = StartupClass.Container.BeginLifetimeScope())
{
var userContent = scope.Resolve<User>(); //IT'S YOUR OBJECT, USE!!
//smth code..
}
}
}
The quickest way is to define a variable in App, it can be accessible to the entire project .
Because App itself has been defined inside Application class ,and it is a static property .
public partial class App : Xamarin.Forms.Application
{
public string UserID;
}
// set or get
(App.Current as App).UserID

Blazor creating a service, to pull data from SQL Server stored procedure

I'm a noob when it comes to Blazor, and C# for that matter.
I'm trying to create a simple service. Simple process that includes running a stored procedure, and displaying its results in a table. I'm starting simple, by just making sure that the process runs.
Here's what my service looks like:
public class ACheckerService : IACheckerService
{
// Database connection
private readonly SqlConnectionConfig _config;
public ACheckerService(SqlConnectionConfig config)
{
_config = config;
}
public async Task<IEnumerable<AChecker>> GetAllExceptions()
{
IEnumerable<AChecker> exceptions;
using (var conn = new SqlConnection(_config.Value))
{
exceptions = await conn.QueryAsync<AChecker>("Auto.GetAll", commandType: System.Data.CommandType.StoredProcedure);
}
return exceptions.ToList(); ;
}
}
I created AChecker model class which comprises of the fields that are to be pulled in by the stored procedure. Just 4 fields.
Once I had this done, I added a Blazor component (.Razor)
This is what my code looks like:
#page "/Data/Auto"
#using AChecker.Data
#inject IACheckerService CheckerService
<h3>Auto</h3>
<p>This component demonstrates Auto Exceptions</p>
#if (aChecker is null)
{
<p><em>Loading....</em></p>
}
else
{
<p><em>Data....</em></p>
}
#code {
private IEnumerable<AChecker> aChecker;
protected override async Task OnInitializedAsync()
{
aChecker = await CheckerService.GetAllAutoExceptions();
}
}
I added this service in Startup.Cs like :
services.AddSingleton<ACheckerService>();
Is there any issue with the code above or why it would not work?
I have this service added in my app, basically when I click on the menu, I expect to see either Loading or Data (ideally down the road, I want to create a table and populate the fields from the stored procedure in it, there are 4 fields), however I get a bunch of errors:
InvalidOperationException: Cannot provide a value for property CheckerService on type AChecker.Pages.MyPages.Auto. There is no registered service of type AChecker.Data.IACheckerService
Try registering the service using this overload instead which specifies the interface and its concrete implementation:
services.AddSingleton<IACheckerService, ACheckerService>();

Accessing Session state outside controller

I am trying to access data saved in Session state, in an ASP.Net Core Web Application, outside the controller, but the httpcontext is always null, how do I send the state over to a class?
I have added the correct statements in Startup.cs, to use sessions.
Furthermore, using this inside the controller works perfectly fine:
HttpContext.Session.SetString("Threshold",threshold);
HttpContext.Session.GetString("Treshold");
both work completely fine when accessing within the controller, yet I want to access this data in another class. Currently I am just using a static variable, but this is of course not the way to go, I want to access the session in here:
public class ImageAnalysisExtensionValues
{
public static double ConfidenceThreshold { get; set; }
}
(Data has been converted to double).
What do I do?
You can make use of Asp.Net Cores dependency injection and use the IHttpContextAccessor interface.
You have to register it first in your Startup.cs class (it is not always registered as default - therefore the use of TryAddSingleton<>()):
public void ConfigureServices(IServiceCollection services)
{
// ...
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
// ...
}
Then use it like this:
public YourClassOutsideOfController
{
private IHttpContextAccessor _contextAccessor;
public YourClassOutsideOfController(IHttpContextAccessor contextAccessor)
{
_contextAccessor = contextAccessor;
}
private void YourMethod()
{
var context = _contextAccessor.HttpContext;
context.Session.SetString("Threshold",threshold);
context.Session.GetString("Threshold");
}
}

DBContext dependency injection within Web Api using repositories [duplicate]

I am trying to use Ninject and OpenAccess for the first time. Please help me with the following. Here is what my project looks like...
public class ContentController : Controller
{
private ContentService contentSvc;
public ContentController(ContentService contentSvc)
{
this.contentSvc = contentSvc;
}
}
The following class is under a folder in my web app.
public class ContentService
{
private IContentRepository contentRepository;
public ContentService(IContentRepository contentRepository)
{
this.contentRepository = contentRepository;
}
public void InsertContent(Content content)
{
contentRepository.InsertContent(content);
}
}
The following repository belongs to a separate assembly.
public class ContentRepository : IContentRepository
{
DBContext db;
public ContentRepository(DBContext _db)
{
db = _db;
}
public void InsertContent(Content content)
{
db.Add(content);
}
}
Here is what Ninject binding look like..
kernel.Bind<ContentService>().To<ContentService>().InRequestScope();
kernel.Bind<IContentRepository>().To<ContentRepository>().InRequestScope().WithConstructorArgument("_db", new DBContext());
Everything works fine if I fetch one page at a time. I am using a simple tool 'XENU' to fetch multiple pages simultaneously. This is when I get errors with DBContext by fetching multiple pages at a time.
I am not sure if Ninject is dosposing the DBContext in each REQUEST?? I get different errors, e.g. 'Object reference not set to an instance of an object.', OR 'ExecuteReader requires an open and available Connection. The connection's current state is open.'
P.S.
I have ContentService under a folder in my MVC web app. ContentRepository is a separate assembly. I will be adding business logic in ContentService and use 'ContentRepository' only for CRUD operations. Also, please let me know if this architecture is okay or is there a better way to create services and repositories.
Here's how I would do your Ninject bindings,
kernel.Bind<DBContext>().ToSelf().InRequestScope();
kernel.Bind<ContentService>().ToSelf().InRequestScope();
kernel.Bind<IContentRepository>().To<ContentRepository>().InRequestScope();
This pattern should work fine in the example above with EF and Ninject.

Ninject + ASP.NET Web Forms Not Working

I've successfully implemented Ninject in an MVC3 application, but am running into some trouble doing the same thing with ASP.NET Web Forms. I'm getting null references every time I try to access an injected property in my business layer. After setting breakpoints within the CreateKernel method, as well as several places within the ServiceLocator class, it looks like none of them are ever getting hit, so it's not even loading.
I'm sure I'm just approaching this wrong, but there is very little documentation or info out there for wiring up Ninject in a Web Forms application.
Basically here's what I have so far:
code behind
public class ReviewManager
{
[Inject] private IReviewRepository _reviewRepository { get; set; }
public ReviewManager() { }
public ReviewManager(IReviewRepository reviewRepository)
{
_reviewRepository = reviewRepository;
}
public Review GetById(int id)
{
if (id <= 0) throw new ArgumentException("ID must be greater than zero");
**I get a null reference exception on the next line. _reviewRepository is null**
return _reviewRepository.GetById(id);
}
}
global.asax.cs
public class Global : NinjectHttpApplication
{
protected override IKernel CreateKernel()
{
return ServiceLocator.Kernel;
}
// deleted for brevity
}
ServiceLocator.cs (edited for brevity, the relevant parts are here)
public static class ServiceLocator
{
public static IKernel Kernel { get; set; }
public static ILogger Logger { get; set; }
static ServiceLocator()
{
Kernel = new StandardKernel(new INinjectModule[] {
new LoggerBindings(),
new DataBindings()
});
if (Logger == null)
Logger = Kernel.Get<ILogger>();
}
}
public class LoggerBindings : NinjectModule
{
public override void Load()
{
Bind<ILogger>().To<NLogLogger>();
}
}
public class DataBindings : NinjectModule
{
public override void Load()
{
Bind<IReviewRepository>().To<ReviewRepository>();
}
}
ASP.Net via WebForms does not allow you to manage the lifecycle of all object instances (like MVC does). For example, the framework instantiates page objects. This means you probably can't implement DI in quite the same way as you would in MVC/WPF/Silverlight (the same problem is present in WinForms IIRC). You will likely have to initiate the dependency graph directly in each of your code behinds.
Translation: you will want to call ServiceLocator.Kernel.Get<IReviewRepository> when your page loads (or as lazy-init on the property).
The cool thing about MVC ist that it can run side a side of ASP.NET WebForm pages in the same application. In my opinion the best way to extend ASP.NET WebForms websites is to create new pages using MVC3 and to refactor every page that needs major changes to MVC3.
If this is no option go and use the Ninject.Web extension. It contains a IHttpModule that property injects all web pages and controlls after they are initialized. That way you can property inject the services als have them created by Ninject.
A potential workaround, by changing your DataBindings class as follows:
public class DataBindings : NinjectModule
{
public override void Load()
{
Bind<IReviewRepository>().To<ReviewRepository>();
Bind<ReviewManager>().ToSelf();
}
}
And within your caller, instead of
var rm = new ReviewManager();
Try using
var rm = ServiceLocator.Kernel.Get<ReviewManager>();
I havent tested this code, but i think it'll solve your null reference problem.
I use property injection for pages, masterpages and usercontrols. All my pages, for example, inherit from a base class that overrides RequestActivation method with the following code:
''' <summary>
''' Asks the kernel to inject this instance.
''' </summary>
Protected Overridable Sub RequestActivation()
ServiceLocator.Kernel.Inject(Me)
End Sub
And in each page I declare injectable properties:
<Inject()>
Property repo As IMyRepository

Categories