<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
  <channel><title>Code Blog</title>
<description>Coding Tipps &amp; Tricks</description>
<generator>Miniblog.Core</generator>
<link>https://deceed.ch/</link>
<item>
  <title>Web-Applikation in VisualStudio 2022 langsam</title>
  <link>https://deceed.ch/code/blog/web-applikation-in-visualstudio-2022-langsam/</link>
  <description>&lt;p&gt;Beim Wechsel von VisualStudio 2019 auf 2022 hatte ich k&amp;uuml;rzlich den Fall, dass eine Web-Applikation sehr langsam lief. Jeder HTTP Request hatte eine Verz&amp;ouml;gerung von ca. 2 Sekunden.&lt;/p&gt;
&lt;p&gt;Nach langem Suchen fand ich heraus, dass dies an der Eisntellung "&lt;strong&gt;CSS Hot Reload&lt;/strong&gt;" lag. Sobald ich diese Einstellung deaktiviert hatte, lief alles wieder im gewohnten Tempo. Die Einstellung befindet sich unter Debug =&amp;gt; Options... =&amp;gt; Projects and Solutions =&amp;gt; ASP.NET Core&lt;/p&gt;
&lt;p&gt;&lt;img src="/code/Posts/files/pastedImage_637953720107941539.png" alt="" /&gt;&lt;/p&gt;</description>
  <author>deceed</author>
  <category>visual studio</category>
  <guid isPermaLink="false">https://deceed.ch/code/blog/web-applikation-in-visualstudio-2022-langsam/</guid>
  <pubDate>Sat, 06 Aug 2022 08:40:10 GMT</pubDate>
</item>
<item>
  <title>INSERT wenn nicht vorhanden</title>
  <link>https://deceed.ch/code/blog/insert-wenn-nicht-vorhanden/</link>
  <description>&lt;p&gt;Will man einen Datensatz in eine Tabelle einf&amp;uuml;gen falls er noch nicht existiert, behilft man sich normalerweise mit einem NOT EXISTS(...) Statement. Eleganter - und vor allem threadsafe mit einem atomaren Statement - geht es mit dem MERGE Befehl.&lt;/p&gt;
&lt;p&gt;Hier ein Beispiel anhand einer Tabelle &lt;strong&gt;Table1&lt;/strong&gt; mit Primary-Key &lt;strong&gt;Id&lt;/strong&gt; und den Spalten &lt;strong&gt;Name&lt;/strong&gt; und &lt;strong&gt;Wert&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class="language-sql"&gt;&lt;code&gt;MERGE INTO
  Table1 WITH (HOLDLOCK) t 
USING
  (VALUES (1, 'test', 6)) s([Id], [Name], [Wert])
ON
  t.[Id] = s.[Id]
WHEN NOT MATCHED THEN
  INSERT ([Id], [Name], [Wert]) VALUES (s.[Id], s.[Name], s.[Wert]);
&lt;/code&gt;&lt;/pre&gt;</description>
  <author>deceed</author>
  <category>sql</category>
  <guid isPermaLink="false">https://deceed.ch/code/blog/insert-wenn-nicht-vorhanden/</guid>
  <pubDate>Sat, 06 Aug 2022 08:31:38 GMT</pubDate>
</item>
<item>
  <title>Dateien von Publish ausschliessen</title>
  <link>https://deceed.ch/code/blog/dateien-von-publish-ausschliessen/</link>
  <description>&lt;p&gt;Projekt in Visual Studio "entladen" damit die .csproj Datei bearbeitet werden kann. Dann eine neue &lt;strong&gt;ItemGroup&lt;/strong&gt; einf&amp;uuml;gen:&lt;/p&gt;
&lt;pre class="language-markup"&gt;&lt;code&gt;&amp;lt;ItemGroup&amp;gt;
  &amp;lt;Content Update="appsettings*.json" CopyToPublishDirectory="Never" /&amp;gt;
  &amp;lt;Content Update="wwwroot/Posts/*.*" CopyToPublishDirectory="Never" /&amp;gt;
&amp;lt;/ItemGroup&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
  <author>deceed</author>
  <category>visual studio</category>
  <guid isPermaLink="false">https://deceed.ch/code/blog/dateien-von-publish-ausschliessen/</guid>
  <pubDate>Thu, 21 Apr 2022 18:38:02 GMT</pubDate>
</item>
<item>
  <title>"Bitte warten" Animation während der Generierung einer Datei anzeigen</title>
  <link>https://deceed.ch/code/blog/bitte-warten-animation-wahrend-der-generierung-einer-datei-anzeigen/</link>
  <description>&lt;p&gt;Oftmals m&amp;ouml;chte man ein "Bitte warten..." mit einer Animation anzeigen, w&amp;auml;hrend dem eine Datei auf dem Server dynamisch generiert und dann heruntergeladen wird.&lt;/p&gt;
&lt;p&gt;Das Problem hierbei ist, dass man nicht weiss wann der Download beginnt (Browser zeigt Download Dialog) und man die Animation wieder stoppen muss.&lt;/p&gt;
&lt;p&gt;Einen simplen aber effektiven Trick daf&amp;uuml;r habe ich auf folgender Seite gefunden: &lt;a href="http://gruffcode.com/2010/10/28/detecting-the-file-download-dialog-in-the-browser/"&gt;http://gruffcode.com/2010/10/28/detecting-the-file-download-dialog-in-the-browser/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Ganz einfach zusammengefasst funktioniert es folgendermassen:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ein Klick auf einen Download-Link zeigt eine Animation an und f&amp;uuml;hrt einen Postback aus&lt;/li&gt;
&lt;li&gt;Gleichzeitig wird clientseitig ein Timer gestartet der alle 500ms pr&amp;uuml;ft ob ein bestimmtes Cookie vorhanden ist&lt;/li&gt;
&lt;li&gt;Ist das Cookie vorhanden, wird die Animation wieder ausgeblendet&lt;/li&gt;
&lt;li&gt;Serverseitig wird die Datei die heruntergeladen werden soll dynamisch erstellt und direkt in die HTTP Response geschrieben&lt;/li&gt;
&lt;li&gt;Zus&amp;auml;tzlich wird auch das oben erw&amp;auml;hnte Cookie in die Response geschrieben&lt;/li&gt;
&lt;li&gt;Sobald der Browser die Antwort des Servers erh&amp;auml;lt, wird der Download-Dialog angezeigt und gleichzeitig das Cookie gespeichert&lt;/li&gt;
&lt;li&gt;Der clientseitigt JavaScript Timer l&amp;auml;uft dabei im Hintergrund weiter, erkennt das Cookie und blendet die Animation aus&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Beispielimplementierung einer ASP.NET MVC View:&lt;/p&gt;
&lt;pre class="language-markup"&gt;&lt;code&gt;&amp;lt;div&amp;gt;
    @using (Html.BeginForm("Download", "Home"))
    {
        @Html.Hidden("cookieValue")
        
    }
&amp;lt;/div&amp;gt;

@section scripts {
&amp;lt;script&amp;gt;
    var _tmr;
    $(function () {
        $('#btnDownload').click(function () {
            $('#cookieValue').val(Date.now().toString());
            $('#divAnimation').show();

            _tmr = window.setInterval(function () {
                var _str = 'dlc=' + $('#cookieValue').val();
                if (document.cookie.indexOf(_str) !== -1) {
                    $('#divAnimation').hide();
                }
            }, 500);
        });
    });
&amp;lt;/script&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Und die Implementierung des Controllers:&lt;/p&gt;
&lt;pre class="language-csharp"&gt;&lt;code&gt;[HttpPost]
public FileResult Download(string cookieValue)
{
    System.Threading.Thread.Sleep(5000);
    string content = "Das ist meine Datei.";            
    ControllerContext.HttpContext.Response.Cookies.Add(
        new HttpCookie("dlc", cookieValue));
    return File(System.Text.Encoding.ASCII.GetBytes(content),
        "text/plain", "dateiname.txt");
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Damit das Ganze auch mehrfach hintereinander funktioniert, muss jedes Mal auf einen neuen eindeutigen Wert des Cookies gepr&amp;uuml;ft werden. Dazu wird ein Timestamp generiert und in einem Hidden-Field im POST Request an den Server &amp;uuml;bermittelt. Der Server setzt dann das Cookie mit diesem Wert.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
  <author>deceed</author>
  <category>web</category>
  <category>download</category>
  <guid isPermaLink="false">https://deceed.ch/code/blog/bitte-warten-animation-wahrend-der-generierung-einer-datei-anzeigen/</guid>
  <pubDate>Tue, 12 Apr 2022 19:25:18 GMT</pubDate>
</item>
<item>
  <title>ASP.NET MVC Lokalisierung aus Datenbank</title>
  <link>https://deceed.ch/code/blog/aspnet-mvc-lokalisierung-aus-datenbank/</link>
  <description>&lt;p&gt;Mittels DataAnnotations auf den Model Properties lassen sich die Texte f&amp;uuml;r Labels und Validierungsfehler festlegen. Hardcodierte Texte und Texte in Ressourcedateien werden durch das MVC Framework bereits unterst&amp;uuml;tzt. Konkret sieht dass dann z.B. so aus:&lt;/p&gt;
&lt;pre class="language-csharp"&gt;&lt;code&gt;public class LoginModel
{
    [Required(ErrorMessage = "Benutzername darf nicht leer sein.")]
    [Display(Name = "Benutzername")]
    public string UserName { get; set; }

    [Required(ErrorMessageResourceName = "PasswordRequired", 
        ErrorMessageResourceType = typeof(Properties.Resources))]
    [Display(Name = "Password",
        ResourceType = typeof(Properties.Resources))]
    public string Password { get; set; }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In der View werden Anzeigename, Eingabefeld und Validierung folgendermassen ausgegeben:&lt;/p&gt;
&lt;pre class="language-markup"&gt;&lt;code&gt;@Html.LabelFor(m =&amp;gt; m.UserName):
@Html.TextBoxFor(m =&amp;gt; m.UserName)
@Html.ValidationMessageFor(m =&amp;gt; m.UserName)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Jetzt sollen die Texte aber aus einer Datenbank gelesen werden. In den DataAnnotations wird darum nur noch die Datenbank-ID der entsprechenden Texte angegeben:&lt;/p&gt;
&lt;pre class="language-csharp"&gt;&lt;code&gt;[Required(ErrorMessage = "27")]
[Display(Name = "42")]
public string UserName { get; set; }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Um diese IDs in Texte umzuwandeln sind noch einige Zusatzklassen notwendig. Dabei funktionieren Anzeigename (Display Attribut) und Validierung (Required Attribut) komplett unterschiedlich.&lt;/p&gt;
&lt;h3&gt;Lokalisierung des Anzeigenamens&lt;/h3&gt;
&lt;p&gt;Wir erstellen uns einen Metadata-Provider:&lt;/p&gt;
&lt;pre class="language-csharp"&gt;&lt;code&gt;public class MetadataProvider : AssociatedMetadataProvider
{
    protected override ModelMetadata CreateMetadata(
        IEnumerable&amp;lt;Attribute&amp;gt; attributes, Type containerType,
        Func&amp;lt;object&amp;gt; modelAccessor, Type modelType,
        string propertyName)
    {
        var metadata = new ModelMetadata(this,
            containerType, modelAccessor, modelType, propertyName);
        if (propertyName != null)
        {
            var displayAttr = attributes.
                OfType&amp;lt;DisplayAttribute&amp;gt;().FirstOrDefault();
            if (displayAttr != null)
            {
                int textId;
                if (Int32.TryParse(displayAttr.Name, out textId))
                {
                    // TODO: read text from database
                    metadata.DisplayName = ...
                }
            }
        }

        return metadata;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unsere Klasse muss jetzt noch in Global.asax.cs in der Application_Start() Methode registriert werden:&lt;/p&gt;
&lt;pre class="language-csharp"&gt;&lt;code&gt;ModelMetadataProviders.Current = new MetadataProvider();&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Lokalisierung der Validierungsfehler&lt;/h3&gt;
&lt;p&gt;Hierzu sind zwei zus&amp;auml;tzliche Klassen notwendig. Zuerst einmal der ModelValidatorProvider:&lt;/p&gt;
&lt;pre class="language-csharp"&gt;&lt;code&gt;public class LocalizableModelValidatorProvider :
    DataAnnotationsModelValidatorProvider
{
    protected override IEnumerable GetValidators(
        ModelMetadata metadata, ControllerContext context,
        IEnumerable attributes)
    {
        var validators = base.GetValidators(metadata,
          context, attributes);
        return validators.Select(validator =&amp;gt;
            new LocalizableModelValidator(validator, metadata,
                context)).ToList();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Diese Klasse sorgt daf&amp;uuml;r, dass wir unsere eigene Klasse "LocalizableModelValidator" zur Validierung des Models verwenden k&amp;ouml;nnen. Wie schon beim MetadataProvider muss auch der ModelValidatorProvider in Global.asax.cs in der Application_Start() Methode registriert werden:&lt;/p&gt;
&lt;pre class="language-csharp"&gt;&lt;code&gt;var provider = ModelValidatorProviders.Providers.FirstOrDefault(p =&amp;gt;
    p.GetType() == typeof(DataAnnotationsModelValidatorProvider));
if (provider != null)
{
    ModelValidatorProviders.Providers.Remove(provider);
}
ModelValidatorProviders.Providers.Add(
    new LocalizableDataAnnotationsModelValidatorProvider());&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Eine bereit vorhandener Provider vom Typ DataAnnotationsModelValidatorProvider wird dabei zuerst entfernt.&lt;/p&gt;
&lt;p&gt;Und schlussendlich noch die etwas umfangreichere Implementierung des ModelValidators:&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class="language-csharp"&gt;&lt;code&gt;public class LocalizableModelValidator : ModelValidator
{
    private readonly ModelValidator innerValidator;

    public LocalizableModelValidator(ModelValidator innerValidator,
        ModelMetadata metadata, ControllerContext controllerContext)
        : base(metadata, controllerContext)
    {
        this.innerValidator = innerValidator;
    }

    public override IEnumerable
        GetClientValidationRules()
    {
        var rules = innerValidator.GetClientValidationRules();
        var modelClientValidationRules = 
            rules as ModelClientValidationRule[] ?? rules.ToArray();
        foreach (var rule in modelClientValidationRules)
        {
            int textId;
            if (Int32.TryParse(rule.ErrorMessage, out textId))
            {
                // TODO: read text from database
                rule.ErrorMessage = ...
            }
        }
        return modelClientValidationRules;
    }

    public override IEnumerable
        Validate(object container)
    {
        var results = innerValidator.Validate(container);
        return results.Select(result =&amp;gt;
        {
            int textId;
            if (Int32.TryParse(result.Message, out textId))
            {
                // TODO: read text from database
                result.Message = ...
            }
            return new ModelValidationResult() {
                Message = result.Message };
        });
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
  <author>deceed</author>
  <category>web</category>
  <category>localization</category>
  <category>mvc</category>
  <guid isPermaLink="false">https://deceed.ch/code/blog/aspnet-mvc-lokalisierung-aus-datenbank/</guid>
  <pubDate>Tue, 12 Apr 2022 19:23:20 GMT</pubDate>
</item>
<item>
  <title>Berechtigungen für Application-Pool</title>
  <link>https://deceed.ch/code/blog/berechtigungen-fur-application-pool/</link>
  <description>&lt;p&gt;Um Berechtigungen, z.B. auf dem Dateisystem f&amp;uuml;r den Application-Pool zu setzen, kann der Benutzer &lt;strong&gt;IIS APPPOOL\&amp;lt;NameDesAppPools&amp;gt;&lt;/strong&gt; verwendet werden.&lt;/p&gt;</description>
  <author>deceed</author>
  <category>web</category>
  <category>iis</category>
  <guid isPermaLink="false">https://deceed.ch/code/blog/berechtigungen-fur-application-pool/</guid>
  <pubDate>Tue, 12 Apr 2022 19:19:03 GMT</pubDate>
</item>
<item>
  <title>SQL Cursor</title>
  <link>https://deceed.ch/code/blog/sql-cursor/</link>
  <description>&lt;p&gt;Als kleine Hilfe die Syntax eines T-SQL Cursors. (Ja, Cursors sind langsam und sollten wenn immer m&amp;ouml;glich nicht verwendet werden...)&lt;/p&gt;
&lt;pre class="language-sql"&gt;&lt;code&gt;DECLARE @Id INT
DECLARE cur CURSOR LOCAL FAST_FORWARD READ_ONLY FOR
    SELECT
        Id
    FROM
        ItemObjects
    WHERE
        Version_Id = @SourceVersionId


OPEN cur
FETCH NEXT FROM cur INTO @ItemObject_Id
WHILE @@FETCH_STATUS = 0
BEGIN


    FETCH NEXT FROM cur INTO @Id
END
CLOSE cur
DEALLOCATE cur&lt;/code&gt;&lt;/pre&gt;</description>
  <author>deceed</author>
  <category>sql</category>
  <category>cursor</category>
  <guid isPermaLink="false">https://deceed.ch/code/blog/sql-cursor/</guid>
  <pubDate>Tue, 12 Apr 2022 19:02:29 GMT</pubDate>
</item>
<item>
  <title>Diakritische Zeichen in SQL entfernen</title>
  <link>https://deceed.ch/code/blog/diakritische-zeichen-in-sql-entfernen/</link>
  <description>&lt;p&gt;Diakritische Zeichen - oder Diakritika - sind an Buchstaben angebrachte kleine Zeichen wie Punkte, Striche, H&amp;auml;kchen, B&amp;ouml;gen oder Kreise. Also z.B. die Punkte auf dem &amp;ouml;, oder der Akzent auf dem &amp;eacute;. In T-SQL gibt es eine einfache Methode diese Zeichen zu entfernen und durch die reinen Buchstaben zu ersetzen:&lt;/p&gt;
&lt;pre class="language-sql"&gt;&lt;code&gt;SELECT '&amp;agrave;&amp;eacute;&amp;ecirc;&amp;ouml;hello!' Collate SQL_Latin1_General_CP1253_CI_AI&lt;/code&gt;&lt;/pre&gt;</description>
  <author>deceed</author>
  <category>sql</category>
  <guid isPermaLink="false">https://deceed.ch/code/blog/diakritische-zeichen-in-sql-entfernen/</guid>
  <pubDate>Tue, 12 Apr 2022 19:01:46 GMT</pubDate>
</item>
<item>
  <title>Tabellenzeilen als String zurückgeben</title>
  <link>https://deceed.ch/code/blog/tabellenzeilen-als-string-zuruckgeben/</link>
  <description>&lt;p&gt;Das FOR XML Schl&amp;uuml;sselwort in T-SQL kann dazu missbraucht werden die Resultate eines Sub-Select zu einem String zusammenzusetzen.&lt;/p&gt;
&lt;p&gt;Schauen wir uns mal folgendes Datenmodell an:&lt;/p&gt;
&lt;p&gt;&lt;img src="/code/Posts/files/erm_637853868603182087.png" alt="erm.png" width="611" height="152" /&gt;&lt;/p&gt;
&lt;p&gt;Wir m&amp;ouml;chten eine Liste aller Produkte ausgeben, und in derselben Liste zeigen in welchen Farben ein Produkt lieferbar ist. Mit Hilfe des FOR XML Konstrukts l&amp;auml;sst sich das einfach in einer einzigen SQL Abfrage realisieren:&lt;/p&gt;
&lt;pre class="language-sql"&gt;&lt;code&gt;SELECT
  P.Name,
  (SELECT STUFF((
    SELECT ', ' + C.Name
    FROM 
      Color C
    INNER JOIN
      ProductColor PC ON PC.ColorId = C.ColorId
    WHERE
      PC.ProductId = P.ProductId
    FOR XML PATH('')), 1, 2, '')
  ) AS Colors
FROM
  Product P&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Diese Abfrage liefert folgendes Ergebnis:&lt;/p&gt;
&lt;p&gt;&lt;img src="/code/Posts/files/table2_637853868603240942.png" alt="table2.png" width="177" height="79" /&gt;&lt;/p&gt;</description>
  <author>deceed</author>
  <category>sql</category>
  <category>for xml</category>
  <guid isPermaLink="false">https://deceed.ch/code/blog/tabellenzeilen-als-string-zuruckgeben/</guid>
  <pubDate>Tue, 12 Apr 2022 19:01:00 GMT</pubDate>
</item>
<item>
  <title>JOIN auf Tabelle A oder Tabelle B</title>
  <link>https://deceed.ch/code/blog/join-auf-tabelle-a-oder-tabelle-b/</link>
  <description>&lt;p&gt;Eine Person hat eine Rechnungsdresse und optional eine Lieferadresse. Falls eine Lieferadresse vorhanden ist, soll diese ausgegeben werden, falls nicht die Rechnungsadresse. Dies k&amp;ouml;nnte man folgendermassen l&amp;ouml;sen:&lt;/p&gt;
&lt;pre class="language-sql"&gt;&lt;code&gt;SELECT
  A.AddressString
FROM
  Person P
INNER JOIN
  Address A ON A.AddressId = ISNULL(P.AddressId, P.DeliveryAddressId)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dummerweise kann die JOIN Bedingung mit der ISNULL Funktion dazu f&amp;uuml;hren, dass ein bestehender Index auf Address.Id nicht verwendet wird.&lt;/p&gt;
&lt;p&gt;Folgende Variante gibt zwar etwas mehr zu schreiben, die Performance ist jedoch um Welten besser:&lt;/p&gt;
&lt;pre class="language-sql"&gt;&lt;code&gt;SELECT
  ISNULL(A2.AddressString, A1.AddressString) AddressString
FROM
  Person P
INNER JOIN
  Address A1 ON A.AddressId = P.AddressId
LEFT OUTER JOIN
  Address A2 ON A.AddressId = P.DeliveryAddressId&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
  <author>deceed</author>
  <category>sql</category>
  <category>join</category>
  <guid isPermaLink="false">https://deceed.ch/code/blog/join-auf-tabelle-a-oder-tabelle-b/</guid>
  <pubDate>Tue, 12 Apr 2022 18:49:13 GMT</pubDate>
</item></channel>
</rss>