Insights and discoveries
from deep in the weeds
Outsharked

Friday, July 8, 2011

CsQuery: a C# jQuery port / implementation

Update 6/21/2012 - This project is now in formal release and is feature complete. Please see more recent posts about CsQuery or the GitHub repo. It's also on NuGet:
PM> Install-Package CsQuery
Update 11/29/2011 - still more features, tests, and so on. The project is nearing release: see new post.

Update 8/2/2011 - more features added, read what's new.

Original Post Below - Contains Outdated Information!

Get CsQuery now on github.
I began this project some time ago as part of a CMS "plug-in" I had developed for an ASP.NET site. That is, the legacy site was in a custom (non-CMS framework) and I was sick of maintaining content, so I developed a quick and dirty CMS around CKEditor.

As a side note about that, in some fantasy world where I have any free time, I look forward to cleaning it up, adding some important security features, and creating a drop-in CMS for any web site - rather than having to use a CMS framework first and developing your site around it. That world is not here today unfortunately.

Anyway - as the content evolved and became more complex, I had to find a way to "activate" it so it could include form controls, postback links, and so on, that would all work within the context of an ASP.NET web site that knew nothing about those HTML content blocks. I had to search for certain tags, change attributes/values, and so on.

This was starting to sound a lot like client-side life with jQuery. I considered using the HTML Agility Pack to parse and deal with the HTML, which I am sure would have done the job just fine - but I had already written a simple HTML parser some time ago for another purpose, and I thought it would be fun to make something that acted like jQuery rather than learning a new, possibly complex piece of software. I am sure I didn't save any time by doing this, but having a good jQuery implementation in C# would open up a lot of possibilities for unifying server and client logic in a web site architecture. So I proceeded.

It's now stable and while certainly not complete, implements many of jQuery's most used features. What's great about it is that you already know how to use it. The syntax is identical with a few exceptions.

Creating a new DOM:

var csq = CsQuery.Create("<html> ... </html>")
Selecting:
csq.Select("selector");
Chaining:
// Select the 3rd div, and add markup before it
    csq.Select("div").Eq(2).Before("<span>Hello!</span>");
Change something:
// Select the 3rd div, and add markup before it
    csq.Select("#error_message").Html("You haven't selected anything.");
Replace an entire ASP.NET WebForms page with pure HTML and CSQuery:
// no other methods are needed in codebehind. There is no need to use server controls,
// you can access and manipulate everything with CsQuery.

protected override void Render(HtmlTextWriter writer)
{
       
    Dom = new CsQuery();
    
    // use included "server" plug-in to obtain HTML from the "render" method

    Dom.Server().CreateFromRender(base.Render,writer);

    // Updates dom with values from postback data
    if (Page.IsPostBack) {
        Dom.Server().RestorePost();
    }

    // Now, manipulate "Dom" to your heart's content just as you would on the client with jQuery
    ...
    //

    // When done, output it

    Dom.Server().Render();

}

Selectors:
tagname
    .class
    #id
     
    [attr]            attribute exists
    [attr="value"]    attribute equals
    [attr^="value"]   attribute starts with
    [attr*="value"]   attribute contains
    [attr~="value"]   attribute contains word
    [attr!="value"]   attribute not equal (nor does not exist)
    [attr$="value"]   attribute ends with
    
    :button           type="button" or <button>
    :checkbox         type="checkbox"
    :text             type="text"
    :file             type="file"
    :checked          checked
    :selected
    :contains         
    :disabled
    :enabled
    :first
    :last
    :eq(n)            nth matching result
    :gt(n)
    :lt(n)
    :even
    :odd
    :has(selector)    returns elements that have descendants matching the subselector

    selectorA, selectorB  cumulative selector
    selectorA selectorB  descendant selector
    selecotrA > selectorB child selector
Methods:
jQuery (create new jQuery object from existing object, HTML, or DOM element(s))

    Add
    AddClass
    Append
    AppendTo
    Attr
    Before
    Children
    Clone
    Closest
    Contents
    Css
    Data
    Each (uses delegates - can pass a function delegate or anonymous function)
    Eq
    Extend (see update 8/2 for details on how this works in C#)
    Find
    Filter
    First 
    Get
    Height
    Hide
    Html
    Index (partial)
    InsertAfter
    InsertBefore
    Is
    Last
    Next
    Parent
    ParseJSON
    Prev
    Prop
    Remove
    RemoveAttr
    RemoveClass
    RemoveData
    ReplaceWith
    Show
    Text
    Val
    Width
There's a growing test suite which is now part of the github project! It includes a bunch of new tests, plus a growing number migrated from the real jQuery test suite.