Monday, 29 December 2014

Deploying from ASP.Net MVC to GitHub Pages using AppVeyor part 1

There's a small open source project I'm responsible for called jQuery Validation Unobtrusive Native. (A catchy name is a must for any good open source project. Alas I'm not quite meeting my own exacting standards on this particular point... I should have gone with my gut and called it "Livingstone" instead. Too late now...)

The project itself is fairly simple in purpose. It's essentially a bridge between ASP.Net MVC's inbuilt support for driving validation from data attributes and jQuery Validation's native support for the same. It is, in the end, a collection of ASP.Net MVC HTML helper extensions. It is not massively complicated.

I believe it was Tony Blair that said "documentation, documentation, documentation" were his priorities for open source projects. Or maybe it was someone else... Anyway, the point stands. Documentation is supremely important if you want your project to be in any way useful to anyone other than yourself. A project, no matter how fantastic, which lacks decent documentation is a missed opportunity.

Anyway I'm happy to say that jQuery Validation Unobtrusive Native has documentation! And pretty good documentation at that. The documentation takes the form of the jVUNDemo project which is part of the jQuery Validation Unobtrusive Native repo. jVUNDemo is an ASP.Net MVC web application which is built on top of the jQuery Validation Unobtrusive Native helpers. It demonstrates the helpers in action and documents how you might go about using them. It looks a bit like this:

When I first put jVUNDemo together I hosted it on Azure so the world could see it in all it's finery. And that worked just fine. However, there's something you ought to know about me:

I'm quite cheap

No really, I am. My attention was grabbed by the essentially "free" nature of GitHub Pages. I was immediately seized by the desire to somehow deploy jVUNDemo to GitHub Pages and roll around joyfully in all that lovely free hosting.

"But", I hear you cry, "you can't deploy an ASP.Net MVC application to Git Hub Pages... It only hosts static sites!" Quite right. Sort of. This is where I get to pull my ace of spades: jVUNDemo is not really an "app" so much as a static site. Yup, once the HTML that makes up each page is generated there isn't any app like business to do. It's just a collection of text and 1 screen demo's really.

That being the case, there's no reason why I shouldn't be able to make use of GitHub Pages. So that's what I decided to do. Whilst I was at it I also wanted to automate the deployment process. When I tweak jVUNDemo I don't want to have to manually push out a new version of jVUNDemo to wherever it's being hosted. No, I'm a developer so I'll automate it.

I've broken this up into 2 posts. This first one will cover how you generate a static site from an ASP.Net MVC site. The second will be about how to use AppVeyor to ensure that on each build your documentation is getting republished.

You Wget me?

So, static site generation. There's a well known Unix utility called Wget which covers exactly that ground and so we're going to use it. It downloads and saves HTML, it recursively walks the links inside the site and grabs those pages too and it converts our routes so they are locally browsable ("Demo/Required" becomes "Demo/Required.html").

You can use Chocolatey to get a copy of Wget. We're also going to need IIS Express to host jVUNDemo whilst Wget converts it. Once jVUNDemo has been built successfully you should be be able to kick off the process like so:

cd C:\projects\jquery-validation-unobtrusive-native
.\makeStatic.ps1 $pwd.path

This invokes the makeStatic Powershell script in the root of the jQuery Validation Unobtrusive Native repo:

param([string]$buildFolder)

$jVUNDemo = "$($buildFolder)\jVUNDemo"
$staticSiteParentPath = (get-item $buildFolder).Parent.FullName
$staticSite = "static-site"
$staticSitePath = "$($staticSiteParentPath)\$($staticSite)"
$wgetLogPath = "$($staticSiteParentPath)\wget.log"
$port = 57612
$servedAt = "http://localhost:$($port)/"
write-host "jVUNDemo location: $jVUNDemo"
write-host "static site parent location: $staticSiteParentPath"
write-host "static site location: $staticSitePath"
write-host "wget log path: $wgetLogPath"

write-host "Spin up jVUNDemo site at $($servedAt)"
$process = Start-Process 'C:\Program Files (x86)\IIS Express\iisexpress.exe' -NoNewWindow -ArgumentList "/path:$($jVUNDemo) /port:$($port)"

write-host "Wait a moment for IIS to startup"
Start-sleep -s 15

if (Test-Path $staticSitePath) { 
    write-host "Removing $($staticSitePath)..."
    Remove-Item -path $staticSitePath -Recurse -Force
}

write-host "Create static version of demo site here: $($staticSitePath)"
Push-Location $staticSiteParentPath
# 2>&1 used to combine stderr and stdout
wget.exe --recursive --convert-links -E --directory-prefix=$staticSite --no-host-directories $servedAt > $wgetLogPath 2>&1
write-host "lastExitCode: $($lastExitCode)"
cat $wgetLogPath
Pop-Location

write-host "Shut down jVUNDemo site"
stop-process -Name iisexpress

if (Test-Path $staticSitePath) { 
    write-host "Contents of $($staticSitePath)"
    ls $staticSitePath
}

The above Powershell does the following:

  1. Starts up IIS Express serving jVUNDemo on http://localhost:57612/
  2. Waits 15 seconds for IIS Express to get itself together (probably a shorter wait time would be sufficient)
  3. Points Wget at jVUNDemo and bellows "go!!!!"
  4. Wget downloads and saves the static version of jVUNDemo to a directory called "static-site"
  5. Stops IIS Express
  6. Prints out the contents of the "static-site" directory

When run, it pumps something like this out:

jVUNDemo location: C:\projects\jquery-validation-unobtrusive-native\jVUNDemo 
static site parent location: C:\projects 
static site location: C:\projects\static-site 
wget log path: C:\projects\wget.log 
Spin up jVUNDemo site at http://localhost:57612/ 
Wait a moment for IIS to startup 
Create static version of demo site here: C:\projects\static-site 
wget.exe : --2014-12-29 07:49:56--  http://localhost:57612/
Resolving localhost... 
127.0.0.1
Connecting to localhost|127.0.0.1|:57612... connected.
HTTP request sent, awaiting response... 
200 OK

..... lots of HTTP requests.....

Downloaded: 30 files, 1.0M in 0.09s (10.8 MB/s)
Converting static-site/Demo/CreditCard.html... 34-0
Converting static-site/Demo/Number.html... 34-0
Converting static-site/Demo/Range.html... 34-0
Converting static-site/Demo/Email.html... 34-0
Converting static-site/AdvancedDemo/CustomValidation.html... 35-0
Converting static-site/Demo/Date.html... 36-0
Converting static-site/Home/License.html... 27-0
Converting static-site/index.html... 29-0
Converting static-site/AdvancedDemo/Dynamic.html... 35-0
Converting static-site/Demo/MaxLengthMinLength.html... 34-0
Converting static-site/Demo/Required.html... 34-0
Converting static-site/AdvancedDemo.html... 32-0
Converting static-site/Demo/Remote.html... 35-0
Converting static-site/Demo/EqualTo.html... 34-0
Converting static-site/AdvancedDemo/Globalize.html... 38-0
Converting static-site/Demo/Url.html... 34-0
Converting static-site/Demo.html... 37-0
Converting static-site/Home/GettingStarted.html... 29-0
Converting static-site/Home/Download.html... 27-0
Converting static-site/AdvancedDemo/Tooltip.html... 34-0
Converted 20 files in 0.03 seconds.
 
Shut down jVUNDemo site 
Contents of C:\projects\static-site 
 
 
    Directory: C:\projects\static-site
 
 
Mode                LastWriteTime     Length Name
----                -------------     ------ ----
d----        12/29/2014   7:50 AM            AdvancedDemo
d----        12/29/2014   7:50 AM            Content
d----        12/29/2014   7:50 AM            Demo
d----        12/29/2014   7:50 AM            Home
d----        12/29/2014   7:50 AM            Scripts
-a---        12/29/2014   7:50 AM       5967 AdvancedDemo.html
-a---        12/29/2014   7:50 AM       6802 Demo.html
-a---        12/29/2014   7:47 AM      12862 favicon.ico
-a---        12/29/2014   7:50 AM       8069 index.html

And that's it for part 1 my friends! You now have a static version of the ASP.Net MVC site to dazzle the world with. I should say for the purposes of full disclosure that there are 2 pages in the site which are not entirely "static" friendly. For these 2 pages I've put messages in that are displayed when the page is served up in a static format explaining the limitations. Their full glory can still be experienced by cloning the project and running locally.

Next time we'll take the mechanism detailed above and plug it into AppVeyor for some Continuous Integration happiness.

Friday, 12 December 2014

Gulp, npm, long paths and Visual Studio.... Fight!

How I managed to gulp-angular-templatecache working inside Visual Studio

Every now and then something bites you unexpectedly. After a certain amount of pain, the answer comes to you and you know you want to save others from falling into the same deathtrap.

There I was minding my own business and having a play with a Gulp plugin called gulp-angular-templatecache. If you're not aware of it, it "Concatenates and registers AngularJS templates in the $templateCache". I was planning to use it so that all the views in an Angular app of mine were loaded up-front rather than on demand. (It's a first step in making an "offline-first" version of that particular app.)

I digress already. No sooner had I tapped in:

npm install gulp-angular-templatecache --saveDev

Then I noticed my Visual Studio project was no longer compiling. It was dying a death on build with this error:

ASPNETCOMPILER : error ASPRUNTIME: The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters.

I was dimly aware that there were issues with the nested node_modules leading to Windows-killing paths. This sounded just like that.... And it was! gulp-angular-templatecache had a dependency on gulp-footer which had a dependency on lodash.assign which had a dependency on lodash._basecreatecallback which had.... You see where I'm going? It seems that the lovely lodash has created the path from hell.

For reasons that aren't particularly clear this kills Visual Studio's build process. This is slightly surprising given that our rogue path is sat in the node_modules directory which isn't part of the project in Visual Studio. That being the case you'd imagine that you could do what you liked there. But no, it seems VS is a delicate flower and we must be careful not to offend. Strange.

It's Workaround Time!

After a great deal of digging I found the answer nestled in the middle of an answer on Stack Overflow. To quote:

If you will add "lodash.bind" module to your project's package.json as dependency it will be installed in one level with gulp and not as gulp's dependency

That's right, I just needed to tap enter this at the root of my project:

npm install lodash.bind --saveDev

And all was sweetness and light once more - no more complaints from VS.

The Future

It looks like lodash are on course to address this issue. So one day this this workaround won't be necessary anymore which is good.

However, the general long path issue concerning node / npm hasn't vanished for Windows users. Given VS 2015 is planning to make Gulp and Grunt 1st class citizens of Visual Studio I'm going to guess that sort of issue is likely to arise again and again for other packages. I'm hoping that means that someone will actually fix the underlying path issues that upset Windows with npm.

It sounds like npm are planning to take some steps which is great. But I can't be alone in having a slightly nagging feeling that Windows isn't quite a first class citizen for node / io / npm yet. I really hope it will become one.

Friday, 5 December 2014

What's in a (Domain) Name?

The observant amongst you may have noticed that this blog has a brand new and shiny domain name! That's right, after happily trading under "icanmakethiswork.blogspot.com" for the longest time it's now "blog.icanmakethiswork.io". Trumpets and fanfare!

Why the change? Well let's break that question down a little. First of all, why change at all? Secondly, why change to blog.icanmakethiswork.io?

Why do things have to change at all?

I mean, weren't we happy? Wasn't it all good? Well quite. For the record, I have no complaints of Blogger who have hosted my blog since it began. They've provided good tools and a good service and I'm happy with them.

That said, I've been toying with the idea for a while now of trying out a few other blogging solutions - possibly even hosting it myself. Whilst my plans are far from definite at the moment I'm aware that I don't own icanmakethiswork.blogspot.com - I can't take it with me. So if I want to make a move to change my blogging solution a first step is establishing my own domain name for my blog. I've done that now. If and when I up sticks, people will hopefully come with me as the URL for my blog should not change.

Also, in the back of my mind I'm aware that Google owns Blogger. Given their recent spate of closing services it's certainly possible that the Google reaper could one day call for Blogger. So it makes sense to be ready to move my blog elsewhere should that day come.

Why blog.icanmakethiswork.io?

Why indeed? And why the ".io" suffix? Doesn't that just make you a desperate follower of fashion?

Good questions all, and "no, I hope not". My original plans were to use the domain name "icanmakethiswork.com". icanmakethiswork was the name of the blog and it made sense to keep it in the URL. So off I went to register the domain name when to my surprise I discovered this:

My domain is being cybersquatted! I mean.... What??!!!!

I started to wonder "is there another icanmakethiswork out there"? Am I not the one and only? So I checked with DuckDuckGo ("The search engine that doesn't track you.") and look what I found:

A whole screen of me. Just me.

As of June 3rd 2014 someone has been sitting on my blog name. I was actually rather outraged by this. I became even more so as I discovered that there was a mechanism (not free) by which I could try and buy it off the squatter. I could instead be like my life idol Madonna and go to court to get it back. But frankly in this sense I'm more like Rachel Green in Friends; not litigous.

So that's why I went for icanmakethiswork.io instead. Path of least resistance and all that. I'd still like icanmakethiswork.com to be mine but I'm not going to court and I'm not paying the squatter. Maybe one day I'll get it. Who knows?

Either way, from now on this is blog.icanmakethiswork.io - please stick around!

Is anything else going to change?

Not for now, no.