Archiv für den Monat September 2013

ASP.NET MVC & TDD mit Unit Tests “Grundlagen” – Part 1


Wenn man sich das erste mal intensiv mit dem Thema Test Driven Development (TDD) auseinandersetzt, dann hat man es hier mit vielen Begriffen und evtl. neuen Technologien zu tun. Da ich bisher auch noch sehr frisch auf dem Gebiet des TDD unterwegs bin und ich meine aktuellen Entwicklungsversuche noch nicht als TDD bezeichnen würde, will ich hier einem kurzen Überblick über die verwendeten Begrifflichkeiten und Technologien geben auf die ich gestoßen bin und dann in einem späteren Artikel im Detail auf einzelne Umsetzungen eingehen.

Um einen ersten Einblick in die Entwicklung für TDD zu bekommen habe ich mir eine Projektvorlage für ein ASP.NET MVC4 Projekt mit Entity Framework 5 (EF5) “erstellt” mit den passenden Klassenbibliotheken für die Contracts (Interfaces), Webseite (Ui), Models, Entities, … (Grundprojekt kann bei mir unter Codeplex angeschaut werden).

Auf die folgenden Themen bin ich dabei gestoßen:

  • Dependency Injections (DI) und Inversion of Control (IoC)
  • DI Frameworks wie Castle Windsor, Unity, Autofac, …
  • Simplemembership Provider für eine einfache Userverwaltung
  • Unit of Work und Repository Design Pattern für die EF Umsetzung
  • Moq und shims bzw. stubs für die Unit Tests

Dependency Injections (DI) und Inversion of Control (IoC)

Diese beiden Begrifflichkeiten hängen eng mit einander zusammen und  ich wüsste spontan nicht wo der Unterschied lieg oder wie ich diesen erläutern sollte, daher nicht wundern, wenn diese beiden Begriffe euren Weg immer gemeinsam kreuzen.

Durch DI werden dem Konstruktor einer Klasse bereits wichtige Objekte übergeben und nicht mehr in der klasse selbst Instanziiert/erstellt. Die Umsetzung von DI ist auch möglich ohne ein entsprechendes DI Framework wie z.B. Castle Windsor, das wird dann z.B. auch “poor man’s dependency” genannt.

Um ASP.NET MVC mit DI umzusetzen z.B. für das DB Repository wird nur noch an einer Stelle und nicht mehr in jeder Klasse selbst eine Instanz unseres Repositories erstellt. In ASP.NET MVC wird hier die “Global.asax” verwendet und eine eigene ControllerFactory erstellt, welche von der DefaultControllerFactory ableitet und die Funktion „GetControllerInstance” überschreibt und die jeweils angefragte Controllerinstanz zurück gibt. Der Controller besteht dann nicht mehr aus einem Parameterlosen Konstruktor, sondern hat z.B. unser DB Repository als Parameter, welchen er Instanziiert übergeben bekommt. Dabei ist es immer wichtig das entweder Abstrakte Klassen oder Interfaces für die Übergabeparameter verwendet werden um später einfacher die Unit Tests erstellen zu können und um nicht immer von einer Implementierung abhängig zu sein. Hier lassen sich in den Tests dann einfacher eigene Testimplementierungen umsetzten.

DI Frameworks

DI Frameworks nehmen einem die Arbeit ab, wenn es darum geht z.B. einen Abhängigkeiten Baum aufzulösen. Denn man Definiert an einer Stelle im Programm welches Interface z.B. durch welche Klasse Instanziiert wird und wenn diese Klasse auch wieder eine Abhängigkeit besitzt und wir diese bereits definiert haben, dann wird diese ebenfalls automatisch aufgelöst. Hier lässt sich ebenfalls viel Zeit beim Definieren von Abhängigkeiten sparen, wenn man sich an eigene Namenskonventionen hält, denn diese lassen sich meist automatisch vom DI Framework auflösen. In unserer ASP.NET MVC Anwendung wird nur beim Erstellen des Controllers die passende Controllerinstanz in unsere eigenen ControllerFactory aufgelöst und die entsprechende Instanz zurückgegeben und an keiner weiteren Stelle wird das DI Framework benutzt um Objekte zu erstellen!

Wer einen guten Einblick in den Umgang mit DI und den zugehörigen Frameworks haben möchte, dem kann ich nur das Buch von Mark Seemann empfehlen “Dependency Injection in .NET” hier werden alle wichtigen Grundlagen was man beachten sollte und wie man DI umsetzt beschrieben.

Ich persönlich habe mich für die Umsetzung der DI Aufgaben für Castle Windsor entschieden, da ich bereits einmal damit Kontakt hatte und ich aktuell keine speziellen DI Aufgaben gesehen habe die ich damit nicht umsetzten könnte.

Simplemembership Provider für die Userverwaltung

An dieser Stelle muss ich vorab bereits einmal auf einen älteren Artikel “SimpleMembership mit ASP.NET MVC4 und EF” von mir verweisen, hier ist beschrieben wie man den SimpleMembership Provider einsetzt.

Denn ich habe mich in meinem Projekt gegen die Verwendung des SimpleMembership Providers entschieden. Da ich ein Beispielprojekt erstellen wollte in dem auch die Nutzerverwaltung per DI umgesetzt wird. Der Simplemembership Provider verwendet aber Statische Funktionen und hier ist keine DI möglich. Wer es nicht auf die Pure Umsetzung von DI anlegt, kann sicherlich problemlos auf den Simplemembership Provider zurückgreifen, so lange der Funktionsumfang ausreichend ist. Sollte man aber bereits zu beginn merken das man diesen erweitern muss, dann würde ich hier eher auf eine eigene Umsetzung setzten.

Unit of Work und Repository Design Pattern (EF)

Wenn man seine Projekte mit EF umsetzt und dann noch auf DI setzt, wird man schnell mit den Begriffen “Unit of Work” und “Repository Desing Pattern” in Kontakt kommen.

Da ich mich aber für EF5 entschieden habe und EF5 bereits diese Muster umsetzt mit dem DBContext, habe ich hier auf eine weitere Zwischenschicht verzichtet und keine zusätzliche “Unit of Work” bzw. “Repository Design Pattern” erstellt. Hier gibt es auch ein paar interessante Artikel zum Thema, ob man wirklich noch eine weitere Abstraktionsschicht benötigt.

http://cockneycoder.wordpress.com/2013/04/07/why-entity-framework-renders-the-repository-pattern-obsolete/
http://stackoverflow.com/questions/16467576/using-unitofwork-and-repository-pattern-with-entity-framework

Ob eine weitere Abstraktionsschicht hier wirklich notwendig ist, muss am Ende jeder für sich selbst entscheiden ob er den zusätzlichen Aufwand betreiben möchte.

Ich habe mich dafür bei der Erstellung des DBContext und für das Trennen der Entities vom Kontext entschieden, was sehr gut in dem Video Beitrag von Uli Armbruster “Entity Framework – Teil 1 DBContext und POCOS” erläutert wird.

Moq und shim bzw. stubs für die Unit Tests

Erst nach dem ich den ersten Teil meines Projektes erfolgreich erstellt hatte, war ich auch bereit für meine ersten Unit Tests, auch wenn dies nicht dem richtigen Vorgehen von TDD entspricht. In den Unit Tests selbst wird z.B. kein DI Framework verwendet, hier ernten wir nur die Früchte unserer Umsetzung in dem wir recht einfach unsere Objekte (Interfaces) Mocken können. Dabei ist es wichtig das wir alle Funktionen als Virtual deklariert, haben die wir mit moq mocken wollen.

Aber was ist Mocken eigentlich und was ist moq? Bei Unit Tests werden die Funktionen von Klassen gemockt auf die man in seiner zu testenden Funktion / Klasse zugreift, d.h. man kann angeben welche Rückgabewerte diese Funktionen liefern sollen, um damit den Funktionsablauf der Testfunktion zu bestimmen. Dafür gibt es unterschiedliche Mocking Frameworks und eines dieser Frameworks heißt “moq”, welches ich per NuGet in mein Testprojekt eingebunden habe.

Mit moq kann man aber nicht den Rückgabewert von allen Funktionsaufrufen anpassen, vor allem wenn es sich um System DLLs mit Statischen Funktionen handelt. Hier gibt es seit VS 2012 ab Premuim SP2 die Möglichkeit ganze DLLs zu “faken” siehe dazu z.B. “VS 2012 Fake DLLs für Tests (stubs und shims)”, die Integrierte MS Variante ist dabei die mir einzig bekannte Version die kostenlos ist um z.B. auch statische Funktionen zu faken/mocken.

Codeplex

Mein bereits angefangenes Projekt findet Ihr wie immer unter Codeplex –> DiTemplates –> UserManagement

Wie bereits weiter oben erwähnt, bin ich noch nicht so lange auf diesem Gebiet unterwegs und es gibt einfach unglaublich viel zu lesen über dieses Thema. Daher wenn jemand einen guten Vorschlag hat was ich evtl. falsch mache oder besser machen könnte, dann freue ich mich immer über einen guten Kommentar.

Auf die direkte Umsetzung einzelner Punkte werde ich in späteren Blogeinträgen genauer eingehen.

Advertisements

SimpleMembership mit ASP.NET MVC4 und EF


Wenn es mal wieder schnell gehen soll und man nicht viel Zeit in eine eigene Benutzerverwaltung investieren möchte, dann ist man beim SimpleMembership Provider für ASP.NET Projekte genau richtig.

Aber was macht bzw. ist der SimpleMembership Provider eigentlich?

Wenn man eine neue ASP.NET MVC4 Webseite für das Internet erstellt, dann ist hier bereits der SimpleMembership Provider “enthalten” und muss nur noch entsprechend konfiguriert werden. Dabei stellt der Provider bereits fertige Funktionen wie “WebSecurity.CreateUserAndAccount”, “WebSecurity.Login”, … zur Verfügung. Der Provider erstellt beim erstmaligen Ausführen eigene Tabellen in der angegebenen Datenbank und verwaltet damit die passenden Passwörter, Rollen, … Damit ist eine einfache Nutzerverwaltung innerhalb von ein paar Minuten konfiguriert und man hat Zugriff auf alle wichtigen Funktionen die man für eine einfache Benutzerverwaltung benötigt.

Man muss bei älteren Webprojekten zusätzlich aufpassen, denn es gibt einen “alten” MemberShip Provider aus ASP.NET 2.0 Zeiten welcher anders implementiert wird, hier muss man die Datenbanktabellen z.B. über ein extra Tool anlegen, außerdem wird kein OAuth unterstützt.

Einen sehr ausführlichen Artikel zum Thema SimpleMembership hat z.B. Jon Galloway geschrieben, dieser Artikel verschafft einen sehr guten Überblick über die Thematik.

Einbinden des SimpleMembership Providers in ein ASP.NET MVC Projekt mit EF

Im Folgenden will ich kurz erläutern wie man den bereits “fertigen” SimplemMembership Provider in einem MVC4 Projekt mit EF einrichtet und einige “unnötige” Abhängigkeiten wie einen zusätzlichen DB Context entfernt.

Dazu erstellen wir zuerst ein neues ASP.NET MVC4 Internet Projekt.

image

In dem neu erstellten ASP.NET MVC Projekt gibt es unter Filters die Datei “InitializeSimpleMembershipAttribute.cs” hier findet man den folgenden Aufruf:


 WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "UserName", autoCreateTables: true);

In diesen Aufruf tragen wir später unseren passenden Connectionstring und die Tabelle sowie die Spaltennamen für UserID und UserName ein.

image

Als nächstes legen wir erst einmal ein EF5 Model an in einer extra Klassenbibliothek an

image

Dafür wählen wir ein leeres Model

image

und erstellen eine neue Entität für das Model, den “Member” dieser wird später benötigt um unserem SimpleMembership Provider zu sagen wo der Username und die passende UserId stehen.

image

Da ich in meinen Projekten nie auf den eingebauten Connectionstring zurückgreife, sondern diesen lieber selbst “zusammenbaue” fügen wir unserer Klassenbibliothek in der sich das EF Model befindet noch eine weitere Datei “MemberModelContainer.cs” hinzu die unseren Context um einen weiteren Konstruktor erweitert. Diesem Konstruktor können wir direkt einen ConnectionString übergeben, den wir selbst zusammengebaut haben.

image

public partial class MemberModelContainer
{
    public MemberModelContainer(string connectionString) : base(connectionString)
    {
    }
}

Jetzt fügen wir unserer Webseite einen Verweis auf unser neues Projekt “Member.EF.Model” hinzu und erweitern die Web.config unter den appSettings um die folgenden Werte:


Damit wir jetzt überall auch auf unser Model zugreifen können, erstellen wir eine weitere Helper Klasse “CurrentHttpContext” die uns für den jeweiligen Request das aktuelle EF Model zur Verfügung stellt, mit den angegebenen Verbindungsdaten aus der Web.config.

public class CurrentHttpContext
{
    #region Properties
    private static string m_httpContextName = "EFDataModelHttpContextName";
    #endregion

    ///
    /// Gibt den aktuellen DataModelContainer für diesen Request zurück.
    ///
    public static MemberModelContainer GetDataModel()
    {
         //Den aktuellen dataContext nur für den aktuellen Request speichern
        if (HttpContext.Current.Items[m_httpContextName] == null)
        {
            //Connectionstring wird anhand des ModelNamens aus der web.config geladen
            MemberModelContainer efModelContainer = new MemberModelContainer(GetConnectionString("MemberModel"));
            HttpContext.Current.Items.Add(m_httpContextName, efModelContainer);
        }

        return (MemberModelContainer)HttpContext.Current.Items[m_httpContextName];
    }

    ///
    /// Gibt den Connectionstring zur DB zurück für das EntityFramework
    ///
    public static string GetConnectionString(string modelName)
    {
        //Initialize the EntityConnectionStringBuilder.
        EntityConnectionStringBuilder entityBuilder = new EntityConnectionStringBuilder();
        //Providernamen setzten
        entityBuilder.Provider = "System.Data.SqlClient";
        //Connectionstring setzten
        entityBuilder.ProviderConnectionString = GetSqlBuilder().ToString();
        //Metadaten setzten.
        entityBuilder.Metadata = string.Format(@"res://*/{0}.csdl|res://*/{0}.ssdl|res://*/{0}.msl", modelName);
        return entityBuilder.ConnectionString;
    }

    public static SqlConnectionStringBuilder GetSqlBuilder()
    {
        SqlConnectionStringBuilder sqlBuilder = new SqlConnectionStringBuilder();
        //Die Zugangsdaten aus der Web.Config laden, nicht den Connectionstring verwenden.
        sqlBuilder.DataSource = ConfigurationManager.AppSettings["DBDataSource"]; ;
        sqlBuilder.InitialCatalog = ConfigurationManager.AppSettings["DBCatalog"]; ;
        sqlBuilder.UserID = ConfigurationManager.AppSettings["DBUser"];
        sqlBuilder.Password = ConfigurationManager.AppSettings["DBPassword"];
        //Wichtige Option damit mehrere Abfragen geöffnet werden können
        //um Subabfragen ausführen zu können von Untergordneten Projekten
        sqlBuilder.MultipleActiveResultSets = true;
        sqlBuilder.IntegratedSecurity = false;
        //Dient zur Identifizierung der DB Connection/Application wenn man z.B: Fehler auf dem DB Server sucht.
        sqlBuilder.ApplicationName = "MemberManagement";

        return sqlBuilder;
    }
}

Jetzt können wir überall auf unsere eigenen Modeldaten zugreifen. Als nächstes erstellen wir die passende Datenbank aus unseren Modeldaten. Um unsere DB zu erstellen, fügen wir einfach in der Global.asax unter “Application_Start” folgendes hinzu:

MemberModelContainer container = new MemberModelContainer(CurrentHttpContext.GetConnectionString("MemberModel"));
container.Database.CreateIfNotExists();

(Privat nutze ich diese Funktion nur wenn ich entwickle, im Livebetrieb erstelle ich mir das passende SQL Script über das edmx Model und erstelle darüber meine Datenbank einmalig.)

Jetzt konfigurieren wir noch die Standardeinstellungen des SimpleMembership Providers und entfernen Code der nicht benötigt wird.

Als erstes bearbeiten wir die Datei “InitializeSimpleMembershipAttribute.cs”, hier wird einiges in die Global.asax ausgelagert, dazu gehört auch der Funktionsaufruf “LazyInitializer.EnsureInitialized”, denn diese Zeile muss nur einmal ausgeführt werden und dies lässt sich in der Global.asax im Application_Start am besten umsetzten.

Damit sieht die “InitializeSimpleMembershipAttribute.cs” am Ende folgendermaßen aus:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class InitializeSimpleMembershipAttribute : ActionFilterAttribute
{
    public class SimpleMembershipInitializer
    {
        public SimpleMembershipInitializer()
        {
            try
            {
                  WebSecurity.InitializeDatabaseConnection(CurrentHttpContext.GetSqlBuilder().ConnectionString, "System.Data.SqlClient", "MemberSet", "Id", "Username", autoCreateTables: true);
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException("The ASP.NET Simple Membership database could not be initialized. For more information, please see http://go.microsoft.com/fwlink/?LinkId=256588", ex);
            }
        }
    }
}

Wichtig ist in der Funktion “SimpleMembershipInitializer” der Aufruf für

WebSecurity.InitializeDatabaseConnection(CurrentHttpContext.GetSqlBuilder().ConnectionString, „System.Data.SqlClient“, „MemberSet“, „Id“, „Username“, autoCreateTables: true);” Hier wird unser Connectionstring angegeben, sowie der Name der Tabelle in der die Userdaten gespeichert werden und die Spalte mit der “UserId” sowie mit dem “Benutzernamen”. Diese Funktion muss entsprechend eurer eigenen Modeldaten angepasst werden. Außerdem erstellt, diese Funktion auch die passenden DB Tabellen die der SimpleMembership Provider benötigt.

Aufgrund dieser Informationen, ist der Provider dann in der Lage alle wichtigen Funktionen wie Login, Logout, Register, … uns in einem einfachen Aufruf zur Verfügung zu stellen.

Über die Klasse “WebSecurity” haben wir Zugriff auf alle wichtigen Account Funktionen und über die Klasse “Roles” haben wir Zugriff auf die Rollenverwaltung für die User, die der SimpleMembership Provider ebenfalls enthält.

Die angepasste Global.asax sieht dann folgendermaßen aus:

public class MvcApplication : System.Web.HttpApplication
{
    private static InitializeSimpleMembershipAttribute.SimpleMembershipInitializer _initializer;
    private static object _initializerLock = new object();
    private static bool _isInitialized;

    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        WebApiConfig.Register(GlobalConfiguration.Configuration);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
        AuthConfig.RegisterAuth();

        MemberModelContainer container = new MemberModelContainer(CurrentHttpContext.GetConnectionString("MemberModel"));
        container.Database.CreateIfNotExists();

        LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock);

    }
}

Außerdem kann der folgende Code aus der “AccountModel.cs” entfernt werden, da wir unser eigenes Model und eigenen DB Context verwenden:

public class UsersContext : DbContext
 {
        public UsersContext()
            : base("DefaultConnection")
        {
        }

        public DbSet UserProfiles { get; set; }
  }

    [Table("UserProfile")]
    public class UserProfile
    {
        [Key]
        [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
        public int UserId { get; set; }
        public string UserName { get; set; }
    }

In der “AccountController.cs” muss noch die Funktion “ExternalLoginConfirmation” auf unser Model angepasst werden

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLoginConfirmation(RegisterExternalLoginModel model, string returnUrl)
{
    string provider = null;
    string providerUserId = null;

    if (User.Identity.IsAuthenticated || !OAuthWebSecurity.TryDeserializeProviderUserId(model.ExternalLoginData, out provider, out providerUserId))
    {
        return RedirectToAction("Manage");
    }

    if (ModelState.IsValid)
    {
        // Insert a new user into the database
        using (MemberModelContainer db = CurrentHttpContext.GetDataModel())
        {
            var user = db.MemberSet.FirstOrDefault(u => u.Username.ToLower() == model.UserName.ToLower());
            // Check if user already exists
            if (user == null)
            {
                // Insert name into the profile table
                db.MemberSet.Add(new global::Member.EF.Model.Member() { Username  = model.UserName, Vorname = "", Nachname = ""});
                db.SaveChanges();

                OAuthWebSecurity.CreateOrUpdateAccount(provider, providerUserId, model.UserName);
                OAuthWebSecurity.Login(provider, providerUserId, createPersistentCookie: false);

                return RedirectToLocal(returnUrl);
            }
            else
            {
                ModelState.AddModelError("UserName", "User name already exists. Please enter a different user name.");
            }
        }
    }

    ViewBag.ProviderDisplayName = OAuthWebSecurity.GetOAuthClientData(provider).DisplayName;
    ViewBag.ReturnUrl = returnUrl;
    return View(model);
}

Und die Registerfunktion muss ebenfalls angepasst werden, denn hier muss der Vorname und Nachname mit in der DB eingetragen werden, sonst kommt es zu einem Fehler beim Anlegen des Users.

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Register(RegisterModel model)
{
    if (ModelState.IsValid)
    {
        // Attempt to register the user
        try
        {
            //TODO  RegisterModel und View um Vorname und Nachname erweitern, dann kann dies hier mit ausgfüllt werden.
            WebSecurity.CreateUserAndAccount(model.UserName, model.Password, new { Vorname = "TestVorname", Nachname="TestNachname" });
            WebSecurity.Login(model.UserName, model.Password);
            return RedirectToAction("Index", "Home");
        }
        catch (MembershipCreateUserException e)
        {
            ModelState.AddModelError("", ErrorCodeToString(e.StatusCode));
        }
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

Außerdem kann das Attribut “[InitializeSimpleMembership]” über der AccountController Klasse entfernt werden, denn die Initialisierung des SimpleMembership Providers, findet jetzt in der Global.asax statt.

Wenn wir jetzt unsere Webseite entsprechend ausführen und einen neuen User Registrieren, sieht unsere Datenbank dann folgendermaßen aus:

image

Die Tabelle “dbo.MemberSet” wurde von unserem EF Model angelegt und die restlichen Tabellen vom SimpleMembership Provider.

Dipendency Injection und TDD mit dem SimpleMembership Provider

Da der SimpleMembership Provider nicht über einen Konstruktor initialisiert werden kann, ist es nicht möglich diesen per DI zu initialisieren. Da ich versuche in meinen aktuellen Projekten auf DI zu setzten, überlege ich aktuell ob ich wirklich auf den SimpleMembership Provider setzte oder einfach ein eigenes UserRepository erstelle, wer hier bereits entsprechende Erfahrungen gemacht hat, würde ich mich über einen Kommentar freuen.

Um den SimpleMembership Provider zu testen, habe ich nur den folgenden Artikel gefunden, der angibt wie man den SimpleMembership Provider zumindest “testen” kann.

Auch den folgenden Artikel darüber ob man auf den SimpleMembership Provider setzten sollte fand ich zumindest sehr informativ.

Das fertige Projekt kann wie immer über meinen Codeplex Account angeschaut werden unter:

MVC –> SimpleMembershipProvider