Showing posts with label EpiServer. Show all posts
Showing posts with label EpiServer. Show all posts

20110209

Deploying EPiServer CMS to Windows Azure using VM Role

Intro


This post describes running EPiServer CMS version 6 on the Windows Azure using Virtual Machine (VM) Role Beta Program. The main goal was to take developer Hyper-V machine and deploy it to the cloud as staging environment.

Prerequisites



Creating VHD


First Windows Server 2008 R2 is required - I've used Windows Server 2008 R2 Enterprise (Full Installation). Additionally it should be installed on a single partition, which is not the default behaviour. There are few posts on how to remove this additional partition but sysprep (described later) fails after those operations - it can be done only during system installation.

  1. select Custom (Advanced) instead of Upgrade
  2. on partition selection screen press Shift + F10 to open console
  3. run diskpart
    • list disk
    • select disk 0
    • create partition primary
    • exit
  4. close console
  5. refresh
  6. select created partition

After installation go to Server Manager:
  1. add Role: Web Server (IIS)
  2. add Feature: .NET Framework 3.5.1 (inside .NET Framework 3.5.1 Features node)
You may need to switch off Windows Update, but it should be inactive by default.
You may backup Virtual Machine now to reuse it later.

Installing Application


Now it's time to install EPiServer or any other application you'd like to deploy to the could. I've used empty EPiServer version 6 with Public Templates. Only web.config change was to switch to SQL Role and Membership providers.

Setting up database


Local database needs to be migrated to Microsoft SQL Azure Database - there's quite simple way to do it using SQL Azure Migration Wizard

Because this tool doesn't clean tables previously filled with data this script can be used to remove all tables if another run is required for any reason.

You should modify connectionStrings.config to connect to Azure SQL instance and test if application works.

Finalizing VHD


It's highly recommended to backup Virtual Machine now.
Further steps caused it hang during start up until in cloud.

Mount C:\Program Files\Windows Azure SDK\v1.3\iso\wavmroleic.iso as DVD drive and install WaIntegrationComponents-x64.msi. Provide it with valid Administrator password and let it install all the drivers it needs. After reboot run %windir%\system32\sysprep\sysprep.exe enter options as on image below and let Virtual Machine shutdown - now VHD image is ready for deployment.


If you ever start this Virtual Machine again (which is not obvious to succeed) you need to launch sysprep once more.

Creating Management Certificate


  1. Start Visual Studio
  2. create project of type C# / Cloud / Windows Azure Project.
  3. click Publish
  4. Select 'Deploy your Windows Azure project to Windows Azure'
  5. Credentials - select Add... and name it
  6. Click 'Copy the full path' to get Public key path and upload it to Azure



  7. Copy subscription ID back to Management Authentication dialog and click OK - it will be verified.
  8. You can now forget about this project :).

Uploading VHD


Open a Windows Azure SDK Command Prompt as an administrator from Start | All Programs | Windows Azure SDK v1.x.
Run command below replacing XXXXX and YYYYY with your Subscription Id and Certificate Thumbprint respectively. You should of course provide path to VHD and destination location to reflect your needs.

csupload Add-VMImage -Connection "SubscriptionId=XXXXX; CertificateThumbprint=YYYYY" -Description "Base image Windows Server 2008 R2" -LiteralPath "D:\VM\AzureTest\Virtual Hard Disks\Clean win 2008 r2.vhd" -Name baseimage.vhd -Location "North Europe"



Anywhere locations are not supported (i.e. Anywhere Europe and Anywhere US).

Statuses visible in Management Portal while uploading:
  • pending means uploading
  • committed means ready

Creating the Service Model (Visual Studio part)



  1. Start VS2010
  2. create Windows Azure Project without any roles
  3. right-click Roles > Add > New Virtual Machine Role
  4. in properties of new role > Virtual Hard Disk tab > select account and uploaded VHD
  5. in Endpoints tab add:
    • name: HttpIn
    • type: Input
    • protocol: Http
    • public port: 80
    • private port: 80
  6. go to Publish
  7. Configure Remote Desktop (save certificate to file WITH the private key)
  8. select Create Service Package Only
  9. click OK

Creating the Service Model (Management Portal part)


  1. New Hosted Service - name it, give it URL prefix, choose region and 'Do not deploy'
  2. Add Certificate (select one created in previous step)
  3. New Production Deployment - name it and select two files created by VS (.cspkg and .cscfg)
  4. wait...
  5. wait more...
  6. wait even more...
  7. you can connect to IIS using URL on the right
  8. Connect on the ribbon downloads RDP file




Further works


All file system modification on Virtual Machine will be lost when it's reimaged from uploaded VHD. It may happen because of machine failure or restart. At the moment only data stored in Azure SQL is safe - every change to VPP will be lost. There are two options - to store VPP data in database or in Cloud Storage. There's a post on this by Daniel Berg but I haven't tried yet.

There's also licensing issue as EPiServer license is linked either to IP or MAC address. Unfortunately MAC address changes as well during deployment. At the moment I've generated new demo license using MAC address obtained from Virtual Machine but it will be lost. Not sure what can be done about it.

20100317

EpiServer - Itera.MultiProperty examples

MultiProperty by Itera is very useful EpiServer extension.

Two basic ways of using MultiProperty are described on a blog by Anders Hattestad.

Basics

Most common way to use MultiProperty is to create set of fields and then create second property that allows user to create list of those items.

Let's assume user needs to provide list of ExampleItem.

public class ExampleItem
    {
        public string Field { get; set; }
        public string Regex { get; set; }        
        public string Default { get; set; }
    }

First you have to create property that lets user enter single item.

public class PropertyExampleField : PropertySingleBase
    {
        protected const string PROPERTY_NAME_FIELD = "Field";
        protected const string PROPERTY_NAME_REGEX = "Regex";
        protected const string PROPERTY_NAME_DEFAULT = "Default";

        PropertyDataCollection _innerPropertyCollection;
        protected override PropertyDataCollection InnerPropertyCollection
        {
            get
            {
                if (_innerPropertyCollection == null)
                {
                     _innerPropertyCollection =
                        new PropertyDataCollection
                            {
                                {PROPERTY_NAME_FIELD, new PropertyString()},
                                {PROPERTY_NAME_REGEX, new PropertyString()},
                                {PROPERTY_NAME_DEFAULT, new PropertyString()},
                            };
                }
                return _innerPropertyCollection;
            }
            set { _innerPropertyCollection = value; }
        }

        public string Field
        {
            get { return (PropertyCollection[PROPERTY_NAME_FIELD].Value as string) ?? ""; }
            set { PropertyCollection[PROPERTY_NAME_FIELD].Value = value; }
        }

        public string Regex
        {
            get { return PropertyCollection[PROPERTY_NAME_REGEX].Value as string; }
            set { PropertyCollection[PROPERTY_NAME_REGEX].Value = value; }
        }    

        public string Default
        {
            get { return PropertyCollection[PROPERTY_NAME_DEFAULT].Value as string; }
            set { PropertyCollection[PROPERTY_NAME_DEFAULT].Value = value; }
        }

        public ExampleItem ExampleItem
        {
            get
            {
                return new ExampleItem
                           {
                               Field = Field,
                               Regex = Regex,
                               Default = Default
                           };
            }
        }
    }

Then you have to create property that lets user create list of items.

[Serializable]
    [PageDefinitionTypePlugIn(DisplayName = "List of ExampleField")]
    public class ExampleFieldList : PropertyMultiBase
    {
        PropertyDataCollection baseProperties;
        public override PropertyDataCollection BaseProperties
        {
            get
            {
                if (baseProperties == null)
                {
                    baseProperties = new PropertyDataCollection { { "ExampleField", new PropertyExampleField() } };
                }
                return baseProperties;
            }
        }

        public List ExampleItems
        {
            get
            {
                List result = new List();
                foreach (PropertyExampleField item in PropertyCollection)
                {
                    result.Add(item.ExampleItem);
                }
                return result;
            }
        }

    }

Advanced

You can sort fields on save - it will help users to deal with long lists. You can remove empty fields - but you have to remember to let user have one empty so he can add new items. To achieve it override SerializeToXmlNode method in ExampleFieldList class.

public override System.Xml.XmlNode SerializeToXmlNode(PropertyDataCollection propCollection, PropertyDataCollection basePropertys, Dictionary newGuids)
        {
            List fields = new List();
            bool firstEmpty = true;
            foreach (PropertyData data in propCollection)
            {
                PropertyExampleField field = data as PropertyExampleField;
                if (field == null)
                {
                    continue;
                }
                if (field.Field != "")
                {
                    fields.Add(field);
                }
                else
                {
                    if (firstEmpty)
                    {
                        fields.Add(field);
                        firstEmpty = false;
                    }
                }
            }
            fields.Sort((a, b) => a.Field.CompareTo(b.Field));
            PropertyDataCollection newPropCollection = new PropertyDataCollection();
            foreach (PropertyExampleField data in fields)
            {
                newPropCollection.Add(data);
            }
            return base.SerializeToXmlNode(newPropCollection, basePropertys, newGuids);
        }

You an also force some mandatory items to be placed on the list each time user enters edit mode. To achieve it override ParseToSelf method in ExampleFieldList class.

Example below adds 3 fields named aaa, bbb and ccc.
Even if user removes any of them they will be recreated next time anyone enters edit mode.

public override void ParseToSelf(string xml, PropertyDataCollection propCollection, PropertyDataCollection basePropertys)
        {
            base.ParseToSelf(xml, propCollection, basePropertys);            
            string fieldList = "aaa,bbb,ccc";
            foreach (string part in fieldList.Split(",".ToCharArray()))
            {
                string fieldName = part.Trim();
                if (fieldName == "")
                {
                    continue;
                }
                bool found = false;
                foreach (PropertyData data in propCollection)
                {
                    PropertyExampleField field = data as PropertyExampleField;
                    if (field == null)
                    {
                        continue;
                    }
                    if (field.ExampleItem.Field == fieldName)
                    {
                        found = true;
                        break;
                    }
                }
                if (!found)
                {
                    propCollection.Add(
                        new PropertyExampleField 
                                           {
                                               Name = String.Format("ExampleField_{0}", Guid.NewGuid()),
                                               Field = fieldName,
                                               Regex = "",
                                               Default = ""
                                           });
                }
            }
        }