1

Using the EPiServer Community API outside IIS

Posted on 11 December 2009 and tagged with , , ,

I have recently come across a scenario where i had  to migrate alot of data into EPiServer Community from a legacy system. Since there is quite of a lot of data that needed to be transfered from one system to another, the ideal solutions for us was to do this by code via a console app The console app exports data from the legacy system and inserts it into EPiServer Community by using the powerful community framework. This is however not as simple as it sounds. Here is a little howto on what you have to do to overcome this challenge.

1. Creating a community/relate site

Create a new site using the deployment center. This is the community site i want to import data into. In this example i have installed an relate site into the following directory c:\EPiServer\Sites\RelateDemo\Source\Web. Open the project file in the web folder in Visual Studio. Rename the EPiServer.Templates.RelatePlus to Web. Save the solution in by select the solution project in Visual studio and save it via File -> Save as. Store it in the parent folder of the project (c:\EPiServer\Sites\RelateDemo\Source)

2. Creating the console app

Create a new console application by selecting the solution file -> new project -> console application. I called my console app MigrationTool. Set the console application as the startup project. The solution should now look similar to this:

Solution FolderStructure

Now you must add a bounch of references to the EPiServer Common Framework and THe EPiServer Community Framework. Simple right click the console app project (e.g MigrationTool), then select add reference. Add all assemblies found in the following directories:

  • C:\Program Files (x86)\EPiServer\CommonFramework\2.3.517.36\bin
  • C:\Program Files (x86)\EPiServer\Community\3.2.517.24\bin

    3. Reusing the community configuration

The goal of the console app is to use the community framework in the same matter as the RelatePlus templates does. This requires however a fair amount of configuration in the console apps app.config file and it would basicly look the same as the web.config of the Web project. Instead of creating a new config file for the console app, it would be better to reuse the copying the existing configuration from our web project.

Create a script that copies the config files from the web project by right clicking the the console project and select Properites and then select the Build events tab. Add the following script in the Post-Build events textbox

xcopy "$(SolutionDir)web\*.config" "$(TargetDir)" /y /q /s
copy $(TargetDir)web.config $(TargetPath).config /y

Like this

Script

Notice the last line of the script. It creates a file called MigrationTool.exe.config so that the console app can read the community configuration.

When you build the console app and click the "don't lie to me"-button you will notice the bin folder will now contain the following files:

CopiedFiles

You can now add some code uses the community framework in the console app. E.g fetching the default admin user via the community api:

//Using the community api
EPiServer.Common.Security.IUser user = EPiServer.Community.CommunitySystem.CurrentContext.DefaultSecurity.GetUser(1);
Console.WriteLine(user.UserName);
 
Don't be disapointed. It won't run quite yet.


4. Initializing the Community Framework

If you try to use any of the methods on the community api now you will get a horrible null reference exception since the community framework is not initialize. To work around this you need to trigger the community api to initialize. Add the following code above the code from the previous section:

//Initializing the framework                
EPiServer.Common.Web.Global.OnBeginRequest();

5. Mocking the HttpContext

The console app will still not work. I digged far and deep into to the community api and i spoted that the api references a lot of HttpContext.Current. The most notable is the CachingHandler class that relies on the HttpContext.Current.Cache object. To work around this you need to mock the HttpContext. Add a reference to the System.Web.dll in the console app project and add the following code above the code from the previous sections 

//Mock HttpContext
string fileName = "default.aspx";
string url = "http://local.relatedemo.com";
string queryString = null;
                
var sb = new StringBuilder();
var sw = new StringWriter(sb);
var request = new HttpRequest(fileName, url, queryString);
var response = new HttpResponse(sw);
var context = new HttpContext(request, response);
HttpContext.Current = context;

6. The Hack: Injecting the community configuration

Oh no. Another problem. When running the application now it will throw the following exception:

The application relative virtual path '~/' is not allowed here.

It turnes out that the EPiServer Common Framework + EPiServer Community Framework assemblies is reading their configuration by using the the System.Web.Configuration.WebConfigurationManager and providing it with a relative path "~" which won't work outside a web server environment. To avoid the configuration being read using the WebConfigurationMananger you need to inject the configuration into the framework classes yourself.

Add a reference to the System.configuration.dll and add the following code above the previous code.

//Read app config file
System.Configuration.Configuration config = null;
config = System.Configuration.ConfigurationManager.OpenExeConfiguration(typeof(Program).Assembly.Location);

//Inject configuration into the EPiServer Common + EPiServer Community framework
Type type = null;
FieldInfo field = null;

type = typeof(EPiServer.Common.Configuration.EPiServerCommonSection);
field = type.BaseType.GetField("m_config", BindingFlags.Static | BindingFlags.NonPublic);
field.SetValue(null, config);

type = typeof(EPiServer.Community.Configuration.EPiServerCommunitySection);
field = type.BaseType.GetField("m_config", BindingFlags.Static | BindingFlags.NonPublic);
field.SetValue(null, config);

This code reads the configuration from the MigrationTool.exe.config file and injects it into two private static fields deep inside the framework classes using reflection. The EPiServer Common Framework and the EPiServer Common Framework will hence detect the configuration has already been read verifying that the m_config is no longer null and WebConfigurationManager.OpenWebConfiguration("~") is never called :-)

7. The Happy Ending

Finally. The code is working and the alias of the username of the admin is printed to the screen:

 HappyEnding
 
Here is the complete source code for this examples:
 
//Read app config file
System.Configuration.Configuration config = null;
config = System.Configuration.ConfigurationManager.OpenExeConfiguration(typeof(Program).Assembly.Location);

//Inject configuration into the EPiServer Common + EPiServer Community
Type type = null;
FieldInfo field = null;

type = typeof(EPiServer.Common.Configuration.EPiServerCommonSection);
field = type.BaseType.GetField("m_config", BindingFlags.Static | BindingFlags.NonPublic);
field.SetValue(null, config);

type = typeof(EPiServer.Community.Configuration.EPiServerCommunitySection);
field = type.BaseType.GetField("m_config", BindingFlags.Static | BindingFlags.NonPublic);
field.SetValue(null, config);

//Mock HttpContext
string fileName = "default.aspx";
string url = "http://local.relatedemo.com";
string queryString = null;
                
var sb = new StringBuilder();
var sw = new StringWriter(sb);
var request = new HttpRequest(fileName, url, queryString);
var response = new HttpResponse(sw);
var context = new HttpContext(request, response);
HttpContext.Current = context;

//Initializing the framework                
EPiServer.Common.Web.Global.OnBeginRequest();

//Using the community api
EPiServer.Common.Security.IUser user = EPiServer.Community.CommunitySystem.CurrentContext.DefaultSecurity.GetUser(1);
Console.WriteLine(user.UserName);
 

Now you can create users (with belonging mypage), forum rooms, topics and threads etc. All inside a external non web application.

I hope that this hack is not needed in future releases of the EPiServer Community, but rater add more flexiblity to the framework like using IoC when reading configuration, cach etc so that these small but anoying dependencies can be finally be removed. :-)

Comments (1) -

Vincent says: 1/20/2011 4:08:31 PM
Vincent

Hi there,

Do you happen to have the Community4 equivalent of EPiServer.Common.Web.Global.OnBeginRequest(); ?
I am trying to do exactly what you did because I need Community to run on WebDev.WebServer.EXE instead of IIS.

Thanks,
Vincent

Add comment




biuquote
Loading


Disclaimer

© Copyright 2009, Jarle Friestad. The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

Widget Twitter not found.

Root element is missing.X

Month List