Gerüstbau für ASP.NET Core–Scaffolding

Dieser Blog Artikel setzt auf das Daten Modell Rechnung-Positionen 1:N Relation auf. Das Ziel ist so schnell wie möglich ein Formular für die Eingabe, Auflistung, Editieren und Löschen einer Tabelle aus einem SQL Server zu erstellen.

aluminum-mobile-scaffold-500x500

Visual Studio stellt in der Deutschen Version einen Assistenten zum “Gerüstbau” zur Verfügung. In der englischen Version wird das Feature “Scaffolding” bezeichnet. In jedem Fall wird ein Entity Framework core Model vorausgesetzt.

Stellt sich noch die Frage, wohin mit den Daten. Nach wie vor existiert eine Light Version des SQL Servers, der völlig ohne Konfiguration eine Datenbank dynamisch attachen kann. So lassen Sich Website samt Datenbank per Copy Paste distribuieren. Allerdings, so der Eindruck des Autors, handelt es sich um ein ungeliebtes Kind des Redmonder Software Konzerns. Azure unterstützt das Feature nicht (mehr). ASP.NET core hat diesbezüglich Einschränkungen. 

Der ASP.NET Webforms Entwickler wird noch eine Reihe weiterer Unterschiede feststellen. Der Connection String  wird nicht in der Datei Web.Config sondern in appsettings.json abgelegt. Leider ist auch der automatische Platzhalter DataDirectory nicht ausprogrammiert. Bei Webforms und MVC zeigt der Pfad so auf das App_data Verzeichnis, das vom IIS speziell geschützt wird.

   1:  {
   2:    "ConnectionStrings": {
   3:      "ConnectionString1": "Data Source=(localdb)\\mssqllocaldb;
AttachDbFilename=d:\\corebill\\corebill\\app_data\\rechnung1.mdf;database=rechnung1;Integrated Security=True; MultipleActiveResultSets=True"
,
   4:      "ConnectionString2": "Data Source=(localdb)\\mssqllocaldb;
AttachDbFilename=|DataDirectory|\\rechnung.mdf;database=rechnung1;Integrated Security=True; MultipleActiveResultSets=True"
   5:   
   6:    },

So muss die Variante 1 mit hart codierten Pfad verwendet werden. Wenn man sich die Mühe macht und in den Source Code schaut, erkennt man das ein 10 Zeiler die Aufgaben erledigen würde.

Der nächste Schritt findet in der Datei startup.cs statt. Microsoft setzt hier auf Dependency Injection um SQL Server als Service zu injizieren.

   1:  services.AddDbContext<ModelRechnung>(options =>
   2:  options.UseSqlServer(
   3:   Configuration.GetConnectionString("ConnectionString1"))
   4:  );

Wenn die Datenbank nicht existiert, kann man diese in der Main Methode von program.cs erzeugen lassen. Konkret wird die Methode EnsureCreated aus EF verwendet.

   1:  var context = services.GetRequiredService<ModelRechnung>();
   2:  context.Database.EnsureCreated();

 

Soweit sind die vorarbeiten abgeschlossen. Razor Pages werden unterhalb des Verzeichnisses Pages angelegt. Um diese pro Tabelle zu gruppieren, dient ein weiteres Unterverzeichnis. Auf dem Ordner Rechtsklick zeigt das Kontextmenü –hinzufügen- neues Gerüstelement (Scaffolding)

image

Der Menüpunkt fehlt am 7.11.2018 wenn das asp.net core Projekt die Version 2.2 nutzt.

 

Aus dem folgenden Template wird auf Basis von Entity Framework CRUD ausgewählt.

image

Im Projekt existiert eine Daten Context Klasse Modelrechnung und eine Model Klasse Rechnung. Beide können per Dropdown ausgewählt werden.

Die restlichen Einstellungen werden wie vorbelegt übernommen.

image

Der Visual Studio Assistent erzeugt zehn Dateien. Fünf Razor Views.

image

 

Das Projekt kann in Visual Studio gestartet werden. Als Web Host nutzt Visual Studio iis Express an einem zufälligen Port. In Kombination mit dem Ordner wird im Browser die Liste dargestellt ala https://localhost:44339/demo

image

Für die Liste wird folgender HTML Code generiert

   1:  @page
   2:  @model Core21.Pages.demo.IndexModel
   3:   
   4:  @{
   5:      ViewData["Title"] = "Index";
   6:  }
   7:   
   8:  <h2>Index</h2>
   9:   
  10:  <p>
  11:      <a asp-page="Create">Create New</a>
  12:  </p>
  13:  <table class="table">
  14:      <thead>
  15:          <tr>
  16:              <th>
  17:                  @Html.DisplayNameFor(model => model.Rechnung[0].Date)
  18:              </th>
  19:              <th>
  20:                  @Html.DisplayNameFor(model => model.Rechnung[0].KopfText)
  21:              </th>
  22:              <th>
  23:                  @Html.DisplayNameFor(model => model.Rechnung[0].KundeID)
  24:              </th>
  25:              <th>
  26:                  @Html.DisplayNameFor(model => model.Rechnung[0].Summe)
  27:              </th>
  28:              <th></th>
  29:          </tr>
  30:      </thead>
  31:      <tbody>
  32:  @foreach (var item in Model.Rechnung) {
  33:          <tr>
  34:              <td>
  35:                  @Html.DisplayFor(modelItem => item.Date)
  36:              </td>
  37:              <td>

Es lohnt noch der Blick auf den Code Behind. Es wird eine Instanz des RechnungModel instanziert. In diesem Fall wird wegen HTTP Get in die OnGetAsync Methode geroutet und eine Liste von Rechungsobjekte zurück geliefert.

   1:  public class IndexModel : PageModel
   2:      {
   3:          private readonly CoreBill.Models.ModelRechnung _context;
   4:   
   5:          public IndexModel(CoreBill.Models.ModelRechnung context)
   6:          {
   7:              _context = context;
   8:          }
   9:   
  10:          public IList<Rechnung> Rechnung { get;set; }
  11:   
  12:          public async Task OnGetAsync()
  13:          {
  14:              Rechnung = await _context.Rechnung.ToListAsync();
  15:          }
  16:      

Es bleibt die Frage warum Microsoft in der cshtml Sicht den Html Helper Code nutzt. In der Edit View kommen die neueren Taghelper zum Einsatz. Zeile 18 zeigt den Taghelper im Einsatz. Sieht aus wie ein neues HTML Control mit Funktion und/oder Datenbindung an das Rechnung Model bzw eine konkrete Property wie Date. 

   1:  @page
   2:  @model Core21.Pages.demo.EditModel
   3:   
   4:  @{
   5:      ViewData["Title"] = "Edit";
   6:  }
   7:   
   8:  <h2>Edit</h2>
   9:   
  10:  <h4>Rechnung</h4>
  11:  <hr />
  12:  <div class="row">
  13:      <div class="col-md-4">
  14:          <form method="post">
  15:              <div asp-validation-summary="ModelOnly" class="text-danger"></div>
  16:              <input type="hidden" asp-for="Rechnung.ID" />
  17:              <div class="form-group">
  18:                  <label asp-for="Rechnung.Date" class="control-label"></label>
  19:                  <input asp-for="Rechnung.Date" class="form-control" />
  20:                  <span asp-validation-for="Rechnung.Date" class="text-danger"></span>
  21:              </div>

Der Taghelper rendert zur Laufzeit passend zum Datentyp ein HTML Input type=date.

Die Codebehind Datei hat nun zwei Aufgaben. Beim Aufruf mit Get werden Daten geladen. Bei Post eben gespeichert. ASP.NET liefert über das Attribut Bindpropert auf die Instanz des Rechnung Model eine Art magische codelose Verknüpfung zwischen HTML Formular und Objekt am Server.

   1:    [BindProperty]
   2:          public Rechnung Rechnung { get; set; }
   3:   
   4:          public async Task<IActionResult> OnGetAsync(int? id)
   5:          {
   6:              if (id == null)
   7:              {
   8:                  return NotFound();
   9:              }
  10:   
  11:              Rechnung = await _context.Rechnung.FirstOrDefaultAsync(m => m.ID == id);
  12:   
  13:              if (Rechnung == null)
  14:              {
  15:                  return NotFound();
  16:              }
  17:              return Page();
  18:          }
  19:   
  20:          public async Task<IActionResult> OnPostAsync()
  21:          {
  22:              if (!ModelState.IsValid)
  23:              {
  24:                  return Page();
  25:              }
  26:   
  27:              _context.Attach(Rechnung).State = EntityState.Modified;
  28:   
  29:              try
  30:              {
  31:                  await _context.SaveChangesAsync();
  32:              }
  33:              catch (DbUpdateConcurrencyException)
  34:              {
  35:                  if (!RechnungExists(Rechnung.ID))
  36:                  {
  37:                      return NotFound();
  38:                  }
  39:                  else
  40:                  {
  41:                      throw;
  42:                  }
  43:              }
  44:   
  45:              return RedirectToPage("./Index");
  46:          }
Kommentare sind geschlossen