Musielak Marek

Posted on Wednesday, April 15, 2009 by Marek Musielak

EPiServer - filter page tree view

Recently I was working on the EPiServer site that contains dozens of subsections. Every of them is managed by different editor. I was asked to display in page tree view in edit mode only those pages, that editor has editing access. It is reasonable as having dozens of pages (all marked with padlock) makes editing harder or at least less comfortable. Lets do not talk more about the background and take a look at implementation...


In general I think that EPiServer code is a little bit too sealed. I hate all those classes where you can not reimplement part of the code on your own or where you can not overwrite methods. However, this time I was nicely surprised. It was enough to create custom implementation of the EPiServer.PlugIn.PlugInAttribute and use DataFactory.Instance.FinishedLoadingChildren event.

Actually, on the beginning I wanted to use DataFactory.Instance.LoadingChildren and set RequiredAccess to AccessLevel.Edit (see below), but it didn't work for me. If you know why, please comment so I can learn something :)
private static void Instance_LoadingChildren(object sender, ChildrenEventArgs e)
{
e.RequiredAccess = AccessLevel.Edit;
}


First I created my class inheriting from EPiServer.PlugIn.PlugInAttribute and implemented Start method so it adds new ChildrenEventHandler to the DataFactory.Instance.FinishedLoadingChildren event:
using System.Web;
using EPiServer;
using EPiServer.Configuration;
using EPiServer.Core;
using EPiServer.PlugIn;
using EPiServer.Security;

namespace Maras.TreeAdapter
{
public class TreeAdapter : PlugInAttribute
{
public static void Start()
{
DataFactory.Instance.FinishedLoadingChildren += Instance_FinishedLoadingChildren;
}

private static void Instance_FinishedLoadingChildren(object sender, ChildrenEventArgs e)
{
if (IsAccessCheckNecessary(e))
{
int index = 0;
while (index < e.Children.Count)
{
if (e.Children[index].ACL.HasAccess(PrincipalInfo.Current.Principal, AccessLevel.Edit))
{
index++;
}
else
{
e.Children.RemoveAt(index);
}
}
}
}
}
}

As you can see, I check whether the access control is necessary and then I remove all the children that current user is not allowed to edit. Pretty important is the IsAccessCheckNecessary method. My implementation assumes that I want to filter pages in edit mode and for the children of the start page only, so it looks like this:
private static readonly string UI_URL = Settings.Instance.UIUrl.ToString().ToLower();

private static bool IsAccessCheckNecessary(ChildrenEventArgs e)
{
return e.PageLink == PageReference.StartPage &&
HttpContext.Current.Request.Url.ToString().ToLower().StartsWith(UI_URL);
}


If you know any easier way of doing what I needed, feel free to comment below.

Source code can be downloaded from here: source code



Shout it
Read More »

Posted on Thursday, March 12, 2009 by Marek Musielak

EPiServer rich text editor

PropertyLongString and XHTML String are probably the most frequently used types of properties in all EPiServer sites. They are used on nearly all content pages. However I wanted to use the editor control on the standard aspx page. I thought it would be really easy - just put HtmlEditor control on my page and voila. It was a little bit more complex but finally I managed to do what I needed.


Lets start with an aspx page. What we need to do is to put <EPiServer:Property /> on the page and set EditMode attribute to true. I set Width and Height attributes as well but this is optional. Remember not to forget about setting runat="server" property. I also put message panel and button on the page so I can show how to access the text put into the wysiwyg editor. Here is my aspx page:

<%@ Page Language="C#" AutoEventWireup="True" CodeBehind="HtmlEditorPage.aspx.cs" Inherits="MyProject.HtmlEditorPage" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Html Editor Page</title>
<link rel="stylesheet" type="text/css" href="/App_Themes/Default/Styles/system.css" />
</head>
<body>
<form runat="server" method="post">
<asp:Panel ID="pnlMessage" runat="server" Visible="false" CssClass="EP-systemMessage">
<asp:Literal ID="litMessage" runat="server" />
</asp:Panel>
<EPiServer:Property id="myHtmlEditor" Width="500" height="350" EditMode="true" runat="server" />
<asp:Button ID="btnSend" runat="server" Text="Send" OnClick="btnSend_Click" />
</form>
</body>
</html>


Now it's time to write something in code behind. First we need to register scripts that are required by the editor. I used ResolveUrlFromUI and ResolveUrlFromUtil methods but if you know your UI and Util directories then you can put whole paths without using those methods. Now lets create new PropertyLongString. We can select chosen editor options for the property or just select EditorToolOption.All. If you don't want to use any of them you don't have to set it at all. Next set Name of your property and assign it to the InnerProperty of the editor. That's all .You have pretty HtmlEditor on your page.

The last thing is accessing the data from the editor. I tried several ways of accessing it but only one worked for me: retrieving content of the property by using value from Request.Form. If you know any smarter way, let me know. So here is code behind (source code can be downloaded from here: source code):

#region

using System;
using EPiServer;
using EPiServer.Core;
using EPiServer.Editor;

#endregion

namespace MyProject
{
public partial class HtmlEditorPage : SimplePage
{
protected override void OnInit(EventArgs e)
{
base.OnInit(e);

if (!Page.ClientScript.IsClientScriptIncludeRegistered("system.js"))
{
Page.ClientScript.RegisterClientScriptInclude("system.js",
((PageBase) Page).ResolveUrlFromUI("javascript/system.js"));
Page.ClientScript.RegisterClientScriptInclude("system.aspx",
((PageBase) Page).ResolveUrlFromUI("javascript/system.aspx"));
Page.ClientScript.RegisterClientScriptInclude("episerverscriptmanager.js",
((PageBase) Page).ResolveUrlFromUtil(
"javascript/EPiServerScriptManager.js"));
}

PropertyLongString longString = new PropertyLongString();

// select chosen options or use EditorToolOption.All
longString.EditorToolOptions = EditorToolOption.Bold | EditorToolOption.Italic | EditorToolOption.Underline;
longString.Name = "MyLongStringProperty";
myHtmlEditor.InnerProperty = longString;
}

protected void btnSend_Click(object sender, EventArgs e)
{
// yeah this is the only way of accessing what you put in the editor
litMessage.Text = Request.Form[myHtmlEditor.ID + "$ctl00$" + myHtmlEditor.PropertyName];
pnlMessage.Visible = true;
}
}
}


Shout it
Read More »

Posted on Friday, March 06, 2009 by Marek Musielak

TheBugger - to see without looking

In my view, one of the most important developer skill is to see without looking. Let me explain what I mean. Assume that we have a piece of code that have some complex functionality. You see it for the very first time and you want to know how it works and what it is used for. You could debug the code of course. However it would be perfect if you were able to see without looking - look at the code, read it and know what it is responsible for.

As in all other cases training makes a master. But still, doing the same thing to many times makes it boring and even annoying. That's why I do really like to practice my skills in some other ways. One of them is minesweeper. Yeah, the same old minesweeper that you had in your first MS Windows. But the rules of the game are to easy for you. So lets make them a little bit harder.


It's really easy to find all bugs when you have a possibility of marking bugs. But what when you can not tick their location? You have to remember their placement, to see where they are without looking at them. First time is not so effortless. Believe me, the second one is much simpler. And tenth is even easier than you expected. The same is with code reading. So don't give up - practice.

Are you able to debug the field without a possibility of marking bugs? Sure you are. If you weren't, you wouldn't be here ;)



Start again


So how fast have you debugged the field?

Any ideas how to make TheBugger more challenging? Leave your comment and I will consider it for sure.

And what are your ways of training your mind?

Shout it
Read More »

Posted on Tuesday, March 03, 2009 by Marek Musielak

The easiest way of editing web.config dynamically

Some time ago I needed to update web.config programatically from my web site in order to add my own key to <appSettings /> tag. I spent a lot of time looking for a solution on the Internet. I wasted a lot of time trying to reuse existing code from the Web but nothing worked for me. I most cases they thrown an exception, some didn't even compile. Finally I found one on my own.


First lets read existing configuration and value of the settings element:
protected void Page_Load(object sender, System.EventArgs e)
{
if (!IsPostBack)
{
System.Configuration.Configuration config = System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration("~");

System.Configuration.KeyValueConfigurationElement setting = config.AppSettings.Settings["MyValue"];

if (null != setting)
{
// lets do something with the value - displaying it in a textbox is an ides
textboxValue.Text = setting.Value;
}
}
}

We can use web.config file from some other directory if we use its name instead of '~' in WebConfigurationManager.OpenWebConfiguration("~") method.

We've already read the value and displayed it in textbox so we are ready to save updated value (or add new settings element if it hasn't been saved yet).
protected void btnSave_Click(object sender, System.EventArgs e)
{
try
{
System.Configuration.Configuration config = System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration("~");

System.Configuration.KeyValueConfigurationElement setting = config.AppSettings.Settings["MyValue"];

if (null != setting)
{
config.AppSettings.Settings["MyValue"].Value = textboxValue.Text;
}
else
{
config.AppSettings.Settings.Add("MyValue", textboxValue.Text);
}

config.Save();
literalMessage.Text = "New value saved. Application will be restarted automatically.";
}
catch (System.Exception exc)
{
literalMessage.Text = (exc.Message + "<br />" + exc.StackTrace).Replace("\n", "<br />");
}
}


And that's all. The solution is short so the post is short ;) Any comments? Do not hesitate to write them below.

Shout it
Read More »

Posted on Friday, February 27, 2009 by Marek Musielak

EPiServer online users list

Working with the application which is edited by dozens of people makes the application harder to maintain. Have you ever thought about the people that are doing their job editing the site before you start deploy the new version of the application? I hadn't used to... until I got a lot of complaints from editors that lost their job. "I wanted to save what I'd wrote but I got 'service unavailable' message instead" they repined.

It made me look for the solution for this problem. Sure, I could do the new builds during the nights but what if the site is hosted by external company and you don't have access to the servers? You won't be able to force anyone from that company to stay in the office over night just to deploy the new version of the site.

The other solution would be to send emails to all editors saying that server will be restarted so they can can save all their work. This idea has only only small drawback - it assumes that editors check their emails frequently. I've already spent enough time maintaining sites to know that it barely happens.

Finally I found a solution that works fine for me. I use System.Web.Security.Membership to get all users that are logged in and deploy the new builds when no one but me is editing the site.

So lets go step by step with this solution. Firstly I created the web page that would be used to diplay active users. I put the repater on it:
<asp:Repeater ID="rptUsers" runat="server">
<HeaderTemplate><table></HeaderTemplate>
<ItemTemplate>
<tr>
<td><%# ((MembershipUser)Container.DataItem).UserName %></td>
<td><%# ((MembershipUser)Container.DataItem).LastActivityDate.ToString("HH:mm:ss")%></td>
</tr>
</ItemTemplate>
<FooterTemplate></table></FooterTemplate>
</asp:Repeater>

and overrode OnLoad method:
public partial class OnlineUsers : SimplePage
{
private static DateTime applicationStartTime;

public static DateTime ApplicationStartTime
{
set { applicationStartTime = value; }
}

protected override void OnLoad(EventArgs e)
{
if (!User.Identity.IsAuthenticated)
{
AccessDenied();
}

List<MembershipUser> users = new List<MembershipUser>();

foreach (MembershipUser user in Membership.GetAllUsers())
{
if (user.IsOnline && user.LastActivityDate > applicationStartTime)
{
users.Add(user);
}
}

users.Sort(delegate(MembershipUser u1, MembershipUser u2)
{ return u2.LastActivityDate.CompareTo(u1.LastActivityDate); });

rptUsers.DataSource = users;
rptUsers.DataBind();

base.OnLoad(e);
}
}

On the beginning I though it would be enough to check MembershipUser.IsOnline property, but it turned out that when application is started all users are treated as online, cause MembershipUser.LastActivityDate is set to the time when application started. That's why I added applicationStartTime field that I set in Global.asax.cs file in Application_Start method and compare it with LastActivityDate property:
public class Global : EPiServer.Global
{
protected void Application_Start(Object sender, EventArgs e)
{
OnlineUsers.ApplicationStartTime = DateTime.Now;
}
}

And that's all. I can check whether my build would interrupt anyone's work. Remember, a happy customer is your walking advertisement. Lets try not to upset them unnecessarily ;)

Source code can be downloaded from here: source code



Shout it
Read More »