Fortuna Entwickler Blog

Hier wird Ihnen geholfen

nuGet Package Versionen einschränken

Wie im Workshop von Herrn Schmidt kurz gezeigt, habe ich nun mal eine packages.config exemplarisch angepasst:

Die eckige öffnende Klamme und die Runde schließende Klammer bedeuten ein 'Inclusive Min' respektive 'Exclusive Max'. Demnach ist eine Version von wenigstens 6.01 erlaubt und eine Version unterhalb (nicht einschliesslich) 10.0.2.

In Visual Studio stellt sich das dann so dar:


Ergebnisse Workshop 14./15. November 2017

nuGet packages

Es ist nicht sinnvoll, nuGet Packages in unsere Release-Zyklen einzubinden; dies widerspricht dem eigentlichen Sinn der nuGet Packages.

In einem Package sollen Funktionen umgesetzt werden, welche Produktunabhängig sind und allgemein Verwendet werden können, z.B. Mailversand, PDF Erstellung und Ähnliches

Empfehlung Herr Schmidt: 

Rückführung Produktspezifischer Packages in die jeweiligen Solutions

Neustrukturierung der Ordnerstruktur für Solutions

Alle Solutions in einem Ordner

Alle Webanwendungen in einem eigenen Ordner in einem Unterordner (‚web‘)

Alle Bibliotheken in einem eigenen Ordner in einigen wenigen Unterordnern (‚logic‘, ‚data‘)

Damit dann Kombination aus nuGet und Bibliotheken

Builds für Projekte automatisieren

Builds für nuGet Packages erstellen Symbole für Debugging, welche vorgehalten werden


Branches, Merges, etc.

Der aktuelle Stand kann so weiter geführt werden.

Das Verschieben von Branches kann möglicherweise in Zukunft wieder verfügbar sein. Derzeit gibt es keine andere Möglichkeit, als es so zu tun, wie wir es derzeit machen.

Herr Schmitt hat uns Alternativen zur derzeitigen Vorgehensweise aufgezeigt. Diese wären gggf. Zu diskutieren.


Event- und Fehlerlogging

Das Logging, so wie wir es derzeit betreiben ist weder performant noch sonst zu empfehlen.

Empfehlung Herr Schmidt: 

Einsatz von NLog

Umstellung aller Anwendungen auf einen Asynchronen Logger

Logging gegen einen WebService (in Azure)

Archivierung von Logs in Azure Storage

Ggf. Automatisiertes Herunterladen aus Azure Storage


Benutzer, Security

Eine eigene Benutzerverwaltung ist aufwendig, fehleranfällig und nur schwer abzusichern.

Empfehlung Herr Schmidt:

Microsoft Azure Active Directory

Vergleichsweise einfache Integration in bestehende Webanwendungen

Keine Verwendung mit VENTA möglich

Derzeit nur für Versicherungskunden

Integration in Azure Service Management, um APIs abzusichern (myPension z.B.)


Swagger

Der Einsatz von Swagger, so wie es derzeit bei uns verwendet wird, entspricht vorgesehenen Verwendung. Keine ToDos.


Oracle / Entities

Oracle ODP.NET ist nicht empfehlenswert, weil vergleichsweise langsam und schlechte Tool-Unterstützung.

DevArt for Oracle ist das Tool der Wahl für den Zugriff auf Oracle Datenbanken via Entities. Der fehlende Service seitens DevArt muss selbst geleistet werden: Neue Versionen als nuGet zur Verfügung stellen, eigene Versionshistorie vorhalten.


Sonstiges

Herr Schmitt empfiehlt die Anbindung unseres AD an Azure AD.

C# 7.0/8.0 bringt wenig sinnvolles Neues, was wir nicht schon kennen

ASP.NET Core bringt für uns keine Vorteile, solange wir nicht mit Docker und/oder anderen Betriebssystemen arbeiten.

Angular.js (oder React.js oder Vue) sind Frameworks, die interessant sein können und gesondert betrachtet werden müssten.

TypeScript als Ersatz für JavaScript ist empfehlenswert.

JavaScript in Webseiten selbst sollte überhaupt nicht benutzt werden, sondern immer in eigene Dateien (dann auch als TypeScript) ausgelagert werden.


Fotos


Themen_Workshop.pptx (578,3KB)

RESTful Service erstellen und Konsumieren mit RestSharp

Ich habe mich im Rahmen der Restschuldversicherung für die von Essen Bank mal mit RESTful Services beschäftigt, da ich eine ganze Reihe an Daten an den Service übergeben muss und auch viele Daten zurückbekomme, liegt der Fokus erst einmal auf POST.

Das Ganze wollte ich erst einmal einfach halten, um bei etwaigen Fehlern nicht in Unmengen von Code zu suchen. Also habe ich erst einmal mit einem ganz kleinen Service begonnen.

In Visual Studio habe ich ein neues Projekt erstellt: Installed -> Visual C# -> WCF -> WCF Service Application. Im neuen Projekt bekommt man dann ein Interface und einen Service vorgegeben, die man nach eigenem Gusto benennen kann. In meinem Fall war dies hinsichtlich des eigentlichen Ziels Fortuna.RSV.Services

Im Interface (Fortuna.RSV.Services.IRSVCalc.cs) habe ich dann erst mal einen ganz einfachen Service angelegt:

        [OperationContract]
        [WebInvoke(Method = "POST",
            ResponseFormat = WebMessageFormat.Json,
            RequestFormat = WebMessageFormat.Json,
            BodyStyle = WebMessageBodyStyle.Bare,
            UriTemplate = "Berechnen")]
        int Berechnen(int i);

Der zugehörige Code (Fortuna.RSV.Services.RSVCalc.svc.cs) im Service selbst ist dazu denkbar einfach:

        public int Berechnen(int i)
        {
            return i * 2;
        }

In der Web.config ist der Service zudem noch einzutragen:

  <system.serviceModel>
    <services>
      <service name="Fortuna.RSV.Services.RSVCalc" behaviorConfiguration="serviceBehavior">
        <endpoint address="" binding="webHttpBinding" contract="Fortuna.RSV.Services.IRSVCalc" behaviorConfiguration="web"></endpoint>
      </service>
    </services>
    <behaviors>
      <endpointBehaviors>
        <behavior name="web">
          <webHttp />
        </behavior>
      </endpointBehaviors>
      <serviceBehaviors>
        <behavior name="serviceBehavior">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>

Damit ist der Service auch schon fertig und grundsätzlich einsatzbereit. Man kann ihn schon im Visual Studio testen.

Nun wollte ich natürlich auch mal sehen, wie man den Service konsumieren kann. Natürlich geht das, indem man einen Request an den Service schickt und die Response auswertet. Anstatt alles 'von Hand' zu machen, habe ich mich entschieden, RestSharp zu verwenden. Informationen dazu findet man auf RestSharp.org und man kann das Package über nuget in sein Projekt einbinden.

Um das nun zu testen habe ich ein neues Projekt (einfach eine C# ClassLibrary) hinzugefügt und über nuget RestSharp in den Referenzen ergänzt. Das Projekt habe ich Fortuna.RSV.ServiceClient genannt, aus class1.cs wurde RSVService.cs:

    public class RSVService
    {
        private string _ServiceBaseUri;
        public RSVService()
        {
        }
        public RSVService(string Uri)
        {
            _ServiceBaseUri = Uri + "RSVCalc.svc/";
        }
        public int Berechnen(int Eingabe)
        {
            int Ausgabe = 0;
            var client = new RestClient(_ServiceBaseUri);
            var request = new RestRequest("Berechnen", Method.POST);
            request.RequestFormat = DataFormat.Json;
            request.AddBody(Eingabe);
            var response = client.Execute(request);
            if (response.ResponseStatus == ResponseStatus.Completed)
                Ausgabe = int.Parse(response.Content);
            return Ausgabe;
        }
    }

Was passiert hier in der Methode Berechnen?

Es wird ein RestClient erstellt; dies ist ein Objekt, welches von RestSharp zur Verfügung gestellt wird. Als Parameter wird die Uri des Service angegeben. Selbige wird im Konstruktor meiner Klasse übergeben. Anschliessend wird der RestRequest definiert. Auch dies ist ein Objekt von RestSharp. Hier geben wir schon an, wie die Methode heisst, die aufgerufen wird, in unserem Fall 'Berechnen', was dem Wert entspricht, der oben als 'UriTemplate' im Contract steht. Als Methode habe ich auch hier (wie im Contract) POST definiert. Ebenso analog zum Contract verwende ich JSON als Datenformat.

Der Parameter (int i) wird dem RequestBody hinzugefügt; RestSharp kümmert sich um die Serialisierung in JSON, was in diesem Fall denkbar wenig ist, aber für die eigentliche Zielsetzung durchaus von Belang ist.

Anschliessend wird der Request mit Execute ausgeführt und das Ergebnis (die Response) in einer Variablen abgelegt. Das Ergebnis des Service erhält man bei dieser Art des Aufrufs in der RestResponse-Property 'Content'. Fertig.

Hier würde natürlich erst mal alles ganz einfach gehalten: Ein integer wird übergeben und ein integer wird zurückgeliefert. Im nächsten Schritt wollte ich dann ein einfaches Objekt mit zwei integer-Werten liefern und einen integer zurückbekommen. Im Service-Interface sieht das dann so aus:

        [OperationContract]
        [WebInvoke(Method = "POST",
            ResponseFormat = WebMessageFormat.Json,
            RequestFormat = WebMessageFormat.Json,
            BodyStyle = WebMessageBodyStyle.Bare,
            UriTemplate = "Berechnen3")]
        int Berechnen3(BerechnenClass data);

Die Berechnen Class ist denkbar übersichtlich:

    [DataContract(Namespace="http://fortuna.mylife-leben.de/RSV")]
    public class BerechnenClass
    {
        [DataMember]
        public int parm1 { get; set; }
        [DataMember]
        public int parm2 { get; set; }
    }

Und auch den Code des Service habe ich einfach gehalten:

        public int Berechnen3(BerechnenClass data)
        {
            return data.parm1 * data.parm2;
        }

In der Web.config ist jetzt nichts weiter einzutragen; der Service ist dort ja bereits vorhanden; es kommt nur eine neue Methode dazu.

Im Client benötige ich natürlich auch das Objekt und der Code ist in grossen Teilen mit dem ersten identisch:

        public int Berechnen3(int parm1, int parm2)
        {
            BerechnenClass c = new BerechnenClass() { parm1 = parm1, parm2 = parm2 };
            int Ausgabe = 0;
            var client = new RestClient(_ServiceBaseUri);
            var request = new RestRequest("Berechnen3", Method.POST);
            request.RequestFormat = DataFormat.Json;
            request.AddBody(c);
            var response = client.Execute(request);
            if (response.ResponseStatus == ResponseStatus.Completed)
                Ausgabe = int.Parse(response.Content);
            return Ausgabe;
        }

Auch hier wieder: Client erstellen, Request erstellen, dann aber eben das zu übergebende Objekt dem RequestBody hinzufügen, die Serialisierung nach JSON wird von RestSharp übernommen, dann der Aufruf des Services und die Verarbeitung der Response. Fertig.

Nachdem auch dies funktioniert wollte ich nun das Objekt übergeben, welches zur Berechnung und als Ergebnis bei der Restschuldversicherung bereits vorhanden ist. Ein Klasse mit deutlich über 30 Parametern, sowohl integer als auch double Werte, also schon etwas größer als das, was ich hier bisher verwendet habe. Die Klasse heisst 'RSVdaten'

Das Interface ist ähnlich wie gehabt:

        [OperationContract]
        [WebInvoke(Method = "POST",
            ResponseFormat = WebMessageFormat.Json,
            RequestFormat = WebMessageFormat.Json,
            BodyStyle = WebMessageBodyStyle.Bare,
            UriTemplate = "Berechnen2")]
        RSVdaten Berechnen2(RSVdaten data);

Der Code im Service ist sehr einfach, weil die eigentliche Berechnung in C++ abgebildet ist:

        public RSVdaten Berechnen2(RSVdaten data)
        {
            RSVInterfaceUnmanaged.ClassRSVBer myClass = new ClassRSVBer();
            myClass.ber(data);
            return data;
        }

Während dies alles nicht weiter aufregend ist, gab es jetzt für den Client eine gewisse Herausforderung. Bisher haben die Methoden int zurückgeliefert, hier jetzt aber ein komplexes Objekt. RestSharp bietet hier eine schöne Variante des 'Execute' an:

        public RSVdaten Berechnen2(RSVdaten data)
        {
            RSVdaten Ausgabe = new RSVdaten();

            var client = new RestClient(_ServiceBaseUri);
            var request = new RestRequest("Berechnen2", Method.POST);
            request.RequestFormat = DataFormat.Json;
            request.AddBody(data);

            var response = client.Execute<RSVdaten>(request);
            if (response.ResponseStatus == ResponseStatus.Completed)
                Ausgabe = response.Data;

            return Ausgabe;
        }

Beim Execute gibt man bereits an, von welchem Typ man die Response erwartet. Den Inhalt findet man dann nciht im Parameter Content, sondern in Data, welcher dann schon vom angegeben Typ ist. Mehr ist auch hier nicht zu tun.