Creating Composite View Components In MonoRail / Refactoring Exercise
In a previous post, I showed some examples of how to create custom view components in MonoRail and touched on the ability to create composite view components. Since then I’ve refactored towards the use of interfaces which I think is a cleaner approach, so I thought I’d share.
First, let’s review the code that I want to improve upon:
1: private void ShowSearchButton()
2: {
3: LinkSubmitButtonComponent linkButton = new LinkSubmitButtonComponent();
4: linkButton.Init(RailsContext, Context);
5: linkButton.Context.ComponentParameters.Add("linkText", "Search");
6: linkButton.Context.ComponentParameters.Add("formToSubmit", searchFormName);
7: linkButton.Initialize();
8: linkButton.Render();
9:
10: string html = string.Empty;
11: linkButton.Context.Writer.Write(html);
12: RenderText(html);
13: }
Having this logic inside of the SearchForm component pretty clearly violates SRP and is just plain ugly. So coming at this from a TDD-like approach, I write what I want to be able to do in this method.
1: private void ShowSearchButton()
2: {
3: RenderComponent(new LinkSubmitButtonComponent(), "linkText=Search",
4: string.Format("formToSubmit={0}", searchFormName));
5: }
Notice the design decisions I’ve made in this one statement. I’ve said that I want to call a new method named RenderComponent which will take in an instance of a view component and a string array of key/value pairs as component parameters to use when rendering this view component to html.
(Side Note: The Castle MonoRail framework provides a great little utility class named DictHelper for converting an array of string key/value pairs (“key=value”) to a an IDictionary. Very handy for situations like this. More on that in a sec.)
For now, I’ll just add a new private method in this same class named RenderComponent and move the logic needed to render the LinkSubmitButton view component into this new private method.
1: private void RenderComponent(ViewComponent viewComponent, params string[] componentParams)
2: {
3: viewComponent.Init(RailsContext, Context);
4: foreach (DictionaryEntry dictionaryEntry in DictHelper.Create(componentParams))
5: {
6: viewComponent.Context.ComponentParameters.Add(dictionaryEntry.Key, dictionaryEntry.Value);
7: }
8:
9: viewComponent.Initialize();
10: viewComponent.Render();
11:
12: string html = string.Empty;
13: viewComponent.Context.Writer.Write(html);
14: RenderText(html);
15: }
But this logic for rendering the LinkSubmitButton to html is still sitting in my SearchForm class. Also notice how nothing in this method is directly using our LinkSubmitButton component, just any class that inherits from ViewComponent. I smell some opportunities for reuse here; more on that in a sec.
This may be jumping a little ahead, but I want to go ahead and create a common interface for my view components. Unfortunately, no common interface already exists in the MonoRail framework, but it’s easy enough to create yourself. Just extract an interface based on the ViewComponent class.
1: public interface IViewComponent
2: {
3: void Init(IRailsEngineContext railsContext, IViewComponentContext context);
4: void Initialize();
5: void Render();
6: bool SupportsSection(string name);
7: IViewComponentContext Context { get; }
8: }
I then changed the two view components I’m working with to implement my new custom interface:
1: public class SearchFormComponent : ViewComponent, IViewComponent
2: ...
3: public class LinkSubmitButtonComponent : ViewComponent, IViewComponent
4: ...
Now I’ll perform a “Pull Members Up” refactoring on the private method named RenderComponent in our SearchForm component to place that method on our newly created_ _IViewComponent interface which will of course change the method to be public.
1: public interface IViewComponent
2: {
3: void Init(IRailsEngineContext railsContext, IViewComponentContext context);
4: void Initialize();
5: void Render();
6: bool SupportsSection(string name);
7: IViewComponentContext Context { get; }
8: void RenderComponent(IViewComponent component, params string[] componentParams);
9: }
(One minor change I’ve also made is the RenderComponent method now accepts the interface IViewComponent instead of MonoRail’s ViewComponent class. This is just because I always like to program to interfaces when at all possible.)
Ok, all seems good except the fact that our LinkSubmitButton component won’t compile because it doesn’t implement the RenderComponent method on the IViewComponent interface. Remember when I mentioned that it appears that all of the logic in that method is completely generic and can probably easily be reused? Well, instead of just duplicating this method in the LinkSubmitButton class, I’m going to perform an “Extract Superclass” refactoring to create a base class for my view components and then another “Pull Members Up” refactoring to move the RenderComponent method up to our new base class as shown below:
1: public class BaseViewComponent : ViewComponent, IViewComponent
2: {
3: public void RenderComponent(IViewComponent component, params string[] componentParams)
4: {
5: component.Init(RailsContext, Context);
6: foreach (DictionaryEntry dictionaryEntry in DictHelper.Create(componentParams))
7: {
8: component.Context.ComponentParameters.Add(dictionaryEntry.Key, dictionaryEntry.Value);
9: }
10:
11: component.Initialize();
12: component.Render();
13:
14: string html = string.Empty;
15: component.Context.Writer.Write(html);
16:
17: RenderText(html);
18: }
19: }
Then we’ll change our two classes LinkSubmitButton and SearchForm to inherit from this new base class:
1: public class LinkSubmitButtonComponent : BaseViewComponent, IViewComponent
2: ..
3: public class SearchFormComponent : BaseViewComponent, IViewComponent
4: ..
Now our rendering logic for the LinkSubmitButton in our SearchForm composite view component is simplified to:
1: private void ShowSearchButton()
2: {
3: RenderComponent(new LinkSubmitButtonComponent(), "linkText=Search",
4: string.Format("formToSubmit={0}", searchFormName));
5: }
And we have the added benefit of reusability on our rendering logic making future composite view components easy to create. Note: This steps shown in this post were actually performed in a matter of minutes, so it was a very quick refactoring. Writing up this post took considerably longer than the work itself, as is usually the case.
As usual you can find the full source code here.
Anyways, hope this can be of some use to those utilizing this great MVC framework we call MonoRail. 🙂