Can't figure out how to inject LinkGenerator - .net-core

So I know how to setup my controller so that I can accept a LinkGenerator injected into the controller. What I can't figure out is how do I inject my controller at startup with a LinkGenerator.
Controller
protected readonly LinkGenerator _linkGenerator;
public SomeController(config config, LinkGenerator linkGenerator)
{
config = Config;
_linkGenerator = linkGenerator;
}
StartUp - ConfigureServices
Controllers.SomeController someController = new
Controllers.SomeController(config, linkGenerator); //how do I get an
instance of link generator here.
services.AddSingleton(someController);
I tried this in the Configure method of startup, but ConfigureServices runs before Configure
app.Use(async (context, next) =>
{
linkGenerator = context.RequestServices.GetService<LinkGenerator>();
});
What am I missing?

Try the following approach in ConfigureServices of Startup.cs
public Startup(IConfiguration configuration , IHttpContextAccessor accessor)
{
Configuration = configuration;
_accessor = accessor;
}
public readonly IHttpContextAccessor _accessor;
public IConfiguration Configuration { get; }
var linkGenerator = _accessor.HttpContext.RequestServices.GetService<LinkGenerator>();
services.AddScoped<LinkGenerator>();
services.AddTransient(ctx =>
new ValuesController(linkGenerator));
Controller
private readonly LinkGenerator _linkGenerator;
public ValuesController(LinkGenerator linkGenerator)
{
_linkGenerator = linkGenerator;
}
Reference :https://andrewlock.net/controller-activation-and-dependency-injection-in-asp-net-core-mvc/

I found this article: https://kontext.tech/article/974/generate-absolute-path-using-linkgenerator-in-aspnet-core.
It states:
To use this class in your code, we need to inject it. LinkGenerator is
a singleton service available from DI. It is registered automatically
when you call services.AddRouting();. All the methods also need to
access HttpContext instance for each request, which can be accessed by
injecting HttpContextAccessor.
services.AddHttpContextAccessor();

Related

I'm new to .NET Core 2.1 MVC and I'm having trouble understanding how a few things work

I'm currently following a .Net Core Angular 8 tutorial in Udemy. I'm able do get/post requests in Postman and I can also see what I've posted in a .db file using sqlite as my database and viewing the data through Db Browser. Everything seems to be working great but is all for nothing if I can't comprehend what's going on in some areas of the application. I would really appreciate it if someone could help me answer a few questions.
My entire project is in GitHub: https://github.com/cjtejada/ASP.NetCoreAngular8/tree/master/DatingApp.API
Problem 1: I have the following the following controller:
[Route("api/[controller]")]
[ApiController]
public class AuthController : ControllerBase
{
private readonly IAuthRepository _repo;
private readonly IConfiguration _config;
public AuthController(IAuthRepository repo, IConfiguration config)
{
_repo = repo;
_config = config;
}
[HttpPost("register")]
public async Task<IActionResult> Register(UserForRegisterDto userForRegisterDto)
{
// validate request
userForRegisterDto.Username = userForRegisterDto.Username.ToLower();
if (await _repo.UserExists(userForRegisterDto.Username))
return BadRequest("User already exists");
var userToCreate = new User
{
Username = userForRegisterDto.Username
};
var createdUser = await _repo.Register(userToCreate, userForRegisterDto.Password);
return StatusCode(201);
}
}
I know that when the client makes a request to register, the register() method will be called and the Username that gets passed in will set the Username from DTO userForRegisterDto. After this then we call method UserExists() to check if the user exists in our database.
Question 1:
How is _repo aware of the logic in method UserExists() when it is only using the interface IAuthRepository? I know that IAuthRepository and class AuthRepository are somehow linked but I don't see anywhere in the app where Constructor DI is happening. My suspicion is that it has something to do with this line in startup.cs under the ConfigureServices method :
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DataContext>(x => x.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddCors();
services.AddScoped<IAuthRepository, AuthRepository>(); //<---- This Line
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options => {
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration.GetSection("AppSettings:Token").Value)),
ValidateIssuer = false,
ValidateAudience = false
};
});
}
After these two are "linked up", then the UserExists() method can be accessed through the AuthRepository class:
public class AuthRepository : IAuthRepository
{
private readonly DataContext _context;
public AuthRepository(DataContext context)
{
_context = context;
}
public async Task<User> Login(string username, string password)
{
}
private bool VerifyPasswordHash(string password, byte[] passwordHash, byte[] passwordSalt)
{
}
public async Task<User> Register(User user, string password)
{
byte[] passwordHash, passwordSalt;
CreatePasswordHash(password, out passwordHash, out passwordSalt);
user.PasswordHash = passwordHash;
user.PasswordSalt = passwordSalt;
await _context.Users.AddAsync(user);
await _context.SaveChangesAsync();
return user;
}
private void CreatePasswordHash(string password, out byte[] passwordHash, out byte[] passwordSalt)
{
}
public async Task<bool> UserExists(string username)
{
if (await _context.Users.AnyAsync(x => x.Username == username))
return true;
return false;
}
}
I've been reading about the AddScoped method and what it does but this is not clear to me that this is the case. Any clarification as to how this works would be great.
Problem 2:
This one is more or less the same. If we keep following the path of the request we will hit the register() method in the AuthRepository class.
Question 2:
How does this class have access to the properties of DataContext _context when I also can't spot any instances of constructor DI anywhere?
Here are the rest of my project files if needed:
Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DataContext>(x => x.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddCors();
services.AddScoped<IAuthRepository, AuthRepository>();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options => {
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration.GetSection("AppSettings:Token").Value)),
ValidateIssuer = false,
ValidateAudience = false
};
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
//app.UseHsts();
}
//app.UseHttpsRedirection();
app.UseCors(x => x.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
app.UseAuthentication();
app.UseMvc();
}
}
DataContext.cs
public class DataContext : DbContext
{
public DataContext(DbContextOptions<DataContext> options) : base (options){}
public DbSet<Value> Values { get; set; }
public DbSet<User> Users { get; set; }
}
Any clarifications and suggestions are greatly appreciated. Thanks, all.
You are correct. The line services.AddScoped<IAuthRepository, AuthRepository>(); simply instructs the ASP.NET Core service container to substitute an instance of concrete class AuthRepository wherever it sees a reference to IAuthRepository at runtime.
The various Add* methods all do the same thing under the hood regarding registering the mapping of interfaces => classes, the key difference is the scope of the created class, i.e. how long it persists for:
AddScoped classes will be created at the beginning of every request to the server, and destroyed at the end of every request. In other words, every request results in a new instance of that class being created.
AddSingleton classes are created when your ASP.NET Core application starts up, and are destroyed when it shuts down. In other words, only a single instance of that class exists within your application.
AddTransient classes are recreated whenever they are requested. In other words, if a page on your site used the same service transient twice, there would be two instances created. (Contrast this with a scoped service, where only a single instance would be created, as each page is a single request.)
A fuller explanation, including examples: https://stackoverflow.com/a/38139500/70345
In order to fulfill (1) by creating an instance of your class AuthRepository, the service container needs to call that class's constructor. The container inspects your class to find the first public constructor and retrieves any arguments to that constructor, in this case an instance of the DataContext class. The container then searches its internal class mappings for that class and, because you have registered that mapping via services.AddDbContext<DataContext>(...), is able to construct and return the class instance. Thus it's able to pass that instance to AuthRepository, so AuthRepository is constructed successfully.
The AddDbContext method is simply a wrapper around AddScoped, that performs some additional scaffolding to allow Entity Framework DbContexts to work correctly.
For the official explanation, refer to Microsoft's official page on DI and IoC.
Question 1 - You've right this line in Startup.cs provide creating a new object AuthRepository. For this example you must to know that DI container creates an AuthRepository object for you based on the interface and his own implementation and you only need to pass an interface in properly constructor. AddScope() is related with lifetime of created objects. When you register object by method AddScope() then the object will be created for a single request and after the request, the object will be disposed.
Question 2 - Your dbContext is registered in DI container. AddDbContext() is a specific extension method provided to registration of entity framework dbContextes. This line of code registers your dbContext with connection strings got from the appSetting.json file.
services.AddDbContext<DataContext>(x =>
x.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));
This DbContext is injected into the constructor of the AuthRepository class and when you use this class DI container created DbContext instance for you.
private readonly DataContext _context;
public AuthRepository(DataContext context)
{
_context = context;
}

How to use singleton in asp.net core 2.1 with parameter (interface)

In Starup file
services.AddScoped<IUserResponsitory , UserResponsitory>();
services.AddSingleton<IAuthService>(service=>new AuthServiceImpl(
service.GetService<IUserResponsitory>(),service.GetService<IConfiguration>()));
In AuthServiceImpl file:
private IUserResponsitory m_userResponsitory;
private IConfiguration m_config;
public AuthServiceImpl(IUserResponsitory userResponsitory, IConfiguration config)
{
m_config = config;
m_userResponsitory = userResponsitory;
}
In the UserResponsitory file.
public class UserResponsitory : Responsitory<Users>,IUserResponsitory
{
private DbSet<Users> userEntity;
public UserResponsitory(MyDBContext context) : base(context)
{
userEntity = context.Set<Users>();
}
}
The error below
Some of the help:
help1
help2
Can you help me.Please!
Since scope validation is applied by .NET Core, you can not resolve services with scoped lifetime (IUserResponsitory in your case) in the constructor of a service registered with singleton lifetime (IAuthService).
So you need to change your IAuthService lifetime to scoped.
services.AddScoped<IAuthService, AuthService>();
Useful Resources:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2#scope-validation
https://bartwullems.blogspot.com/2019/02/aspnet-core-scope-validation.html
As the error indicates, you could not resolve a scoped service from root provider.
If you prefer use IAuthService as Singleton, you could reolve the service from IServiceProvider like
Startup.cs
services.AddScoped<IUserResponsitory , UserResponsitory>();
services.AddSingleton<IAuthService, AuthServiceImpl>();
AuthServiceImpl
public class AuthService : IAuthService
{
private readonly IServiceProvider _serviceProvider;
private readonly IConfiguration m_config;
public AuthService(IServiceProvider serviceProvider
, IConfiguration configuration)
{
_serviceProvider = serviceProvider;
m_config = configuration;
}
public void Auth()
{
using (var scope = _serviceProvider.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<IUserResponsitory>();
var result = db.Users.ToList();
}
}
}

How to read appsettings.json values ASP.NET Core

After I've read this article about dependency injection Here I still do not have a clear understanding on how to read the appsetting in other than a controller classes.
Lets say for instance I have a helper class with a bunch of static methods that I'm planning to use, I do not create an instance of this class, how do I read setting values to use inside the methods of this class?
I used to create helper class to read data from appsettings.config in one of my applications:
public static class ConfigValueProvider
{
private static readonly IConfigurationRoot Configuration;
static ConfigValueProvider()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
Configuration = builder.Build();
}
public static string Get(string name)
{
return Configuration[name];
}
}
However later I reviewed my application to get away from static methods which depends on application config in order to make my application testable.
You should use services.Configure as below:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<JSonAsClass>(Configuration.GetSection("MySectionName"));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
then you can inject JSonAsClass inside any class you want to use it:
private JSonAsClass jSonAsClass;
public MailService(IOptions<JSonAsClass> jSonAsClass)
{
this.jSonAsClass = jSonAsClass.Value;
}

.NET CORE 2.1 Constructor asks for IConfiguration

I've followed several methods on StackOverflow to fix my issue, none with a result:
My DefaultConnection-string is in my AppSettings.json. To retrieve info I am reading to use the IConfiguration from my startup.cs. The constructor of my MsSQL-context is still asking for this IConfiguration. Note: I'm using a repository pattern.
startup.cs:
public Startup(IConfiguration configuration)
{
this.Configuration = configuration;
}
public IConfiguration Configuration { get; private set; }
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IConfiguration>(Configuration);
I've added the Singleton in my startup after a suggestion. With or without this the constructor of MsSQLContext is still requesting this as a variable to be passed. Leaving the constructor without this gives me the error: Connectionstring not initialized.
AdminMsSQLContext:
private readonly string _connectionString;
public MSSQLAdminContext(IConfiguration configuration)
{
_connectionString = configuration.GetConnectionString("DefaultConnection");
}
Injecting IConfiguration is actually an anti-pattern, anyways. What you should be doing is supplying an action to your scope registration and change your MSSQLAdminContext class to accept just the connection string in its constructor:
public MSSQLAdminContext(string connectionString)
{
_connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
}
Then:
services.AddScoped(_ =>
new MSSQLAdminContext(Configuration.GetConnectionString("DefaultConnection)));
Your repo should not have knowledge of something like your configuration. If it needs a connection string, then it should take the connection string, and that is all.
I believe the issue you are having is that you have not registered the MSSQLAdminContext with the DI container. Because of this the DI engine does not know to inject the IConfiguration into the class. In your start up you will register this class however you need, I tend to use scoped for these types of classes you may use in multiple places. So something like this.
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<MSSQLAdminContext>();
services.AddSingleton<IConfiguration>(Configuration);
}

How to inject IHubContext in Azure Mobile Services

To get an IHubContext from outside the hub I use:
public class EventSender
{
private readonly IHubContext context;
public EventSender(ApiServices services)
{
context = services.GetRealtime<MyHub>();
}
public void Send(string message)
{
context.Clients.All.Send(message);
}
}
Where services is an ApiServices instance that gets injected into the calling class.
What if I want to inject the IHubContext itself? How do I do that?
I tried to register the IHubContext instance in WebApiConfig.cs like this:
var configBuilder = new ConfigBuilder(options, (httpConfig, autofac) =>
{
autofac.RegisterType<Logger>().As<ILogger>().SingleInstance();
autofac.RegisterInstance(??).As<IHubContext>(); <-- ????
....
But this has 2 problems:
I don't have access to the ApiServices instance (and WebApiConfig class is static so I can't inject it there).
What type do I register it as? IHubContext seems too general.
If you can inject ApiServices, you can have access while trying to register your dependencies.
Can you try something like this?
autofac.Register(c =>
{
var services = c.Resolve<ApiServices>();
return services.GetRealtime<MyHub>();
}.As<IHubContext>();
And in your constructor:
public EventSender(IHubContext context)
{
this.context = context;
}

Resources