Blog Page

Javascript als Resource verpacken/entpacken (Arbeiten mit Embedded Resources)

In meinen Utils-Bibliotheken, habe ich einige WebControls, welche auf div. Javascript-Bibliotheken zugreifen. Unter anderem z.B. jQuery oder jQueryUI aber eben auch selbst geschnitzte Bibliotheken.

Nachdem es bei Projektstart immer recht mühsam ist, die Files zusammenzutragen, habe ich mir überlegt, diese als Resource meinen Utils hinzuzufügen und zur Laufzeit zu entpacken und registrieren.

Zunächst fügt man die benötigten Files dem Projekt hinzu. Wichtig ist es, dass man in den Eigenschaften des Files die Option „Build Action -> Embedded Resource“ einstellt. Nur damit wird das File in der DLL verpackt! Die Größe der DLL vergrößert sich dadurch natürlich. Man sollte daher abwiegen, welche Resourcen man wirklich auf diesem Wege hinzufügen will.

Um die verpackten Resourcen nun zur Laufzeit zu entpacken, habe ich in meiner PageBase (Ableitung von Page, verwende ich in den Code-Behinds statt Page) einen Event hinzugefügt (im Page_Load()-Event), welcher eine entsprechende Funktion aufruft. Nachfolgend der Sourcecode:

[sourcecode language=“csharp“]
private static string _fnJquery = String.Empty;
/// <summary>
/// Gets the fn cookie.
/// </summary>
/// <value>The fn cookie.</value>
public static string fnJquery
{
get
{
if (string.IsNullOrEmpty(_fnJquery))
return ConfigUtils.ReadStringFromAppSettings("BasicLib_FileName_JqueryScript", "jquery-1.4.1.js");
else
return _fnJquery;
}
set
{
_fnJquery = value;
}
}
/// <summary>
/// Gets a value indicating whether this instance is basic lib enabled.
/// </summary>
/// <value>
/// <c>true</c> if this instance is basic lib enabled; otherwise, <c>false</c>.
/// </value>
public static bool IsBasicLibEnabled
{

get {
if(System.Web.HttpContext.Current.Session["BasicLib_Enabled"] == null)
return ConfigUtils.ReadBoolFromAppSettings("BasicLib_Enabled", false);
else
{
return Convert.ToBoolean(System.Web.HttpContext.Current.Session["BasicLib_Enabled"].ToString());
}
}
set
{
System.Web.HttpContext.Current.Session.Add("BasicLib_Enabled", value);
}
}

/// <summary>
/// Registers the basic lib.
/// </summary>
/// <param name="p">The p.</param>
public static void RegisterBasicLib(Page p)
{
if (IsBasicLibEnabled)
{
// write resources to stream
if (!System.IO.File.Exists(StringUtils.AddSlashIfNeeded(ClientScriptFilePath) + fnJquery))
{
ExtractAndSaveResource();
}

// register script
if (!p.ClientScript.IsClientScriptIncludeRegistered(fnJquery))
p.ClientScript.RegisterClientScriptInclude(fnJquery,
StringUtils.AddSlashIfNeeded(ClientScriptFilePath) +
fnJquery);

}

}

/// <summary>
/// Extracts the and save resource.
/// </summary>
public static void ExtractAndSaveResource()
{
if (IsBasicLibEnabled)
{
const string nameSpace = "Utils.Web.Javascript.BasicLib.Resources";

Stream sJquery = ResourceUtils.LoadResourceFromExecutingAssembly(nameSpace, fnJquery,
ResourceUtils.TargetAssembly.
ExecutingAssembly);
// save streams
string absClientScriptFilePath = System.Web.HttpContext.Current.Server.MapPath(ClientScriptFilePath);

FileUtils.SaveContentToFile(fnJquery, absClientScriptFilePath, sJquery);
}
}
[/sourcecode]

Achtung! Es muss unbedingt der genaue Namespace verwendet werden!! Ansonsten wird die Resource nicht gefunden und eine Exception geworfen.
Das Ergebnis sieht wie folgt aus und lässt keinen Unterschied zum „normalen“ hinzufügen einer JS-Datei erkennen:

Viel Spaß!

Querystrings sicher verschlüsseln

Wer kennt das nicht? Verwendet man zu viele QueryStrings, welche evtl. auch noch einfach entschlüsselbar sind, fangen die Anwender an, herumzubasteln und selbst die Adresszeile zu verändern. Was macht man nun, wenn man Querystrings verwenden muss? Eine Variante wäre es jetzt, diese einfach zu verschlüsseln.

Dabei bin ich auf einen interessanten Artikel von Jonathan Wood gestossen (http://www.blackbeltcoder.com/Articles/security/encrypting-query-arguments). Sein Codesnippet hat Einzug in meine Helper-Utils gefunden. Nachfolgend noch ein einfaches Anwendungsbeispiel:

Querystring erzeugen:
[sourcecode language=“csharp“]
protected void Button1_Click(object sender, EventArgs e)
{
EncryptedQueryString args = new EncryptedQueryString();
args["arg1"] = "val1";
args["arg2"] = "val2";
Response.Redirect(String.Format("page.aspx?args={0}", args.ToString()));
}
[/sourcecode]

Querystrings entschlüsseln:
[sourcecode language=“csharp“]
protected void Page_Load(object sender, EventArgs e)
{
EncryptedQueryString args =
new EncryptedQueryString(Request.QueryString["args"]);
Label1.Text = string.Format("arg1={0}, arg2={1}", args["arg1"], args["arg2"]);
}
[/sourcecode]

Viel Spaß!

Performance einer Anwendung testen

Nachfolgend eine kleine Helper-Klasse, mit der man ganz einfach die Performance einer Applikation testen kann.

[sourcecode language=“csharp“]
using System;
using System.Runtime.InteropServices;

/// <summary>
/// Basic PerformanceTimer by using Kernel32.dll.
/// </summary>
/// <example>
/// PerformanceTiming.Start();
/// double seconds = PerformanceTiming.Stop();
/// </example>
public class PerformanceTiming
{
/// <summary>
/// Queries the performance counter.
/// </summary>
/// <param name="nPfCt">The n pf ct.</param>
/// <returns></returns>
[DllImport("KERNEL32")]
public static extern bool QueryPerformanceCounter(ref Int64 nPfCt);

/// <summary>
/// Queries the performance frequency.
/// </summary>
/// <param name="nPfFreq">The n pf freq.</param>
/// <returns></returns>
[DllImport("KERNEL32")]
public static extern bool QueryPerformanceFrequency(ref Int64 nPfFreq);

protected Int64 Mi64Frequency;
protected Int64 Mi64Start;

public PerformanceTiming()
{
QueryPerformanceFrequency(ref Mi64Frequency);
Mi64Start = 0;
}

/// <summary>
/// Starts the performance counter.
/// </summary>
public void Start()
{
QueryPerformanceCounter(ref Mi64Start);
}

/// <summary>
/// Ends the performance counter and returns the number of seconds the counter has measured.
/// </summary>
/// <returns></returns>
public double End()
{
Int64 i64End = 0;
QueryPerformanceCounter(ref i64End);
return ((i64End – Mi64Start) / (double)Mi64Frequency);
}
}
[/sourcecode]

Viel Spaß!

Webservice Reference unter Visual Studio 2010 hinzufügen

Microsoft empfiehlt eigentlich seit .NET 3.5 (und Visual Studio 2008) statt der „alten“ Webservice Referenzen, für Webservice Service Referenzen hinzuzufügen.
Die Option bzw. der Menüpunkt eine Webservice Referenz hinzuzufügen, ist ebenfalls nicht mehr (wie bisher) auffindbar.
Das ist bei Neuentwicklungen kein Problem, stellt jedoch bei alten Entwicklungen, die man nicht großartig umschreiben möchte, durch aus ein Problem dar.

Nachfolgend ein paar Screenshots, die zeigen, wies trotzdem noch möglich ist:

Cheers,
Chris aka. alles geht, man muss nur wissen wie!

Random String / Integer erzeugen

Auch diesmal nur ein kurzes Codesnippet, welches ich gerade beruflich im Einsatz hatte:

[sourcecode language=“csharp“]
/// <summary>
/// Gets a random integer value.
/// </summary>
/// <param name="intMin">The int min.</param>
/// <param name="intMax">The int max.</param>
/// <returns></returns>
public static int GetRandomInteger(int intMin, int intMax)
{
//Create a new instance of the class Random
Random randomNumber = new Random();
//Generate a random number using intMin as the minimum and intMax as the maximum
int i = randomNumber.Next(intMin, intMax);

// destroying the object is neessary, otherwise if calling the GetRandomInteger function
// several times you will always get the same result!!!
randomNumber = null;
return i;
}

/// <summary>
/// Gets the random string.
/// </summary>
/// <param name="intLenghtOfString">The int lenght of string.</param>
/// <returns></returns>
public static string GetRandomString(int intLenghtOfString)
{
//Create a new StrinBuilder that would hold the random string.
StringBuilder randomString = new StringBuilder();
//Create a new instance of the class Random
Random randomNumber = new Random();
//Create a variable to hold the generated charater.
//Create a loop that would iterate from 0 to the specified value of intLenghtOfString
for (int i = 0; i <= intLenghtOfString; ++i)
{
//Generate the char and assign it to appendedChar
Char appendedChar = Convert.ToChar(Convert.ToInt32(26 * randomNumber.NextDouble()) + 65);
//Append appendedChar to randomString
randomString.Append(appendedChar);
}
//Convert randomString to String and return the result.
return randomString.ToString();
}
[/sourcecode]

Chris aka. schönes Wochenende!

In Registry schreiben und lesen

Nachfolgend ein kurzes Code-Snippet zum lesen und schreiben in die Registry.

Viel Spaß damit!

[sourcecode language=“csharp“]
/// <summary>
/// Writes the registry.
/// </summary>
/// <param name="parentKey">The parent key.</param>
/// <param name="subKey">The sub key.</param>
/// <param name="valueName">Name of the value.</param>
/// <param name="value">The value.</param>
public static void WriteRegistry(RegistryKey parentKey, String subKey, String valueName, Object value)
{
RegistryKey key;
Console.WriteLine(@"parentKey: " + parentKey.ToString());
Console.WriteLine(@"subKey: " + subKey);

try
{
key = parentKey.OpenSubKey(subKey, true);
if (key == null) //If the key doesn’t exist.
key = parentKey.CreateSubKey(subKey);

//Set the value.
key.SetValue(valueName, value);

Console.WriteLine(@"Value:{0} for {1} is successfully written.", value, valueName);
}
catch (Exception e)
{
Console.WriteLine(@"Error occurs in WriteRegistry: " + e.Message + "(" + e.Source + ")");
}
}

/// <summary>
/// Reads the registry.
/// </summary>
/// <param name="parentKey">The parent key.</param>
/// <param name="subKey">The sub key.</param>
/// <param name="valueName">Name of the value.</param>
static void ReadRegistry(RegistryKey parentKey, String subKey, String valueName)
{
RegistryKey key;

try
{
key = parentKey.OpenSubKey(subKey, true);
if (key == null) //If the key doesn’t exist.
throw new Exception("The registry key doesn’t exist");

//Get the value.
var value = key.GetValue(valueName);

Console.WriteLine(@"Value:{0} for {1} is successfully retrieved.", value, valueName);
}
catch (Exception e)
{
Console.WriteLine(@"Error occurs in ReadRegistry" + e.Message);
}
}
[/sourcecode]

Cheers,
Chris aka. diesmal kein Roman!

Backup-Batch-Job

Man kennt das ja. Immer wieder möchte man seine Daten zusätzlich, zum hoffentlich bestehenden und funktionierendem Backup-System, manuell sichern.
Und sei es nur aus dem Zweck, nicht länger auf die Kollegen zu warten, bis diese das Backup ausgegraben haben.

Für diesenZweck habe ich mir einen Batch-Job eingerichtet, der sämtliche Dateien (rekursiv) aus meinem „D:work“ -Verzeichnis auf einen Networkshare kopiert.

[sourcecode language=“powershell“]

@echo off
xcopy D:work*.* Y:Backup /e/s/y/d
FOR /F "tokens=1,2 delims=:" %%d IN (‚date /t‘) DO set dat=%%d
FOR /F "tokens=1,2 delims=:" %%i IN (‚time /t‘) DO set zeit=%%i-%%j
rename Y:Backup "%dat%%zeit%.Safe"
FOR /F "skip=2 tokens=*" %%c IN (‚DIR /O-D /B D:Batch*safe‘) DO (rd Y:Backup"%%c" /s/q)

[/sourcecode]

Viel Spaß damit!

Chris aka. jetzt gibt es keine Ausreden mehr!

„Deadlock detected“ gefolgt von „Only one usage of each socket address is normally permitted“

Viel Spaß beim Lesen des ersten „richtigen“ Artikels auf diesem Blog.

Wie es im Betrieb einer großen Anwendung eben ab und zu vorkommt, wird ein Patch des Lieferanten eingespielt. Ein Patch der eigentlich eine Menge Unannehmlichkeiten im täglichen Betrieb lösen sollte. Die Betonung sollte auf „lösen sollte“ liegen. Denn was unausweichlich geschehen musste, geschah. Zunächst funktionierte die Applikation noch einwandfrei. Doch am nächsten Tag, als mehrere hundert Konzernmitarbeiter begonnen haben mit der Anwendung zu arbeiten, begannen die Probleme.

Im gefühlten 15-Minuten-Takt verabschiedete sich die Applikation, der Application Pool der betroffenen Webseite startete sich neu.
Ein Blick in den Eventviewer zeigte folgende zwei Einträge:

…gefolgt von …

Nachdem die Applikation, wie bereits beschrieben, von einem Lieferanten stammt und wir keine Source-Einsicht haben, gestaltete sich die Recherche entsprechend schwierig. Wir stießen u.a. auf die folgenden Artikel:

http://support.microsoft.com/kb/821268

http://blogs.msdn.com/b/perfdude/archive/2008/09/21/web-server-deadlocks-aspnet-isapi-dll.aspx

http://blogs.msdn.com/dgorti/archive/2005/09/18/470766.aspx

In beiden Artikeln gibt es mehrere Lösungsansätze, die wir nach und nach probierten.

1. Ansatz: maxWorkerThreads und maxIoThreads
Beide Einstellungen sind in der machine.config zu treffen. Per Default sind diese beiden Settings jeweils auf 20 gesetzt. MaxWorkerThreads stellt ein, wieviele Threads ein Worker-Process offen halten darf/kann. Das gilt PRO WorkerProcess und nicht für alle WorkerProcesse bzw. global. Nachdem wir mittels TaskManager festgestellt haben, dass der betreffende WorkerProcess bis zu 45 Threads geöffnet hat, dachten wir hier auf der richtigen Spur zu sein. Microsoft empfiehlt bei stark frequentierten Webseiten nämlich jeweils die Einstellung 100. Ich werde den Artikel, aus dem ich diese Information habe nachreichen, sobald ich ihn wieder finde.  Diese Einstellung brachte leider keine Verbesserung. Die Applikation stürzte immer noch ab, jedoch benötigte der IIS länger um den Deadlock zu erkennen.

2. Ansatz: MaxUserPort und TIME_WAIT (Registry Settings; 3. Link).
Beide Einstellungen befinden sich in der Systemregistry unter HKLMSystemCurrentControlSetServicesTcpipParameters. Beim Setting sind „DWORD“s.  Mit MaxUserPort  kann man einstellen, wie groß die „Dynamic Port Range“ ist, was gleichbedeutent mit der Anzahl an Socket Addresses ist. Hier ist ein Wert zwischen 5000 (default) und 65534 zulässig. Um „auf der sicheren Seite zu sein“ und nach Empfehlung des Lieferanten haben wir hier gleich den Höchstwert verwendet.

TIME_WAIT stellt ein, wie lange die offene Connection im Status „TIME_WAIT“ verweilt. Ob diesese Setting für einen interessant ist, kann man ganz einfach mit dem Command-Shell-Befehl „netstat“ herausfinden. Befinden sich hier viele Connections im Status „TIME_WAIT“ so kann man diesen Wert herabsetzen. Default-Wert ist hier 4 Minuten. Im angesprochenen Artikel vorgeschlagen diesen Wert auf 30 Sekunden herabzusetzen.

Zu beachten gilt, dass beide Werte erst nach NEUSTART der Maschine übernommen werden!!

Nachdem auch diese Einstellungen nicht zur Verbesserung beitrugen und auch der Lieferant keine Lösung oder Lösungsansatz mehr parat hatte, haben wir uns entschieden nochmals zu patchen und eine „noch neuere“ Version einzuspielen. Die Probleme konnten dadurch gelöst werden, die eigentliche Ursache des Problems wird zumindest für uns im verborgenen bleiben.

Abschließend sollte erwähnt werden, dass der problembehaftete Patch ein halbes Jahr auf zwei Qualitätssicherungssystemen (erfolgreich) getestet wurde. Erst unter der Last von mehreren hundert Anwendern traten die Probleme auf…

Cheers,
Chris aka. der Weg ist das Ziel.