Scenario: ASP.Net web app (n-tier linq2sql) on local IIS with connection to SQL 2008 database over VPN. Some data is replicated to a local SQL 2008 express DB (different name). If the connection is down to VPN database, we would like to use the local instance for some parts of the web app.
My Question is, how can the following solution be improved. we have involves a lot of passing the connection string about. If this involved mirroring, we could set the failover partner but as it is a different database I don't think this is possible.
Current Code:
Check if we can connect remotely, put the working connection string in session
Session_Start()
{
//if can connect to remote db
Session["ConnStr"] = //remote connection
//else
Session["ConnStr"] = //local connection
}
Pass connection string in UI to BLL
protected void ods1_ObjectCreating(object sender, ObjectDataSourceEventArgs e)
{
TestManager myTestManager = new TestManager(Session["ConnString"].ToString());
e.ObjectInstance = myTestManager;
}
Pass to DAL
public class TestManager
{
private readonly string _connectionString;
public TestManager(string connectionString)
{
_connectionString = connectionString;
}
[DataObjectMethod(DataObjectMethodType.Select, true)]
public List<Test> GetAll()
{
TestDB testDB = new TestDB(_connectionString);
return testDB.GetAll();
}
}
Set connection in DAL creating DataContext in constructor
public class TestDB
{
public TestDB(string connectionString)
{
_connectionString = connectionString;
_dbContext = new TestDataContext(_connectionString);
}
private TestDataContext _dbContext;
private string _connectionString;
public string ConnectionString
{
get
{
return _connectionString;
}
set
{
_connectionString = value;
}
}
public TestDataContext DbContext
{
get
{
return _dbContext;
}
set
{
_dbContext = value;
}
}
public List<Test> GetAll()
{
var query = from t in DbContext.Tests
select new DTO.Test()
{
Id = t.Id,
Name = t.Name
};
return query.ToList();
}
Related
I'm trying to connect to Oracle through .NET Core following this docs:
https://docs.oracle.com/en/database/oracle/oracle-data-access-components/19.3/odpnt/InstallCoreConfiguration.html#GUID-24C963AE-F20B-44B5-800C-594CA06BD24B
But I'm facing this error:
This property cannot be set after a connection has been opened
System.InvalidOperationException: This property cannot be set after a
connection has been opened at
Oracle.ManagedDataAccess.Client.OracleDataSourceCollection.Add(String
tnsName, String tnsDescriptor) at
Infrastructure.Persistence.Factory.ConnectionFactory.SetupOracleConnection()
in
C:\Users\WINDOWS\RiderProjects\TicketsAPI\Infrastructure\Persistence\Factory\ConnectionFactory.cs:line
22
I don't have clue why this is happening, there's my ConnectionFactory:
using System.Data;
using Microsoft.Extensions.Logging;
using Oracle.ManagedDataAccess.Client;
namespace Infrastructure.Persistence.Factory;
public class ConnectionFactory : IConnectionFactory
{
private const string TnsName = "ORCL";
private readonly ILogger<ConnectionFactory> _logger;
public ConnectionFactory(ILogger<ConnectionFactory> logger)
{
_logger = logger;
}
public IDbConnection? Connection => SetupOracleConnection();
private OracleConnection? SetupOracleConnection()
{
OracleConfiguration.OracleDataSources.Add(TnsName,
"(DESCRIPTION =(ADDRESS = (PROTOCOL = TCP)(HOST = DESKTOP-FP8GDE4)(PORT = 1521))(CONNECT_DATA =(SERVER = DEDICATED)(SERVICE_NAME = orcl)))"); // <-- This is the line 22 mentioned in the StackTrace
. . . //Some configs that are in the doc file.
OracleConnection oracleConnection = null!;
try
{
oracleConnection = new OracleConnection($"user id=kevinadmin; password=1234; data source={TnsName}");
oracleConnection.Open();
return oracleConnection;
}
catch (Exception e)
{
_logger.LogError("An error occurred while trying to connect to database {EMessage}", e.Message);
return null;
}
finally
{
oracleConnection?.Close();
}
}
}
[edit:
I may have misunderstood the issue. If the exception is happening on second and subsequent calls to Connection, then this answer might apply]
By declaring your property like
public IDbConnection? Connection => SetupOracleConnection();
you're instructing the { get; } (which is what the => is sugar for) to execute the SetupOracleConnection() every time it is accessed.
You should try to encapsulate that into a singleton instance.
private IDbConnection? _connection = null;
public IDbConnection? Connection => _connection ?? ( _connection = SetupOracleConnection());
How can I change the environment variable RUN TIME?
Using Login PAGE I am selecting Development or Production.
Based on that I have to use my ConnectionString device specifically.
"ConnectionStrings": {
"PROJECTNAME_Development": "SERVER_DETAILS",
"PROJECTNAME_Production": "SERVER_DETAILS",
}
Here are my LoginServices.CS file
public class LoginService: ILoginService
{
private readonly string _connectionString = string.Empty;
private readonly IConfiguration _configuration;
}
public LoginService (IConfiguration configuration)
{
_configuration = configuration;
//If ADMIN SELECT Development
_connectionString = _configuration.GetConnectionString("PROJECTNAME_Development");
//If ADMIN SELECT Production
_connectionString = _configuration.GetConnectionString("PROJECTNAME_Production");
}
UPDATE 1
I tried this code. It works fine but is there any other way like at one location I make a change and Environment Variable value gets updated.
My Controller
[HttpPost]
public IActionResult Login(LoginModel model)
{
try
{
if (ModelState.IsValid)
{
model.DevelopmentServer = "Development";
// model.DevelopmentServer = "Production";
...
var Result = ILoginService.AdminLogin(model);
public LoginService(IConfiguration configuration, IOptions<ConnectionStringDetails> connectionStrings)
{
_proDBCon = connectionStrings.Value.PROJECTNAME_Development;
_proDBConProduction = connectionStrings.Value.PROJECTNAME_Production;
}
In the same file LoginService.cs File
public LoginInfo AdminLogin(LoginModel model)
{
if (model.DevelopmentServer == "Development") {
_connectionString = _proDBCon;
}
else {
_connectionString = _proDBConProduction;
}
}
Assuming that you need to switch between two databases from UI at any cost, you won't be able to complete it entirely using containers/injections. You have to use the value passed from UI to determine which database connection string should be used.
public class LoginService {
// ...
private string GetConnectionString(string connectionStringNameFromUi){
return _configuration.GetConnectionString
}
}
Meanwhile, I would consider deploying two separate instances of an application, one configured for dev and another for prod. In such way, you would get more out of the box (injections container and environment configuration).
I share my code to connect to SQLSERVER :
public class Context:DbContext
{
private readonly IDbConn dbConn;
public Context()
{
dbConn = new DbConn();
}
public DbSet<table1> t1{ get; set; }
public DbSet<table2> t2{ get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(dbConn.Connection);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<table1>().HasKey(x => new { x.column1, x.column2});
modelBuilder.Entity<table2>().HasKey(x => new { x.column1, x.column2});
}
}
public class DbConn : IDbConn
{
private SqlConnection sqlConnection;
public SqlConnection Connection
{
get
{
return new SqlConnection(#"SERVER=xxxxxxx;Initial Catalog=xxxxxxx;User ID=xxxxxx; Password=xxxxxx");
}
}
}
public class Test
{
public static table1 GetFirst()
{
using (var context = new Context())
{
var cb = context.t1.FirstOrDefault(); //The error occurs here
return cb;
}
}
}
I'm a beginner in XAMARIN project.
I try to connect to SQLSERVER and try to get some data from database, but when I execute a LINQ request, I get an error. I already tried to change the connectionStrings but still have the same error.
Can someone help me to resolve this error.
Thanks
I think if your sql server is on a fixed port you can just say what port it is using. Normally not needed for standard ports
return new SqlConnection(#"Data Source=xxxxxxx,portnumberhere;Initial Catalog=xxxxxxx;User ID=xxxxxx; Password=xxxxxx");
if you are using dynamic ports you normally need to give the instance name.
return new SqlConnection(#"Data Source=xxxxxxx\myinstance;Initial Catalog=xxxxxxx;User ID=xxxxxx; Password=xxxxxx");
check no firewalls are blocking traffic between you and the server you are using and as the message says you may in some cases need to ensure the browser service on the database server is running, particularly if using dynamic ports.
Edit:
I think the issue could be where you use
return new SqlConnection(#"SERVER=xxxxxxx;Initial Catalog=xxxxxxx;User ID=xxxxxx; Password=xxxxxx");
instead off
return new SqlConnection(#"Data Source=xxxxxxx;Initial Catalog=xxxxxxx;User ID=xxxxxx; Password=xxxxxx");
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?
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.