Archive for the 'Allgemein' Category

Software-Projekte sind keine Videorekorder!

Folgende Mail möchte ich der Community jetzt nicht vorenthalten:

Diesen Satz (nur Bezogen auf das Bau-Wesen) hab ich gestern (zu spaeter Stunde) im Fernsehen gesehen.
Damit ist gemeint, dass es die Leute gewohnt sind, in den Supermarkt zu gehen und sich einen Videorekorder zu kaufen. Dieser wird perfektioniert produziert und Massenhaft verkauft.
Genau das funktioniert bei Software-Projekten eben nicht (oder wie die Person gemeint hat, beim Bau), weil eben jedes Projekt eine Individual-Anforderung ist. Und waehrend des Projektes die Probleme auftauchen geloescht werden muessen.

Ich fand den Satz einfach nur gut und wollte den hier mal publizieren ;-)

Software-Projekte sind keine Videorekorder!

In diesem Sinne, viel Spass :)

Nachtrag: TypeConversions reloaded

Ich hatte ja hier schon etwas zu eigenen impliziten und expliziten Konvertierungen geschrieben. Dazu noch ein Nachtrag zur Konvertierungen zwischen zwei eigenen Datentypen. Wir haben wieder unseren Datentypen MyDataType:

class MyDataType
{
	public string Wert { get; set; }
	public static implicit operator string(MyDataType toConvert)
	{ return toConvert.Wert; }

	public static implicit operator MyOtherDataType(MyDataType toConvert)
	{
		int Converted;
		if (Int32.TryParse(toConvert.Wert, out Converted))
			return new MyOtherDataType() { Wert = Converted };
		else
			return null;
	}
}

Dazu definieren wir uns einen eigenen zweiten Datentypen:

class MyOtherDataType
{
	public int Wert { get; set; }
	public static implicit operator MyDataType(MyOtherDataType toConvert)
	{ return new MyDataType() { Wert = Convert.ToString(toConvert.Wert) }; }

	public static implicit operator string(MyOtherDataType toConvert)
	{
		if (toConvert != null)
			return Convert.ToString(toConvert.Wert);
		else
			return "toConvert is null";
	}
}

Ich habe jetzt allerdings die Konvertierungen zu SecureString rausgenommen, und implizite Konvertierungen zwischen diesen beiden Datentypen hinzugefügt. Prinzipiell muss nach dem Keyword “operator” lediglich der Zieldatentyp angegebene werden, und als Methodenparameter der Quelldatentyp. Das kann man so vereinfachen:

public static implicit/explicit operator Zieldatentyp (Quelldatentyp pInstance) { Konvertierungsimplementation }

Die Main Methode unserer Konsolenanwendung sieht demnach dann so aus:

static void Main(string[] args)
{
	MyDataType myTypeInstance = new MyDataType() { Wert = "hallo" };
	Log("myTypeInstance: " + myTypeInstance);

	//ist null, da "hallo" kein gültiger int ist
	MyOtherDataType myOtherTypeInstance = myTypeInstance;
	Log("myOtherTypeInstance nach ungültigem int in myTypeInstance: " + myOtherTypeInstance);

	myTypeInstance.Wert = "10";
	myOtherTypeInstance = myTypeInstance;
	Log("myOtherTypeInstance nach gültigem int in myTypeInstance: " + myOtherTypeInstance);
	Console.Read();
}

“Warum verwendet der jetzt nicht “string.Format” zum Logging?!” werden sich wohl einige Fragen. Die Antwort ist einfach: string.Format(“Text”, parameter) erwartet als parameter “objects”. Würden wir nun unsere Instanz “myOtherTypeInstance” übergeben, würde unsere Typumwandlung nach string nicht ausgeführt werden… :)
Damit sollte es jetzt eigentlich klar sein wie die eigene Konvertierungen direkt innerhalb eines Datentyps implementiert werden können.

TypeConversions – Oder: Wer bist du denn?

Wir haben eine eigene Klasse welche unseren Datentyp darstellt. Diese möchten wir jetzt mit ein paar Typkonvertierungen versehen, allerdings nicht über Extension methods oder andere ausgelagerte Funktionen. Unser Datentyp sieht wie folgt aus:

class MyDataType
{
	public string Wert { get; set; }
}

Der Einfachheit halber nur eine simple Property. Normalerweise hat man ja sehr viele Unterschiedliche Properties in seinem Datentyp. Wir möchten jetzt daraus z.B. einen SecureString machen, oder auch einfach den Wert loggen. Man könnte jetzt natürlich einfach schreiben:

MyDataType myTypeInstance = new MyDataType() { Wert = "hallo" };

Log(myTypeInstance.Wert);

Wobei unsere Logfunktion einen string-Parameter entgegennimmt der dann – wo auch immer – protokolliert wird.

Diese Verwendung ist aber spätestens bei komplexeren Datentypen einfach nur unhandlich. Deshalb wollen wir einfach schreiben können:

Log(myTypeInstance);

oder um daraus einen SecureString zu machen:

SecureString mySecString = (SecureString)myTypeInstance;

Die meisten Implementierungen (die ich bisher gesehen habe) setzen auf eine extra ausgelagerte Funktion oder Extension methods. Die ausgelagerten Funktionen haben einen Nachteil: Sie konvertieren einfach mal alles was man reinwirft. Dieses Verhalten haben wir aber eigentlich nicht bezweckt, oder wir wollen das einfach nicht, da wir ja beim Zeitpunkt des Codens die Verwendungsmöglichkeiten einfach nicht alle im Überblick haben kann oder auch nicht testen kann.
Gehen wir also über Extension methods. OK – damit haben wir im Idealfall schonmal den Basistyp eingegrenzt. Nachteil allerdings hier: Extension methods funktionieren erst ab .Net 3.5… Also stehen wir wieder am Anfang.

Wie gehen wir also nun an die Sache ran?
Die Lösung ist liegt auf der Hand und ist eigentlich so simpel, das es mich immer wieder verwundert das es so selten eingesetzt wird:
Innerhalb unseres Datentyps definieren wir zwei neue statische Funktionen zur Typumwandlung hinzu:

public static implicit operator string(MyDataType toConvert) { }

public static explicit operator SecureString(MyDataType toConvert) { }

Diese beiden Funktionen führen nun unsere gewünschten impliziten wie expliziten Konvertierungen aus.
Wir füllen diese beiden Methoden also wie folgt auf:

public static implicit operator string(MyDataType toConvert)
{
	return toConvert.Wert;
}
public static explicit operator SecureString(MyDataType toConvert)
{
	SecureString mySecureString = new SecureString();
        foreach (char item in toConvert.Wert)
	{
		mySecureString.AppendChar(item);
	}
	return mySecureString;
}

Das heißt, dadurch gewinnen wir saubereren Code und klarere Strukturen, da die Umwandlungen direkt innerhalb der Typen definiert werden können. Sie sind also genau da, wo sie auch hingehören.
Das komplette Testprogamm hier:

class MyDataType
{
	public string Wert { get; set; }
	public static implicit operator string(MyDataType toConvert)
	{
		return toConvert.Wert;
	}
	public static explicit operator SecureString(MyDataType toConvert)
	{
		SecureString mySecureString = new SecureString();
		foreach (char item in toConvert.Wert)
		{
			mySecureString.AppendChar(item);
		}
		return mySecureString;
	}
}

class Program
{
	static void Main(string[] args)
	{
		MyDataType myTypeInstance = new MyDataType() { Wert = "hallo" };
		Log(myTypeInstance);
		SecureString mySecString = (SecureString)myTypeInstance;
		Log(mySecString.ToString());
		Console.Read();
	}
	static void Log(string message)
	{
		Console.WriteLine(message);
	}
}

To yield or not to yield

Das yield Keyword ist ein in C# häufig unterschätztes Keyword. Die MSDN – bzw. die C# Spezifikation – sagt dazu:

Def: Wird in einem Iterator-Block verwendet, um für das Enumerationsobjekt einen Wert zu liefern oder um das Ende der Iteration zu signalisieren.
Der Ausdruck wird ausgewertet und als ein Wert an das Enumerationsobjekt zurückgegeben. Der expression muss implizit in den yield-Typ des Iterators konvertierbar sein.
Die yield-Anweisung kann nur innerhalb eines iterator-Blocks stehen, der als Rumpf einer Methode, eines Operators oder eines Accessors verwendet wird. Der Rumpf solcher Methoden, Operatoren oder Accessoren unterliegt folgenden Einschränkungen:

- Unsichere Blöcke werden nicht zugelassen.
- Parameter für die Methode, den Operator oder den Accessor dürfen nicht ref oder out sein.

Eine yield-Anweisung darf nicht in einer anonymen Methode stehen. Weitere Informationen finden Sie unter Anonyme Methoden (C#-Programmierhandbuch).
Bei Verwendung mit expression darf eine yield return-Anweisung nicht in einem catch-Block oder einem try-Block stehen, der mehr als eine catch-Klausel enthält.

Wie sieht das jetzt in der Praxis aus?
Das Beispiel in der MSDN ist jetzt nicht besonders praxistauglich, daher hier mal eine andere Verwendungsweise:

class Program
    {
        static void Main(string[] args)
        {
            string searchedCRMId = "de7ea510-8e55-de11-a210-0017084f5232";
            Console.WriteLine("Starting to search for user with CRM_ID '{0}'...", searchedCRMId);
            foreach (UserProfile item in GetProfiles("http://your_ssp_host/"))
            {
                string currentID = Convert.ToString(item["CRMUserID"].Value);
                if (string.IsNullOrEmpty(currentID))
                    continue;

if (currentID.Equals(searchedCRMId, StringComparison.CurrentCultureIgnoreCase))
                {
                    Console.WriteLine("Found searched user!\nAccountname: {0}", item[PropertyConstants.AccountName].Value);
                    break;
                }
            }
            Console.WriteLine("Done");
            Console.ReadLine();
        }

static IEnumerable<UserProfile> GetProfiles(string SharedServiceProviderUrl)
        {
            SPWebApplication webapp = SPWebApplication.Lookup(new Uri(SharedServiceProviderUrl));
            UserProfileManager upm = new UserProfileManager(ServerContext.GetContext(webapp));
            foreach (UserProfile profile in upm)
            {
                yield return profile;
            }
        }
    }

Diese ConsoleApplication iteriert über alle vorhandenen UserProfile und vergleicht eine CustomProperty mit einem Suchwert. In der GetProfiles() Methode wird allerdings mittels “yield return profile” das aktuelle Profil aus der Auflistung zurückgegeben und die übergeordnete foreach-Schleife weiter bearbeitet. Das spart zum einen Performance und zum anderen Speicher.
Zu beachten ist dabei allerdings, das bei folgender Verwendung immer das erste Item “verschwindet”:

IEnumerable<UserProfile> profileList = GetProfiles("http://your_ssp_host/");
foreach (UserProfile item in profileList)
{
 string currentID = Convert.ToString(item["CRMUserID"].Value);
        if (string.IsNullOrEmpty(currentID))
         continue;

if (currentID.Equals(searchedCRMId, StringComparison.CurrentCultureIgnoreCase))
        {
         Console.WriteLine("Found searched user!\nAccountname: {0}", item[PropertyConstants.AccountName].Value);
                break;
        }
}

Die Ursache ist recht einfach:

Typische Implementation Implementation mit yield
1. Funktionsaufruf 1.Funktionsaufruf
2.Funktion wird ausgeführt und gibt eine Liste zurück 2.Aufrufer fordert ein Item an
3.Aufrufer arbeitet mit der Liste 3.Nächstes Item aus der Auflistung wird zurückgegeben
  4.Sprung zu 2. solange wie der Aufrufer kein Item mehr haben will (break im Caller) oder keine Items mehr vorhanden sind

Was man letztendlich verwendet ist natürlich immer von der Anforderung abhängig. :)

=(2,02095)^36 €

In etwa so viel haben Italienische Beamter an der Schweizer Grenze bei zwei Männern beschlagnamt, wie der Spiegel berichtet.

Sachen gibts…  :)

The following error was encountered while reading module ‘Microsoft.SharePoint’: Could not resolve type: ObjectModel.

Bei Projekten mit einer Referenz auf die “Microsoft.SharePoint.dll” laufen keine Code Metric Analysen. Der Grund ist nach einer kurzen Google-Recherche auch schnell gefunden: Ein Bug im Visual Studio 2008.
Ein Workaround gibts auch dazu: Man muss lediglich eine Referenz auf die “C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\ISAPI\Microsoft.SharePoint.Security.dll” einbinden, dann klappts auch mit der Analyse.

SharePoint: Alle Sharedservice Provider ermitteln

Ab und an kommt mal wieder die Anforderung einen Timerjob an die Webapplication des Sharedserviceproviders anzuhängen. Bei einem SSP ist das auf den ersten Blick auch kein Problem: Entweder die URL als Konfigurationseinstellung verdraten oder über eine Settingspage in der Zentraladministration konfigurierbar machen. Spätestens wenn der Kunde aber mehr als einen SSP im Einsatz hat, und der Timerjob an alle SSPs angehängt werden soll, steht man vor einem Problem: Entweder immer wieder Einstellungen hinzufügen oder man überlegt sich eine generelle Lösung.

Jeder SSP verfügt über eine Administrationsoberfläche die mit einer bestimmten Sitedefinition erzeugt wurde. Diese findet man im 12er Hive unter TEMPLATE\SiteTemplates\OSRV. Wie kann man sich nun eine Liste aller vorhandenen SSPs erzeugen? Zum Beispiel mit folgender Funktion:

        public static List GetSharedServiceProviders()
        {
            List result = new List();
            SPFarm farm = SPFarm.Local;
            SPWebService service =
                farm.Services.GetValue();
            foreach (SPWebApplication webApp in
                service.WebApplications)
            {
                if (webApp.Sites.Count == 0)
                    continue;
                using (SPSite rootsite = webApp.Sites[0])
                {
                    if (rootsite.AllWebs.Count == 0)
                        continue;
                    using (SPWeb rootweb = rootsite.RootWeb)
                    {
                        if (rootweb.WebTemplateId == 40)
                            result.Add(rootsite.Url);
                    }
                }
            }
            return result;
        }

Eigentlich ist es simpel: Man holt sich aus der Farm alle WebApplications, öffnet die Rootsite und prüft mit welchem SiteTemplate das Rootweb angelegt wurde. Ist die TemplateID gleich der TemplateID der SSP Sitedefinition, dann ist es ein Sharedservice Provider.

Integrated Authentication funktioniert nicht bei Websites mit Host Headern

Symptome:

  • Der IIS fragt nach Credentials, auch wenn man an einer Domain eingeloggt ist und die Anmeldeinformationen durchgereicht werden sollten
  • Anmeldung an einer Seite mit keiner User-Password-Kombination möglich 
  • Man bekommt einen HTTP 404.1 beim Seitenzugriff 
  • Authentication events im Security EventLog:  Event ID 537 : An error occurred during logon

Obwohl alles richtig konfiguriert ist kommt man dennoch nicht auf die Seite.

Die Ursache ist eine Sicherheitsfunktion zur Überprüfung von Loopbacks, die so genannte Reflection Attacks verhindern soll. Daher schlägt die Authentifizierung fehl, wenn der verwendete vollqualifizierte Domänenname oder der benutzerdefinierte Hostheader nicht mit dem lokalen Computernamen übereinstimmt.

Methode 1: Disable Loopback Checking

  1. regedit öffnen
  2. HKLM\System\CurrentControlSet\Control\Lsa öffnen
  3. Neuen DWORD value ”DisableLoopbackCheck” und auf 1 setzen
  4. Reboot

Methode 2: Host Namen angeben

  1. regedit öffnen
  2. HKLM\system\CurrentControlSet\Control\Lsa\MSV1_0 öffnen
  3. Neuen multi-string value ”BackConnectionHostNames” anlegen
  4. Host header eintragen
  5. Reboot

Meine Top 3 Tools

Nach langer Zeit melde ich mich auch mal wieder zurück, heute mit meinen Top 3 Tools zur Entwicklung.

  1. Da ich mit und für SharePoint entwickle nutze ich den WSPBuilder. Dieses Tool nimmt einem viel manuelle Arbeit bei der Entwicklung ab (Anhängen an die IIS Prozesse zum Debuggen, viele Itemtemplates, leichte Konfigurierbarkeit und große Erweiterbarkeit)
  2. Sandcastle in Verbindung mit dem Sandcastle Help File Builder. Diese beiden Tools ersparen sehr viel Arbeit bei der Generierung von ansprechenden Dokumentationen. Man muss nicht mehr selbst die HTML Files erstellen und in ein CHM packen, einfach in die Post-Build Action der Release Version diese beiden Tools integrieren und man bekommt zu jedem neuen Release eine komplette Dokumentation für den Kunden. Einfach, praktisch, gut.
  3. Die Team Foundation Power Tools ! Unverzichtbar bei der Entwicklung, vor allem aber beim Projektstart und der Projektvorbereitung. Wer den Team Foundation Server nutzt und diese Tools nicht einsetzt ist selber Schuld. :)

Transactional NTFS: Ein totes Pferd?

Mit Vista bzw. dem Server 2008 ist das neue Transactional NTFS ebenfalls verfügbar. Höchste Zeit also, sich dieses Stück Technik auch mal anzusehen. Worum es sich dabei genau handelt möchte ich an dieser Stelle nicht großartig durchkauen, das kann man sich hier (Wikipedia) oder hier (Transactional NTFS Blog von Microsoft) durchlesen. Kurz gesagt, man kann damit Dateioperationen innerhalb von Transaktionen durchführen. Das ganze ist an die Transaktionen bei Datenbanksystemen angelegt; Man gruppiert also mehrere Arbeitsschritte in einer “LUW” (Logical Units of Work). Beim Abschließen einer Transaktion kann man dann die Ergebnisse entweder in den Datenspeicher übernehmen (Commit) oder verwerfen (Rollback).

Ein super Beispiel wie man das unter .NET verwendet findet man im Channel9. Hier gibt es eine komplette Solution mit verschiedenen Szenarien (TxF Only, TxF and SQL, TxF and WCF). Und auch die MSDN liefert einige Informationen zum Thema. Das hört sich ja alles schonmal ganz praktisch an.

Das KO-Kriterium für diese Technik ist allerdings folgendes:

Unsupported Scenarios
TxF does not support the following transaction scenarios:
- Transactions on network volumes, for example on file shares. TxF is not supported by the CIFS/SMB protocols.
- Transacted operations against files cached by client-side caching.

Nun, bei diesen Einschränkungen fällt mir kein praktisches Einsatzszenario für diese Technik ein… Soviel dann also zum Thema “Totes Pferd”.