Sonntag, März 16, 2008

Exchange 2007 Webservice Zugriff, GetItem macht ärger!

Dieses Wochenende bin ich noch mal das Thema Exchange Webservices angegangen. So richtig steige ich nicht durch die Webservices, zumindest verstehe ich das Verhalten nicht so ganz, oder in der Implementierung sind Fehler. Mein letzter Versuch ging auf die Distributionlists, nun sollen es endlich Emails sein!

Auf jeden Fall wollte ich nun diesmal endlich alle Emails der letzten X-Tage oder sonst was abrufen. Ich hatte mir mehrere Beispiele angeschaut, unter anderem von Stephen Griffin und den Eintrag auf Jive into Messaging world, natürlich gab es noch viele andere Artikel. Der letzte Eintrag diente als Vorlage für meine Anpassungen. Beim Abrufen von Daten kommt bei der FindItem-Methode nur ein Teil der Informationen mit und maximal eine Zusammenfassung des Inhalts. Durch die zusätzliche Eigenschaften fiFindItemRequest.ItemShape.AdditionalProperties kann der Menge an Informationen noch verändert werden. Für mich war das aber nicht so wichtig, ich wollte den gesamten Inhalt, dafür soll man die GetItem-Methde aufrufen. Allerdings wenn ein Extra aufruft gemacht werden muss für die Einträge, so reicht es nur die IDs für den zweiten Aufruf zu sammeln. Alle Anpassungen sind in meinem Code zu sehen.

    85   const double MessageWindow = 2;

   86   private static void GetLastEmails()

   87  {

   88      ExchangeServicePortTypeClient exchange = new ExchangeServicePortTypeClient ();

   89      exchange.ClientCredentials.Windows.ClientCredential = System.Net. CredentialCache .DefaultNetworkCredentials;

   90      ExchangeImpersonationType exExchangeImpersonation = new ExchangeImpersonationType ();

   91      ConnectingSIDType csConnectingSid = new ConnectingSIDType ();

   92      csConnectingSid.PrimarySmtpAddress = "<email>@capevision.de" ;

   93      exExchangeImpersonation.ConnectingSID = csConnectingSid;

   94      FindItemType fiFindItemRequest = new FindItemType ();

   95      fiFindItemRequest.Traversal = ItemQueryTraversalType .Shallow;

   96      ItemResponseShapeType ipItemProperties = new ItemResponseShapeType ();

   97      //Load only the message ids for better performance (second request for details required)

   98      ipItemProperties.BaseShape = DefaultShapeNamesType .IdOnly; //AllProperties

   99      fiFindItemRequest.ItemShape = ipItemProperties;

  100  

  101      //The next 3 blocks load some additional properties with first request

  102      PathToExtendedFieldType ptItemURI = new PathToExtendedFieldType ();

  103      ptItemURI.PropertyTag = "0x10F3" ;

  104      ptItemURI.PropertyType = MapiPropertyTypeType .String;

  105  

  106      PathToExtendedFieldType ptFromEmailDisplay = new PathToExtendedFieldType ();

  107      ptFromEmailDisplay.PropertyTag = "0x0C1A" ;

  108      ptFromEmailDisplay.PropertyType = MapiPropertyTypeType .String;

   109  

  110      PathToExtendedFieldType ptBodySum = new PathToExtendedFieldType ();

  111      ptBodySum.PropertyTag = "0x3FD9" ;

  112      ptBodySum.PropertyType = MapiPropertyTypeType .String;

  113  

  114  

  115      //Limit items to be received

  116      PathToUnindexedFieldType StartDateReceivedField = new PathToUnindexedFieldType ();

  117      StartDateReceivedField.FieldURI = UnindexedFieldURIType .itemDateTimeReceived;

  118      ConstantValueType StartDateReceivedToGet = new ConstantValueType ();

  119      StartDateReceivedToGet.Value = DateTime .Now.Subtract( TimeSpan .FromDays(MessageWindow)).ToUniversalTime().ToString( "u" );

  120      FieldURIOrConstantType StartDateReceivedConstant = new FieldURIOrConstantType ();

  121      StartDateReceivedConstant.Item = StartDateReceivedToGet;

  122      IsGreaterThanOrEqualToType igtett = new IsGreaterThanOrEqualToType ();

  123      igtett.FieldURIOrConstant = StartDateReceivedConstant;

  124      igtett.Item = StartDateReceivedField;

  125      RestrictionType rt = new RestrictionType ();

  126      rt.Item = igtett;

  127      fiFindItemRequest.Restriction = rt;

  128  

  129      DistinguishedFolderIdType [] faFolderIDArray = new DistinguishedFolderIdType [2];

  130      faFolderIDArray[0] = new DistinguishedFolderIdType ();

  131      faFolderIDArray[0].Id = DistinguishedFolderIdNameType .inbox; //only load Inbox-items

  132      //fiFindItemRequest.ItemShape.AdditionalProperties = new BasePathToElementType[3];

  133      //fiFindItemRequest.ItemShape.AdditionalProperties[0] = ptBodySum;

  134      //fiFindItemRequest.ItemShape.AdditionalProperties[1] = ptItemURI;

  135      //fiFindItemRequest.ItemShape.AdditionalProperties[2] = ptFromEmailDisplay;

  136      fiFindItemRequest.ParentFolderIds = faFolderIDArray;

  137      FindItemResponseType frFindItemResponse;

  138      int emails = 0;

  139      int errors = 0;

  140      try

  141      {

  142          exchange.FindItem( null , null , Thread .CurrentThread.CurrentUICulture.ToString(), null , fiFindItemRequest, out frFindItemResponse);

  143          foreach ( FindItemResponseMessageType firmtMessage in frFindItemResponse.ResponseMessages.Items)

  144          {

  145              if (firmtMessage.RootFolder.TotalItemsInView > 0)

  146              {

  147                  foreach ( ItemType miMailboxItem in (( ArrayOfRealItemsType )firmtMessage.RootFolder.Item).Items)

  148                  {

  149                      //if (miMailboxItem.ExtendedProperty != null)

  150                      //{

  151                      //    if (miMailboxItem.ExtendedProperty.Length == 3)

  152                      //    {

  153                      emails++;

  154                      //TODO: Create item batches for effective download

  156                      try

  157                      {

  158                          GetItemResponseType giResponse;

  159                          giResponse = GetCurrentItem(exchange, miMailboxItem);

  160                          if (giResponse != null && giResponse.ResponseMessages != null && giResponse.ResponseMessages.Items.Count() > 0)

  161                          {

  162                              ItemType [] array = (( ItemInfoResponseMessageType )giResponse.ResponseMessages.Items[0]).Items.Items;

  163                              foreach ( ItemType t in array)

  164                              {

  165                                  MessageType details = t as MessageType ;

  166                                  if (details == null )

  167                                  {

  168                                      //This is not a mail item

  169                                      //should i handle this?

  170                                  }

  171                                  else

  172                                  {

  173                                      Console .WriteLine( "[{0}] - <{1}>{2} - {3}" , details.DateTimeReceived.ToString( "u" ), details.From.Item.Name, details.From.Item.EmailAddress, details.Subject.ToString());

   174                                      //if (details.Body != null)

  175                                      //    Console.WriteLine(details.Body.Value);

  176                                  }

  177                              }

  178                          }

  179                      }

  180                      catch ( Exception ex)

  181                      {

  182                          errors++;

  183                          Console .WriteLine( "[{0}] - <{1}> - {2}" , miMailboxItem.DateTimeReceived.ToString( "u" ),

miMailboxItem.DisplayTo, miMailboxItem.ItemId.Id);

  184                          Console .WriteLine( "--Error receiving item: " + ex.Message);

  185                      }

  186                      //only default, then no email address loaded (and some other properties)

  189                      //    }

  190                      //}

  191                  }

  192              }

  193          }

  194      }

  195      catch ( Exception ex)

  196      {

  197          Console .WriteLine(ex.ToString());

  198          throw ;

  199      }

  200      Console .WriteLine( "Emails received {0}, errors {1}" , emails, errors);

  201  }

  203   private static GetItemResponseType GetCurrentItem( ExchangeServicePortTypeClient exchange, ItemType miMailboxItem)

  204  {

  205      GetItemResponseType giResponse;

  206      GetItemType giType = new GetItemType ();

  207      giType.ItemShape = new ItemResponseShapeType ();

  208      giType.ItemShape.BaseShape = DefaultShapeNamesType .Default;

  209      giType.ItemShape.IncludeMimeContent = true ;

  210      giType.ItemShape.IncludeMimeContentSpecified = false ;

  211      giType.ItemShape.BodyType = BodyTypeResponseType .Text; // miMailboxItem.Body.BodyType1;

  212      giType.ItemShape.BodyTypeSpecified = true ;

  213      ItemIdType current = new ItemIdType ();

  214      current.Id = miMailboxItem.ItemId.Id.ToString();

  215      giType.ItemIds = new ItemIdType [] { current };

  216      exchange.GetItem( null , null , null , null , giType, out giResponse);

   217      return giResponse;

  218  }

Ende Bei einigen Versuchen erhielt ich die Fehlermeldung, dass er Response zu groß war, allerdings gab es dafür eine schnelle Abhilfe, einfach die Verarbeitung des Responses auf Streaming umstellen.

   19                   < binding name = " ExchangeServiceBinding " closeTimeout = " 00:01:00 "

   20                       openTimeout = " 00:01:00 " receiveTimeout = " 00:10:00 " sendTimeout = " 00:01:00 "

   21                       allowCookies = " false " bypassProxyOnLocal = " false " hostNameComparisonMode = " StrongWildcard "

   22                       maxBufferSize = " 65536 " maxBufferPoolSize = " 524288 " maxReceivedMessageSize = " 655369 "

   23                       messageEncoding = " Text " textEncoding = " utf-8 " transferMode = " StreamedResponse "

   24                       useDefaultWebProxy = " true " >

   25                       < readerQuotas maxDepth = " 32 " maxStringContentLength = " 8192 " maxArrayLength = " 16384 "

   26                           maxBytesPerRead = " 4096 " maxNameTableCharCount = " 16384 " />

   27                       < security mode = " Transport " >

   28                         <!-- <transport clientCredentialType="Windows" proxyCredentialType="None" /> -->

   29                         < transport clientCredentialType = " Ntlm " proxyCredentialType = " None " />

   30                         < message clientCredentialType = " UserName " algorithmSuite = " Default " />

    31                       </ security >

   32                   </ binding >

Mein größtes Problem ist allerdings, dass die GetItem-Methode für einige Mails immer mal wieder einen Fehler wirft. Leider bekomme ich den Fiddler auch nicht zur Arbeit mit dem Exchange überredet, da bei uns nur HTTPs zulässig ist. Falls jemand einen Tipp für mich hat, was das sein kann, wäre ich sehr dankbar. Da in den meisten Fällen die Email erfolgreich verarbeitet werden kann, kann man die Lösung durchaus zum Verarbeiten nutzen. Mich ärgert allerdings trotzdem, dass Emails nicht korrekt herausgeschrieben werden.

In meinem Code sollte noch einige Funktionen optimiert werden. Zum einen sollte man mehrere Items mit einer Anfrage abrufen, da jeder Abruf ein Roundtrip mit dem Server erfordert. Des Weiteren muss das ErrorHandling deutlich verbessert werden, aber Schritt für Schritt.

Sonntag, März 09, 2008

Cruise Control 1.4 und MSBuild/LaTeX

Diese Woche hatte ich mal wieder das Thema Build Integration bei uns angehen müssen. Der Auslöser war mal wieder die zwingende Aktualisierung von einigen Dokumenten unserer Anwendung. Außerdem habe ich eine Anwendung, die schon eine Weile nicht mehr funktionierte, nun kamen auch noch einige Bug Fixes dazu.

Grundsätzlich war der Build-Prozess bei uns immer so, dass wir Build-Skripte auf dem Server liegen hatte und auch notwendige Erweiterungen. CCNet konnte in früheren Versionen SVN-Dateien nicht (korrekt) abrufen, aus diesem Grund enthielten die Build-Skripte die Anweisungen zum Abrufen der Informationen aus der Versionsverwaltung. Zusätzlich wollten wir immer ein Clean-Build, d. h. der Server wird in den Ursprungszustand zurückgesetzt.

Sowohl abrufen, als auch das ausführen von Skripten vor dem eigentlichen Build ist mit den neueren CCNet Versionen möglich. (1.3 und 1.4) Somit werden nun die Skripte ordnungsgemäß versioniert und können sich auch in den Versionen deutlich unterscheiden. Ich hatte bereits vor einiger Zeit schon Teile unserer Build-Skripte angepasst, so dass Funktionen wie das Abrufen von Daten aus dem SCM-System nicht mehr ausgeführt wurden. Manche werden sich fragen, warum Dokumente über Build-Skripte erzeugt werden müssen, die Antwort ist ganz simpel, weil bei uns die Dokumente geTeXt sind.

Dokumente mit TeX bzw. LaTeX zu erstellen ist bei umfangreichen Dokumenten sehr nützlich und gerade bei einer Integration mit der Anwendung oder Build-Prozessen sinnvoll. Die großen Vorteile aus meiner Sicht sind:

·         Textbasierende Dokumente, dadurch effektives Merging möglich und geringere Größe

·         Wenige Formatierungen

·         Sauberes Schriftbild

·         Automatisches ersetzen von Platzhaltern, Versionsnummern der Anwendung, Versionsnummern des Dokumentes

Die Vorurteile, dass die Dokumente nicht mehr für alle lesbar sind, ist durch den Einsatz des Build-Systems ausgehebelt. Jeder kann sich das Finale Dokument laden und anschauen. Anmerkungen können genau zu einer Version gemacht werden. Hier das Target für die Erstellung der Dokumente:

  289   <Target Name="CreatePdf" DependsOnTargets="CreateRawFolderStructure">

  290     <!-- Eventuelle die Version der Anwendung setzen s-->

  291     <Message Text="Creating TexFile-Items! File is $(TexFile)" Importance="high"/>

  292     <CreateItem Include="$(SourceFolder)\**\$(TexFile)" Exclude="$(SvnExcludes)">

  293       <Output TaskParameter="Include" ItemName="texMaster"/>

  294     </CreateItem>

  295     <Message Text="Creating TexFile-Items  created! Versionnumber suffix is $(VersionNumberFileSuffix)" Importance="high"/>

  296     <CreateProperty Value="$(VersionNumberFile)-tex$(VersionNumberFileSuffix)">

  297       <Output TaskParameter="Value" PropertyName="TexVersionNumberFile"/>

  298     </CreateProperty>

  299     <CreateItem Include="$(SourceFolder)\**\*.tex" Exclude="$(SvnExcludes)">

  300       <Output TaskParameter="Include" ItemName="versionTexFiles"/>

  301     </CreateItem>

  302     <Exec Command="echo 1.0.0.0> &quot;$(VersionNumberFile)&quot;" Condition="!Exists('$(VersionNumberFile)')"/>

  303     <Version VersionFile="$(VersionNumberFile)" RevisionType="None" BuildType="None">

  304       <Output TaskParameter="Major" PropertyName="Major" />

  305       <Output TaskParameter="Minor" PropertyName="Minor" />

  306       <Output TaskParameter="Build" PropertyName="Build" />

  307       <Output TaskParameter="Revision" PropertyName="Revision" />

  308     </Version>

  309     <FileUpdate Files="@(versionTexFiles)" Singleline="false" Encoding="ISO-8859-1"

  310                 Regex="\\newcommand{\\Version}{.+}"

  311                 ReplacementText="\newcommand{\Version}{$(Major).$(Minor).$(Build)}"/>

  312 

  313     <Exec Command="echo 1.0.0.0> &quot;$(TexVersionNumberFile)&quot;" Condition="!Exists('$(TexVersionNumberFile)')"/>

  314     <Version VersionFile="$(TexVersionNumberFile)" RevisionType="Increment" BuildType="Increment" Condition="'$(CCNetLabel)'==''">

  315       <Output TaskParameter="Major" PropertyName="Major" />

  316       <Output TaskParameter="Minor" PropertyName="Minor" />

  317       <Output TaskParameter="Build" PropertyName="Build" />

  318       <Output TaskParameter="Revision" PropertyName="Revision" />

  319     </Version>

  320     <Exec Command="echo $(CCNetLabel)> &quot;$(TexVersionNumberFile)&quot;" Condition="'$(CCNetLabel)'!=''"/>

  321     <!-- immer Version herausholen -->

  322     <Version VersionFile="$(TexVersionNumberFile)" RevisionType="None" BuildType="None" >

  323       <Output TaskParameter="Major" PropertyName="Major" />

  324       <Output TaskParameter="Minor" PropertyName="Minor" />

  325       <Output TaskParameter="Build" PropertyName="Build" />

  326       <Output TaskParameter="Revision" PropertyName="Revision" />

  327     </Version>

  328     <FileUpdate Files="@(versionTexFiles)" Singleline="false"  Encoding="ISO-8859-1"

  329                 Regex="\\newcommand{\\DocVersion}{.+?}"

  330                 ReplacementText="\newcommand{\DocVersion}{$(Major).$(Minor).$(Build)}"/>

  331     <CreateProperty Value="$(CCNetArtifactDirectory)\texify.log">

  332       <Output PropertyName="TexifyLogFile" TaskParameter="Value"/>

  333     </CreateProperty>

  334     <Exec Command="&quot;$(LatexBinFolder)\texify&quot; -b --pdf -q --max-iterations=1 -e &quot;@(texMaster)&quot;" IgnoreExitCode="true" WorkingDirectory="@(texMaster->'%(RootDir)%(Directory)')" Outputs="$(TexifyLogFile)"/>

  335     <Exec Command="&quot;$(LatexBinFolder)\makeindex&quot; &quot;@(texMaster->'%(RootDir)%(Directory)%(Filename).nlo')&quot; -s nomencl.ist -o &quot;@(texMaster->'%(RootDir)%(Directory)%(Filename).nls')&quot;" IgnoreExitCode="true" WorkingDirectory="@(texMaster->'%(RootDir)%(Directory)')" Outputs="$(TexifyLogFile)"/>

  336     <Exec Command="&quot;$(LatexBinFolder)\bibtex&quot; &quot;@(texMaster->'%(RootDir)%(Directory)%(Filename)')&quot;" IgnoreExitCode="true" WorkingDirectory="@(texMaster->'%(RootDir)%(Directory)')" Outputs="$(TexifyLogFile)"/>

  337     <Exec Command="&quot;$(LatexBinFolder)\texify&quot; -b --pdf -q --max-iterations=1 -e &quot;@(texMaster)&quot;" IgnoreExitCode="true" WorkingDirectory="@(texMaster->'%(RootDir)%(Directory)')" Outputs="$(TexifyLogFile)"/>

  338     <!--<Exec Command="&quot;$(LatexBinFolder)\texify&quot; -b -pdf -e &quot;@(texMaster)" WorkingDirectory="@(texMaster->'%(RootDir)%(Directory)')" Outputs="$(TexifyLogFile)"/>-->

  339     <CreateProperty Value="@(texMaster->'%(RootDir)%(Directory)%(Filename).pdf')">

  340       <Output TaskParameter="Value" PropertyName="PdfResult"/>

  341     </CreateProperty>

  342     <Copy SourceFiles="$(PdfResult)" DestinationFiles="@(texMaster->'$(OutputPath)\%(Filename)_$(Major).$(Minor).$(Build).pdf')"/>

  343     <CreateItem Include="@(texMaster->'%(RootDir)%(Directory)Anlagen\*.*')" Exclude="$(SvnExcludes)">

  344       <Output ItemName="attachments" TaskParameter="Include"/>

  345     </CreateItem>

  346     <Copy SourceFiles="@(attachments)" DestinationFolder="$(OutputPath)\Anlagen"/>

  347   </Target>

Es ist auch möglich statt mit Regex die Dokumente anzupassen über ein Include-File alles Variable im Dokument in eine separate Datei auszulagern und diese während des Builds zu generieren. Die interessanten Zeilen in dem Skript sind die Aufrufe von Texify. Bei uns mussten die Anweisungen 2-mal durchlaufen werden, nach dem 1. Durchlauf wurden Index und Bibtex erzeugt/ausgeführt.

Das zweite Problem was auftrat war die Unterstützung von .NET Framework 3.5. Wobei ich hier einschränken muss, der Standard Logger ist gegen das .NET Framework 2.0 kompiliert, das Verursacht das Problem. Die Lösung ist eigentlich ganz einfach, Code nehmen und gegen .NET Framework 3.5 kompilieren. Leider habe ich die Quellen nicht gefunden. Nach etwas suchen habe ich mich dann für den „Improved MsBuild Logger“, allerdings habe ich zusätzlich noch die Lösung um einen detailierteren Build-Status zu erhalten gewählt "Viewing build progress". Beide Lösungen zusammen geworfen und angepasst und herausgekommen ist ein neuer Logger, ich wollte noch einige Infos über die Tasks haben. Eventuell werde ich noch ein separates Xslt schreiben, um die Timings besser auszuwerten. Den Fortschritt des Build zu sehen ist echt klasse, so hat man wenigstens einige Details zum Fortschritt. Der Logger wird wie gewöhnlich beim Aufruf angegeben, heraus kommt das Xml-File mit den Status-Infos.

Der Build-Prozess bei uns läuft nun so ab:

1.       Löschen aller Dateien – CCNET prebuild-Task mit MsBuild-Skript

2.       Abrufen der Sourcen aus der Versionsverwaltung – CCNET Sourcen abrufen

3.       Anstossen des eigentlichen Builds (kompilieren, Documente erstellen, FxCop, MSI erstellen, …) – MSBuild-Task

4.       Publishen der Sourcen – Über eigenen MsBuild-Task oder Publisher-Task

5.       Statistiken refreshen usw.

Ich habe leider kein vollständiges Projekt zur Hand, so dass es leider ohne Code gehen muss. L

Samstag, Februar 23, 2008

Sql Server Reporting Service Multivalue und die Integration in SharePoint

Am Wochende habe ich mich intensiv mit dem Sql Server Reporting Service beschäftigt, ich muss sagen, dass ich mir mal endlich richtig Zeit genommen habe und auch einige Tricky-Sachen gelöst habe. (Natürlich durch googlen) Letztendlich wollte ich dem Kunden ermöglichen in unserer Datenbank nach Einlieferungsnummern zu suchen, um so simple Fragen „Was ist mit meinen Daten passiert?“ selbst zu lösen. Der erste Schritt war das überfliegen eines Tutorials zu dem Thema. Das Beste was ich kenne, sind diese: Beginning SQL Server 2005 Reporting Services Part 1 - Building and deploying basic reports , Beginning SQL Server 2005 Reporting Services Part 2 , Beginning SQL Server 2005 Reporting Services Part 3 - The Chart Control und Beginning SQL Server Reporting Services Part 4 alle Artikel sind von Steve Joubert . Ich kann jedem diese Artikel nur Empfehlen.

Nun aber zu den eigentlichen Problemstellungen. Ich bin nun nicht gerade der beste zum Schreiben von Sql Statements, einige meiner Kollegen werden noch mit erschrecken zurückdenken. … Immer wenn ich an eine Prozedur in der DB ran wollte, weil es mir zu langsam ging, haben die Kollegen das Priorisiert gemacht, quasi gleich, denn immer nachdem ich an der DB war musste alles aufgeräumt werden … Die Statements gingen recht leicht, hätte ich gar nicht gedacht, ich hatte mir allerdings die Woche auch einige Tricks für die Erstellung und das zusammen setzen von komplexen Statements von einem Kollegen geholt. Für die Reporting Services sind die Datenquellen das wichtigste bei der Realisierung. Gerade bei der Realisierung von Online-Statements sollte man auf schnelle Ausfrührung und möglichst limitierte Daten achten. Gerade bei großen Ergebnismengen ist die Aufbereitung problematisch und zeitintensiv.

Für die Darstellung habe ich anschließend ein Drill Down Report gewählt, der über 4 Ebene/Gruppen geht, so wird die Anzeige kompakt und doch informativ.

Die Anforderung das MultiValue-Feld zu realisieren, war zunächst extrem schwierig umzusetzen. Den besten Tipp habe ich auf dem Blog RDA Corp – BI and Sql Server gefunden. Man benötigt für die Verwendung des Parameters die Prozedur um die Werte zurück zubekommen.

Parameter Expression: Join(Parameters!Param.Value, ",")

Statement:
… [tableSynonym].[KeyColumn] IN
(
SELECT [columniID]
FROM [schema].[table] tb, [dbo].[Split](@ItemList, ',') tmp
WHERE patindex('%' + tmp.Item + '%', tb.[KeyColumn]) > 0
)

Prozedur: ( RDA Corp – BI and Sql Server )
CREATE FUNCTION [dbo].[Split]
(
@ItemList NVARCHAR(8000 ),
@delimiter CHAR(1 )
)
RETURNS @IDTable TABLE (Item VARCHAR(200 ))
AS
BEGIN
DECLARE @tempItemList NVARCHAR(8000 )
SET @tempItemList = @ItemList
 
DECLARE @i INT
DECLARE @Item NVARCHAR(8000 )
SET @tempItemList = REPLACE (@tempItemList, @delimiter + ' ', @delimiter )
SET @i = CHARINDEX(@delimiter, @tempItemList )
WHILE (LEN(@tempItemList) > 0 )
BEGIN
IF @i = 0
SET @Item = @tempItemList
ELSE
SET @Item = LEFT(@tempItemList, @i - 1 )
INSERT INTO @IDTable(Item) VALUES(@Item )
IF @i = 0
SET @tempItemList = ''
ELSE
SET @tempItemList = RIGHT(@tempItemList, LEN(@tempItemList) - @i )
SET @i = CHARINDEX(@delimiter, @tempItemList )
END
RETURN
END

Nach dem der Report so richtig gut funktionierte, war es Zeit die Integration in den Sharepoint durchzuführen. Es gibt von MS ein Report Service WebPart für Sharepoint, außerdem hat man die Möglichkeit mit dem .NET FX2 das ReportViewer Webpart zu nutzen. Ich hatte zu erst versucht das fertige WebPart zu nutzen, bin dabei allerdings gescheitert, außerdem habe ich damit nicht die Möglichkeit kleinere Modifikationen durchzuführen. Also einen kleinen Wrapper für erstellt, der Connections-Akzeptiert, Parameter von einer anderen Quelle empfangen kann und über ein Parameter-Mapping verfügt. Man kann im Viewer-Control sehr schön die Ansichten modifizieren, so sind bei uns die Parameter nicht vorhanden, da diese durch ein anderes WebPart geliefert werden. Bisschen Probleme hat mir die Anzeige gemacht, da die Scrollbalken immer störten, leider konnte ich nicht einfach die gesamte Seitenhöhe verwenden, hier kann evtl. ein JavaScript helfen, aber es gibt wichtigeres.

Nach dem auch der Schritt erledigt war, hat ein Kollege sich den Report noch mal vorgenommen und versucht die Abfragen zu optimieren (ohne den Plan zu nutzen). Außerdem wurde das zugehörige DataWarehouse angepasst, um Datenstrukturen bereite optimiert vorzuhalten.

Man muss einfach sagen, dass die Reportings Services so viele extrem schöne Feature mitliefern, dass man diese einfach viel mehr nutzen sollte. Die Exportformate sehen sehr schick aus, richtig Klasse finde ich die Excel-Version des Reports. Man sollte bei der exzessiven Nutzung aber unbedingt die Serverresourcen im Auge behalten. Reporting kann sehr Rechenintensiv sein! Sorry, dass ich kein Bild vom Report einfüge, aber leider sind sehr viele vertrauliche Daten enthalten.

Hier noch eine weitere Webseite http://www.gotreportviewer.com/ mit vielen Tipps und Tutorials.

Donnerstag, Februar 21, 2008

Windows Vista Service Pack1

Wow, seit heute 19:30 zieht mein Rechner das Service Pack 1 über Windows Update. Ich bin vielleicht schockiert und habe schnell noch eine Sicherung des Rechners angeschmissen. Das Update wird gezogen, ohne das eine Veränderung an meinem System gemacht wurde. Jetzt bin ich mal gespannt, ob alles erfolgreich ist und danach noch alles ordentlich funktioniert. Ich vermute, dass das SP1 für x64 eher freigegeben wurde, da es schon am Anfang höhere Anforderungen an die Treiber gestellt hat.

Nun kann ich aber leider nicht den eigentlichen Blog-Post weiterschreiben, weil der Rechner gut beschäftigt ist. Ich habe schon einiges zusammengetragen für den Post und denke, dass ich ihn spätestens am Samstag fertig habe, wenn der Rechner nicht das zeitliche segnet.

Also bis auf weiteres Daumen drücken und die Meldungen der letzten Tage vergessen. (Loop restart with pre sp1, recovery after installing sp1, ...)


Update: Hab gestern bis kurz vor 23:00 das Backup laufen lassen und nach dem es abgeschlossen war das SP1 angeworfen. Nach dem ersten Schreck heute morgen, Rechner war nicht heruntergefahren, und den 2 reboots danach war alles Spitze. Service Pack ist installiert, System läuft noch. Bis jetzt weder positives noch negatives gemerkt. ICh freue mich schon darauf, wenn kein Delay mehr bei den Browser-Dialogs ist. Ansonsten geht nun die ganz normale Arbeit weiter.

Montag, Februar 18, 2008

Slowly blogging

In letzter Zeit habe ich das Bloggen etwas vernachlässigt, ich habe mich einfach z sehr in Arbeit eingedeckt. Ich wollte endlich unsere Portal-Anwendung live bringen. Dies hat aber leider nicht so richtig geklappt. Momentan gibt es erneut Diskussionen über die Navigation. Außerdem habe ich versucht noch einige Gimmicks einzubauen, allerdings bin ich mir noch nicht sicher, ob diese Funktion enthalten bleibt.

Ich habe immer enorm Schwierigkeiten, sobald ich alleine bin und Zeit zum nachdenken habe. Sie ist bestimmt schwer beschäftigt und hat für nichts Zeit, ich wünschte mir trotzdem, dass ich sie mal wieder sehe. Ich würde so gerne Informationen über sie hören und lesen, natürlich am liebsten sie treffen. Nur gleichzeitig macht mir das so Angst. Ich vermisse die Gefühle vom letzten Jahr, die Zeit mit ihr.

Am Wochenende habe ich eine tolle Zeit mit meinen besten Freunden verbracht. Die 4 sind schon was ganz spezielles und die beiden kleinen schaffen es 100ig einen abzulenken. Am Freitag habe ich mich zu dem mal mit Ex-Kollegen getroffen, die einige lustige Sachen über Timing bei meinem alten Arbeitgeber erzählt. Leider ist der Grund für die Erheiterung eigentlich nicht lustig.

Auf Arbeit läuft es momentan wie auf einem Schiff oder Achterbahn, es geht auf und ab oder auch hin und her. Es macht Spass, aber manchmal ist es einfach frustrierend. Wir haben seit 2 Wochen Storage, der Storage soll für unsere Virtualisierungsserver als Speicherplatz dienen. Leider musste wir feststellen, dass es nicht anzuschließen geht, zumindest bekommen wir das nicht hin.

Ich will die nächsten Tage einen kurzen Blog-Eintrag zum Sql Server Reporting Service erstellen, bzw. die Integration in ASP.NET/WSS.

Ich werde mir in nächster Zeit mehr Mühe geben, um regelmäßiger und pünktlicher zu bloggen.

Sonntag, Februar 10, 2008

ASP.NET Membership Provider custom password reset

Ich hatte schon länger Probleme mit dem Zurücksetzen des Passwortes in einer ASP.NET-Anwendung. Benutzt man nicht das Standardverhalten in dem man altes Passwort und neues Passwort abfragt, so hat man richtig Probleme ein Passwort-Reset durchzuführen. Diese Woche habe ich noch mal gegooglet und auch 2 Blog-Einträge mit einer ähnlichen Problemstellung gefunden. Der Blog-Eintrag „MembershipUser.ChangePasswort“ von Simon Steckermeier hat eine schöne Erläuterung der Problemstellung, einige kleine Macken hatten aber die Lösungen. Was passiert, wenn das Passwort nicht gesetzt werden kann, weil es die Regeln verletzt?

Also konnte ich die Lösung nicht so verwenden. Aber ein wenig ausprobieren und eine Lösung war gefunden. Allerdings kann man diese Lösung nicht mit Hashed-Passwords verwenden. Folgende Code-Zeilen sind meine Lösung:

string oldPW = Membership.Provider.GetPassword(this.txtUserName.Text, null);

string generatedPW = Membership.Provider.ResetPassword(this.txtUserName.Text, null);

try

{

Membership.GetUser(this.txtUserName.Text).ChangePassword(generatedPW, pw);

}

catch (ArgumentException ex)

{

Membership.GetUser(this.txtUserName.Text).ChangePassword(generatedPW, oldPW);

throw;

}

Unbedingt zu beachten ist, dass die web.config für die Provider-Einstellung korrekt eingestellt ist! Hier auch die Web.config-Attribute, die für den Provider entsprechend eingestellt sein müssen: enablePasswordRetrieval="true" enablePasswordReset="true" requiresQuestionAndAnswer="false" passwordFormat="Encrypted"

Im Anschluss ist alles super und die Password Veränderung kann man ansteuern, wie man möchte.