Par e-parakstu, un EDOC 1.02, sertifikātiem un vispārējo mulsumu

April 29th, 2013

Esmu apmulsis ar to, kā LTVRTC saviem lietotājiem stāsta par plānotajām izmaiņām. Aprīlī-maijā to viņiem ir vairākas, bet info, ko varam saņemt, ir ļoti skopa. Cik esmu sapratis, reālās izmaiņas ir divas:

  • jauna EDOC formāta versija, kurā atrisināta problēma ar milzīgajiem CRL datu blokiem parakstītajā dokumentā (jo EDOC 1.01 bija pārprasts, ko nozīmē Xades-C paraksts),
  • saknes sertifikātu maiņa un līdz ar to nepieciešamība pamainīt truststore.jks failu, kas iekļauts Java bibliotēkās.

Patērētājiem (kas, starp citu, lielākā daļā ir IT cilvēki un droši vien šos teikumus būtu sapratuši) to tiek mēģināts pārdot kā “mums te vajag lai jums te vajag, jo citādi mums visiem kopā nevērsies, būs tehniskas problēmas, tāpēc no 27. aprīļa līdz 5. maijam pamazām, bet vispār 5. maijā. Pārtaisiet.”

Īsa notikumu hronoloģija:

25. martā pa e-pastu saņemu šādu ziņu no bizness@eparaksts.lv, kur stāstīts: lūdzam līdz 1. maijam pārkompilēt savus e-paraksta risinājumus pret jaunākajām Java bibliotēkām, “citādi nebūs labi” (precīzi nav paskaidrots, bet citi nevarēšot izlasīt citu veidotus EDOCus). E-pastā arī saite uz jauno Java bibliotēku versiju. Aizejot uz norādīto adresi, protams, nav informācijas, kas tā par Java bibliotēku versiju. Pēc mana lūguma šos datus pievienot, parādas info – tā ir 2.2.0.1 versija.

24. aprīļa rītā esmu beidzot ticis līdz tam, ka varētu sākt šo izmaiņu veikt. Aizeju atkal uz šo saiti – re, priekšā jau 2.2.0.1 versija. Ceru, ka tā arī man derēs, kaut gan papildus info no LVRTC nav bijis. Pārkompilēju savus apletus pret jauno versiju. Sāku testēt. Brīnišķīgi, nekas nestrādā. eParakstītājs failu never vaļā, paziņo, ka “Dokumentā trūkst nepieciešamā mape” (ai, gramatika, kur Tu paliki?) Mēģinu šo pašu failu atvērt https://www.eparaksts.lv/lv/?signChipcard – ha, viss strādā! Tik tāds sīkums, parakstīšanas rīks rāda, ka faila formāts ir EDOC 1.02. Nu, ko – Java bibliotēka saprot šo formātu, bet eParakstītājs nē.

24. aprīļa dienas vidū atkal saņemu ziņu, ka LVRTC plāno veikt sertifikātu ķēžu atjaunošanu (un kāds man sakars ar viņu ķēdēm?). Atsūtīta, pēc viņu vārdiem, “detalizēta” instrukcija, kas darāms. Instrukcijā rakstīts, ka, pārkompilējot kodu pret “jaunākajām bibliotēkām” (vai tiešām tik grūti norādīt, KURU VERSIJU??) un viss būs. Pa e-pastu noskaidroju, ka būs VĒL CITA versija pēc 2.2.0.1:

> @@ KrissR
  > Papildus jautājums: šodienas instrukcijā runa ir par versiju 2.2.2.  - 
  > vai tas nozīmē, ka tuvākajās dienās būs arī jauna bibliotēkas versija, 
  > kurā jau būs edoc.properties  fails ar pareizu konfigurāciju? 

Atbilde pienāk drīz:

> @@ eParaksts:  
  > Jā, būs arī jaunās Java eDoc bibliotēkas, bet tās būs pieejamas 
  > tikai pēc šo sertifikātu ķēdes nomaiņas posma.

26. aprīlī mēģinu noskaidrot, kad būs pieejama jauna eParakstītāja versija, kas spēs atvērt EDOC:1.02:

> ‏@naivists  
  >> @eParaksts, kad varēsim dabūt e-Parakstītāja versiju, kas spēj atvērt EDOC 1.02?

29. aprīlī @eParaksts atbild, visu vēl vairāk sarežģījot:

> ‏@eParaksts 
  >> @naivists Edoc varēs atvērt vienmēr ar visām iespējamajām programmatūrām neatkarīgi no edoc versijas..
  >> .. Edoc 1.02 dokumentācija būs pieejama tad, kad 1.02 tiks ieviests produkcijā.

29. aprīlī pa dienu @eParaksts tviterī arī apstiprina to, ka kopumā notiek divas izmaiņas, taču izstāsta to, ko nevajadzētu – ka pastāv (tiesa, stipri teorētisks) servisa pārtraukums specifiskam gadījumam:

> ‏@eParaksts 
  >> @naivists Tās ir divas atsevišķas lietas-jaunā arhitektūra(edoc 1.02)un 
  >> jaunais pamata sertifikāts.Pagaidām uz java bibliotēkām būvētā..
  >> ..sistēmā nav iespējams pārbaudīt no sestdienas parakstītos 
  >> dok.(ar virt.eParakstu)tiklīdz būs iespēja mēs visiem izstrādātājiem..
  >> .. izsūtīsim atjauninājumus  un nepieciešamās  instrukcijas Java bibl.atjaunināšanai!

29. aprīļa vakarā saņemu samērā saprotamu ziņu, kas puslīdz atkārto iepriekš teikto. Ziņā ir saite uz aprakstošu lapu pašā eparaksts.lv. Gandrīz viss būtu lieliski. Tikai — nekādas informācijas par to, kur dabūt to solīto Java bibliotēku 2.2.2.01 vai kad tā būs pieejama.

Tas liek uzdot dažus jautājumus un veikt dažus secinājumus:

  • Ja plānots, ka parādīsies jauni saknes sertifikāti, tos tiešām nevar uzveidot un nopublicēt agrāk? Uzņēmumi nevar visu izplatīt 20 minūšu laikā!
  • Ja ir izveidots jauns dokumenta formāts, KĀPĒC mums vispirms nav programmatūras, kas to spēj atvērt un tikai tad prasības to ieviest?
  • Kā tieši mums vajadzēs izskaidrot “tantei VIDā”, ka eDOC dokuments ir labs, tikai viņai atkal ir novecojusi eParakstītāja versija un jākontaktējas ar vietējiem IT vīriem?
  • Ja jau tās ir plānotas izmaiņas, kāpēc tika ieplānota situācija, kad “nav iespējams pārbaudīt no sestdienas parakstītos dok.(ar virt.eParakstu)”?
  • Kāpēc man ir īpaši jāprasa e-pastā, lai saņemtu saprotamu atbildi?
  • Kāpēc to visu nevar cilvēkiem saprotamā valodā nopublicēt vietnē eparaksts.lv? Es saprotu, ka “viņuprāt” visi ir Java programmētāji no dzimšanas un visiem viss ir skaidrs par to, kā strādā ocsp, crl un citas tehnoloģijas, bet TĀ NAV. Runājiet manā valodā un es būšu laimīgs.
  • IT speciālistu atbalsts e-parakstam daudz dotu nākamā līmeņa atbalsta sasniegšanai, bet LVRTC pie tā negrib piestrādāt.

2. maija rītā ir nopublicēts visiem saprotams skaidrojums, kas notiek un kā sakārtot konfigurāciju: https://www.eparaksts.lv/lv/jaunumi/svariga-informacija-eparaksts-viedkarsu-un-eid-karsu-lietotajiem/

3. maijā tieku pie skaidrības par e-parakstītāja nākotni:

sarakste ar @JanisBokta tviterī

Secinājums 3. maijā – jebkādas izmaiņas sākšu veikt tikai tad, kad būs pieejams jaunais e-parakstītājs un tas būs izplatīts visiem lietotājiem vismaz manā iestādē.

Remembering the user language choice

November 9th, 2012

Recently, I had to develop a small web portal for a client. Since the portal consisted mostly of static pages which the client wanted to update himself, and I had limited time for development, WordPress platform was an obvious choice. We agreed to derive the design from the standard “Responsive” theme (WordPress Codex has a great article on how to do that).

The portal had to be multi-lingual, most pages would be in English, but some would have translations in Latvian. I found WordPress qTranslate plugin the best solution for my needs. It changes the way WordPress dashboard forms work, now a blog post has two fields “Title (Latvian)” and “Title (English)” as well as “Post content (Latvian)” and “Post content (English)”. If you don’t fill the fields for a particular language, the page would be treated as not translated and may be hidden in the navigation as well. You have to pick the default language of the site – what content to show if the user has not picked any language.

qTranslate provides several ways how you can do localization:

  • using a subdomain, e.g. lv.yourdomain.com and en.yourdomain.com
  • by adding a GET parameter to each page, eg. yourdomain.com/article_title/?lang=lv
  • using a directory prefix, e.g. yourdomain.com/lv/articletitle, yourdomain.com/en/articletitle

I picked the latter, as it seemed most rational of the three. Now every page had two addresses and everything seemed to be running just fine. However, I found it a bit annoying that a returning visitor always sees the default language, regardless which language he had chosen in the previous visit. It would seem logical that qTranslate could just set a cookie “last language” or something like that. I found out, it does not. Moreover, qTranslate authors are not going to implement it, they say “it’s a wanted behavior”.

Not wanting to mess with plugin’s PHP code, I decided to fix this at the web server level (as you may know, Apache Mod_Rewrite can both read and set cookies).

So, I needed two parts of the algorithm:

– when the user requests a page where the URL contains language code, save that language in a cookie. In “.htaccess language” it is

    #Only for requests which start with "/lv/" or "/en/". 
    RewriteCond %{REQUEST_URI} ^/(lv|en)/.*$ [NC]

    #no need to set a cookie if you already have one with the same value
    RewriteCond %{HTTP_COOKIE} !lang=%1 [NC] 

    #rewrite to the same address, but set a cookie to the value captured in first RewriteCond
    RewriteRule . - [cookie=lang:%1:.%{HTTP_HOST}:144000:/]

– when the user requests the “root” of the portal and there is a “lang” cookie set, do a redirect to the particular language root page

  #only for the root page /
  RewriteCond %{REQUEST_URI} ^/$ [NC]

  #only if you have a "lang" cookie
  RewriteCond %{HTTP_COOKIE} lang=(en|lv) [NC] \

  #redirect (not rewrite!) to either /en/ or /lv/
  RewriteRule ^(.*)$ /%1/ [R=302,L]

So, after all modifications my .htaccess file looks like this:

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /

RewriteCond %{REQUEST_URI} ^/$ [NC]
RewriteCond %{HTTP_COOKIE} lang=(lv|en) [NC] 
RewriteRule ^(.*)$ /%1/ [R=302,L]

RewriteCond %{REQUEST_URI} ^/(lv|en)/.*$ [NC] 
RewriteCond %{HTTP_COOKIE} !lang=%1 [NC] 
RewriteRule . - [cookie=lang:%1:.%{HTTP_HOST}:144000:/]

RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress

Kārtošana latviešu valodā ar Microsoft SQL Server

July 8th, 2011

Jau kopš senām Microsoft SQL Server versijām ir bijusi problēma, ka latviešu valodas teksti tiek kārtoti nepareizi. Proti, pat tad, ja datubāzei iestatīta “Latviešu” valoda, netiek šķiroti īsie un garie burti (šeit ar “latviešu” saprotu tādas kolācijas kā, piemēram, ‘Latvian_CI_AS’ vai ‘SQL_Latvian_Cp1257_CI_AS_KI_WI’). Līdz ar to sarakstu bērziņš, avotiņš, āboliņš SQL serveris kārtoja kā āboliņš, avotiņš, bērziņš. Kā zinām no ābeces mācīšanās, latviešu alfabēts ir A, Ā, B, C, Č..., tas ir, diakritiskās zīmes ir atsevišķas un tās seko aiz tā paša burta bez diakritiskās zīmes, tāpēc pareizi būtu bijis avotiņš, āboliņš, bērziņš.

Kopš SQL server 2008 ir pieejama vēl viena kolāciju saime – tādas, kas sākas ar Latvian_100, piemēram, Latvian_100_CI_AS. Šajās kolācijās gan viss ir salabots un kārtošana tiešām strādā pareizi:

select VARDS from (
        select 'Bērziņš' as VARDS union
        select 'Āboliņš' as VARDS union
        select 'Avotiņš' as VARDS) as SAMPLES
order by VARDS collate Latvian_100_CI_AS

rezultāts ir

VARDS
-------
Avotiņš
Āboliņš
Bērziņš

Papildus informāciju var pasmelties MSDN dokumentācijā.

InfoPath form cleanup for faster load times

May 11th, 2011

My team has been working on an InfoPath 2007 form (using Visual Studio Tools for Applications) for a while. The form is bound to an XML structure having approximately 100 fields/properties. Since its initial build, the form has been modified, recompiled and re-packaged for quite a lot of times.

Users complained that form load times are quite long. It seemed that by every version they are getting even worse. The initial thought was that the delay was due to loading of SharePoint data (through secondary data sources). However, profiling showed that it takes less than a second to load them.

Recently, we found out that by each build the manifest.xsf file becomes larger and larger. The initial version was less than 100K, while the current one is 500K. The reason for such growth was – automatically generated “InfoPath variables” in the form. Something like this:

 <xsf:xmlToEdit name="SomeFieldName_3721" item="/my:SomeContainer/my:SomeSubNode/my:SubSubNode">
    <xsf:editWith autoComplete="no" component="xField"></xsf:editWith>
 </xsf:xmlToEdit>

The variables can be later referenced in InfoPath’s own XSLT views as well as in context menu definitions in the manifest.xsf. However, these variables are never cleaned up. The list is growing each time you save your form using form designer.

It seems that Infopath initializes each variable upon form load. The more variables you have, the longer it takes to load the form.

When there are thousands of such variables, cleanup is not very easy. Hence, I wrote a PowerShell script to do the job. It enumerates all the variables and looks for references to this variable in manifest.xsf and in all .xsd views of your InfoPath form.

Download the InfoPath manifest.xsf clean-up script here.

InfoPath forms in SharePoint

September 30th, 2010

These two articles just made my day. At least, they made my intranet portal a much better “place to live”.

Document title in SharePoint’s “ECB” dropdown menu

September 16th, 2010

Have you ever wondered why the SharePoint team decided to show only filename in dropdown menus, but not the title of the document? So when you save your file with an unreadable filename, it is displayed in your list and looks something like this (Historical note: it was not the case in SharePoint 2001. They changed it in SPPS2003 and in 2007 versions. 2010. has it fixed):

You can, of course, modify the “All Items” view and add the “Title” column, but it never has a link to the document, nor has it the fancy drop-down for editing (see the column “Title” in the picture).

So, I decided to dig into CAML schemas and to create a field which would display the title of the document and have the dropdown menu. To achieve that, one can use “computed fields” – fields which do not actually represent data editable by the user, but are only used for presentational purposes. Hence, one only has to define the DisplayPattern of that field.
There are two fields already built into SharePoint – the _EditMenuTableStart and _EditMenuTableEnd, which build the dropdown menu contents. What I had to add, was generation of the readable content. The most important part of the display pattern is this:

<IfEqual>
  <Expr1><LookupColumn Name="Title" /></Expr1>
 <Expr2></Expr2>
<Then>
     <Field Name="FileLeafRef" />
 </Then>
 <Else>
     <Column HTMLEncode="FALSE" Name="Title" Default="(no title)" />
  </Else>
</IfEqual>

It compares the value of the Title column to an empty string. If this is the case (you have uploaded a document with no title or created a folder using Explorer view), it just shows FileLeafRef – the filename. Otherwise, it displays the value of the title field.

In my case I had an existing document library which I wanted to “fix”. Hence, I created a PowerShell script, which adds my field to the library. I used “AddFieldAsXML()” method of SPList object to inject my CAML code into sharepoint.

Now it looks like this:

You can dowload the whole script here: http://tips.naivist.net/wp-content/CreateTitleField1033.ps1_.txt Rename it to .ps1 and run it .\CreateTitleField1033.ps1 -url http://yourserver/sites/somesite/someweb -ListName "Your list". Then modify the view settings to display the field.

If you are developing your own list defintion, you can use the particular fragment in schema.xml and have your field defined in a regular fashion.

param(
  [string] $url      =  "",
  [string] $ListName = ""
)

$sharepoint = [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")
$site=[Microsoft.Sharepoint.SPSite]($url);
$web=$site.openweb();
$list= $web.lists[$ListName];

$schema =@"
    <Field ID="{E2ABF8D3-6435-4773-A3D6-67508FEB7CF5}" ReadOnly="TRUE" 
             Type="Computed" 
             Name="KRItemLinkTitleDropdown" 
             DisplayName="Title" 
             DisplayNameSrcField="Title"
             AuthoringInfo="(dropdown displaying the title field)"
             EnableLookup="TRUE" 
             SourceID="http://schemas.microsoft.com/sharepoint/v3"
             StaticName="KRItemLinkTitleDropdown" FromBaseType="TRUE" ClassInfo="Menu">
         <FieldRefs>
          <FieldRef Name="Title" />
          <FieldRef Name="LinkTitleNoMenu" />
          <FieldRef Name="FSObjType" />
        </FieldRefs>
        <DisplayPattern>
           <Field Name="_EditMenuTableStart" />
           <HTML>
           <![CDATA[<A onfocus="OnLink(this)" HREF="]]></HTML>
       <IfEqual>
            <Expr1>
              <LookupColumn Name="FSObjType" />
            </Expr1>
            <Expr2>1</Expr2>
            <Then>
                <FieldSwitch>
                <Expr>
                  <GetVar Name="RecursiveView" />
                </Expr>
                <Case Value="1">
                  <LookupColumn Name="FileLeafRef" HTMLEncode="TRUE" />
                </Case>
                <Default>
                  <SetVar Name="UnencodedFilterLink">
                    <SetVar Name="RootFolder"><HTML>/</HTML>
                      <LookupColumn Name="FileRef" />
                    </SetVar>
                    <SetVar Name="FolderCTID">
                      <FieldSwitch>
                        <Expr>
                          <ListProperty Select="EnableContentTypes" />
                        </Expr>
                        <Case Value="1">
                          <Column Name="ContentTypeId" />
                        </Case>
                      </FieldSwitch>
                    </SetVar>
                    <FilterLink Default="" Paged="FALSE" />
                  </SetVar>
                  <GetVar Name="UnencodedFilterLink" HTMLEncode="TRUE" />
                </Default>
              </FieldSwitch>     
             </Then>
            <Else>
                <Field Name="ServerUrl" URLEncodeAsURL="TRUE" />
            </Else>
            </IfEqual>


       <HTML><![CDATA[" onclick="return DispEx(this,event,']]></HTML>
                  <ScriptQuote NotAddingQuote="TRUE">
                    <ServerProperty Select="HtmlTransform" />
                  </ScriptQuote><HTML><![CDATA[',']]></HTML>
                  <ScriptQuote NotAddingQuote="TRUE">
                    <ServerProperty Select="HtmlTrAcceptType">
                      <Column Name="File_x0020_Type" />
                    </ServerProperty>
                  </ScriptQuote><HTML><![CDATA[',']]></HTML>
                  <ScriptQuote NotAddingQuote="TRUE">
                    <ServerProperty Select="HtmlTrHandleUrl">
                      <Column Name="File_x0020_Type" />
                    </ServerProperty>
                  </ScriptQuote><HTML><![CDATA[',']]></HTML>
                  <ScriptQuote NotAddingQuote="TRUE">
                    <ServerProperty Select="HtmlTrProgId">
                      <Column Name="File_x0020_Type" />
                    </ServerProperty>
                  </ScriptQuote><HTML><![CDATA[',']]></HTML>
                  <ScriptQuote NotAddingQuote="TRUE">
                    <ListProperty Select="DefaultItemOpen" />
                  </ScriptQuote><HTML><![CDATA[',']]></HTML>
                  <ScriptQuote NotAddingQuote="TRUE">
                    <MapToControl>
                      <Column Name="HTML_x0020_File_x0020_Type" /><HTML>|</HTML>
                      <Column Name="File_x0020_Type" />
                    </MapToControl>
                  </ScriptQuote><HTML><![CDATA[',']]></HTML>
                  <ScriptQuote NotAddingQuote="TRUE">
                    <Column Name="HTML_x0020_File_x0020_Type" />
                  </ScriptQuote><HTML><![CDATA[',']]></HTML>
                  <ScriptQuote NotAddingQuote="TRUE">
                    <ServerProperty Select="GetServerFileRedirect">
                      <Field Name="ServerUrl" /><HTML>|</HTML>
                      <Column Name="HTML_x0020_File_x0020_Type" />
                    </ServerProperty>
                  </ScriptQuote><HTML><![CDATA[',']]></HTML>
                  <ScriptQuote NotAddingQuote="TRUE">
                    <Column Name="CheckoutUser" />
                  </ScriptQuote><HTML><![CDATA[',']]></HTML>
                  <ScriptQuote NotAddingQuote="TRUE">
                    <UserID AllowAnonymous="TRUE" />
                  </ScriptQuote><HTML><![CDATA[',']]></HTML>
                  <ScriptQuote NotAddingQuote="TRUE">
                    <ListProperty Select="ForceCheckout" />
                  </ScriptQuote><HTML><![CDATA[',']]></HTML>
                  <ScriptQuote NotAddingQuote="TRUE">
                    <Field Name="IsCheckedoutToLocal" />
                  </ScriptQuote><HTML><![CDATA[',']]></HTML>
                  <ScriptQuote NotAddingQuote="TRUE">
                    <Field Name="PermMask" />
                  </ScriptQuote><HTML><![CDATA[')">]]></HTML>
                  <UrlBaseName HTMLEncode="TRUE">
                  </UrlBaseName>
                    <IfEqual>
                    <Expr1>
                      <LookupColumn Name="Title" />
                    </Expr1>
                    <Expr2></Expr2>
                    <Then>
                        <Field Name="FileLeafRef" />
                     </Then>
                    <Else>
                        <Column HTMLEncode="FALSE" Name="Title" Default="(no title)" />
                    </Else>
                    </IfEqual>
                  <IfEqual>
                    <Expr1>
                      <GetVar Name="ShowAccessibleIcon" />
                    </Expr1>
                    <Expr2>1</Expr2>
                    <Then><HTML><![CDATA[<img src="/_layouts/images/blank.gif" class="ms-hidden" border=0 width=1 height=1>]]></HTML>
                    </Then>
                  </IfEqual><HTML><![CDATA[</A>]]></HTML>
                  <IfNew Name="Created_x0020_Date"><HTML><![CDATA[<IMG SRC="/_layouts/1033/images/new.gif" alt="New!">]]></HTML>
                  </IfNew>
                  <Field Name="_EditMenuTableEnd" />
        </DisplayPattern>
      </Field>
"@
$fieldname = $list.fields.AddFieldAsXml($schema);
$list.update();

String concatenation versus array.join() in JavaScript

January 19th, 2010

To answer a question on StackOverflow, I made an experiment which actually qualifies for a separate blog post.

So, I compared the speed of str1+str2 concatenation and array.push(str1, str2).join() methods. The code I used was quite simple:

var iIterations =800000;
var d1 = (new Date()).valueOf();
str1 = "";
for (var i = 0; i<iIterations; i++) str1 = str1 + Math.random().toString();
var d2 = (new Date()).valueOf();
log("Time (strings): " + (d2-d1));

var d3 = (new Date()).valueOf();
arr1 = [];
for (var i = 0; i<iIterations; i++)arr1.push(Math.random().toString());
var str2 = arr1.join("");
var d4 = (new Date()).valueOf();
log("Time (arrays): " + (d4-d3));

I tested it in IE8 and FireFox 3.5.5, both on a Windows 7 x64.

In the beginning I tested on small number of iterations (some hundred, some thousand items). The results were unpredictable (sometimes string concatenation took 0 milliseconds, sometimes it took 16 milliseconds, the same for array joining).

When I increased the count to 50’000, the results were different in different browsers – in IE the string concatenation was faster (94 milliseconds) and join was slower(125 milliseconds), while in Firefox the array join was faster (113 milliseconds) than string joining (117 milliseconds).

Then I increased the count to 500’000. Now the array.join() was slower than string concatenation in both browsers: string concat 937ms in IE, 1155 ms in Firefox, array join 1265 in IE, 1207 in Firefox.

Maximum iteration count I could test in IE without having “the script is taking too long to execute” was 850’000. Then IE was 1593 for string concatenation and 2046 for array join, Firefox had 2101 for string concatenation and 2249 for array join.

Results – if the number of iterations is small, you can try to use array.join(), as it might be faster in Firefox. When the number increases, the string1+string2 method is faster.

UPDATE
I performed the test on IE6 (WindowsXP). The process stopped to respond immediately and never ended, if I tried the test on more than 100’000 iterations. On 40’000 iterations the results were

Time (strings): 59175 ms
Time (arrays): 220 ms

This means – if you need to support IE6, choose array.join() which is way faster than string concatenation.

Restoring the top navigation bar of a SharePoint site

December 1st, 2009

It’s a known issue that if you delete the “Top Navigation” menu through the API in SharePoint 2007, you cannot restore it easily. I came across this problem in one of my development sites. When looking for a solution, I found this article, saying you have to do it through the database.

The code below solves the problem programmatically (and yes, it changes the database directly). Of course, it’s an unsupported solution – if you use it, you may get in trouble with Microsoft official support.

string sSiteUrl = "http://moss/sites/yoursite/";
//after the code gets executed, we have to dispose the 
//spweb object, since the Navigation object of the current SPWeb instance will contain wrong information
using (SPSite oSite = new SPSite(sSiteUrl))
{
    using (SPWeb oWeb = oSite.RootWeb)
    {
        //check if the top navigation bar is already there
        SPNavigationNode oTopnav = null;
        try
        {
            oTopnav = oWeb.Navigation.GetNodeById(1002);
        }
        catch (Exception)
        {
            oTopnav = null;
        }

    if (oTopnav == null)
    {
        //we've found out there is no top navigation bar. Let's create one;

        //we create a temporary navigation node pointing back to web site root
        Microsoft.SharePoint.Navigation.SPNavigationNode oTempNode = new Microsoft.SharePoint.Navigation.SPNavigationNode("Navigation", oWeb.ServerRelativeUrl);
        //we add this node to the "global" collection (where quick launch and top nav bar normally live)
        oWeb.Navigation.GlobalNodes.AddAsLast(oTempNode);

        //now the dirty work - go to the database and change the ID of the navigation node
        System.Data.SqlClient.SqlConnection oConn = new System.Data.SqlClient.SqlConnection(oSite.ContentDatabase.DatabaseConnectionString);
        System.Data.SqlClient.SqlCommand oCmd = new System.Data.SqlClient.SqlCommand();
        oCmd.Connection = oConn;
        oCmd.CommandType = System.Data.CommandType.Text;
        //we only update the node which has the same ID as the one we just created, but, to be completely sure that 
        //nothing else gets changed, we add a requirement to have the same siteid and webid
        oCmd.CommandText = @"UPDATE NavNodes SET Eid=1002 WHERE (Eid=" + oTempNode.Id.ToString() + 
             @") AND (SiteId='" + oSite.ID.ToString("D") + "') AND (WebId='" + oWeb.ID.ToString("D") + "')"; ;
        oConn.Open();
        oCmd.ExecuteNonQuery();
        //closing the connection
        oConn.Dispose();
    }
}

} //the oSite object is disposed here

“_vti_bin” folder defined

September 11th, 2009

Ever wondered why SharePoint “lists web service” (i.e., lists.asmx) lives in an interesting address of http://mossserver/sites/somesite/_vti_bin/lists.asmx ?

This is actually not the only place where you can see the magic _vti_something. I remember myself deleting such directories from “wwwroot” quite often back in the days “everyone” used MS FrontPage. FrontPage used to create the folders “_vti_bin”, “_vti_cnf” on your computer locally as you developed a website .. and also on the server, if you used “Frontpage Extensions” for publishing. The _bin folder would normally contain executables required for FP server components (as far as I understand, something like cgi_bin).

So, even though FP is announced dead, guys from the SharePoint team apparently like the idea of puting executables in the _vti_bin folder which was used by FrontPage Extensions.

Anyway, the question is, what does the acronym “VTI” mean. Apparently, Microsoft did not develop MS FrontPage from scratch, they acquiredVermeer Technologies Inc”. Thus the name of “bin” folder in FrontPage. Thus the URL for lists.asmx in SharePoint.

Shooting yourself in the foot

October 8th, 2008

Ideja no “labās prakses” prezentācijas

  • C: You shoot yourself in the foot
  • PHP: You shoot yourself in the foot with a gun made from pieces taken from 300 other guns
  • Ruby on Rails: You want to shoot yourself in the foot, but the convention is to shoot yourself in the head

(pievienošu no sevis)

  • SharePoint: You want to shoot yourself in the foot, but first you have to “IISRESET /noforce”