Wednesday, April 11, 2007

ASP.net DB-backed Micro CMS in 50 Lines and 5 Minutes

I was working on a small web app where one area was intended to allow users to publish their own content. This area was not going to be ready for a while. In the meantime, I thought, there should be some way for users to put pages up on the site if they are inspired to do so. Images, too.

There are great lightweight CMS apps just for this, such as wikis. But with asp.net and SubSonic, I put this micro-CMS up in about 5 minutes. If you need a little area with WYSIWYG editable web content and binary uploads (images, PDFs, etc.) you might find it useful. n.b.: for my site, the created pages are meant to be be publicly readable and writable. If you want to restrict access, you will need to make some adjustments for that purpose. So here's how to brew it up:

Step 1: Create a table in SQL Server 2005

CREATE TABLE DataObject (
[ID] [uniqueidentifier] NOT NULL CONSTRAINT [DF_DataObject_id] DEFAULT (newid()),
[TextContent] [ntext] NULL,
[ImageContent] [image] NULL,
[MimeType] [nvarchar](50) NULL,
[CreatedOn] [datetime] NULL,
[CreatedBy] [nvarchar](50) NULL,
[ModifiedOn] [datetime] NULL,
[ModifiedBy] [nvarchar](50) NULL,
[IsDeleted] [bit] NULL CONSTRAINT [DF_DataObject_IsDeleted] DEFAULT ((0)),
CONSTRAINT [PK_DataObject] PRIMARY KEY CLUSTERED ( [ID] ASC ) )

You'll notice a number of convention-over-configuration moves that let SubSonic do some of my work: Created/Modified On and By as well as IsDeleted.

The main data fields are TextContent as ntext (i18n clob), ImageContent (blob), and MimeType. The PK and ID is an autogenerated GUID.

Step 2: Create a web page for editing and uploading content. Grab FreeTextBox for WYSIWYG HTML editing. Then toss in controls:

<FTB:FreeTextBox ID="FreeTextBox1" runat="Server" Width="600px" />
<asp:Button id="SavePageButton" runat="server" Text="Save"
OnClick="SavePageButton_Click"/>
<asp:FileUpload ID="FileUpload1" runat="server" />
<asp:Button id="Upload" runat="server" Text="Upload"
OnClick="Upload_Click" />
<asp:Label ID="LabelForNewURL" runat="server">(none)</asp:Label>

Add line breaks and formatting to taste.

Step 3: Add some code-behind to save the page.

protected void SavePageButton_Click(object sender, EventArgs e)
{
object id = Session[Globals.PAGE_EDIT_ID_CONSTANT]; // add some code in
// Page_Load to grab this out of the request,
// and load it into the FreeTextBox instance
DataObject o;
if (id == null)
o = new DataObject();
else
o = new DataObject(id); // this is SubSonic foo...
//read all about SubSonic, which rocks!
o.MimeType = "text/html";
o.TextContent = MyHtmlUtilities.CleanUpHTML(FreeTextBox1.Xhtml);
// Remove anything we don't want, like scripts
o.Save(User.Identity.Name);
LabelForNewURL.Text = "URL is http://www.mysite.com/Object.ashx?id="
+ o.ID;
Session[Globals.PAGE_EDIT_ID_CONSTANT] = o.ID;
}

Add some more code-behind to save an uploaded file.

protected void Upload_Click(object sender, EventArgs e)
{
if (IsPostBack && FileUpload1.HasFile &&
FileUpload1.PostedFile.ContentLength < MAX_LENGTH)
{
String fileExtension = System.IO.Path.GetExtension
(FileUpload1.FileName).ToLower();
if (allowedFileTypes.ContainsKey(fileExtension))
{
DataObject o = new DataObject();
o.MimeType = allowedFileTypes[fileExtension];
o.ImageContent = FileUpload1.FileBytes;
o.Save(User.Identity.Name);
LabelForNewURL.Text
= "URL is http://www.mysite.com/Object.ashx?id=" + o.ID;
}
}
}

This code omits the definition of MAX_LENGTH, a limit to the length of uploaded files, and allowedFileTypes, a Dictionary<string, string> that maps allowed file extensions to MIME types, like so: allowedFileTypes[".png"] = "image/png";

Now you have file-upload, database storage, and WYSIWYG editing in around 20 lines of code and 5 markup tags!

Step 4: Create the Object.ashx handler that serves these pages and objects. Add a new asp.net request handler like so:

<%@ WebHandler Language="C#" Class="Object" %>

using System;
using System.Web;

public class Object : IHttpHandler {

public void ProcessRequest (HttpContext context) {
DataObject obj = new DataObject(context.Request["id"]);
if (string.IsNullOrEmpty(obj.MimeType))
context.Response.ContentType = "text/plain";
else
context.Response.ContentType = obj.MimeType;

if (!string.IsNullOrEmpty(obj.TextContent))
context.Response.Write(obj.TextContent);
else
context.Response.BinaryWrite(obj.ImageContent);
}
}

That's it! Of course if you wanted to build in more features here, it's easy to do all sorts of stuff, from adding an attribute for a document/file name, to sorting or searching the database table to generate product thumbnails, automagic menus or site maps, etc. Ok, including all the SQL, asp.net markup, and the C# (except braces) that's at least 51 lines.

Addendum: I had to monkey with line breaks to keep the code readable outside of RSS readers, so never mind the line counts, I think the code is fun and the point is made anyway.

1 comment:

Bot and me said...

I have no words for this great post such a awe-some information i got gathered. Thanks to Author.
Money Talks| Super Bowl Commercials 2012|