Well it's me, back again with more Mongo! (Hey, its gotten so bad, I've started listening
to Mongo Santamaria while I code. Now, that's some Salsa!). This time I'm building on a previous implementation
of Custom Authentication from this article, and adding a "Favorites Tracker". The result is basically an entire web
site built around MongoDb and the NoRM C# driver.
For starters, if you don't have MongoDb installed, here are the instructions:
1. First download the pre-built binaries, either 32-bit or 64-bit. I recommend 64-bit if you have an x64 machine and OS.
2. Create a folder at the root of your C:\ drive and name it “MongoDb”. Unzip the
distribution into that folder.
3. Create the default repository folders: Again, at the root of your C:\ drive, create
a new folder “data” and then under that folder, another folder called “db”.
4. Install the service. Execute mongod.exe –install from a command prompt in the
C:\MongoDb\bin folder. This will install mongod.exe as a Windows Service – but
you are not done!
5. Due to “developer error” when building the most recent distribution that I used,
the registry entry for the service image path is incorrect. Open up REGEDIT and
navigate to:
HKLM\System\Services\MongoDb\ImagePath
(on some systems it may be HKLM\System\CurrentControlSet\Services\MongoDb)
You will notice that the value there is:
"C:\mongodb\bin\mongod" –install
That’s wrong – what that will do is when the Service Control Manager attempts to
start the service, it will try to install itself again instead of starting. Change
this value to:
"C:\mongodb\bin\mongod" –service and close REGEDIT.
6. In Services, locate the MongoDb Windows Service, and start the service. Now download
the NoRM driver:
http://github.com/atheken/NoRM
You can review the first article about providerless custom forms authentication and
roles to see how that works. From there, I've kept the solution / project name
the same, and added some new infrastructure: A Master Page with a nice menu at
the top, and a "Link" class to model the Favorites that you'll be able
to add:
using System;
using System.Security.Principal;
using Norm;
using Norm.Attributes;
using System.Collections.Generic;
namespace CustomAuth
{
[Serializable]
public class Link
{
public Guid _id { get; set; }
public string Url { get; set; }
public string Title { get; set; }
public string User { get; set; }
public List<String> Tags { get; set; }
public String Note { get; set; }
public DateTime DateAdded { get; set; }
}
}
I've also added a custom Javascript "Favorite" to the site that you can
put in your Favorites/ Bookmarks:
<a href="javascript:function getSel(){return (!!document.getSelection) ? document.getSelection():(!!window.getSelection)?window.getSelection():document.selection.createRange().text;}
window.location.href='<%=Request.Url %>?url='+encodeURIComponent(window.location.href)+'&text='+
getSel()+'&title='+document.title;">Grab it!</a>
What the above script does is that whenever you are viewing a web page that you want
to add to your Favorites site, you can highlight some relevant text on the page,
and click your "Grab it!" favorite. This puts you right on your "AddUrl.aspx"
page, with the link, title, and your selected text all prefilled in on the form.
Add some optional custom TAGS, hit the Submit button, and your new "Favorite"
will be added to the MongoDb repository. Notice that I've made this script portable
by including some server-side script: window.location.href='<%=Request.Url %>'. So no matter where you've deployed the app, the user will get the correct url when
they add the favorite to their browser's bookmarks.
Let's have a look at the AddUrl page codebehind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Norm;
using Norm.Collections;
namespace CustomAuth
{
public partial class AddUrl : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if(! User.Identity.IsAuthenticated)
Response.Redirect("default.aspx?msg=Must be logged in.");
string url = Request["url"];
string text = Request["text"];
string title = Request["title"];
this.txtUserUrl.Text = url;
this.txtUserName.Text = this.User.Identity.Name;
this.txtTitle.Text = title;
this.txtText.Text = text;
}
protected void btnSave_Click(object sender, EventArgs e)
{
using (Mongo mongo = Mongo.Create(MongoHelper.ConnectionString()))
{
Norm.Collections.MongoCollection<Link> coll =
(MongoCollection<Link>)mongo.GetCollection<Link>();
Link link = new Link();
link._id = Guid.NewGuid();
link.Note = txtText.Text;
link.User = User.Identity.Name;
link.Url = this.txtUserUrl.Text;
link.DateAdded = DateTime.Now;
link.Title = this.txtTitle.Text;
var tags = txtTags.Text.Split(" ".ToCharArray()).ToList();
link.Tags = tags;
coll.Insert(link);
Response.Redirect("ViewLinks.aspx");
}
}
}
}
When the page loads, first we check to ensure the user is logged in. If not, you
get redirected to the login page. The form fields are filled in with the values
of the querystring items from the user clicking on their bookmark. Then, when
you press the "Save Link Info" button, we save the item via the NoRM
driver. Really, that's the heart of the whole concept.
Now all we need to be able to do is view and search our links. That gets a little
more sophisticated since we need to be able to page the datalist. First, here's
what the markup for the DataList looks like:
<asp:DataList id="DataList1" runat="server" CellPadding="3"
GridLines="Vertical" BorderWidth="1px" BorderColor="#999999"
RepeatDirection="Horizontal" RepeatColumns="1" Width="540px"
Font-Names="verdana" Font-Size="10pt"
BackColor="White" BorderStyle="Solid" ForeColor="Black"
ViewStateMode="Enabled" onitemcreated="DataList1_ItemCreated">
<SelectedItemStyle backcolor="#000099" Font-Bold="True" ForeColor="White" ></SelectedItemStyle>
<HeaderTemplate>
<asp:Literal id="litPagerLinksH" runat="Server" ></asp:Literal>
</HeaderTemplate>
<ItemStyle verticalalign="Top"></ItemStyle>
<ItemTemplate>
<p>
<asp:Label ID=lblAdded runat="server" ><%# Eval("DateAdded")
%></asp:Label>
<asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl='<%#
Eval("Url") %>' ><%# Eval("Title") %></asp:HyperLink>
<div ID="label1" runat="server"><%#Eval("Note") %></div>
<div id="divTags" runat="server"><b>TAGS:</b> <%# GetTags (Eval("Tags")) %></div>
</p>
</ItemTemplate>
<FooterStyle backcolor="#87C5DB"></FooterStyle>
<HeaderStyle backcolor="#87C5DB" Font-Bold="True" ForeColor="White"></HeaderStyle>
<AlternatingItemStyle BackColor="#CCCCCC" />
<FooterTemplate>
<asp:Literal id="litPagerLinks" runat="Server" ></asp:Literal>
</FooterTemplate>
</asp:DataList>
And the respective Codebehind class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using Norm;
using Norm.Collections;
namespace CustomAuth
{
public partial class ViewLinks : System.Web.UI.Page
{
private PagedDataSource pagedData = null;
protected void Page_Load(object sender, EventArgs e)
{
ViewState["allUsers"] = chkAllUsers.Checked;
GetData();
}
public void Prev_Click(object obj, EventArgs e)
{
int newPageIndex = ((int)(pagedData.CurrentPageIndex - 1));
pagedData.CurrentPageIndex = newPageIndex;
Response.Redirect(Request.CurrentExecutionFilePath + "?Page=" +
newPageIndex.ToString());
}
public void Next_Click(object obj, EventArgs e)
{
int newPageIndex = ((int)(pagedData.CurrentPageIndex + 1));
pagedData.CurrentPageIndex = newPageIndex;
Response.Redirect(Request.CurrentExecutionFilePath + "?Page=" +
newPageIndex.ToString());
}
protected void GetData( )
{
if (Session["links"] == null || chkAllUsers.Checked)
{
using (Mongo mongo = Mongo.Create(MongoHelper.ConnectionString()))
{
Norm.Collections.MongoCollection<Link> coll =
(MongoCollection<Link>) mongo.GetCollection<Link>();
List<Link> links = null;
if(chkAllUsers.Checked || ((bool)ViewState["allUsers"]) )
links = coll.AsQueryable().Where(x => x.User!=null).ToList();
else
links = coll.AsQueryable().Where(x => x.User == User.Identity.Name).ToList();
((HtmlGenericControl) Master.FindControl("liInfo")).InnerText = "Displaying " +
links.Count.ToString() + " links.";
Session["links"] = links;
}
}
var k = (List<Link>) Session["links"];
pagedData = new PagedDataSource();
pagedData.AllowPaging = true;
pagedData.PageSize = 4;
pagedData.DataSource = k;
if (Request.QueryString["Page"] == null)
pagedData.CurrentPageIndex = 0;
else
{
int pg = int.Parse(Request.QueryString["Page"]);
if (pg < 0) pg = 0;
pagedData.CurrentPageIndex = pg-1;
}
DataList1.DataSource = pagedData;
DataList1.DataBind();
}
protected string GetTags(object item)
{
List<string> tags = (List<string>) item;
string ret = "";
foreach (string s in tags)
{
ret += "<a href=ViewByTags.aspx?tag=" + s + ">" + s + "</a>|";
}
return ret;
}
// this is for "search". notice there's a checkbox to view "all users'"
links.
protected void Button1_Click(object sender, EventArgs e)
{
string s = txtSearch.Text;
using (Mongo mongo = Mongo.Create(MongoHelper.ConnectionString()))
{
Norm.Collections.MongoCollection<Link> coll =
(MongoCollection<Link>)mongo.GetCollection<Link>();
List<Link> links = null;
if(chkAllUsers.Checked)
links = coll.AsQueryable().Where( x => /* x.User == User.Identity.Name && */ (x.Note.ToLower().Contains(s.ToLower()) || x.Title.ToLower().Contains(s.ToLower(
)) )).ToList();
else
links = coll.AsQueryable().Where(x => x.User == User.Identity.Name && (x.Note.ToLower().Contains(s.ToLower())
|| x.Title.ToLower().Contains(s.ToLower()))).ToList();
this.DataList1.DataSource = links;
((HtmlGenericControl)Master.FindControl("liInfo")).InnerText = "Displaying " +
links.Count.ToString() + " links.";
DataList1.DataBind();
}
}
protected void DataList1_ItemCreated(object sender, DataListItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Footer )
{
Literal lit;
lit = (Literal) e.Item.FindControl("litPagerLinks");
lit.Text = Pager.CreatePagerLinks(pagedData, Request.CurrentExecutionFilePath + "?a=1");
}
if (e.Item.ItemType == ListItemType.Header)
{
Literal lit;
lit = (Literal)e.Item.FindControl("litPagerLinksH");
lit.Text = Pager.CreatePagerLinks(pagedData, Request.CurrentExecutionFilePath + "?a=1");
}
}
}
}
Basically, the above code comes from a previous article where I created an "SEO
Friendly" paging Datalist. The Pager class is in the downloadable solution
below. The display looks like this:

There's more to it, including a ViewLinksByTag page, but all the above pretty much
covers the entire concept. Feel free to download the Visual Studio 2010 solution, install MongoDb and grab the NoRM driver, and have some fun with the code. Oh hey,
they're playing "Manteca"!
NOTE: 8/5/2010 - If you're just starting out with MongoDb and need a hosted solution
(e.g., you do not have your own public webserver) you might want to look into
MongoHQ: https://mongohq.com/home . They have hosted MongoDb instances starting at "FREE" (16MB). Very interesting!