I swear I’ll eventually run out of crazy titles that include the names “Spark” and “Fubu”, but until then you’ll just have to take what you can get :)
In my last post, I talked about getting FubuMVC up and running with Spark as the view engine of choice. That dealt with all the bootstrapping stuff and getting a basic “hello world” example off the ground.
This time we’re digging a little deeper by bringing to bear the powerful 3-pass layout rendering system built into the Spark View Engine. I’m also going to talk about using Spark Partials, mainly showing that they will now work in conjunction with FubuMVC.
The goal
As I said last time, Spark is very opinionated, and if you work with those in mind, you can pretty much get a lot of complex wiring-up for free. The Spark documentation talks about 4 ways (conventions) in which a master layout can be defined and automatically picked up in Ascending order of importance (number 4 wins every time):
- Application.spark file in the Shared or Layouts folders
- A [ControllerName].spark in the Shared or Layouts folders (Pssst….the convention is to strip off “Controller”)
- Naming the Layout as an argument when returning the View – I’ve deliberately not added support for this method because I feel it’s a code smell. Disagree with me? Then just say so and tell me why, and I’ll think about it ;)
- Naming the master layout in the .spark file using the <use master=”MasterOfTheUniverse” /> syntax.
The goal here for me was to ensure that people familiar with using Spark on Castle MonoRail or ASP.NET MVC projects could pretty much hit the ground running when it came to working with their Views in terms of Master Layouts and Partial use.
FubuMVC has its own opinions on how partials and layouts are derived, but for now they are separate. I’m not quite up to speed yet on how it all works, but if there is a gap that Spark isn’t filling, then I’ll be making use of the Fubu way where it makes sense. For now though – if you’re coming from a Spark background, you should be able to use it very comfortably.
How do I use it?
Well, I’ve tried to make it as easy as possible. I’ve created a fork of my own from the main FubuMVC-examples repository where you’ll be able to see all the code I’ll show below. One day I hope to have this integrated into the main branch, but for now you’ll have to get it from my fork.
I’ve taken the liberty of compiling the latest dlls from the Spark integration work on my fork and put them in the lib folder. After creating a standard ASP.NET Web Application, You can reference them along with all the Fubu bits like so:
Project setup
Here are files we’ll be working with for this example:
From the top
Once you’ve done a few of these, it really does start to get easier. Start with the Global.asax which is as empty as ever given that I’ve following my pre-built conventions discussed in the last post:
namespace MasterAndPartialViews
{
public class Global : SparkStructureMapApplication
{
}
}
Next I like to go straight for the controller. For this example I chose a simple question-answer app that I can build out more and more as I find more things to write about. For now though, it’s only demonstrating this particular topic. Two methods on the controller – easy:
using MasterAndPartialViews.Models.Question;
namespace MasterAndPartialViews.Controllers
{
public class QuestionController
{
public QuestionListViewModel Ask()
{
return new QuestionListViewModel();
}
public AnswerViewModel Answer(QuestionInputModel model)
{
return new AnswerViewModel(model.Question);
}
}
}
The models I’ve used are also fairly simple, although at this point you could see them calling out to a Repository or Data Service somewhere:
using System.Collections.Generic;
namespace MasterAndPartialViews.Models.Question
{
public class QuestionListViewModel
{
public ListQuestions = new List
{
"What is the answer to life, the universe, and everything?",
"What is the first decimal place of Pi?"
};
}
}
namespace MasterAndPartialViews.Models.Question
{
public class QuestionInputModel
{
public string Question { get; set; }
}
}
namespace MasterAndPartialViews.Models.Question
{
public class AnswerViewModel
{
public AnswerViewModel(string question)
{
Answer = GetAnswer(question);
}
public string Answer { get; set; }
private string GetAnswer(string question)
{
switch (question)
{
case "What is the answer to life, the universe, and everything?":
return "42";
case "What is the first decimal place of Pi?":
return "1";
default:
return "I used to know that one. Hmm, let's see...";
}
}
}
}
At this stage, our code compiles, but we haven’t added any views. Let’s have a look at what the handy FubuMVC diagnostics are telling us so far – Hit F5:
We create the two Spark files (Ask.spark and Answer.spark) as per the controller method names and place them inside a folder (see project layout above) decided by conventions already configured. The files can be blank, but as long as they’re there, they’ll be picked up by the conventions. Hit F5 on the browser this time:
OK – now that we’ve established that FubuMVC has spotted our views and bound them to the correct Actions, we’re ready to start mucking about with the layouts and partials. First let’s take a look at the Ask.spark view:
<viewdata model="MasterAndPartialViews.Models.Question.QuestionListViewModel" />
<QuestionList questions="Model.Questions" />
<form action="Answer" method="POST">
<div>Ask your question:
<input id="SomeId" type="text" width="500" name="Question"/>
<input type="Submit"/>
</div>
</form>
You’ll notice I’ve specified that it is a strongly typed view using the <viewdata> Spark convention. Now the view has access to dig into the ViewModel.
Next you’ll notice an Html tag called QuestionList. “What the HELL is that ?!?!” You may ask…Well that’s our partial, and that fact that we’ve used the Spark convention of naming the partial (see project files above) starting with an underscore means that we can refer to it as a native Html tag and the vIew engine will pick it up as part of the rendering pipeline. We can also “feed” it part or all of the ViewModel coming in to help it construct itself. Here is the _QuestionList.spark in the flesh:
<!-- expects a list of questions as an input from anything that uses this partial -->
<div>
Here is the list of questions you can ask:
<ul>
<li each="var question in questions">${question}</li>
</ul>
</div>
The Answer.spark view is just a simple display that has a strongly typed ViewModel of its own:
<viewdata model="MasterAndPartialViews.Models.Question.AnswerViewModel" />
<div><h2>The answer is: ${Model.Answer}</h2></div>
<div><a href="/Question/Ask">Ask another...</a></div>
There you have it – partials for a start. Trust me, you can go so much further with them, and I plan on putting a few more complex samples together to demonstrate everything from separation of concerns for your views up to doughnut caching techniques for the more extreme optimizers among us. We’ve only just begun!
Mastering the Layouts
Following the conventions at the top of this post, and looking at my project layout above, you’ll see I have two layouts defined: Application.spark, and Question.spark. Given the order of preference rules at the top, the layout that the whole site with use is Application.spark, except for views that are tied to the Question Controller – all of which will use the Question.spark layout instead as an override. The two layouts look like this:
<html>
<head>
<use content="head" />
</head>
<body>
<div><------- I'm from the Application Layout Begin -------></div>
<use content="view" />
<div><------- I'm from the Application Layout End-------></div>
</body>
</html>
<html>
<head>
<use content="head" />
</head>
<body>
<div><------- I'm from the Question Layout Begin -------></div>
<use content="view" />
<div><------- I'm from the Question Layout End-------></div>
</body>
</html>
The output of the Ask view looks like this:
You can then employ override rule number 4 to the layouts by changing the Question.spark layout like so:
<use master="Application" />
<html>
<head>
<use content="head" />
</head>
<body>
<div><------- I'm from the Question Layout Begin -------></div>
<use content="view" />
<div><------- I'm from the Question Layout End-------></div>
</body>
</html>
And then you have an output that contains one layout wrapped within another like so:
Of course it doesn’t make sense to have two <html> elements etc nested inside one another, but you get the idea – you can stack layouts inside one another as they make sense to build up a page like Lego(TM) blocks.
The possibilities are endless here and I really think this post is getting a bit long. If there are specific scenarios you’d like to tackle, but can’t figure out how, then I’d be keen to put an example like that out – so let me know. Either way, I hope you find this useful…
What’s next?
As you’ll see from my code samples above, I’m using POHF – Plain Old Html Forms. Whilst this is not necessarily a bad thing, many of you will be used to HtmlHelpers if you’ve come from ASP.NET MVC background or perhaps some other for of fluent html tag building or convention based mechanism. Well as it turns out, Jeremy Miller been working on just such a mechanism for FubuMVC – but alas, this only works with the standard Webforms View Engine code at the moment.
I see this as the area can possibly provide another piece of integration, and I’m probably gonna focus on that next. If you disagree, or have a particular show-stopper that’s preventing you from using the Spark View Engine on FubuMVC, then by all means get my attention and I’ll see what I can do.
That goes for you too Jeremy, Chad, Josh, Mark et al :) Let me know where you think I should focus regarding this work…
Conclusion
I haven’t been able to test everything regarding partials and master layouts, but I’m hoping that with just a few people using it, they can discover if I’ve left anything out and shout for help. I’d be very keen to plug any holes if they’re found, but for the most part, I’ve been using it to put something together the way I have using ASP.NET MVC before and I’ve pretty much plugged all the holes for my personal use – now go find me some more! ;)
Until next time…
RobertTheGrey
