code it

Martins Tech Blog

FileUpload-Control in Update-Panels verwenden

Versucht man ein asp:FileUpload innerhalb eines AJAX-UpdatePanels zu verwenden, so ist während der asynchronen Postbacks im FileUpload immer keine Datei enthalten, obwohl man eine ausgewählt hat.

Einfache und doch wirkungsvolle Lösung für das Problem ist: Der Klick auf den Upload-Link oder -Button muss als PostBackTrigger eingerichtet werden.

<asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional">  
    <Triggers>  
        <asp:PostBackTrigger ControlID="LinkButton1" />  
    </Triggers>  

    <ContentTemplate>  
        <asp:FileUpload ID="FileUpload1" runat="server" />  
        <asp:LinkButton ID="LinkButton1" runat="server" Text="hochladen" OnClick="LinkButton1_OnClick" />   
    </ContentTemplate>  
</asp:UpdatePanel>

Treffen der SharePoint-UserGroup

Review
Gestern fand das 2. Treffen der SharePoint-UserGroup Dresden organisiert von Communardo und T-Systems in den Räumen der T-Systems statt. Thema war diesmal Nintex Workflow. Sebastian Gerling von Spirit Link hat eine kurze Einleitung zum Thema gegeben und an einigen praktischen Beispielen gezeigt, welche Möglichkeiten bestehen.

Im Anschluss daran wurde von allen die Gelegenheit zum Erfahrungsaustausch genutzt. Als besondere Überraschung stand ein Surface-Tisch bereit.

Die Folien zur Präsentation stehen in Sebastians Blog zur Verfügung.

Wie geht es weiter?
Der nächste Termin wird nach derzeitiger Planung im April stattfinden. Themenvorschläge nehmen wir immer gern in der Diskussion in unserer Xing-Gruppe entgegen. Vermutlich wird das nächste Treffen etwas mehr entwicklungslastig.

Eigene Wörterbücher für die Rechtschreibkontrolle in der Codeanalyse definieren

Nachdem ich für mein Projekt CodeAnalysis aktiviert hatte und dabei auch die Rechtschreibkontrolle angehakt hatte, bekam eine ganze Menge an Rechtschreibfehlern angezeigt, die sich hauptsächlich darauf bezogen, dass z.B. der Firmenname in Namespace-Namen enthalten waren und dieser angeblich falsch geschrieben war. Die im ersten Moment einfachste Lösung dafür ist: Rechtsklick -> Suppress in project suppression file. Dumm an dieser Lösung ist nur, dass mit jedem neuen Namespace das Problem wieder auftritt. Aus diesem Grund hab ich mich heute mit der etwas intelligenteren Lösung befasst und ein Custom Dictionary in das Projekt eingebunden. Ein guter Blogeintrag in dem steht wie man dazu vorgeht findet sich im Visual Studio Code Analysis Team Blog.

Treffen der SharePoint UserGroup Dresden

Am 19.02.2009 findet das nächste Treffen der SharePoint User Group in den Räumen der T-Systems MMS statt.

Thema wird dieses Mal Nintex Workflow sein. Eingeladen sind drei Experten für Nintex Workflow. Sebastian Gerling von der Spirit Link GmbH wird eine Einführung in Nintex geben, die mit 2 Demos und einem Anwendungsszenario untermauert werden. Markus Alt und Steven Schmitt von der Data One GmbH stehen als technische Experten zur Verfügung. So ist das ganze Spektrum vom Anwender bis zum Entwickler abgedeckt.

Daten aus der aspnet_profile Tabelle per SQL auslesen

Profildaten werden in der Tabelle aspnet_profile abgelegt. Allerdings werden diese dort nicht relational gespeichert, sondern in einem Textfeld. Möchte man diese dann wieder abfragen, stößt man sehr leicht an seine Grenzen.

Die Daten liegen im Feld PropertyValuesString wobei das Feld PropertyNames definiert, an welcher Stelle die Daten des jeweiligen Feldes beginnen und wie lang die Information ist. So deutet der Eintrag "FirstName:S:0:7:" darauf hin, dass die Eigenschaft FirstName bei Zeichen 0 beginnt, 7 Zeichen lang ist und vom Typ String. Ein Text dieses Formats wird im Folgenden Elementtoken genannt.

Gewappnet mit diesem Wissen kann man nun die Informationen aus PropertyNames extrahieren und damit die Daten aus PropertyValuesString auslesen. Möchte man das alles in einer SQL-Query machen, so kommt leicht ein sehr komplexes Konstrukt zustande. Aus diesem Grund ist man besser beraten, sich benutzerdefinierte Skalarfunktionen zu definieren, die die Zeichenkettenoperationen übernehmen.

Die erste Funktion wird benötigt, um aus einem Text des oben genannten Formats einen bestimmten Eintrag zu extrahieren.

CREATE FUNCTION dbo.GetSplitElement(
   @index int,
   @text nvarchar(4000),
   @separator nvarchar(1))
RETURNS nvarchar(4000)
AS
BEGIN
 -- if input parameters are invalid return null
 IF LEN(ISNULL(@text, '')) = 0
    OR ISNULL(@index, 0) < 1
    OR @index > LEN(@text) - LEN(REPLACE(@text, @separator, '')) + 1
    RETURN NULL

 DECLARE @pos int, @currentindex int

 SET @pos = 1
 SET @currentindex = 1

 -- iterate through elements of string
 WHILE @currentindex < @index
 BEGIN
   SET @pos = CHARINDEX(@separator, @text, @pos) + 1
   SET @currentindex = @currentindex + 1
 END

 RETURN SUBSTRING(@text, @pos, CHARINDEX(@separator, @text + @separator, @pos) - @pos)
END

Der Aufruf gibt das jeweilige Element des übergebenen Elementtokens zurück - am Beispiel:

  • dbo.GetSplitElement(1, 'FirstName:S:0:7:', ':') ergibt 'FirstName'
  • dbo.GetSplitElement(2, 'FirstName:S:0:7:', ':') ergibt 'S'
  • dbo.GetSplitElement(3, 'FirstName:S:0:7:', ':') ergibt '0'
  • dbo.GetSplitElement(4, 'FirstName:S:0:7:', ':') ergibt '7'

Eine weitere Funktion übernimmt die restliche Arbeit:

CREATE FUNCTION dbo.GetProfileElement
(
   @fieldname nvarchar(100),
   @propertyfields nvarchar(4000),
   @propertyvalues nvarchar(4000)
)
RETURNS nvarchar(4000)
AS
BEGIN
  
 -- if input parameters are invalid --> return null
 IF LEN(ISNULL(@fieldname, '')) = 0
    OR LEN(ISNULL(@propertyfields, '')) = 0
    OR LEN(ISNULL(@propertyvalues, '')) = 0
    --RETURN NULL

 DECLARE @fieldnametoken nvarchar(50)
 DECLARE @fieldnamestart int, @fieldnameend int, @valuestart int, @valuelength int

 -- search for startindex of token in propertyfields
 SET @fieldnamestart = CHARINDEX(@fieldname + ':S',@propertyfields, 0)
 -- field name not found --> return null
 IF @fieldnamestart = 0 RETURN NULL
 -- @fieldnamestart is now startindex of the fieldtoken

 -- search for the fourth ":" -> it defines the end of the token
 DECLARE @i int
 SET @i = 0
 SET @fieldnameend = @fieldnamestart

 WHILE ((@i < 4) AND (@fieldnameend > 0))
 BEGIN
   SET @fieldnameend = CHARINDEX(':',@propertyfields, @fieldnameend + 1)
   SET @i = @i +1
 END

 IF @fieldnameend = 0 RETURN NULL

 SET @fieldnametoken = 
 SUBSTRING(@propertyfields, @fieldnamestart,@fieldnameend-@fieldnamestart+1)


 SET @valuestart = CAST(dbo.GetSplitElement(3, @fieldnametoken,':') as int)
 SET @valuelength = CAST(dbo.GetSplitElement(4, @fieldnametoken,':') as int)

 IF @valuelength = 0 RETURN ''

 RETURN SUBSTRING(@propertyvalues, @valuestart+1, @valuelength)
END

Sie sucht zunächst das Elementtoken mit dem gegebenen Feldnamen und ermittelt dann anhand der Informationen Startindex und Länge den gespeicherten Wert. Nun kann ganz einfach mit dieser Funktion ein SELECT-Statement auf die Tabelle aspnet_profile ausgeführt werden:

SELECT dbo.aspnet_Users.UserId, dbo.aspnet_Users.UserName,
dbo.GetProfileElement('FirstName',
    dbo.aspnet_Profile.PropertyNames,dbo.aspnet_Profile.PropertyValuesString) FirstName,
dbo.GetProfileElement('LastName',
    dbo.aspnet_Profile.PropertyNames,dbo.aspnet_Profile.PropertyValuesString) LastName
FROM dbo.aspnet_Profile INNER JOIN dbo.aspnet_Users ON
dbo.aspnet_Users.UserId = dbo.aspnet_Profile.UserId

Babylonisches SharePoint

Wer sich etwas intensiver mit SharePoint beschäftigt, stolpert früher oder später über Sprachabhängigkeiten. So lassen sich beispielsweise Templates nicht verwenden, weil sie für eine andere Sprache entwickelt wurden oder Listenspalten heißen plötzlich anders.

Besonders interessant wird das Ganze, wenn man sich dann noch auf einem SharePoint mit installierten Language-Packs befindet, wo jedes Web eine andere Sprache haben kann.

Die Sprache eines Webs lässt sich recht einfach ermitteln, wenn man ein Objekt vom Typ SPWeb (Namespace: Microsoft.SharePoint) instanziiert hat. In diesem Fall kann man die Eigenschaft Language verwenden: 

Diese beinhaltet die LCID der Sprache in der das Web erstellt wurde.

Etwas spannender gestaltet es sich dann schon, wenn man in Erfahrung bringen möchte, in welcher Sprache der Server installiert wurde, denn danach richten sich z.B. die Namen der Search Scopes. Allerdings sucht man in der Definition von SPServer (Namespace: Microsoft.SharePoint.Administration) vergeblich nach einer Eigenschaft Language oder LCID.

Die Lösung ist - recht versteckt - zu finden in der Klasse SPRegionalSetting (Namespace: Microsoft.SharePoint).

Die statische Property SPRegionalSettings.GlobalServerLanguage bietet Zugriff auf die Sprache des Servers:

int globalLCID =  + SPRegionalSettings.GlobalServerLanguage.LCID;
string installed = "server was installed with LCID " + globalLCID.ToString();

Die statische Property SPRegionalSettings.GlobalInstalledLanguages zeigt an, auf welche Sprachen der SharePoint aufgrund von LanguagePacks erweitert wurde:

List<string> languageList = new List<string>();
SPLanguageCollection langColl = SPRegionalSettings.GlobalInstalledLanguages;
foreach (SPLanguage lang in langColl)
{
   languageList.Add(lang.LCID.ToString());
}
string installed = "installed Language Packs: "
   + string.Join(",", languageList.ToArray());

ramp up your career with MSDN Ramp Up

... so oder so ähnlich ist der Tenor von MSDN Ramp Up. Wobei handelt es sich hier? Ramp Up ist eine kostenfreie Möglichkeit, sich in diversen Online-Tracks über bestimmte Technologien zu informieren - einzige Voraussetzung: eine Live-Id.

Folgende Tracks stehen im Moment zu Verfügung:

  • SharePoint for Developers (Part 1)
  • SharePoint for Developers (Part 2)
  • Visual Studio 2008
  • Developer Basics (VS 2005)
  • Learn .NET for Java Developers
  • Learn Visual Basic .NET for VB 6 Developers
  • Learn Visual Studio 2005 for VS 2002/2003 Developers

Ich hab mich mal reingeklickt und mein Fazit: Für Newbies sehr interessant, weil es die Technologien sehr gut erklärt, aber auch für "alte Hasen" durchaus verwendbar, wenn man sich in einen neuen Teilaspekt einarbeiten möchte.

Kleiner Wermutstropfen: Die Slides stehen nur als Video zur Verfügung (zumindest in den von mir angeschauten Tracks) und man kann nicht "mal eben schnell" was nachlesen.