The "We accept pull requests" Addiction


Hello, my name is Jeff.  I last said “We accept pull requests” one week ago on February 18th during the MVP Summit.

My apologies

I want to apologize to the MVPs who heard me say this last week.  The words did not convey the message I was attempting to convey.

At the end of the day on Monday, we were soliciting feedback on new feature ideas for ASP.NET.  One gentleman asked about creating server-side HTML tags that would be recognized by Razor, and translated into different client-side HTML.  The idea would be a bit of a blend between ASP.NET Web Forms and ASP.NET MVC with Razor templates; a very neat idea indeed.  After a few minutes of discussion, it sounded like the idea should be relatively straight-forward to implement within Razor as it is today.  I thought, “That should be pretty straight-forward; maybe he would want to take a stab at it himself.”  And then I did it; I said the cringe-inducing words, “We accept pull requests.”

As the sentence left my mouth, I realized how terrible it sounded.  A moment later, I heard a couple murmurs about the remark.  It didn’t come out right and it wasn’t received well.  I only hope I didn’t offend the person requesting the feature or dissuade him from suggesting other features.  I honestly meant for the remark to mean “That’s a great idea, thanks!  Do you want to take a stab at it?  We’ll help.”  I am sorry my words didn’t reflect my sentiment.

The stigma

Why do the words “we accept pull requests” have such a stigma?  Why were they cringe-inducing when I spoke them?  Because too many OSS projects use these words as an easy way to shut people up.  We (the collective of OSS project owners) can too easily jump to this phrase when we don’t want to do something ourselves.  If we don’t see the value in a feature, but the requester persists, we can simply utter, “We accept pull requests,” and drop it until the end of days or when a pull request is submitted, whichever comes first.  The phrase now basically means, “Buzz off!”

Trigger happy

At Microsoft, having open source projects that actually accept pull requests is a fairly new concept.  I work on NuGet, which is an Outercurve project that accepts contributions from Microsoft and many others.  I was the dev lead for Razor and Web Pages at the time it went open source through Microsoft Open Tech.  I collaborate with teams that work on EntityFramework, SignalR, MVC, and several other open source projects.  I spend virtually all my time thinking about projects that are open source.  Just a few years ago, this was unimaginable at Microsoft.  Sometimes I feel like it still hasn’t sunk in how awesome it is that we have gotten to where we are, and I think I’ve been trigger happy and I’ve said “We accept pull requests” too often  I typically use the phrase in jest, but I admit that I have said it when I was really thinking “Buzz off!”

Coming to a head

Phil's short rantThe weekend after the MVP summit, I was still regretting having said what I said.  I wished all week I could take the words back.  And then I saw someone else fall victim.  On a highly controversial NuGet issue, the infamous Phil Haack used a similar phrase as part of a response stating that the core team probably wouldn’t be taking action on the proposed changes, but that there was nothing stopping those affected from issuing a pull request.  With my mistake still fresh in my mind, I read Phil’s words just as I’m sure everyone in the room at the MVP summit heard my own.  It sounded flippant and it had the opposite effect from what Phil intended or what I would want people thinking of the NuGet core team.  From there, the thread started turning nasty.  We were stuck arguing opinions and we were no longer discussing the actual issue and how it could be solved.

A short while later, a Twitter conversation broke out, initiated by a short rant from Phil.  Several people started weighing in:

With that many mentions on the tweets, we ran out of characters and eventually moved into JabbR.  By the end of the conversation, we all agreed that the words “we accept pull requests” should never be used again.  Phil proposed a great phrase to use instead: “Want to take a crack at it? We’ll help.”

Beating an addiction

I am vowing to stop saying “We accept pull requests.”  Instead I’m going to be more intentional and say things like “Want to take a crack at it? We’ll help.”  And if it’s a feature that doesn’t make sense in the project, I’ll be more honest and say so, explaining why.

author: Jeff Handley | posted @ Monday, February 25, 2013 11:43 PM | Feedback (4)

Bulk Publishing NuGet Packages


One of the responsibilities I have come to own is our group’s NuGet package publishing.  My group owns all of the ASP.NET MVC, Web API, Web Pages, Razor, and SignalR packages, among others; and there are quite a few of them.  When it’s time to publish a set of packages, we go through a bit of a dance:

  1. Gather all of the packages that are going to be published together into a single location
    1. We have packages that are built out of different repositories that need to be brought together
    2. Each team that has packages to be published as part of a larger release will put their packages into a designated TFS folder (don’t judge, but TFS is perfect for this)
    3. Then, when we have QA sign-off on all of the packages, we will publish them all from that folder
  2. Upload all of the packages to nuget.org, but then immediately mark them as unlisted
  3. When all of the other release assets are ready, and we’re ready to “flip the switch,” list all of the packages

To make this process smooth, I have created a few PowerShell functions that I use each time.  These are wrapped up in a PowerShell module so that I can easily reuse them.  Here is the repository for the module:
https://github.com/jeffhandley/JeffHandley.NuGetPS

Within that repository, the NuGetPackagePublishing.psm1 file exports the following functions.

Submit-Package

NAME
    Submit-Package

SYNOPSIS
    Submits a NuGet package (or set of packages) to the gallery, but as hidden (unlisted).


SYNTAX
    Submit-Package [-packageId <Object>] [-packageVersion <Object>] [-packageFile <Object>] [-apiKey <Object>]
    [-galleryUrl <Object>] [<CommonParameters>]

    Submit-Package [-packagesConfig <Object>] [-apiKey <Object>] [-galleryUrl <Object>] [<CommonParameters>]


DESCRIPTION
    Uploads the specified package (or all packages from a packages.config file) to the gallery and then immediately
    marks the package(s) as unlisted by running the nuget delete command.


RELATED LINKS

REMARKS
    To see the examples, type: "get-help Submit-Package -examples".
    For more information, type: "get-help Submit-Package -detailed".
    For technical information, type: "get-help Submit-Package -full".

Hide-NuGetPackage

NAME
    Hide-NuGetPackage

SYNOPSIS
    Hides a package from the NuGet gallery


SYNTAX
    Hide-NuGetPackage [-packageId <Object>] [-packageVersion <Object>] [-apiKey <Object>] [-galleryUrl <Object>]
    [<CommonParameters>]

    Hide-NuGetPackage [-packagesConfig <Object>] [-apiKey <Object>] [-galleryUrl <Object>] [<CommonParameters>]


DESCRIPTION
    Marks the specified NuGet package as unlisted, hiding it from the gallery.


RELATED LINKS

REMARKS
    To see the examples, type: "get-help Hide-NuGetPackage -examples".
    For more information, type: "get-help Hide-NuGetPackage -detailed".
    For technical information, type: "get-help Hide-NuGetPackage -full".

Show-NuGetPackage

NAME
    Show-NuGetPackage

SYNOPSIS
    Shows a package on the NuGet gallery, listing an already-published but unlisted package.


SYNTAX
    Show-NuGetPackage [-packageId <Object>] [-packageVersion <Object>] [-apiKey <Object>] [-galleryUrl <Object>]
    [<CommonParameters>]

    Show-NuGetPackage [-packagesConfig <Object>] [-apiKey <Object>] [-galleryUrl <Object>] [<CommonParameters>]


DESCRIPTION
    Marks the specified NuGet package as listed, showing it on the gallery.


RELATED LINKS

REMARKS
    To see the examples, type: "get-help Show-NuGetPackage -examples".
    For more information, type: "get-help Show-NuGetPackage -detailed".
    For technical information, type: "get-help Show-NuGetPackage -full".

Set-NuGetPackageVisibility

NAME
    Set-NuGetPackageVisibility

SYNOPSIS
    Sets a package's visibility within the NuGet gallery


SYNTAX
    Set-NuGetPackageVisibility [-action <Object>] [-packageId <Object>] [-packageVersion <Object>] [-apiKey <Object>]
    [-galleryUrl <Object>] [<CommonParameters>]

    Set-NuGetPackageVisibility [-action <Object>] [-packagesConfig <Object>] [-apiKey <Object>] [-galleryUrl <Object>]
    [<CommonParameters>]


DESCRIPTION
    Hide (unlist) a package from the gallery or show (list) a package on the gallery.


RELATED LINKS

REMARKS
    To see the examples, type: "get-help Set-NuGetPackageVisibility -examples".
    For more information, type: "get-help Set-NuGetPackageVisibility -detailed".
    For technical information, type: "get-help Set-NuGetPackageVisibility -full".

 

Ship It!

With the above module imported, by using PowerShell's Import-Module command, I can then very easily take a directory of NuGet packages and publish them all at once. There's still a little bit that I don't have automated, and that is creating a packages.config file to use as a parameter to the functions. To do that, my process is presently to do the following:

  1. In Windows Explorer, select all of the files
  2. Shift+Right-Click and choose "Copy as Path"
  3. Paste the paths into a text editor like Sublime Text
  4. Use search/replace to change the file paths into a packages.config file format

For the packages I published a few minutes ago, the packages.config file looks like this:

<packages>
    <package id="Microsoft.Owin.Host.SystemWeb" version="1.0.0-rc1" />
    <package id="Microsoft.AspNet.FriendlyUrls" version="1.0.0-rc1" />
    <package id="Microsoft.AspNet.FriendlyUrls.ViewSwitcher" version="1.0.0-rc1" />
    <package id="Microsoft.AspNet.Mvc.Facebook" version="0.3.0-rc" />
    <package id="Microsoft.AspNet.SignalR" version="1.0.0-rc1" />
    <package id="Microsoft.AspNet.SignalR.Client" version="1.0.0-rc1" />
    <package id="Microsoft.AspNet.SignalR.Core" version="1.0.0-rc1" />
    <package id="Microsoft.AspNet.SignalR.Hosting.Utils" version="1.0.0-rc1" />
    <package id="Microsoft.AspNet.SignalR.JS" version="1.0.0-rc1" />
    <package id="Microsoft.AspNet.SignalR.Owin" version="1.0.0-rc1" />
    <package id="Microsoft.AspNet.SignalR.Redis" version="1.0.0-rc1" />
    <package id="Microsoft.AspNet.SignalR.ServiceBus" version="1.0.0-rc1" />
    <package id="Microsoft.AspNet.SignalR.SystemWeb" version="1.0.0-rc1" />
    <package id="Microsoft.AspNet.SignalR.Utils" version="1.0.0-rc1" />
    <package id="Microsoft.AspNet.WebApi.HelpPage" version="0.3.0-rc" />
    <package id="Microsoft.AspNet.WebApi.HelpPage.VB" version="0.3.0-rc" />
    <package id="Microsoft.AspNet.WebApi.OData" version="0.3.0-rc" />
    <package id="Microsoft.AspNet.WebApi.Tracing" version="0.3.0-rc" />
</packages>

Once I have that packages.config (which I also check into the TFS repository with the packages), I can then run the following two commands:

  1. Upload the packages as unlisted
    Submit-Package -packagesConfig packages.config -apiKey <apikey>

  2. List the packages that have already been uploaded
    Show-NuGetPackage -packagesConfig packages.config -apiKey <apikey>

That's it. Now, with only 18 packages being published, this might seem like overkill. However, for the RTM release ASP.NET MVC 4, Web Pages 2, etc., we had 491 NuGet packages that were published! With that volume, having the process scripted out like this was crucial. And now I like to follow the same process regardless of the number of packages. If you too have lots of NuGet packages that you publish and you want to simplify your process, feel free to grab the NuGetPackagePublishing.psm1 file and use it within your process.

Other Tips

Here are some other tips to help with your NuGet Publishing:

  1. nuget.exe push accepts wildcards. That means you can run nuget.exe push *.nupkg to publish all nupkg files in a folder.
    I don't use this approach because my process typically involves uploading as unlisted and then listing the packages later. In order to do that, I need to have the package Id and Version, which is why I have the packages.config file approach.
  2. nuget.exe delete will unlist a package. You may have noticed this from my process above, but it's a little-known feature.
  3. nuget.org supports POST and DELETE operations for a package, allowing you to set the package's visibility through a simple web request. The URL for the web request is https://nuget.org/api/v2/Package/<packageId>/<packageVersion>?apiKey=<apiKey>. You can see this in use within the SetVisibility method in the NuGetPackageVisibility.psm1 file.

author: Jeff Handley | posted @ Thursday, December 13, 2012 10:42 AM | Feedback (0)

RIA Services NuGet Package Updates – Including Support for EntityFramework 5.0.0


It’s been a long time coming, but we have now published updated and new NuGet packages for RIA Services, including an update to RIAServices.EntityFramework that adds support for EntityFramework 5.0.0 (including all versions >= 4.1).

Each of the following NuGet packages was uploaded to nuget.org today with the version number of 4.2.0.  Aside from improving the EntityFramework support with a detailed README, NuGet packaging updates, and the creation of a couple of new packages from existing Toolkit assets, there are no further updates across the features.  I do hope the new and updates packages are helpful though!

RIAServices.EntityFramework

RIAServices.EntityFramework provides the DbDomainService<T> class which can be used to create Domain Service classes for use with a DbContext from the EntityFramework package.  This package supports versions of EntityFramework starting with EF 4.1, including EntityFramework 5.0.0.

Special thanks to the EF Team’s Brice Lambson, as this package would not have been possible without his work on it.  The package includes a README.txt that NuGet will open automatically, as there are some known installation issues with the package, but they all have straight-forward solutions within your web.config file.

For more information, Varun Puranik’s blog post from when this package was initially published is still applicable.

  1. http://varunpuranik.wordpress.com/2011/06/29/wcf-ria-services-support-for-ef-4-1-and-ef-code-first/

Note that while the NuGet packages are now compatible, there may be features in EF 5.0+ that will not be compatible with RIA Services due to how it serializes entities.  Unless a property’s data type is known to RIA Services, it is unable to serialize the property.  Therefore, properties such as Geometry will not be supported.

RIAServices.EF4

RIAServices.EF4 provides the LinqToEntitiesDomainService<T> class which can be used to create Domain Service classes for use with an ObjectContext from Entity Framework 4.0.  This package can be used in place of a GAC reference to the System.ServiceModel.DomainServices.EntityFramework assembly, making bin-deployment easier.  The dependency on RIAServices.Server provides the other assemblies that are needed for the server-side runtime of RIA Services.

RIAServices.LinqToSql

RIAServices.LinqToSql provides the LinqToSqlDomainService<T> class which can be used to create Domain Service classes for use with a DataContext class from Linq to Sql.

RIAServices.WindowsAzure

RIAServices.WindowsAzure provides the TableDomainService<T> class, as well as the TableEntity and TableEntityContext classes that can be used to create Domain Services backed by Windows Azure Table Storage.

For more information, Kyle McClellan’s blog has some good documentation:

  1. http://blogs.msdn.com/b/kylemc/archive/2010/11/01/ria-services-and-windows-azure-table-storage.aspx
  2. http://blogs.msdn.com/b/kylemc/archive/2010/11/19/azure-table-storage-associations-with-ria-services.aspx
  3. http://blogs.msdn.com/b/kylemc/archive/2010/11/19/windows-azure-table-partitionkey-options-for-tabledomainservices.aspx

RIAServices.Server

RIAServices.Server provides the System.ServiceModel.DomainServices.Hosting and System.ServiceModel.DomainServices.Server assemblies in place of the GAC references, making bin-deployment easier.  The web.config.transform adds the necessary configSections, HTTP modules, and system.serviceModel elements to make your Domain Services available at runtime.  The package also includes a targets file and tools assembly that provides build-time validation of your DomainService classes.

This package can be used instead of going through the Add New Domain Service wizard to prime your ASP.NET web application with the configuration necessary to utilize WCF RIA Services on the Server.

There is one known issue, where some users have experienced duplicate web.config entries getting created as a result of installing this package.  To resolve this issue, simply eliminate one of the duplicate entries -- they vary only by character case.

RIAServices.Endpoints

RIAServices.Endpoints provides the Microsoft.ServiceModel.DomainServices.Hosting assembly, which serves the SOAP and JSON endpoints for Domain Services.  The necessary web.config entries are added with this package as well.

RIAServices.T4

RIAServices.T4 provides the CSharpClientCodeGenerator class, as well as many supporting classes, which can be used to override the existing code generation pattern for RIA Services.

For more information, Varun Puranik’s blog has a couple of posts:

  1. http://varunpuranik.wordpress.com/2010/11/05/t4-code-gen/
  2. http://varunpuranik.wordpress.com/2010/12/02/t4-code-generator-dec-10-update/

RIAServices.UnitTesting

RIAServices.UnitTesting provides a DomainServiceTestHost that can be used to unit test your Domain Services.

For more information, Kyle McClellan has a few blog posts on this package:

  1. http://blogs.msdn.com/b/kylemc/archive/2011/08/18/unit-testing-a-wcf-ria-domainservice-part-1-the-idomainservicefactory.aspx
  2. http://blogs.msdn.com/b/kylemc/archive/2011/08/18/unit-testing-a-wcf-ria-domainservice-part-2-the-repository-pattern.aspx
  3. http://blogs.msdn.com/b/kylemc/archive/2011/08/18/unit-testing-a-wcf-ria-domainservice-part-3-the-domainservicetesthost.aspx

RIAServices.WebForms

RIAServices.WebForms contains the ASP.NET DomainDataSource and DomainValidator controls for using a DomainService in an ASP.NET Web Forms application.

RIAServices.Toolkit.All

This is a rollup package that includes all of the packages listed above.

author: Jeff Handley | posted @ Monday, December 10, 2012 5:28 PM | Feedback (19)

Merry Christmas! It’s my off-site backup.


Almost 4 years ago, I moved my family from Cincinnati, Ohio to Redmond, Washington so that I could join Microsoft.  Since we have two young children, and a small family, the move has been really hard on the grandparents.  But as our first Christmas out here approached, I came up with an idea that I’ve been repeating each year since.  Last night, I tweeted about this idea and the tweet went a bit viral, with hundreds of retweets (thanks largely to Scott Hanselman, I’m sure).

Like many families, we take thousands of digital photos and we store them on an HP MediaSmart HomeServer.  Every year for the last 4 years, I sit down the weekend before Christmas, copy all of the year’s pictures from the HomeServer to my PC, organize them into folders of 4GB, burn them to DVD, and then send them to the grandparents as my Christmas gift to them.  What they don’t know (until now anyway), is that the gesture isn’t completely selfless.  As succinctly expressed in my tweet, those disks are my off-site backup of our family photos.  And we’re talking 2300 miles off-site, so I consider myself pretty well covered.

Here are a few details for my process, in case you decide to pick up this routine too:

  1. Organize the photos into folders with the year, subfolders with the year-month, then subfolders again with year-month-day, and file names that have date-time stamps.  (I store these on our HomeServer).
  2. When copying the folders in preparation for the year’s disks, always include December from the previous year, so that photos from the previous Christmas are included in the set.
  3. Split the photos into groups of ~4GB, and burn the groups to disks.  This year I had 7 DVDs.
  4. Deliver the disks to the grandparents, and ideally copy the photos to their PC and add them to the screensaver.

My wife and I have agreed many times that in the case of a disaster, our family photos are what we’d miss the most.  Having all of our photos archived at the grandparents’ offers us a bit of reassurance that we’ll never lose them.  And you cannot imagine how much joy the grandparents get when they watch the photo slideshow.  We’ve spent several hours sitting together watching the screensaver.

In the days of Angry Birds, this is what I call killing two stones with one bird.  Merry Christmas!

author: Jeff Handley | posted @ Monday, December 19, 2011 12:26 AM | Feedback (2)

WCF RIA Services V1.0 SP2 Released


On December 9, 2011, Silverlight 5 was released.  As always, RIA Services is included in the Tools for Silverlight, and this new release included our V1.0 SP2 final build.  We refer to this as our RTW build, meaning Released-To-Web.  In addition to the Tools installer including WCF RIA Services V1.0 SP2, you can also find the standalone installer on Microsoft Download Center.  The silverlight.net/getstarted/riaservices page was also updated to reflect the new release.

Aside from some bug fixes here and there, not much changed between SP2 RC and SP2 RTW.  The primary focus as been ensuring really solid compatibility with both Silverlight 5 and Silverlight 4 with this release, and testing the bits on tons of configurations for both design-time and runtime.  If you are upgrading from V1.0 SP1 to V1.0 SP2 though, here’s what you’ll find in this release:

  1. Support for both Silverlight 4 and Silverlight 5 (SP1 had subtle issues that would cause bugs in SL5 apps)
  2. DateTimeOffset support
  3. Entity Framework Code-First development, using this NuGet package

There are some more details in my SP2 RC announcement.

Known issues include:

  1. Entity Framework 4.1 is required, but version 4.2 isn’t supported yet
  2. Requires Visual Studio 2010 or SP1. Visual Studio 11 isn’t supported yet
  3. Work on supporting Universal Providers for improved Windows Azure deployment is ongoing
  4. If you want to create a Silverlight 4 Business Application Template, first create an instance of the Silverlight 5 template, and then use the Silverlight project properties to retarget it to Silverlight 4. You might see some temporary errors (System.Windows.Markup.XamlParseException) in the designer and error window, but rebuilding will make those disappear.

I want to send a shout out to Yavor Georgiev and point you to his blog, as he has been driving this SP2 RTW release for the last couple of months.  I’ve transitioned off of the WCF RIA Services team and over to ASP.NET Web Pages, NuGet, and Razor.

If you have any questions or issues with the SP2 release, jump over to the forums.

Technorati Tags: ,

author: Jeff Handley | posted @ Monday, December 12, 2011 11:04 PM | Feedback (8)

GitHub, Triggered Deployments, and In-Browser Editing


Earlier this month, I explored using WebMatrix, Git, and AppHarbor to quickly create and publish a web site in the cloud.  While at work this afternoon I got a phone call from a relative with a usability issue on the site.  To fix the issue, I simply needed to wrap a hyperlink around an image, but I only had about 60 seconds to spare, which wasn’t enough time.  Here’s what I would have needed to do:

  1. Open up a command prompt and cd into D:\github
  2. git clone https://jeffhandley@appharbor.com/xanderhandley.git
  3. Open the offending cshtml file, make the edit
  4. git commit –am "Made the popcorn site image a hyperlink"
  5. git push https://jeffhandley@appharbor.com/xanderhandley.git

Okay, so in retrospect it would have only taken about 120 seconds to do, but at that moment, I didn’t think I could do it in the minute I had to spare.  I am going to use this opportunity to introduce GitHub into my workflow and be prepared for these kinds of adjustments should they arise again.

GitHub

I will describe GitHub by quoting their own site:

GitHub is the best way to collaborate with others. Fork, send pull requests and manage all your public and private git repositories.

It’s also a nice and free place to clone my repositories in the cloud so that I don’t have to worry about backups and whatnot.  And honestly, it’s kind of a “central” repository for distributed version control. Don’t hate–you know you think of it that way too.

When I first created and published my son’s popcorn site though, GitHub wasn’t needed in the workflow.  Instead, I just went directly from my PC to the cloud.  But with today’s episode, I needed to be able to get the latest version of the code on my work computer.  I wanted a central repository, and starting tonight, GitHub will serve that purpose.  Here’s how:

  1. Go to http://github.com and sign up / log in
  2. Create a New Repository
  3. On my home PC, where my existing repository is for the project, open a command window
  4. git remote add origin https://jeffhandley@github.com/jeffhandley/XanderHandley.git
  5. git push –u origin master

I now have my repository cloned up in GitHub and I can push changes to it.  But just like with regular expressions, now I have two problems.  When I want to deploy an update to the website, I need to get the updates to both GitHub and AppHarbor.

Triggered Deployments

Fortunately, GitHub has a feature that turns the two problems (of pushing to both GitHub and AppHarbor) back into 1 problem.  I can configure a Service Hook in GitHub such that any time I push an update to GitHub, the changes are also automatically pushed to AppHarbor.

Here’s how to do it:

  1. On GitHub, while viewing the files for your repository, click on the “Admin” button at the top
  2. Click on “Service Hooks” on the left
  3. Select AppHarbor
  4. Open a new tab in your browser and go load up your AppHarbor application
  5. Under where AppHarbor shows your Repository URL, you’ll see a link next to “Create build URL” – click that to see your build URL
  6. Paste the Application Slug and Token into the AppHarbor Service Hooks configuration in GitHub as they explain, make the hook Active, and click Update Settings

The GitHub repository is now set up so that any push into my master branch on GitHub will also push to AppHarbor.  To verify this, I just went through the following steps:

  1. Opened the website on my home PC
  2. Made the change, adding the hyperlink
  3. Opened a command prompt where the website files are
  4. git commit –am "Made the popcorn site image a hyperlink"
  5. git push origin master

I then refreshed github and saw the commit listed there.  Then I went over to AppHarbor and refreshed and saw the commit there too.  Then I went to http://xanderhandley.com/popcorn and refreshed and saw the new hyperlink.

In-Browser Editing

With all of this magic awesomeness, I was thinking it couldn’t be any easier to edit Xander’s website.  But sure it can.  Now that I have GitHub and AppHarbor connected, I can utilize another goodie that GitHub has to offer: in-browser editing.  GitHub allows you to edit files directly in the browser; just browse to a file and click on the “Edit this file” button.  You’ll get a text editor in the browser, along with a commit message box and a “Commit Changes” button.  That’s right, you can make single-file edits directly in the browser and commit them to your repository in GitHub.  After committing, the service hook we configured will be executed, and the change will be deployed to AppHarbor immediately.

I just exercised this capability by editing default.cshtml, adding a “This page intentionally left blank” message to it, and committing the change.  Within seconds, that change was deployed to xanderhandley.com.  I now have an in-browser editor of my son’s website such that any changes I commit are immediately deployed to the cloud.  This certainly isn’t a routine I’d use for a business website or anything remotely complicated, but for my son’s website, this is perfect.  And it’s free.

Technorati Tags: ,,

author: Jeff Handley | posted @ Tuesday, October 25, 2011 12:01 AM | Feedback (0)

WebMatrix, Git, and AppHarbor


I needed to crank out a single-page web site for my son, to help him promote his popcorn sales for cub scouts.  I decided it was a perfect opportunity to try some new things, so I jumped into WebMatrix to create the site, added it to a Git repository, and deployed it to AppHarbor under Xander’s own domain name.  Start to finish, it took 30 minutes and my son gave me an A+.

Install WebMatrix 2 Beta NowInstalling WebMatrix

WebMatrix is one of the easiest-to-install Microsoft products ever.  Really.  Just go to http://www.microsoft.com/web/webmatrix/ and click the big green button for “Install WebMatrix” or “Or try the latest Beta.”  I decided to try the latest beta, and after just a couple more simple clicks and about 4 minutes, WebMatrix 2 Beta was on my machine and open.

Creating the Web Site

I clicked on Templates and since this is going to be an extremely simple, one-page site, I went for the “Empty Site” template.  The Personal Site template was tempting, but over-the-top for my needs.  WebMatrix created the site in moments and then gave me a nice start page.  I immediately jumped to the “Files” view and here’s what I saw I was given:

image

I created a new Popcorn.cshtml file that contains the content my son helped author.  It was a no-brainer to use the CSHTML file, as that lets me use the awesome Razor syntax as needed.  I created an /images folder and a /videos folder, where I put some media I had prepared.  Then I hit the Run button.  Yes, I prefer Chrome.

image

Since I had added my content to Popcorn.cshtml, and hadn’t added anything to the Default.cshtml, I got a blank page.  That’s good with me, as a default page is out of scope for tonight’s project. :-)  What I really wanted to see is what I got when I browsed to /popcorn…

image

Perfect!  Notice that I didn’t have to browse to popcorn.cshtml – just /popcorn is sufficient because ASP.NET Web Pages automatically routes this form me.  That feels so much cleaner than creating a popcorn folder and adding a default.cshtml to it.  So, the website is now complete and ready for source control.

Installing Git

github has an awesome page for setting up Git.  There’s no need for me to repeat anything here.  Just go to github’s page and then come back here when you’re ready.

Creating the Git Repository

This was easy stuff.  Using Git Bash, here’s what it took to initialize the Git repository, add the website, review the pending changes, and commit them.

image

Deploying to AppHarbor

If you’re not familiar with AppHarbor, it’s a service that lets you host sites using Git to push a repository to the cloud (no, it’s not Azure – shame).  AppHarbor will host this site for free, which is a requirement for this project.  Signing up for AppHarbor was extremely simple and took about 1 minute.

Before I could deploy, I needed to first create an application.  It’s a single-field form.  Seriously.

image

After naming the application and clicking Create, I was given idiot-proof instructions for how to set up a Git repository, configure it to push to AppHarbor, and do my first push.  Since I already initialized the repository, I skipped down to the “Add AppHarbor Repository and Push” section.

image

At that point, the site was deployed to AppHarbor and running in the cloud.  Browsing to http://xanderhandley.apphb.com/popcorn/ worked. I was close to finished; I just needed to set up the custom hostname.

Using a Custom Hostname

I wanted the site to have an easy URL, with http://xanderhandley.com/popcorn as the goal.  Needless to say, I already own xanderhandley.com, so I just needed to point it to AppHarbor.  This was another single-field form on AppHarbor’s site, reached by clicking on the “Hostnames” menu link.

image

Before hitting ‘Add hostname,’ I copied the IP address to my clipboard.  Then I went off to my DNS settings for the domain and configured an A record for the domain to point to that IP address.  After a few minutes of propagation, it worked.  http://xanderhandley.com/popcorn loads the site I created in WebMatrix just 30 minutes ago, and Xander’s now able to sell popcorn online!

Technorati Tags: ,,

author: Jeff Handley | posted @ Saturday, October 08, 2011 11:25 PM | Feedback (2)

RIA Services updates for //build


At //build, Dinesh Kulkarni from our team gave a presentation alongside Asad Khan from the OData team.  They showed RIA/JS consuming both a RIA Services DomainService as well as an OData feed from Azure.  Dinesh also showed RIA/JS working in the browser, in a Win8 Metro Style app, and on an iPhone using PhoneGap.  You can watch their talk on Channel 9.

To coincide with their talk, the RIA Services team published updates for RIA/JS:

  • Updated RIAServices.jQuery NuGet package
  • New RIAServices.jQuery.Sample NuGet package
  • WCF RIA Services Toolkit (September 2011)
  • New RIA/JS landing page: http://win8.ms/riajs

RIAServices.jQuery

We are favoring NuGet over the MSI for toolkit components, as we can update the packages independently and more quickly.

  • $.observable for data editing / change events – This is a prototype implementation of an observability pattern we’re working with the jQuery and jQuery UI teams on, following the pattern we’ve established with $.dataSource.
  • Query data from OData services – We’re now able to load / query / mash-up data from OData sources.
  • $.dataSource IntelliSense – You’ll now find detailed API guidance from the $.dataSource IntelliSense
  • Simpler packaging / JavaScript classes/namespaces – RIA/JS now only has a single JavaScript file: ria.js.  It contains classes like DataSource, DataContext, et al, and we’ve refined how we define classes and namespaces to follow common jQuery patterns.

RIAServices.jQuery.Sample

Following the lead of the Entity Framework team, we’ve created a NuGet package that adds sample code into your project for using RIA/JS.  This is a great way to temporarily add sample code into your project, learn from it, and then uninstall it once you’ve implemented your own scenarios.  Give it a try!

WCF RIA Services Toolkit (September 2011)

The Toolkit has been updated to have the same content as the RIAServices.jQuery package.  There were no other updates to the Toolkit in this release.  You can always find the latest toolkit here: http://jeffh.me/riatoolkit.

This Toolkit build works with both WCF RIA Services V1.0 SP1 and the SP2.

Version Compatibility

With all of these new releases flying all around, it’s hard to know what works where. Hopefully this helps:

Release Silverlight 4 Silverlight 5 RIA/JS Visual Studio 11 Developer Preview
WCF RIA Services V1.0 (RTM) Yes No No No
WCF RIA Services V1.0 SP1 Yes No No No
WCF RIA Services V1.0 SP2 RC Yes Yes Yes No
WCF RIA Services Toolkit (September 2011) Yes Yes Yes No

At the time of WCF RIA Services V1.0 SP2 RC, Visual Studio 11 is not yet supported.  This is mostly because of our design-time components, and we are working to get that in place to offer VS11 support as soon as possible--stay tuned for updates therein.

As you can see above, if you are building Silverlight 4 applications, you can use RIA Services V1.0 SP1 or SP2.  If you are developing Silverlight 5 applications though, you must use RIA Services V1.0 SP2.

Technorati Tags: ,,

author: Jeff Handley | posted @ Friday, September 16, 2011 6:07 PM | Feedback (4)

RIA Services Validation: Available on GitHub


I’ve gotten quite a few requests for the source code behind the RIA Services Validation blog post series.  I am pleased to announce that the code is now published on GitHub.

http://github.com/jeffhandley/RIAServicesValidation

Within that repository, you’ll find:

  1. RudeValidation.Web/Models – Server-side model classes
  2. RudeValidation.Web/Validators – Custom validators including CompareValidator, ConditionallyRequired, and DateValidator
  3. RudeValidation/Views – Sample screens showing the validation in action
  4. RudeValidation/ViewModels - Where you’ll find the sample ViewModel as well as the very simple ViewModelBase

If you’re not familiar with Git, don’t be intimidated.  You can easily view the code directly in your browser by starting out at http://github.com/jeffhandley/RIAServicesValidation and just clicking through the folders and files.  To download all of the source, just click the Downloads button, and click to download the Zip file.  As always when downloading a Zip from the tubes, be sure to Unblock the Zip file through the Properties window before you extract the contents.  Then, be sure to set the RudeValidation.Web project as the startup.  After that, you are welcome to do whatever you want with the source code included.

RIA Services Validation Recap

Over the course of a 10-post series, we learned about the standard validators, how to create different types of custom validators, how the validation attributes get propagated, how to perform common cross-field and cross-entity validation, and now we have a utility for importing model rules into ViewModel classes.  Here’s the full series:

    1. Standard Validators
    2. Custom Validation Methods
    3. Custom Reusable Validators
    4. Attribute Propagation
    5. Validation Triggers
    6. Cross-Field Validation
    7. Entity-Level Validation
    8. Providing ValidationContext
    9. Using ValidationContext (Cross-Entity Validation)
    10. ViewModel Validation with Entity Rules

author: Jeff Handley | posted @ Tuesday, September 06, 2011 11:50 PM | Feedback (2)

RIA Services Validation: ViewModel Validation with Entity Rules


For those of you familiar with the ViewModel (or MVVM) pattern, you are likely also familiar with a typical pain point regarding validation: you often need to duplicate your entity validation metadata onto your ViewModel classes.  This can lead to burdensome dual maintenance of your validation rules, and it can seem very frustrating that with the server to client metadata propagation that RIA Services offers, your ViewModel classes are left dangling out there for you to manage yourself.  In this post, I’ll illustrate a utility I created that allows a ViewModel to assume validation metadata from model classes or properties, eliminating the dual maintenance.

ModelPropertyValidator

Virtually every ViewModel will have properties that actually represent properties from your model.  Let’s use the meeting invitation model from earlier RIA Services Validation posts as an example.  My meeting object has a bunch of properties such as Title, Start, End, Location, MinimumAttendees, MaximumAttendees, and Details.  It’s easy to imagine a ViewModel that could be used for managing meetings where the UI would have a subset or superset of the meeting properties.  For each field on the screen that represents a meeting property, I should be able to indicate which model property is associated with each ViewModel property so that the model validation can be imported automatically.  This is where ModelPropertyValidator comes in.

Here’s a subset of the Meeting class with its data annotations in my Web project:

Code Snippet
  1. [CustomValidation(typeof(MeetingValidators), "PreventExpensiveMeetings")]
  2. [CustomValidation(typeof(MeetingValidators), "PreventDoubleBooking")]
  3. [MetadataType(typeof(Meeting.MeetingMetadata))]
  4. public partial class Meeting
  5. {
  6.     public class MeetingMetadata
  7.     {
  8.         [Key]
  9.         [Display(AutoGenerateField = false)]
  10.         public int MeetingId { get; set; }
  11.  
  12.         [Required]
  13.         [CustomValidation(typeof(MeetingValidators), "NoEarlyMeetings")]
  14.         [CompareValidator(CompareOperator.LessThan, "End",
  15.             ErrorMessage = "Meetings cannot result in time travel.")]
  16.         [DateValidator(DateValidatorType.Future)]
  17.         [Display(Order = 1)]
  18.         public DateTime Start { get; set; }
  19.  
  20.         [Required]
  21.         [CompareValidator(CompareOperator.GreaterThan, "Start",
  22.             ErrorMessage = "Meetings cannot result in time travel.")]
  23.         [Display(Order = 2)]
  24.         public DateTime End { get; set; }
  25.  
  26.         [Required]
  27.         [StringLength(80, MinimumLength = 5,
  28.             ErrorMessageResourceType = typeof(ValidationErrorResources),
  29.             ErrorMessageResourceName = "TitleStringLengthErrorMessage")]
  30.         // {0} must be at least {2} characters and no more than {1}.
  31.         [Display(Order = 0)]
  32.         public string Title { get; set; }

 

When a ViewModel is going to expose the Start property for a meeting, it would be a nightmare to have to keep all of the validation attributes in sync.  To alleviate this, I created an attribute called ModelPropertyValidatorAttribute that acts as a proxy from a ViewModel property to a Model property.  Here’s what it looks like to use it:

Code Snippet
  1. public class MeetingEntryViewModel : ViewModelBase
  2. {
  3.     private DateTime start;
  4.     private DateTime end;
  5.  
  6.     [ModelPropertyValidator(typeof(Meeting))]
  7.     public DateTime Start
  8.     {
  9.         get { return this.start; }
  10.         set
  11.         {
  12.             if (this.start != value)
  13.             {
  14.                 this.ValidateProperty("Start", value);
  15.                 this.start = value;
  16.                 this.RaisePropertyChanged("Start");
  17.             }
  18.         }
  19.     }
  20.  
  21.     [ModelPropertyValidator(typeof(Meeting))]
  22.     public DateTime End
  23.     {
  24.         get { return this.end; }
  25.         set
  26.         {
  27.             if (this.end != value)
  28.             {
  29.                 this.ValidateProperty("End", value);
  30.                 this.end = value;
  31.                 this.RaisePropertyChanged("End");
  32.             }
  33.         }
  34.     }
  35. }

 

With a simple attribute on each, the Start and End properties now import all of the validation attributes from their corresponding Model properties on the Meeting type; no more validation duplication!  When the property name is the same on the Model and the ViewModel, you can get away with only specifying the model type, but I also have an overload that accepts the model property name in case the property names differ.

ModelObjectValidator

Just as there are cases when you want a ViewModel property to import the validation attributes from a model property, there are also times when you want a ViewModel class to import a model’s object-level validation attributes.  That’s where ModelObjectValidatorAttribute comes in.  Very similarly to the ModelPropertyValidator, you just put the attribute on the ViewModel and specify the corresponding model type.

Code Snippet
  1. [ModelObjectValidator(typeof(Meeting))]
  2. public class MeetingEntryViewModel : ViewModelBase, IStartAndEnd
  3. {
  4.     private DateTime start;
  5.     private DateTime end;

 

That’s all there is to it.  Now the MeetingEntryViewModel class imports the object-level validation from the Meeting type.  You can even specify multiple ModelObjectValidator attributes if you want to import validation attributes from multiple classes.

How’d you do that?

Here’s how it works:

  1. ModelPropertyValidatorAttribute and ModelObjectValidatorAttribute both derive from ModelValidatorAttribute
  2. ModelValidatorAttribute is an abstract class that derives from ValidationAttribute
  3. ModelValidatorAttribute overrides IsValid and performs much of the same work that Validator performs, iterating over the attributes on the target and producing validation errors as ValidationResult instances
  4. If the ViewModel derives from the RIA Services ComplexObject class, then there’s a nice ValidationErrors property that can be used to add multiple error messages
  5. Otherwise, the first validation error is returned as the error message from this custom validator

Yes, I suggested that you make your ViewModel classes derive from ComplexObject.  ComplexObject is a really useful class and it exposes a great ValidationErrors collection that any consumer can manipulate, and all updates to that collection generate events from the INotifyDataErrorInfo interface.  This works perfectly for this scenario where I want my validation attribute to be able to push multiple validation results onto the target object, despite the fact that the IsValid method is only capable of returning a single error.

Here’s the full code for the ModelValidatorAttribute and the two derived forms.

Code Snippet
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel.DataAnnotations;
  4. using System.Linq;
  5. using System.Reflection;
  6. using System.ServiceModel.DomainServices.Client;
  7.  
  8. namespace RudeValidation.Helpers
  9. {
  10.     public class ModelPropertyValidatorAttribute : ModelValidatorAttribute
  11.     {
  12.         public ModelPropertyValidatorAttribute(Type modelType)
  13.             : base(modelType, ModelValidationMode.InferProperty) { }
  14.  
  15.         public ModelPropertyValidatorAttribute(Type modelType, string propertyName)
  16.             : base(modelType, propertyName) { }
  17.     }
  18.  
  19.     public class ModelObjectValidatorAttribute : ModelValidatorAttribute
  20.     {
  21.         public ModelObjectValidatorAttribute(Type modelType)
  22.             : base(modelType, ModelValidationMode.Object) { }
  23.     }
  24.  
  25.     [AttributeUsage(AttributeTargets.Property | AttributeTargets.Class | AttributeTargets.Parameter, AllowMultiple = true)]
  26.     public abstract class ModelValidatorAttribute : ValidationAttribute
  27.     {
  28.         public Type ModelType { get; private set; }
  29.         public ModelValidationMode ValidationMode { get; private set; }
  30.         public string ModelProperty { get; private set; }
  31.  
  32.         private object model;
  33.  
  34.         public ModelValidatorAttribute(Type modelType, ModelValidationMode validationMode)
  35.         {
  36.             this.ModelType = modelType;
  37.             this.ValidationMode = validationMode;
  38.         }
  39.  
  40.         public ModelValidatorAttribute(Type modelType, string modelPropertyName)
  41.         {
  42.             this.ModelType = modelType;
  43.             this.ValidationMode = Helpers.ModelValidationMode.SpecifiedProperty;
  44.             this.ModelProperty = modelPropertyName;
  45.         }
  46.  
  47.         protected override ValidationResult IsValid(object value, ValidationContext validationContext)
  48.         {
  49.             if (model == null)
  50.             {
  51.                 model = Activator.CreateInstance(this.ModelType);
  52.             }
  53.  
  54.             ValidationContext redirectedContext = new ValidationContext(model, validationContext, validationContext.Items);
  55.  
  56.             switch (this.ValidationMode)
  57.             {
  58.                 case ModelValidationMode.InferProperty:
  59.                     redirectedContext.MemberName = validationContext.MemberName;
  60.                     break;
  61.                 case ModelValidationMode.SpecifiedProperty:
  62.                     redirectedContext.MemberName = this.ModelProperty;
  63.                     break;
  64.                 case ModelValidationMode.Object:
  65.                     redirectedContext.MemberName = null;
  66.                     break;
  67.             }
  68.  
  69.             ComplexObject targetEntity = validationContext.ObjectInstance as ComplexObject;
  70.             var breakOnFirstError = (targetEntity == null);
  71.             IEnumerable<ValidationResult> validationResults = TryValidateProperty(value, validationContext, redirectedContext, breakOnFirstError);
  72.  
  73.             if (validationResults.Any())
  74.             {
  75.                 if (validationResults.Count() == 1)
  76.                 {
  77.                     return validationResults.Single();
  78.                 }
  79.  
  80.                 if (targetEntity != null)
  81.                 {
  82.                     foreach (ValidationResult result in validationResults.Skip(1))
  83.                     {
  84.                         targetEntity.ValidationErrors.Add(result);
  85.                     }
  86.                 }
  87.  
  88.                 return validationResults.First();
  89.             }
  90.  
  91.             return ValidationResult.Success;
  92.         }
  93.  
  94.         private static IEnumerable<ValidationResult> TryValidateProperty(object value, ValidationContext validationContext, ValidationContext modelValidationContext, bool breakOnFirstError)
  95.         {
  96.             ICustomAttributeProvider validatorProvider;
  97.  
  98.             if (!string.IsNullOrEmpty(modelValidationContext.MemberName))
  99.             {
  100.                 validatorProvider = modelValidationContext.ObjectType.GetProperty(modelValidationContext.MemberName);
  101.             }
  102.             else
  103.             {
  104.                 validatorProvider = modelValidationContext.ObjectType;
  105.             }
  106.  
  107.             IEnumerable<ValidationAttribute> validators = validatorProvider
  108.                 .GetCustomAttributes(typeof(ValidationAttribute), true)
  109.                 .Cast<ValidationAttribute>();
  110.  
  111.             IEnumerable<ValidationResult> results = GetValidationErrors(value, validationContext, validators, breakOnFirstError);
  112.             return results;
  113.         }
  114.  
  115.         private static IEnumerable<ValidationResult> GetValidationErrors(object value, ValidationContext validationContext, IEnumerable<ValidationAttribute> attributes, bool breakOnFirstError)
  116.         {
  117.             List<ValidationResult> errors = new List<ValidationResult>();
  118.             bool hasErrors = false;
  119.             ValidationResult result;
  120.  
  121.             foreach (RequiredAttribute required in attributes.OfType<RequiredAttribute>())
  122.             {
  123.                 result = required.GetValidationResult(value, validationContext);
  124.  
  125.                 if (result != ValidationResult.Success)
  126.                 {
  127.                     errors.Add(result);
  128.                     hasErrors = true;
  129.  
  130.                     if (breakOnFirstError)
  131.                     {
  132.                         return errors;
  133.                     }
  134.                 }
  135.             }
  136.  
  137.             if (hasErrors)
  138.             {
  139.                 return errors;
  140.             }
  141.  
  142.             foreach (ValidationAttribute attribute in attributes)
  143.             {
  144.                 if (attribute is RequiredAttribute)
  145.                 {
  146.                     continue;
  147.                 }
  148.  
  149.                 result = attribute.GetValidationResult(value, validationContext);
  150.  
  151.                 if (result != ValidationResult.Success)
  152.                 {
  153.                     errors.Add(result);
  154.                     hasErrors = true;
  155.  
  156.                     if (breakOnFirstError)
  157.                     {
  158.                         return errors;
  159.                     }
  160.                 }
  161.             }
  162.  
  163.             return errors;
  164.         }
  165.     }
  166.  
  167.     public enum ModelValidationMode
  168.     {
  169.         InferProperty,
  170.         SpecifiedProperty,
  171.         Object
  172.     }
  173. }

 

RIA Services Validation Recap

This was the 10th installment in a blog post series about RIA Services Validation.  We’ve learned about the standard validators, how to create different types of custom validators, how the validation attributes get propagated, how to perform common cross-field and cross-entity validation, and now we have a utility for importing model rules into ViewModel classes.  Here’s the full series:

    1. Standard Validators
    2. Custom Validation Methods
    3. Custom Reusable Validators
    4. Attribute Propagation
    5. Validation Triggers
    6. Cross-Field Validation
    7. Entity-Level Validation
    8. Providing ValidationContext
    9. Using ValidationContext (Cross-Entity Validation)
    10. ViewModel Validation with Entity Rules

The source code for everything shown during the series is available on GitHub.

author: Jeff Handley | posted @ Tuesday, September 06, 2011 11:31 PM | Feedback (5)