Dieser Beitrag ist eine kurze allgemeine Anleitung zum Erstellen einer MVC Webapplikation mit folgenden Komponenten:
- Eine SQL-Datenbankanbindung mit dem Entity Framework
- Dependency Injection mit Castle Windsor
- Einer SignalR-API über die Daten mit einem Client ausgetauscht werden können
Weiterhin wird in dieser Anleitung ein minimaler .NET Client für die SingalR-API entwickelt.
Anlegen eines neuen Projekts in Visual Studio 2017
- Wählen Sie File > New > Project im Menü und wählen Sie ASP.NET Web Application (.NET Framework) aus dem Ordner Visual C# / Web als Projekttemplate aus. Geben Sie einen Namen für die Webapplikation ein z.B. „MyWebApplication“ und klicken Sie auf OK.
- Im folgenden Fenster wählen Sie MVC als Projekttyp und klicken Sie OK.
Installieren von Castle Windsor
- Klicken Sie im Solution Explorer mit der rechten Maustaste auf das Projekt und wählen Sie Manage NuGet Packages im erscheinenden PopUp-Menü.
- Suchen Sie im Tab Browse nach dem Paket Castle.Windsor und installieren Sie dieses.
Um während der weiteren Installation überprüfen zu können, ob Castle Windsor funktioniert erstellen wir zunächst eine Beispiel-Klasse sowie ein Interface, welches wir anschließend in Castle Windsor registrieren und verwenden können. Unsere Beispiel-Klasse stellt zwei Methoden zum Addieren und Subtrahieren zweier Zahlen zur Verfügung.
Erstellen Sie zwei neue Dateien namens „ExampleDependency.cs“ und „IExampleDependency.cs“ mit folgendem Inhalt:
IExampleDependency.cs
namespace MyWebApplication
{
public interface IExampleDependency
{
int add(int a, int b);
int subtract(int a, int b);
}
}
ExampleDependency.cs
namespace MyWebApplication
{
public class ExampleDependency :IExampleDependency
{
public int add(int a, int b)
{
return a + b;
}
public int subtract(int a, int b)
{
return a - b;
}
}
}
Als nächstes erstellen wir eine Installer-Klasse für Castle Windsor, in der alle Abhängigkeiten registriert werden. Fügen Sie die folgende neue Datei samt Inhalt zum Projekt hinzu:
WindsorInstaller.cs
using Castle.MicroKernel.Registration;
using Castle.MicroKernel.SubSystems.Configuration;
using Castle.Windsor;
using System.Web.Mvc;
namespace MyWebApplication
{
public class WindsorInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Classes.FromThisAssembly().BasedOn<IController>().LifestyleTransient());
container.Register(Component.For<IExampleDependency>().ImplementedBy<ExampleDependency>());
}
}
}
Die Klasse WindsorInstaller registriert alle Klassen, die von IController erben. Bei einem eingehenden Webrequest kann das zu dessen Verarbeitung erforderliche MVC-Controller-Objekt im Folgenden ebenfalls durch Castle Windsor erzeugt werden. Weiterhin wird unsere Klasse ExampleDependency als Singleton-Klasse registriert. Sie wird also nur bei der ersten Verwendung einmalig instanziiert. Sie können weitere Abhängigkeiten nach dem gleichen Schema hinzufügen.
Damit das Erzeugen der MVC-Controller-Objekte durch Castle Windsor erfolgt, müssen wir noch eine neue Controller-Factory schreiben, welche dazu einen Windsor-Container verwendet. Fügen Sie die dazu folgende neue Datei samt Inhalt zum Projekt hinzu:
WindsorControllerFactory.cs
using Castle.Windsor;
using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace MyWebApplication
{
public class WindsorControllerFactory : DefaultControllerFactory
{
WindsorContainer container;
public WindsorControllerFactory(WindsorContainer container)
{
this.container = container;
}
public override void ReleaseController(IController controller)
{
container.Kernel.ReleaseComponent(controller);
}
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if (controllerType == null)
{
throw new HttpException(404, "The controller for path " + requestContext.HttpContext.Request.Path + " could not be found.");
}
return (IController)container.Kernel.Resolve(controllerType);
}
}
}
Unsere Anwendung soll einen zentralen Windsor-Container zum Erstellen aller Objekte verwenden. In diesem Controller sollen beim Start der Anwendung alle Abhängigkeiten aus unserer Klasse WindsorInstaller registriert werden.
Dazu müssen Sie die Datei Global.asax.cs wie folgt bearbeiten:
- Legen Sie eine neue Klassenvariable namens „container“ vom Typ WindsorContainer an.
- In der Methode Application_Start erstellen Sie einen neuen WindsorContainer und führen unseren WindsorInstaller aus. (Siehe dazu die Kommentare unten im Quelltext)
- Damit die Controller von unserer neuen Controller-Factory erzeugt werden, registrieren Sie diese im ControllerBuilder.
- Legen Sie die Methode Application_End an, sofern diese noch nicht existiert, und rufen Sie darin die Dispose-Methode des Windsor-Containers auf.
Anschließend sieht Ihre Global.asax.cs z.B. wie folgt aus:
using Castle.Windsor;
using Castle.Windsor.Installer;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
namespace MyWebApplication
{
public class MvcApplication : System.Web.HttpApplication
{
public static WindsorContainer container;
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
container = new WindsorContainer(); // Neuen WindsorContainer erstellen
container.Install(FromAssembly.This()); // Unseren WindsorInstaller ausführen
//container.Install(FromAssembly.Named("Library")); // Bei Bedarf: Weitere WindsorInstaller aus z.B. zusätzlichen DLLs ausführen.
ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(container)); // Neue Factory für Controller registrieren
}
protected void Application_End()
{
container.Dispose();
}
}
}
Funktionstest
Testen wir nun, ob die Dependency Injection durch Castle Windsor in Ihrer neuen Applikation funktioniert. Ersetzen Sie den Inhalt der Datei HomeController.cs durch folgenden Quelltext:
using System.Web.Mvc;
namespace MyWebApplication.Controllers
{
public class HomeController : Controller
{
IExampleDependency calculator;
public HomeController(IExampleDependency calculator)
{
this.calculator = calculator;
}
public ActionResult Index()
{
return Content("Addieren zweier Zahlen: 5 + 3 = " + calculator.add(5, 3));
}
}
}
Wenn Sie nun die Webapplikation starten und mit dem Webbrowser aufrufen, sehen Sie folgenden Text im Webbrowser:
Addieren zweier Zahlen: 5 + 3 = 8
Perfekt! Alles funktioniert.
Zum besseren Verständnis erkläre ich noch einmal was genau beim Laden der Webseite passiert ist:
Der Webbrowser sendet eine Http-Anfrage an unsere Applikation. Um diese zu bearbeiten benötigt ASP.NET einen neuen HomeController, welcher durch unsere neue WindsorControllerFactory erstellt werden muss. Unsere WindsorControllerFactory beauftragt den WindsorContainer, welcher beim Start der Applikation angelegt wurde, damit das Objekt zu erstellen. Der WindsorContainer erkennt, dass zum Erstellen eines HomeControllers ein Objekt, welches das Interface IExampleDependency implementiert, vom Konstruktor benötigt wird. Durch den WindsorInstaller haben Sie vorgegeben, dass hierfür ein Objekt der Klasse ExampleDependency verwendet werden soll. Dieses Objekt wird ebenfalls vom WindsorContainer erstellt und der fertige HomeController zurück gegeben, welcher die Http-Anfrage verarbeitet.
Bei einer erneuten Http-Anfrage wird der WindsorContainer kein neues ExampleDependency-Objekt erstellen, da bereits ein Objekt dieser Klasse existiert und diese Klasse mit LifeStyleSingleton() im WindsorInstaller als Singleton-Klasse markiert wurde.
Installieren des Entity Frameworks
Im nächsten Schritt möchten wir in unserer Applikation eine Datenbank verwenden und dafür das Entity Framework benutzen:
- Klicken Sie im Solution Explorer mit der rechten Maustaste auf das Projekt und wählen Sie Manage NuGet Packages im erscheinenden PopUp-Menü.
- Suchen Sie im Tab Browse nach dem Paket EntityFramework und installieren Sie dieses.
Wir werden nun die Anwendung dahingehend erweitern, dass sie Angestellte einer Firma verwalten kann. Dazu soll sie Objekte vom Typ Employee in einer Datenbank speichern und daraus laden können. Fügen Sie die Klasse Employee zu Ihrem Projekt hinzu:
Employee.cs
using System;
using System.ComponentModel.DataAnnotations;
namespace MyWebApplication
{
public class Employee
{
[Key]
public int Id { get; set; }
public string Forename { get; set; }
public string Surname { get; set; }
public string Department {get; set;}
public DateTime Birthday { get; set; }
public decimal Salary { get; set; }
}
}
Erstellen Sie nun eine DbContext-Klasse. Aus dieser Klasse entnimmt das Entity Framework, welche Tabellen in der Datenbank existieren sollen. In unserem Beispiel möchten wir nur eine Tabelle haben, deren Spalten den Properties aus der Klasse Employee entsprechen. Mit dem Attribut [Key] in der Employee-Klasse legen wir fest, dass Id der PrimaryKey in der Datenbanktabelle sein soll. Fügen Sie die folgende neue Datei zum Projekt hinzu:
AppDbContext.cs
using System.Data.Entity;
namespace MyWebApplication
{
public class AppDbContext :DbContext
{
public AppDbContext() : base("Persist Security Info=False;Integrated Security=true;Initial Catalog=MyWebApplication;server=localhost")
{
}
public DbSet<Employee> Employees { get; set; }
}
}
Der ConnectionString, welcher mit base() an den Konstruktor von DbContext übergeben wird, legt die Verbindungseinstellungen mit der Datenbank fest. Damit wir unsere AppDbContext-Objeke von Castle Windsor verwalten lassen können, müssen wir die Klasse zu unserem WindsorInstaller hinzufügen. Fügen Sie dazu die Zeile 8 des folgenden Listings zur Klasse WindsorInstaller hinzu.
public class WindsorInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Classes.FromThisAssembly().BasedOn<IController>().LifestyleTransient());
container.Register(Component.For<IExampleDependency>().ImplementedBy<ExampleDependency>().LifestyleSingleton());
container.Register(Component.For<AppDbContext>().LifestylePerWebRequest());
}
}
Zum Testen ändern Sie die Klasse HomeController wie folgt:
public class HomeController : Controller
{
IExampleDependency calculator;
AppDbContext ctx;
public HomeController(IExampleDependency calculator, AppDbContext ctx)
{
this.calculator = calculator;
this.ctx = ctx;
}
public ActionResult Index()
{
return Content("Addieren zweier Zahlen: 5 + 3 = " + calculator.add(5, 3));
}
public ActionResult AddEmployees()
{
ctx.Employees.Add(new Employee()
{
Forename = "Max",
Surname = "Mustermann",
Department = "Entwicklungsabteilung",
Birthday = new DateTime(1986, 4, 1),
Salary = 65000
});
ctx.Employees.Add(new Employee()
{
Forename = "Bernd",
Surname = "Beispiel",
Department = "Verwaltung",
Birthday = new DateTime(1988, 5, 10),
Salary = 40000
});
ctx.SaveChanges();
return Content("Mitarbeiter wurden gespeichert!");
}
}
}
Wenn Sie nun die Methode AddEmployees im Webbrowser aufrufen, wird Ihre Webapplikation sich mit der Datenbank verbinden, die Tabelle anlegen und zwei neue Mitarbeiter darin speichern. Sie können dies z.B. mit dem Microsoft SQL-Server Management Studio überprüfen.
Installation von SignalR mit Dependency Injection durch Castle Windsor
Unsere Webapplikation soll weiterhin eine SignalR-API bereitstellen, über die Clients mit der Applikation Daten austauschen können.
- Klicken Sie im Solution Explorer mit der rechten Maustaste auf das Projekt und wählen Sie Manage NuGet Packages im erscheinenden PopUp-Menü.
- Suchen Sie im Tab Browse nach dem Paket Microsoft.AspNet.SignalR und installieren Sie dieses.
Bevor wir einen SignalR-Hub zu unserer Applikation hinzufügen, werden wir (analog zu der ControllerFactory für MVC-Controller) auch eine neue Factory für SignalR-Hubs schreiben, damit wir die Hubs ebenfalls durch Castle Windsor erzeugen lassen können. Erstellen Sie dazu eine neue Klasse namens SignalrHubFactory mit folgendem Inhalt:
SignalrHubFactory.cs
using Castle.Windsor;
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.SignalR;
namespace MyWebApplication
{
public class SignalrHubFactory : DefaultDependencyResolver
{
WindsorContainer container;
public SignalrHubFactory(WindsorContainer container)
{
this.container = container;
}
public override object GetService(Type serviceType)
{
return container.Kernel.HasComponent(serviceType) ? container.Resolve(serviceType) : base.GetService(serviceType);
}
public override IEnumerable<object> GetServices(Type serviceType)
{
return container.Kernel.HasComponent(serviceType) ? container.ResolveAll(serviceType).Cast<object>() : base.GetServices(serviceType);
}
}
}
Die SignalrHubFactory bekommt in ihrem Konstruktor eine Referenz auf den WindsorContainer. Sobald ein neuer Hub von ihr erstellt werden soll, versucht sie es zunächst über den WindsorContainer zu beziehen. Falls die gewünschte Klasse dort nicht verfügbar ist, wird sie mit dem DefaultDependencyResolver von SignalR erstellt.
Damit Castle Windsor alle Hubs für uns erstellen kann, müssen die Hub-Klassen noch im WindsorInstaller registriert werden. Fügen Sie dazu die Zeile 6 aus dem folgenden Listing in Ihre Klasse WindsorInstaller ein.
public class WindsorInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Classes.FromThisAssembly().BasedOn<IController>().LifestyleTransient());
container.Register(Classes.FromThisAssembly().BasedOn<Microsoft.AspNet.SignalR.Hub>().LifestyleTransient());
container.Register(Component.For<IExampleDependency>().ImplementedBy<ExampleDependency>().LifestyleSingleton());
container.Register(Component.For<AppDbContext>().LifestylePerWebRequest());
}
}
Nun erstellen Sie eine OWIN-Startup-Klasse, in der Sie die neue SignalrHubFactory als Resolver für SignalR-Hubs festlegen:
Startup.cs
using Microsoft.AspNet.SignalR;
using Microsoft.Owin;
using Owin;
[assembly: OwinStartup(typeof(MyWebApplication.Startup))]
namespace MyWebApplication
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
var factory = new SignalrHubFactory(MvcApplication.container);
var cfg = new HubConfiguration() { Resolver = factory, EnableDetailedErrors = true };
app.MapSignalR(cfg);
}
}
}
Abschließend erstellen wir noch als Beispiel einen SignalR-Hub, welcher zwei Funktionen zum addieren und subtrahieren zweier Zahlen bereitstellt. Der Hub berechnet die Summen bzw. Differenzen nicht selbst, sondern greift dazu auf unsere ExampleDependency zurück, welche von Castle Windsor über den Konstruktor des Hubs bereit gestellt wird. Erstellen Sie eine neue Klasse namens ExampleHub.cs und fügen Sie den folgenden Inhalt darin ein:
ExampleHub.cs
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
namespace MyWebApplication
{
[HubName("ExampleHub")]
public class ExampleHub : Hub
{
IExampleDependency calculator;
public ExampleHub(IExampleDependency calculator)
{
this.calculator = calculator;
}
public int Add(int a, int b)
{
return calculator.add(a, b);
}
public int Subtract(int a, int b)
{
return calculator.subtract(a, b);
}
}
}
Programmieren eines SignalR-Clients
Zum Testen unserer SignalR-Schnittstelle, werden wir eine einfache Client-Anwendung programmieren. Dazu erstellen wir eine neue Applikation für die Konsole, welche sich beim Start mit der API verbindet und mit ihrer Hilfe zwei Zahlen subtrahiert.
- Wählen Sie File > New > Project im Menü und wählen Sie Console App (.NET Framework) aus dem Ordner Visual C# als Projekttemplate aus. Geben Sie einen Namen für die Webapplikation ein z.B. „MyClient“ ein.
- Wählen Sie unten unter Solution „Add to Solution“ aus und klicken Sie auf OK.
- Klicken Sie im Solution Explorer mit der rechten Maustaste auf das Projekt und wählen Sie Manage NuGet Packages im erscheinenden PopUp-Menü.
- Suchen Sie im Tab Browse nach dem Paket Microsoft.AspNet.SignalR.Client und installieren Sie dieses.
Öffnen Sie die Datei Program.cs und fügen Sie folgenden Inhalt ein:
using Microsoft.AspNet.SignalR.Client;
using System;
namespace MyClient
{
class Program
{
static void Main(string[] args)
{
using (HubConnection hubConnection = new HubConnection("http://localhost:57256/"))
{
IHubProxy proxy = hubConnection.CreateHubProxy("ExampleHub");
hubConnection.Start().Wait();
int d = proxy.Invoke<int>("Subtract", 10, 3).Result;
Console.WriteLine("Subtrahieren zweier Zahlen: 10 - 3 = " + d);
}
Console.ReadLine();
}
}
}
Vergessen Sie nicht die Portnummer in Zeile 10 durch die Portnummer, auf der Ihre Webapplikation läuft, zu ersetzen.
Klicken Sie nun mit der rechten Maustaste im Solution Explorer auf Ihre Solution und wählen Sie im PopUp-Menü Set Startup Projects. Wählen Sie Multiple Startup Projects und stellen Sie beide Projekte (die Webapplikation und den Client) auf Start.
Wenn Sie nun Ihre Solution starten, werden beide Projekte ausgeführt. Die Webapplikation erscheint wie gehabt im Browserfenster. Der Client erscheint als Konsolenfenster und zeigt folgenden Text an:
Subtrahieren zweier Zahlen: 10 – 3 = 7
Im Folgenden soll der Ablauf noch einmal erklärt werden: Beim Start erstellt der Client einen Proxy, über den wir auf die Methoden des ExampleHubs zugreifen können. Durch den Aufruf von proxy.Invoke() rufen wir eine Methode auf dem Server auf (im Beispiel die Methode Subtract). Dazu wird zunächst eine Instanz des ExampleHubs benötigt, welche von unserer SignalrHubFactory mit dem WindsorContainer erstellt wird. Castle Windsor erkennt, dass zum erstellen eines ExampleHubs eine ExampleDependency benötigt wird, und stellt auch diese automatisch bereit.
Den volständigen Beispiel-Quellcode können Sie hier herunterladen: MyWebApplication.zip