SQL Practice Blog – SQL Server, BizTalk und .NET Erfahrungen

Archive for the ‘.NET’ Category

SelfHosted ASP.NET Core 2.0 Application

with 2 comments

Auf Grund einer Projektanforderung musste ich mich die Tage damit auseinandersetzen, wie man eine ASP.NET Core 2.0 Website und Web API in einem eigenen Prozess als Windows Service bereitstellt.
Hierbei gibt es einige Besonderheiten zu beachten, auf welche ich in diesem Post gern näher eingehen möchte.

Folgende Technologien kommen zum Einsatz:

  • Alle Projekte basieren auf .NET Core 2.0 oder .NET Standard 2.0
  • Autofac als IoC Container
  • NLog für das Logging
  • Peter Kottas WindowsService als ServerFabric (GitHub)

Grundlegende Architektur der Anwendung

Architecture

Zu finden ist dieses Beispiel auf GitHub.

Grundlegendes Vorgehen

Die Konsolenapplikation kann als WindowsService oder direkt ausgeführt werden und hostet die Website und die API. Die Website und die API sind beides .NETStandard 2.0 Projekte, während die Console eine .NET Core 2.0 Anwendung darstellt.

Aufbau der SelfHosted.Console

IApplication definiert ein Interface zum Starten und Stoppen der Applikationen. Eine Applikation kann eine Website oder eine WebApi sein.

IApplication

Diese Applikationen werden in Autofac registriert und einem gekapselten MicroService geladen und gestartet.

	public class SelfHostedWindowService : MicroService, IMicroService
    {
        private IServiceProvider _provider;

        public void Start()
        {
            this.StartBase();

            var builder = new ContainerBuilder();
            builder.RegisterType<WebApplication>().As<IApplication>();
            builder.RegisterType<WebApiApplication>().As<IApplication>();
            var applicationContainer = builder.Build();

            _provider = new AutofacServiceProvider(applicationContainer);

            foreach (var app in _provider.GetServices<IApplication>())
            {
                app.Start();
            }

            System.Console.WriteLine("Windows services started.");
        }

        public void Stop()
        {
            this.StopBase();

            foreach (var app in _provider.GetServices<IApplication>())
            {
                app.Stop();
            }

            System.Console.WriteLine("Windows services stopped.");
        }
    }

Beim Starten der Konsole oder des Services, wird der MicroService registriert und in einer ServiceFactory geladen. Dadurch starten alle Applikationen, welche in den jeweiligen MicroService definiert sind.

	ServiceRunner<SelfHostedWindowService>.Run(config =>
	{
		var serviceName = "SelfHosted.WindowsService";

		config.SetName(serviceName);
		config.Service(serviceConfig =>
		{
			serviceConfig.ServiceFactory((service, extraArguments) =>
			{
				return new SelfHostedWindowService();
			});

			serviceConfig.OnStart((service, extraArguments) =>
			{
				System.Console.WriteLine("Service {0} started", serviceName);
				service.Start();
			});

			serviceConfig.OnStop((service) =>
			{
				System.Console.WriteLine("Service {0} stopped", serviceName);
				service.Stop();
			});

			serviceConfig.OnError(e =>
			{
				System.Console.WriteLine($"Service '{serviceName}' errored with exception : {e.Message}");
			});
		});
	});

Besonderheiten in der ASP.NET Core 2.0 Website

Es gibt jedoch beim hosten seiner ASP.NET Core 2.0 Website in einer Console noch drei wichtige Dinge zu beachten.

      1. Alle Views müssen Embedded werden. Dafür habe ich folgende Extension Methode geschrieben, welche im Startup bei AddRazorOptions aufgerufen wird.
        	public static RazorViewEngineOptions AddViews(this RazorViewEngineOptions options)
        	{
        		options.FileProviders.Add(new EmbeddedFileProvider(typeof(ServiceCollectionExtensions).GetTypeInfo().Assembly, "SelfHosted.Website"));
        		return options;
        	}
        
      2.  Danach stellte sich heraus, dass noch einige Assemblies fehlten. Diese werden ebenfalls per Extension Methode im Startup geladen.
        	public static RazorViewEngineOptions AddCompilationAssemblies(this RazorViewEngineOptions options)
        	{
        		var myAssemblies = AppDomain
        		   .CurrentDomain
        		   .GetAssemblies()
        		   .Where(x => !x.IsDynamic)
        		   .Concat(new[] { // additional assemblies used in Razor pages:
        					typeof(HtmlString).Assembly, // Microsoft.AspNetCore.Html.Abstractions
        					typeof(IViewLocalizer).Assembly, // Microsoft.AspNetCore.Mvc.Localization
        					typeof(IRequestCultureFeature).Assembly // Microsoft.AspNetCore.Localization
        		   })
        		   .Select(x => MetadataReference.CreateFromFile(x.Location))
        		   .ToArray();
        
        		var previous = options.CompilationCallback;
        
        		options.CompilationCallback = context =>
        		{
        			previous?.Invoke(context);
        			context.Compilation = context.Compilation.AddReferences(myAssemblies);
        		};
        
        		return options;
        	}
        
      3. Jetzt müssen noch alle statischen Files, welche z.B. im wwwroot liegen embedded werden. Auch hier gibt es wieder eine passende Extension Methode.
        	public static IServiceCollection AddStaticFiles(this IServiceCollection collection)
        	{
        		// static files are embedded resources in the "wwwroot" folder
        		collection.Configure<StaticFileOptions>(options =>
        		{
        			options.FileProvider = new EmbeddedFileProvider(typeof(Startup).Assembly, typeof(Startup).Namespace + ".wwwroot");
        		});
        
        		return collection;
        	}
        

Aufgerufen werden die Extension Methods im Startup der Website, in der Funktion ConfigureServices wie folgt:

	public IServiceProvider ConfigureServices(IServiceCollection services)
	{
		services.AddSingleton<IConfiguration>(Configuration);
		services.AddMvc()
			.AddRazorOptions(options =>
			{
				options.AddViews();
				options.AddCompilationAssemblies();
			});
		services.AddStaticFiles();

		var builder = new ContainerBuilder();
		builder.Populate(services);
		this.ApplicationContainer = builder.Build();

		return new AutofacServiceProvider(this.ApplicationContainer);
	}

 

Zusammenfassung

Auf diesem Weg erreicht man eine sehr leichtgewichtige Anwendung, welche komplett in einem eigenen Prozess unabhängig von dem Betriebssystem und Webserver installiert werden kann. Somit erreicht man bei seiner Produktentwicklung, welchen bei Kunden vor Ort installiert werden muss, eine sehr hohe Flexibilität und ich unabhängig der Umgebung.

Advertisements

Written by Robert Meyer

September 22, 2017 at 10:40

NUnit: Testdaten aus einer externen Datenquelle beziehen

leave a comment »

Wer kennt dieses Problem nicht? Ich bin fleißig am Tests schreiben für meine Applikation und bemerkte nach einer Weile das ich ein immer wiederkehrendes Szenario, nur mit unterschiedlichen Werten, testen muss. Hier wäre doch die Möglichkeit externe Quellen mit Testwerten anzuzapfen eine prima Idee.

Ich möchte dies am Beispiel einer einfachen Divisionsfunktion und einer externen CSV Datei mit den Testwerten einmal näher beleuchten. Hierfür nutze ich das Testframework NUnit 2.5.7.

Was für Daten benötige ich für einen Divisionstest? Ich benötige einen Dividend und einen Divisor, desweiteren brauche ich das erwartete Ergebnis. Demzufolge würde unsere CSV wie folgt aussehen:

image

Der grundlegende Lösungsansatz für unser Problem ist die Nutzung des Attribute TestCaseSource von NUnit. TestCaseSource erlaubt es aus einer Property, welche eine IEnumerable zurückgibt Testfälle zu gewinnen.
Dieses Property (statisch) wird zusammen mit den benötigten Properties (Dividend, Divisor und zu erwartendes Ergebnis) in einer Klasse abgebildet. Dieses Property liest in meinem Beispiel eine CSV Datei mit den Testwerten aus und generiert daraus Objekte. Alternativ kann hierfür auch der Zugriff auf eine beliebige andere Datenquelle (Excel, Datenbank, Services …) genutzt werden.

image

Der Test welcher die Testdaten nutzen wird bekommt neben dem Attribute [Test] nun auch noch das Attribute [TestCaseSource] zugewiesen. Bei [TestCaseSource] muss ich den Typ der Testdatenklasse hinterlegen und den Namen des Properties aus der die Tastdaten kommen.
Desweiteren benötige ich einen Übergabeparameter an die Testfunktion vom Typ der Testdaten. Über diesen Übergabeparameter erhalte ich dann meine Testdaten.

image

Es ist natürlich nicht zu empfehlen Dank dieses Attributes nur noch einen Test pro Funktion zu schreiben und damit alle Testfälle abzudecken. Sinnvoll eingesetzt kann das Attribute [TestCaseSource] gerade in Szenarien wo ich gegen viele Werte prüfen muss, mir viel Arbeit abnehmen.

Written by Robert Meyer

August 12, 2010 at 14:14

Veröffentlicht in .NET

Tagged with , , , ,