Link to home
Start Free TrialLog in
Avatar of dyarosh
dyarosh

asked on

How to override a method in a class that implements an interface in C#

I have a class that is compiled into its own DLL.  The purpose of the class is to interface with a database that can be an ORACLE or SQL Server database.  I created the class as public partial class DatabaseMethods : IDatabaseMethods.  I'm including the class code here but have removed the methods that aren't needed for this question.
namespace DatabaseMethodsClass
{
    public interface IDatabaseMethods
    {
        string GetDatabaseConnection(int dbid);
        string GetEFConnectionString();
    }

    public partial class DatabaseMethods : IDatabaseMethods
    {
        private string _serversxml;
        public virtual string serversxml
        {
            get { return _serversxml; }
            set { this._serversxml = @"file://PL-WLMSPTPV04/websites/i21/Includes/xml/Servers.xml"; }
        }

        private HttpContextBase _httpContext = null;
        private CryptorEngine _CryptorEngine = null;
        public DatabaseMethods() : this(new HttpContextWrapper(HttpContext.Current), new CryptorEngine()) { }
        public DatabaseMethods(HttpContextBase hcb, CryptorEngine ce)
        {
            _httpContext = hcb;
            _CryptorEngine = ce;
        }

// Other methods removed to make this easier to read

        public virtual string GetDatabaseConnection(int dbid)
        {

            // Servers.xml contains a list of all the servers Intranet Support Team Applications are deployed to;
            // Also contains the Connection Strings for the Application Catalog application for each environment (DEV, UAT, PROD)
            XmlDocument xmldoc = LoadServersXML(serversxml);

            string host = DetermineHost();
            int env = DetermineEnvironmentFromHost(host, xmldoc);
            string appcatalogcs = GetAppCatalogConnectionString(env);

            // Get Database record
            AppCatalog_DBConnection db = new AppCatalog_DBConnection();
            db = GetDBConnectionRecord(dbid, appcatalogcs, env);
            return FormatConnectionString(db);
        }

        public virtual string GetEFConnectionString()
        {
            // Servers.xml contains a list of all the servers Intranet Support Team Applications are deployed to;
            // Also contains the Connection Strings for the Application Catalog application for each environment (DEV, UAT, PROD)
            XmlDocument xmldoc = LoadServersXML(serversxml);

            string host = DetermineHost();
            int envid = DetermineEnvironmentFromHost(host, xmldoc);

            // Create EF Connection String based on environment
            string EFConnectionString = "";
            switch (envid)
            {
                case 1:     // DEV
                    EFConnectionString = @"metadata=res://*/Models.AppCatalog.csdl|res://*/Models.AppCatalog.ssdl|res://*/Models.AppCatalog.msl;provider=System.Data.SqlClient;provider connection string='data source=servername;initial catalog=Catalog;persist security info=True;user id=username;password=password;multipleactiveresultsets=True;App=EntityFramework'";
                    break;
                case 2:     // UAT
                    EFConnectionString = @"metadata=res://*/Models.AppCatalog.csdl|res://*/Models.AppCatalog.ssdl|res://*/Models.AppCatalog.msl;provider=System.Data.SqlClient;provider connection string='data source=servername;initial catalog=Catalog;persist security info=True;user id=username;password=password;multipleactiveresultsets=True;App=EntityFramework'";
                    break;
                case 3:     // PROD
                    EFConnectionString = @"metadata=res://*/Models.AppCatalog.csdl|res://*/Models.AppCatalog.ssdl|res://*/Models.AppCatalog.msl;provider=System.Data.SqlClient;provider connection string='data source=servername;initial catalog=Catalog;persist security info=True;user id=username;password=password;multipleactiveresultsets=True;App=EntityFramework'";
                    break;
                default:
                    // Throw error - AppCatalog environment doesn't exist in Servers.xml
                    throw new ValidationException(String.Format("AppCatalog Connection String does not exist for Environment {0} in Server Configuration File", envid));
            }
            return EFConnectionString;
        }
    }

    public partial class AppCatalog_DBConnection
    {
        public int dbconnectionID { get; set; }
        public string dbconnectionUserName { get; set; }
        public string dbconnectionUnsecuredPassword { get; set; }
        public string dbconnectionServer { get; set; }
        public string dbconnectionPortNumber { get; set; }
        public string dbconnectionServiceName { get; set; }
        public string dbconnectionCatalog { get; set; }
        public string dbconnectionSID { get; set; }
        public int dbconnectionEnvironmentID { get; set; }
        public int dbconnectionDatabaseID { get; set; }
        public int dbconnectionServerTypeID { get; set; }
    }
}

Open in new window


Now I have an app that wants to use the DatabaseMethods class but needs to provide its own version of the GetEFConnectionString Method.

How would I do that?  If I create a new class that implements the Interface and implement the 2 methods in the interface, how do I get access to all of the private methods in the DatabaseMethods class?  I need the class to be an interface so I can do Dependency Injection for my testing.

Here is what I have so far but don't konw how to tell it to implement the Interface also.

public class REF_DatabaseMethods : DatabaseMethods
{
     public override string GetEFConnectionString()
     {
            // my override code
     }
}

Open in new window


Any help is greatly appreciated!
Avatar of ste5an
ste5an
Flag of Germany image

This concept is called Dependency Inversion Principle (DSP - the D in SOLID). One consequence of it is: the interface should be declared in the orbit of the consumer assembly (higher level), not in the assembly containing the concrete implementation (lower level).

// Placed in the higher-level assembly.
namespace DatabaseConsumer
{
	public interface IDatabaseMethods
	{
		string GetDatabaseConnection(int dbid);
		string GetEFConnectionString();
	}
}

// Placed in one of the lower-level assemblies.
// Which is referenced in the DI container (Unity, MEF).
namespace DatabaseProviderDevelopment
{
    public class DatabaseMethods : IDatabaseMethods
    {
        public string GetDatabaseConnection(int dbid) { }
		string GetEFConnectionString() {};
    }
}

// Placed in one of the lower-level assemblies.
// Which is referenced in the DI container (Unity, MEF).
namespace DatabaseProviderProduction
{
    public class DatabaseMethods : IDatabaseMethods
    {
        public string GetDatabaseConnection(int dbid) { }
		string GetEFConnectionString() {};
    }
}

Open in new window


Now for the code smells:
1. An interface named database methods makes no sense. It's either a catch basin, then the methods belong to different interfaces. Or it is badly named, it does not transport the usage in its name.
2. Methods should never use the verbs Get and Set. Except they are property getter/setter methods. As we have properties in C#, use them instead in your interface declaration.
3. A connection (GetDatabaseConnection) is not a string. string as a return type makes no sense.
4. A better name: CreateDatabaseConnection.
5. You don't need virtual methods, as long as you don't want to override some of your methods.
6.
        public virtual string GetDatabaseConnection(int dbid)
        {

            // Servers.xml contains a list of all the servers Intranet Support Team Applications are deployed to;
            // Also contains the Connection Strings for the Application Catalog application for each environment (DEV, UAT, PROD)
            XmlDocument xmldoc = LoadServersXML(serversxml);
            return FormatConnectionString(db);
        }

Open in new window

You're describing entities. Implement them. Use XML serialziation.
7. Sounds like your server list should be also decoupled and injected.
8. Never hardcode connection strings. Use .config files instead of it.
9. switch() indicates often (really often) that you can and should use inheritance. See my sample above.
10.Your class AppCatalog_DBConnection does not represent a connection. It's a data transfer object (DTO, POCO), which holds the configuration information for a connection. Name it accordingly.
11. I would guess from your code, the setters of AppCatalog_DBConnection should be private.
Avatar of dyarosh
dyarosh

ASKER

I appreciate your comments but need some clarification.

1. The DatabaseMethods Class (I will come up with a better name) purpose is to "create" (as you suggested) the Connection String for a database that would be used as a parameter into an OracleConnection object or SqlConnection object which takes a string as a parameter.  That is why the the method returns a string.  The information needed to create the connection string is stored in a database.  The dbid parameter in the method call is used to determine which database record to retrieve for the information.

We have many applications and environments that those applications run in (i.e. DEV, UAT, PROD) which is why we don't store the connection strings in config files.  We also don't want the sensitive information such as username and password stored in flat file config files.

With that said, based on your recommendations I would remove the Interface definition from the same file and assembly as the definition for the DatabaseMethods class and put it in the same assembly as the class that is consuming the DatabaseMethods object.  Is that correct?

VS Studio Project 1 (DatabaseProviderDevelopment.DLL)
namespace DatabaseProviderDevelopment
{
    public class DatabaseMethods : IDatabaseMethods
    {
        public string GetDatabaseConnection(int dbid) { }
		string GetEFConnectionString() {};
    }
}

VS Studio Project 2 (Project.DLL)
namespace DatabaseConsumer
{
	public interface IDatabaseMethods
	{
		string GetDatabaseConnection(int dbid);
		string GetEFConnectionString();
	}
}

Open in new window


If the above is correct and the interface is declared in a separate assembly, how do I get the first assembly to compile since IDatabaseMethods isn't defined in the assembly?  

If the Interface is defined in the Higher Assembly then every project that wants to use the DatabaseMethods object would have to know to define the interface which doesn't make sense to me.

I want to have one class that will have a method for creating the connection string and also a method to create the EF connection string.  For the most part, creating the connection string won't need to be overridden unless we add a new database to our mix.  However creating the EF Connection String will need to be overridden by each project that consumes the object that uses the EF.

So how do I structure the DatabaseMethods class so that it can be consumed by many applications?  I apologize for my ignorance but we are in the process of converting all of our legacy classic asp applications to asp.net and I want to get the infrastructure setup using SOLID OOP which is new to our group.
ASKER CERTIFIED SOLUTION
Avatar of ste5an
ste5an
Flag of Germany image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of dyarosh

ASKER

Thank you for your insight and the links.