Fasil Hayat's blog

...C#, .Net, Sharepoint, BizTalk, JQuery...

Først skal IIS'en, der hoster Sharepoint 2010 sitet konfigureres for at WCF Services kommer til at fungere.

- Åbn IIS'en
- Find Sharepoint sitet og gå ind under 'Authentication'
- Sæt 'Anonymous Authentication' til disabled
- Sæt 'Basic Authentication' til enabled (hvis man sidder på en udviklerboks. Skal ellers være disabled).

Herefter laver man en Service reference til fx. 'http://MinBoks/sites/MitSite/_vti_bin/ListData.svc' i VS2010.
Man vil herefter få adgang til 'MitSiteDataContext', som skal benyttes for at få adgang til alle SP lister, der findes under 'MitSite'.

Man kan gøre brug af LINQ for at tilgå sharepoint 2010 lister på 'MitSite'. Inden da skal kontekst objekt sættes op. og Credentials skal sættes op. Bemærk kun credentials til en bruger, der har læse rettigheder til en SP 2010 liste, kan afvikle nedenstående kode.

   1:  var host = new Uri("http://MinBoks/sites/MitSite");
   2:  var kontekst = new MitSiteDataContext(new Uri
   3:              (string.Format("{0}/_vti_bin/ListData.svc", host)));
   4:  kontekst.Credentials = CredentialCache.DefaultCredentials;


LINQ udtryk der henter alle SPListItems fra 'MinSPListe' og danner en List<Artikel>:

   1:  var artikler = kontekst.MinSPListe.Where(
   2:                  p => p.Name == "John Doe").Select(p => new Artikel()
   3:                  {
   4:                      Title = p.Title,
   5:                      Id = p.Id,
   6:                      Kode = p.kode
   7:                  }).ToList();


Ved indhentning af et specifikt SPListItem:

   1:  var artikel = kontekst.MinSPListe.Where(
   2:                  p => p.Id == 5).Select(p => new Artikel()
   3:                  {
   4:                      Title = p.Title,
   5:                      Id = p.Id,
   6:                      Kode = p.kode,
   7:                      Name = p.Name,
   8:                      Path = p.Path
   9:                  }).FirstOrDefault();

 



Der findes mange eksempler på nettet, hvor man viser oprettelse af labels, tekstfelter, checkboks felter og dropdown menuer etc. i 'CreateChildControls()' scopet i C# eller VB.Net filen.
Dette har jeg altid set som et skridt tilbage, som næsten kan sammenlignes med de gode gamle VBScript dage, hvor man skrev html kode ud fx. i response.write eller dengang man kodede Java servlets, og man ikke havde muligheden for at abstrahere markup fra serverside kode. En god måde at undgå at erklære serverkontrollerne deklarativt i 'CreateChildControls()', er ved at gøre brug af usercontrols. Her bibeholder man opdelingen i markup fil og en codebehind fil.

Derfor dette hurtigt lille eksempel på, hvordan man beriger en webpart med en asp.net userkontrol under 'CreateChildControls()'.

Sådan indlæses en usercontrol i webpart kode under 'CreateChildControls()':

using System.Web.UI.WebControls.WebParts;
 
namespace MyCompany.MyProduct.MyWebparts {
    /// <summary>
    /// MyWebpart class.
    /// </summary>
    public class MyWebpart : WebPart {
        /// <summary>
        /// 
        /// </summary>
        protected override void CreateChildControls() {
            var ctrl = Page.LoadControl("~/_ControlTemplates/MyWebpart/MyUsrCtrl.ascx");
            Controls.Add(ctrl);
        }
    }
}

 

Dette er helt klart en meget renere tilgang til programmering af webparts, og man udnytter .net mulighederne fuldt ud ved at synliggøre adskillelsen mellem markup og codebehind fil.



Best practice for oprettelse af sharepoint lister er gennem sharepoint features. Oprettelsen af sharepoint lister skal med andre ord ske gennem aktivering af features, og ikke gennem afvikling af *.exe filer, man har kodet til det formål. Hvorfor er det best practice? Det er det af den simple grund, at man udnytter Sharepoint mulighederne fuldt ud, og placerer administrations delen i CA (Central Adminitration). STSAdm kommandoer bundlet i scripts kan herefter simulere de administrative klik man ellers ville foretage i CA. Selvom sharepoint api'et er tilgængeligt i kode, skal man tænke sig godt om og have en god grund til at oprette lister herigennem.

Dette indlæg vil handle om, hvordan man opretter sharepoint lister gennem features, og ved aktivering opretter listen et angivet sted. Man kan opnå samme effekt ved at lave en 'feature receiver', der opretter en liste ved aktivering af featuren, men dette eksempel vil vise den samme opnåelse uden en 'feature receiver'.

Først skal skemaet for listen defineres. Skemaet vil indholde definitioner på de forskellige kollonner samt deres type. Det nemmeste er helt klart at lave listen manuelt. Dvs. oprette listen på ens udviklingsmiljø, der hvor den skal placeres i produktion. Skemaet kan også skrives i hånden, men det er en del besværligt at gøre, så man sparer en masse tid ved at oprette listen, med alle de relevante felter (kollonner).

!Bemærk: Undgå navne med specielle tegn dvs. danske 'æ', 'ø' og 'å', erstat disse med henholdsvis 'ae', 'oe' og 'aa'. Når man opretter felterne første gang bliver deres første navn systemnavn og displaynavn. Dvs. at det første navn altid vil være et internt systemnavn og alle fremtidige omdøbninger vil kun blive rettet i displaynavet, der er det navn, der bliver vist. Har man stavet forkert, så slet listen og start forfra. Det vil spare for en masse problemer.

Er alle navnene blevet indtastet på felterne / kollonnerne, omdøb herefter da alle ord med danske 'ae', 'oe' og 'aa' til 'æ', 'ø' og 'å'. Dette vil medføre at displaynavnet vil stå rigtigt.

Åbn herefter SharePoint Manager 2007 eller SharePointManager 2010. Lokalisér sharepoint sitet og find frem til den oprettede liste og klik på 'Schema Xml' fanebladet. Her står skemaet for den oprettede liste. Kopier herefter indholdet af skemaet ind i en nyoprettet 'schema.xml' fil. Inde i 'schema.xml' filen kan der evt. refereres til tekst nøgler i *.resx (resource) filen. Flyt 'schema.xml' filen i fx. en mappe omdøbt til 'MySolutionName.MyFeatureName'. I denne mappe skal der nogle yderligere filer, som er ret faste samme med sharepoint lister. Disse filer kan derfor hentes her.
Placér disse filer sammen med 'schema.xml' filen i 'MySolutionName.MyFeatureName' mappen. Opret herefter endnu en xml fil i 'MySolutionName.MyFeatureName' mappen, som er navnet på featuren fx. 'MyFeatureNameList.xml'.

Mappen kommer til at se således ud:

SPList feature

I 'MySolutionName.MyFeatureName' skal skabelonen af listen placeres, og indholdet af 'MyFeatureNameList.xml' filen kan fx. se således ud:
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
    <ListInstance
           FeatureId="ee91fb59-a939-475c-9ca2-b8e6085b0684"
           Title="$Resources:MyCompany.MySolutionName.Portal, ListName_MyListName;"
           Description="$Resources:MyCompany.MySolutionName.Portal, ListDescription_MyListDescription;"
           Id="MyFeatureNameListe"
           TemplateType="100"
           Url="Lists/MySPListName">
    </ListInstance>          
</Elements>

Bemærk skabelon typen '100' er for 'Generic' type lister. En fuld liste af de forskellige List definitioner kan ses her. Under 'Url' angives placering for instansen af listen. Som man kan se i eksemplet, er der ligeledes angivet referencer til en resource. Indtast en evt. en klartekst for titlen og beskrivelsen af listen, hvis ikke en resource fil er tilrådighed med indholdende tekster.

Nu er skabelonen af listen på plads, og en ny mappe kaldet 'ListTemplates' oprettes side om side med 'MySolutionName.MyFeatureName' mappen. I samme omgang oprettes en 'feature.xml' og en 'elements.xml' fil.

Feature indhold

I 'feature.xml' filen vil defintionen på featuren angives:
<?xml version="1.0" encoding="utf-8"?>
<Feature  Id="ee91fb59-a939-475c-9ca2-b8e6085b0684"
          Title="$Resources:MyCompany.MySolutionName.Portal, ListName_MyListName;"
          Description="$Resources:MyCompany.MySolutionName.Portal, ListDescription_MyListDescription;"
          Version="1.0.0.0"
          Hidden="FALSE"
          Scope="Web"
          DefaultResourceFile="core"
          xmlns="http://schemas.microsoft.com/sharepoint/">
    <ElementManifests>
      <ElementManifest Location="ListTemplates\MyFeatureNameList.xml" />
      <ElementManifest Location="MySolutionName.MyFeatureName\MyFeatureNameList.xml"/>
    </ElementManifests>
</Feature>

Feature id er en GUID, som skal genereres for at sikre unik id. Denne feature GUID skal ligeledes angives i FeatureId i 'MyFeatureNameList.xml' filen. Den måde bindes instansen af listen til denne feature. Under '<ElementManifests>' angives lokationen til skabelon filen samt instans filen. Instans filen 'MySolutionName.MyFeatureName\MyFeatureNameList.xml' er allerede på plads.

I 'elements.xml' filen angives følgende:
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
    <ListInstance
           FeatureId="ee91fb59-a939-475c-9ca2-b8e6085b0684"
           Title="$Resources:MyCompany.MySolutionName.Portal,
ListName_MyListName;"
           Description="$Resources:
MyCompany.MySolutionName.Portal, ListDescription_MyListDescription;"
           Id="1036"
           TemplateType="100"
           Url="/Lists/
MySPListName">
    </ListInstance>          
</Elements>


Under 'ListTemplates' oprettes en ny 'MyFeatureNameList.xml' fil.
Indholdet af denne fil:
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <ListTemplate Name="MySolutionName.MyFeatureName" 
                Type="100"
                BaseType="0"
                OnQuickLaunch="TRUE"
                SecurityBits="14"
                Sequence="410"
                DisplayName="$Resources:MyCompany.MySolutionName.Portal.da-DK,ListName_MyListName;"
                Description="$Resources:MyCompany.MySolutionName.Portal, ListDescription_MyListDescription;"
                Image="/_layouts/images/itgen.gif">
    </ListTemplate>
</Elements>

Opret en mappe med et sigende navn på listen. I eksemplet er 'MyList' mappen anvendt jf. illustrationen. Kopier 'schema.xml' filen fra 'MySolutionName.MyFeatureName' ind i 'MyList' mappen. Hent de øvrige filer under 'MyList' mappen her.


Nu er featuren med en custom sharepoint liste klar, og skal blot pakketeres i en *.wsp fil. Der er flere måder at danne en *.wsp pakke fil på. Man kan enten skrive scriptet selv, eller benytte sig af wsp builder. For at benytte sig af wsp builder, skal denne addon være installeret i Visual Studio.

Strukturen i Visual Studio skal se således ud:

VS Struktur

I 'Resources' kan *.resx filerne med tekst nøglerne og teksten naturligvis placeres.
Hørjeklik herefter på projektet og vælg 'Build WSP'. En *.wsp fil vil herefter blive oprettet, og denne *.wsp pakke kan herefter deployeres på de relevante Sharepoint servere.
For at aktivere og instansiere featuren og listen, kan disse scripts med almindelige STSAdm kommandoer bruges.

Opret to bat filer med følgende navne:
'InstallMyFeature.bat'
'DeactivateRemoveMyFeature.bat'

'InstallMyFeature.bat' indholder følgende STSAdm kommandoer:
@echo on
set stsadm="C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\BIN\STSADM.EXE"

%stsadm% -o installfeature -name MySolutionName.MyFeatureName
%stsadm% -o activatefeature -name MySolutionName.MyFeatureName -url http://localhost -force

echo Finished installing and activating 'MyFeatureName' feature

pause
@echo on

'DeactivateRemoveMyFeature.bat' indholder følgende STSAdm kommandoer:
@echo off
set stsadm="C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\BIN\STSADM.EXE"

%stsadm% -o forcedeletelist -url "http://localhost/Lists/MySPListName"

echo Deactivating 'MySolutionName.MyFeatureName' feature...
%stsadm% -o deactivatefeature -filename "MySolutionName.MyFeatureName\feature.xml" -url "http://localhost" -force

echo Unistalling 'MyFeatureName' feature...
%stsadm% -o uninstallfeature -name "MySolutionName.MyFeatureName" -force

iisreset
pause
@echo on

Kør 'InstallMyFeature.bat' efter *.wsp pakken er udrullet på serveren. Åbn 'Central Administration' og kig under webfunktioner og bekræft at 'MyFeature' er installeret. Herefter kan Url'en på listen tastes ind og bekræftes at den er oprettet under aktivering af featuren efter afvikling af bat scriptet. Det andet script bruges til inaktivering samt oprydning, dvs. listen bliver slettet.

Hent de to *.bat scripts her.
Hent hele eksemplet her.



Selvom der stadigvæk er bagud kompabilitet af STSAdm kommandoer relateret til administrativt arbejde med sharepoint portaler, så peges der fremadrettet mere mod PowerShell scripting til udførsler af de disse opgaver.
Her er en liste over de typiske STSAdm kommandoer, jeg har haft brug for igennem deployment eller andet administrativt arbejde på sharepoint servere, sammen med den tilsvarende PowerShell variant samt et eksempel.

STSAdm operation
Windows PowerShell cmdlet
Createsite New-SPSite
New-SPSite http://mitsite/sites/test -OwnerAlias "DOMAIN\Eksempel" –Language 1033
Createweb New-SPWeb
New-SPWeb http://mitsite/subweb -Template "STS#0"
Deactivatefeature Disable-SPFeature
Disable-SPFeature –identity "MinFeature" -URL http://mitsite
Deletesite Remove-SPSite
Remove-SPSite –Identity "http://sitename" –GradualDelete –Confirm:$False
Deletesolution Remove-SPSolution
Remove-SPSolution -Identity minpakke.wsp
Deleteuser Remove-SPUser
Remove-SPUser "MITDOMAIN\fasil" –web http://mitsite/mitweb
Deleteweb Remove-SPWeb
Remove-SPWeb http://mitsite/subsite
Deploysolution Install-SPSolution
Install-SPSolution -Identity pakke.wsp -GACDeployment
Execadmsvcjobs Start-SPAdminJob
Start-SPAdminJob -Verbose
Installfeature Install-SPFeature
Install-SPFeature -path "MinFeature"
Retractsolution Uninstall-SPSolution
Uninstall-SPUserSolution -Identity pakke.wsp
Uninstallfeature Uninstall-SPFeature
Uninstall-SPFeature -path "MinFeature"
Upgradesolution Update-SPSolution
Update-SPSolution -Identity pakke.wsp -LiteralPath c:\pakke.wsp -GACDeployment


Her er en udemærket video, der viser brugen af PowerShell 2.0 CmdLets på en Sharepoint 2010.

Som Sharepoint udvikler kan disse kommandoer naturligvis bundles i scripts, der så kan eksekveres på de pågældende Sharepoint servers. Som Sharepoint administrator vil PowerShell ligeledes blive et vigtigt værktøj til administrive opgaver på Sharepoint portaler.
Jeg lægger ved lejlighed nogle *.ps1 filer som eksempler til download her.

En anden video om PowerShell 2.0 kan hentes her.



Selvom jeg principielt er modstander af brugen af sharepoint lister til at holde på forretningskritiske data, kan man ikke komme udenom det faktum, at man kan udstille en sharepoint liste som en ganske almindelig service. Indlægget kommer til at omhandle, hvordan denne service kan konsumeres af andre applikationer. Man skal dog rent arkitektur mæssigt forholde sig særligt kritisk overfor dette mønster, da sharepoint lige pludselig fungerer som backend, hvilket kan utydeliggøre en en klar skillelinje mellem backend systemer og frontend portaler. Det kan hurtigt blive et virvar af lagdelinger, der overlapper hinanden, istedet for at have klare afgrænsede ansvarsområder. Det overholder selv ikke principperne bag onion arkitektur, så hellere klare afgrænsede snitflader frem for en blanding af lagene.

Bliver en sharepoint liste derimod populeret med data fra en database, er problemet nok ikke så stort, men i Sharepoint 2007 findes der stadigvæk ikke mulighed for at implementere relationer fuldt ud. Det hele bliver nogle logiske relationer i form af lookup tabeller her og der uden referentiel integritet. Dette skulle være taget hånd om i Sharepoint 2010. Dog retfærdiggør dette stadigvæk ikke placering af forretningskritiske data i en Sharepoint liste, men Sharepoint 2010 giver derimod en mere sikker og renere repræsentation af relationel datastruktur.

Har man en intern kaffeklub eller andet i en liste, der skaber værdi i interne arbejdsprocesser i en organisation, og man gerne vil udstille denne liste som en service, så kan det opnås på følgende måde:

List Data Retrieval Service
http://<server-url>/_vti_bin/dspsts.asmx

Har man allerede en liste oprettet med noget data, så åbnes Visual Studio og man tilføjer ganske enkelt referencen: 'http://<server-url>/_vti_bin/dspsts.asmx'.

En sharepoint liste er beskyttet af rettigheder, og derfor skal der sættes credentials op for at tilgå listen med tilstrækkelige rettigheder. I dette eksempel tilgås listen med Network Credentials. Dvs. listen tilgås med den bruger, der er logget på netværket.

var stsAd = new StsAdapter();
stsAd.Credentials = CredentialCache.DefaultNetworkCredentials;

//Resten af kode snippet ser således ud:
string[] vArray = new string[1];
vArray[0] = "1.0";
var ver = new Versions();
ver.version = vArray;

stsAd.versions = ver;
var reqHeader = new RequestHeader();
reqHeader.document = DocumentType.content;
reqHeader.method = MethodType.query;

stsAd.request = reqHeader;

//Listen refereres ved hjælp af GUID.
//Find GUID på listen under instillingerne for listen.
const string listGuid = "{08D53A6B-947E-406F-B3C0-74534FC6BE07}";

var req = new QueryRequest();

var xpq = "/list[@id='" + listGuid + "']";
var sQuery = new DSQuery();
sQuery.select = xpq;
req.dsQuery = sQuery;

var spQuery = new DspQuery();
req.dsQuery.Query = spQuery;

XmlNode myNode = stsAd.Query(req);


Xmlnoden 'myNode' holder på listen, og skal herefter pakkes ud.

Er der behov for at filtrere svaret, kan man benytte sig af CAML. Fremadrettet vil / kan LINQ blive brugt til dette. Her er et eksempel på, hvordan man sender CAML med i et request.

var xmlDoc = new XmlDocument();
var eqQuery = xmlDoc.CreateElement("Eq");
eqQuery.InnerXml = "<FieldRef Name='Vaerdi'/><Value Type='Text'>fisk</Value>";

var spQuery = new DspQuery();
spQuery.Where = eqQuery;
req.dsQuery.Query = spQuery;

XmlNode myNode = stsAd.Query(req);


Find yderligere informationer ved brug af 'List Data Retrieval Web Service' her.



Hvorfor skal ens assemblies strong names? Ifølge Microsoft er det anbefalet praksis, da det giver nogle fordele. Kigger man på Microsofts egne assemblies fx. Enterprise Library, kan man se, at disse har versions numre, 'culture information', et 'friendly name' og en PublicKeyToken. Et 'strong name assembly' får påtrykt en unik værdi i form af en tilfældig hashværdi, der bliver genereret udfra assembly manifest filen. Ved at 'strong name' assemblies garanterer man ens assembly er unik, og ikke kan bygges eller efterlignes af tredje part. Dette er forudsat af, de ikke har adgang til nøglefilen og kender password. Med et strong name garanteres, at assemblien er bygget og forvaltet hos den udgivende instans. Desuden garanterer versionerings delen at man undgår dll hell, hvor en opdateret assembly i værste tilfælde kan nedlægge andre applikationer. Man kan med versionering sørge for at assemblies kan co-eksistere side om side med forskellige versions numre. Konsumerings applikationerne kan herefter referere til den nyeste assembly, når de er klar til det.

Man giver ens assembly et strong name ved at oprette en nøglefil. Project -> Properties -> Signing. Her kan man oprette en ny nøgle og sågar password beskytte det.

Det er anbefalet praksis, at man som udgiver af software i et firma gør brug af samme nøgle fil, da man herved undgår et unødigt administrations helvede med forskellige nøglefiler.

! Bemærk, man kan kun referere til andre strong named assemblies fra ens assembly. Assemblies, der skal registreres i Global Assembly Cache (GAC) skal altid strong names, inden de får lov at blive registreret.



Fasil Malik Hayat

Developer
.Net, WCF, Sharepoint, MOSS, Biztalk, JQuery.

linkedin facebook twitter plaxo google+ grooveshark boxee
fasil

Education

fasil

Bachelor of Science (Honours)
De Montfort University, Leicester.

Fag: Java, MVC, Information Strategy, ITIL

Microsoft Certified Technology Specialist

Skills

.net vs2010 - C# sharepoint sql server jquery ubuntu java netbeans


  

Sign in