Yesterday, I spent an hour setting up a Debian Linux VM to host a MediaWiki, which is now a 1.2 GB big folder in the /doc folder of a new project, checked into SVN. While I was doing that despite the utter stupidity behind it, I was remembering that I tried to create a better Wiki System a few months back with SWiki. Now SWiki is nice in theory, but I think I made some fundamentally wrong decisions in it's scope and in the end, I didn't want to use it myself for anything. So I was thinking again about what problem I tried to solve and what I want to achieve.
MediaWiki is a fantastic system, but it requires a LAMP Stack to really work, and the fact that all data is in a database doesn't make it very Source Control friendly. So I really wanted to solve two different problems: I wanted all the data in the file system, so that it can be checked into source control and easily be shared by other people. So no database server. But to share it with other people, they need to be easily able to read the files. I did not want an application that digs deep into the system. Ideally, I do not want to require installation at all and leave no marks in the system.
So the original SWiki solved problem 1 by using a SQLite Database. There is nothing wrong with using a database as long as it's 100% filesystem based. My attempt at Problem 2 was to host the Internet Explorer COM Object Microsoft exposes and feed it HTML. That way, I don't even need a web server. But that attempt ultimately proved a failure. I could not do much of the rich formatting I wanted, even embedding images was impossible. At some point I questioned whether my use of HTML was correct, or if I shouldn't just use RTF and embed stuff using OLE.
But my problem wasn't the Markup part. I like the WikiPlex markup, and the library is clean and extensible. I really didn't want to try using OLE/RTF as these technologies are not meant for human consumption. Also, I want to keep the ability to export the Wiki to HTML so that it can be hosted on a real web server if desired (e.g., the documentation section of a project).
So really, I need a Web Server. But I don't need a particularly "good" one, especially because I do not want a large "footprint" in the network. Really, running a web server is a great way to trigger all sorts of security software across the entire company. But without a webserver, SWiki is simply not good. So I've started looking at the Cassini Web Server, which is conveniently open source under Ms-PL. This is a great server for many reasons. One, it's 100% managed code, so it can be completely embedded in my app. But more importantly, it can be configured to only ever listen on local loopback, that is 127.0.0.1 for IPv4 or ::1 for IPv6. This may still trigger some security software, but it seals off the application from the network, so no interruption should be caused in theory.
Overall, this is a good compromise. I gain the ability to turn SWiki into a full-blown ASP.net application by simply bundling it with a lightweight web server and control utility, all while still being able to keep the data in a single file. So this is what I will do now. I haven't decided if I keep SQLite as database or use SQL Compact due to it's much better integration and if I'm able to offer an upgrade for databases between "SWiki old" and "SWiki new". I haven't even decided if I keep the name or change it, although the existence of another wiki by that name makes me want to change it.
The only thing I really have decided on is that it will support images and that it will still use WikiPlex as it's parser, all while still requiring .net 3.5 and all while - at it's core - still being a simple, standalone desktop wiki, although a little bit less simple than originally envisioned.
I've been starting to pick up ASP.net AJAX again, mainly because as a SharePoint developer I will be "stuck" with WebForms 3.5 for the next few years (SharePoint 2010 will not be a .net 4.0 application). As part of that, I remembered one issue I had: The UpdateProgress renders a div with display:block set, which means it is not possible to have the updateProgress right next to a button. Joe Audette had the right idea: Grab the UpdateProgress from the Mono Project and modify it. The nice thing about the Class Libraries of Mono is that they are licensed under the very liberal MIT X11 license, making them good candidates as a base for modifications.
Here is the source code of the UpdateProgressEx, which includes a new Property, DisplayInline. If this is set to true, then we render a span with display: inline. If this is set to false or not set at all, we have the default behavior of a div/block. If DisplayInline is set to true, then we force DynamicLayout to be false. Why? Because if DynamicLayout is true, then it seems like the MS AJAX JavaScript control the display, and they render it as a block. As I did not want to modify it any further, I just force DynamicLayout to false because for me, that works good enough. Also, I haven't tested if it works in visual design mode and if the property panel shows the new DisplayInline Property, as I work only in code view. YMMV, but it's Open Source after all, so feel free to properly fix it
Here is the code:
// UpdateProgressEx
//
// Author:
// Michael Stum <website@stum.de>
// http://www.stum.de/2010/01/28/an-asp-net-ajax-updateprogress-that-can-render-inline
//
// Original Author:
// Igor Zelmanovich <igorz@mainsoft.com>
// (C) 2007 Mainsoft, Inc. http://www.mainsoft.com
//
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Web.UI;
namespace StumDE.WebControls
{
[PersistChildren(false)]
[ParseChildren(true)]
[DefaultProperty("AssociatedUpdatePanelID")]
[Designer("System.Web.UI.Design.UpdateProgressDesigner, System.Web.Extensions.Design, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")]
public class UpdateProgressEx : Control, IScriptControl
{
ITemplate _progressTemplate;
ScriptManager _scriptManager;
[Category("Behavior")]
[DefaultValue("")]
[IDReferenceProperty(typeof(UpdatePanel))]
public string AssociatedUpdatePanelID
{
get
{
return (string)ViewState["AssociatedUpdatePanelID"] ?? String.Empty;
}
set
{
ViewState["AssociatedUpdatePanelID"] = value;
}
}
[Category("Behavior")]
[DefaultValue(false)]
public bool DisplayInline
{
get
{
object o = ViewState["DisplayInline"];
if (o == null)
return false;
return (bool)o;
}
set
{
ViewState["DisplayInline"] = value;
}
}
[Category("Behavior")]
[DefaultValue(500)]
public int DisplayAfter
{
get
{
object o = ViewState["DisplayAfter"];
if (o == null)
return 500;
return (int)o;
}
set
{
ViewState["DisplayAfter"] = value;
}
}
[Category("Behavior")]
[DefaultValue(true)]
public bool DynamicLayout
{
get
{
// If DisplayInline is set, force dynamic layout to be false
if(DisplayInline) return false;
object o = ViewState["DynamicLayout"];
if (o == null)
return true;
return (bool)o;
}
set
{
ViewState["DynamicLayout"] = value;
}
}
[PersistenceMode(PersistenceMode.InnerProperty)]
[Browsable(false)]
public ITemplate ProgressTemplate
{
get
{
return _progressTemplate;
}
set
{
_progressTemplate = value;
}
}
ScriptManager ScriptManager
{
get
{
if (_scriptManager == null)
{
_scriptManager = ScriptManager.GetCurrent(Page);
if (_scriptManager == null)
throw new InvalidOperationException(String.Format("The control with ID '{0}' requires a ScriptManager on the page. The ScriptManager must appear before any controls that need it.", ID));
}
return _scriptManager;
}
}
protected virtual IEnumerable<ScriptDescriptor> GetScriptDescriptors()
{
string updatePanelClientId;
if (String.IsNullOrEmpty(AssociatedUpdatePanelID))
updatePanelClientId = null;
else
{
var updatePanel = FindControl(AssociatedUpdatePanelID) as UpdatePanel;
if (updatePanel == null)
throw new InvalidOperationException("No UpdatePanel found for AssociatedUpdatePanelID '" + AssociatedUpdatePanelID + "'.");
updatePanelClientId = updatePanel.ClientID;
}
var descriptor = new ScriptControlDescriptor("Sys.UI._UpdateProgress", this.ClientID);
descriptor.AddProperty("associatedUpdatePanelId", updatePanelClientId);
descriptor.AddProperty("displayAfter", DisplayAfter);
descriptor.AddProperty("dynamicLayout", DynamicLayout);
descriptor.AddProperty("displayInline", DisplayInline);
yield return descriptor;
}
protected virtual IEnumerable<ScriptReference> GetScriptReferences()
{
yield break;
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
ScriptManager.RegisterScriptControl(this);
if (_progressTemplate == null)
throw new InvalidOperationException(String.Format("A ProgressTemplate must be specified on UpdateProgress control with ID '{0}'.", ID));
var container = new Control();
_progressTemplate.InstantiateIn(container);
Controls.Add(container);
}
protected override void Render(HtmlTextWriter writer)
{
if (DynamicLayout)
writer.AddStyleAttribute(HtmlTextWriterStyle.Display, "none");
else
{
writer.AddStyleAttribute(HtmlTextWriterStyle.Display, DisplayInline ? "inline" : "block");
writer.AddStyleAttribute(HtmlTextWriterStyle.Visibility, "hidden");
}
writer.AddAttribute(HtmlTextWriterAttribute.Id, ClientID);
if (DisplayInline)
{
writer.RenderBeginTag(HtmlTextWriterTag.Span);
}
else
{
writer.RenderBeginTag(HtmlTextWriterTag.Div);
}
base.Render(writer);
writer.RenderEndTag();
ScriptManager.RegisterScriptDescriptors(this);
}
#region IScriptControl Members
IEnumerable<ScriptDescriptor> IScriptControl.GetScriptDescriptors()
{
return GetScriptDescriptors();
}
IEnumerable<ScriptReference> IScriptControl.GetScriptReferences()
{
return GetScriptReferences();
}
#endregion
}
}
For a Project I had on a SharePoint site I thought it would be a good idea to create a new master page that is based on the default.master and exposes some placeholder for my content pages to use. Nested Master Pages are trivial to do in ASP.net normally, but for some reason on my SharePoint site, the child page was not rendering its content in one of the new placeholders.
Well, turns out that SharePoint 2007/WSSv3 explicitly does not support nested master pages. To quote an Article in MSDN directly:
Although the underlying technology enables the creation of nested master pages, nested master pages are not supported in Windows SharePoint Services. Using nested master pages can cause unexpected behavior in some scenarios, such as preventing content from rendering.
Indeed, unexpected behavior, such as content not rendering is the exact problem I have. Now I remember why I love SharePoint so much: It's taking the essential functions of ASP.net and then adds some traps that really hurt you unexpectedly.
The first episode in the Debugging in Visual Studio 2010 series is about the most fundamental concept of all, Breakpoints. Also, we cover the Locals window, which is the first additional window of the debugger.
As this is the first episode, there are some kinks I haven't manage to fully iron out. The audio quality isn't perfect, mainly because the Microphone I specifically bought for stuff like this turned out to be junk and I had to use the one in my Webcam. Also I experimented with having a script vs. speaking freely and tend to overuse the phrase "You may notice" quite a bit...
You can watch the video in higher, full HD quality directly on Vimeo.
I found that if I want to learn something, I should try explaining or teaching it to someone else. As I work as a SharePoint Developer, the Debugger became my best friend very quickly, but I always ever only touched the surface and maybe a little bit more. A recent problem requires me now to dig really deep and is forcing me to really learn to use the debugger. So I thought this would be an excellent opportunity to create a little video series, of which the first chapter will be "Beginning Debugging in Visual Studio 2010". Now let me first start with a disclaimer: I am still learning this stuff. I am not a senior developer at Microsoft with 20 years experience writing operating system, I am just someone who picked up C# 4 years ago and is now trying to learn through teaching.
This series is not about design or architecture, it is not about unit testing or best practices. This is purely about having some buggy code and using the debugger to find the issue and solve it. This is about moving from trial-and-error/guessing to evidence-gathering.
There is no set schedule for releases and I am not sure how many episodes there will be. I do have plans for three chapters of varying "difficulty" and I do have a to-do list of stuff I want to cover, but as some items on this list are really hardcore I'm not sure what will come out of this.
Anyway, here is an index to all released episodes:
Chapter One: Beginning Debugging in Visual Studio 2010
So we have a bool that we switch on every iteration and then we derive the CSS Class Name from that. If you do that in one place, it's okay, but if you have to do that in many places, it can become quite a bit tedious. Sure, if you happen to use a for-loop you can just use "index % 2 == 0" to find out if you are on an even row, but I instead created a class:
public class EvenOddCycler
{
private readonly string _oddClassName;
private readonly string _evenClassName;
private int _numCycles;
public EvenOddCycler() : this("evenRow","oddRow"){}
public EvenOddCycler(string evenClassName, string oddClassName)
{
_evenClassName = evenClassName;
_oddClassName = oddClassName;
_numCycles = 0;
}
public string Cycle()
{
_numCycles++;
return IsCurrentlyEven ? _evenClassName : _oddClassName;
}
public void Reset()
{
_numCycles = 0;
}
public bool IsCurrentlyEven
{
get { return (_numCycles % 2 == 0); }
}
}
Really simple stuff here: The constructor takes two strings, one for even and for odd. It has a Cycle function that increases a counter and returns one of the two strings. Also included a Reset() function to re-use the cycler in case you have more than one table on a page.
The usage looks like this:
var cycler = new EvenOddCycler();
foreach (var item in someList)
{
SomeTable.Rows.Add(SomeFunctionThatReturnsATableRow(item,cycler.Cycle()));
}
What did we gain?
No need to constantly repeat the class names over and over again. You shouldn't hardcode them, but even if you put it in a Resource class of some kind, you still have to have the boolean switch somewhere to get the correct class name. No need here anymore.
No need to copy/paste this if you have multiple tables on a Page. Just call cycler.Reset()
Granted. it's a small thing, but every little thing I don't have to worry about is good.
This is not Thread-safe because I see no scenario where you would share one cycler. Making it Thread-safe is a simple matter of adding a lock object and locking all three method bodies though.
Okay, so my system Hard Drive isn't too big, only 139 GB. But it's a 10k Raptor, so for the time being it will have to suffice until SSDs get good and affordable.
To my shock, today it was full:
While checking what took so much space, I found quite a few things, but one caught my attention, the Hibernate file:
I have 8 Gigabytes of RAM in my machine, so I have to live with a large Page File (no, I'm not disabling it. If even Mark Russinovich advises against disabling it, that counts more), but I certainly do not need the Hibernate file as this is a Desktop PC that's either running or off.
Weirdly, I couldn't find any option in Windows 7 to do so.Not sure if it's me or if there really is none, but it can easily be done on the command line. Run an elevated command prompt (cmd.exe, Run As Administrator) and type in
powercfg /hibernate off
The Hiberfil.sys should now be gone and the disk space should be free again.
I remember playing around with VMWare Workstation around 2000 when it was a new idea. It seemed awesome, as I was working in a PC Shop at the time serving both German and English customers. We ran Windows 2000 on our Work machines, but our customers usually had Windows 98 SE or Windows ME. With VMWare, I could install German and English Windows 98 and ME and troubleshooting became a lot easier.
Fast forward to the present day. VMs are a staple in the toolset of every developer now. Need to test your app on Windows XP, Vista and 7? Need to test deployment on both Server 2003 and 2008? Need a legacy Internet Explorer 6 machine? Or want to give Linux a spin? Great, just create a VM. Virtualization products are available for free now, thanks to Microsoft giving away VirtualPC and VMWare giving away their VMWare Server.
Now, VirtualPC is rather useless sadly, as it can not run 64-Bit Guests. Yup, surprised me as well. On the other hand, VMWare Server isn't really well suited for Desktops - it goes "too deep" into the system and it's whole interface is more aimed at remote usage (you can't select an .iso file on your hard drive unless you previously added it to a list of known locations...). There was a different free VMWare product though, VMWare Player.
The first versions could only run, but not create Virtual machines. They were great to run Live Systems/Appliances, for example Mono. Needless to say, soon sites like EasyVMX emerged to allow creating new Virtual Machines, so that Player was a full VM solution. So yesterday I installed the newest VMWare Player 3, already prepared to head to EasyVMX to create my VM. But then I was pleasantly surprised - Player now creates VMs!
And VMWare really went all out here. At least for Windows, they automatically detect your Operating System and offer Easy Install:
Easy Install means that it will do an unattended installation - you only have to enter your Product Key and maybe a password, then you can lean back and wait until it presents you with the Login Screen of a fresh Windows installation.
This, my dear friends, is awesome! I don't have much use for the advanced features of VMWare Workstation, so Player is my solution of choice - and Player 3 is a significant update!
Well done, VMWare! I wonder if Microsoft will ever catch up? XP Mode in Windows 7 is kinda nice, but I'd rather setup an XP VM in Player than installing a 32-Bit-only Virtual PC.
Last week I decided to do a little Giveaway for TekPub's ASP.net MVC 2 Video Series. If you are an Open Source WebForms developer, you could win one coupon from me and 4 coupons from TekPub.
Now that the Giveaway is over, here are the Winners:
Yup, that's it. Only one entry at all. When I wrote "Okay, here is a little experiment. No idea if it works, but one can try" I meant that honestly: I really didn't know if this would be a success or not. As it turned out, it wasn't one. But this is science, so I still declare it a success because it yielded results.
I will of course keep my promise, so Sean should receive a coupon today or tomorrow - Have fun with it
But let's see what went wrong here. Let's take the perfectly logical approach here and start with...
Blaming the others
So one theory is that there are simply no Webforms OSS developers. If I would have opened this for PHP developers, I'm fairly certain I would actually have to write a Randomizer to pick 5 Winners. I can't think of many Webforms OSS projects apart from med-profile 2 blogging engines (SubText and dasBlog). Are there really none? I can't think of a single high profile one.
Did WebForms miss the push to Open Source?
Between November 2007 and today, Open Source became relatively big in the .net world. Sure, most code is still written internally, but there is a significant amount of mid-to-high profile open source .net applications. But WebForms traditionally lived within companies, due to the relative complexity of deployment. Could it be that WebForms (Which I consider a Legacy Technology, despite all counter-claims by Microsoft) simply missed the OSS train?
I don't know, but I remember my (abandoned) WebForms Photo Album and how painful deployment was, with manually editing web.config and stuff. It really felt hard to make a WebForms application open source.
Granted, MVC has many of the same problems, but it's target audience is different. WebForms is a great technology for internal apps where it is more important to have it working quickly rather than achieving an exact look or getting an A Grade in Y!Slow, but I'm guessing the people who use it are not exactly into contributing to OSS. Of course, I am generalizing here, but in my mind there are two Venn Diagrams: One with "WebForms and Open Source" and one with "ASP.net MVC and Open Source". Both will have overlaps, but I strongly believe that the overlap is much bigger in the MVC one.
That was one part of the experiment, I wanted to find out if there are some high profile projects and hoped to collect some of them in this blog post. The second part of this experiment involves...
Blaming myself
I've started blogging again in November 2007, funnily enough with a posting about Open Source and .net. However, I never really tried to build a profile and advertise the blog, also I blog too infrequently. Analytics tells me I have about 100 Visitors a day, which is tiny compared to the high profile blogs. If I am lucky enough to be retweeted or even linked, I might reach 200 Visitors. My all time high is 234 visitors.
However, 2009 saw a more of less steady increase. This is partially due to StackOverflow, which helped me gain a little profile by leading the Scoreboard for quite some time, eventually being the first user to reach 10.000 reputation. Also, my Twitter activity helped me expand my reach a bit. But it is still rather insignificant, because I am not producing enough interesting content yet.
So the part of this experiment was a) to see how many people I can reach and b) see if I can increase my reach.
The Giveaway article is the most read article in the blog, thanks to several ReTweets by people with a much higher profile than me, so there is certainly no reason to believe the article was "buried".
But still, 484 reads isn't much. I've tried reaching out to some websites and podcasts for a mention, but that was badly planned. Many podcasts are produced weeks in advance, so I should have thought about that before. And many Websites want real money for their ad space, which is perfectly understandable (but I still had to try asking them, right?)
Goals for 2010 and future Giveaways
So what did I learn out of this experiment? I learned that my reach isn't there yet. But I also learned that I can increase my reach by posting more and making sure my posts get mentioned (For more exciting and totally unexpected statements, read this article).
This is logical, but it was worth for me experimenting just what works best, and I found that RTs and Mentions on Twitter are about 10 times more useful than Pingbacks/Links in other people's blog comments. This is not surprising - when I read a blog posting, I never read the "300 Pingbacks for this article" section, and I rarely read more than 10 or so comments. But on Twitter, I click on many links simply because they are "fed" to me.
Overall, I do not feel that I just wasted $28. I knew it was an experiment, I wanted to know my reach, and now I know it.
So I'll increase my blogging output during 2010, Sean will have fun with the MVC 2 Videos, WebForms will always be a legacy technology in my eyes, and I do plan another Giveaway for another series sometime this year, although with proper preparation this time.
Just a tiny little extension method. You may know the ?? operator for null checks:
var something = objectThatCouldBeNull ?? anotherObject;
Basically if objectThatCouldBeNull is null, then anotherObject is used instead. Unfortunately, this does not work with empty strings, so here is a quick extension method for that:
public static string IfEmpty(this string input, string otherString)
{
if (string.IsNullOrEmpty(input)) return otherString;
return input;
}
call it like
var myString = someString.IfEmpty("someOtherString");