2022
ASP.NET Grundlagen

ASP.NET Grundlagen

Datum: August 2022
Lesedauer: 12 Minuten


In diesem Blogpost wird beschrieben, was ASP.NET ist und welche Projekte damit erstellt werden können.

.NET

NET ist eine Entwicklungsplattform von Microsoft, um Apps zu bauen. Das Framework ist gratis, cross-platform, open-source, objektbasiert und zählt heute zu den effizientesten.

Eckdaten

Erstveröffentlichungsdatum: Ende des Jahres 2000 Entwickler: Microsoft Aktuelle Version: 6.0.3 Programmiersprachen: C#, F#, Visual Basic, C++

ASP.NET

ASP.NET (Active Server Pages Network Enabled Technologies) ist ein Web Framework zur Erstellung von dynamischen HTML, CSS und JS Webapps, Applikationen und Services. Dieses gibt es seit dem Jahre 2002.

Zu den Vorteilen von ASP.NET zählen:

  • Reduzierte Entwicklungszeit
  • Out of the box Features
  • Einfachheit
  • Sicherheit
  • Anpassungsfähigkeit
  • ...

ASP.NET Applikationen haben jeweils zwei Files.
Das Program.cs konfiguriert, baut und startet die Applikation.
Im appsettings.json befindet sich die Konfiguration. In dieser Datei sind die erlaubten Hosts, Logging Informationen und auch Connection Strings festgehalten. Diese Daten werden im Program.cs verwendet, können aber auch von Models benutzt werden.

Learning

Wenn Fragen zu einem bestimmten Punkt bezüglich ASP.NET aufkommen, kann man die jeweils relativ schnell klären.
Für Anfänger sind zudem die Microsoft Tutorials in Videoformat zu empfehlen: https://dotnet.microsoft.com/en-us/learn/aspnet (opens in a new tab)

Modelle

Beim Erstellen eines Projektes in VS stösst man auf einige Möglichkeiten. In diesem Abschnitt gehe ich auf die einzelnen Modelle ein.

modelle

Web Forms

Dokumentation: https://docs.microsoft.com/en-us/aspnet/web-forms/ (opens in a new tab)

Was ist Web Forms?

Web Forms Applikationen werden über den Browser aufgerufen. Die Seiten werden mit HTML, client-scripts, server controls und server code geschrieben. Beim Aufruf wird alles kompiliert und vom Framework ausgeführt.
Eine solche Webseite besteht jeweils aus einem File für Code und Markup. In VS ist die Web Forms Entwicklung äusserst benutzerfreundlich. Dies gilt als der grösste Vorteil. Man kann Elemente hineinziehen und Seiten über einen Editor bearbeiten. Zudem ist es möglich, auf eine einfache Art und Weise, Properties, Methoden und Events zu setzen.
Heutzutage wird Web Forms aber kaum noch eingesetzt. Die Vorgehensweise ist nicht mehr modern und auch die Tutorials sind längst veraltet.

what

Installation

Beim Erstellen eines neuen Projektes muss man folgende Vorlage auswählen.

installation

In einem späteren Schritt kann man sich dann für Web Forms entscheiden.

installation-two

Es kann durchaus sein, dass Web Forms hier nicht angezeigt wird. Dann muss man vorerst unter ASP. NET web development zwei Dinge installieren:

  • .NET Framework project and item templates
  • Additional project templates

details

MVC

Dokumentation: https://docs.microsoft.com/en-us/aspnet/core/mvc/overview?view=aspnetcore-6.0 (opens in a new tab) Tutorial: https://docs.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/start-mvc?view=aspnetcore-6.0&tabs=visual-studio (opens in a new tab)

Was ist MVC?

MVC ist ein weit verbreitetes Konzept. Dabei trennt man in Models, Views und Controllers.
Der grosse Vorteil ist, dass man mit MVC GUIs und APIs in einem Projekt erstellen kann. Zudem ist es der moderne Projekttyp, welcher schon am längsten existiert. Aus diesen Gründen gibt es auch sehr viele Projekte, die diesen Ansatz verfolgen.

mvc

Installation

mvc-installation

Beispiel

In diesem wird demonstriert, was es braucht, um eine Seite anzuzeigen.

Oft wird vergessen, dass das MVC nicht nur aus drei Teilen besteht. Denn ein Request muss zuerst auf einen Controller geroutet werden.
In ASP.NET findet man die Routing im Program.cs.
Dieses File beschreibt, wie die URL beim Aufruf der Seite auszusehen hat.

Program.cs
app.MapControllerRoute(
	name: "default",
	pattern: "{controller=Home}/{action=Index}/{id?}");

Wenn der Request jetzt mit der URL /Home/Index/2 abgesetzt wird. Dann wird im HomeController die Index Methode mit dem Parameter 2 durchgeführt.

Hier geht man davon aus, dass sowas wie /Welcome/kay/3 aufgerufen wird. Wichtig ist auch, dass die Route dementsprechend angepasst ist.

Controllers/HelloWorldController.cs
using Microsoft.AspNetCore.Mvc;
 
namespace MvcMovie.Controllers
{
	public class HelloWorldController : Controller
	{
		public IActionResult Index()
		{
			return View();
		}
 
		public IActionResult Welcome(string name, int numTimes = 1)
		{
			ViewData["Message"] = "Hello " + name;
			ViewData["NumTimes"] = numTimes;
			return View();
		}
	}
}

Nachdem der Code in der Welcome Methode durchlaufen wurde und ViewData abgefüllt ist, wird die View Methode ausgeführt. Diese zeigt diese View an, welche denselben Namen trägt.

Views/HelloWorld/Welcome.cshtml
@{
	ViewData["Title"] = "Welcome";
}
 
<h2>Welcome</h2>
 
<ul>
	@for (int i = 0; i < (int)ViewData["NumTimes"]!; i++)
	{
		<li>@ViewData["Message"]</li>
	}
</ul>

In der View wird neben dem HTML vom @ gebrauch gemacht. Durch dieses kann C# Code geschrieben werden.
In diesem Fall wird über einen Loop der Name eine bestimmte Anzahl Male ausgegeben.

Model

In MVC gibt es einige nützliche Dinge, die man tun kann, welche mit dem Model zu tun haben.

Entity Framework

Arbeitet man mit Models, ergibt es auch Sinn, vom EF gebrauch zu machen.
Durch das EF kann man die Datenbank aus dem Code heraus oder Code auf der Grundlage der Datenbank generieren. Dabei unterscheidet man zwischen Model First und DB First. Da ich in meinem aktuellen Projekt von der Model First Variante Gebrauch mache, erläutere ich diese hier.

Beispiel: https://docs.microsoft.com/en-us/ef/core/get-started/overview/first-app?tabs=netcore-cli (opens in a new tab)

Vorerst habe ich die entsprechenden Models erstellt. Danach konnte ich über die Package Manger Console eine Migration erstellen und diese ausführen.

Package Manger Console
Add-Migration InitialCreate
Update-Database

Zeile 1: Generiert ein Migrations/{timestamp}_InitialCreate.cs File. Zeile 2: Verwendet die generierte Migration um die Datenbank zu aktualisieren.

Context

Beim Vorgang wurde eine Context Klasse erstellt. Diese enthält die DbSet<Nistbox.Models.Nistbox> Properties, welches die Daten aus der Datenbank repräsentieren.

Das Projekt handelt von Nistkästen (Nestboxes).

Data/NestboxContext.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Nestbox.Models;
 
namespace Nestbox.Data
{
	public class NestboxContext : DbContext
	{
		public NestboxContext (DbContextOptions<NestboxContext> options)
			: base(options)
		{
		}
 
		public DbSet<Nestbox.Models.Coordinates>? Coordinates { get; set; }
		public DbSet<Nestbox.Models.Log>? Log { get; set; }
		public DbSet<Nestbox.Models.Nestbox>? Nestbox { get; set; }
	}
}
Dependency Injection

In den Controllern kann der NestboxContext durch die DI verwendet werden.

NestboxesControllers.cs
private readonly NistboxContext _context;
 
public NistboxesController(NistboxContext context)
{
	_context = context;
}

Um dies zu ermöglichen, wurde im Program.cs der folgende Code hinzugefügt.

Program.cs
builder.Services.AddDbContext<NistboxContext>(options =>
	options.UseSqlServer(
		builder.Configuration.GetConnectionString("NistboxContext") ??
		throw new InvalidOperationException("Connection string 'NistboxContext' not found.")
	)
);
Connection String

Um eine Verbindung zur DB aufzubauen, wird ein Connection String benötigt.

appsettings.json
"ConnectionStrings": {
	"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-Nestbox-CONNECTION-KEY;
	Trusted_Connection=True;MultipleActiveResultSets=true",
	"NistboxContext": "Server=(localdb)\\mssqllocaldb;Database=Nestbox.Data;Trusted_Connection=True;
	MultipleActiveResultSets=true"
},
Initial Create

Durch dieses File werden die Daten in der DB erstellt.

20220714140140_InitialCreate.cs
// ...
protected override void Up(MigrationBuilder migrationBuilder)
{
	migrationBuilder.AlterColumn<float>(
		name: "Longitude",
		table: "Coordinates",
		type: "real",
		nullable: false,
		oldClrType: typeof(int),
		oldType: "int");
 
	migrationBuilder.AlterColumn<float>(
		name: "Latitude",
		table: "Coordinates",
		type: "real",
		nullable: false,
		oldClrType: typeof(int),
		oldType: "int");
}
// ...
Alternativen

Wie bereits beschrieben, gibt es auch den DB-First Ansatz. Dabei erstellt man die Tabellen auf der Datenbank und generiert anschliessen den Code daraus.
Mehr dazu erfährt man hier: https://docs.microsoft.com/en-us/ef/ef6/modeling/designer/workflows/database-first (opens in a new tab)

View

C# Code

Wie schon oben beschrieben, kann man in den Views C# Code schreiben. Deshalb werden einem einige Türen geöffnet. In der Dokumentation wird noch mehr dazu beschrieben: https://docs.microsoft.com/en-us/aspnet/mvc/overview/older-versions-1/views/ (opens in a new tab)

Partial Views

Auch ein Vorteil von MVC ist, dass es saubere Views gibt, welche die Daten nur noch darstellen müssen. Jedoch können solche Views auch sehr gross und unübersichtlich werden.
Aus diesem Grund kann man Teile einer View auslagern. Dies wird auch schon standardmässig getan.
Um dies nachzuvollziehen, kann man die Layout-Datei betrachten. Darin befindet sich nämlich @RenderBody(), welche den Inhalt der Seiten in das Layout hineinfügt.

Shared/_Layout.cshtml
<div class="container">
	<main role="main" class="pb-3">
		@RenderBody()
	</main>
</div>

Solche Partial Views sind an den Bodenstrichen zu erkennen.
Man kann auch selbst solche Views erstellen und einbinden.

partial

Shared/_Layout.cshtml
@Html.Partial("_NavBar")

Controller

In Controllers kann man viel anstellen. Wichtig ist aber auch, wie man mit der View kommuniziert. Dies wird hier beschrieben.

Passing Data into Views

Wenn die Logik in den Controllern geschieht und in den Views angezeigt werden muss, kommt die Frage auf, wie man die Daten in die Views hinein bekommt.
Dafür gibt es verschiedene Möglichkeiten.

MoviesController.cs
ViewData["Movie"] = movie;
return View();
Random.cs
<h2>@( ((Movie) ViewData["Movie"]).Name )</h2>
View Method

Später konnte man dann den Wert direkt über die View Methode übergeben.

MoviesController.cs
return View(movie);
Random.cs
<h2>@Model.Name</h2>
ViewBag

Das Problem mit den beiden vorherigen Varianten ist, dass man nicht leicht mehrere Daten übergeben kann. Aus diesem Grund gibt es ViewBag.

MoviesController.cs
ViewBag.Title= "Hello World";
return View();
Random.cs
<h2>@ViewBag.Title</h2>
Action Results

View ist nur eine der Helper Methoden. Dies sind ein paar weitere.

action

MoviesController.cs
namespace Vidly.Controllers
{
	public class MoviesController : Controller
	{
		// GET: Movies
		public ActionResult Random()
		{
			var movie = new Movie() { Name = "Shrek" };
			return View(movie);
			//return Content("Hello Wolrd");
			//return HttpNotFound();
			//return new EmptyResult();
			//return RedirectToAction("Index", "Home", new { page = 1, sortBy = "name" });
		}
	}
}

Blazor

Dokumentation: https://docs.microsoft.com/en-us/aspnet/core/blazor/?WT.mc_id=dotnet-35129-website&view=aspnetcore-6.0 (opens in a new tab)
Tutorial: https://dotnet.microsoft.com/en-us/learn/aspnet/blazor-tutorial/intro (opens in a new tab)
Webseite: https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor (opens in a new tab)

Was ist Blazor?

Bei Blazor unterscheidet man zwischen zwei Projekttypen.

Zum einen gibt es Blazor Server. Blazor Server ist äusserst einzigartig. Es kombiniert serverseitiges und clientseitiges Rendering. So kann es die Seiten sehr schnell zusammenstellen, bleibt aber trotzdem sehr interaktiv. Der User erhält also die Webseite schnell und kann zudem, wie beispielsweise in React oder Angular, Elemente hinzufügen. Als frontendseitige Sprache wird C# anstelle von JS eingesetzt. Das Markup und der Code wird in Razor Files geschrieben.
Durch diese Kombination von serverseitigem und cleintsetigem Rendering gilt Blazor Server als eines der schnellsten Projekte.

Es gibt aber auch noch Blazor WebAssembly. Solche Projekte werden nur clientseitig geladen. Zudem sind WebAssembly Applikation schlecht geschützt. Man darf also keine Keys verwenden, keine direkten Datenbankverbindungen aufbauen und nicht einmal das appsettings File ist erwünscht. Will man mit Daten arbeiten, so ist eine zusätzliche API notwendig.
Neben diesen Nachteilen gibt es aber auch einen wichtigen Grund, es zu verwenden. Solche Projekte können, nach einem Download von ca. 10 Megabyte, offline verwendet werden. Für den User sieht es danach wie eine Desktop-Applikation aus.

Installation

Bei der Blazor installation ist es wichtig, dass man das gewünschte Projekt wählt.

blazor

blazor-web

Beispiel

In diesem Fall zeige ich, wie mächtig Blazor Server ist.

Dieses Razor File reicht aus, um einen voll funktionsfähigen Counter zu bauen.

Counter.razor
@page "/counter"
 
<PageTitle>Counter</PageTitle>
 
<h1>Counter</h1>
 
<p role="status">Current count: @currentCount</p>
 
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
 
@code {
	private int currentCount = 0;
	private void IncrementCount()
	{
		currentCount++;
	}
}

Razor Pages

Tutorial: https://docs.microsoft.com/en-us/aspnet/core/tutorials/razor-pages/?view=aspnetcore-6.0 (opens in a new tab) Dokumentation: https://docs.microsoft.com/en-us/aspnet/core/razor-pages/?view=aspnetcore-6.0&tabs=visual-studio (opens in a new tab)

Was ist Razor Pages?

Beim MVC Modell musste man sich jeweils um einiges an verschiedenen Orten kümmern. Mit Razor Pages sollte man Seiten produktiver erstellen können. Razor Pages ist laut Microsoft das neuere und einfachere Modell wie MVC. Jedes File stellt einen Endpoint dar. Und das HTML kann elegant mithilfe von C# ausgegeben werden.
Gerade für statische Anwendungen ist dieser Projekttyp empfehlenswert. Denn das serverseitige Rendering ist äusserst schnell.

Installation

Hier kann man nach der ASP.NET Core Web App Installation bereits von den Razor Pages Features Gebrauch machen.

razor

Beispiel

Wie man an diesem simplen Beispiel erkennen kann, besteht eine Seite aus zwei Files.

Das cshtml File stellt die Seite dar.

Page/Index.cshtml
@page
@model IndexModel
@{
	ViewData["Title"] = "Home page";
}
 
<div class="text-center">
	<h1 class="display-4">Welcome</h1>
	<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

Im cshtml.cs File werden die Daten bereitgestellt.

Page/Index.cshtml.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace RazorPagesMovie.Pages
{
	public class IndexModel : PageModel
	{
		private readonly ILogger<IndexModel> _logger;
 
		public IndexModel(ILogger<IndexModel> logger)
		{
			_logger = logger;
		}
 
		public void OnGet()
		{
		}
	}
}

In diesem weiteren, etwas grösserem, Beispiel erkennt man gut, wie die zwei Files zusammenspielen.

razor-beispiel

Im verschachteltem cshtml.cs File werden die Daten behandelt und an die Seite geliefert.

Edit.cshtml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Data;
using RazorPagesMovie.Models;
namespace RazorPagesMovie.Pages.Movies
{
	public class EditModel : PageModel
	{
		private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;
		public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
		{
			_context = context;
		}
 
		[BindProperty]
		public Movie Movie { get; set; } = default!;
 
		public async Task<IActionResult> OnGetAsync(int? id)
		{
			if (id == null || _context.Movie == null)
			{
				return NotFound();
			}
			var movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);
			if (movie == null)
			{
				return NotFound();
			}
			Movie = movie;
			return Page();
		}
 
		// To protect from overposting attacks, enable the specific properties you want to bind to.
		// For more details, see https://aka.ms/RazorPagesCRUD.
		public async Task<IActionResult> OnPostAsync()
		{
			if (!ModelState.IsValid)
			{
			return Page();
			}
 
			_context.Attach(Movie).State = EntityState.Modified;
 
			try
			{
				await _context.SaveChangesAsync();
			}
			catch (DbUpdateConcurrencyException)
			{
				if (!MovieExists(Movie.ID))
				{
					return NotFound();
				}
				else
				{
					throw;
				}
			}
 
			return RedirectToPage("./Index");
		}
 
		private bool MovieExists(int id)
		{
			return (_context.Movie?.Any(e => e.ID == id)).GetValueOrDefault();
		}
	}
}

In den cshtml Dateien befindet sich unter anderem gewöhnliches HTML. Jedoch kann man hier die Daten aus dem Controller nutzen.
Hier sieht man auch wieder, dass sich die verschiedenen Konzepte nicht in allen Punkten voneinander unterscheiden.

Edit.cshtml
@page
@model RazorPagesMovie.Pages.Movies.EditModel
 
@{
	ViewData["Title"] = "Edit";
}
 
<h1>Edit</h1>
 
<h4>Movie</h4>
<hr />
<div class="row">
	<div class="col-md-4">
		<form method="post">
			<div asp-validation-summary="ModelOnly" class="text-danger"></div>
			<input type="hidden" asp-for="Movie.ID" />
			<div class="form-group">
				<label asp-for="Movie.Title" class="control-label"></label>
				<input asp-for="Movie.Title" class="form-control" />
				<span asp-validation-for="Movie.Title" class="text-danger"></span>
			</div>
			<div class="form-group">
				<label asp-for="Movie.ReleaseDate" class="control-label"></label>
				<input asp-for="Movie.ReleaseDate" class="form-control" />
				<span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
			</div>
			<div class="form-group">
				<label asp-for="Movie.Genre" class="control-label"></label>
				<input asp-for="Movie.Genre" class="form-control" />
				<span asp-validation-for="Movie.Genre" class="text-danger"></span>
			</div>
			<div class="form-group">
				<label asp-for="Movie.Price" class="control-label"></label>
				<input asp-for="Movie.Price" class="form-control" />
				<span asp-validation-for="Movie.Price" class="text-danger"></span>
			</div>
			<div class="form-group">
				<input type="submit" value="Save" class="btn btn-primary" />
			</div>
		</form>
	</div>
</div>
 
<div>
	<a asp-page="./Index">Back to List</a>
</div>
 
@section Scripts {
	@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

API

An dieser Stelle auch noch erwähnenswert, ist das API-Modell.

Bei solchen Projekten hat man keine GUI. Man erstellt eine Anwendung, welche auf Anfragen ein Json (oder sonstige Daten) zurückgibt. So sind die Daten auch gut geschützt.
Dabei ist der Zweck, dass eine andere Applikation, wie z. B. ein React Projekt, von der API Gebrauch macht.

Ein solches Projekt sollte man nicht erstellen, wenn man in kurzer Zeit eine Applikation erstellen will. Denn damit ein User die App bedienen kann, muss diese zuerst von einem Frontend verwendet werden.
(Ausser man will, dass der User die Daten im Request Body mitschickt. Dies ist aber nicht für den DAU (opens in a new tab) geeignet.)

Fazit

In meinem aktuellen Projekt arbeite ich mit dem MVC Modell. Bisher habe ich damit nur positive Erfahrungen gemacht. MVC ermöglicht, meiner Meinung nach, eine effiziente Arbeit. Zudem bleibt, durch die strikte Trennung, auch alles sauber und übersichtlich.
Ich habe aber auch die anderen Modelle ausprobiert. Auch an denen kann ich bisher nichts aussetzen.

Man kann auch nicht behaupten, dass eines der Modelle besser ist als die anderen. Denn je nach Projekt gibt es eine Variante, welche am sinnvollsten ist. Deshalb ist es hilfreich, wenn man die Konzepte kennt und unterscheiden kann.
Anmerkung: Man kann Projekte bei Bedarf auch kombinieren.

Hier noch das Wichtigste im Überblick.

ProjekttypVorteileNachteile
Web FormsDrag and DropNicht mehr modern, veraltete Tutorials, sollte nicht mehr eingesetzt werden
MVCHohe Geschwindigkeit, GUI und API in einem Projekt möglich, bekanntes Konzept, weit verbreitetSchon etwas älter, Blazor Server als moderne Alternative
Blazor ServerBeste Geschwindigkeit, serverseitiges und clientseitiges Rendering, hohe Interaktivität für den NutzerNicht optimal für statische Seiten
Blazor WebAssemblyOffline Access nach Download (ca. 10 MB)Nur clientseitig, Sicherheitsrisiken, erfordert API-Nutzung
Razor PagesHohe Geschwindigkeit, schnelle Erstellung von Seiten, beste Wahl für statische InhalteServerseitig gebunden
APISehr schnell, einfach umzusetzen, hohe SicherheitKeine GUI, muss von anderen Applikationen genutzt werden, nicht direkt mit den anderen Projekttypen vergleichbar