måndag 27 augusti 2012

Programmatiskt skapa ett timestamp field i en sharepointlista

Då var det dags för lite Sharepoint. Inte min favorit-kopp té. Men ibland får man offra sig.

Fick i uppdrag att skapa en lista programmatiskt som innehåller bl.a. ett timestamp-fält. 

Eftersom jag hittat ytterst sparsamt med dokumentation och exempel kring hur man gör detta med kod så bestämde jag mig för att göra det omvända, dvs. skapa upp allt via GUI och sedan debugga och accessa fältet för att vad Sharepoint gjorde.

Det visar sig att man gör som här:
  • Sharepoint tillhandahåller alltid ett dolt fält "Created". Vill man lösa det snabbt kan man bara ändra egenskaper på detta fält så att det visas (toggla SPField-egenskapen ShowInDisplayForm), alt. lägga till det i önskad vy.
  • Det går att skapa ett s.k. "SPFieldCalculated" som läser upp innehållet från "Created" och formatterar det som ett datum.
För det senare fallet så gör man nåt i stil med:

SPSecurity.RunWithElevatedPrivileges(delegate()
{
    using (SPSite site = new SPSite(siteUrl))
    {
        using (SPWeb web = site.RootWeb)
        {
            SPList list = web.Lists["test"];
            string fieldName = list.Fields.Add("Timestamptest", SPFieldType.Calculated, false);
            SPFieldCalculated field = list.Fields[fieldName] as SPFieldCalculated;
            field.Formula = "=Created";
            field.OutputType = SPFieldType.DateTime;
            field.ShowInEditForm = false;
            field.Update();
            list.Update();
            SPView defaultView = list.DefaultView;
            defaultView.ViewFields.Add(field);
            defaultView.Update();

        }
    }
});

Testar:







Et voila! En timestamp kolumn som pekar på "Created". Gött!

tisdag 21 augusti 2012

Ändra tabeller i SQL Server Management Studio



När man utvecklar en databas och initialt behöver göra stora ändringar som medför att t.ex. tabellen behöver scriptas om, kan det vara riktigt irriterande att mötas av följande meddelande:







Vän av ordning kanske tycker att en dialog, "vill du göra detta?", beroende på användarens privilegier hade varit på sin plats, men det får vi alltså inte nu.

Istället är det läge att ta sig till Tools -> Options -> Designers -> Table and Database Designers och se till att checkboxen "Prevent saving changes that require table re-creation" är urkryssad.

















Och nu kan jag rodda bäst jag vill! Obs. tänk på att du kan förlora data i din tabell och att detta arbetssätt inte lämpar sig i prod-miljöer :)

torsdag 16 augusti 2012

Visual Studio Tips & Trix vol 2

Ytterligare lite smågrejjer som underlättar min vardag :)

1. Track Active Item in Solution Explorer

När man har en fil öppen och jobbar i ett namespace och kanske behöver komma åt en närliggande fil i samma projekt el. dylikt så är denna inställning ett måste.

Gå till Tools -> Options -> Projects & Solutions -> General, och du bör se följande vy:

Visual Studio 2010















Medför helt enkelt att när du öppnar en fil i din solution. Så kommer solutionexplorern att automatiskt hilighta denna fil och positionera trädvyn så att filen är synlig. Max perry!

2. Cmd i Windows Explorers adressfält för att öppna aktuell folder i kommandoprompten

Detta tips tog jag nyligen del av när jag såg en webcast om Orchard av Kevin Keubler. Är egentligen inte relaterat till Visual Studio, snarare Windows. Men ändå göttigt.

Säg att du jobbar i Visual Studio med en konsolapp, och du vill köra den genom cmd.exe. Det snabbaste sättet att få upp bin-foldern för att hitta den är att antingen:

1. visa dolda filer, högerklicka i solution explorer och välja "Open Folder in Windows Explorer".
2. högerklicka i projekt-foldern, välja "Open Folder in Windows Explorer" och därifrån navigera sig till exe-filen (\bin\<release/debug>\).
















Nästa steg är själva knepet, skriv in cmd i explorer-fönstrets adressfält och tryck enter. Å vips så öppnas kommandoprompten i korrekt katalog. Gött!

3. Utnyttja snippets

Snippets är ett bra sätt för oss lata .NET-programmerare att få saker gjorda med en hand så vi kan koncentrera oss på annat viktigt (att dricka kaffe med andra).

En snippet är en fördefinierad mall över en kodsekvens som du accessar genom att börja skriva snippetens namn, den dyker då upp i intellisensen. Välj den genom att trycka ENTER följt av TAB.

Några ex.:

  • ctor
  • for
  • foreach


4. Skriva egen snippet

Nu är det ju så att de flesta inbyggda snippets är väldigt basic och enbart sparar oss några futtiga knapptryckningar. Därför kan man med fördel skapa egna. Ett bra exempel är ju om man vill behålla en viss struktur/kodstandard på olika klasser med standardiserat användande av #region osv.

Jag t.ex. använder singleton-klasser flitigt i olika webbprojekt och lyckas ofta glömma bort syntaxen (har outsourcat min hjärna till Google).

Nåväl, här är ett ex. på hur det går till:
1. Skapa en ny tom xml-fil i Visual Studio, döp den till <snippetnamn>.snippet.
2. Töm filen på xml-headern, högerklicka i editorfönstret, välj "Insert snippet" och sedan "snippet" i menyn som dyker upp.
3. Deklarera din snippet. Här är mitt "singleton"-exempel:


<CodeSnippet Format="1.0.0" xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <Header>
    <Title>Singleton snippet</Title>
    <Author>Maets</Author>
    <Shortcut>singleton</Shortcut>
    <Description>Inserts a class as a singleton</Description>
    <SnippetTypes>
      <SnippetType>Expansion</SnippetType>
    </SnippetTypes>
  </Header>
  <Snippet>
    <Declarations>
      <Literal>
        <ID>SingletonName</ID>
        <ToolTip>Replace with the name of your singleton class</ToolTip>
        <Default>SingletonName</Default>
      </Literal>
    </Declarations>
    <Code Language="CSharp">
      <![CDATA[
        /// <summary>
        ///
        /// </summary>
        public class $SingletonName$
        {
          #region Singleton definition
       
          private static $SingletonName$ _instance;
         
          public static $SingletonName$ Instance
          {
            get
            {
              if (_instance == null)
                _instance = new $SingletonName$();
              return _instance;
            }
          }
         
          public $SingletonName$() { }
       
          #endregion
       
          #region Public methods
          #endregion
         
          #region Private methods
          #endregion
        }
      ]]>
    </Code>
  </Snippet>
</CodeSnippet>



4. Spara filen och gå till Tools -> Code Snippets Manager. Välj "import" i dialogen som dyker upp. Leta fram din snippet fil, välj vart du vill importera din snippet och klicka "Finish".
5. Dags att testa! Skapa en ny klass i Visual studio, töm den på innehåll (behåll using-statements &  namespace). Ställ dig inuti namespace definitionen och skriv singleton.











Dunka in TAB och koden dyker upp. Notera att klassens namn är markerat och highlightat. Skriv in namnet på din singletonklass och tryck ENTER så utförs replace på samtliga ställen där platshållaren definierats.


























Gött mos! :)



fredag 10 augusti 2012

Införa lite finkultur i ditt CMS

Pillade lite med språk i Orchard CMS och kände ett starkt behov av att kunna uttrycka mig på mitt modersmål.

Googlade lite och hittade CultureAndRegionInfoBuilder-klassen som MS gömt i dll:en sysglobl.dll men som tillhör namespacet System.Globalization. Så för att kunna använda den måste du referera sysglobl.dll.

Skapade en liten konsolapp:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;

namespace CultureCreator
{
    class Program
    {
        static void Main(string[] args)
        {
            new Program(args);
        }

        public Program(string[] args)
        {
            if (args == null || args.Length == 0)
            {
                Console.WriteLine("Error: No args supplied.");
                return;
            }
            try
            {
                if (args[0].ToLower() == "register")
                {
                    CreateCulture();
                    Console.WriteLine("Culture created.");
                }
                else if (args[0].ToLower() == "unregister")
                {
                    RemoveCulture();
                    Console.WriteLine("Culture removed.");
                }
            }
            catch (Exception)
            {
                Console.WriteLine("Could not perform the operation, make sure you are running the script as an administrator!");
            }
        }
       
        public void CreateCulture()
        {
            if (CultureInfo.GetCultures(CultureTypes.AllCultures).Any(c => c.Name == "vl-SE"))
            {
                Console.WriteLine("The culture has already been created!");
                return;
            }
               
            CultureAndRegionInfoBuilder cib = null;
            cib = new CultureAndRegionInfoBuilder("vl-SE", CultureAndRegionModifiers.None);
           
            CultureInfo ci = new CultureInfo("sv-SE");
            cib.LoadDataFromCultureInfo(ci);

            RegionInfo ri = new RegionInfo("SE");
            cib.LoadDataFromRegionInfo(ri);

            cib.CultureEnglishName = "Wermlandish (Sweden)";
            cib.CultureNativeName = "värmländska (Sverige)";

            cib.Register();
        }

        public void RemoveCulture()
        {
            CultureAndRegionInfoBuilder.Unregister("vl-SE");
        }
    }
}


Körde den:




Hoppar in i Orchards dashboard och kollar lista över tillgängliga språk:












Jajjamen! Där satt 'an!

Skapar en översättning av en text på min sida:
























Kollar att min språklistare på sidan fångar upp det tillagda språket:











Yes, ser gôrfint ut!

Nu testar vi att välja språket!






Vackert! Funkar antagligen precis lika fint i andra CMS:er.

Ps.:
Upptäckte att det inte var lika lätt att bli av med det vackra språket i Orchard (antar att Orchard fäste sig vid det). Det fanns nämligen ingen removeknapp för detta språk i GUI:t. Därför får man först städa bort gjorda översättningar, sedan kika in i databasen och köra:

DELETE FROM dbo.Orchard_Framework_CultureRecord WHERE Culture = 'vl-SE'

Om detta beror på felaktigheter i min registrerade Culture eller på någon Orchard-bugg låter jag vara osagt :)


torsdag 9 augusti 2012

Single-window mode i Gimp 2.8

Windowsporten till Gimp 2.8 har funnit ute en tid nu och innehåller en go feature som jag saknat länge.

Nämligen "Single-Window Mode".

Det är sprättlätt att byta till detta läge.














Nu håller gimp ihop alla delar i ett fönster. De dockningsbara dialogerna kan dra å släppas på valfritt ställe samt ges önskat utrymme med drag n drop. Öppna Gimp-fönster lägger sig som tabbar.

Äntligen slipper jag leta efter det där dret-fönstret som försvunnit iväg på äventyr till nån annan skärm :)

tisdag 7 augusti 2012

Komma runt bugg med hierarkiska menyer i Orchard CMS 1.5.1

Hej!

Idag stötte jag på en bugg i Orchard CMS 1.5.1 (http://orchard.codeplex.com/workitem/18896)

Problemet kan kort beskrivas som följande:
När man har en orchard-site med hierarkiska menyer (3 nivåer+) så hämtas inte hierarkin korrekt. Utan modellen returnerar sub-meny-items både på den inre och den yttersta nivån inuti objektet. Följdaktligen får man dubletter.

I mitt fall har jag en toppmeny där översta menyhierarkin visas och en vänstermeny som visar övriga nivåer, och buggen artar sig såhär i vänstermenyn:

















Eftersom jag inte hinner sätta mig in i källkoden för Orchard så blir det ett redigt fulhack för att komma runt problemet:

1. Kopiera meny view till aktuell Theme-katalog
Leta upp filen Menu.cshtml (under <sökvägtillorchard>\Core\Shapes\Views) och kopiera in den i din view foldern under din theme-katalog (i mitt ex. <sökvägtillorchard>\Themes\<namnpåtema>\Views\).

2. Editera och ersätt hela innehållet i filen med följande:


@functions {
public void RecurseItems(IList<dynamic> items, ref Dictionary<string, string> dict) {
if (items == null)
return;

foreach(var item in items) {
if (item.Items != null && item.Items.Count > 0)
RecurseItems(item.Items, ref dict);

if (dict.ContainsKey(item.Content.MenuPosition))
return;
else
dict.Add(item.Content.MenuPosition, string.Empty);
}
}
}

@{
    // Model is Model.Menu from the layout (Layout.Menu)
    var tag = Tag(Model, "ul");

    var items = (IList<dynamic>)Enumerable.Cast<dynamic>(Model.Items);

    if (items.Any()) {
        items[0].Classes.Add("first");
        items[items.Count - 1].Classes.Add("last");
    }  

/* Remove duplicate entries (bug: http://orchard.codeplex.com/workitem/18896) */
var alreadyAdded = new Dictionary<string, string>();
var filteredItems = new List<dynamic>();

foreach(var item in items) {
if (alreadyAdded.ContainsKey(item.Content.MenuPosition)) {
continue;
} else {
filteredItems.Add(item);
alreadyAdded.Add(item.Content.MenuPosition, string.Empty);
// Recurse inner items if any
if (item.Items != null && item.Items.Count > 0) {
RecurseItems(item.Items, ref alreadyAdded);
}
}
}
}

<nav>
    @tag.StartElement
        @* see MenuItem shape template *@
        @DisplayChildren(filteredItems)
    @tag.EndElement
</nav>


Kort och gött en rekursiv funktion som fyller en dictionary med redan tillagda poster. För varje nytt menyitem kollar vi mot dictionaryn innan vi lägger till den i den filtrerade listan. Slutligen skickar vi in den filtrerade listan som argument till DisplayChildren (istället för som tidigare Model).

Nu visas istället:















Success!

Som synes alltså riktigt fulhack med inlinekod, jag löser inte grundproblemet, och det kan ge prestandaproblem på en större site. Men det funkar för mig i detta fall eftersom jag bara bygger en prototyp.

Följer upp inlägget när Orchard-teamet löst felet, ska bli kul å se hur lång tid det tar :)