I am new to IoC, especially with Autofac. Some days I was confused with IoC in the Windows Application Form. The obstacle is how to display (like: Show, ShowDialog) the form that has been registered. While IContainer can only be accessed locally (private) Program.cs.
Actually, can IoC be used in the Windows Application Form? I gave a sample code that confused me.
#
# Demo.Core Project
#
namespace Demo.Core.Repository
{
public interface IBaseRepository<T>
{
DbConnection CreateConnection();
IEnumerable<T> Get(IDbTransaction transaction = null, int? commandTimeout = null);
}
public abstract class BaseRepository<T> : IBaseRepository<T> where T : class
{
public DbConnection CreateConnection()
{
return new SqlConnection("Data Source=localhost;User ID=sa;Password=Default!3;Initial Catalog=DemoIoC;");
}
public IEnumerable<T> Get(IDbTransaction transaction = null, int? commandTimeout = null)
{
using (var connection = CreateConnection())
return connection.GetAll<T>(transaction, commandTimeout);
}
}
public interface IUserRepository : IBaseRepository<User> { }
public class UserRepository : BaseRepository<User>, IUserRepository { }
}
namespace Demo.Core.Models
{
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
}
#
# Demo.Winform Project
#
using Demo.Core.Models;
using Demo.Core.Repository;
namespace Demo.Winform
{
static class Program
{
public static IContainer Container;
[STAThread]
static void Main()
{
Container = Configure();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
static IContainer Configure()
{
var builder = new ContainerBuilder();
builder.RegisterType<UserRepository>().As<IUserRepository>();
builder.RegisterType<UserManagerForm>();
return builder.Build();
}
}
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
button1.Click += new EventHandler(delegate (object sender, EventArgs e)
{
using (var container = *** HOW TO GET CONTAINER ? ***)
{
Form manager = container.Resolve<UserManagerForm>();
manager.ShowDialog();
}
});
}
}
public partial class UserManagerForm : Form
{
private readonly IUserRepository repository;
public UserForm(IUserRepository repository) : this()
{
this.repository = repository;
}
public UserForm()
{
InitializeComponent();
}
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
listBox1.ValueMember = "Id";
listBox1.DisplayMember = "Name";
listBox1.DataSource = repository.Get();
}
}
}
I have read how to register form types in containers: Autofac - Register all Windows Forms. But the question is, how do I resolve the form that was registered and show form?
Thank you.
This worked for me ,You just get the service from the container \
static class Program
{
public static IContainer Container;
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Container = Configure();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1(Container.Resolve<IWindowsFormsApp3Client>()));
}
static Autofac.IContainer Configure()
{
var builder = new ContainerBuilder();
builder.Register<IWindowsFormsApp3Client>(ctor => new WindowsFormsApp3Client(new Uri("https://localhost:44381"), new CustomLoginCredentials()))
.AsImplementedInterfaces();
// builder.RegisterType<WindowsFormsApp3Client>().As<IWindowsFormsApp3Client>();
builder.RegisterType<Form1>();
return builder.Build();
}
}
public class CustomLoginCredentials : ServiceClientCredentials
{
private string AuthenticationToken { get; set; }
public override async Task ProcessHttpRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// AuthenticationToken = Extensions.GetAppsettingsToken()?.AccessToken;
if (AuthenticationToken != null)
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", AuthenticationToken);
}
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
await base.ProcessHttpRequestAsync(request, cancellationToken);
}
}
Related
Using SqlSugar ORM, based on blazor, dependency injection business service, an error is reported when calling, and it is empty。
SqlSugarService:
public static class SqlSugarService
{
private static readonly ILog log = LogManager.GetLogger(typeof(SqlSugarService));
public static void AddSqlSugarSevice(this IServiceCollection services)
{
if (services == null) throw new ArgumentNullException(nameof(services));
services.AddScoped<ISqlSugarClient>(o =>
{
var listConfig = new List<ConnectionConfig>();
listConfig.Add(new ConnectionConfig
{
DbType = DbType.SqlServer,
ConnectionString = "Server=.\\SQLEXPRESS;DataBase=Test;Uid=sa;Pwd=123456",
IsAutoCloseConnection = true,
InitKeyType = InitKeyType.Attribute
});
var dbContext = new SqlSugarClient(listConfig);
return dbContext;
});
}
}
The interface:
public interface IReportRepository
{
public DataTable GetTest(string sql);
}
Interface implementation:
public class ReportRepository : IReportRepository
{
private ISqlSugarClient _dbBase;
public ReportRepository(ISqlSugarClient sqlSugar)
{
_dbBase = sqlSugar;
}
public DataTable GetTest(string sql)
{
return _dbBase.Ado.GetDataTable(sql);
}
}
Injection:
services.AddSqlSugarSevice();
services.TryAddTransient<IReportRepository, ReportRepository>();
Used in component code:
public partial class Report
{
[Inject]
public IReportRepository ReportService { get; set; }
public Report()
{
ReportService.GetTest("select * from test");
}
}
ERROR :
System.NullReferenceException,HResult=0x80004003,Message=Object reference not set to an instance of an object, Source=MyReport
I have asp.net core application. Given S3 urls, the application needs to download file. These S3 urls may belongs to different AWS regions in the US.
public class Downloader
{
public void DownloadFile(string s3Url, string destFolder)
{
//download files
}
}
The DownloadFile() method gets called concurrently. Each url pass to this method may belong to different region.
AWS documentation shows how to Configure the AWS SDK for .NET with .NET Core. In my case AWS credentials are stored in profile file on server and the same credentials can be used across all US regions. So appsettings.json looks like
"AWS": {
"Profile": "default",
"ProfilesLocation": "C:\\aws\\awsprofile"
},
Issue
Since URLS can belong to different region, i cannot follow documentation code. I cannot register IAmazonS3 with DI framework and inject that instance into Downloader to download files from different regions. Because IAmazonS3 instance tried to a particular region.
Solution
So i created a factory which provides instance of IAmazonS3 given region name.
public interface IS3ClientFactory : IDisposable
{
IAmazonS3 GetS3Client(RegionEndpoint region);
}
public class S3ClientFactory : IS3ClientFactory
{
private bool _disposed = false;
private IDictionary<string, IAmazonS3> _container = null;
private S3ClientFactory()
{
_container = new Dictionary<string, IAmazonS3>();
}
public static IS3ClientFactory Configure(AWSOptions option, RegionEndpoint[] regions)
{
var factory = new S3ClientFactory();
foreach (RegionEndpoint region in regions)
{
option.Region = region;
factory._container.Add(region.SystemName, option.CreateServiceClient<IAmazonS3>());
}
return factory;
}
public IAmazonS3 GetS3Client(RegionEndpoint region)
{
return _container[region.SystemName];
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
if (_container != null && _container.Any())
{
foreach (var s3Client in _container)
{
if (s3Client.Value != null)
{
s3Client.Value.Dispose();
}
}
}
_disposed = true;
}
}
}
}
and in startup.cs register factory with DI
services.AddSingleton<IS3ClientFactory>(S3ClientFactory.Configure(Configuration.GetAWSOptions(),
new RegionEndpoint[]
{
RegionEndpoint.USWest1,
RegionEndpoint.USWest2,
RegionEndpoint.USEast1,
RegionEndpoint.USEast2
}));
and Downloader class would look like
public class Downloader : IDownloader
{
private readonly IS3ClientFactory _factory;
public Downloader(IS3ClientFactory factory)
{
_factory = factory;
}
public void DownloadFile(string s3Url, string destFolder)
{
var s3Uri = new AmazonS3Uri(s3Url);
var s3Client = _factory.GetS3Client(s3Uri.Region);
// use s3Client to download file
}
}
Questions
In S3ClientFactory's Configure method i am dynamically assigning RegionEndpoint to AWSOptions and then call option.CreateServiceClient<IAmazonS3>() is this a correct way of creating region specific instance of IAmazonS3? The code need to be unit testable so i could not use new AmazonS3Client(RegionEndpoint).
foreach (RegionEndpoint region in regions)
{
option.Region = region;
factory._container.Add(region.SystemName, option.CreateServiceClient<IAmazonS3>());
}
Is it okay to have singleton instance of IAmazonS3?
You can just inject IEnumerable of your service interface. And then find the instance that you want using LINQ.
Startup
foreach (string snsRegion in Configuration["SNSRegions"].Split(',', StringSplitOptions.RemoveEmptyEntries))
{
services.AddAWSService<IAmazonSimpleNotificationService>(
string.IsNullOrEmpty(snsRegion) ? null :
new AWSOptions()
{
Region = RegionEndpoint.GetBySystemName(snsRegion)
}
);
}
services.AddSingleton<ISNSFactory, SNSFactory>();
services.Configure<SNSConfig>(Configuration);
SNSConfig
public class SNSConfig
{
public string SNSDefaultRegion { get; set; }
public string SNSSMSRegion { get; set; }
}
appsettings.json
"SNSRegions": "ap-south-1,us-west-2",
"SNSDefaultRegion": "ap-south-1",
"SNSSMSRegion": "us-west-2",
SNS Factory
public class SNSFactory : ISNSFactory
{
private readonly SNSConfig _snsConfig;
private readonly IEnumerable<IAmazonSimpleNotificationService> _snsServices;
public SNSFactory(
IOptions<SNSConfig> snsConfig,
IEnumerable<IAmazonSimpleNotificationService> snsServices
)
{
_snsConfig = snsConfig.Value;
_snsServices = snsServices;
}
public IAmazonSimpleNotificationService ForDefault()
{
return GetSNS(_snsConfig.SNSDefaultRegion);
}
public IAmazonSimpleNotificationService ForSMS()
{
return GetSNS(_snsConfig.SNSSMSRegion);
}
private IAmazonSimpleNotificationService GetSNS(string region)
{
return GetSNS(RegionEndpoint.GetBySystemName(region));
}
private IAmazonSimpleNotificationService GetSNS(RegionEndpoint region)
{
IAmazonSimpleNotificationService service = _snsServices.FirstOrDefault(sns => sns.Config.RegionEndpoint == region);
if (service == null)
{
throw new Exception($"No SNS service registered for region: {region}");
}
return service;
}
}
public interface ISNSFactory
{
IAmazonSimpleNotificationService ForDefault();
IAmazonSimpleNotificationService ForSMS();
}
Now you can get the SNS service for the region that you want in your custom service or controller
public class SmsSender : ISmsSender
{
private readonly IAmazonSimpleNotificationService _sns;
public SmsSender(ISNSFactory snsFactory)
{
_sns = snsFactory.ForSMS();
}
.......
}
public class DeviceController : Controller
{
private readonly IAmazonSimpleNotificationService _sns;
public DeviceController(ISNSFactory snsFactory)
{
_sns = snsFactory.ForDefault();
}
.........
}
I have the next problem, i dont understand why this code dont work i think is because i dont injectate the class of constructor by autofac but i dont know how do that, can us help me to do that the better way?
Before I add the generator this work if i comment the generator code in service work.
This is my code:
I have a class Controller that invoke a serv:
public class ZonesController : Controller
{
private IZoneService zoneService;
public ZonesController(IZoneService zoneService)
{
this.zoneService = zoneService;
}
[HttpGet]
//Do work
}
This is the service and interface:
public class ZoneService : IZoneService
{
private readonly IZoneRepository zoneRepository;
private readonly IDtoFactory dtoFactory;
private readonly ZoneGenerator zoneGenerator;
public ZoneService(IZoneRepository zoneRepository,
IDtoFactory dtoFactory,
ZoneGenerator zoneGenerator)
{
this.zoneRepository = zoneRepository;
this.dtoFactory = dtoFactory;
this.zoneGenerator = zoneGenerator;
}
public void Add(ZoneDetailDTO zone)
{
zoneGenerator.Generate(zone);
}
//Do Work
}
public interface IZoneService
{
void Add(ZoneDetailDTO zone);
//Do Methods
}
The generator invoke ohter class, factories:
public class ZoneGenerator
{
private readonly ZoneFactory zoneFactory;
private readonly IZoneRepository zoneRepository;
public ZoneGenerator(ZoneFactory zoneFactory, IZoneRepository zoneRepository)
{
this.zoneFactory = zoneFactory;
this.zoneRepository = zoneRepository;
}
public void Generate(ZoneDetailDTO zoneModel)
{
var zone = zoneFactory.Create(zoneModel);
zoneRepository.Add(zone);
}
}
The Factory:
public class ZoneFactory
{
private readonly ZoneMapFactory zoneMapFactory;
private readonly ZoneScheduleFactory zoneScheduleFactory;
public ZoneFactory(ZoneMapFactory zoneMapFactory,
ZoneScheduleFactory zoneScheduleFactory)
{
this.zoneMapFactory = zoneMapFactory;
this.zoneScheduleFactory = zoneScheduleFactory;
}
public Zone Create(zoneDetailDTO zone)
{
var map = zoneMapFactory.Create(zone.Map.Address, zone.Map.Latitude, zone.Map.Longitude);
var schedule = zoneScheduleFactory.Create(zone.Schedule.StartHour, zone.Schedule.EndHour);
return new Zone(zone.Name,
zone.ProvinceId,
map,
schedule,
zone.Tags);
}
}
And finally my container:
//method in Startup class Asp.Net Core
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddSingleton(_ => Configuration);
// Add framework services.
services.AddApplicationInsightsTelemetry(Configuration);
services.AddMvc();
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterModule<DefaultModule>();
containerBuilder.Populate(services);
var container = containerBuilder.Build();
return new AutofacServiceProvider(container);
}
public class DefaultModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<ZoneService>().As<IZoneService>();
builder.RegisterType<ZoneRepository>().As<IZoneRepository>();
builder.RegisterType<ProvinceService>().As<IProvinceService>();
builder.RegisterType<ProvinceRepository>().As<IProvinceRepository>();
builder.RegisterType<DtoFactory>().As<IDtoFactory>();
}
}
You have missed to add to your Load method the following:
builder.RegisterType<ZoneGenerator>().AsSelf();
builder.RegisterType<ZoneFactory>().AsSelf();
builder.RegisterType<ZoneMapFactory>().AsSelf();
builder.RegisterType<ZoneScheduleFactory>().AsSelf();
How to bind classes with required connection string in constructor using Ninject?
Here are the classes that I am using:
AppService class:
using SomeProject.LayerB;
namespace SomeProject.LayerA;
{
public class AppService
{
private readonly ISomeRepository someRepository;
public LocationManagementService(ISomeRepository someRepository)
{
this.someRepository = someRepository;
}
// other codes ...
}
}
SomeRepository class:
namespace SomeProject.LayerB;
{
public class SomeRepository : ISomeRepository
{
private readonly SomeDbContext context;
public SomeRepository(SomeDbContext context)
{
this.context = context;
}
// other codes ...
}
}
SomeDbContext class:
namespace SomeProject.LayerB;
{
public class SomeDbContext : DbContext
{
public SomeDbContext(string nameOrConnectionString)
: base(nameOrConnectionString)
{
}
// other codes ...
}
}
Then, I use a Ninject module containing the following code:
namespace SomeProject.LayerC;
{
public class SomeModule : NinjectModule
{
public override void Load()
{
Bind<ISomeRepository>().To<SomeRepository>();
// My problem is on this part.. I want to provide the connection string on the
// main program, not here on this class.
// Bind<SomeDbContext>().ToSelf().WithConstructorArgument("nameOrConnectionString", "the connection string I want to inject");
}
}
}
Main program:
using SomeProject.LayerA;
using SomeProject.LayerC;
namespace SomeProject.LayerD;
{
public class MainProgram
{
public MainProgram()
{
IKernel kernel = new StandardKernel(new SomeModule());
AppService appService = kernel.Get<AppService>();
}
}
}
NOTE: The only layer that main program can reference is LayerA where AppService class is located and as well as LayerC where the ninject module is found.
Add a Configuration class like this:
public class Config
{
public static string ConnectionString { get; set; }
}
and in your ninject module write this:
Bind<SomeDbContext>().ToSelf()
.WithConstructorArgument("nameOrConnectionString",
c => Config.ConnectionString);
then in your main method you could write following:
public class MainProgram
{
public MainProgram()
{
IKernel kernel = new StandardKernel(new SomeModule());
Config.ConnectionString = "The connection string";
AppService appService = kernel.Get<AppService>();
}
}
Update:
You can use ninject to locate config class also if you don't want use static methods:
class Config2
{
public string ConnectionString { get; set; }
}
in module:
Bind<Config2>().ToSelf().InSingletonScope();
Bind<SomeDbContext>().ToSelf()
.WithConstructorArgument("nameOrConnectionString",
c=>c.Kernel.Get<Config2>().ConnectionString);
in main:
IKernel kernel = new StandardKernel(new SomeModule());
var conf = kernel.Get<Config2>();
conf.ConnectionString = "The connection string";
AppService appService = kernel.Get<AppService>();
I have had a website asp.net MVC solution
My controller had 2 constructors: one parameterless and one with few parameter.
The code used to go through the ctor with the parameters, injection them with DI using Unity container.
I have merged my website into another one.
Now my controller is initialized via the parameterless ctor.
What am I missing?
My code:
The initializer code is executed.
public static class Initializer
{
private static bool isInitialize;
private static readonly object LockObj = new object();
private static IUnityContainer defaultContainer = new UnityContainer();
static Initializer()
{
Initialize();
}
public static void Initialize()
{
if (isInitialize)
return;
lock (LockObj)
{
IUnityContainer container = defaultContainer;
//registering Unity for web API
GlobalConfiguration.Configuration.DependencyResolver = new Unity.WebApi.UnityDependencyResolver(container);
//registering Unity for MVC
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
container.RegisterType<ICritieriaLoader, CritieriaLoader>();
container.RegisterType<IAppsLoader, AppsLoader>();
container.RegisterType<ISaveTestToBD, SaveTestToDB>();
container.RegisterType<ITestOperations, TestOperations>();
container.RegisterType<IServicesManager, ServicesManager>();
container.RegisterType<IMamConfigurationManager, MamConfigurationsManager>();
container.RegisterType<IAppsDataManager, AppsDataManager>();
container.RegisterType<IMamConfiguration_V1Manager, MamConfiguration_V1Manager>();
container.RegisterType<ICtidProd_V1Manager, CtidProd_V1Manager>();
container.RegisterType<ICtidsAliasManager, CtidsAliasManager>();
container.RegisterType<IDalFactory, DalFactory>();
container.RegisterType<IDal<AppsDataBl>, AppsDataDal>();
container.RegisterType<IApplicationLogger, Log4NetLogger>();
container.RegisterType<IDal<MamConfigurationBL>, MamConfigurationBlDal>();
container.RegisterType<IDal<Conduit.Mam.MaMDBEntityFramework.MamConfiguration>, MamConfigurationDal>();
container.RegisterType<IDal<AddServiceBL>, NewService>();
container.RegisterType<IAlias_V1UiToEfConvertor, Alias_V1UiToEfConvertor>(new ContainerControlledLifetimeManager());
container.RegisterType<IMamConfiguration_V1UiToEfConvertor, MamConfiguration_V1UiToEfConvertor>(new ContainerControlledLifetimeManager());
container.RegisterType<ICtidProdValidator, CtidPRodValidator>(new ContainerControlledLifetimeManager());
container.RegisterType<IGeoProdValidator, GeoProdValidator>(new ContainerControlledLifetimeManager());
container.RegisterType<IConstListsProvider, ConstListsProvider>(new ContainerControlledLifetimeManager());
container.RegisterType<IEntityToUiConvertor, EntityToUiConvertor>();
if (!isInitialize)
{
isInitialize = true;
}
}
}
}
.
protected void Application_Start()
{
Log.Application.Warn("Application started", "Application started");
WebApiConfig.Configure(GlobalConfiguration.Configuration);
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
Initializer.Initialize();
.
[Authorize]
public class CtidProdController : SAPController
{
#region Members
private IApplicationLogger mApplicationLog { get; set; }
//private IMamConfiguration_V1Manager mMamConfigurationManager { get; set; }
private ICritieriaLoader mCritieriaLoader { get; set; }
private IAppsLoader mAppsLoder { get; set; }
private IMamConfiguration_V1UiToEfConvertor mMamConfiguration_V1UiToEfConvertor { get; set; }
private CtidProdModel mCtidProdModel { get; set; }
private IConstListsProvider mConstListsProvider { get; set; }
private ICtidProd_V1Manager mCtidProd_V1Manager { get; set; }
private ICtidsAliasManager mCtidsAliasManager { get; set; }
#endregion
#region CTOR
public CtidProdController()
{
}
public CtidProdController(IApplicationLogger logger, ICtidProd_V1Manager ctidProd_V1Manager,
ICritieriaLoader critieriaLoader, IAppsLoader appsLoder, IMamConfiguration_V1UiToEfConvertor mamConfiguration_V1UiToEfConvertor,
IConstListsProvider constListsProvider, ICtidsAliasManager ctidsAliasManager)
{
mApplicationLog = logger;
mCtidProd_V1Manager = ctidProd_V1Manager;
mCritieriaLoader = critieriaLoader;
mAppsLoder = appsLoder;
mMamConfiguration_V1UiToEfConvertor = mamConfiguration_V1UiToEfConvertor;
mConstListsProvider = constListsProvider;
mCtidsAliasManager = ctidsAliasManager;
FillModel(mConstListsProvider);
}
#endregion
}
The new website code: The injection code:
public class NinjectDependencyScope : IDependencyScope
{
private IResolutionRoot resolver;
internal NinjectDependencyScope(IResolutionRoot resolver)
{
Contract.Assert(resolver != null);
this.resolver = resolver;
}
public void Dispose()
{
IDisposable disposable = resolver as IDisposable;
if (disposable != null)
disposable.Dispose();
resolver = null;
}
public object GetService(Type serviceType)
{
if (resolver == null)
throw new ObjectDisposedException("this", "This scope has already been disposed");
return resolver.TryGet(serviceType);
}
public IEnumerable<object> GetServices(Type serviceType)
{
if (resolver == null)
throw new ObjectDisposedException("this", "This scope has already been disposed");
return resolver.GetAll(serviceType);
}
}
public class NinjectDependencyResolver : NinjectDependencyScope, IDependencyResolver
{
private IKernel kernel;
public NinjectDependencyResolver(IKernel kernel)
: base(kernel)
{
this.kernel = kernel;
}
public IDependencyScope BeginScope()
{
return new NinjectDependencyScope(kernel.BeginBlock());
}
}
like Darren Davies pointed you are maybe missing some attributes. Unity takes by default in fact the constructor with the most parameters, but to be sure if you have more than one ctor, you should decorate the desired one with the InjectionConstructor attribute!
Hope this helps.
Kind regards