Justin Toth's Blog

Justin is a web developer living in Maryland

Running nunit test cases from an ASP.NET MVC web app

clock August 25, 2011 13:01 by author Justin Toth

 

I had a goal of being able to run nunit test cases from within my ASP.NET MVC web app, like what some CI tools like teamCity do, except within my own app and in a way that I can customize the output.

 

I first tried starting the nunit-console process using Process.Start and redirecting its output to my web app, however this is not a good solution because the redirected output from the Process class is buffered and you have no control over when it flushes the buffer. I found certain programs like msbuild work great and it flushes constantly, however with nunit-console it holds onto the output until all test cases are complete, which means you can't see the progress of the test cases as they run.

 

The solution is to use the RemoteTestRunner nunit class and create an event listener class that implements the NUnit.Core.EventListener interface:

 

    public class NUnitEventListener : NUnit.Core.EventListener

        {

            public event EventHandler CompletedRun;

            public StringBuilder Output;

            private int TotalTestsPassed = 0;

            private int TotalTestsErrored = 0;

 

            public void RunStarted(string name, int testCount)

            {

                Output.AppendLine(TimeStamp + "Running " + testCount + " tests in " + name + "<br/><br/>");

                TotalTestsPassed = 0;

                TotalTestsErrored = 0;

            }

 

            public void RunFinished(System.Exception exception)

            {

                Output.AppendLine(TimeStamp + "Run errored: " + exception.ToString() + "<br/>");

                //notify event consumers.

                if (CompletedRun != null)

                    CompletedRun(exception, new EventArgs());

            }

 

            public void RunFinished(TestResult result)

            {

                Output.AppendLine(TimeStamp + "<label class='normal " + (TotalTestsErrored == 0 ? "green" : "red")

                    + "'>" + TotalTestsPassed + " tests passed, " + TotalTestsErrored + " tests failed</label><br/>");

                Output.AppendLine(TimeStamp + "Run completed in " + result.Time + " seconds<br/>");

                //notify event consumers.

                if (CompletedRun != null)

                    CompletedRun(result, new EventArgs());

            }

 

            public void TestStarted(TestName testName)

            {

                Output.AppendLine(TimeStamp + testName.FullName + "<br/>");

            }

 

            public void TestOutput(TestOutput testOutput)

            {

                if(testOutput.Text.IndexOf("NHibernate:") == -1)

                    Output.AppendLine(TimeStamp + testOutput.Text + "<br/>");

            }

 

            public void TestFinished(TestResult result)

            {

                if (result.IsSuccess)

                {

                    Output.AppendLine(TimeStamp + "<label class='green normal'>Test Passed!</label><br/><br/>");

                    TotalTestsPassed++;

                }

                else

                {

                    Output.AppendLine(TimeStamp + "<label class='red normal'>Test Failed!<br/>" + result.Message.Replace(Environment.NewLine, "<br/>") + "</label><br/>");

                    TotalTestsErrored++;

                }

            }

 

            public void UnhandledException(System.Exception exception)

            {

                Output.AppendLine(TimeStamp + "Unhandled Exception: " + exception.ToString() + "<br/>");

            }

 

            public void SuiteStarted(TestName testName)

            {

            }

 

            public void SuiteFinished(TestResult result)

            {

            }

 

            private string TimeStamp

            {

                get

                {

                    return "[" + DateTime.Now.ToString() + "] ";

                }

            }

        }

 

After that, create a TestRunner class that calls RemoteTestRunner and uses your sexy new event listener class:

 

    public static class TestRunner

        {

            public static bool InProgress = false;

            public static StringBuilder Output = new StringBuilder();

            private static RemoteTestRunner Runner;

 

            public static void Start(string fileName)

            {

                InProgress = true;

                Output = new StringBuilder();

                StartTests(fileName);

            }

 

            private static void StartTests(string fileName)

            {

                //start nunit.

                var testPackage = new TestPackage(fileName);

                Runner = new RemoteTestRunner();

                Runner.Load(testPackage);

                var nunitEventListener = new NUnitEventListener();

                nunitEventListener.CompletedRun += new EventHandler(nunitEventListener_CompletedRun);

                nunitEventListener.Output = Output;

                Runner.BeginRun(nunitEventListener);

            }

 

            static void nunitEventListener_CompletedRun(object sender, EventArgs e)

            {

                if (Runner != null)

                {

                    Runner.CancelRun();

                    Runner = null;

                }

                InProgress = false;

            }

        }

 

Now call the TestRunner class in your ASP.NET MVC Controller:

 

    public class TestController : ApplicationController

        {

            //GET: /Test/Index/

            public ActionResult Index()

            {

                TestRunner.Start(@"C:\PathToTestProject\bin\Release\SystemTest.dll");

                return View();

            }

 

            //POST: /Test/GetOutput/

            [AcceptVerbs(HttpVerbs.Post)]

            public ActionResult GetOutput()

            {

                var result = new

                {

                    InProgress = TestRunner.InProgress,

                    Output = TestRunner.Output.ToString()

                };

                return Json(result);

            }

        }

 

Lastly, create a simple view to show the output as the test cases run. My example uses dojo but it could easily be modified to use jquery or vanilla javascript:

 


    <script type="test/javascript">
    var nunit = {
 
        init: function () {
 
            nunit.get();
 
        },
 
        get: function () {
 
            //ajax call.
            ajax.post("test/getoutput/", {}, nunit.display);
 
        },
 
        display: function (result) {
 
            console.debug(result);
            dojo.byId("output").innerHTML = result.Output.length > 0 ? result.Output : dojo.byId("output").innerHTML;
            if (result.InProgress)
                window.setTimeout(nunit.get, 10000);
 
        }
 
    };
 
    dojo.addOnLoad(nunit.init);
    </script>
 
    <div id="output">
        The tests are running, please wait....
    </div>

 

That's it... Hope this helps some others, as all of the examples online of RemoteTestRunner (including on stackoverflow) pass in a NullListener, which means you can't capture the output of the test run.

 



Housters easy homes for sale search

clock May 16, 2011 00:18 by author Justin Toth

I wanted to blog about a new web application I'm working on called Housters. I had been searching real estate using sites like zillow.com and realtor.com and had felt like neither did that great of a job. Realtor has a good amount of data, however they don't put any thought into how it's displayed so the user experience is poor. Zillow is much better about how they display the data they get, however they don't seem to have as much data as Realtor. 

I created Housters to feel like a google-esque search engine except only for homes for sale in the US. I ended up using jQuery, ASP.NET MVC 3, Razor, and C# for this task. For the backend, I originally was using SQL Server 2008, yet quickly realized that it wouldn't be up to the task, as I only have one server and there are 3.5 million homes for sale in the US right now. I switched to MongoDB, a "No SQL" implementation that is used by Craigslist, and WOW! It handles storing millions of homes for sale, not to mention the hundreds of millions of child collections I have for storing things like photos and sales history.. all on one server no problem!!

A few things that make Housters unique:

 

  • Ability to search and filter on a map.
  • Graphs of sales history, tax history, and price change history.
  • Ability to leave feedback on a property using Facebook comments.

 

Feel free to check out Housters and let me know what you think.



Manually programming automatic updates into your .NET WPF app

clock February 6, 2011 01:25 by author Justin Toth

I have a WPF app that I created a few weeks ago. For starters, I created an installer using Click Once Deployment. COD's best feature is that it handles updates of your app automatically. You can publish a new version of your app to a web server, and then when users open the app, it'll see that there's a new version and download/install it. However, COD seems to be a half-baked solution and has a number of shortcomings. One example is that there is no good way to add your app to startup on windows boot.

The natural option is to switch over to using a .NET setup project, which generates a .MSI installer for your app. The setup project is a solid solution and allows all of the basic features such as adding your app to startup, start menu, desktop, etc... However, one glaring thing missing is the ability to handle automatic updates, so in this regard it falls short of COD. There are valid reasons for why it doesn't have this ability, which I won't go into in this post, but we still need a way to handle automatic updates in a good way.

The recommended approach is to upload the latest setup (.MSI) file to a web server and then on application startup, you can follow these steps:

 

  1. Check if a new version of the application is available at the web server url.
  2. If a newer version is available, download it.
  3. Generate a .cmd file that will install the new version over the old one.
  4. Run the .cmd file.
  5. Close the existing application.
Here are more detailed instructions and some code so that others don't have to reinvent the wheel.

First, on a web server somewhere, copy the latest .MSI generated by the setup project to that location. Also create an xml file called Version.xml at that location:


<?xml version="1.0" encoding="UTF-8" ?>
<root>
  <versions>
    <version>
      <name>Housters Crawler 1.06</name>
      <number>6</number>
      <url>http://tothsolutions.com/apps/housterscrawler/HoustersCrawler.msi</url>
      <date>2/5/2011 9:00 PM</date>
    </version>
  </versions>
</root>

Next, create a new class in your WPF project called VersionHelper.cs. Also make sure to add a "Version" app key in your app.config appSettings section, which you will increment with each new version and should match the version/number in the xml file on the server.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.IO;
using System.Xml;
using System.Xml.Linq;
using System.Net;
using System.Diagnostics;
using System.Windows;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Management;
using System.Threading;

namespace Housters.Tray.Installer
{
    public class VersionHelper
    {
        private string MSIFilePath = Path.Combine(Environment.CurrentDirectory, "HoustersCrawler.msi");
        private string CmdFilePath = Path.Combine(Environment.CurrentDirectory, "Install.cmd");
        private string MsiUrl = String.Empty;

        public bool CheckForNewVersion()
        {
            MsiUrl = GetNewVersionUrl();
            return MsiUrl.Length > 0;
        }

        public void DownloadNewVersion()
        {
            DownloadNewVersion(MsiUrl);
            CreateCmdFile();
            RunCmdFile();
            ExitApplication();
        }

        private string GetNewVersionUrl()
        {
            var currentVersion = Convert.ToInt32(ConfigurationManager.AppSettings["Version"]);
            //get xml from url.
            var url = ConfigurationManager.AppSettings["VersionUrl"].ToString();
            var builder = new StringBuilder();
            using (var stringWriter = new StringWriter(builder))
            {
                using (var xmlReader = new XmlTextReader(url))
                {
                    var doc = XDocument.Load(xmlReader);
                    //get versions.
                    var versions = from v in doc.Descendants("version")
                                   select new
                                   {
                                       Name = v.Element("name").Value,
                                       Number = Convert.ToInt32(v.Element("number").Value),
                                       URL = v.Element("url").Value,
                                       Date = Convert.ToDateTime(v.Element("date").Value)
                                   };
                    var version = versions.ToList()[0];
                    //check if latest version newer than current version.
                    if (version.Number > currentVersion)
                    {
                        return version.URL;
                    }
                }
            }
            return String.Empty;
        }

        private void DownloadNewVersion(string url)
        {
            //delete existing msi.
            if (File.Exists(MSIFilePath))
            {
                File.Delete(MSIFilePath);
            }
            //download new msi.
            using (var client = new WebClient())
            {
                client.DownloadFile(url, MSIFilePath);
            }
        }

        private void CreateCmdFile()
        {
            //check if file exists.
            if (File.Exists(CmdFilePath))
                File.Delete(CmdFilePath);
            //create new file.
            var fi = new FileInfo(CmdFilePath);
            var fileStream = fi.Create();
            fileStream.Close();
            //write commands to file.
            using (TextWriter writer = new StreamWriter(CmdFilePath))
            {
                writer.WriteLine(@"msiexec /i HoustersCrawler.msi /quiet");
            }
        }

        private void RunCmdFile()
        {//run command file to reinstall app.
            var p = new Process();
            p.StartInfo = new ProcessStartInfo("cmd.exe", "/c Install.cmd");
            p.StartInfo.CreateNoWindow = true;
            p.Start();
            //p.WaitForExit();
        }

        private void ExitApplication()
        {//exit the app.
            Application.Current.Shutdown();
        }
    }
}

Lastly, call the code in your WPF app on start. I commented out the lines where I also show in the system tray that a new version is being downloaded so the user knows.

//check for new version.
                var versionHelper = new VersionHelper();
                if (versionHelper.CheckForNewVersion())
                {
                    //NotifyIcon.BalloonTipText = "A new version is being downloaded, the crawler will restart once download is complete...";
                    //NotifyIcon.ShowBalloonTip(5000);
                    versionHelper.DownloadNewVersion();
                }

We're just about done. There's one catch with what we have done above. If you try to install the downloaded .MSI file over the existing app, it will launch the repair/remove wizard instead of overwriting it. Left click on the setup project in solution explorer and then go to the properties pane. Make sure that RemovePreviousVersion is true and also increment the Version. When it asks if you want to change the Product Code, click Yes to generate a new Guid. Lastly, change the Version in your WPF project's Properties/AssemblyInfo.cs.

Each time you release a new version on the server, you should do the following things:

  1. Increment the AssemblyInfo version in your WPF app.
  2. Increment the setup project version via the properties window.
  3. Increment the "Version" app key in your WPF app.
  4. Build the setup project to generate the new .MSI.
  5. Copy the new .MSI to the web server.
  6. Update the version/number in the xml file on the web server.
Now whenever the WPF app is launched, it will check for a new version. If it exists then it will download the new .MSI, generate a .cmd file, run the .cmd file to install the new .MSI over the old one, and exit the app. When the new .MSI is installed then it will launch the app again, assuming that you have set up a custom rule in your setup project for launching the app after install.

You can take it from here, but one tip is that you may not want to only check for a new version on app start. You could easily have some sort of timer that checks every so often for a new version and runs this process async.

 



ASP.NET MVC + Dojo's Dijit.Form for Form Validation and Submission

clock April 25, 2010 20:03 by author Justin Toth

One of the major hurdles when you switch over from ASP.NET web forms to ASP.NET MVC is deciding how to handle your submission forms. You no longer have all of the input controls and the nice validation controls, so you may be a little lost as to how to proceed. Doing server-side validation isn't enough anymore in the ajax world we live in. Writing custom javascript code to handle all of the validation would be a huge undertaking. Luckily, Dojo comes to the rescue with its Dijit.Form widgets. Below is what a login form would look like using dojo:


 
<div dojoType="dijit.form.Form" jsId="loginForm" 
            encType="multipart/form-data" action="" method="">
            <script type="dojo/method" event="onSubmit">
                if (this.validate()) {
                    SubmitLoginForm(loginForm.getValues());
                }
                return false;
            </script>
 
            <table cellspacing="0">
                <tr>
                    <td class="label">Username:</td>
                    <td class="input mediumfield">
                        <input id="tbUsername" name="tbUsername" type="text" size="20"
                            dojoType="dijit.form.ValidationTextBox"
                            required="true"
                            promptMessage="Enter Username."
                            invalidMessage="Username is required." />
                    </td>
                </tr>
                <tr>
                    <td class="label">Password:</td>
                    <td class="input mediumfield">
                        <input id="tbPassword" name="tbPassword" type="password" size="20"
                            dojoType="dijit.form.ValidationTextBox"
                            required="true"
                            promptMessage="Enter Password."
                            invalidMessage="Password is required." />
                    </td>
                </tr>
            </table>
 
            <table id="buttons" class="buttons" cellspacing="0">
                <tr>
                    <td class="center">
                        <button dojoType="dijit.form.Button" type="submit" value="Submit">Login</button>
                    </td>
                </tr>
            </table>
 
        </div>
 

 

As you can see, you create a dijit.form.Form widget, which will act as our <form> element normally would. When the login button is clicked, it'll validate all of the controls within the form and then call a javascript method called SubmitLoginForm. You'll notice that my dojo method for onSubmit returns false. Normally you would have it return false if the form was invalid and true otherwise, but this would actually submit the form and do a server-side postback. Seeming that we're using purely ajax to make our calls in this example, we're returning false regardless of if the form is valid or not so that it won't postback to the server.

Next we need to write our javascript to submit the login form using ajax:


    dojo.require("dojo.parser");
    dojo.require("dijit.form.Form");
    dojo.require("dijit.form.ValidationTextBox");
    dojo.require("dijit.form.Button");
    dojo.addOnLoad(SetFocus);
    function SetFocus() {
        //find controls.
        var tbUsername = dojo.byId("tbUsername");
        //set focus.
        tbUsername.focus();
    }
    function SubmitLoginForm(fields) {
        //ajax call.
        var request = { "userName": fields.tbUsername, "password": fields.tbPassword };
        Post("login/validate/", request, FormResult);
    }
    function FormResult(result) {
        if (result.indexOf("Error") > -1) {
            //TODO: show error.
        }
        else {
            //redirect.
            window.location = result;
        }
    }
 

 

The cool part is that when you call loginForm.getValues(), it returns an object containing the values of all of the form fields. Within the SubmitLoginForm method we're able to easily create an ajax request and post it back to the server. Above I omitted the function declaration for Post, so below I'm showing what mine looks like. It can be a lot simpler if you only want to make one ajax call, but my example below will try up to 5 times to make the ajax call, in case there are some sort of flukey issues that cause your ajax requests to fail every now and then:

 


 
var maxAjaxCalls = 5;
var currentAjaxCalls = new Array();
 
function Post(path, request, callbackFunction) {
    //increment current ajax calls.
    currentAjaxCalls[currentAjaxCalls.length] = path;
    //make sure hasn't gone over max ajax calls.
    var numAjaxCall = NumAjaxCalls(path);
    if (numAjaxCall <= maxAjaxCalls) {
        if (numAjaxCall > 1) {
            console.debug("Invoking " + path + " ajax call for try #" + numAjaxCall);
        }
        dojo.xhrPost({
            url: baseUrl + path,
            handleAs: 'json',
            timeout: 60000,
            content: request,
            contentType: "application/x-www-form-urlencoded",
            load: function(result) { PostSuccess(result, path, callbackFunction); },
            error: function(error, args) { AjaxError(error, args, path, request, callbackFunction); }
        });
    }
    else {
        console.debug("Tried to make " + path + " ajax call " + maxAjaxCalls + " times but failed!");
    }
}
 
function NumAjaxCalls(path) {
    var numAjaxCalls = 0;
    for (var k = 0; k < currentAjaxCalls.length; k++) {
        if (currentAjaxCalls[k] == path) {
            numAjaxCalls++;
        }
    }
    return numAjaxCalls;
}
 
function PostSuccess(result, path, callbackFunction) {
    //remove path from array.
    for (var k = 0; k < currentAjaxCalls.length; k++) {
        if (currentAjaxCalls[k] == path) {
            currentAjaxCalls.splice(k, 1);
            k--;
        }
    }
    //invoke callback function.
    callbackFunction(result);
}
 
function AjaxError(error, args, path, request, callbackFunction) {
    //show error message.
    //console.debug("ajax error: " + error.message + " " + error.responseText);
    //console.debug(error);
    //try ajax call again in 1 second.
    window.setTimeout(function() { Post(path, request, callbackFunction); }, 1000);
}
 

 

The last step would be to create your LoginController and create a method called Validate:

 

//GET: /Login/Validate/

        public ActionResult Validate(string userName, string password, string returnUrl)

        {

            string result = String.Empty;

            try

            {

                if(IsAuthenticated)//TODO: check if they're authenticated.

                    result = "someurltoredirectto";

                }

                else

                {

                    result = "Error: Authentication failed, check your Username and Password.";

                }

            }

            catch (Exception ex)

            {

                result = "Error: " + ex.Message;

            }

            return Json(result);

        }

 

 

Pretty simple stuff. In this way you're able to save yourself from having to write a lot of code, letting dojo do the majority of the heavy lifting. If you were to use this example, you'd obviously want to make sure you took care of server-side validation as well, in case someone disabled javascript.



Making your ASP.NET web forms app valid with XHTML 1.0 Strict

clock March 19, 2010 13:01 by author Justin Toth

Today I was working on making SportsAlert valid against the doctype XHTML 1.0 Strict. I used http://validator.w3.org to test my homepage and fixed all of the issues except for 2. There are 2 issues inherent in ASP.NET web forms. One is that it adds name="aspnetForm" to the <form> tag, which is valid in XHTML Transitional but not XHTML Strict. Secondly, it adds input tags for things such as Viewstate right after the opening <form> tag, which is also valid in XHTML Transitional but not in XHTML Strict. To fix these 2 issues, you need to include:

<xhtmlConformance mode="Strict" />

In your web.config file within the system.web node. This will remove the name="aspnetForm" attribute from the <form> tag and will place the input tags within a div tag so that they have a valid container.

That should be enough, however for some reason the w3.org validator will still complain about these two things and when you look at the source code that it's pulling down, it's different from the real source code. The name="aspnetForm" will still be on the form tag and the input tags won't be wrapped in the div tag, when you can clearly see by doing view source on the page that those changes have been made. The solution is to add a .browser file to the App_Browsers directory in your ASP.NET application:


<browsers>
  <browser id="w3cValidator" parentID="default">
    <identification>
      <userAgent match="^W3C_Validator" />
    </identification>
 
    <capture>
      <userAgent match="^W3C_Validator/(?'version'(?'major'\d+)(?'minor'\.\d+)\w*).*" />
    </capture>
 
    <capabilities>
      <capability name="browser" value="w3cValidator" />
      <capability name="majorversion" value="${major}" />
      <capability name="minorversion" value="${minor}" />
      <capability name="version" value="${version}" />
      <capability name="w3cdomversion" value="1.0" />
      <capability name="xml" value="true" />
      <capability name="tagWriter" value="System.Web.UI.HtmlTextWriter" />
    </capabilities>
  </browser>
</browsers>

 

I didn't take the time to figure out why this works but it does and my page is now XHTML 1.0 Strict valid, so I'm happy... :)



SportsAlert Facebook Application

clock March 12, 2010 21:40 by author Justin Toth

A while ago I posted on developing facebook applications with ASP.NET MVC and the .NET Facebook API. At the time I was developing on a Windows XP box, which runs IIS 5. ASP.NET MVC routing is intended for IIS 6 and above, so it was a painful process. Now that I'm on Windows 7 with IIS 6.5 I decided to come back to that project and give it a try. Amazingly everything just started working without any code changes.

Without further ado, here is the SportsAlert Facebook application:

http://apps.facebook.com/sportsalert/

This app is very simple, allowing you to sign up for SportsAlert and subscribe to teams from within the Facebook app. For the teams you pick, you will get text messages at the end of the games. If you want to subscribe to individual games and/or change the frequency of your alerts (on score change, on quarter change, etc...) then you can login automatically to the SportsAlert web site from the facebook app.



301 Permanent Redirects in ASP.NET 3.5 & 4.0

clock February 15, 2010 23:23 by author Justin Toth

I've been going through an SEO phase recently and part of that was renaming the aspx files of my ASP.NET 3.5 site to reflect the keywords and phrases i was trying to target. For example, a page called Services.aspx that might target web development services would be renamed Web-Development-Services.aspx. I noticed after renaming the files that I was getting errors because the search engines still had my old page names cached and these pages no longer existed.

To stop the bleeding, I had the customErrors node in my web.config redirect to my home page (this is not a solution!) Next I recreated the old pages (such as Services.aspx) and left them empty. Then in the code-behind of each I added some code:

 

Response.Status = "301 Moved Permanently";

Response.AddHeader("Location", "http://mydomain.com/Web-Development-Services.aspx");

 

What does this do? When someone requests the old outdated url, it sends a 301 permanent redirect to the new url, which lets the search engine know to use the new page.

In .NET 4.0, there will be an even simpler way to do this:

 

Response.RedirectPermanent("~/Web-Development-Services.aspx");

 

Using one of these 301 permanent redirects methods you can easily keep users and search engines up to date on the structure of your site, even if you've renamed all of the pages for SEO purposes! 



Clean up your JavaScript Code with Dojo

clock December 19, 2009 19:39 by author Justin Toth

You may have heard of Dojo, the javascript toolkit found at http://www.dojotoolkit.org/. The difference between it and its competitors such as jQuery and Ext JS is that it's much more than a toolkit, it's a full-fledged javascript framework. It provides things such as object-oriented programming (who would've thought you'd ever be able to have class inheritance in javascript?!), template-based widgets (you can dynamically create widgets in javascript that have html templates rather than having to dynamically create the tr's and td's with DOM manipulation), and basic utility functionality.

I've been using the more complex features of dojo, such as OO and template-based widgets, for some years but I never bothered with the basic utility functionality until today. It's pretty cool what dojo lets you do and it makes me sad that I was writing manual javascript code for years to do the same things. Here are a few of the cooler things that dojo lets you do:

1. Finding and looping through DOM elements

dojo.forEach(
                dojo.query("#myDiv img"),
                function(element) {
                    dojo.attr(element, { src: "_images/test.png" });
                }
            );

You can see the use of 3 dojo functions here: dojo.forEach, dojo.query, and dojo.attr. This will loop through all img elements that are children of the element with id "myDiv" and set the src of each image. Dojo.query is very powerful and can let you easily select the element(s) that you want from the page or from a specific parent node.

2. Creating DOM elements

var imgArrow = dojo.create("img", { src: "_images/arrow.png", title: "my arrow!" }, divContainer);

This code creates an image, sets the src and title of it, and appends it to a div, all in 1 line of code!

3. Clearing DOM elements

 dojo.empty(divContainer);

Dojo.empty clears all child elements of the element inputted, no more grabbing all child elements, looping through them, and doing childNode.parentNode.removeChild(childNode).

I've barely scratched the surface of what you can do with dojo but you can go a long way with just these 3 concepts...



Developing Facebook Applications with ASP.NET MVC

clock July 26, 2009 14:00 by author Justin Toth

As Facebook continues to grow at a rapid pace, more and more businesses have been signing on developers to build custom Facebook applications using Facebook's API's so that they can gain exposure to the Facebook masses and the extraordinary amount of personal data that those users have stored within Facebook.

I recently was asked to build a Facebook application for a project. Naturally, I wanted to find a nice solution that would work with my technology set, mainly ASP.NET, not Facebook's standard language: PHP. I even considered building the app using Silverlight, but decided against it since Silverlight is still so new and hasn't been installed yet by so many users. Since we're in the .NET 3.5 era, I decided to go with ASP.NET MVC rather than the standard web forms version of ASP.NET.

The first thing to do is to find a .NET Facebook framework. Like most people, when I first started searching around, I found two choices: Facebook.NET and the Facebook Developer Toolkit.

The Facebook Developer Toolkit is probably the more popular of the two but it has some shortcomings. From my reading, I got the overwhelming impression that the code base was poorly written by the creator, Clarity Consulting. Furthermore, there is no built-in support for ASP.NET MVC so you have to figure out how to make them mesh yourself. Lastly, many of the methods aren't up to date to match the Facebook API methods so you're on your own to update them manually. The Facebook API's change frequently so it's very hard for a .NET Facebook Famework to keep up with those changes, and the FDT doesn't seem to do too good a job of that.

Facebook.NET was written by a well-respected MSFT employee, Nikhil Kothari, and thus, the code base is nice and clean. It provides for much more flexibility than the FDT, hence it'd be easier to make it work with ASP.NET MVC. However, Nikhil seems to have ditched the project, so it hasn't been updated in a couple of years. That means that its methods no longer match the Facebook API methods.

I wasn't satisfied with either of these solutions so I kept looking and was extremely happy when I found a new project called the .NET Facebook API Client, which is still in Alpha. This project is specifically designed for ASP.NET MVC and provides a Visual Studio 2008 template that sets everything up for you, such as Facebook authentication and using Facebook Connect. The code is nice and clean and is provided to you so that if you run into bugs (since it's an Alpha release), you can modify the code yourself to get it working. Here's the best part - they built a tool that will automatically update the methods when the Facebook API methods change, so it will always match the Facebook API, something that is sorely missing from the big two that I mentioned above.

With the .NET Facebook API Client, I was able to build my first ASP.NET MVC Facebook application without running into too many issues. You can find the link to the app below, which has you enter in your email address, mobile #, and carrier, and then lets you pick your favorite sports teams. It will send you a text message at the end of each game for the teams you picked with the final scores.

http://apps.facebook.com/sportsalert/



Silverlight 3 Released!

clock July 10, 2009 14:18 by author Justin Toth

Just as promised, Silverlight 3 has been released, actually 1 day earlier than the expected July 10th date. You can get the latest tools here.

For those of us who have been already playing with Silverlight 3 beta, there are some breaking changes. The most obvious one is that the System.Web.Silverlight assembly has been removed, meaning that the ASP.NET Silverlight control that we were using won't work anymore. Alternately, you need to set up your Silverlight app using an object and an iframe tag. The object tag shouldn't be new to anyone and was always an alternative to using the Silverlight control. The iframe will be used to handle browser history, which comes along with the new SL3 Navigation features.

To view a full list of the changes, go here:

http://docs.google.com/View?id=dnkk749_0czvc86gx



About the author

Justin

Justin is a senior developer who has been working with .NET since 2003. His main focus is building highly-interactive web applications using ASP.NET MVC and Dojo or jQuery. Visit his company's site at http://tothsolutions.com.

Page List

Sign in