torsdag 4 februari 2016

Sätta default value till nyskapat content innan det publicerats

Ett litet tips:

För att sätta default värden på en content typ i EPiServer gör man ju en override på SetDefaultValues() metoden i sin contenttype deklarationsklass.

Men hur sätter  man default värde på ContentReference egenskaper när man vill peka ut den egna sidan? SetDefaultValues anropas ju innan sidan/blocket ens har tilldelats ett ContentReference-värde.

Jo, svaret stavas ContentReferense.SelfReference !!

public override void SetDefaultValues(ContentType contentType)
{
   base.SetDefaultValues(contentType);

   // Default on local property!
   this.MyContentReferenceProperty = ContentReference.SelfReference;
           
   // Also works for properties on local blocks!
   this.MyLocalBlock.ContentReferenceProperty = ContentReference.SelfReference;

}


Lätt som en plätt!

tisdag 2 februari 2016

Sätta behörigheter programmatiskt i EPiServer 8

Tjo!

Long time no see. Råkade skaffa 2 barn så har inte haft tid att skriva nåt på ett tag.

Eftersom jag inte hittade något användbart själv när jag googlade på behörighetsfipplande i EPiServer 8 så tänkte jag att jag drar mitt strå till stacken och lägger upp ett inlägg själv:

Scenariot är att när en egenskap som pekar ut en användare ändras på en EPiServer-sida så ska EPiServer-sidans behörigheter automatiskt ändras. Detta görs genom en initialiseringsmodul som hookar upp på Published-eventet. Men detta är en annan historia...

För att uppnå mina behov behövde jag två metoder, en som tar bort behörigheter från en sida och en metod som lägger till behörigheter.

Detta görs numera bäst med hjälp av servicen IContentSecurityRepository som nås via ”ServiceLocator”-klassen (fick det aldrig att fungera genom att manipulera PageData.ACL...).

private IContentSecurityRepository _contentSecurityRepository
{
  get
  {
    return ServiceLocator.Current.GetInstance<IContentSecurityRepository>();
  }
}

Ta bort behörighet
En metod som kollar om den utpekade sidan har någon entry för den specificerade rollen eller användaren. Om så tas entryn bort. En borttagning sker lite klurigt genom att man lägger till en entry med “NoAccess”-behörigheten och skriver över befintliga regler (mha parametern SecuritySaveType.Replace i save-metoden).
        
/// <summary>
/// Remove ACL entry for specified user and accessright for specified page.
/// </summary>
/// <param name="userOrRoleName"></param>
/// <param name="securityEntityType"></param>
/// <param name="page"></param>
public void RemoveAccessRight(string userOrRoleName, SecurityEntityType securityEntityType, PageData page, bool keepInheritedSecurity)
{
  // Check entry exists
  if (page.ACL.Contains(userOrRoleName))
  {
    IContentSecurityDescriptor securityDescriptor = (IContentSecurityDescriptor)_contentSecurityRepository.Get(page.ContentLink).CreateWritableClone();

    // Remove inherited if set (will throw error otherwise)
    if (securityDescriptor.IsInherited)
    {
      securityDescriptor.IsInherited = false;

      // Keep inherited security?
      if (keepInheritedSecurity)
        AddInheritedACLEntries(ref securityDescriptor, page.ParentLink);
    }

    // Remove by adding an entry with NoAccess and replace existing one (achieved with SecuritySaveType argument to Save method)
    securityDescriptor.AddEntry(new AccessControlEntry(userOrRoleName, AccessLevel.NoAccess, securityEntityType));
    _contentSecurityRepository.Save(page.ContentLink, securityDescriptor, SecuritySaveType.Replace);
  }
}

Manuellt lägga till ärvda behörigheter
Dock så finns risken att sidans behörigheter är inställda på att ärva behörigheter från föräldersidan. Denna flagga ” IsInherited” på IContentSecurityDescriptor-objektet medför att vi får ett exception kastat om vi försöker spara nya behörigheter. Så vi måste först för säkerhets skull sätta “IsInherited” till false. För att då inte tappa alla behörigheter som arvet medförde så har jag lagt till en parameter ”keepInheritedSecurity” som vid true manuellt lägger till de ärvda behörigheterna mha metoden ”AddInheritedACLEntries” nedan.

/// <summary>
/// Adds the inherited ACL entries as new entries on specified security descriptor object
/// </summary>
/// <param name="securityDescriptor"></param>
/// <param name="parentLink"></param>
private void AddInheritedACLEntries(ref IContentSecurityDescriptor securityDescriptor, ContentReference parentLink)
{
  if (!ContentReference.IsNullOrEmpty(parentLink))
  {
    // Read parent ACL entries and add to security descriptor
    PageData parent = _contentLoader.Get<PageData>(parentLink);
    foreach (var aclEntry in parent.ACL.Entries)
    {
      securityDescriptor.AddEntry(aclEntry);
    }
  }
}


Lägga till behörighet
Slutligen en metod för att lägga till en behörighet som är ganska self explanatory. Samma sak här angående flaggan ”IsInherited”.

Tänk också på att AccessLevel enum är en bitmask. Den kan alltså innehålla flera värden samtidigt, t.ex. kan man skriva såhär för att ge en variabel alla värden utom ”Administer”:

AccessLevel accessLevel = AccessLevel.Read | AccessLevel.Create | AccessLevel.Edit | AccessLevel.Delete | AccessLevel.Publish;




Och här är metoden för att lägga till en behörighet:

/// <summary>
/// Add ACL entry for specified user or role with specified level on specified page.
/// </summary>
/// <param name="userOrRoleName"></param>
/// <param name="accessLevel"></param>
/// <param name="securityEntityType"></param>
/// <param name="page"></param>
/// <param name="keepInheritedSecurity"></param>
public void AddAccessRight(string userOrRoleName, AccessLevel accessLevel, SecurityEntityType securityEntityType, PageData page, bool keepInheritedSecurity)
{
  if (page != null && !page.ACL.Contains(userOrRoleName, accessLevel, securityEntityType))
  {
    IContentSecurityDescriptor securityDescriptor = (IContentSecurityDescriptor)_contentSecurityRepository.Get(page.ContentLink).CreateWritableClone();

    // Remove inherited if set (will throw error otherwise)
    if (securityDescriptor.IsInherited)
    {
      securityDescriptor.IsInherited = false;

      // Keep inherited security?
      if (keepInheritedSecurity)
        AddInheritedACLEntries(ref securityDescriptor, page.ParentLink);
    }
                
    securityDescriptor.AddEntry(new AccessControlEntry(userOrRoleName, accessLevel, securityEntityType));
    _contentSecurityRepository.Save(page.ContentLink, securityDescriptor, SecuritySaveType.Replace);
  }

}


Och det var det!
Dessa metoder lade jag sedan i en egen klass för behörighetsfippel.

tisdag 28 maj 2013

Lagra custom property sträng som binär protobuf-net sträng istället för xml

Hej!

Som en reaktion på mitt föregående inlägg bestämde jag mig för att kika på alternativ för serialisering/deserialisering. Kom över följande "benchmark" (vetenskapligheten i inlägget orkar jag inte analysera).

http://stackoverflow.com/questions/3790728/performance-tests-of-serializations-used-by-wcf-bindings/3793091#3793091

Sagt och gjort, protobuf-net it is.

1. Börjar med att tanka ner dll från https://code.google.com/p/protobuf-net/

2. Följer instruktionerna och dekorerar min databärande klass med attributen [ProtoContract] & [ProtoMember(x)] (https://code.google.com/p/protobuf-net/wiki/GettingStarted)















3. Implementerar ToString() override och den statiska Parse-metoden som min custom property använder när objektet skrivs och läses:














4. Som synes använder jag mig av en statisk helper-klass för ändamålet:



5. Och nu ska det alltså bara vara att tuta å köra! Mindre dret i databasen ist krieg! Medger dock att läsbarheten försämras rejält vid t.ex. hantering av olika objekt-versioner. Men det är ett senare problem! :)






fredag 24 maj 2013

EPiServer Custom Property som lagras som longstring och stringDelayedLoadThreshold

Hej igen. Nej bloggen är inte död, den har bara idlat lite.

Stötte på en irriterande detalj i EPiServer 7 när jag lekte med en custom property (använder legacyeditorn tills jag orkat sätta mig in i Dojo-tänket) vars Data serialiserades till en sträng och lagrades i EPiServers db som en LongString.

Egenskapen fungerade utmärkt i redigeraläget och efter publicering av en sida. Ända tills en IISRESET av sidan gjordes. Då returnerade propertyn alltid null. Troligtvis nåt i cache-mekanismen som strular alltså.

Det visade sig härröra från ett filter för longstrings-egenskaper vars värden är för långa. Lite osäker på vad defaultvärdet är, men när jag satte attributet stringDelayedLoadThreshold="0" för noden siteSettings i EPiServer.config så försvann problemet. Drygt fel...

Länk till var jag hittade lösningen:
http://world.episerver.com/Modules/Forum/Pages/thread.aspx?id=65572&pageIndex=1#reply

Dokumentation för egenskapen:
http://world.episerver.com/Documentation/Class-library/?documentId=cms/7/89421fe0-ae9b-42fe-06d0-224bd9e4a528




torsdag 15 november 2012

EPiServer Powershell deploy script

Tjipp! som Bengt Alsterlind säger här i Värmland (iaf inte hört någon annan använda ordet).

Blivit mycket powershell för min del på sistone. Men måste säga att jag gillar språket mer och mer, ett klart fall framåt från gamla dos-batch. Här kommer ytterligare lite powershell:

Tänkte presentera ett litet förslag på hur ett EPiServer powershell deploy script kan se ut. Alltså ett script som kopierar visst innehåll från en Visual Studio projekt-katalog till en annan katalog i syfte att kunna zippa ihop innehållet och flänga upp på en testserver/prod.server. Har läst att det ska finnas färdiga såna här templates i EPiServer 7 (vet inte om det är MSBuild eller PS), men vet inte mer om detta just nu.

<Dicsclaimer>Observera att alla webbplatser har sina egna krav och behov och att detta script antagligen inte överensstämmer med just dina krav.</Disclaimer> :P


# DeployPackage.ps1
# 2012-11-14 Määts

# Example call:
# ./DeployPackage.ps1 -verbose -c "Release" -s "C:\Path\To\My\EPiServer\Project\" -d "C:\Path\To\My\Deploy\Folder\"

# Script params
param([switch]$verbose, [string]$configurationMode, [string]$sourceDir, [string]$destDir)

$verboseoutput = @"
Executing script with params:
configurationMode: $configurationMode
sourceDir:         $sourceDir
destDir:           $destDir
"@

# Handle switches
if ($verbose.IsPresent) {
    Write-Host $verboseoutput
    $VerbosePreference = "Continue"
} else {
    $VerbosePreference = "SilentlyContinue"
}
# Validate params
if (-NOT($sourceDir -eq $NULL) -and (Test-Path $sourceDir) -eq $FALSE) {
    Write-Host "ERROR: Parameter ""sourceDir"" does not appear to be a valid path... aborting."
    Write-Host $sourceDir
    break
}

if (-NOT($destDir -eq $NULL) -and (Test-Path $destDir) -eq $FALSE) {
    Write-Host "ERROR: Parameter ""destDir"" does not appear to be a valid path... aborting."
    Write-Host $destDir
    break
}
if ($configurationMode -eq $NULL)
{
    Write-Host "ERROR: Parameter ""configurationMode"" not set... aborting."
    break
}
if (([string]$configurationMode).ToLower() -ne "release") {
    Write-Host "INFO: Parameter ""configurationMode"" not set to release mode... aborting."
    break
}

# Declare function
function DeployPackage() {
<#
    .SYNOPSIS
    Creates an EPiServer site deployment package
   
    .DESCRIPTION
   
    .PARAMETER
   
    .EXAMPLE
   
    .INPUTS
   
    .OUTPUTS
#>

    # Function params
    param([string]$configurationMode, [string]$sourceDir, [string]$destDir)
       
    # Variables
    $dateString = (Get-Date -format "yyyy-MM-dd_HH.mm.ss") # String representation of current date that will be used when naming the deploy package
    $fileTypes = @("*.aspx","*.ascx","*.jpg","*.gif","*.png","*.asmx","*.resx","*.xml","*.jpg","*.js","*.css","*.dll",
                   "*.pdb","*.swf","*.txt","*.html","*.htm","*README*","*.php","*.pdn","*.master","*.xsd","*.fla",
                   "*.as","*.asax") # This is the filetypes that the script will copy into the deploy
    $excludeFolders = @("obj")
    $excludeFiles = @("EPiServerErrorLog.txt")
                 
    $newDestDir = "$destDir\DeployPackage_$dateString\"
   
    # Create new folder
    New-Item $newDestDir -type directory
 
    #Get all files to copy
    $filesToCopy = Get-ChildItem $sourceDir -recurse -include $fileTypes
   
    # Copy items
    foreach ($file in $filesToCopy)
    {
        $newFilePath = $file.Fullname.Replace($sourceDir,$newDestDir)
       
        # Create new file (unix touch) in order to maintain folder structure
        New-Item -ItemType File -Path $newFilePath -Force
       
        # Copy file (overwrite)
        Copy-Item $file.Fullname $newFilePath -Force
    }
   
    # Remove unwanted folders
    Get-ChildItem $newDestDir -include $excludeFolders -recurse | Where-Object { $_.PSIsContainer } | Foreach-Object { Remove-Item $_.Fullname -recurse -Force }
   
    # Remove unwanted files
    Get-ChildItem $newDestDir -recurse -include $excludeFiles -Force | Foreach-Object { Remove-Item $_.Fullname }
}

# Call function
DeployPackage -c $configurationMode -s $sourceDir -d $destDir


Kommentar:
För att köra scriptet slänger man in följande i post-build events i Visual Studio för EPiServer webb-projektet.

 Powershell.exe -file "$(SolutionDir)DeployPackage.ps1" -ExecutionPolicy Unrestricted -verbose -c "$(ConfigurationName)" -d "C:\Deploy" -s "$(ProjectDir)\"

 Mitt script ligger alltså i "Solutiondir"-roten. Jag skickar in $(ConfigurationName) eftersom jag enbart vill att en deploy mapp ska skapas när projektet byggs i release-mode. Slutligen skickar jag med aktuell projektkatalog och vart jag vill att deploy-mappen ska skapas.

Som synes har jag slarvat lite i scriptet och inte orkat skriva klart kommentarer. Erkänner också villigt att #Remove unwanted files/folders inte är speciellt snyggt utan hade kunnat filtrerats redan i kopieringsstadiet. Det hade också varit snyggt om jag kunde avslutat med att zippa ihop mappen men detta är inte sååå jobbigt att göra själv jämfört med att sitta och hantera alla filtyper osv. 

Om nån läsare har kritik/bättre idéer så är jag idel öra, är som sagt fortfarande grön i powershell träsket :)