I am trying to use DI with Factory. What I have so far is:
Factory:
public interface IConnectionFactory
{
IDbConnection Create();
}
public class DbConnectionFactory : IConnectionFactory
{
private readonly DbProviderFactory _providerFactory;
private readonly string _connectionString;
public DbConnectionFactory(string connectionString, string providerName)
{
_connectionString = connectionString;
_providerFactory = DbProviderFactories.GetFactory(providerName);
}
public IDbConnection Create()
{
var connection = _providerFactory.CreateConnection();
if (connection != null)
connection.ConnectionString = _connectionString;
return connection;
}
}
public class DbContext
{
private readonly IDbConnection _connection;
public DbContext(IConnectionFactory connectionFactory)
{
_connection = connectionFactory.Create();
}
public IDbCommand CreateCommand()
{
var cmd = _connection.CreateCommand();
return cmd;
}
public void Dispose()
{
_connection.Dispose();
}
}
public static class DbCommandExtensions
{
public static IDbDataParameter CreateParameter(this IDbCommand command, string name, object value)
{
var parameter = command.CreateParameter();
parameter.ParameterName = name;
parameter.Value = value;
return parameter;
}
}
Repository:
public class AgentRepository : IAgentRepository
{
private readonly DbContext _context;
public AgentRepository(DbContext context)
{
_context = context;
}
public bool Exists(string input)
{
using (var command = _context.CreateCommand())
{
command.Connection.Open();
command.CommandType = CommandType.Text;
command.CommandText = #"";
command.Parameters.Add(command.CreateParameter("#input", input));
return (bool)command.ExecuteScalar();
}
}
}
Now, in my client application I would like to pass connection string down to repository. Connection string will be created dynamically from other data source. I also would like to have service layer which pulls data from repository and passes DTO to the client. As DI container I am using Ninject. My question is how client should be implemented and how should configure Factory in Ninject?
-- EDIT 1
To move my project forward I implemented an anti pattern:
Service:
public AgentService(string connectionString)
{
var connectionFactory = new DbConnectionFactory(connectionString, "System.Data.SqlClient");
var context = new DbContext(connectionFactory);
_agentRepository = new AgentRepository(context);
}
Client:
var connectionString = _systemService.GetConnectionString(ip);
var agentService = new AgentService(connectionString);
var exists = agentService.Exists(input);
I know that this is not best practice but I wasn't able to implement it using DI. Please let me know how can I refactor this piece of code as I am going to implement the same for over 30 classes.
-- EDIT 2
I've did dome refactoring and end up with:
// Client:
var connectionString = _systemService.GetConnectionString(ip);
var dbConnectionFactory = new DbConnectionFactory(connectionString, "System.Data.SqlClient");
var agentService = new AgentService(dbConnectionFactory);
var exists = agentService.AgentExists(input);
// Service:
public class AgentService : IAgentService
{
private readonly IAgentRepository _agentRepository;
public AgentService(IDbConnectionFactory connectionFactory)
{
var context = new DbContext(connectionFactory);
_agentRepository = new AgentRepository(context);
}
public bool AgentExists(string input)
{
return _agentRepository.AgentExists(input);
}
}
// Repository:
public class AgentRepository : IAgentRepository
{
private readonly DbContext _context;
public AgentRepository(DbContext context)
{
_context = context;
}
public bool AgentExists(string input)
{
using (var command = _context.CreateCommand())
{
command.Connection.Open();
command.CommandType = CommandType.Text;
command.CommandText = #"";
command.Parameters.Add(command.CreateParameter("#param", input));
return (bool)command.ExecuteScalar();
}
}
}
// Connection Factory:
public class DbConnectionFactory : IDbConnectionFactory
{
private readonly DbProviderFactory _provider;
private readonly string _connectionString;
public DbConnectionFactory(string connectionString, string providerName)
{
_provider = DbProviderFactories.GetFactory(providerName);
_connectionString = connectionString;
}
public IDbConnection Create()
{
var connection = _provider.CreateConnection();
if (connection != null)
connection.ConnectionString = _connectionString;
return connection;
}
}
// DbContext:
public class DbContext
{
private readonly IDbConnection _connection;
public DbContext(IDbConnectionFactory connectionFactory)
{
_connection = connectionFactory.Create();
}
public IDbCommand CreateCommand()
{
var cmd = _connection.CreateCommand();
return cmd;
}
public void Dispose()
{
_connection.Dispose();
}
}
I wonder if it would be possible to replace new operators in Service class with dependency injection. If it is possible how should it be configured using Ninject?
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 a ServiceModule class which extends Module:
public class ServiceModule : Module
{
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
var ConnA = ConfigurationManager.ConnectionStrings["ConnA"].ConnectionString;
var ConnB = ConfigurationManager.ConnectionStrings["ConnB"].ConnectionString;
builder.Register(c => new SqlConnection(ConnA)).As<IDbConnection>().InstancePerLifetimeScope();
builder.RegisterType<ServiceA>().As<IServiceA>();
builder.RegisterType<ServiceB>().As<IServiceB>();
}
}
My ServiceA:
public class ServiceA: GenericRepo<Model>, IServiceA
{
public ServiceA(IDbConnection conn) : base(conn) { this.conn = conn; }
public async Task<RModel<Model>> Method(Model model)
{
return await ResultGet("ProcedureName", new
{
parameters...
}, parameters...);
}
}
Connection is passed to GenericRepo which has the ResultGet method. This method connects to database via connection and get data.
My GenericRepo:
public class GenericRepo<T> : IGenericRepo<T> where T : BaseModel
{
protected IDbConnection conn;
public GenericRepo(IDbConnection conn)
{
this.conn = conn;
}
public async Task<Model<T>> ResultGet(parameters)
{
var result = await conn.QueryAsync<T>(parameters);
}
}
So, my question is; how can I register ServiceA with ConnA and ServiceB with ConnB?
Thanks for your help.
We are developing windows service, and i want to change dbcontext class dynamically in repositories.
bellow is the scenario.
I have three db context classes
public abstract class Context : DbContext, IUnitOfWork
{
protected Context(string connectionString) : base(connectionString)
{
}
}
public class PlatformContext : Context
{
private readonly string _connectionString;
public PlatformContext(string connectionString)
: base(connectionString)
{
_connectionString = connectionString;
}
}
public class PlatformReplicaContext : Context
{
private readonly string _connectionString;
public PlatformReplicaContext(string connectionString)
: base(connectionString)
{
_connectionString = connectionString;
}
}
public class TempContext : Context
{
private readonly string _connectionString;
public TempContext(string connectionString)
: base(connectionString)
{
_connectionString = connectionString;
}
}
and i have repository
public interface ICategoryRepository : IRepository<Category>
{
}
public class CategoryRepository :Repository<Category>, ICategoryRepository
{
public CategoryRepository(Context context) : base(context)
{
}
}
hence im using CQRS i have another three classes
public class CategoryBasicQuery:IRequest<BaseQueryResponse>
{
public int CategoryId { get; set; }
}
public class CategoryBasicQueryHandler : IRequestHandler<CategoryBasicQuery, BaseQueryResponse>
{
private readonly ICategoryRepository _categoryRepository;
private readonly IMapper _mapper;
public CategoryBasicQueryHandler(ICategoryRepository categoryRepository, IMapper mapper)
{
_categoryRepository = categoryRepository;
_mapper = mapper;
}
public async Task<BaseQueryResponse> Handle(CategoryBasicQuery request, CancellationToken cancellationToken)
{
var entry = await _categoryRepository.FindAsync(request.CategoryId);
if (entry == null)
{
return new NotFoundResponse();
}
var response = _mapper.Map<CategoryBasicResponse>(entry);
return response;
}
}
Now here is the issue
Here category repository should be able to execute queries in all 3 types of contexts.
but how should i register classes in using autofac?
then i came up with a solution generating repositories in run time as below
public class RepositoryFactory
{
public static TRepository GetRepositoryInstance<T, TRepository>(
params object[] args)
where TRepository : IRepository<T>
{
return (TRepository)Activator.CreateInstance(typeof(TRepository), args);
}
}
im calling this method inside CategoryBasicQueryHandler class like this
var categoryRepo = RepositoryFactory.GetRepositoryInstance<Category, CategoryRepository>(new PlatformReplicaContext("connectionString"));
but when calling from CQRS
var categoty = new Category();
var command = new CategoryBasicQuery {CategoryId = categoryId};
var result = _mediator.Send(command);
VS give me following error
and my autofac registration as follows
builder.RegisterType<CategoryService>().AsSelf();
builder.RegisterType<ActionRepository>().As<IActionRepository>();
builder.RegisterType<CategoryRepository>().As<ICategoryRepository>();
builder.RegisterType<Mapper>().As<IMapper>();
can anyone help me resolve this or suggest good method to handle this situation.
thanks.
This may give you a good starting point for a possible solution: http://autofaccn.readthedocs.io/en/latest/resolve/relationships.html#keyed-service-lookup-iindex-x-b
builder.RegisterType<PlatformContext>().Keyed<Context>("platform");
builder.RegisterType<PlatformReplicaContext>().Keyed<Context>("replica");
builder.RegisterType<TempContext>().Keyed<Context>("temp");
You mentioned in a comment that there is a variable named action somewhere that will indicate which implementation to use:
public class Class1
{
private readonly IIndex<string, Context> contexts;
public Class1(IIndex<string, Context> contexts)
{
this.contexts = contexts;
}
public void Whatever()
{
string action = ...; // platform, replica or temp
Context context = this.contexts[action];
...
}
}
Of course this needs to be adapted so that it will fit in the rest of your application design. A possible example could be:
Context context = this.contexts[action];
using(ILifetimeScope scope = container.BeginLifetimeScope(builder =>
{
builder.RegisterInstance(context).As<Context>();
}))
{
// Because we are resolving IMediator from the scope, the selected Context will be used in all dependencies
var mediator = scope.Resolve<IMediator>();
mediator.Send(...);
}
I have created a .net core API, which pushes a message in RabbitMQ queue. I have used IOptions to read configuration data from .json file and added it as dependency.
Below is the code of my controller:
[Route("api/[controller]")]
public class RestController : Controller
{
private RabbitMQConnectionDetail _connectionDetail;
public RestController(IOptions<RabbitMQConnectionDetail> connectionDetail)
{
_connectionDetail = connectionDetail.Value;
}
[HttpPost]
public IActionResult Push([FromBody] OrderItem orderItem)
{
try
{
using (var rabbitMQConnection = new RabbitMQConnection(_connectionDetail.HostName,
_connectionDetail.UserName, _connectionDetail.Password))
{
using (var connection = rabbitMQConnection.CreateConnection())
{
var model = connection.CreateModel();
var helper = new RabbitMQHelper(model, "Topic_Exchange");
helper.PushMessageIntoQueue(orderItem.Serialize(), "Order_Queue");
}
}
}
catch (Exception)
{
return StatusCode((int)HttpStatusCode.BadRequest);
}
return Ok();
}
}
Connection details class has the below properties
public class RabbitMQConnectionDetail
{
public string HostName { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
}
Now I want to unit test it, but since I am going to test it against a blackbox, I'm not able to think of how to unit test it and looking for kind help.
ConnectionClass
public class RabbitMQConnection : IDisposable
{
private static IConnection _connection;
private readonly string _hostName;
private readonly string _userName;
private readonly string _password;
public RabbitMQConnection(string hostName, string userName, string password)
{
_hostName = hostName;
_userName = userName;
_password = password;
}
public IConnection CreateConnection()
{
var _factory = new ConnectionFactory
{
HostName = _hostName,
UserName = _userName,
Password = _password
};
_connection = _factory.CreateConnection();
var model = _connection.CreateModel();
return _connection;
}
public void Close()
{
_connection.Close();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_connection.Close();
}
}
~ RabbitMQConnection()
{
Dispose(false);
}
}
Helper class
public class RabbitMQHelper
{
private static IModel _model;
private static string _exchangeName;
const string RoutingKey = "dummy-key.";
public RabbitMQHelper(IModel model, string exchangeName)
{
_model = model;
_exchangeName = exchangeName;
}
public void SetupQueue(string queueName)
{
_model.ExchangeDeclare(_exchangeName, ExchangeType.Topic);
_model.QueueDeclare(queueName, true, false, false, null);
_model.QueueBind(queueName, _exchangeName, RoutingKey);
}
public void PushMessageIntoQueue(byte[] message, string queue)
{
SetupQueue(queue);
_model.BasicPublish(_exchangeName, RoutingKey, null, message);
}
public byte[] ReadMessageFromQueue(string queueName)
{
SetupQueue(queueName);
byte[] message;
var data = _model.BasicGet(queueName, false);
message = data.Body;
_model.BasicAck(data.DeliveryTag, false);
return message;
}
}
Tightly coupling your Controller to implementation concerns are making it difficult to test your controller without side-effects. From the sample you provided you have shown that you are encapsulating the 3rd part API implementations and only exposing abstractions. Good. You however have not created an abstraction that would allow you to mock them when testing. I suggest a refactor of the RabbitMQConnection to allow for this.
First have your own backing abstraction.
public interface IRabbitMQConnectionFactory {
IConnection CreateConnection();
}
And refactor RabbitMQConnection as follows
public class RabbitMQConnection : IRabbitMQConnectionFactory {
private readonly RabbitMQConnectionDetail connectionDetails;
public RabbitMQConnection(IOptions<RabbitMQConnectionDetail> connectionDetails) {
this.connectionDetails = connectionDetails.Value;
}
public IConnection CreateConnection() {
var factory = new ConnectionFactory {
HostName = connectionDetails.HostName,
UserName = connectionDetails.UserName,
Password = connectionDetails.Password
};
var connection = factory.CreateConnection();
return connection;
}
}
Take some time and review exactly what was done with this refactor. The IOptions was moved from the Controller to the factory and the RabbitMQConnection has also been simplified to do it's intended purpose. Creating a connection.
The Controller now would need to be refactored as well
[Route("api/[controller]")]
public class RestController : Controller {
private readonly IRabbitMQConnectionFactory factory;
public RestController(IRabbitMQConnectionFactory factory) {
this.factory = factory;
}
[HttpPost]
public IActionResult Push([FromBody] OrderItem orderItem) {
try {
using (var connection = factory.CreateConnection()) {
var model = connection.CreateModel();
var helper = new RabbitMQHelper(model, "Topic_Exchange");
helper.PushMessageIntoQueue(orderItem.Serialize(), "Order_Queue");
return Ok();
}
} catch (Exception) {
//TODO: Log error message
return StatusCode((int)HttpStatusCode.BadRequest);
}
}
}
Again note the simplification of the controller. This now allows the factory to be mocked and injected when testing and by extension allows the mocks to be used by the RabbitMQHelper. You can use your mocking framework of choice for dependencies or pure DI.
I dont think it is a unit test scenario. If you want to to test with external component ie database or message queue then i suggest you do it as integration test.
What we do is to have a sand box environment with component SQL database and azure message bus. We have code to correctly set the state for this component ie seed the database and clear the message bus. Then we run test on the environment and check the state of the database or message bus count etc.
Here I write some code in static methode which is in class file please Help me how can i create instance of static class
public class ConnectionString
{
public static void CreateCommand(string querystring, string connectionString)
{
using(SqlConnection cn = new SqlConnection(connectionString))
{
SqlCommand cmd = new SqlCommand(querystring, cn);
cmd.Connection.Open();
cmd.ExecuteNonQuery();
}
}
}
Just call it like this:
string querystring = "Your values here";
string connectionString = "Your values here";
ConnectionString.CreateCommand(querystring, connectionString);
That's it.
Your ConnectionString class can be refactored to implement an interface like this:
public interface IDataAccess
{
void CreateCommand(string querystring, string connectionString);
}
this interface allows us to inject its implementation in the controller that you mentioned in the comments. So your ConnectionString class (renamed to more meaningful name DataAccess) should look like this:
public class DataAccess : IDataAccess
{
public void CreateCommand(string querystring, string connectionString)
{
using (SqlConnection cn = new SqlConnection(connectionString))
{
cn.Open();
using (SqlCommand cmd = new SqlCommand(querystring, cn))
{
cmd.ExecuteNonQuery();
}
}
}
}
then in your controller / client class you can have the concrete implementation injected at the run time..
public class DataController : Controller
{
private readonly IDataAccess dataAccess;
public DataController(IDataAccess dataAcces)
{
this.dataAccess = dataAcces;
}
public ActionResult ShowData()
{
string querystring = "you t-sql query";
string connectionString = "<you sql connection string>";
this.dataAccess.CreateCommand(querystring, connectionString);
return this.View();
}
}
If you are using MVC and dont know how to resolve the dependencies then refer to this article
Alternatively you can just new up the instance of DataAccess class like this:
public class DataController : Controller
{
private readonly IDataAccess dataAccess;
public DataController()
{
this.dataAccess = new DataAccess();
}
public ActionResult ShowData()
{
string querystring = "you t-sql query";
string connectionString = "<you sql connection string>";
this.dataAccess.CreateCommand(querystring, connectionString);
return this.View();
}
}
I will not recommend this approach as it wont be possible to unit test it.
Hope this helps!