Disclaimer: let me start by saying that the technique described in this blog is experimental, and is meant as a first step to see where this might take us. This is not in any way an officially supported technique!
If you are an ASP.NET user, you are likely aware that there are two different types of apps that you can create: Web Sites and Web Applications. Here is a quick summary of how they differ:
Web Sites
In web sites, all compilation is done at runtime rather than design time. They don’t use any VS project systems, and msbuild is never involved.
Advantages: very dynamic. You can just FTP files to the server, and everything just works. In that sense, it’s similar to ASP Classic and PHP.
Disadvantages: lack of fine control over the build process; hard to unit test; often slower in VS; not available for MVC.
Web Applications
In Web Applications, all the source code is built by VS in the designer using a standard .csproj file and msbuild. Pages and views (.aspx, .cshtml, …) are still built dynamically at runtime, so it’s sort of a mixed mode model.
Advantages: full power of msbuild, easy to unit test code, fast build in VS.
Disadvantages: once you xcopy your built app to the server, you can’t modify the code by just changing files (though you can still do this for pages & views).
What if we could get the best of both worlds?
I was recently chatting with my coworker Louis DeJardin about compilation models, and he put out the idea that we might get something interesting if we were to run msbuild on the server, which is where this came from.
In a sense, it’s sort of an ‘obvious’ thing to try if you look at the Pros can Cons of Web Sites and Web Applications. We want the full power of msbuild, but we also want the more dynamic nature of Web Sites, so the only logical thing to do is to run msbuild dynamically on the server!
Try it now using NuGet!
Before I give you more details, let me show you how you can try this in no time via NuGet:
- Create a new MVC app
- Install my ‘WebAppBuilder’ NuGet package
- Run the app
- Change the message in Controllers\HomeController.cs, and don’t rebuild
- Refresh the page in the browser (and then again per the message you’ll get)
- Now try to make a change with a compile error and refresh again
How does it all work?
There really isn’t much code at all to make this work. First, it uses the technique I described in my previous post to dynamically register a module. This is what allows it to kick in without any registration.
Whenever the appdomain starts, the module looks for the csproj file and builds it. Doing this is quite simple since msbuild is well exposed to managed code (take a look at Microsoft.Build.Execution.BuildManager). Note that it always does that on startup, with the assumption that the incremental build will be super fast if there is nothing to build.
Then if something actually got built, it sends back a simple page telling the user to refresh. This is a bit ugly as it effectively takes two refreshes to get the result, but it’s necessary since we can’t use the freshly built assembly in the same domain used to build it (since creating it causes a domain unload).
The other thing it does is listen to file change notification so it can unload the domain if any source files change. Then on the next request things get built as above.
There may be smarter ways of doing this, but this works pretty well as a proof of concept.
You can see find code on bitbucket.
Caveat: requires full trust
One big caveat of this approach is that it doesn’t work in partial trust, because launching msbuild requires full trust. This is not something that I think can be worked around easily, so I’d say it’s an inherent limitation.
Where can we take this?
Well, I’m not really sure yet, but it is certainly interesting to think about the possibilities of using this type of build model in ASP.NET.
Let me know if you think this is crazy or may have potential :)
hmm, a tad scary if it means making dynamic changes against say production. if not i guess it gives you a bit of of a web site feel in a web app.
ReplyDelete@John: clearly, you wouldn't do this on a production server any more than you would change files in App_Code, unless you have fully tested the same changes elsewhere first.
ReplyDeleteReally usefull for development environment.
ReplyDeleteApp_Code is also an option.
ReplyDelete@Imran: App_Code is what's used for Web Sites, and it doesn't have any of the benefits of web applications that I listed below. The whole idea is to be able to compile sources at runtime in a way that works better than App_Code.
ReplyDelete@David
ReplyDeleteI used App_Code in my ASP.NET MVC application for classes which needs to be dynamic and it worked perfectly.
I remembered that Levi Broderick give me the idea for using App_Code in MVC for dynamic code which was necessary for me.
Any way it's seems to be very nice in comparison with App_Code.
Yes, in some cases, using App_Code in MVC can make sense. But the point is that for things like controllers, you rarely want to do that because you lose testability. By using this (experimental) technique, you sort of get the best of both worlds.
ReplyDeleteDavid this is awesome! Do you think the rebuild could overwrite the /bin dlls via shadow copy so you wouldn't need the refresh page?
ReplyDelete@Harry: I'm not sure that would help. The problem is that by the time I build the assembly, the app domain has already gone too far in its cycle to actually use it. I have experimented with doing a client redirect, but couldn't get it to work properly. Feel free to try different things though! The code is on bitbucket (link above).
ReplyDeleteI really like the idea of this - makes Chromebook+cloud9ide a decent possibility :D
ReplyDeleteAs for the redirect - might work if you just add a meta-refresh tag to the "please refresh" page. Might give it a try if I get some time!
I was thinking of ditching my blog and going with Blogger recently.. but after seeing "blog" against my name again, I think I'll avoid it a little longer :(
ReplyDelete@dantup: sure, give that a try! As for blogger showing 'blog', I know that's a pain. I'm surprised they haven't fixed that...
ReplyDeleteI see I can create a view in an existing controller, and add it as an action result in existing view and your dll works fine.
ReplyDeleteBut, if I wanted to created a new controller, or rename an existing controller (yes I renamed the view folder name to match the new controller name), your app does nto work. Any ideas?
@Ed N: I would think it should work in that case too, since it rebuilds the whole assembly. If you have a specific repro where it doesn't work, please file a bug on https://bitbucket.org/davidebbo/webappbuilder.
ReplyDelete