Aaronontheweb

Hacking .NET and Startups

How to Use Asynchronous Controllers in ASP.NET MVC2 & MVC3

January 6, 2011 12:01 by Aaronontheweb in ASP.NET // Tags: , , , , // Comments (6)

The primary reason I added asynchronous methods to Quick and Dirty Feed Parser 0.3 was because I wanted to use QD Feed Parser in conjunction with asynchronous controllers in ASP.NET MVC3.

MSDN has some excellent documentation which explains the ins and outs of asynchronous controllers in ASP.NET MVC, yet there aren’t many good examples of how to use it online. Given this, I thought I would make one.

Asynchronous Controllers in Theory

Here’s how asynchronous controllers work:

async controllers asp.net mvc

And in case my visual isn’t clear:

  1. A user sends a request to some resource on your server, i.e. GET: /controller/action
  2. Since the action method is part of an asynchronous controller, the processing of the request is handed off to a CLR thread pool worker while the IIS thread bails and serves other requests. This is the key advantage of asynchronous controllers: your limited, precious IIS threads get to off-load long-running, blocking tasks to inexpensive CLR worker threads, freeing up said IIS threads to serve more requests while work is being done in the background.
  3. The CLR worker thread diligently works away while the IIS worker thread gets recycled.
  4. The CLR worker thread finishes its task, and invokes the AsnycManager.Sync method and passes back some piece of processed data to be returned to the end-user.
  5. The AsyncManager hitches a ride on the first available IIS thread and passes along the processed data from the CLR worker thread into a special action method which returns some sort of consumable data back to the User, such as a View or a JsonResult.

Asynchronous controllers are all about utilizing additional, cheap CLR threads to free up expensive IIS threads as often as possible. In any situation where you have a long-running IO-bound task, they’re a good way to improve the performance of your ASP.NET MVC application.

Asynchronous Controllers in Code

To create an asynchronous controller, all you have to do is inherit from the AsyncController class:

public class FeedController : AsyncController
{}

For every asynchronous action method you want to add to your controller, you have to actually supply two different methods:

// GET: /Feed/

public void FeedAsync(string feeduri, 
			int itemCount){ ... }

public JsonResult FeedCompleted(IFeed feed,
			int itemCount){ ... }
			

Both of these methods on the asynchronous controller support the same action method (“Feed”) and any request that goes to “Feed/Feed” in our ASP.NET MVC application will be served asynchronously.

Asynchronous Action Naming Conventions

Both methods have to follow these naming conventions:

  1. The first method is the worker method that actually performs the long-running task on a worker thread; it should return void; and it must be named [Action Name]Async.
  2. The second method is the output method which is joined back to an IIS worker thread by the AsyncManager; it must return a valid ActionResult derivative (View, RouteResult, JsonResult, etc…;) and it must be named [Action Name]Completed.

Full Asynchronous Action Methods

Here’s full source code from the asynchronous controller I used to build Geeky Reads, minus a bit of work I put in to use object caching:

public class FeedController : AsyncController    {
protected IFeedFactory _feedfactory;

public FeedController(IFeedFactory factory){
	_feedfactory = factory;        
}

// GET: /Feed/       
public void FeedAsync(string feeduri, 
			int itemCount){            

AsyncManager.OutstandingOperations.Increment();

_feedfactory.BeginCreateFeed(new Uri(feeduri),
	async => AsyncManager.Sync(
		() => { 
		
		var feed = _feedfactory.EndCreateFeed(async);
		AsyncManager.Parameters["feed"] = feed;
		AsyncManager.Parameters["itemCount"] = itemCount;
		AsyncManager.OutstandingOperations.Decrement();
		
		}));
		
}

public JsonResult FeedCompleted(IFeed feed, int itemCount){

return Json(FeedSummarizer.SummarizeFeed(feed, itemCount), 
	JsonRequestBehavior.AllowGet);
}

}

Here’s what you should be looking at in this example:

AsyncManager.OutstandingOperations.Increment and .Decrement – the number of .Decrement operations must mach the number of .Increment operations; otherwise, the AsyncManager will time out operations that are running too long and the completion method will never return an ActionResult to the end user.

The accounting is pretty simple: you call .Increment at the beginning of your long-running operation, and .Decrement once your operation is finished and the results have been passed into the AsyncManager.Parameters collection, which brings me to my next point.

AsycManager Parameters and Argument Names for the Completion Method

Notice how the AsyncManager parameters “feed” and “itemCount” match the argument names for the FeedCompleted method – that’s not an accident. The AsyncManager binds its parameter collection to the FeedCompleted method arguments once the .Sync method completes, and this is what allows the asynchronous method results to be passed back to the consumer.

If you’d like to see the full source for this example, check it out on Github.

If you enjoyed this post, make sure you subscribe to my RSS feed!



How to Make Any Operation Asynchronous in .NET

I’m in the middle of writing some updates to Quick and Dirty Feed Parser for use in a new personal project of mine; namely, I need to make QD Feed Parser work asynchronously so I can use it in conjunction with asynchronous controllers in ASP.NET MVC3, and eventually Silverlight + Windows Phone 7.

Asynchronous network IO is a breeze in every version of .NET since 1.1 – WebRequest and many others have supported asynchronous methods for as long as I’ve been using them. However, asynchronous disk IO, which I need to support in QD Feed Parser, is not something that comes so easily in .NET 4.0 and below.

StreamReader is the most user-friendly tool for reading files off of local disk because you don’t have to manage any buffers or do any of the sort of accounting that a class like FileStream requires1. Here’s how nice and easy life is using StreamReader, using actual code from QD Feed Parser as an example:

protected static FeedTuple DownloadXmlFromUri(Uri feeduri)
{
    string xmlContent;
    using (var reader = new StreamReader(feeduri.LocalPath))
    {
        xmlContent = reader.ReadToEnd();
    }

    return new FeedTuple{FeedContent = xmlContent, FeedUri = feeduri};
}

Given that StreamReader doesn’t have any built-in methods for making this asynchronous, what’s the best way to do this, given that the  async and await keywords coming down the pipe in .NET 5.0 aren’t fully available yet? The answer is: we’re going to wrap our disc IO operations using delegates.

Out of the box delegates have both a BeginInvoke and EndInvoke methods, which behave like any other standard asynchronous method call and you, the developer get to decide what functions are invoked on the background thread.

Here are the steps:

1. Create a delegate and a static worker method

protected delegate FeedTuple BackGroundWorker(Uri feeduri);

protected static FeedTuple DownloadXmlFromUri(Uri feeduri)
{
    string xmlContent;
    using (var reader = new StreamReader(feeduri.LocalPath))
    {
        xmlContent = reader.ReadToEnd();
    }

    return new FeedTuple { FeedContent = xmlContent, FeedUri = feeduri };
}

The delegate and the static worker method need to have the same signatures.

2. Define your own Begin[Operation] and End[Operation] methods

For Quick and Dirty Feed Parser, here is what I created, minus some integrity checks in order to make the code more readable:

public override IAsyncResult BeginDownloadXml(Uri feeduri, AsyncCallback callback)
{
    return FeedWorkerDelegate.BeginInvoke(feeduri, callback, 
		new FeedTuple());

}

public override FeedTuple EndDownloadXml(IAsyncResult asyncResult)
{
    var result = FeedWorkerDelegate.EndInvoke(asyncResult);
    return result;
}

Your begin method should return the IAsyncResult from your delegate – this way your caller can pass the asynchronous result back to the End[Operation] method and return your result. You can also use the IAsyncResult’s WaitHandle to put a block a calling thread if you wanted.

When dealing with delegates you have to be careful with your accounting – for every BeginInvoke there should be exactly one EndInvoke.

3. Calling the asynchronous methods

Here’s a quick unit test I wrote for testing this method in Quick and Dirty Feed Parser.

[Test, TestCaseSource("TestCases")]
public void CanDownloadXmlStreamAsync(string rsslocation)
{
    var feeduri = new Uri(rsslocation);
    var result = Factory.BeginDownloadXml(feeduri, null);
    var resultantTuple = Factory.EndDownloadXml(result);

    Assert.IsNotNull(resultantTuple);
    Assert.That(resultantTuple.FeedContent != String.Empty);
    Assert.AreEqual(feeduri, resultantTuple.FeedUri);
}

You should note that I’m effectively blocking the calling thread until the asynchronous worker thread returns a result in this method, and that’s because it’s easier to write unit tests this way.

The best practice for taking advantage of asynchronous method calls is to utilize a callback function, which you can do like so:

public void DownloadXmlStreamAsync(string rsslocation)
{
    var feeduri = new Uri(rsslocation);
    var result = Factory.BeginDownloadXml(feeduri, async => 
	{
		var callbackTuple = Factory.EndDownloadXml(async);
		Dispatcher.BeginInvoke(() => { 
		//... Do some client-side stuff with the data...		
		});
	});
}

And that’s it!


1It should be noted that FileStream does support asynchronous operations out of the box, but they’re a pain in the ass to use due to the reasons I just described.

If you enjoyed this post, make sure you subscribe to my RSS feed!



Upgraded to BlogEngine.NET 2.0, Updated Extensions

December 28, 2010 07:11 by Aaronontheweb in BlogEngine.NET // Tags: , // Comments (2)

BlogEngine.NET 2.0 was released just before Thanksgiving this year, and it includes a bunch of major changes, the most noticeable of which is the drastically improved admin dashboard.

There are also a number of external-facing improvements too such as jQuery out of the box, SEO improvements, and so forth. I contributed one small improvement to BlogEngine.NET 2.0, and that was the alternative captcha engine I wrote – Recaptcha causes as many problems as it solves and was rife with bugs in BlogEngine.NET 1.6 1.

SimpleCaptcha

SimpleCaptcha was inspired by the captcha engine Jeff Atwood used on Coding Horror for so many years, where he asked his bots to politely type in the word “orange.” In that vein I decided to follow in his footsteps.

The default SimpleCaptcha out of the box looks like this:

image

Simply stated, the end user is expected to be able to add 5+5 = 10 in order to post their comment. If they cannot, then the comment isn’t posted. Feel free to give it a try on this post if you’d like.

Of course you can modify the captcha prompt and answer in the extensions panel like so:

image

 

I released a small UI update for SimpleCaptcha this morning and committed the changes to a BlogEngine.NET fork (waiting for somebody to accept my pull request;) I recommend downloading it here if you’re already using BlogEngine.NET 2.0 and want to use SimpleCaptcha: SimpleCaptcha1.1.zip (3.07 kb)

Subscribe Remind

One of the things that happened in BlogEngine.NET 2.0 was a major refactoring of how extension settings are stored, so a number of really simple extensions like my Subscribe Remind BlogEngine.NET Extension were broken in the process.

I’ve released an updated version of Subscribe Remind for BlogEngine.NET 2.0, which you can download here.

Many other basic extensions may have been broken in the process of upgrading to BlogEngine.NET 2.0, so make sure you check your App_Code folder carefully when you upgrade. Virtually any extension that saved settings data has been subject to breaking changes.

New Theme – “NerdRage”

I’ve also updated this blog’s theme – I wanted a theme that I could call my own, so I hacked and hacked the Inove and Indigo themes until I had something I was happy with. My goal here is to try to maximize readability and I really need your feedback to make that possible, so please let me know if you find my blog difficult to read in any way, shape, or form.


1just ask the people who were trying to comment on my post on ASP.NET MVC3 Remote Validation the other day

If you enjoyed this post, make sure you subscribe to my RSS feed!



Quick and Dirty Feed Parser 0.3 Now Available

December 25, 2010 05:33 by Aaronontheweb in QDFeedParser // Tags: , // Comments (0)

My Christmas gift to the developer community: Quick and Dirty Feed Parser 0.3 is now available on CodePlex!

The big feature added in this release is the ability to perform IFeedFactory.CreateFeed operations asynchronously both for disc IO and network IO. I added this to QD Feed Parser primarily to support the use of asynchronous controllers in ASP.NET MVC3, and to support Silverlight and Windows Phone 7 eventually.

All classes which implement the IFeedFactory interface now have some new members:

IAsyncResult BeginCreateFeed(Uri feeduri, AsyncCallback callback);

IFeed EndCreateFeed(IAsyncResult asyncResult);

IAsyncResult BeginDownloadXml(Uri feeduri, AsyncCallback callback);

FeedTuple EndDownloadXml(IAsyncResult asyncResult);

If you’re at all familiar with the Begin[Procedure] End[Procedure] asynchronous programming pattern in .NET, then the purpose of these functions should be self-evident. Internally, BeginCreateFeed and EndCreateFeed are dependent upon the output of BeginDownloadXml and EndDownloadXml, but should a developer want to be able to work strictly with just the XML content itself then that’s why the [Begin/End]DownloadXml methods are publicly exposed.

Here’s a quick example which shows how you can use these functions to quickly and asynchronously parse RSS and Atom feeds:

Uri feeduri;
IFeedFactory factory;
if (UseFileSys)
{
    feeduri = new Uri(Path.GetFullPath(strFeedUri));
    factory = new FileSystemFeedFactory();
}
else
{
    feeduri = new Uri(strFeedUri);
    factory = new HttpFeedFactory();
}

factory.BeginCreateFeed(feeduri, async =>
{
    var feed = factory.EndCreateFeed(async);
    foreach (var feedItem in feed.Items)
    {
        Console.WriteLine("{0} {1}",
			feedItem.DatePublished, feedItem.Title);
        Console.WriteLine(feedItem.Link);
    }
});

If you’d like to play with this example of using Quick and Dirty Feed Parser asynchronously you can download the source from Github or the full release from CodePlex.

Originally I developed Quick and Dirty Feed Parser to support legacy NET 2.0 projects that wanted functionality that you could find using the System.ServiceModel.Syndication namespace. I’m developing it going forward because I think it’s easier to use than the ServiceModel classes and I’m using it on my own projects going forward.

If you’re interested in contributing to the project, we’d love to have you. Check out our issues list or come up with your own feature ideas that aren’t in it yet!

If you enjoyed this post, make sure you subscribe to my RSS feed!



Fluent NHibernate: How to Create Bidirectional Many-to-Many Relationships with Additional Attributes on Relationship Table

December 24, 2010 10:50 by Aaronontheweb in NHibernate // Tags: , // Comments (1)

Suppose you have a relationship that looks like this in your database:

fluent-nhibernate-complex-many-to-many-mapping

This is a common use-case. In this instance, I wanted to be able to create a table which would log all of the times an individual user read a particular item from an RSS feed. I used an identity key here instead of a composite key so I could take advantage of NHibernate’s auto-magical SaveOrUpdate feature.

Normally with a ManyToMany relationship between two entities in Fluent NHibernate you would simply define a HasManyToMany attribute on either side of the entity, and .Inverse one of them such that only one entity is actually responsible for persisting changes to the database.

In this type of relationship, where your many-to-many relationship actually has additional attributes sitting in the middle of it, you have to define a third type of entity to sit in the middle, and this third entity becomes your relationship table. Here’s what this looks like in code:

The Entities

User – the entity responsible for saving; simplified in order to make it easier to read the code.

public class User
{
    virtual public int UserId { get; set; }
    virtual public string UserName { get; set; }
    virtual public string EmailAddress { get; set; }
    virtual public string Password { get; set; }
    virtual public DateTime DateJoined { get; set; }
	
    virtual public IList<FeedRead> Reads { get; set; }

    public User()
    {
        Reads = new List<FeedRead>();
    }
}

FeedItem – the entity on the other side of the relationship; also simplified.

public class FeedItem
{
    public virtual int FeedItemId { get; set; }
    public virtual Feed Feed { get; set; }
    public virtual string Title { get; set; }
    public virtual DateTime DatePublished { get; set; }
    public virtual Uri OriginalUri { get; set; }

    public virtual IList<FeedRead> Reads { get; set; }

    public FeedItem()
    {
        Reads = new List<FeedRead>();
    }
}

FeedRead – the relationship entity which contains the bidirectional relationships.

public class FeedRead
{
    public virtual int ReadId { get; set; }
    public virtual DateTime TimeRead { get; set; }
    public virtual FeedItem ItemRead { get; set; }
    public virtual User Reader { get; set; }
}

The entities themselves are pretty simple – it’s the mappings that are more interesting.

The Mappings

UserMap – the mapping for our User entity class; the key item here is the HasMany relationship and its attributes. The .Fetch and .Join properties are optional (just a way of telling the NHibernate engine how I want to perform queries against the table on the other side of this relationship,) but everything else is key.

public class UserMap : ClassMap<User>
    {
        public UserMap()
        {
            Table("EasyFeedUser");
            Id(x => x.UserId).GeneratedBy.Identity();
            Map(x => x.UserName)
				.Length(DataConstants.UserNameLength)
				.Unique().Not.Nullable();
            Map(x => x.EmailAddress)
				.Length(DataConstants.EmailAddressLength)
				.Unique().Not.Nullable();
            Map(x => x.DateJoined)
				.Not.Nullable();
            Map(x => x.Password)
				.Length(DataConstants.PasswordHashLength)
				.Not.Nullable();
            HasMany(x => x.Reads)
				.Cascade.AllDeleteOrphan()
				.Fetch.Join()
				.Inverse().KeyColumn("UserId");
        }
    }

FeedItemMap – the mapping for the FeedItem entity class.

public class FeedItemMap : ClassMap<FeedItem>
{
    public FeedItemMap()
    {
        Id(x => x.FeedItemId).GeneratedBy.Identity();
        Map(x => x.OriginalUri)
			.Not.Nullable()
			.Unique()
			.Length(DataConstants.FeedUriLength);
        Map(x => x.DatePublished)
			.Not.Nullable();
        Map(x => x.Title)
			.Not.Nullable()
			.Length(DataConstants.FeedItemTitleLength);
        HasMany(x => x.Reads)
			.Cascade.AllDeleteOrphan()
			.Inverse()
			.Fetch.Join().KeyColumn("FeedItemId");
    }
}

FeedReadMap – the mapping for the FeedRead entity.

public class FeedReadMap : ClassMap<FeedRead>
    {
        public FeedReadMap()
        {
            Table("Reads");
            Id(x => x.ReadId).GeneratedBy.Identity();
            Map(x => x.TimeRead).Not.Nullable();
            References(x => x.Reader)
				.Not.Nullable()
				.Cascade.SaveUpdate()
				.Column("UserId");
            References(x => x.ItemRead)
				.Not.Nullable()
				.Cascade.SaveUpdate()
				.Column("FeedItemId");
        }
    }

So now that I’ve shown you all of the code, what does this mean?

In order to have a bidirectional many-to-many relationship with additional attributes, you need to do the following things:

  1. Create a third entity type which contains all of the columns for the relationship table;
  2. Define the relationships between the two primary entities and the relationship entity (FeedRead) as HasMany.Inverse().Cascade.AllDeleteOrphan and name an explicit foreign key.
  3. Map the relationships of the relationship entity (FeedRead) to the primary entities as References.Cascade.SaveUpdate() and name an explicit column name, which is the same name as the foreign key you defined on the other mappings.

And that’s it – now you can automatically add new FeedRead entities by calling Session.SaveOrUpdate(user) or Session.SaveOrUpdate(feeditem).

If you enjoyed this post, make sure you subscribe to my RSS feed!



Los Angeles Azure 101 Event - 12/16/2010

December 13, 2010 09:04 by Aaronontheweb in Microsoft, Azure // Tags: , // Comments (0)

Just a quick post for any readers in the Southern California area who are interested in learning more about Windows Azure. My team is putting on an event at Microsoft's Los Angeles offices on Thursday morning from 9:00am to 1:00pm and its intent is to help introduce Azure to any curious developers who've never had a chance to play around with it before. These guys are all top-notch Azure experts and will be able to answer your questions better than I ever could :p

Register or learn more:

http://bit.ly/laazure 

If you enjoyed this post, make sure you subscribe to my RSS feed!



How-To: Remote Validation in ASP.NET MVC3

ASP.NET MVC3 has been a major boon to my productivity as a web developer since I started using it at the beginning of November – the new Razor view engine has been attracting most of the attention with this iteration of MVC, but one extremely sexy feature has gone unnoticed thus far: Remote validation.

Remote validation was one of the new features added in the November Release Candidate (RC) for MVC3 – I had a chance to demo it in front of the LA .NET User Group last night and it was a monster hit.

Example:

You’ve all seen remote validation before – ever see one of these when you’re signing up for a service like Twitter?

image

That’s remote validation at work. Twitter, Facebook et al make an AJAX call to a remote service hook that checks the database to see if the username is available as the user types it. It’s a major user experience improvement as they get that feedback instantly instead of having to wait until after they fill out the rest of the form and submit it.

In the past with ASP.NET MVC you had to write your own custom jQuery scripts on top of the jQuery validation engine to achieve this end-result; in ASP.NET MVC3 this is taken care of for you automatically!

Let me show you how it works:

1. Decorate a model class with the Remote attribute

Take the class you want to validate and decorate the attributes you need to remotely validate with the Remote attribute.

public class NewUser
{
	[Remote("UserNameExists", "Account", "Username is already taken.")]
	public string UserName { get; set; }

	[Remote("EmailExists", "Account", "An account with this email address already exists.")]
	public string EmailAddress { get; set; }

	public string Password { get; set; }
}

In both of these instances of the Remote attribute, I’ve passed the following arguments:

  • The name of an action method;
  • The name of the controller where the action method lives; and
  • A default error message should user input fail this validation challenge.

2. Implement an action method to support your Remote attribute

You will need to implement an action method that supports your Remote attribute. Add an action method which returns a JsonResult to the controller you named in your Remote attribute arguments. In this example I’ll need to add these action methods to the “Account” controller:

public JsonResult UserNameExists(string username)
{
    var user = _repository.GetUserByName(username.Trim());
    return user == null ? 
		Json(true, JsonRequestBehavior.AllowGet) : 
		Json(string.Format("{0} is not available.", username),
			JsonRequestBehavior.AllowGet);
}

public JsonResult EmailExists(string emailaddress)
{
    var user = _repository.GetUserByEmail(emailaddress.Trim());
    return user == null ?
		Json(true, JsonRequestBehavior.AllowGet) : 
		Json(
			string.Format("an account for address {0} already exists.",
			emailaddress), JsonRequestBehavior.AllowGet);
}

 

If the repository returns null, meaning that a user account with this particular user name or email address doesn’t already exist, then we simply return a JsonResult with a value of true and go off on our merry way. This will suppress the jQuery validation library from raising a validation error.

If the action method returns false, then jQuery will raise a validation error and display the default error message provided in the Remote attribute arguments – if you didn’t provide a default error message yourself then the system will use an ambiguous default one.

If the action method returns a string, jQuery will raise a validation error and display the contents of the string, which is what I’m doing here in this example.

Small Gotcha – Naming Action Method Parameters

There’s one small gotcha that can be easy to miss – the name of the argument on your action method must match the name of your property on your model. If I changed the EmailExists body to look like this:

public JsonResult EmailExists(string email);

Then ASP.NET would pass a null value to this method.

Case 1: parameter name matches the name of the model’s property

image

Case 2: parameter name does not match the name of the model’s property

image

This is because the jQuery parameter takes the name of the property on the model and passes it as a querystring argument to your action method – here’s what your validation request looks like in Firebug:

[host]/account/emailexists?area=an%20account%20with%20this%20email%20address%20already%20exists.&EmailAddress=test%40test.com

The ASP.NET MVC model-binder isn’t all-knowing – it’s not going to be able to tell that EmailAddress and email are the same thing, thus it ultimately won’t bind an argument to your action method, hence why the null value is passed.

If you follow the convention of using a common name for your Remote validator action method arguments and your model properties, you won’t run into this issue.

UPDATE: Thanks to Rick Anderson for directing me to the much more extensive MSDN documentation on how to implement custom Remote validaiton in ASP.NET MVC3.

If you enjoyed this post, make sure you subscribe to my RSS feed!



Where Are My Windows Phone 7 Icons?

December 6, 2010 21:00 by Aaronontheweb in Windows Phone 7 // Tags: // Comments (0)

appbar.favs.addto.rest This is a quick post, but for anybody who isn’t using Expression Blend (which automatically puts all of the Windows Phone 7 icons for you in a drop-down list as default resource values for images) and wants to get access to icons in Windows Phone 7, here is where you can find them:

C:/Program Files (x86)/Microsoft SDKs/Windows Phone/v7.0/Icons/

image

If you enjoyed this post, make sure you subscribe to my RSS feed!



LA .NET User Group ASP.NET MVC3 Lecture Notes

December 6, 2010 11:46 by Aaronontheweb in ASP.NET // Tags: , , , // Comments (3)

Putting this online for the benefit of the .NET LA User Group – this content is meant to accompany my talk on ASP.NET MVC3:

ASP.NET MVC3 links:

Source code:

PeerLearningSite on Github (Example Project with ASP.NET MVC3 RC)

Presenters:

Lastly, join the LA Windows Phone 7 Developers Group.

I’m going to put up some blog content to accompany this once my talk is done tonight – now I’ve got to go practice and give my talk :p

If you enjoyed this post, make sure you subscribe to my RSS feed!



How to Make it Easy for New Developers to Adopt Your Open Source Project

James Gregory is one of my heroes in the .NET community – he’s the creator of Fluent NHibernate, my favorite new ORM (Object-Relational Mapper) for my ASP.NET MVC projects. James expressed some dismay earlier today when a newbie Fluent NHibernate developer posted a $100+ bounty for a few basic usage examples; having just recently taught myself the very things the developer was asking for, I can empathize.

NHibernate (Fluent NHibernate runs on top of it) is a bear of an open source project for new developers – it’s a mature project that solves a problem with a massive surface area and the NHibernate community is comprised of experienced developers who’ve been using it for years. It can be difficult for new developers to get involved, as the learning curve is steep even for just adopting the project, let alone contributing to it.

So how can we make it easier for new developers to adopt advanced, mature projects like NHibernate?

Here are some ideas:

  1. Offer new developers a low-friction adoption path – in many of the projects I’ve tried to adopt over the years, I’ve had a difficult time figuring out what the best way to start using some of them itself. The thing that is going to kill adoption of your project is friction – if a new developer encounters resistance just trying to build a basic “Hello World” equivalent on top of your project, they’re going to lose interest and try to find something else. Gently guide your developers down on low-friction path to adoption.

  2. Describing the functionality of features isn’t enough; explain their intent – NHibernate has a ton of content for in its Wiki for new developers, but few of them explain the intent behind NHibernate’s powerful features – what new developers really need to understand is why and when they should use certain facets of your project’s functionality, not so much how that functionality works.

  3. Use real-world examples in the documentation – one issue that frustrates me when I’m trying to learn a new technology is when all of the 101-level examples are canned. I don’t want a Fibonacci sequence example or anything else that I would never use in the real-world. Show me the hard stuff; show me how to build a human user-interaction around your project’s technology; show me how to use it in a middle use-case; and show me how to integrate your technology into projects that resemble production scenarios.

  4. Include reference materials – .NET developers get to use object explorer as an option of last resort for reference materials, which is a plus. However, the goal of open source project leads should be to make it easy for new adopters to find the right piece of functionality to suit their needs, and a reference guide is often the best way to do it.

  5. StackOverFlow first – I was trying to troubleshoot a small Fluent NHibernate issue I ran into earlier today (will blog about it soon), and I saw a number of questions identical to my own on StackOverFlow – all of them unanswered. The first place your users are going to turn when they run into trouble is Bing and Google, and StackOverFlow will dominate the results; answering someone’s question on StackOverFlow doesn’t solve a problem for just the person who asked it – it answers the question for many other people who may run into the same issues down the road.

Open source is fun, and it's fun for new developers too. Onboarding new developers to your project isn't as sexy as writing a patch or coming out with a new version, but it's ultimately better for the longevity of the community.

If there's anything else I should add to this list, please add them in the comments!

If you enjoyed this post, make sure you subscribe to my RSS feed!



Search

About

My name is Aaron, I'm an entrepreneur and a .NET developer who develops web, cloud, and mobile applications.

I left Microsoft recently to start my own company, MarkedUp - we provide analytics for desktop developers, focusing initially on Windows 8 developers.

You can find me on Twitter or on Github!

Recent Comments

Comment RSS

Sign in