I have a page:
#page "/registration";
#inherits RegistrationPageViewModel;
<h3>
#Localizer["Registration"]
</h3>
And its ViewModel looks like this:
public class RegistrationPageViewModel : ComponentBase
{
public IStringLocalizer<AppLocalizations> Localizer;
public IValidator<RegistrationPageViewModel> _validator;
public RegistrationPageViewModel(IValidator<RegistrationPageViewModel> validator)
{
_validator = validator;
}
...
Now, when I try to inject into the view model stuff like validators, I get errors like this:
CS7036 There is no argument given that corresponds to the required
parameter 'validator' of
RegistrationPageViewModel.RegistrationPageViewModel(IValidator<RegistrationPageViewModel>)
How can I fix this? I guess it has something to do with dependency injection and the fact that my view model is a child of ComponentBase, but I have no clue where to go from there.
Here's how my DI looks like:
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.RegisterAppServices()
.RegisterViewModels()
.RegisterViews()
});
builder.Services.AddMauiBlazorWebView();
return builder.Build();
}
public static MauiAppBuilder RegisterValidators(this MauiAppBuilder mauiAppBuilder)
{
mauiAppBuilder.Services.AddScoped<IValidator<RegistrationPageViewModel>, RegistrationValidator>();
return mauiAppBuilder;
}
public static MauiAppBuilder RegisterViewModels(this MauiAppBuilder mauiAppBuilder)
{
mauiAppBuilder.Services.AddScoped<MainPageViewModel>();
mauiAppBuilder.Services.AddScoped<LoginPageViewModel>();
mauiAppBuilder.Services.AddScoped<RegistrationPageViewModel>();
return mauiAppBuilder;
}
public static MauiAppBuilder RegisterViews(this MauiAppBuilder mauiAppBuilder)
{
mauiAppBuilder.Services.AddScoped<LoginPage>();
mauiAppBuilder.Services.AddScoped<RegistrationPage>();
return mauiAppBuilder;
}
...
Validator
public class RegistrationValidator : AbstractValidator<RegistrationPageViewModel>
{
public RegistrationValidator(IStringLocalizer<AppLocalizations> stringLocalizer)
{
Your RegistrationPageViewModel is a Component
public class RegistrationPageViewModel : ComponentBase
Which you then inherit in your page:
#page "/registration";
#inherits RegistrationPageViewModel;
And you also register the base class as a service:
mauiAppBuilder.Services.AddScoped<RegistrationPageViewModel>();
While you might have patched this together to make it work, I'm pretty sure registering components as services is not good practice. I can see all sorts of problems.
RegistrationPageViewModel should be a normal class that you inject into your page.
Looking at your configuration you appear to be registering Components.
mauiAppBuilder.Services.AddScoped<LoginPage>();
mauiAppBuilder.Services.AddScoped<RegistrationPage>();
Why?
Components are managed by the Renderer, not the services container.
Related
I have a class library where I want to access a Connectionstring from appsettings.json.
appsettings.json :
"DatabaseSettings": {
"ConnectionString": "Server=.;Database=Test;Trusted_Connection=True;"
},
In startup.cs I have the following code:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<DatabaseSettings>(Configuration.GetSection("DatabaseSettings"));
services.AddOptions();
services.AddTransient<IConnectionOption, Class1>();
}
And in class library
IConnectionOption.cs
public interface IConnectionOption
{
void ReadValue();
}
Class1.cs
public class Class1 : IConnectionOption
{
private readonly DatabaseSettings test;
public Class1(IOptions<DatabaseSettings> dbOptions)
{
test = dbOptions.Value;
}
public void ReadValue()
{
var r = test;
}
}
Now from index.cshtml I want to Invoke the class Library Class1
public class IndexModel : PageModel
{
public void OnGet()
{
Class1 test = new Class1();
test.ReadValue();
}
}
But of course that doesnt work since there are no Constructor taking zero parameters, I don´t think I should add IOptions as an parameter. But how do I invoke the class library to read the connectionstring? (When I get this to work I will of course read data and return instead of the connectionstring). I have looked at several examples including net core 2.1 Dependency injection
But I don´t understand how to use the class library directly, Is it necessary to use an controller ?!
If DatabaseSettings is accessible to the class library, there really is not much need for tightly coupling Class1 library to IOptions, which is more framework related.
Ideally Class1 can explicitly depend on DatabaseSettings via explicit constructor injection
public class Class1 : IConnectionOption {
private readonly DatabaseSettings test;
public Class1(DatabaseSettings settings) {
test = settings;
}
public void ReadValue() {
var r = test;
//...
}
}
then in Startup, the dependency can be extract from configuration and registered with the DI container
public void ConfigureServices(IServiceCollection services) {
var settings = Configuration.GetSection("DatabaseSettings").Get<DatabaseSettings>();
services.AddSingleton<DatabaseSettings>(settings);
services.AddTransient<IConnectionOption, Class1>();
}
That way when ever Class1 is resolved, the container will know how to inject the DatabaseSettings dependency.
Another option could have also been to use the factory delegate
public void ConfigureServices(IServiceCollection services) {
var settings = Configuration.GetSection("DatabaseSettings").Get<DatabaseSettings>();
services.AddTransient<IConnectionOption, Class1>(_ => new Class1(settings));
}
That way, when the IndexModel depends on IConnectionOption for injection.
public class IndexModel : PageModel {
private readonly IConnectionOption connectionOption;
public IndexModel(IConnectionOption iConnectionOption) {
connectionOption = iConnectionOption;
}
public void OnGet() {
connectionOption.ReadValue();
//...
}
}
the proper dependency will be injected when the page model is initialized.
You are using Dependency Injection but only halfway. This is what you are missing:
Register the service in the container (I'm going to assume a better name for Class1):
services.AddScoped<IConnectionOption, DatabaseConnectionOption>();
Make the page receive the service:
public class IndexModel : PageModel
{
private readonly IConnectionOption _IConnectionOption;
public IndexModel(IConnectionOption iConnectionOption)
{
_IConnectionOption = iConnectionOption;
}
public void OnGet()
{
_IConnectionOption.ReadValue();
}
}
I have two static classes with single static factory method for each.
public static class First
{
public IMyService Factory()
{
return IMyService()
{
//configure with Configs
};
}
}
public static class Second
{
public IMyService Factory()
{
return IMyService()
{
// configure with different Configs
};
}
}
The following would make provider return an instance when asked for:
services.AddSingleton(mb =>
{
var myService= First.Factory();
return myService;
});
How do I call different factories when need to get an instance with different configs?
If it's a one-time decision (app startup) than you should extract your config as a dependency:
in appsettings.json:
"mysettings":{"bla":"val1"}
somewhere in project:
public class mysettings { public string bla {get;set; }
in myservice constructor:
public myservice(IOptions<mysettings> settings) { ... }
in startup.cs:
services.Configure<mysettings>(this.Configuration.GetSection("mysettings"));
services.AddSingleton<Imyservice, myservice>();
Like this you inject the settings and your service will be instantiated with those that are specified in the appsettings.json
If you need to deside "live" which settings to use:
public interface IMyServiceFactory{
IMyService Create(MySettings settings);
}
Than you inject IMyServiceFactory to the class where you want to use IMyService and instantate it there with the right settings. Or even:
public interface IMyServiceFactory{
IMyService Create1();
IMyService Create2();
}
In any case you just register the factory in startup:
services.AddSingleton<IMyServiceFactory, MyServiceFactory>();
Somehow your client code or the bootstrapping code needs to express what kind of implementation is needed. You could implement it the following way:
public Interface IReqeust
{
// Some code
}
public class HttpRequest : IRequest
{
// Implementation
}
public class TcpRequest : IRequest
{
// Implementation
}
One way could be to offer multiple methods. You can still hide the configuration but some implementation details leak into your client code.
public Interface IRequestFactory
{
IRequest CreateHttpRequest();
IRequest CreateTcpRequest();
}
public class RequestFactory : IRequestFactory
{
// Implementation
}
Another solution would be to determine whats needed while constructing your factory.
public Interface IRequestFactory
{
IRequest CreateRequest();
}
public class RequestFactory : IRequestFactory
{
private IConfigReader configReader;
public RequestFactory(IConfigReader configReader)
{
this.configReader = configReader;
}
public IRequest CreateRequest()
{
var currentProtocoll = configReader.GetCurrentProtocoll();
if(currentProtocoll is HTTP)
return new HttpRequest();
else
return new TcpRequest();
}
}
I would not recommend your solution with more factories. At least not with what you wrote so far.
I'm trying to inject a dependency into a class used internally in a controller,
I have
//Startup.cs
public void ConfigureServices(IServiceCollection services){
services.AddMvc();//IDocumentService is a WCF service from our legacy stack
services.AddScoped(typeof(IDocumentService),typeof(DocumentServiceClient));
}
//Controller.cs
[Route("api/eci/test/[action]")]
public class Controller{
private IDocumentService injectedDocService;
public Controller(IDocumentService client){
injectedDocService=client;
}
[HttpPost({"id"})]
public void ingestedDocs(string id){
new Logic(injectedDocService).ingest(id);
}
}
//Logic.cs
public class Logic{
private IDocumentService injectedDocServiceActualTarget;
public Logic(IDocumentService injected2){
injectedDocServiceActualTarget=injected2;
}
public void injest(string id){
injectedDocServiceActualTarget.doWork(id);
}
}
It seems a little redundant to have it injected to the target class's parent. Is this the right way of doing things?
You need to register the Logic, then inject it to the controller. DocumentService will be injected to the Logic then.
The idea behind dependency injection is to implement the IoC (inversion of control) principle. In your example, it is only partial, since you explicitly instantiate Logic in your controller. If you want to do the dependency inversion properly - all your dependencies need to be passed as constructor parameters.
You should abstract the Logic behind an interface, only exposing the members that are to be used by dependents.
public interface ILogic {
void injest(string id);
}
Have the Logic class derive from the abstraction.
//Logic.cs
public class Logic : ILogic {
private readonly IDocumentService injectedDocServiceActualTarget;
public Logic(IDocumentService injected2) {
this.injectedDocServiceActualTarget=injected2;
}
public void injest(string id) {
injectedDocServiceActualTarget.doWork(id);
}
}
The Controller should now only explicitly depend on the ILogic interface
//Controller.cs
[Route("api/eci/test/[action]")]
public class Controller {
private readonly ILogic service;
public Controller(ILogic service) {
this.service = service;
}
[HttpPost({"id"})]
public void ingestedDocs(string id) {
service.ingest(id);
}
}
With that the last thing to do is to register all dependencies with the service collection.
//Startup.cs
public void ConfigureServices(IServiceCollection services){
services.AddMvc();
services.AddScoped<IDocumentService, DocumentServiceClient>();
services.AddScoped<ILogic, Logic>();
}
So now when the controller is called all dependencies will be resolved and injected into their respective dependents.
I have this CacheAttribute that accepts Duration Value like such
public class MyTestQuery : IMyTestQuery
{
private readonly ISomeRepository _someRepository;
public TestQuery(ISomeRepository someRepository)
{
_someRepository = someRepository;
}
[Cache(Duration = 10)]
public MyViewModel GetForeignKeysViewModelCache()
{
...code here...
return viewModel;
}
}
The Attribute looks like this
[AttributeUsage(AttributeTargets.Method)]
public class CacheAttribute : Attribute
{
public int Duration { get; set; }
}
When Intercepted using Castle.Proxy.IInterceptor it works but when I perform an Attribute.GetCustomAttribute either by IInvocation.MethodInvocationTarget or IInvocation.Method both returns a null value
Here it is in code
public class CacheResultInterceptor : IInterceptor
{
public CacheAttribute GetCacheResultAttribute(IInvocation invocation)
{
var methodInfo = invocation.MethodInvocationTarget;
if (methodInfo == null)
{
methodInfo = invocation.Method;
}
return Attribute.GetCustomAttribute(
methodInfo,
typeof(CacheAttribute),
true
)
as CacheAttribute;
}
public void Intercept(IInvocation invocation)
{
var cacheAttribute = GetCacheResultAttribute(invocation);
//cacheAttribute is null always
...more code here...
}
}
And this is how I register them
public class Bootstrapper
{
public static ContainerBuilder Builder;
public static void Initialise()
{
Builder = new ContainerBuilder();
...other codes in here...
CacheInstaller.Install();
var container = Builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
}
public class CacheInstaller
{
public static void Install()
{
Bootstrapper.Builder.RegisterType<CacheResultInterceptor>()
.SingleInstance();
Bootstrapper.Builder.RegisterAssemblyTypes(Assembly.Load("MyApplication.Web"))
.Where(t => t.Name.EndsWith("Query"))
.AsImplementedInterfaces()
.EnableInterfaceInterceptors()
.InterceptedBy(typeof(CacheResultInterceptor))
.SingleInstance();
}
}
My Expensive Method Class Ends with Query
Now the question is why invocation.MethodInvocationTarget and/or invocation.Method returns null?
What am I doing wrong?
Any other strategies so I can pass a parameter value without creating a Method for each value I can think of?
BTW I am using
Autofac 4.3.0.0
Autofac.Extras.DynamicProxy 4.2.1.0
Autofac.Integration.Mvc 4.0.0.0
Castle.Core 4.0.0.0
UPDATE 1
Here is what it returns when it runs for clarity
Here's what I found.
invocation.Method returns the method declaration on the interface, in your case IMyTestQuery.
On the other hand, invocation.MethodInvocationProxy returns the method that is going to be called when invoking invocation.Proceed(). This means it can be:
the next interceptor if you have several
a decorator if you have decorators over your interface
the final implementation of your interface
As you can see, MethodInvocationProxy is less deterministic than Method, which is why I would recommend you avoid using it, at least for what you're trying to achieve.
When you think about it, an interceptor should not be tied to an implementation as it proxies an interface, so why don't you put the [Cache] attribute at the interface level?
Using your code, I could successfully retrieve it when put on the interface.
Edit:
OK, I've put together a repository on GitHub that uses the specific versions of the NuGet packages you mentioned and shows how to retrieve an attribute on intercepted methods.
As a reminder, here are the used NuGet packages:
Microsoft.AspNet.Mvc v5.2.3
Autofac v4.3.0
Autofac.Mvc5 4.0.0
Autofac.Extras.DynamicProxy v4.2.1
Castle.Core v4.0.0
I created 2 query interfaces, IMyQuery and IMySecondQuery. Please note that as mentioned in my original answer, the [Cache] attributes are placed on the interfaces methods, not on the implementing classes.
public interface IMyQuery
{
[Cache(60000)]
string GetName();
}
public interface IMySecondQuery
{
[Cache(1000)]
string GetSecondName();
}
Then we have 2 very basic implementations of these classes. Not relevant at all, but for the sake of completeness:
public class DefaultMyQuery : IMyQuery
{
public string GetName()
{
return "Raymund";
}
}
public class DefaultMySecondQuery : IMySecondQuery
{
public string GetSecondName()
{
return "Mickaël Derriey";
}
}
And then the interceptor:
public class CacheResultInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
var cacheAttribute = invocation.Method.GetCustomAttribute<CacheAttribute>();
if (cacheAttribute != null)
{
Trace.WriteLine($"Found a [Cache] attribute on the {invocation.Method.Name} method with a duration of {cacheAttribute.Duration}.");
}
invocation.Proceed();
}
}
Note that the GetCustomAttribute<T> method is an extension method over MemberInfo present in the System.Reflection namespace.
Let's move on to the registration in the Autofac container. I tried to follow you registration style as much as I could:
var builder = new ContainerBuilder();
builder.RegisterControllers(typeof(MvcApplication).Assembly);
builder
.RegisterType<CacheResultInterceptor>()
.SingleInstance();
builder
.RegisterAssemblyTypes(typeof(MvcApplication).Assembly)
.Where(x => x.Name.EndsWith("Query"))
.AsImplementedInterfaces()
.EnableInterfaceInterceptors()
.InterceptedBy(typeof(CacheResultInterceptor));
DependencyResolver.SetResolver(new AutofacDependencyResolver(builder.Build()));
The queries are then used in the HomeController:
public class HomeController : Controller
{
private readonly IMyQuery _myQuery;
private readonly IMySecondQuery _mySecondQuery;
public HomeController(IMyQuery myQuery, IMySecondQuery mySecondQuery)
{
_myQuery = myQuery;
_mySecondQuery = mySecondQuery;
}
public ActionResult MyQuery()
{
return Json(_myQuery.GetName(), JsonRequestBehavior.AllowGet);
}
public ActionResult MySecondQuery()
{
return Json(_mySecondQuery.GetSecondName(), JsonRequestBehavior.AllowGet);
}
}
What I did to test this is just put a breakpoint in the interceptor, F5 the application, open a browser and navigate to both http://localhost:62440/home/myquery and http://localhost:62440/home/myquery.
It did hit the interceptor and find the [Cache] attribute. In the Visual Studio Output window, it did show:
Found a [Cache] attribute on the GetName method with a duration of 60000.
Found a [Cache] attribute on the GetSecondName method with a duration of 1000.
Hopefully that helps you pinpoint what's going on in your project.
I pushed changes to the repository so that the first query calls the second one.
It still works. You should really make an effort and put some code on the question.
I am trying to create a custom resolver for automapper which needs to access one of my data repositories to retreive the logged in users account.
Here is my code so far...
public class FollowingResolver : ValueResolver<Audio, bool>
{
readonly IIdentityTasks identityTasks;
public FollowingResolver(IIdentityTasks identitTasks)
{
this.identityTasks = identitTasks;
}
protected override bool ResolveCore(Audio source)
{
var user = identityTasks.GetCurrentIdentity();
if (user != null)
return user.IsFollowingUser(source.DJAccount);
return false;
}
}
However I am getting this error:
FollowingResolver' does not have a default constructor
I have tried adding a default contrstructor but my repository never gets initialised then.
This is my autoampper initialisation code:
public static void Configure(IWindsorContainer container)
{
Mapper.Reset();
Mapper.Initialize(x =>
{
x.AddProfile<AccountProfile>();
x.AddProfile<AudioProfile>();
x.ConstructServicesUsing(container.Resolve);
});
Mapper.AssertConfigurationIsValid();
}
Am I missing something, is it even possible to do it like this or am I missing the boat here?
Found the solution shorlty after...i was forgetting to add my resolvers as an IoC container.
Works great now!
I was getting the same error using Castle Windsor while trying to inject a service.
I had to add:
Mapper.Initialize(map =>
{
map.ConstructServicesUsing(_container.Resolve);
});
before Mapper.CreateMap calls.
Created a ValueResolverInstaller like this:
public class ValueResolverInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Classes.FromThisAssembly()
.BasedOn<IValueResolver>()
.LifestyleTransient());
}
}
and the ValueResolver itself:
public class DivergencesResolver : ValueResolver<MyClass, int>
{
private AssessmentService assessmentService;
public DivergencesResolver(AssessmentService assessmentService)
{
this.assessmentService = assessmentService;
}
protected override int ResolveCore(MyClass c)
{
return assessmentService.GetAssessmentDivergences(c.AssessmentId).Count();
}
}