ASP.NET Webforms Blazor Clone

Aktuell arbeite ich an einem Chat für die ppedv Website. Im Rahmen des Software Entwurfes habe ich mir überlegt die Ideen von Server Side Blazor in Webforms nachzubauen. Dabei wird der HTML Code am Server gerendert und per Websockets zum Browser gesendet. SignalR Setup Zuerst wird in das Visual Studio Web Projekt SignalR 2.4 per Nuget installiert. In der Startup.vb (oder cs) muss das Routing für den Hub aktiviert werden. 1: Public Partial Class Startup 2: Public Sub Configuration(app As IAppBuilder) 3: app.MapSignalR() .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } UserControl ala Partial renderer In ASP.NET Core kann man per partial, Teile einer HTML Page rendern. Ganz ohne den HTML, Body etc Gedöns außen rum. Dieses HTML Schnipsel wird später zum Client geschickt. Der KLösungsansatz ist ein sogenanntes Benutzersteuerelement oder auch Usercontrol, mit der Endung .ascx Was das tut ist eigentlich egal. Nur der Vollständigkeit halber mein Chat Part ASPX Part im Usercontrol 1: <%@ Control Language="VB" AutoEventWireup="false" 2: CodeFile="ChatUserControl.ascx.vb" Inherits="uc_ChatUserControl" %> 3: <asp:Repeater ID="Repeater1" ItemType="ChatMsg" runat="server" 4: SelectMethod="Repeater1_GetData" 5: EnableViewState="false"> 6: <ItemTemplate> 7: <div class="chatcontainer"> 8: <img src="/images/chat<%#Item.Extern %>.svg" alt="Avatar" 9: style="width: 100%;" class='<%#If(Item.Extern, "right", "left") %>'> 10: <p><%#Item.Nachricht %></p> 11: <p><%#Item.User %></p> 12: <span class='time-<%#If(Item.Extern, "right", "left") %>'> 13: <%#Item.Zeit.ToShortTimeString%></span> 14: </div> 15: </ItemTemplate> 16: </asp:Repeater> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }   Übrigens ganz modern mit Modelbinding gelöst. Der SignalR Hub Code Eine Klasse erbt von Hub und dient als Schnittstelle für eingehende und ausgehende Nachrichten an beliebige Clients. Wir nutzen hier nur den Browser. Es gibt aber auch Signalr implementierungen für WPF und co. Dies nicht unbedingt Thema des ASP.NET Blog Artikels. 1: Public Class chathub 2: Inherits Hub 3: Public Shared ListeMsgs As New List(Of ChatMsg) 4:   5: Public Async Function NeueNachricht(name As String, msg As String) As Task 6: .... 7: Await Clients.All.ChatMessage("name", xml) .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }   Der HTML Control Renderer Da sich die Hub Klasse außerhalb der HTTP Request Pipeline befindet, gibt es einiges nicht was man so gewohnt ist. Unter anderem ein Page Objekt, das wir uns ganz frech neu erfinden. 1: Public Class EmptyPage 2: Inherits Page 3: Public Overrides Sub VerifyRenderingInServerForm(ByVal control As Control) 4: Return 5: End Sub 6: End Class .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Dies um den kompletten HTML Overhead einfach wegzulassen. Kein HEAD, BODY und co. In der Hub Klasse wird eine Instanz dieser leeren Page erzeugt, das Usercontrol geladen und gerendert. Im VB.NET Beispiel mit einer statischen Hilfsklasse 1: Public Shared Function RenderChat() As String 2: Dim p As New EmptyPage ' Page ohne <Form 3: Dim ctrl = 4: (p.LoadControl("~/UC/ChatUserControl.ascx")) 5: ctrl.DataBind() 6: Dim sw As System.IO.StringWriter = New System.IO.StringWriter() 7: Dim hw As System.Web.UI.HtmlTextWriter = New HtmlTextWriter(sw) 8: ctrl.RenderControl(hw) 9:   10: 'Update UI 11: Return sw.ToString() 12: End Function .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Diese Zeichenkette wird dann per ChatMessage Signalr an die konnektierten Browser geschickt und einem JavaScript Event übergeben. Also das was im Listing mit ,xml) vorher codiert wurde. JavaScript im Browser Diese HTML Zeichenkette kann per Jquery DOM operation einfach einem DIV Chatliste als HTML zugewiesen werden. 1: $(function () { 2: var chat = $.connection.chathub; 3: chat.client.ChatMessage = function (name, msg) { 4: $('#chatliste').html(msg); 5: }; 6:   7: $.connection.hub.start().done(function () { 8: $('#sendmsg').click(function () { 9: ... 10: }); 11: }); 12: }); .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Mein ASP.NET Webforms Blazor Clone. Verwende ich übrigens produktiv.

Responsive Image Preview List mit Bootstrap 4 und ASP.NET Webforms

Im folgenden Lab erstellen sie eine Bildervorschau im Grid Layout. Dieses ändert je nach verfügbarer Breite die Anzahl der Spalten. Erstellen Sie mit Visual Studio ein neues ASP.NET Webforms Projekt. Die Templates verwenden allerdings noch die UI Library Bootstrap 3. Wählen Sie den Nuget Paket Manager und aktualisieren Sie alle Pakete. Die aktuelle Version 4.x erfordert ein neues Layout für das Menü. Bearbeiten Sie dazu die Datei Site.Master im Visual Studio Editor. Ersetzen Sie den vorhanden HTML Code für das Navigation Menü 1: <header> 2: <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3"> 3: <div class="container"> 4: <a class="navbar-brand" href="/Index">ppedv AG</a> 5: <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent" 6: aria-expanded="false" aria-label="Toggle navigation"> 7: <span class="navbar-toggler-icon"></span> 8: </button> 9: <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse"> 10: <ul class="navbar-nav flex-grow-1"> 11: <li class="nav-item"> 12: <a class="nav-link text-dark" href="/Index">Home</a> 13: </li> 14: <li class="nav-item"> 15: <a class="nav-link text-dark" href="/Privacy">Privacy</a> 16: </li> 17: </ul> 18: </div> 19: </div> 20: </nav> 21: </header> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }   Erstellen Sie eine Klasse Bilder, die später als Model Klasse für das ASP.NET Modelbinding in einer generischen Liste genutzt wird. Ein Property soll reichen um das Konzept zu erläutern. 1: Public Class Bilder 2: Public Property Pfad As String 3: End Class .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Bilder können mehrere MB groß sein. Deswegen wird jedes Bild verkleinert und auf einheitliche Größe formatiert. Dazu wird eine ASHX Handler Klasse erzeugt, die den Dateinamen als Querystring ausliest. Fügen Sie dem Visual Studio Projekt eine neue Datei (Handler) hinzu mit dem Namen Thumbnail. Fügen Sie folgenden Code in ProcessRequest Methode ein. Ersetzen Sie dabei den automatisch generierten VB.NET Code im Methodenrumpf. Es wird nun der Parameter aus der Url ausgelesen. Das Bild geladen und ein miniaturisiertes Vorschaubild erstellt. Aus Performancegründen wird ein einmal so komprimiertes Image in den Cache des Webservers gespeichert und wenn vorhanden daraus abgerufen. 1: Dim img As String = context.Request.QueryString("img") 2: Dim cacheImg As String = context.Request.CurrentExecutionFilePath + ":" + img 3: Dim pic() As Byte 4: Dim cachedPic As Object = context.Cache.Get(cacheImg) 5: If IsNothing(cachedPic) Then 6: Dim im = New Bitmap(context.Server.MapPath("~\images\" + img)) 7: Dim stream = New MemoryStream() 8: im = im.GetThumbnailImage(200, 140, Nothing, Nothing) 9: im.Save(stream, ImageFormat.Jpeg) 10: stream.Close() 11: pic = stream.GetBuffer() 12: context.Cache.Add(cacheImg, pic, Nothing, DateTime.MaxValue, New TimeSpan(2, 0, 0), CacheItemPriority.Normal, Nothing) 13: Else 14: pic = CType(cachedPic, Byte()) 15: End If 16: context.Response.ContentType = "image/jpg" 17: context.Response.Cache.SetCacheability(HttpCacheability.Public) 18: context.Response.BufferOutput = False 19: context.Response.OutputStream.Write(pic, 0, pic.Length) .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Diese Handler muss in einem IMG Element einer HTML Seite als Source eingebettet werden. Dazu erstellen Sie eine ASPX Seite, mit Referenz auf die Masterpage. Innerhalb des Content Elements wird folgender HTML Code platziert. Erstellen Sie ein Repeater Control, weisen den Itemtype zu und lassen sich die SelectMethod automatisch generieren. Im Itemtemplate wird das Bootstrap Feature Cards genutzt um Bild und Bildnamen darzustellen. Wenn das Binding korrekt ist, wird nach tippen von #Item durch Intellisense das Property Pfad vorgeschlagen. 1: <div class="row"> 2: <asp:Repeater ID="rptBilder" runat="server" 3: ItemType="ImageGrid1.Bilder" SelectMethod="rptBilder_GetData"> 4: <ItemTemplate> 5: <div class="card" style="width: 18rem;"> 6: <img class="card-img-top" src="/modules/thumbnail.ashx?img=<%#Item.Pfad %>" 7: alt="<%#Item.Pfad %>"> 8: <div class="card-body"> 9: <p class="card-text"><%#Item.Pfad %></p> 10: </div> 11: </div> 12: </ItemTemplate> 13: </asp:Repeater> 14: </div> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Nun fehlt noch im Codebehind der aspx Seite der Code der zum Laden der Daten ausgeführt wird. Es wird das Verzeichnis ausgelesen und eine neue generische Liste vom Typ Bilder erzeugt und als Return Wert an den Repeater geliefert. 1: Public Function rptBilder_GetData() As IEnumerable(Of Bilder) 2: Dim files As String() = Directory.GetFiles(Server.MapPath("~\images"), "*.jpg") 3: Dim imgs = From item In files 4: Select New Bilder With {.Pfad = Path.GetFileName(item)} 5: Return imgs 6: End Function .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

Visual Basic 14 und Webforms

Bereits seit 2015 hat Visual Basic (und auch C#) einige neue Sprach Features. Zwei VB.NET Funktionen haben es mir dabei angetan in meiner täglichen Entwickler Arbeit. Multi Line String Literals Bereits die XML Literals haben Jahrelang dem C# die leichte Zornesröte des Neides ins Gesicht getrieben. Gibt es meines Wissens bis heute nicht, wobei natürlich XML heute sowieso Bäh ist und durch JSON zu ersetzen. Mein Problem sind immer mehrzeiligen komplexe SQL Abfragen im Source Code. Mit Multiline String Literals geht nun Copy Paste und das lästige schachten in Hochkommas und das verketten mit dem Plus Operator entfällt. String Interpolation Nicht ganz so nützlich, aber deutlicher Gewinn an Lesbarkeit. Es wird das $ Zeichen verwendete um direkt Variablen, samt Format Expression in geschweifte Klammern platzieren zu können. $ ist die Kurzform von String.Format. 1: Dim s = $"hello {p.Name} you are {p.Height:0.00}m tall" .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }   Nun setzen diese Sprach Features auf den neuen Kompiler (Rosyln) auf. Der fehlt in Web Forms Projekten und Websites. Dort gilt praktisch noch VB 12. Das war mein Wissensstand bis gestern. Ich war wieder einmal unzufrieden mit dem 10 zeiligen SQL Script im Quellcode. Relativ schnell bin ich auf die Lösung für Roslyn in ASP.NET Webforms Projekten gestoßen. Um den VB 14 Compiler verwenden zu können, wird das Nuget Paket Microsoft.CodeDom.Providers.DotNetCompilerPlatform  in das Projekt installiert. Nun arbeite ich in der Regel nicht mit Projekten sondern Websites. Der feine Unterschied wird manch einem nicht bekannt sein. Websites betrachten jede ASPX Datei als eigenes “Projekt” und erstellen einzelne Assemblys und nicht wie bei Projects eine dicke DLL ins BIN Verzeichnis. Man erkennt in der ASPX Datei dies am Attribut Codefile (Website) statt Codebehind (Projekt). Außerdem müssen Klassen im APP_CODE Ordner abgelegt werden. Das bedeutet ich kann einen ASPX Seite oder die zugehörige .vb Datei in Notepad editieren. Kompiliert wird diese (und nur die geänderten) Automatisch beim erstem Aufruf. Razor Pages haben später das Konzept übernommen. Jedenfalls klappt das mit Websites nicht, trotz installation des NUGET Pakets. Eine kleine Änderung in der web.config bring dann doch auch eine ASP.NET Website auf die VB 14 Language Version. Einfach “default” auf “14” ändern. 1: <system.codedom> 2: <compilers> 3: <compiler language="c#;cs;csharp" extension=".cs" 4: type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" 5: warningLevel="4" compilerOptions="/langversion:default /nowarn:1659;1699;1701"/> 6: <compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" 7: type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" 8: warningLevel="4" compilerOptions="/langversion:14 /nowarn:41008 /define:_MYTYPE=\&quot;Web\&quot; /optionInfer+"/> 9: </compilers> 10: </system.codedom> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

URL Pfad, Querystring und Segmente

Da ich mich aktuell mit einer Programmieraufgaben mit ASP.NET Web API, Webforms und FriendlyUrls beschäftige, stehe ich dauernd vor Fragen wie Wie bekomme ich die Page? Wie erhalte ich den Querystring, das FriendlyUrlsegment? Wie komme ich auf den vollen Pfad? Zunächst kommt es darauf an, wo man den Code platziert. In einer ASPX Page ist das Request Objekt direkt greifbar 1: Request.Url .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Damit erhält man ein Objekt vom Typ Url. In Modulen, Handlern und Services, muss man über den HTTP Context gehen. 1: HttpContext.Current.Request.Url .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }   Am Beispiel Url http://localhost:50458/test.aspx?Id=demo&val=0 ApplicationPath: / CurrentExecutionFilePath:/test.aspx FilePath: /test.aspx Path: /test.aspx PathInfo: PhysicalApplicationPath: C:\WebSites\companyII\ RawUrl: /test.aspx?Id=demo&val=0 Url.AbsolutePath: /test.aspx Url.AbsoluteUri: http://localhost:50458/test.aspx?Id=demo&val=0 Url.Fragment: Url.Host: localhost Url.Authority: localhost:50458 Url.LocalPath: /test.aspx Url.PathAndQuery: /test.aspx?Id=demo&val=0 Url.Port: 50458 Url.Query: ?Id=demo&val=0 Url.Scheme: http Url.Segments: / test.aspx

Die USTID mit JavaScript und Webforms automatisieren

Die EU wird aktuell (DSVGO oder GDPR) viel gescholten. Ein positiver Aspekt unserer Gemeinschaft ist, das der Handel zwischen Mitgliedsländern einfacher von statten geht, sofern man eine gültige USTID (Umsatzsteuer Identifikation) auf beiden Seiten besitzt. Lieferant kann so innergemeinschaftlich ohne den müßigen Vorsteuerabzug bzw. Einzug liefern. Diese ID wird vom Finanzamt vergeben und muss tagesaktuell geprüft werden, wofür ein EU Portal genannt VIES kostenfrei, bereit steht. Es gibt dort auch einen Webservice mit gutem alten SOAP Ansatz. Etwas was man sich mit JavaScript nicht antun will, um die Client Validierung auszuführen. Entsprechend beinhaltet die ASP.NET Lösung folgende Aufgaben ASP.NET Formular zur Eingabe der ID JavaScript Funktion für Lostfocus und Ajax Call ASP.NET Service der den SOAP Call kapselt JavaScript + Bootstrap um den Erfolg zu visualisieren Web Service hinzufügen Früher, da waren Webservices etwas sehr fixes und definierten die Verbindlichkeit (Contract) per Interface. Entsprechend lassen sich Proxy Klassen per Visual Studio –Add Service Reference- generieren, die als Zugriffsobjekte dienen. Dies allerdings nur wenn der Service per WSDL sich selbst beschreiben kann. Im Visual Studio Web Projekt werden so Verzeichnisse und Dateien angelegt, von denen man die Finger lassen sollte. Der Wizard macht schon. Ab in den Code. Mein Ziel ist ein Service der einen Service kapselt. Da wir nur true oder false auf die Frage “Ustid korrekt”, reicht es eine ASPX Webforms Seite anzulegen. Diese wird bis auf zwei Zeilen komplett geleert. Dies hat noch einen weiteren dramatischen Vorteil. 1: <%@ Page Language="VB" AutoEventWireup="false" CodeFile="CheckUstid.aspx.vb" Inherits="CheckUstid" %> 2: <%@ OutputCache Duration="600" VaryByParam="ustid" %> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Wie der Kenner sehen kann, nutze ich hier Caching und reduziere damit die Requests zum EU Server dramatisch. In 600 Sekunden wird sich die Gültigkeit der USTID nicht ändern. Als Parameter wird der QueryString “ustid” übergeben. Im VB.NET Codebehind wird das Service Objekt instanziiert, der QueryString ausgelesen und zerlegt und dann an den EU Service per checkVat weiter gereicht. Die Rückgabe ist dann eine Zeichenkette true oder false. 1: Private Sub cachy_CheckUstid_Load(sender As Object, e As EventArgs) Handles Me.Load 2: Dim ws As New CheckVatServiceReference1.checkVatPortTypeClient 3: Dim ustid = Request.QueryString("ustid") 4: Dim isvalid As Boolean 5: Dim Name As String 6: Dim adresse As String 7: Dim land As String = Left(ustid, 2) 8: Dim id As String = ustid.Substring(2, ustid.Length - 2) 9: ws.checkVat(land, id, isvalid, Name, adresse) 10: Response.Write(isvalid) .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Im HTML Teil der ASPX Seite kommt das Bootstrap Framework in der Version 3 zum Einsatz. Um im Erfolgsfalle die Box grün und im Fehlerfall rot umrandet zu bekommen, hilft das div ustidform. Das Lostfocus Event in JavaScript nennt sich onblur. Dies sollte ausgelöst werden, wenn der Benutzer das Eingabefeld verlässt. Das kann häufiger sein, als man denkt, z.B. wenn vom Browserfenster in ein anderes Programm gewechselt wird. 1: <div id="ustidform"> 2: <label class="control-label col-md-1" for="ustid">USTID</label> 3: <div class="col-md-2"> 4: <input class="form-control" id="ustid" placeholder="Ustid" 5: onblur="CheckUSTID(this);"> 6: </div> 7: </div> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }   Nun kommt der Sadomaso Teil: JavaScript. Wenn man den Code klein hält, geht das gerade noch. Um die ID eines ASP.NET Steuerelements, wie die Textbox, aus JavaScript auch sicher ansteuern zu können, hilft das Attribut ClientIDMode="Static".  Was hier nicht nötig ist, weil nur pur HTML Input Element. Der HTML Trick sind zwei CSS Klassen aus Bootstrap für Error und success. Diese werden dem DIV hinzugefügt, wenn der ASP.NET Webforms “Service” seine Antwort liefert. Das $ ist die Kurzform für die JQuery Bibliothek. 1: function CheckUSTID(input) { 2: $.ajax({ 3: url: "checkustid.aspx", 4: data: { "ustid": input.value }, 5: success: function (result) { 6: if (result == 'False') { 7: $('#ustidform').addClass("has-error"); 8: console.log("invalid ustid"); 9: } 10: else { 11: $('#ustidform').addClass("has-success"); 12: console.log("valid ustid"); 13: } 14: 15: } 16: }); 17: } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } War doch nicht so schlimm. Benutzer tippt, verlässt das Feld mit Tab und tippt weiter und im Hintergrund findet die Prüfung statt, die im Erfolgsfall so aussieht oder so Dann muss die Rechnung mit MWst ausgestellt werden.

ASP.NET Identity

Da Microsoft in den letzten Monaten durchaus den ein oder anderen Blick über den Tellerrand gewagt hat, hat sich doch so einiges getan. Besonders in der Web-Welt von ASP.NET. Alle sprechen nur noch von One ASP.NET, und das alles einfacher und klarer ist und werden soll. Die ersten Schritte sind schon getan, man findet beim erstellen eines neuen Projekts nur noch eine ASP.NET WebApplication Vorlage welche dann die optionale Auswahl der verschiedensten Technologien ermöglicht. Doch der Fokus dieses Blogbeitrags soll auf das neue IdentityModel abzielen. Bisher war es wohl eher eine Qual mit dem Membership Modell von Microsoft etwas eigenes bzw. angepasstes auf die Beine zu stellen. Doch nun hat das ASP.NET Team erkannt, dass es an der Zeit für etwas frischen Wind ist und so einige Änderungen/Anpassungen vollzogen welche es uns Entwicklern leichter ermöglichen sollen unsere User mit den dazugehörigen Profildaten zu verknüpfen. Mit ASP.NET WebForms Zunächst schauen wir uns mal die Projektvorlage an welche wir beim erstellen einer neuen WebApplication erhalten. Im App_Start Ordner ist die Identityconfig.cs und im Models ordner, welcher selbst auch neu ist, kam die IdentityModel.cs neu hinzu. Beide zusammen bilden so zu sagen das Grundgerüst für das neue IdentityModel. IdentityConfig In der IdentityConfig.cs befinden sich mehrere Klassen, eine davon ist die ApplicationUserManager. Diese ist der Dreh und Angelpunkt der IdentityConfig. Hier werden mehrere Einstellungen und Richtlinien getroffen, hauptsächlich bei der Erzeugung eines neuen Users. Hier wird z. B. die Passwortstärke oder die Anzahl an maximalen fehlversuchen beim Login festgelegt. IdentityModel Im IdentityModel befindet sich einerseits das UserModel (ApplicationUser) und der DbContext welcher von der EntityFramework CodeFirst Migration verwendet wird. Falls wir nun unseren Usern weiter Information beim Login aus den Fingern kitzeln wollen fügen wir diese einfach dem Application User als einfaches Property hinzu. In unserem Fall fügen wir mal ein Property Ort vom Typ string hinzu. 1: public class ApplicationUser : IdentityUser 2: { 3: public string Ort { get; set; } 4:  5: public ClaimsIdentity GenerateUserIdentity(ApplicationUserManager manager) 6: { 7: // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType 8: var userIdentity = manager.CreateIdentity(this, DefaultAuthenticationTypes.ApplicationCookie); 9: // Add custom user claims here 10: return userIdentity; 11: } 12:  13: public Task<claimsidentity> GenerateUserIdentityAsync(ApplicationUserManager manager) 14: { 15: return Task.FromResult(GenerateUserIdentity(manager)); 16: } 17: } Im ApplicationUserContext steht nicht großartig viel Code. Den Context verwenden wir eig. nur um mit Hilfe der CodeFirst Migration auf die Datenbank los zu gehen. Hierzu 2 einfache Befehle: Enable-Migrations –EnableAutomaticMigration Update-Database EnableeAutomaticMigration deshalb, weil wir dann keine Zeit damit verschwenden müssen uns selbst um die Migrationen zu kümmern. Standardgemäß wird die entsprechende Datenbank als lokaler MDF-File im App_Data Verzeichnis erzeugt, dies kann man natürlich auch ändern wie man es gerne möchte in der Web.config  z. B. Letzte Schritte Da wir nun die Config und das Model soweit konfiguriert haben wie wir es gerne möchten und unsere Datenbank geupdated wurde, steht uns nicht mehr viel im Weg bevor wir diese Daten vom User erfassen lassen können. Der nächste Schritt wäre es, in der Register.aspx eine weitere Zeile mit entsprechendem Input Element hinzu zu fügen welche dem Nutzer die Eingabe ermöglicht. 1: <div class="form-group"> 2: <asp:label runat="server" AssociatedControlId="Ort" CssClass="col-md-2 control-label">Email</asp:label> 3: <div class="col-md-10"> 4: <asp:TextBox runat="server" ID="Ort" CssClass="form-control" /> 5: <asp:RequiredFieldValidator runat="server" ControlToValidate="Ort" 6: CssClass="text-danger" ErrorMessage="The Ort field is required." /> 7: </div> 8: </div> Nachdem wir das Input-Element hinzugefügt haben müssen wir noch in der CodeBehind beim erzeugen des neuen Users die jeweiligen Daten mit angebenen. 1: var user = new ApplicationUser() { UserName = Email.Text, Email = Email.Text, Ort = Ort.Text };   Und das wars schon. Mehr muss man gar nicht machen um die Userdaten zu erweitern. Mit MVC sieht das ganze recht ähnlich aus nur anstelle der Register.aspx hat man im Views/Account/ Verzeichnis eine Register.cshtml und die CodeBehind haben wir dort auch nicht. Anstelle davon ist der AccountController da mit der [HttpPost] Register Action. Ein kleiner zusätlicher Schritt ist noch bei MVC zu machen, und zwar man sollte noch das RegisterViewModel anpassen, dort kann man auch direkt mit Data Annotatinos Arbeiten und weitere ValidierungsLogik hinzufügen. Fazit Einfach zu handhaben und gar nicht so kompliziert wie es den meisten im ersten Moment vll. vorkommt. Viel Spaß

ASP.NET WebForms - Check for JavaScript

Zunächst mal ein kleines Vorwort. Natürlich unterstütz heutzutage jeder Vernünftige Browser JavaScript und erlaubt dieses auch auszuführen. Jedoch sollte man die Zahl an ‘uralt’ PC’s/Browsern oder Usern welche JavaScript von Hand deaktivieren nicht unterschätzen. Besondern sollte man sich dabei zu helfen wissen. Nun zur Lösung des Problems. Der Ansatz ist eig. schon vorhanden aber seht selbst… Einfach im Page_Load… 1: if (Request.Browser.JavaScript == false) 2: } 3: Response.Redirect("~/NoJS.aspx"); 4: } … und die Sache sollte funktionieren. Falsch gedacht. Das JavaScript Property sagt nur aus ob der Browser JavaScript unterstützt, nicht ob es erlaubt wird auszuführen. Diese Variante würde sich also nicht dazu eignen herauszufinden, ob der User das Ausführen von JS-Code von Hand deaktiviert hat. Man braucht also einen anderen Lösungsansatz… Zunächst einmal, alle Wege (oder viele davon) führen nach Rom. Einen davon werden wir im Folgenden betrachten. Ich habe mich für die URL-Parameter Variante entschieden, wobei hier natürlich der Nutzer sein Unwesen treiben kann. Dazu habe ich folgenden JavaScript Code eingefügt: 1: window.location = "/?js=true"; Dies hat zur Folge, dass ein Redirect geschieht und der entsprechende Parameter der URL hinzugefügt wird. Jetzt könnten wir zwar ganz einfach bereits herausfinden, ob der Redirect erfolgreich war oder eben nicht. Aber falls JS aktiviert ist wird nun jedes Mal beim Ausführen dieser Zeile ein Redirect durchgeführt und es entsteht eine klassische Endlosschleife :-) Um das zu verhindern fügen wir folgende Abfrage hinzu: 1: if ('<%: tokenExists()%>' == 'false') { 2: window.location = "/?js=true"; 3: } *(1) *(2) In der Methode ‘tokenExists’ welche wir hier aufrufen… 1: public bool tokenExists() 2: { 3: if (Request["js"] != null) 4: { 5: return true; 6: } 7: return false; 8: } …überprüfen liefern wir true zurück, falls der Parameter bereits gesetzt sein sollte, ansonsten false. Je nach dem was nun zurück gegeben wird und JavaScript aktiviert sein sollte wird nun erfolgt nun eben der Redirect, es wird erneut überprüft und nun wird true zurück geliefert wenn JavaScript enabled ist und andernfalls false. Nun haben wir schon mal die Überprüfung ob JavaScript enabled ist oder eben nicht, jetzt fehlt noch die Reaktion darauf. Hier auch wieder ‘alle Wege führen nach Rom’. Ob man nun ein Label einblendet mit der entsprechenden Warnung/Hinweis oder den Nutzer über ein Response.Redirect() auf eine NoneJS.aspx weiterleitet oder was auch immer ist dem Entwickler natürlich überlassen. Dazu könnte man folgendermaßen z. B. im Page_Load abfragen: 1: if (Request["js"] == null) 2: { 3: Response.Redirect("~/NoJS.aspx"); 4: } Und schon sind wir fertig. Diese Methode könnte man noch etwas sicherer gestalten, indem man Sessions mit einbringt, da der Nutzer die URL manipulieren bzw. an andere weiterleiten kann. Einfach zum Beispiel in der tokenExists() Methode noch zusätzlich eine Session-Variable erzeugen, falls diese nicht bereits existiert und somit wäre auch dies kein Problem mehr. zu *(1): hier ist noch wichtig zu wissen, dass der Aufruf von tokenExists() trotzdem ausgeführt wird, selbst wenn JS deaktiviert sein sollte. Da dies beim Rendern der Seite geschieht. zu *(2): der Rückgabewert von tokenExists() wird als String interpretiert, d. h. man muss diesen selbstverständlich auch mit einem String vergleichen deswegen ‘false’

Visitor Counter - ASP.NET WebForms

Eine Frage taucht in jedem meiner ASP.NET-Kurse immer wieder auf: “Wie erzeuge ich einen einfachen Visitor-Counter für meine Website?” Nun muss man sich als ASP-Entwickler natürlich darüber im Klaren sein, wie man dies realisieren kann. In der Regel wird den meisten Entwicklern mit schon etwas Erfahrung natürlich sofort das Application Dictionary einfallen und noch das ein oder andere Event, um an das gewünschte Ziel zu gelangen. Lassen Sie uns einen genaueren Blick darauf werfen! Im Folgenden werde ich Ihnen zeigen, wie Sie mithilfe des Application Dictionary und einiger Events die Benutzer zählen können, welche auf Ihrer Website landen. Zunächst der einfache Part: Hinzufügen eines Label Controls zu einer bestehenden Website (zum Anzeigen der Werte). Hierbei werde ich die Standardvorlage des Visual Studio 2012 WebApplication Project verwenden und benenne das WebForm ‘Sessions.aspx’.       1: Aufrufe seit publish (11.11.2013): 2: <asp:Label ID="lblAufrufe" runat="server" />   Um das Label mit Werten zu versorgen, müssen wir dies natürlich auch noch über die CodeBehind erledigen. Dazu fügen wir folgenden Code dem Page_Load Event hinzu: 1: int count = 0; 2: 3: if (Application["Visitors"] != null) 4: { 5: count = (int)Application["Visitors"]; 6: } 7: 8: lblAufrufe.Text = count.ToString(); Als erstes erstellen wir eine temporäre Variable ‘count’, welche wir standardgemäß auf den Wert 0 setze. Falls in der ‘Visitors’-Variable kein Wert enthalten sein sollte, wird die 0 angezeigt. Nun fragen wir den Wert der ‘Visitors’-Variable von unserem Application Dictionary ab, und befüllen anhand dieser unsere temporäre Variable.Im Application Dcitionary werden Werte immer als Object abgespeichert, d. h. wir müssen zunächst TypCasten (=Konvertieren) nach Integer. Nachdem wir das erledigt haben, müssen wir noch den entscheidenden Part erledigen: Anlegen der ‘Visitors’-Variable im Dictionary und das Inkrementieren dieser. Zunächst widmen wir uns dem Anlegen der Variable. Dies geschieht im Application_Start Event der Global.asax. Hier haben wir mehrere Events zur Verfügung, um den Life Cycle unserer Application UND der Sessions mit zu verfolgen. 1: Application["Visitors"] = 0; Nun, da wir unsere Variable in den Application Pool geworfen haben, müssen wir noch dafür sorgen, dass diese auch erhöht wird, immer wenn ein Besucher bei uns landet. Dazu benötigen wir die Session_Start Event-Funktion. Das Session Start Event wird jedes Mal ausgeführt, wenn ein User eine Session erhält ODER er eine neue SessionID aufgrund eines Timeouts zugewiesen bekommt. 1: void Session_Start(object sender, EventArgs e) 2: { 3: Application["Visitors"] = (int)Application["Visitors"] + 1; 4: } Dort aktualisieren wir einfach den alten Wert von unserer Application-Variablen um eins und speichern diesen wieder ab. UND FERTIG! Zum Thema habe ich direkt noch eine Website auf Windows-Azure gehostet, um eine Live-Demo anbieten zu können. Sämtliche darin vorkommenden Informationen habe ich durch das Request Objekt abgefragt und ausgegeben. (Azure-IIS beendet die Anwendung falls innerhalb von 15min kein Request stattfindet…) Visit Counter Demo