SQL Beispiele

Jedes Datum in einem Zeitraum finden

Hin und wieder braucht man jedes einzelne Datum in einem Zeitraum in SQL. Oft wird eine Tabelle mit Zahlen dafür verwendet (nur ein Wert von 1…) verwendet.

Eine Alternative ist dieses kleine Select mit dem jedes Datum in einem Zeitraum gefunden werden kann.

DECLARE @StartDate as Date =convert(date,'18.09.23',4)
DECLARE @EndDate as Date =convert(date,'24.09.23',4)

SELECT TOP (DATEDIFF(DAY, @StartDate, @EndDate) + 1)
Date = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, convert(date,@EndDate,4))
FROM sys.all_objects a
CROSS JOIN sys.all_objects b

Grundlegend tut das Select nichts anderes als einen Cross Join über alle objekte in der Datenbank zu machen um eine möglichst große Zahlreihe zu erstellen. Diese Zahlenreihe wird anhand der Row_Number Funktion. Dann muss nur noch das Datum ermittelt werden.

SQL geht ganz einfach 🙂

Twitter API

Habe heute eine Weile gebraucht bis ich einen Tweet absetzen konnte. Für mich und im Fall, das es jemand anderer braucht.

Twitter Developer Platform overview | Docs | Twitter Developer Platform

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

public class TwitterClient
{
    private readonly string consumerKey;
    private readonly string consumerSecret;
    private readonly string accessToken;
    private readonly string tokenSecret;

    private readonly HttpClient httpClient;

    public TwitterClient(HttpClient httpClient, string consumerKey, string consumerSecret, string accessToken, string tokenSecret)
    {
        this.httpClient = httpClient;
        this.consumerKey = consumerKey;
        this.consumerSecret = consumerSecret;
        this.accessToken = accessToken;
        this.tokenSecret = tokenSecret;
    }

    public async Task PostTweet(string text)
    {
        var timstamp = CreateTimestamp();
        var nonce = CreateNonce();
        var body = JsonSerializer.Serialize(new { text });
        var uri = new Uri("https://api.twitter.com/2/tweets");

        var request = new HttpRequestMessage
        {
            RequestUri = uri,
            Method = HttpMethod.Post,
            Content = new StringContent(body, Encoding.ASCII, "application/json")
        };

        var signatureBase64 = CreateSignature(uri.ToString(), "POST", nonce, timstamp);

        request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("OAuth",
            $@"oauth_consumer_key=""{Uri.EscapeDataString(consumerKey)}""",
            $@",oauth_token=""{Uri.EscapeDataString(accessToken)}""",
            $@",oauth_signature_method=""HMAC-SHA1"",oauth_timestamp=""{Uri.EscapeDataString(timstamp)}""",
            $@",oauth_nonce=""{Uri.EscapeDataString(nonce)}"",oauth_version=""1.0""",
            $@",oauth_signature=""{Uri.EscapeDataString(signatureBase64)}""");

        var response = await httpClient.SendAsync(request);

        response.EnsureSuccessStatusCode();
    }

    private string CreateSignature(string url, string method, string nonce, string timestamp)
    {
        var parameters = new Dictionary<string, string>();

        parameters.Add("oauth_consumer_key", consumerKey);
        parameters.Add("oauth_nonce", nonce);
        parameters.Add("oauth_signature_method", "HMAC-SHA1");
        parameters.Add("oauth_timestamp", timestamp);
        parameters.Add("oauth_token", accessToken);
        parameters.Add("oauth_version", "1.0");

        var sigBaseString = CombineQueryParams(parameters);

        var signatureBaseString = $"{method}&{Uri.EscapeDataString(url)}&{Uri.EscapeDataString(sigBaseString)}";

        var compositeKey = $"{Uri.EscapeDataString(consumerSecret)}&{Uri.EscapeDataString(tokenSecret)}";

        using (var hasher = new HMACSHA1(Encoding.ASCII.GetBytes(compositeKey)))
        {
            return Convert.ToBase64String(hasher.ComputeHash(Encoding.ASCII.GetBytes(signatureBaseString)));
        }
    }

    private string CreateTimestamp()
    {
        var totalSeconds = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc))
            .TotalSeconds;

        return Convert.ToInt64(totalSeconds).ToString();
    }

    private string CreateNonce()
    {
        return Convert.ToBase64String(
            new ASCIIEncoding().GetBytes(
                DateTime.Now.Ticks.ToString()));
    }

    public string CombineQueryParams(Dictionary<string, string> parameters)
    {
        var sb = new StringBuilder();
        var first = true;

        foreach (var param in parameters)
        {
            if (!first)
            {
                sb.Append("&");
            }

            sb.Append(param.Key);
            sb.Append("=");
            sb.Append(Uri.EscapeDataString(param.Value));

            first = false;
        }

        return sb.ToString

Imports System.Net.Http
Imports System.Security.Cryptography
Imports System.Text
Imports System.Text.Json

Public Class TwitterClient
    Private ReadOnly consumerKey As String
    Private ReadOnly consumerSecret As String
    Private ReadOnly accessToken As String
    Private ReadOnly tokenSecret As String

    Private ReadOnly httpClient As HttpClient

    Public Sub New(httpClient As HttpClient, consumerKey As String, consumerSecret As String, accessToken As String, tokenSecret As String)
        Me.httpClient = httpClient
        Me.consumerKey = consumerKey
        Me.consumerSecret = consumerSecret
        Me.accessToken = accessToken
        Me.tokenSecret = tokenSecret
    End Sub

    Public Async Function PostTweet(text As String) As Task
        Dim timstamp = CreateTimestamp()
        Dim nonce = CreateNonce()
        Dim body = JsonSerializer.Serialize(New With {Key .text = text})
        Dim uri = New Uri("https://api.twitter.com/2/tweets")

        Dim request = New HttpRequestMessage With {
            .RequestUri = uri,
            .Method = HttpMethod.Post,
            .Content = New StringContent(body, Encoding.ASCII, "application/json")
        }

        Dim signatureBase64 = CreateSignature(uri.ToString(), "POST", nonce, timstamp)

        request.Headers.Authorization = New System.Net.Http.Headers.AuthenticationHeaderValue("OAuth", $"oauth_consumer_key=""{Uri.EscapeDataString(consumerKey)}"",oauth_token=""{Uri.EscapeDataString(accessToken)}"", oauth_signature_method=""HMAC-SHA1"",oauth_timestamp=""{Uri.EscapeDataString(timstamp)}"",oauth_nonce=""{Uri.EscapeDataString(nonce)}"", oauth_version=""1.0"", oauth_signature=""{Uri.EscapeDataString(signatureBase64)}""")

        Dim response = Await httpClient.SendAsync(request)

        response.EnsureSuccessStatusCode()
    End Function

    Private Function CreateSignature(url As String, method As String, nonce As String, timestamp As String) As String
        Dim parameters = New Dictionary(Of String, String)()

        parameters.Add("oauth_consumer_key", consumerKey)
        parameters.Add("oauth_nonce", nonce)
        parameters.Add("oauth_signature_method", "HMAC-SHA1")
        parameters.Add("oauth_timestamp", timestamp)
        parameters.Add("oauth_token", accessToken)
        parameters.Add("oauth_version", "1.0")

        Dim sigBaseString = CombineQueryParams(parameters)

        Dim signatureBaseString = $"{method}&{Uri.EscapeDataString(url)}&{Uri.EscapeDataString(sigBaseString)}"

        Dim compositeKey = $"{Uri.EscapeDataString(consumerSecret)}&{Uri.EscapeDataString(tokenSecret)}"

        Using hasher = New HMACSHA1(Encoding.ASCII.GetBytes(compositeKey))
            Return Convert.ToBase64String(hasher.ComputeHash(Encoding.ASCII.GetBytes(signatureBaseString)))
        End Using
    End Function

    Private Function CreateTimestamp() As String
        Dim totalSeconds = (DateTime.UtcNow - New DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds

        Return Convert.ToInt64(totalSeconds).ToString()
    End Function

    Private Function CreateNonce() As String
        Return Convert.ToBase64String(New ASCIIEncoding().GetBytes(DateTime.Now.Ticks.ToString()))
    End Function

    Public Function CombineQueryParams(parameters As Dictionary(Of String, String)) As String
        Dim sb = New StringBuilder()
        Dim first = True

        For Each param In parameters
            If Not first Then
                sb.Append("&")
            End If

            sb.Append(param.Key)
            sb.Append("=")
            sb.Append(Uri.EscapeDataString(param.Value))

            first = False
        Next

        Return sb.ToString()
    End Function
End Class

OpenAI und PDF

Rambo sagte einmal “Das ist eine lange Geschichte” und war damit eine kurze Zusammenfassung der ganzen Rambo Filme.

Mit Open AI soll das nun möglich sein.
Also los aus nostalgischen Gründen werde ich ein Ajax mit einem Webservice machen – quisi quasi fast ein Micoservice 🙂
Also machen wir mal das Upload.

using Google.Protobuf.WellKnownTypes;
using Org.BouncyCastle.Asn1.Ocsp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.ServiceModel.Web;
using System.Web;
using System.Web.Script.Services;
using System.Web.Services;
using System.Web.Services.Description;
using System.Web.UI.WebControls;

namespace GPT3PDF
{
    /// <summary>
    /// Zusammenfassungsbeschreibung für AnaylseService
    /// </summary>
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [System.ComponentModel.ToolboxItem(false)]
    // Wenn der Aufruf dieses Webdiensts aus einem Skript zulässig sein soll, heben Sie mithilfe von ASP.NET AJAX die Kommentarmarkierung für die folgende Zeile auf. 
    [System.Web.Script.Services.ScriptService]
    public class AnaylseService : System.Web.Services.WebService
    {

        [WebMethod]
        public string HelloWorld()
        {
            return "Hello World";
        }

        [WebMethod]
        [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
        [WebInvoke(Method = "POST", ResponseFormat = WebMessageFormat.Json)]
        public PDFResponse Upload()
        {

            HttpFileCollection Files = HttpContext.Current.Request.Files;


            HttpPostedFile File = Files[0];
            byte[] pdfBytes;


            using (var binaryReader = new BinaryReader(File.InputStream))
            {
                pdfBytes = binaryReader.ReadBytes(File.ContentLength);
            }


            try
            {
                PDFAnalysis pDFAnalysis = new PDFAnalysis();
                string extract = pDFAnalysis.AnalyzePDF(pdfBytes);
              
                return new PDFResponse() { Message = extract };
                
            }
            catch (Exception ex)
            {
              //Error do something with it or leave it :)  

            }

            return new PDFResponse() { Message = "Error! " };

        }
    }

    public class PDFResponse
    {
        public string Message { get; set; } 
        
    }
}


Nun müssen wir nur noch den Text aus dem PDF bekommen. Zum Glück müssen wir das Rad nicht neu erfinden und können auf zum Beispiel PdfPig | Read and extract text and other content from PDFs in C# (port of PDFBox) (uglytoad.github.io) verwenden.
Das extrahieren geht ganz einfach:

  private string ExtractTextFromPDF(byte[] file)
        {

            string extractedText;
            using (var pdf = PdfDocument.Open(file))
            {
                var sb = new StringBuilder();
                for (var i = 0; i < pdf.NumberOfPages; i++)
                {
                    var page = pdf.GetPage(i + 1);
                    sb.Append(page.Text);
                }
                extractedText = sb.ToString();

            }
            return extractedText;
        }

Das File übergeben wir als Byte[] damit wir es nicht irgendwo auf dem Server zwischen speichern müssen.


Nostalgisches Upload und Text extrahieren ist doch einfach oder?

So als nächstes kommt der lustige Teil. Der Aufruf des OpenAI Api.

  private string AnalyzeTextWithChatGpt(string extractedText)
        {
            string responseContent = string.Empty;
            try
            {
                List<MessageObj> roles = new List<MessageObj>();
                roles.Add(new MessageObj() { role = "system", content = "Summarize the text" });
                roles.Add(new MessageObj() { role = "user", content = extractedText });
                //MessageObj[] msg;

                // Set up the OpenAI API client
                var content = new StringContent(
            JsonConvert.SerializeObject(new
            {
                model = "gpt-3.5-turbo",
                messages = roles


            }),
            Encoding.UTF8,
            "application/json");

                client.DefaultRequestHeaders.Clear();
                client.DefaultRequestHeaders.Add("Authorization", "Bearer " + chatGptApiKey);
                //var response = client.PostAsync("https://api.openai.com/v1/engines/davinci-codex/completions", content).Result;

                var response = client.PostAsync("https://api.openai.com/v1/chat/completions", content).Result;

                responseContent = response.Content.ReadAsStringAsync().Result;

                var explanation = JsonConvert.DeserializeObject<dynamic>(responseContent).choices[0].message.content;
                return explanation;
            }
            catch (Exception ex)
            {

                Exception exception = new Exception(responseContent, ex);
                throw exception;
            }


        }

Der ganze Source ist unter gpiwonka/GPT3PDF (github.com). Es fehlen natürlich eine gescheite Fehlerbehandlung und auch ein Verfahren, wenn das PDF zu groß ist. Hierzu müsste das PDF gesplittet werden und dann jeder einzelne Teil zusammengefasst werden. Live anzuschauen unter: gpt3pdf20230526121655.azurewebsites.net

.Net Maui Stacklayout und der Scrollviewer

In der aktuellen Version von .Net Maui gibt es einen lustigen Fehler. Dieser tritt bei Android und wenn ein Stacklayout scrollen lassen will. Das funktioniert beim ersten Mal super, jedoch passiert es das beim zweiten Mal die Seite einfach leer bleibt. Am besten ein Grid verwenden.

using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Xaml;

namespace MyApp
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();

            // Create the ScrollView
            var scrollView = new ScrollView();

            // Create the StackLayout
            var stackLayout = new StackLayout();

            // Add some content to the StackLayout
            for (int i = 1; i <= 20; i++)
            {
                var label = new Label
                {
                    Text = $"Item {i}",
                    FontSize = 20,
                    HorizontalOptions = LayoutOptions.Center
                };

                stackLayout.Children.Add(label);
            }

            // Set the StackLayout as the content of the ScrollView
            scrollView.Content = stackLayout;

            // Set the ScrollView as the content of the page
            Content = scrollView;
        }
    }
}

OpenAI und Visual Studio Extension

Als kleine Fingerübung dachte ich mir, dass eine Visual Studio Extension gut wäre die Code erklärt – dank OpenAI sollte das ja kein Problem sein.

War es auch nicht.

Zuerst installierte ich das Extensibilty Template Pack 2022 für Visual Studio 2022.
Danach konnte es beginnen:

Als erstes fügte ich ein Tool Window hinzu. Ein Toolwindow ist im Prinzip nichts anderes als eine XAML User control und eine Klasse die von BaseToolWindow ableitet

  public class ExplicareToolWindow : BaseToolWindow<ExplicareToolWindow>
    {
        public override string GetTitle(int toolWindowId) => "Explicare";

        public override Type PaneType => typeof(Pane);

        public override async Task<FrameworkElement> CreateAsync(int toolWindowId, CancellationToken cancellationToken)
        {

           var dte = await Package.GetServiceAsync(typeof(EnvDTE.DTE)) as EnvDTE80.DTE2;

            return new ExplicareToolWindowControl(dte,Package);
        }

        [Guid("b8381bcb-59d1-4b5c-9951-fdbfb44990c9")]
        internal class Pane : ToolWindowPane
        {
            public Pane()
            {
                BitmapImageMoniker = KnownMonikers.ToolWindow;
            }
        }
    }

ExplicareToolWindowControl(dte,Package); ist die eigentliche UserControl – in der wir beliebige UI Elemente einbauen können.
Ich habe jetzt nur einen Button eingebaut den ich “Explain to me” genannt habe.
Schnell und Schmutzig im Code-Behind ein Button Click Event gemacht.
Nun den Text aus dem Visual Studio Editor auslesen dies geschieht mit:

 var textSelection = (TextSelection)MyDTE.ActiveDocument.Selection;

                if (String.IsNullOrEmpty(textSelection.Text))
                {
                    TextDocument doc = (TextDocument)(MyDTE.ActiveDocument.Object("TextDocument"));

                    var p = doc.StartPoint.CreateEditPoint();
                    TextToExplain = p.GetText(doc.EndPoint);

                }
                else
                {
                    TextToExplain = textSelection.Text;

                }

So das schwierigste habe ich hinter mir nun noch einen Aufruf des OpenAI API aufrufen. Das geht eigentlich ganz einfach

  var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");


                var response = client.PostAsync("https://api.openai.com/v1/completions", content).Result;
                if (response.IsSuccessStatusCode)
                {
                    string responseStr = await response.Content.ReadAsStringAsync();

                    var result = JsonConvert.DeserializeObject<OpenAIResponse>(responseStr);
                    explained = result.choices[0].text;
                }
                else
                {
                    throw new BadRequestExecption();
                }

Den ganzen Source ist auf Git Hub gpiwonka/Explicare: A small extension for Visual Studio to better understand code using OpenAI. (github.com)

Gibt es auch als Extension: Explicare – Visual Studio Marketplace

Code Schnipsel: Service Provider

Dependency Injection macht das Leben einfacher auch in .NET MAUI. Hin und wieder ist es notwendig ein Service zu referenzieren das nicht über den Konstruktor kommt. Hier eine simple Lösung dafür:

public static class ServiceProvider
{
    public static TService GetService<TService>()
        => Current.GetService<TService>();

    public static IServiceProvider Current
        =>
#if WINDOWS10_0_17763_0_OR_GREATER
			MauiWinUIApplication.Current.Services;
#elif ANDROID
            MauiApplication.Current.Services;
#elif IOS || MACCATALYST
			MauiUIApplicationDelegate.Current.Services;
#else
			null;
#endif
}