Jump to content


Check out our Community Blogs

Register and join over 40,000 other developers!


Recent Status Updates

View All Updates

Photo
- - - - -

Decoupling Application Logic from your UIs

decoupling dependancy injection c# abstraction

  • Please log in to reply
No replies to this topic

#1 sam_coder

sam_coder

    CC Addict

  • Senior Member
  • PipPipPipPipPip
  • 380 posts

Posted 14 May 2014 - 04:11 PM

Introduction

So I've been lurking in the forums, and thought I could write something up many of you might benefit from reading.  Admittedly most of the inspiration for this came from tutorials and discussions surrounding application authentication.  But make no mistake, this is not an authentication or Login tutorial.  Though you might learn something from it to help there.

This is a tutorial to explain a simple way of decoupling implementation from its usage.  For the benefit of those newer to programming, I'll briefly explain a bit.

 

When I say decoupling, I mean separating, or abstracting.  Consider a car.  I'm no mechanic, and have very limited knowledge of how they work.  The technical bit however is very much abstracted.  I don't need to know how it works.  And because it's abstracted in a way that is standard.. you know Key to start, Gas to move, Brake to stop, steering wheel to turn; I can jump into any car, regardless of the vendor or model, and start driving.

 

With that in mind; why would I want a form, designed to take credentials from a user, and give them feedback have anything at all to do with the login process itself?  If I decided to change how I log in to the application, I might very well have to teach it how to test credentials again, from scratch.

 

And I will stop here for a second.  Because it is absolutely true, that if I decided to make a change like that, no matter what, I would have to program that from scratch.  but, by exposing that new functionality in the same way, I don't need to change any part of my already working application, to be able to use this new login component.

I think it's time to move on; as we've pretty much hit the extent of my knowledge on cars. lol

 

In this example, I'm going to show you how to build an interface, that your application understands, and a login component pretty much using exactly the same logic I've seen in other tutorials and examples (which are very insecure), but I think it's a great place to start, because it proves my point.  And it also offers up a great opportunity to further explain this concept, and how it can very minimally impact your application, while making amazing improvements to the application over all.

Interfaces

 Interfaces might better be called "usage contracts".  They allow usage logic to make assumptions about how to make use of something, without knowing how they are implemented.  A family sedan, and a pickup truck, if implemented in C#, might both implement the ISmallAutomaticVehicle interface.  We as drivers, have usage logic that know how to use a steering wheel, gas, break and automatic shifter; clearly defined by this interface.

So, imagine this.

private ISmallAutomaticVehicle myVehicle;

I go to the dealer, and I buy a PickupTruck.

myVehicle = new PickupTruck();

but it wouldn't matter if I instead bought a Corolla.

myVehicle = new Corrolla();

Because they both implement ISmallAutomaticVehicle, and my DriveToWork method requires only that.

private void DriveToWork(ISmallAutomaticVehicle vehicle){
}

Have I explained this enough? I'd like to move on and Start showing how we could decouple a simple login provider, again using basically the same thought as seen in a few other places on the C# forums.  If this isn't clear, leave some comments with questions, and I'll do my best to answer.

Our Login Provider

The first thing we're going to do is create a new solution.  I'm actually attaching mine to the thread, so you 're welcome to download that and follow along.  Or you're also welcome to just read, interpret and do it that way.

 

Anyways, call the solution whatever you like, perhaps LoginExample.  And in that solution, add a class library called LoginSupport.

 

In order to properly decouple the Login functionality from the application entirely, we need two things.  We need an interface describing what a login provider can do, which is done using method signatures.  And of course we need to define any custom types that both the login provider implementation, and your application will need to understand.  For the purposes of this tutorial, we will use an enumeration (in a real system, you'd likely have several return types, and interfaces, but I just don't want to overcomplicate this).

LoginResponse

First add a class to your LoginSupport Project.  Call it LoginResponse.  Change its declaration to enum, and remove the constructor.  You're also going to add the enumeration values to it, and it should look like this.

 

using System;

namespace LoginSupport
{

    public enum LoginResponse
    {
        Success,
        
        Failure,
        
        Locked
    }
}

 

Explaining enumerations at this point is beyond the scope of this tutorial, but if you have questions, please feel free to ask.

ILoginProvider

Next we need to create our interface.  This is done in pretty much the same manner.  Add a class to your project, called ILoginProvider.  Change its declaration to interface, and add our method signatures.  It should look like this.

 

using System;

namespace LoginSupport
{
    
    public interface ILoginProvider
    {
        bool IsAuthenticated();
        LoginResponse Login(string username, string password);
        void Logout();
    }
}

 

Creating a login provider is as simple as creating a new class, and implementing the interface.  Compilation will fail until you implement everything in this interface.

HardCodedLoginProvider

For the purposes of this tutorial, as stated, I'm going to make a dummy login provider.  It's not secure, it doesn't follow any best practices, but it's enough to build an application using this ILoginProvider interface.  Maybe in another tutorial, I'll build a DBLoginProvider.

Anyways, add a new class to the LoginSupport project, and call it HardCodedLoginProvider.  No changes needed to its declaration.  We're going to implement the interface, and fill it in with some supporting logic.

 

using System;

namespace LoginSupport
{
    
    public class HardCodedLoginProvider : ILoginProvider
    {
        
        private const string _username = "testaccount";
        
        private const string _password = "notsecure";
        
        private bool _authenticated = false;
        
        private bool _locked = false;
        
        private int _loginAttempts = 0;
        
        private int _maxLoginAttempts = 3;
        
        public bool IsAuthenticated() {
                return _authenticated;
        }
        
        public LoginResponse Login(string username, string password)
        {
            _loginAttempts++;
            if (_loginAttempts > _maxLoginAttempts) {
                _locked = true;
            }
            if (_locked) {
                return LoginResponse.Locked;
            }
            if (username == _username && password == _password) {
                _loginAttempts = 0;
                _authenticated = true;
                return LoginResponse.Success;
            } else {
                return LoginResponse.Failure;
            }
        }
        
        public void Logout() {
            _authenticated = false;
        }
        
    }
}

 

Roll your eyes, laugh it up.  This is actually a great starting point if you only needed this type of really basic password protection. You could start building your application from the ground up, and then worry about the specifics of where you want to store credentials after.  This might even be preferred during early development.

 

In fact, if your following patterns like this, there are some amazing libraries out there that implement something called dependency injection.  They allow you to build additional components that implement common interfaces, that are configurable at deploy time.

 

Imagine your app in production, built to authenticate against SQL server.  A need comes up to use Active Directory.  So you build an Active Directory implementation, drop it on the production server, and change a one liner in the configuration file.  That's it!

 

Really, dependency injection is beyond the scope of this tutorial, but if you were curious, go google it!

Our Application

Ok, so we have our login component.  So let's create a Windows Forms Application.  Call it whatever you like, perhaps LoginTester.  Once you have this completed, you need to add a reference to our LoginSupport project.

Program.cs

The Program class is a great place to start here.  For this example, we're going to use it to create a singleton instance of our login provider.  To start, you should add a using statement to the top, indicating the namespace where our login code is located.

 

using System;
using System.Windows.Forms;

using LoginSupport;

 

After doing this, we will want to add a static ILogin variable to the Program Class.  This can be anywhere in the class, but probably right on top of the Main method.

 

internal sealed class Program
{

    public static ILoginProvider LoginProvider;
    
    [STAThread]
    private static void Main(string[] args)
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new MainForm());
    }
    
}

 

The last thing we need to do to the Program class, is actually create an instance of our LoginProvider.  Note that we set the type, to the interface ILoginProvider.  We are going to create an instance of our HardCodedLoginProvider; and this is fine because it implements this interface.

 

I should also note, that if HardCodedLoginProvider also had other methods, not specified in ILogin, you wouldn't be able to execute them from this reference.  You could direct cast back to HardCodedLoginProvider, but we typically wouldn't do that.  That would defeat the whole point of decoupling the login logic, from the UI.

 

using System;
using System.Windows.Forms;

using LoginSupport;

namespace LoginTester
{

    internal sealed class Program
    {
    
        public static ILoginProvider LoginProvider;
        
        /// <summary>
        /// Program entry point.
        /// </summary>
        [STAThread]
        private static void Main(string[] args)
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
           
            LoginProvider = new HardCodedLoginProvider();
            
            Application.Run(new MainForm());
        }
        
    }
}

 

If and when you ever made a proper login provider, this is the only line in your application that will ever change.  Instead of new HardCodedLoginProvider(), we might instead say new DBLoginProvider() or... new ActiveDirectoryLoginProvider().  Because we would build a provider to work with the interfaces we're already designed, and the application is built to work with them, the transition would be seamless.

MainForm

The last part of code for this tutorial, is of course, the Form.  In my project, I have MainForm, you probably have Form1.  It doesn't matter what it's called, I just didn't use Visual Studio to set this tutorial up.

 

The only really important components you need on you form are, a username textbox, a password textbox and a submission button.  In order for the code to be copy and paste able from this site, you need to ensure they are named the same.  Though I trust you are all more than capable of adjusting control references in my code.

 

Control Description

Control Name

The username text box

usernameTextBox

The password text box

passwordTextbox

Login or Submit button

loginButton

 

Typically you would set a mask character for your password box.  Don't worry about it for this test, if you don't want to.

The code supporting our Login component test might look like this.

 

void TestLoginMechanism() {
    //First validate they entered information, so we dont REALLY annoy the user
    if (!ValidateInputs()) {
        MessageBox.Show("You must enter both a username and a password",
                       "User Input Validation Failure"
                       MessageBoxButtons.OK, 
                       MessageBoxIcon.Exclamation, 
                       MessageBoxDefaultButton.Button1);
        return;
    }
    //We try the entered credentials against our login provider
    //note we can reference that from the Program class, since we made it static.
    var result = Program.LoginProvider.Login(usernameTextBox.Text, passwordTextBox.Text);
    
    //Since we are simply explaining how this works, we test the response, and notify the user accordingly
    if (result == LoginResponse.Success) {
        MessageBox.Show("Successful Login Attempt!",
                       "Successful Login Attempt"
                       MessageBoxButtons.OK, 
                       MessageBoxIcon.Information, 
                       MessageBoxDefaultButton.Button1);
        return;
    }
    if (result == LoginResponse.Failure) {
        MessageBox.Show("Username and/or Password provided are incorrect.  Please try again.  But be careful, you will be locked out after 3 login attempts.",
                       "Failed Login Attempt"
                       MessageBoxButtons.OK, 
                       MessageBoxIcon.Exclamation, 
                       MessageBoxDefaultButton.Button1);
        return;
    } 
    if (result == LoginResponse.Locked) {
        MessageBox.Show("This account has been locked out, please contact your system administrator.",
                       "Account Locked"
                       MessageBoxButtons.OK, 
                       MessageBoxIcon.Stop, 
                       MessageBoxDefaultButton.Button1);
        return;
    }
}

bool ValidateInputs() {
    //basically if either the username or password are false, we want to NOT try to authenticate,
    //this will allow us to correct the user
    if (string.IsNullOrEmpty(usernameTextBox.Text)
        || string.IsNullOrEmpty(passwordTextBox.Text)) {
        return false;
    }
    return true;
}

 

After you paste that in, again if your control names match, you just have to wire it up.  So double click on your Login Button, so you can get a Click event handling stub.  And you will want to call the TextLoginMechanism() from it.

 

void LoginButtonClick(object sender, System.EventArgs e)
{
    //Since this is only explaining how to use a mechanism like this, we will use TestLoginMechanism(),
    //rather than actually logging into the application.
    TestLoginMechanism();
}

 

You could even throw in a Cancel button for good measure if you like.

 

void CancelButtonClick(object sender, System.EventArgs e)
{
    //Just close the form
    Close();
}

 

So what's really cool about all this, is we should now have a fully functioning application.  And our login form is completely decoupled from the login components.  In

fact, the login form has no say in how the security policies work (such as 3 failed login attempts), the type of encryption (if any)...

 

If we were to build that DB Provider, and implement the ILogin interface, the only line of code we would change in the application is the Program.cs, where we actually create the instance of our Login Provider.

Summary

We covered a very little amount of ground here, in a lot of words.  The whole point of your read here is to show firsthand, how decoupling units of functionality can make your code overall more maintainable, and much more scalable.

 

You would typically use similar techniques to abstract specific domains of code.  Another example might be data access.  You might even hear people use the term "layer", like "My data access layer".

 

Units of functionality don't always have to (nor should they always) use interfaces.  Interfaces  are put in place when you feel as though you might have components that from your application's perspective, do the same thing; you may want to swap them out for use in different situations, but underlying have very few similarities.

Another example where this might be useful, would be for logging output.  Maybe you have an ILogWriter interface, which is implemented by a text file writer, a database writer, and XML writer which supports making calls to SOAP services and a windows event log writer.  From your applications point of view, they log, they do the job; but from your customer's point of view, having the logs show up in a useful way is value add! (and you can change it with a single line of code)

 

Anyways, I've talked my face off. 

Thanks for reading!  Questions, comments, corrections, criticisms.. they're all welcome!

 

Attached File  DecoupleTutorial.zip   51.13KB   166 downloads

 

Also, I wrote this while working in Sharp Develop.  I converted everything over best I could to Visual Studio 2008 best I could, so should make an easy upgrade to whatever you're working with.  Let me know if that didn't go so well, I'm not getting errors with VS 2008 any more.


  • 0





Also tagged with one or more of these keywords: decoupling, dependancy injection, c#, abstraction