Beiträge für Februar, 2008

28Feb

Hin und wieder kommt es vor, dass man Dateien im BLOB-Format in einer Tabelle ablegt. Wenn man nun “schnell” auf diese Daten zugreifen will, steht man vor einem Problem, denn der Standard-SQL-Befehlssatz bietet keine Möglichkeit, Spalten mit Binärdaten zu füllen bzw. diese Daten wieder auszulesen und in eine Datei zu speichern.

Nun hat man nicht immer eine Entwicklungsumgebung zu Hand, um ein Programm zu schreiben, das per ADO oder ADO.NET diese Spalten befüllt. Auch der SQL-Server selbst bietet Möglichkeiten, um hier z.B. mittels Management-Studio tätig zu werden. Im Folgenden beschreibt ein Beispiel das generelle Vorgehen:

Als vorbereitende Maßnahme legt man eine Datenbank namens [pictures] an, die eine Tabelle mit dem Namen [images] enthält. Diese Tabelle enthält einfachheitshalber nur einen Primärschlüssel und das eigentliche Datenfeld. Wichtig hierbei ist, dass das Datenfeld den Datentyp [varbinary](max) hat.

CREATE DATABASE pictures
GO
USE pictures
GO  

CREATE TABLE [dbo].[images](
[imageid] [int] IDENTITY(1,1) NOT NULL,
[imageblob] [varbinary](max) NOT NULL
PRIMARY KEY CLUSTERED ([imageid]))
GO

Nun wird die Tabelle mittels INSERT befüllt. Mittels OPENROWSET in Kombination mit der Option BULK ist es möglich, Spalteninhalte als Binärstrom aus einer Datei (im Beispiel: bild1.jpg) zu lesen.

INSERT [dbo].[images]([imageblob])
SELECT BulkColumn
FROM OPENROWSET( BULK 'C:\bild1.jpg',
SINGLE_BLOB) as ExternalFile
GO

Führt man nun einen SELECT auf die Tabelle aus, sieht man, dass die Binärdaten gespeichert wurden.

image

Der umgekehrte Weg ist leider nicht ganz so einfach. Hierzu muss man das Programm bcp bemühen. Um bcp per SQL aufzurufen, muss der Server konfiguriert werden, dass Shell-Aufrufe möglich sind. Auch das ist (notwendige Berechtigungen vorausgesetzt) per SQL-Statement möglich:

EXECUTE sp_configure 'show advanced options', 1
RECONFIGURE WITH OVERRIDE
GO
EXECUTE sp_configure 'xp_cmdshell', '1'
RECONFIGURE WITH OVERRIDE
GO
EXECUTE sp_configure 'show advanced options', 0
RECONFIGURE WITH OVERRIDE
GO

Für den eigentlichen Export ist noch eine Konfigurationsdatei erforderlich. Diese beinhaltet die Formatbeschreibung. Wird diese nicht verwendet hat die resultierende Datei zwar die passende Größe, kann aber nicht gelesen werden, da bcp beim Export einige automatische Zeichenersetzungen vornimmt. Die Formatdatei kann auf Kommandozeilenebene mit bcp erstellt und dann angepasst oder direkt mittels Editor erstellt werden.

image

Die resultierende Datei sollte final wie dargestellt aussehen:

image

Nun kann mittels eines BCP-Befehls eine Datei erstellt werden. Wichtig hierbei ist, dass immer nur 1 Spalte und eine Zeile ausgegeben wird, da a) nur eine Datei geschrieben wird und b) dies auch in der eben erstellten Formatdatei so beschrieben ist.

exec master..xp_cmdshell ‘bcp “select [imageblob] from [pictures].[dbo].[images] where imageid = 1″ queryout C:\kopiebild1.jpg -T -S . -f c:\imageblob.fmt’

image

Die Datei liegt nun als kopiebild1.jpg wieder vor.

Technorati Tags: , , , , ,

27Feb

Als netter ASP.Net Programmierer mutet man dem Anwender wegen eines Klicks in eine CheckBox (in diesem Fall als Item einer CheckBoxList) kein Postback/Reload der Seite zu. Nun hat man ja aber vielleicht doch den Wunsch, das eine oder andere beim Klick zu erledigen - clientseitig per Javascript. Wenn möglich, will man den einzelnen CheckBoxList Items noch ein paar Attribute mitgeben, die in der Javascript-Funktion ausgewertet werden können und im Idealfall weiß die Javascript-Funktion auch gleich, welcher Item geklickt wurde. Das sollte eigentlich kein Problem sein - denkt man.

Also versucht man es erstmal ganz einfach mit folgendem Codeschnipsel im C# CodeBehind der Seite:

image

-> keine Reaktion :-(

Das gleiche noch einmal mit “onclick” anstelle “onchange” -> selbes Ergebnis :-(

Nun hilft alles nichts - man fängt an nachzudenken… Ein Blick in den Seitenquellcode ist ziemlich aufschlussreich: Die CheckBoxList Items haben einen <span> um den eigentlichen <input>-Tag des Items stehen - und in diesem landen unsere Attribute:

image

So funktioniert das also leider nicht. Ein kurzes Googlen zeigt nicht nur, dass andere das Problem auch schon hatten, sondern auch eine Lösung: Das “onclick” darf nicht an die einzelnen Items, sondern muss an die CheckBoxList gebunden werden:

image

Welcher Item geklickt wurde, kann man nun leider nicht mehr einfach an die Javascript-Funktion übergeben. Dies muss man über eine for-Schleife herausfinden. Die Attribute für die einzelnen Items können aber mit einem kleinen Trick trotzdem in der Javascript-Funktion abgefragt werden: Man legt sich zusätzlich zum Array mit den CheckBoxList Items (<input>-Tags) noch ein Array für die Attribute (<span>-Tags) an, das natürlich die gleiche Länge hat und über den gleichen Index abgefragt werden kann.

Alles in allem sieht die Javascript-Funktion in der Seite dann so aus:

function CheckboxChanged()
{
var checkBoxList = document.getElementById(’<%= SampleCheckBoxList.ClientID %>’);
//Array für die CheckBoxList Items
var checkboxes = checkBoxList.getElementsByTagName(’input’);
//Array für die Attribute der CheckBoxList Items
var checkboxAttributes = checkBoxList.getElementsByTagName(’span’);
for (var i=0; i<checkboxes.length;i++)
{
alert(checkboxes[i].checked);
alert(checkboxAttributes[i].code);
alert(checkboxAttributes[i].text);
}
}

Und der C# CodeBehind für das Hinzufügen des “onclick” und der Attribute so:

//set attributes for the items of SampleCheckBoxList (reqired for javascript function)
foreach (ListItem item in SampleCheckBoxList.Items)
{
item.Attributes.Add(”code”, item.Value);
item.Attributes.Add(”text”, item.Text);
}
//set the javascript function to be called at a checkbox item click
SampleCheckBoxList.Attributes.Add(”onclick”, “CheckboxChanged()”);

Das sollte wirklich kein Problem sein… :-)

Technorati Tags: , , , ,

27Feb

Auf der offiziellen Grails Homepage gibt es bereits ein sehr gutes How-To für die Integration von Grails in Eclipse. Da die Konfiguration aber nicht immer reibungslos verläuft und vor allem das Einbinden bestehender Grails Projekte in die IDE nach wie vor Probleme bereiten kann, sollen nachfolgend noch einmal die wichtigsten Schritte und Stolpersteine erläutert werden.

Groovy Plugin

Basis für die Grails Entwicklung ist das (noch) nicht ganz ausgereifte Groovy-Plugin für Syntax Highlighting, Basic Code Completion und Kompilierung der Klassen. Die aktuelle Version gibt es zum Download unter

http://dist.codehaus.org/groovy/distributions/update/

Eclipse Konfiguration

Nach der Installation des Plugins muss eine Variable zum GRAILS_HOME gesetzt werden (Window -> Preferences -> Java -> Classpath Variables -> New…).

Anschließend sollte man noch bei Disable Groovy Compiler Generating Class Files (Window -> Preferences -> Groovy) einen Haken setzen.

Projekte bearbeiten

Bestehendes Projekt
Falls das zu importierende Projekt bereits einmal in Eclipse bearbeitet wurde, dann lässt es sich leicht über Import... -> Existing Projects Into Workspace hinzufügen.

Neues Projekt
Ist das Projekt eben erst angelegt oder noch nie mit Eclipse bearbeitet worden, erkennt die IDE nicht ohne Weiteres das es sich um ein Groovy Projekt handelt und welchen Builder es einsetzen muss. Folgende Schritte führen zum Erfolg:

  1. File -> New -> Java Project
  2. Create project from existing source auswählen und Projekt-Root angeben
  3. Project Name muss den selben Namen haben wie das Root-Verzeichnis des zu importierenden Projektes
  4. Finish!

Sollte man trotzdem noch nicht in den Properties die Groovy Einstellungen sehen, hilft eventuell Add Groovy Nature im Kontextmenü anzuklicken. Sind in der Package Ansicht die entsprechenden Grails Source Folder nicht als Source Folder angezeigt, sondern lediglich als normale Ordner, muss man folgende Einträge in der Datei .classpath ergänzen:

...
<classpathentry kind="src" path="src/java"/>
<classpathentry kind="src" path="src/groovy"/>
<classpathentry kind="src" path="grails-app/conf"/>
<classpathentry kind="src" path="grails-app/controllers"/>
<classpathentry kind="src" path="grails-app/domain"/>
<classpathentry kind="src" path="grails-app/services"/>
<classpathentry kind="src" path="grails-app/taglib"/>
<classpathentry kind="src" path="test/integration"/>
<classpathentry kind="src" path="test/unit"/>

Applikation starten

Bevor man den Server das erste Mal starten kann, muss folgender Befehl im Projekt-Root auf der Kommandozeile ausgeführt werden:    grails dev package

Um nun den Server zu starten, klickt man auf Run As -> Open Run Dialog... und im erscheinenden Fenster auf Java Application -> <Projektname>. Dort sind bereits alle Einstellungen gesetzt und ein Klick auf Run startet den Jetty Server. Beenden kann man die Applikation indem man einfach den Prozess terminiert.

Grails Targets aufrufen

Zwar ist es nun problemlos möglich während der Entwicklung den Jetty Server zu kontrollieren, jedoch stellt uns das System keine Möglichkeit zur Verfügung, auch andere Targets aufzurufen. Der Aufwand, beim Erstellen und Generieren von Domains, Controllern und initialen Views die Konsole zu bemühen, bleibt also bestehen. Um dem Problem zu begegnen, nutzen wir die Funktionen des External Tools Dialog. Im nachfolgenden Beispiel wird eine Domain-Class generiert:

  1. Öffnen der Konfiguration
    Run -> External Tools -> Open External Tools Dialog...
  2. Anlegen einer neuen Konfiguration
    Nach Anlegen einer neuen Konfiguration (Program -> New), müssen folgende Einträge gesetzt werden:
    Name
    Hier wird der Name der Konfiguration eingetragen, in diesem Fall soll er identisch mit dem Standard-Aufruf sein: grails create-domainclass
    Location
    Da die grails.bat für die Ausführung der Targets zuständig ist, wird der absolute Pfad in dieses Feld eingetragen. In diesem Fall ist das C:\Development\Application\grails-1.0-RC4\bin\grails.bat
    Working Directory
    Da alle grails Kommandos im Wurzelverzeichnis eines Projektes abgesetzt werden, muss hier der absolute Pfad zum aktuellen Project-Root gesetzt werden. Um die Konfiguration etwas generischer zu gestalten, lassen wir Eclipse den Pfad des aktuell selektierten Projekts eintragen. Dazu genügt die folgende Variable: ${project_loc}
    Arguments
    Im Beispiel der create-domain-class müssen folgende Argumente übergeben werden: create-domainclass ${string_prompt:the new domain class} Das erste Argument (create-domainclass) ist der Aufruf des Targets, das zweite Argument erzeugt ein Popup indem man die Domain-Klasse angibt. Ein zusätzliches Argument in der Variable ${string_prompt} manipuliert den Abfragedialog, sodass der Nutzer weiss was er angeben muss.

    External Tools Configuration

  3. Speichern der Konfiguration
    Nach dem Selektieren eines Grails-Projektes kann man über das Icon “External Tools Dialog” die Konfiguration abrufen. Es erscheint die Aufforderung zur Eingabe einer neuen Domain und anschließend wird in der Eclipse-Console der entsprechende Aufruf angezeigt. Mithilfe dieses Verfahrens lassen sich nun leicht auch andere Targets konfigurieren und von der Eclipse IDE aus starten.
Fehlermeldungen

Falls beim Ausführen eines Targets der Fehler Error starting Sun’s native2ascii ausgegeben wird, so kann dies zwei Ursachen haben:

  1. Die JAVA_HOME Variable verweist nicht auf das JDK sondern die JRE, eine Änderung des Pfadverweises sollte hier Abhilfe schaffen
  2. In manchen Fällen kommt diese Meldung auch bevor man initial grails dev package auf der Konsole ausgeführt hat
Technorati Tags: , , ,

25Feb

Manchmal hat man ein Problem, und wenn man es dann gelöst hat, ist die Lösung so einfach, dass man es fast nicht glauben mag. So ging es mir mit folgender Aufgabe:

Gegeben ist ein String, der ein XML Document repräsentiert:

<books>
<book author=”Meier”>Lexikon der Meierei</book>
<book author=”Muster”>Patterns in der Schneiderstube</book>
</books>

Diesen String möchte man (möglichst browserunabhängig, wenigstens soll es aber für Internet Explorer und Firefox funktionieren) per Javascript in ein XML DOM Object parsen, um dieses dann, wie auch immer, weiter zu verwenden. Und das geht ganz einfach so:

function MyParseXml(xmlString)
{
var xmlDoc;
//for IE
if (window.ActiveXObject)
{
xmlDoc = new ActiveXObject(”Microsoft.XMLDOM”);
xmlDoc.async = “false”;
xmlDoc.loadXML(xmlString);
}
//for Mozilla, Firefox, Opera, etc.
else if (document.implementation && document.implementation.createDocument)
{
var parser = new DOMParser();
xmlDoc = parser.parseFromString(xmlString,”text/xml”);
}
var x=xmlDoc.getElementsByTagName(’book’); //oder wasauchimmer
}

Fertig! :-)

Technorati Tags: , ,

23Feb

Eine gute Übersicht, wie am Besten verteilte Standorte mit SharePoint versorgt werden, hat das SharePoint Team in seinem Blog veröffentlicht.

http://blogs.msdn.com/sharepoint/archive/2008/02/22/deploying-microsoft-office-sharepoint-server-2007-geographically.aspx

Die meiner Meinung nach beiden wichtigsten Szenarien habe ich mal rausgegriffen.

1. Aufbau einer zentralen Farm.

Jeder Standort greift über das Netz auf die Inhalte und Dienste der zentralen Farm zu. Auch Suchanfragen laufen über das Netz.

Vorteile Nachteile
kein Crawling von Inhalten über das Netz (WAN) notwendig u.U. langsamerer Zugriff und Datenübertragung, Abhängig von der Bandbreite
zentrale Administration einer Farm u.U. langsamere Suchanfragen, Abhängig von der Bandbreite
zentrales Backup  

 

2. Aufbau einer zentralen Farm mit zusätzlichen regionalen Farmen.

Zu der zentralen Farm kommen an jedem Standort eine regionale Farm hinzu. Und genau hier gibt es jetzt mehrere Möglichkeiten. Hier muss man sich überlegen, ob das SharePoint Poral in erster Linie dazu dient, Inhalte (CMS, Dokumente) bereitzustellen oder für Collaboration genutzt werden soll. http://technet2.microsoft.com/Office/en-us/library/cbccbc4d-e560-4b92-89d0-56f49e7f3f4b1033.mspx?mfr=true

Bei der Suche kann die zentrale Farm die komplette Indizierung aller Farmen über das Netz übernehmen. Dies ist aber möglicherweise sehr langsam und endet in Timeouts. Bei einer schnellen Anbindung ist so aber natürlich ein zentraler Index aller Farmen vorhanden. Benutzer müssen so aber immer zur zentralen Farm navigieren, um eine Suche zu starten.

Bei den Suchergebnissen (Plan for global enterprise search) wird dann aber auch nicht zwischen lokalem Inhalt (der regionalen Farm) und der zentralen Farm unterschieden. Ein Mitarbeiter erkennt nicht gleich, ob das gefundene Dokument auf dem Server seiner Niederlassung oder der zentrale liegt. Es wird dabei auch nicht priorisiert, also dass Suchergebnisse aus der Niederlassung zuerst angezeigt werden.

Die Alternative, dass jede Farm nur ihren eigenen Index hat, halte ich dagegen nicht für sinnvoll, da so nicht in einem Schritt über das ganze Unternehmen gesucht werden kann, was ja dem Gedanken des Enterprise Search widerspricht.

Aber es gibt ja immer eine Möglichkeit:

Die zentrale Farm erstellt über den SSP einen Index über alle Farmen im Verbund. Zusätzlich hat jede regionale Farm einen eigenen Index. Über Suchbereiche kann der Benutzer auswählen, wo er suchen will. Dies reduziert die Suchanfragen über den gesamten Index der zentralen Farm, die Performance der Suchanfragen steigt.

Vorteile Nachteile
lokale Suche möglich, schneller nach wie vor muss der Gesamtindex von der zentralen Farm über das WAN erstellt werden
lokale Relevanz wird berücksichtigt Globale Suche nach wie vor von der Bandbreite abhängig
weniger Traffic über das WAN es müssen mehrere Indizes verwaltet werden

13Feb

Im Mitteldeutschen Barcamp in Jena fand eine Session zum Thema SEO (Search Engine Optimization) statt. In dieser wurden Prinzipien diskutiert um ein gutes Ranking für eine Webseite insbesondere unter google zu erzielen. Ein paar Ideen möchte ich an dieser Stelle zusammenfassen.

1. Jede Seite, die durch eine Suche auffindbar sein soll, sollte nur einmal vorhanden sein. Wenn mehrere Einstieg URLs vorhanden sind, so sollten diese, am besten per Refer, auf die Hauptseite verweisen.

2. Jede Seite sollte unter einem permanenten Link erreichbar sein. Der Link sollte sich für die Webseite auch in Zukunft nicht mehr ändern sowie unabhängig seinvon irgendwelchen Sessionvariablen oder vorhergehenden Klickpfaden.

3. HTML Konformität ist für google nicht zwingend erforderlich. Jedoch hat die Beachtungein dieser den Vorteil, dass google die Seite vollständig parsen kann. Treten Fehler beim Parsen der Seite auf, so kann das dazu führen, dass nicht alle Links betrachtet werden, oder die Seite nicht in einem Suchergebnis erscheint, da der Content nicht analysiert wurden konnte.

4. Die Reihenfolge von Content innerhalb einer HTML Seite spielt eine untergeordnete Rolle. Scheitert das Parsen der HTML Seite in der Mitte oder Ende so können nur die Links und Texte, die bis dahin gescannt wurden, verarbeitet werden. Beinhaltet der Anfang eine für die Suche unwichtige Subnavigation, so ist dies eine unnötige Fehlerquelle. Es kann daher sinnvoller sein, den Content immer an den Anfang an einer Seite zu platzieren und stattdessen Navigation o. ä. ans Ende.

4. Das meta Tag description kann verwendet werden, um das Preview in dem Suchergebnis zu definieren. Beim nicht Verwenden des Tags versucht die Searchengine sich eine Description herauszusuchen. Die Vorhersagbarkeit des Ergebnisses stößt dabei gern an ihre Grenzen.

5. Für unwichtige Webseiten, die nicht in einem Suchergebnis erscheinen sollen, kann einem Link ein no-follow mitgegeben werden. Siehe dazu auch hier http://googleblog.blogspot.com/2005/01/preventing-comment-spam.html

6. Google abonniert RSS Feeds. D. h. erscheint eine neue Seite in einem RSS Feed, so scannt Google diese Seite nach neuen Suchbegriffen schneller als wenn es beim Scannen des Webs über diese neue Seite stolpern würde.

7. Während der Session tauchte die Frage auf, welche Bedeutung Headline Tags (h1, h2 usw.) haben, und welche Relevanz diese für die Suche haben. Die überraschende Antwort war, dass h2 oder h3 unter Umständen mehr Bedeutung haben als h1. Dies ist sicherlich abhängig von dem Aufbau der Gesamtseite. Der Hintergrund - sinnfrei wiedergeben - ist, dass h1 mitunter als Gesamtüberschrift angesehen wird, die für den konkreten Content der Seite nur eine untergeordnete Bedeutung hat.

8. Wo platziert man einen Link auf seine Seite, um in einem Suchranking von google weiter oben zu erscheinen? Die Antwort ist an sich ganz banal, man sucht sich die Seite(n) heraus, die für die Zielsuchbegriffe am höchsten gerankt sind, und platziert dort, irgendwie, einen Link. Das “irgendwie” ist dabei der Kreativität des Einzelnen überlassen.

Alle Angaben sind ohne Gewähr. :-)

Technorati Tags: , , , , ,