Upgrading EPiServer CMS from version 7 to 7.5 – TinyMCE

Here’s a little note on how to upgrade EPiServer CMS 7.x to EPiServer CMS 7.5.

Bug in property settings for TinyMCE

When everything seemed to work properly after the update I discovered that Edit view occasionally seemed to stop working. The problem occurred when switching from on-page edit to all properties editing and EPiServer seemed to load for a endlessly long time. When I brought up the console window in Firebug I discovered that there where something wrong with the JavaScripts used by EPiServer CMS.

WidgetFactory: Could not resolve one or more widgets
http://www.mysite.com/Admin/Shell/7.7.0.0/ClientResources/epi/shell/widgets.js
Line 2

My first thought was that I was missing the latest version of the assemblies for the EPiServer UI, but after some investigation, deployment and configuration review I found out that everything seemed to be ok with the binaries and configuration for the web site. Another thing that indicated that everything was correct was that I did not find any direct information among the reported bugs that existed for EPiServer CMS. In other words, it was either an unreported bug or something with my settings.

Since I didn’t find a solution to my problem I let it be a little while, but being a developer means that it is hard to resist to fix broken things, so after a little bit of brainwork I found out that the problem ought to be in TinyMCE and its settings.

Solution

The problem where the custom property settings that I used for XHTML properties. By some reason the previous settings made TinyMCE stop working.

I solved the problem by creating a new property setting for the XHTML property in Admin Mode, and set it to be default for all XHTML properties. That solved my problem.

More information

You will find a list of solved bugs in EPiServer products here.
All current found bugs for EPiServer products can be found here.

Creating a MenuProvider in EPiServer CMS

EPiServer CMS 6 made it is possible to extend the main navigation that is being displayed in the user interface. Extending the navigation in EPiServer goes under the name Menu Providers and is one of the features in the OnlineCenter concept.

The code below is an example on how a menu provider could be implemented.

[SearchProvider]
public class MySearchProvider : ISearchProvider
{
    public string Area
    {
        get { return “My Area”; }
    }

    public string Category
    {
        get { return “Content Types”; }
    }

    public IEnumerable<SearchResult> Search(Query query)
    {
        var contentTypeRepo =
            ServiceLocator.Current.GetInstance<IContentTypeRepository>();

        return contentTypeRepo.List()
            .Where(a => a.Name.ToLower().Contains(query.SearchQuery))
            .Select(a => new SearchResult(“”, a.DisplayName, a.Description));
    }
}

MembershipProvider – Importing old user-accounts

Recently i stumbled into a problem where I needed to import already existing user-accounts from an old website into a new EPiServer website that was using the ASP.NET Membership-system.

The question was:

  • How can you automatically create approximately 7,500 user-accounts in an easy way by using the ASP.NET Membership-system?
  • What are the requirements for doing this and is it possible to connect the newly created user-account with a certain role or several roles?
  • What about Profiles? Can you create the UserProfile as a part of the import-process?

Well, after a little research i came up with the following solution:

  • Create a Windows desktop-application (this is not a requirement, but it eases the development process) that uses the classes for the ASP.NET Mebership system.
  • Make sure the application can access the datasource for the old useraccounts; preferable a SQL Server database or some sort of datasource that is easy to extract data from.
  • Log every account that is imported even if the import failed in order to investigate the reason for the failure.

Requirements:

  • The passwords must be in clear-text in the datasource you are importing from. When the account is created in the ASP.NET Membership-system the password will be hashed/encrypted which means that you will not be able to read it in clear-text, so this is a one-way import process.

The solution is quite simple and straightforward. A couple of methods and some configuration makes it possible to create n user-accounts from the old membership-system.

Sourcecode for the client application.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Web;
using System.Web.Profile;
using System.Web.Security;
using System.IO;
 
namespace AccountConverter
{
    public partial class frmConvert : Form
    {
        public frmConvert()
        {
            InitializeComponent();
        }
 
        /// <summary>
        /// Start the conversion process.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btConvert_Click(object sender, EventArgs e)
        {
            // Get all active users 
            List<ConvertingCustomer> oldAccounts = GetOldAccounts();
 
            progressBar1.Minimum = 0;
            progressBar1.Maximum = oldAccounts.Count();
 
            // Iterate through all users that should be converted.
            int i = 0;
            foreach (ConvertingCustomer account in oldAccounts)
            {
                // Set current rownumber
                i += 1;
 
                try
                {
                    // Convert the current account
                    CreateAccount(account);
 
                    // Update the listbox with status for the current account
                    UpdateListBox(i, string.Format("SUCCESS: {0}\t{1}",
                        account.AccountNumber,
                        account.UserName));
 
                    // Log that the account was successfully converted
                    Log(account.AccountNumber, ConvertStatus.SUCCESS.ToString(), "Account was successfully converted.");
                }
                catch (Exception ex)
                {
                    // Update the listbox with status
                    UpdateListBox(i, string.Format("FAILED: {0}\t{1}\t{2}",
                        account.AccountNumber,
                        account.UserName,
                        ex.Message));
 
                    // Log that the account could not be converted
                    Log(account.AccountNumber, ConvertStatus.FAILED.ToString(), ex.Message);
                }
 
                // Update the progressbar
                progressBar1.Value = i;
                progressBar1.Update();
            }
        }
 
        /// <summary>
        /// Convert a single account. Create a new account with the ASP.NET Membership-system, connect it to a certain role and create an user-profile.
        /// </summary>
        /// <param name="convertingCustomer">An object with necessary data for the customer to be converted.</param>
        private void CreateAccount(ConvertingCustomer convertingCustomer)
        {
            // Create the User
            MembershipUser mu = Membership.CreateUser(
                string.Format("{0}{1}", "TEST_", convertingCustomer.UserName),
                convertingCustomer.Password,
                convertingCustomer.UserName);
            mu.IsApproved = true;
 
            // Add the user to the Customers-role
            Roles.AddUserToRoles(
                convertingCustomer.UserName,
                new string[] { "Customers" });
 
            // Update the User-Profile
            WebProfile webProfile = new WebProfile(WebProfile.Create(convertingCustomer.UserName));
            webProfile.Email = convertingCustomer.UserName;
            webProfile.CustomerNumber = convertingCustomer.AccountNumber;
            // ... add your own fields here
            webProfile.Save();
        }
        
        /// <summary>
        /// Connect to a datasource and get data for the old accounts that is to be converted.
        /// </summary>
        /// <returns></returns>
        private List<ConvertingCustomer> GetOldAccounts()
        {
            // Get old account-data.
            //var myOldAccounts = datasource.GetAccounts();
 
            // Get accounts from datasource
            //var accountDataList = from account in myOldAccounts
            //                      select new ConvertingCustomer
            //                      {
            //                          AccountNumber = account.CustomerId,
            //                          UserName = account.UserName,
            //                          Password = account.Password
            //                      };
            //return accountDataList.ToList<ConvertingCustomer>(); 
            
            return new List<ConvertingCustomer> 
            {
                AccountNumber = "123456",
                UserName = "TestUser@labs.com",
                Password = "Pa$$w0rd"
            }
        }
        
        /// <summary>
        /// Log the progress with the account conversion.
        /// </summary>
        /// <param name="accountNumber">The customers accountnumber in the backend.</param>
        /// <param name="status">Status of the progress.</param>
        /// <param name="message">An errormessage or a message with information about the conversion.</param>
        private void Log(string accountNumber, string status, string message)
        {
            using (StreamWriter sw = new StreamWriter(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "convert.log"), true, Encoding.UTF8))
            {
                sw.WriteLine("{0}\t{1}\t{2}", status, accountNumber, message);
            }
        }
 
        /// <summary>
        /// Update the ListBox in the GUI to display the progress.
        /// </summary>
        /// <param name="recordNumber">An integer with the rownumber of the account that was processed.</param>
        /// <param name="message">The resulting message from the conversion process.</param>
        private void UpdateListBox(int recordNumber, string message)
        {
            lbConvertProgress.Items.Insert(0, string.Format("#{0} - {1}", recordNumber, message));
            lbConvertProgress.Update();
        }
        
        /// <summary>
        /// An internal class used to handle customer-data. 
        /// </summary>
        private class ConvertingCustomer
        {
            public string UserName;
            public string Password;
            public string AccountNumber;
        }
        
        /// <summary>
        /// An enum with conversion-status.
        /// </summary>
        private enum ConvertStatus
        {
            SUCCESS,
            FAILED
        }
 
    }
}

 

The App.Config file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <connectionStrings>
        <add name="MyDataSourceConnectionString"
             connectionString="Data Source=myDatabaseServer;Database=myDatabase;User Id=myDBUser;password=P@ssw0rd;Connection Timeout=30"
             providerName="System.Data.SqlClient" />
    </connectionStrings>
    <system.web>
        <membership defaultProvider="SqlServerMembershipProvider">
            <providers>
                <clear/>
                <add name="SqlServerMembershipProvider"
                     type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
                     connectionStringName="MyDataSourceConnectionString"
                     requiresQuestionAndAnswer="false"
                     applicationName="MyWebApplication"
                     requiresUniqueEmail="true"
                     passwordFormat="Hashed"
                     maxInvalidPasswordAttempts="5"
                     minRequiredPasswordLength="6"
                     minRequiredNonalphanumericCharacters="0"
                     passwordAttemptWindow="10"
                     passwordStrengthRegularExpression=""/>
            </providers>
        </membership>
        <roleManager enabled="true"
                     defaultProvider="SqlServerRoleProvider">
            <providers>
                <clear/>
                <add name="SqlServerRoleProvider"
                     connectionStringName="MyDataSourceConnectionString"
                     applicationName="MyWebApplication"
                     type="System.Web.Security.SqlRoleProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
            </providers>
        </roleManager>
        <profile enabled="true"
                 defaultProvider="SqlProfile"
                 automaticSaveEnabled="true">
            <properties>
                <add name="CustomerNumber"
                     type="System.String"/>
                <add name="Email"
                     type="System.String"/>
                <add name="Name"
                     type="System.String"/>
                <add name="Phone"
                     type="System.String"/>
                <add name="CellPhone"
                     type="System.String"/>
            </properties>
            <providers>
                <clear/>
                <add name="SqlProfile"
                     type="System.Web.Profile.SqlProfileProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
                     connectionStringName="MyDataSourceConnectionString"
                     applicationName="MyWebApplication"/>
            </providers>
        </profile>
    </system.web>
    <appSettings />
</configuration>
Technorati-taggar: ,,