Online Banking mit ASP.NET Core

Meine zwei Lieblings Features an aspnetcore sind Razor Taghelper und die Page Handler Methoden. Letztere erinnern ein wenig an die ASP.NET Webforms Webmethods, nur noch einfacher. In diesem Blog Beitrag wird eine XML Datei im ISO 20022 Format erzeugt, die jede gängige Banking Anwendung einlesen kann. Also Stapelüberweisung ohne HBCI. Als Dummy UI kommt eine Razor Page View zum Einsatz ala

image

Die Überweisungsdaten werden in einer Liste von Objekten gehalten und dieses wieder über ein abstraktes Viewmodel. Alles in C# Klassen.

   1:  public class Beleg
   2:  {
   3:    public string ID { get; set; }
   4:    public string Firma { get; set; }
   5:    public string Sender { get; set; }
   6:    public string Besteller { get; set; }
   7:    public string Betrag { get; set; }
   8:    public string VWZ { get; set; }
   9:    public string IBAN { get; set; }
  10:    public string BIC { get; set; }
  11:    public string Inhaber { get; set; }
  12:    public string Eingang { get; set; }
  13:    public  bool Job { get; set; }
  14:  }
  15:   
  16:  ..
  17:  public class BankingViewModel
  18:  {
  19:   public List<Beleg> ListeBelege { get; set; }
  20:   private double _summe;
  21:   public double Summe
  22:   {
  23:   get
  24:   {
  25:   _summe = ListeBelege.Sum(x => Double.Parse(x.Betrag, CultureInfo.InvariantCulture));
  26:     return _summe;
  27:   }
  28:   set
  29:   {
  30:    _summe = value;
  31:   }
  32:     

Im Codebehind der Razor View wird das BankingViewModel als Property angelegt. Im HTML deklariert man den Link Button mit Hilfe von Bootstrap CSS Klassen. Der Taghelper generiert einen passenden Link (?handler=xml) im Hyperlink Element. Die Anzeige der Überweisungen als Table in der Sicht dient nur zur Demonstration.

   1:  <a asp-page-handler="XML" class="btn btn-outline-primary">export xml</a>
   2:  <table class="table table-striped">
   3:      @foreach (var item in Model.MyVM.ListeBelege)
   4:      {
   5:          <tr>
   6:              <td>@item.ID</td>
   7:              <td>@item.Inhaber</td>
   8:              <td>@item.Betrag</td>
   9:          </tr>
  10:     }
  11:  </table>

Das besondere ist an diesem Razor Beispiel, die Modelklasse ist das ViewModel und nicht das PageModel. Entsprechen in der View per @model auf BankingViewmodel referenziert. Im Codebhind wird im Konstrukor eine Instanz erstellt und mit Dummy Daten gefüllt.

   1:  public BankingViewModel MyVM { get; set; } = new BankingViewModel();
   2:  public BankingModel()
   3:   {
   4:    MyVM.ListeBelege = new List<Beleg>();
   5:    MyVM.ListeBelege.Add(new Beleg() { ID = "1", Betrag = "100.20", BIC = "Bic", IBAN = "iban", Inhaber = "Kontoinhaber", VWZ = "Verwendungszweck" });
   6:    MyVM.ListeBelege.Add(new Beleg() { ID = "2", Betrag = "2000", BIC = "Bic", IBAN = "iban", Inhaber = "Kontoinhaber", VWZ = "Verwendungszweck" });
   7:    MyVM.ListeBelege.Add(new Beleg() { ID = "3", Betrag = "99.20", BIC = "Bic", IBAN = "iban", Inhaber = "Kontoinhaber", VWZ = "Verwendungszweck" });
   8:    MyVM.ListeBelege.Add(new Beleg() { ID = "4", Betrag = "1.99", BIC = "Bic", IBAN = "iban", Inhaber = "Kontoinhaber", VWZ = "Verwendungszweck" });
   9:    }
  10:       

Um die XML Daten Roh zu erzeugen, kommt ein Partial View zum Einsatz. Praktisch eine Razor Page ohne das HTML außen rum und ohne _Layout.cshtml anzuwenden. Meistens beginnen diese mit einem _ im Namen. Wichtig ist das Model und das @Page zu entfernen. Auf die XML Struktur des 20022 Pain 3 Schemas gehe ich nicht ein. Im Kern sind es Kopfdaten und pro Überweisung ein XML Node. Um diesen zu erzeugen einfach per @foreach iterieren durch die Überweisungsdaten.

   1:   
   2:  @model BankingViewModel
   3:  @using System.Globalization
   4:  <?xml version="1.0" encoding="UTF-8" ?>
   5:  <Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.003.03"
   6:            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   7:            xsi:schemaLocation="urn:iso:std:iso:20022:tech:xsd:pain.001.003.03 pain.001.003.03.xsd">
   8:      <CstmrCdtTrfInitn>
   9:          <GrpHdr>
  10:              <MsgId>@Guid.NewGuid().ToString().Substring(0, 35)</MsgId>
  11:              <CreDtTm>@DateTime.Now.ToString("yyyy-MM-ddThh:mm:ssZ") </CreDtTm>
  12:              <NbOfTxs>@Model.ListeBelege.Count()</NbOfTxs>
  13:              <CtrlSum>@Model.Summe.ToString("0.00", CultureInfo.InvariantCulture)</CtrlSum>
  14:              <InitgPty>
  15:                  <Nm>ppedv AG</Nm>
  16:              </InitgPty>
  17:          </GrpHdr>
  18:          <PmtInf>
  19:              <PmtInfId>@Guid.NewGuid().ToString().Substring(0, 35)</PmtInfId>
  20:              <PmtMtd>TRF</PmtMtd>
  21:              <BtchBookg>true</BtchBookg>
  22:              <NbOfTxs>@Model.ListeBelege.Count()</NbOfTxs>
  23:              <CtrlSum>@Model.Summe.ToString("0.00", CultureInfo.InvariantCulture) </CtrlSum>
  24:              <PmtTpInf>
  25:                  <SvcLvl>
  26:                      <Cd>SEPA</Cd>
  27:                  </SvcLvl>
  28:              </PmtTpInf>
  29:              <ReqdExctnDt>@DateTime.Now.ToString("yyyy-MM-ddThh:mm:ssZ")</ReqdExctnDt>
  30:              <Dbtr>
  31:                  <Nm>ppedv AG</Nm>
  32:              </Dbtr>
  33:              <DbtrAcct>
  34:                  <Id>
  35:                      <IBAN>AT</IBAN>
  36:                  </Id>
  37:              </DbtrAcct>
  38:              <DbtrAgt>
  39:                  <FinInstnId>
  40:                      <BIC>GENODEF1BGL</BIC>
  41:                  </FinInstnId>
  42:              </DbtrAgt>
  43:              <ChrgBr>SLEV</ChrgBr>
  44:              @foreach (var item in Model.ListeBelege)
  45:              {
  46:   
  47:                   <CdtTrfTxInf>
  48:                  <PmtId>
  49:                      <EndToEndId>NOTPROVIDED</EndToEndId>
  50:                  </PmtId>
  51:                  <Amt>
  52:                      <InstdAmt Ccy="EUR">@item.Betrag </InstdAmt>
  53:                  </Amt>
  54:                  <CdtrAgt>
  55:                      <FinInstnId>
  56:                          <BIC>@item.BIC</BIC>
  57:                      </FinInstnId>
  58:                  </CdtrAgt>
  59:                  <Cdtr>
  60:                      <Nm>@item.Inhaber</Nm>
  61:                  </Cdtr>
  62:                  <CdtrAcct>
  63:                      <Id>
  64:                          <IBAN>@item.IBAN </IBAN>
  65:                      </Id>
  66:                  </CdtrAcct>
  67:                  <Purp>
  68:                      <Cd>COST</Cd>
  69:                  </Purp>
  70:                  <RmtInf>
  71:                      <Ustrd>@item.VWZ</Ustrd>
  72:                  </RmtInf>
  73:              </CdtTrfTxInf>
  74:              }
  75:   
  76:          </PmtInf>
  77:      </CstmrCdtTrfInitn>
  78:  </Document>

Die Banking Razor Page erhält nun einen PageHandler. Konvention für Benennung ist OnGet + Handler Name, hier OnGetXML. Als Rückgabe wird ein Partial erwartet, der per ActionResult passend in der Funktion auftaucht. Dies rendert nur den HTML des Partials. Seit ASP.net core 2.2 gibt es eine Partial Methode der man den Namen der partiellen Sicht einfach als String mit gibt. Form: return Partial("_xmliso20022"). Das funktioniert in meinem Tests nur sauber, wenn Page und Partial das idente Model und zwar genau das PageModel der View erhalten. In diesem Fall habe ich ein abstraktes Viewmodel eingefügt. Zwar erlaubt Partial in einer Überladung einen zweiten Parameter für das Model, aber leider nur mit einer Fehlermeldung zur Laufzeit. Mein Workaround ist (bitte @page im partial entfernen nicht vergessen) die alte Methode aus ASP.NET MVC Zeiten PartialViewResult.

   1:   return new PartialViewResult
   2:    {
   3:         ViewName = "_xmliso20022",
   4:         ViewData = new ViewDataDictionary<BankingViewModel>(ViewData, MyVM)
   5:    };

Um am Ende noch die Datei sinnvoll abzuspeichern und nicht einfach im Browser anzuzeigen, lässt sich im Page Handler der Response Header auf etwas passendes setzen.

   1:  HttpContext.Response.Headers.Add("Content-Disposition", "attachment;filename=hbci.xml");        

 

Kommentare sind geschlossen