Update: There's a new article which explains the latest development of this project. Read here.</strong />
I will show you how I built a start page similar to Google IG in 7 nights using ASP.NET AJAX, .NET 3.0, LINQ, DLinq and XLinq. I have logged my day to day development experience in this article, and documented all the technical challenges, interesting discoveries and important design dropping widgets on the page. You have complete control over what you want to see, where you want to see, and how you want to see. The widgets are independent applications which provide you with a set of features like to-do-list, address book, contact list, RSS feed, etc. Start Pages are also widely known as RSS aggregators, or in general terms, "content aggregators" from a variety of web sources. But you can not only read RSS feeds using your Start Page but also organize your digital life with it. AJAX Start Pages are one step ahead of old school start pages like My Yahoo by giving you state-of-the-art UI with lots of JavaScript effects. They give you a desktop application like look and feel by utilizing AJAX and lots of advanced JavaScript and DHTML techniques.
Some of the popular AJAX Start Pages are Pageflakes, Live, Google IG, Netvibes, Protopage, Webwag etc. Among these,.
Build your page by dragging and dropping widgets. You can completely personalize the page by putting what you want and where you want. You can add, remove widgets on your page. You can drag and drop them where you like. Close your browser and come back again, you will see the exact setup as you left it. You can use it without registering as long as you like.
Once you put a lot of content on your page, you will find one page is not enough. You have the option to use multiple pages. You can create as many pages as you like.
Widgets provide you with an interesting architecture where you can focus on providing the features relevant to the widget and never worry about authentication, authorization, profile, personalization, storage, framework, etc. All these are something widgets get for granted from their host. Moreover, you can build widgets independent of the host project. You do not need the whole host web application source code in your local development computer in order to build widgets. Just create a regular ASP.NET 2.0 Web site, create a user control, make it do what it's supposed to do in regular postback model without worrying about JavaScript, can utilize .NET 2.0 or 3.0. You can use regular ViewState and store temporary states. You can also use ASP.NET Cache in order to cache data for Widgets. It is far better than what you find in current Start Pages where you have to build the whole widget using JavaScript and you need to abide by specific API guidelines and strict "no postback" model. Those who have built widgets for current Start Pages must know what a traumatizing experience widget development really is for them.
The client side is built using ASP.NET AJAX RC;
}
Here, we pass the UserName (which is basically a GUID for a new user), and we get back a UserPageSetup object which contains the user settings and pages and widgets on the first page that is rendered on screen.
UserName
UserPageSetup
Similarly, on the second visit, it just loads the user's setup by executing UserVisitWorkflow.
UserVisitWorkflow
public UserPageSetup LoadUserSetup( )
{
var properties = new Dictionary<string,object>();
properties.Add("UserName", this._UserName);
var userSetup = new UserPageSetup();
properties.Add("UserPageSetup", userSetup);
WorkflowHelper.ExecuteWorkflow( typeof( UserVisitWorkflow ),
properties );
return userSetup;
}
But how about performance? I did some profiling on the overhead of workflow execution, and it is very fast for synchronous execution. Here's proof from the log you get in the Visual Studio output window:
334ec662-0e45-4f1c-bf2c-cd3a27014691 Activity: Get User Guid 0.078125
b030692b-5181-41f9-a0c3-69ce309d9806 Activity: Get User Pages 0.0625
b030692b-5181-41f9-a0c3-69ce309d9806 Activity: Get User Setting 0.046875
b030692b-5181-41f9-a0c3-69ce309d9806 Activity: Get Widgets in page: 189 0.0625
334ec662-0e45-4f1c-bf2c-cd3a27014691 Total: Existing user visit 0.265625
The first four entries are the time taken by the individual activities during data access. The time entries here are in seconds, and the first four entries represent the duration of the database operations inside activities. The last one is the total time for running a workflow with 5 activities and some extra code. If you sum up all the individual activity execution time for database operations, it is 0.25, which is just 0.015 sec less than the total execution time. This means, executing the workflow itself takes around 0.015 sec, which is almost nothing.
DLinq is so much fun. It's so amazingly simple to write the data access layer that generates really optimized SQL. If you have not used DLinq before, brace for impact!
When you use DLinq, you just design the database and then use SqlMetal.exe (comes with the LINQ May CTP) in order to generate a Data Access class which contains all the data access codes and entity classes. Think about the dark age when you had to hand code all entity classes following the database design and hand code data access classes. Whenever your database design changed, you had to modify the entity classes and modify the insert, update, delete, get methods in the data access layer. Of course, you could use third party ORM tools or use some kind of code generators which generate entity classes from database schema and generate data access layer codes. But, do no more, DLinq does it all for you!
The best thing about DLinq is it can generate something called Projection which contains only the necessary fields and not the whole object. There's no ORM tool or Object Oriented Database library which can do this now because it really needs a custom compiler in order to support this. The benefit of projection is pure performance. You do not select fields which you don't need, nor do you construct a jumbo object which has all the fields. DLinq only selects the required fields and creates objects which contains only the selected fields.
Let's see how easy it is to create a new object in the database called "Page":
Page
var db = new DashboardData(ConnectionString);
var newPage = new Page();
newPage.UserId = UserId;
newPage.Title = Title;
newPage.CreatedDate = DateTime.Now;
newPage.LastUpdate = DateTime.Now;
db.Pages.Add(newPage);
db.SubmitChanges();
NewPageId = newPage.ID;
Here, DashboardData is the class which SqlMetal.exe generated.
DashboardData
Say, you want to change a Page's name:
var page = db.Pages.Single( p => p.ID == PageId );
page.Title = PageName;
db.SubmitChanges();
Here, only one row is selected.
You can also select a single value:
var UserGuid = (from u in db.AspnetUsers
where u.LoweredUserName == UserName &&
u.ApplicationId == DatabaseHelper.ApplicationGuid
select u.UserId).Single();
And, here's the Projection I was talking about:
var users = from u in db.AspnetUsers
select { UserId = u.UserId, UserName = u.LoweredUserName };
foreach( var user in users )
{
Debug.WriteLine( user.UserName );
}
If you want to do some paging like select 20 rows from 100 rows:
var users = (from u in db.AspnetUsers
select { UserId = u.UserId, UserName = u.LoweredUserName }).Skip(100).Take(20);
foreach( var user in users )
{
Debug.WriteLine( user.UserName );
}
If you are looking for a transaction, see how simple it is:
using( TransactionScope ts = new TransactionScope() )
{
List<Page> pages = db.Pages.Where( p => p.UserId == oldGuid ).ToList();
foreach( Page page in pages )
page.UserId = newGuid;
// Change setting ownership
UserSetting setting = db.UserSettings.Single( u => u.UserId == oldGuid );
db.UserSettings.Remove(setting);
setting.UserId = newGuid;
db.UserSettings.Add(setting);
db.SubmitChanges();
ts.Complete();
}
Unbelievable? Believe it.
You may have some mixed feelings about DLinq performance. Believe me, it generates exactly the right SQL that I wanted it to do. Use SqlProfiler and see the queries it sends to the database. You might also think all these "var" stuff sounds like late binding in the old COM era. It will not be as fast as strongly typed code or your own hand written super optimal code which does exactly what you want. You will be surprised to know that all this DLinq code actually gets transformed into pure and simple .NET 2.0 IL by the LINQ compiler. There's no magic stuff or no additional libraries in order to run this code in your existing .NET 2.0 project. Unlike many ORM tools, DLinq also does not heavily depend on Reflection.
var
There are two concepts here, one is the Widget Container and the other is the Widget. Widget Container provides the frame which has a header and a body area. The actual widget is loaded in the body area. WidgetContainer is a server control which is dynamically created on the page for each widget instance. The actual Widget is also a server control which is loaded dynamically inside the widget container.
WidgetContainer
Each Widget contains several UpdatePanels which helps smaller parts of the widgets to get updated without a whole page refresh or a whole Widget refresh. For example, the actual widget which is hosted inside the container is loaded inside an UpdatePanel. So, no matter how many times the actual widget postbacks, the whole widget does not postback or whole the column.
Finding the right combination of UpdatePanel and the distribution of HTML elements inside UpdatePanel was difficult. For example, I first put the whole widget inside one UpdatePanel. It worked nicely, there was only one UpdatePanel per widget so the overhead was small. But the problem was with the extenders which are attached with the HTML elements inside UpdatePanel. When UpdatePanel refreshes, it removes. One UpdatePanel hosts the header area and the other UpdatePanel hosts the actual Widget. This way, if you do something on the widget and the widget body refreshes, it does not refresh the header area, and the extenders attached to the header do not get lost. The CustomFloatingBehavior extender is attached with the header. So, the extender itself needs to be inside the UpdatePanel. But, putting the extender inside the UpdatePanel means every time the UpdatePanel refreshes, the extender is created and initialized again. This gives poor performance.
CustomFloatingBehavior
So, the optimal solution so far is have two UpdatePanels per WidgetContainer, one contains the contents of the header, not the whole header itself. So, when the header UpdatePanel refreshes, the DIV which contains the whole header does not get recreated as it is outside the UpdatePanel. This way, we can put the CustomFloatingBehavior extender outside the UpdatePanel too. Thus, the extender can attach with the header container DIV.
DIV
The WidgetContainer is quite simple. It has the header area where the title and the expand/collapse/close buttons are, and the body area where the actual Widget is hosted. In the solution, the file "WidgetContainer.ascx" is the WidgetContainer.
<asp:Panel
<asp:Panel
<asp:UpdatePanel
<ContentTemplate>
<table class="widget_header_table" cellspacing="0"
cellpadding="0">
<tbody>
<tr>
<td class="widget_title"><asp:LinkButton</td>
<td class="widget_edit"><asp:LinkButton</td>
<td class="widget_button"><asp:LinkButton
<asp:LinkButton
</td>
<td class="widget_button"><asp:LinkButton</td>
</tr>
</tbody>
</table>
</ContentTemplate>
</asp:UpdatePanel>
</asp:Panel>
<asp:UpdatePanel
<ContentTemplate><asp:Panel
</asp:Panel>
</ContentTemplate>
</asp:UpdatePanel>
</asp:Panel>
<cdd:CustomFloatingBehaviorExtender
When the page is loaded, for each widget instance, first a widget container is created and then the widget container hosts the actual widget inside it. WidgetContainer works as a gateway between the core framework and the actual Widget, and provides a convenient API for storing state, or changing the state of the widget like expanding/collapsing, etc. WidgetContainer also conveys important messages to the actual widget like when it is collapsed or when it is closed, etc.
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
var widget = LoadControl(this.WidgetInstance.Widget.Url);
widget.ID = "Widget" + this.WidgetInstance.Id.ToString();
WidgetBodyPanel.Controls.Add(widget);
this._WidgetRef = widget as IWidget;
this._WidgetRef.Init(this);
}
Here, the widget container first loads the actual widget from the URL provided in the Widget definition. Then, it puts the widget inside a body panel. It also passes its own reference as IWidgetHost to the actual widget.
IWidgetHost
WidgetContainer implements the IWidgetHost interface which helps the actual widget to communicate with the framework and the container:
public interface IWidgetHost
{
void SaveState(string state);
string GetState();
void Maximize();
void Minimize();
void Close();
bool IsFirstLoad { get; }
}
The implementations are quite simple. For example, the IWidgetHost.Minimize collapses the widget body area:
IWidgetHost.Minimize
void IWidgetHost.Minimize()
{
DatabaseHelper.Update<WidgetInstance>(this.WidgetInstance,
delegate(WidgetInstance i)
{
i.Expanded = false;
});
this.SetExpandCollapseButtons();
this._WidgetRef.Minimized();
WidgetBodyUpdatePanel.Update();
}
First, we update the WidgetInstance row and then we refresh the UI. The actual widget also gets a callback via the IWidget interface. drop support to panels. It also has a ReorderList control which you can use to provide reordering of items in a single list. Our widgets are basically panels with a header which acts as the drag handle and flows vertically in each column. So, it might be possible that we can create
<asp:Panel
<div id="DropCue1" class="widget_dropcue">
</div>
</asp:Panel>
<cdd:CustomDragDropExtender
LeftPanel becomes a widget container which allows the widgets to be dropped on it and reordered. The DragItemClass attribute on the extender defines the items which can be ordered. This prevents from non-widget HTML Divs from getting ordered. Only the DIVs with the class "widget" are ordered. So, say there are five DIVs with the class named "widget". It will allow reordering of only these five divs:
LeftPanel
DragItemClass
Div
widget
div
<div id="LeftPanel" class="widget_holder" >
<div id="WidgetContainer1_Widget" class="widget"> ... </div>
<div id="WidgetContainer2_Widget" class="widget"> ... </div>
<div id="WidgetContainer3_Widget" class="widget"> ... </div>
<div id="WidgetContainer4_Widget" class="widget"> ... </div>
<div id="WidgetContainer5_Widget" class="widget"> ... </div>
<div>This DIV will not move</div>
<div id="DropCue1" class="widget_dropcue"></div>
</div>
It also takes a DropCallbackFunction which it calls when a widget is dropped on the container.
DropCallbackFunction
function WidgetDropped( container, item, position )
{
var instanceId = parseInt(item.getAttribute("InstanceId"));
var columnNo = parseInt(container.getAttribute("columnNo"));
var row = position;
WidgetService.MoveWidgetInstance( instanceId, columnNo, row );
}
This allows me to get the widget which was dropped or reordered, the column, and the position. I can then call a Web drop does not refresh any part of the page and silently calls a Web Service in the background in order to save the position of the dropped widget.
The HTML output contains the column number inside the column DIV as an attribute, and each widget DIV contains the widget instance ID. These two IDs help the server identify what the column is and which widget has been moved.
ID
<div id="LeftPanel" class="widget_holder" columnNo="0">
<div InstanceId="151" id="WidgetContainer151_Widget" class="widget">
The additional attributes are generated from the server side.
Now, making the first extender is really hard. I generally do not openly admit if something was hard for me, so trust me, when I say hard, it is "H A R D". The architecture is just so overwhelming when you start with. But gradually, you will grasp the idea, and you will surely try hard to appreciate the OOP
I also created a convenient DatabaseHelper class which contains convenient methods for Insert, Update, and Delete. One of the issues with DLinq is that, if your entity travels through multi-tier, then they get detached from the DataContext from where they were initially loaded. So, when you try to update entities again using a different DataContext, you first need to attach the entity instance with the data context, then make the changes and call SubmitChanges. Now the problem is, from
Page p = DashboardData.GetSomePage();
...
...
// Long time later may be after a page postback
p.Title = "New Title";
DashboardData.UpdatePage( p );
Somehow you need to do this:
Page p = DashboardData.GetSomePage();
...
...
// Long time later may be after a page postback
DashboardData.AttachPage( p );
p.Title = "New Title";
DashboardData.UpdatePage( p );
But this is not possible because this means you cannot make DashboardData stateless. You need to create DataContext inside methods, and somehow you need to store the reference to DataContext between the function calls. This might be OK for a single user scenario, but is not an acceptable solution for multiuser websites.
So, I did this approach:
Page p = DashboardData.GetSomePage();
...
...
// Long time later may be after a page postback
DashboardData.Update<Page>( p, delegate( Page p1 )
{
p1.Title = "New Title";
});
Here, the Update<> method first attaches the page object with the DataContext and then calls the delegate passing the reference to the attached object. You can now modify the passed object as if you were modifying the original object inside the delegate. Once the delegate completes, it will be updated using DataContext.SubmitChanges();
Update<>
DataContext.SubmitChanges();
The implementation of the Update<> method is this:
public static void Update<T>(T obj, Action<T> update)
{
var db = GetDashboardData();
db.GetTable<T>().Attach(obj);
update(obj);
db.SubmitChanges();
}
Here's an example usage:
WidgetInstance widgetInstance = DatabaseHelper.GetDashboardData().
WidgetInstances.Single( wi => wi.Id == WidgetInstanceId );
DatabaseHelper.Update<WidgetInstance>( widgetInstance,
delegate( WidgetInstance wi )
{
wi.ColumnNo = ColumnNo;
wi.OrderNo = RowNo;
});
The delegate gives us a benefit that you are in the context of the business layer or the caller. So, you can access UI elements or other functions/properties which you need in order to update the entity's properties.
For convenience, I have made Insert<>, Delete<> also. But they are not required as they do not have such an "Attach first, modify later" requirement.
Insert<>
Delete<>
public static void Delete<T>(Action<T> makeTemplate) where T:new()
{
var db = GetDashboardData();
T template = new T();
makeTemplate(template);
db.GetTable<T>().Remove(template);
db.SubmitChanges();
}
The first widget we will build is a nice Flickr widget.
It downloads Flickr photos as XML feed from
var photos = (from photo in xroot.Element("photos").Elements("photo")
select new PhotoInfo
{
Id = (string)photo.Attribute("id"),
Owner = (string)photo.Attribute("owner"),
Title = (string)photo.Attribute("title"),
Secret = (string)photo.Attribute("secret"),
Server = (string)photo.Attribute("server"),
Farm = (string)photo.Attribute("Farm")
})
But from the screenshot, you see you can navigate between the photos because Flickr actually returns more than 9 photos. So, we need to prepare objects of the PhotoInfo class from only those XML nodes which belong to the current paging index.
Here's how paging is done on the XML:
var);
We take only 9 photos from the current pageIndex.
foreach( var photo in photos )
{
if( col == 0 )
table.Rows.Add( new HtmlTableRow() );
var cell = new HtmlTableCell();
var img = new HtmlImage();
img.Src = photo.PhotoUrl(true);
img.Width = img.Height = 75;
img.Border = 0;
var link = new HtmlGenericControl("a");
link.Attributes["href"] = photo.PhotoPageUrl;
link.Attributes["Target"] = "_blank";
link.Attributes["Title"] = photo.Title;
link.Controls.Add(img);
cell.Controls.Add(link);
table.Rows[row].Cells.Add(cell);
col ++;
if( col == Columns )
{
col = 0; row ++;
}
count ++;
}
The reason why I used HtmlGenericControl instead of HtmlLink is that, HtmlLink does not allow you to add controls inside its Controls collection. This is a limitation of the HtmlLink class.
HtmlGenericControl
HtmlLink
Controls
This was very easy to make using XLinq. Then, I built the RSS Widget which shows RSS Feeds from a feed source. First, I get the URL of the feed from the Widget State and then download the feed XML:
string url = State.Element("url").Value;
int count = State.Element("count") == null ? 3 :
int.Parse( State.Element("count").Value );
var feed = Cache[url] as XElement;
if( feed == null )
{
feed = XElement.Load(url);
Cache.Insert(url, feed, null, DateTime.MaxValue, TimeSpan.FromMinutes(15));
}
Then, I bind the XML to a DataList which shows a list of Hyperlinks:
DataList
Hyperlink
FeedList.DataSource = (from item in feed.Element("channel").Elements("item")
select new
{
title = item.Element("title").Value,
link = item.Element("link").Value
}).Take(this.Count);
The DataList is very simple:
<asp:DataList
<ItemTemplate>
<asp:HyperLink
<%# Eval("title") %>
</asp:HyperLink>
</ItemTemplate>
</asp:DataList>
And that's all!
But there's a bit user's page setup again. This is done inside the new user visit workflow, which is like this:
The last activity named "CallWorkflow" calls the User Visit workflow again in order to load the user setup which is just created. So, here we can see some reuse of the workflow.
CallWorkflow
The activities do very small amount of work. For example, the create new page activity creates a new page and returns the ID:
protected override ActivityExecutionStatus Execute(
ActivityExecutionContext executionContext)
{
DashboardData db = DatabaseHelper.GetDashboardData();
var newPage = new Page();
newPage.UserId = UserId;
newPage.Title = Title;
newPage.CreatedDate = DateTime.Now;
newPage.LastUpdate = DateTime.Now;
db.Pages.Add(newPage);
db.SubmitChanges(ConflictMode.FailOnFirstConflict);
NewPageId = newPage.ID;
return ActivityExecutionStatus.Closed;
}
DashboardFacade, which is the entry point to the business layer, is quite simple. It knows which workflows to invoke on which operations. It just takes the parameters and invokes the right workflow for the operation. For example, it has a NewUserVisit function which does nothing but execute NewUserVisitWorkflow.
DashboardFacade
NewUserVisit
NewUserVisitWorkflow
public class DashboardFacade
{
private string _UserName;
public DashboardFacade( string userName )
{
this._UserName = userName;
};
}
There were three major headaches I had to solve while implementing the business layer using Workflow and DLinq.
Workflow is generally made for asynchronous execution. WorflowRuntime is usually created only once per Application Domain, and the same instance of runtime is used everywhere in the same app domain. In ASP.NET, the only way you can ensure single instance of a WorkflowRuntime and make it available everywhere
public static void ExecuteWorkflow( Type workflowType,
Dictionary<string,object> properties)
{
WorkflowRuntime workflowRuntime =
HttpContext.Current.Application["WorkflowRuntime"] as
WorkflowRuntime;
ManualWorkflowSchedulerService manualScheduler =
workflowRuntime.GetService
<ManualWorkflowSchedulerService>();
WorkflowInstance instance =
workflowRuntime.CreateWorkflow(workflowType, properties);
instance.Start();
manualScheduler.RunWorkflow(instance.InstanceId);
}
It takes the type of workflow to execute and a dictionary of data to pass to the workflow.
Before running any workflow, first,
public static WorkflowRuntime Init()
{
var workflowRuntime = new WorkflowRuntime();
var manualService = new ManualWorkflowSchedulerService();
workflowRuntime.AddService(manualService);
var syncCallService = new Activities.CallWorkflowService();
workflowRuntime.AddService(syncCallService);
workflowRuntime.StartRuntime();
HttpContext.Current.Application["WorkflowRuntime"] = workflowRuntime;
return workflowRuntime;
}
Here, you see two services are added to the workflow runtime. One is for synchronous execution, and another is for synchronous execution of one workflow from another.
This was a major headache to solve. The InvokeWorkflow activity which comes with Workflow Foundation return. If you use
It is a requirement that the interface must use intrinsic data types or types which are serializable. Serializable is a requirement because the workflow can go to sleep or get persisted and restored later on. But, DLinq entity classes cannot be made serializable. The classes that SqlMetal generates are first of all not marked as [Serializable]. Even if you add the attribute manually, it won't work. I believe, during compilation, the classes are compiled into some other runtime class which does not get the Serializable attribute. As a result, you cannot pass DLinq entity classes from call here, so there is no need for serialization. Also, modifying the objects or using them does not cause any performance problem because the objects are allocated in the same process.
Here's an example:;
}
So far so good. But, how do you write DLinq code in a WinFX project? If you create a WinFX project and start writing LINQ code, it won't compile. LINQ requires and use no Declarative rules. But I so desperately wanted to write rules in workflows and activities. I struggled the whole night on this problem, but found no solution. It was so frustrating. Then in the dawn, when there was absolute silence everywhere and the sun was about to rise, I had a divine revelation to me from heaven:
Thou shalt bring forth the source of misery above thy.
So, I did. I brought the .rules file (source of misery) from under the .cs file to one level upward on the project level. It then looked like this:
For this, I had to open the Project file (.csproj) in Notepad and remove the <DependentUpon> node under the <EmbeddedResource> node:
<DependentUpon>
<EmbeddedResource>
<ItemGroup>
<EmbeddedResource Include="Activities\CreateDeafultWidgetsOnPageActivity.rules">
<!-- <DependentNode>CreateDeafultWidgetsOnPageActivity.cs</DependentNode> -->
</EmbeddedResource>
And it worked! There's absolutely no way in the world I could have known that, right?
Widgets need to know whether it's state or reflect small changes on the UI. So, it is important for users to know when they are being rendered for the first time and when it is a postback.
However, when you have multiple tabs, the definition of first time load and postback changes. When you click on another tab, it's a regular postback for ASP.NET because a LinkButton gets clicked. This makes the Tab UpdatePanel postback asynchronously, and on the server-side, we find out which tab is clicked. Then, we load the widgets on the newly selected tab. But, when
So, we need to make sure during tab switch, although it's a regular ASP.NET postback, Widgets must not see it as postback. The idea is to inform widgets whether it is a first time load or not, via the IWidget interface.
On Default.aspx, there's a function SetupWidgets which creates the WidgetContainer and loads the widgets. Here's how it works:
SetupWidgets
private void SetupWidgets(Func<WidgetInstance, bool> isWidgetFirstLoad)
{
var setup = Context.Items[typeof(UserPageSetup)] as UserPageSetup;
var columnPanels = new Panel[] {
WidgetViewUpdatePanel.FindControl("LeftPanel") as Panel,
WidgetViewUpdatePanel.FindControl("MiddlePanel") as Panel,
WidgetViewUpdatePanel.FindControl("RightPanel") as Panel};
// Clear existing widgets if any
foreach( Panel panel in columnPanels )
{
List<WidgetContainer> widgets =
panel.Controls.OfType<WidgetContainer>().ToList();
foreach( var widget in widgets ) panel.Controls.Remove( widget );
}
Skip the Func<> thing for a while. First, I clear the columns which contain the WidgetContainer so that we can create the widgets again. See the cool LINQ way to find out only the WidgetContainer controls from the Panel's Controls collection.
Func<>
Panel
Now, we create the WidgetContainers for the widgets on the newly selected tab:
foreach( WidgetInstance instance in setup.WidgetInstances )
{
var panel = columnPanels[instance.ColumnNo];
var widget = LoadControl(WIDGET_CONTAINER) as WidgetContainer;
widget.ID = "WidgetContainer" + instance.Id.ToString();
widget.IsFirstLoad = isWidgetFirstLoad(instance);
widget.WidgetInstance = instance;
widget.Deleted +=
new Action<WidgetInstance>(widget_Deleted);
panel.Controls.Add(widget);
}
While creating, we set a public property IsFirstLoad of the WidgetContainer in order to let it know whether it is being loaded for the first time or not. So, during the first time load of Default.aspx or during tab switch, the widgets are setup by calling:
public
IsFirstLoad
SetupWidgets( p => true );
What you see here is called Predicate. This is a new feature in LINQ. You can make such predicates and avoid creating delegates and the complex coding model for delegates. The predicate returns true for all widget instances, and thus all widget instances see it as first time load.
true
So, why not just send "true" and declare the function as SetupWidgets(bool). Why go for the black art in Linq?
SetupWidgets(bool)
Here's a scenario which left me no choice but to do this. When a new widget is added on the page, it is a first time loading experience for the newly added widget, but it's a regular postback for existing widgets already on the page. So, if we pass true or false for all widgets, then the newly added widget will see it as a postback just like all other existing widgets on the page and thus fail to load properly. We need to make sure it's a non-postback experience only for the newly added widget, but a postback experience for the existing widget. See how it can be easily done using this Predicate feature: and all user related settings to the newly signed up user.
The difficulty was to get the anonymous user's GUID. I tried Membership.GetUser(), passing Profile.UserName which contains the anonymous user name. But, it does not work. It seems Membership.GetUser only returns a user object which exists in an index on ApplicationID and LoweredUserName. So, if you do not include AapplicationID in the criteria and do not use the LoweredUserName field, the index will not hit, and the query will end up in a table scan which is very expensive. Please see my blog post for details on this: Careful-when-querying-on-aspnet.
LoweredUserName
ApplicationID
AapplicationID
Once we have the UserId of the anonymous user, we just need to update the UserID column in Page and the UserSetting table to the newly registered user's UserId.
UserID
So, first get the new and old UserId:
MembershipUser newUser = Membership.GetUser(email);
// Get the User Id for the anonymous user from the aspnet_users table
AspnetUser anonUser = db.AspnetUsers.Single( u =>
u.LoweredUserName == this._UserName
&& u.ApplicationId == DatabaseHelper.ApplicationGuid );
Guid oldGuid = anonUser.UserId;
Guid newGuid = (Guid)newUser.ProviderUserKey;
Now, update the UserId field of the Pages of the user:
List<Page> pages = db.Pages.Where( p => p.UserId == oldGuid ).ToList();
foreach( Page page in pages )
page.UserId = newGuid;
But here's a catch. You cannot change the field value if it's a primary key, using DLinq. You have to delete the old row using the old primary key and then create a new row usingQ, and ASP.NET AJAX. So, the web.config needs to be configured in such a way that it allows harmonious co-existence of these volatile technologies. The web.config itself requires a lot of explanation. I will just highlight the areas which are important.
You need to use the LINQ compiler so that the default C# 2.0 compiler does not compile the site. This is done by:
<system.codedom>
<compilers>
<compiler language="c#;cs;csharp" extension=".cs"
type="Microsoft.CSharp.CSharp3CodeProvider,
CSharp3CodeDomProvider"/>
</compilers>
</system.codedom>
Then, you need to put some extra attributes in the <compilation> node:
<compilation>
<compilation debug="true" strict="false" explicit="true">
Now, you need to include the ASP.NET AJAX assemblies and WinFX assemblies:
<compilation debug="true" strict="false" explicit="true">
<assemblies>
<add assembly="System.Web.Extensions, ..."/>
<add assembly="System.Web.Extensions.Design, ..."/>
<add assembly="System.Workflow.Activities, ..."/>
<add assembly="System.Workflow.ComponentModel, ..."/>
<add assembly="System.Workflow.Runtime, ..."/>
You also need to put "CSharp3CodeDomProvider.dll" in the "bin" folder and add references to System.Data.DLinq, System.Data.Extensions, System.Query, and System.Xml.Xlinq. All these are required for LINQ.
System.Data.DLinq
System.Data.Extensions
System.Query
System.Xml.Xlinq
I generally remove some unnecessary HttpModule from the default ASP.NET pipeline for faster performance:
HttpModule
<httpModules>
<!-- Remove unnecessary Http Modules for faster pipeline -->
<remove name="Session"/>
<remove name="WindowsAuthentication"/>
<remove name="PassportAuthentication"/>
<remove name="UrlAuthorization"/>
<remove name="FileAuthorization"/>
<add name="ScriptModule" type="System.Web.Handlers.ScriptModule, ..."/>
</httpModules>
Very slow, especially in Internet Explorer 6. In fact, its slowness is so bad that you can visually see it on the local machine while running on your powerful development computer. Try pressing F5 several times on a page where all the required data for the page are already cached on the server side. You will see the total time it takes to fully load the page is quite long. ASP.NET AJAX provides a very rich object oriented programming model and a strong architecture which comes at a high price on performance. From what I have seen, as soon as you put UpdatePanels on the page and some extenders, the page becomes too slow. If you just stick to the core framework for only the Web Service call, you are fine. But as soon as you start using UpdatePanel and some extenders, it's pretty bad. ASP.NET AJAX performance is good enough for simple pages which have, say, one UpdatePanel and one or two extenders for some funky effects., this is something you should try in all AJAX applications because AJAX applications are full of client-side code. Unfortunately, you cannot achieve this using ASP.NET AJAX unless you do serious hacking. You will see that even for a very simple page setup which has only three extenders, the number of file downloadeds is significant:
All the files with ScriptResource.axd are small scripts in the AJAX Control Toolkit and my extenders. The size you see here is after gzip compression, and they are still quite high. For example, the first two are nearly 100 KB. Also, all these are individual requests to the server which could be combined into one JS file and served in one connection. This would result in better compression and much less download time. Generally, each request has 200ms of network roundtrip overhead, which is the time it takes for the request to reach and Extenders are not good for websites which push client-side richness to the extreme like AJAX Start Pages, but definitely very handy for not so extreme websites. It's very productive to have designer support in Visual Studio and very good ASP.NET 2.0 integration. It will save you from building an AJAX framework from scratch and all the JavaScript controls and effects. In Pageflakes, we realized there the browser homepage.
Due to a problem in ASP.NET AJAX RC version, you can't just copy the website to a production server and run it. You will see none of the scripts are loading because ScriptHandler malfunctions. In order to deploy it, you will have to use the "Publish Website" option to pre
If you like this project, let's make some cool widgets for it. For example, a To-do-list, Address book, Mail Widget, etc. This can become a really useful Start Page if we can make some useful widgets for it. We can also try making a widget which runs Google IG modules or Pageflakes' flakes on it.
AJAX Start Page is a really complex project where you push DHTML and JavaScript to their limits. As you add more and more features on the client side, the complexity of the web application increases geometrically. Fortunately, ASP.NET AJAX takes away a lot of complexity on the client-side so that you can focus on your core features and leave the framework and AJAX stuff.