setting all protected methods on WizardStepPage as virtual
[castle.git] / MonoRail / Castle.MonoRail.Framework / ViewComponent.cs
blob7df942da24a7c48ce26d19aa9a9bf525610d3ed1
1 // Copyright 2004-2008 Castle Project - http://www.castleproject.org/
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 namespace Castle.MonoRail.Framework
17 using System;
18 using System.Collections.Specialized;
19 using System.IO;
20 using System.Collections;
21 using System.Reflection;
22 using System.Web;
23 using Castle.Components.Binder;
25 /// <summary>
26 /// Base class for reusable UI Components.
27 /// </summary>
28 /// <remarks>
29 /// Implementors should override <see cref="ViewComponent.Initialize"/>
30 /// for implement proper initialization (if necessary).
31 /// Also implement <see cref="ViewComponent.Render"/> as by default it
32 /// will render a <c>default</c> view on <c>[ViewFolderRoot]/components/[componentname]</c>.
33 /// <para>
34 /// You can also override <see cref="ViewComponent.SupportsSection"/> if your component supports
35 /// neste sections (ie templates provided on the view that uses the view component.
36 /// </para>
37 /// <para>
38 /// Another way is to use the <see cref="ViewComponentDetailsAttribute"/> to specify a custom name
39 /// and the sections supported.
40 /// </para>
41 /// <para>
42 /// Notice that view components can render strings or views on their folder. You can
43 /// create sophisticate components with that mixture. Sections allow the users of your component
44 /// to give it a block of content, usually customizing or definiting the content to be especially
45 /// rendered by your component.
46 /// </para>
47 ///
48 /// <example>
49 /// A very simplist view component that renders the time.
50 /// <code>
51 /// public class ShowTime : ViewComponent
52 /// {
53 /// public override void Initialize()
54 /// {
55 /// }
56 ///
57 /// public override void Render()
58 /// {
59 /// RenderText("Time: " + DateTime.Now.ToString());
60 /// }
61 /// }
62 /// </code>
63 /// <para>
64 /// This can be used from the view using the following syntax (NVelocity view engine)
65 /// </para>
66 /// <code>
67 /// #component(ShowTime)
68 /// </code>
69 /// </example>
70 /// </remarks>
71 public abstract class ViewComponent
73 /// <summary>
74 /// Holds the component context
75 /// </summary>
76 private IViewComponentContext context;
78 /// <summary>
79 /// Holds the <see cref="IEngineContext"/> associated
80 /// to the request lifetime.
81 /// </summary>
82 private IEngineContext engineContext;
84 /// <summary>
85 /// Holds a reference to the <see cref="ViewComponentDetailsAttribute"/> if any.
86 /// </summary>
87 private readonly ViewComponentDetailsAttribute detailsAtt;
89 /// <summary>
90 /// Initializes a new instance of the ViewComponent class.
91 /// </summary>
92 public ViewComponent()
94 object[] attributes = GetType().GetCustomAttributes(typeof (ViewComponentDetailsAttribute), true);
96 if (attributes.Length != 0)
98 detailsAtt = attributes[0] as ViewComponentDetailsAttribute;
102 #region "Internal" core methods
104 /// <summary>
105 /// Invoked by the framework.
106 /// </summary>
107 /// <param name="context">Request context</param>
108 /// <param name="componentContext">ViewComponent context</param>
109 public void Init(IEngineContext context, IViewComponentContext componentContext)
111 engineContext = context;
112 this.context = componentContext;
114 BindComponentParameters();
116 Initialize();
119 /// <summary>
120 /// Binds the component parameters.
121 /// </summary>
122 private void BindComponentParameters()
124 IConverter converter = new DefaultConverter();
126 PropertyInfo[] properties = GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
128 foreach(PropertyInfo property in properties)
130 if (!property.CanWrite) continue;
132 object[] attributes = property.GetCustomAttributes(typeof (ViewComponentParamAttribute), true);
134 if (attributes.Length == 1)
136 BindParameter((ViewComponentParamAttribute) attributes[0], property, converter);
141 private void BindParameter(ViewComponentParamAttribute paramAtt, PropertyInfo property, IConverter converter)
143 string compParamKey = string.IsNullOrEmpty(paramAtt.ParamName) ? property.Name : paramAtt.ParamName;
145 object value = ComponentParams[compParamKey] ?? paramAtt.Default;
147 if (value == null)
149 if (paramAtt.Required &&
150 (property.PropertyType.IsValueType || property.GetValue(this, null) == null))
152 throw new ViewComponentException(string.Format("The parameter '{0}' is required by " +
153 "the ViewComponent {1} but was not passed or had a null value",
154 compParamKey, GetType().Name));
157 else
161 bool succeeded;
163 object converted = converter.Convert(property.PropertyType, value.GetType(), value, out succeeded);
165 if (succeeded)
167 property.SetValue(this, converted, null);
169 else
171 throw new Exception("Could not convert '" + value + "' to type " + property.PropertyType);
174 catch (Exception ex)
176 throw new ViewComponentException(string.Format("Error trying to set value for parameter '{0}' " +
177 "on ViewComponent {1}: {2}", compParamKey, GetType().Name,
178 ex.Message), ex);
183 #endregion
185 #region Lifecycle methods (overridables)
187 /// <summary>
188 /// Called by the framework once the component instance
189 /// is initialized
190 /// </summary>
191 public virtual void Initialize()
195 /// <summary>
196 /// Called by the framework so the component can
197 /// render its content
198 /// </summary>
199 public virtual void Render()
201 RenderView("default");
204 /// <summary>
205 /// Implementor should return true only if the
206 /// <c>name</c> is a known section the view component
207 /// supports.
208 /// </summary>
209 /// <remarks>In general, this should not be implemented in a derived class. <see cref="ViewComponentDetailsAttribute"/>
210 /// should be used to indicate allowed section names instead.
211 /// </remarks>
212 /// <param name="name">section being added</param>
213 /// <returns><see langword="true"/> if section is supported</returns>
214 public virtual bool SupportsSection(string name)
216 if (detailsAtt != null)
218 return detailsAtt.SupportsSection(name);
220 else
222 return false;
226 #endregion
228 #region Useful properties
230 /// <summary>
231 /// Gets the Component Context
232 /// </summary>
233 public IViewComponentContext Context
235 get { return context; }
238 /// <summary>
239 /// Gets the <see cref="IEngineContext"/>
240 /// associated with the current request
241 /// </summary>
242 protected IEngineContext EngineContext
244 get { return engineContext; }
247 /// <summary>
248 /// Gets the component parameters
249 /// </summary>
250 protected IDictionary ComponentParams
252 get { return context.ComponentParameters; }
255 /// <summary>
256 /// Gets the Session dictionary.
257 /// </summary>
258 protected IDictionary Session
260 get { return engineContext.Session; }
263 /// <summary>
264 /// Gets a dictionary of volative items.
265 /// Ideal for showing success and failures messages.
266 /// </summary>
267 protected Flash Flash
269 get { return engineContext.Flash; }
272 /// <summary>
273 /// Gets the web context of ASP.NET API.
274 /// </summary>
275 protected HttpContext HttpContext
277 get { return engineContext.UnderlyingContext; }
280 /// <summary>
281 /// Gets the request object.
282 /// </summary>
283 protected IRequest Request
285 get { return engineContext.Request; }
288 /// <summary>
289 /// Gets the response object.
290 /// </summary>
291 protected IResponse Response
293 get { return engineContext.Response; }
296 /// <summary>
297 /// Provides a way to make data available
298 /// to the view that the component uses
299 /// </summary>
300 protected IDictionary PropertyBag
302 get { return context.ContextVars; }
305 /// <summary>
306 /// Shortcut to Request.Params
307 /// </summary>
308 protected NameValueCollection Params
310 get { return Request.Params; }
313 #endregion
315 #region Useful operations
317 /// <summary>
318 /// Specifies the view to be processed after the component has finished its processing.
319 /// </summary>
320 protected void RenderView(String name)
322 context.ViewToRender = Path.Combine(GetBaseViewPath(name), name);
325 /// <summary>
326 /// Specifies the view to be processed after the component has finished its processing.
327 /// </summary>
328 protected void RenderView(String component, String name)
330 context.ViewToRender = Path.Combine(GetBaseViewPath(component, name), name);
333 /// <summary>
334 /// Specifies the shared view to be processed after the component has finished its
335 /// processing. (A partial view shared
336 /// by others views and usually in the root folder
337 /// of the view directory).
338 /// </summary>
339 protected void RenderSharedView(String name)
341 context.ViewToRender = name;
344 /// <summary>
345 /// Cancels the view processing.
346 /// </summary>
347 protected void CancelView()
349 context.ViewToRender = null;
352 /// <summary>
353 /// Renders the specified content back to the browser
354 /// </summary>
355 /// <param name="content">The content to render.</param>
356 protected void RenderText(String content)
358 context.Writer.Write(content);
361 /// <summary>
362 /// Determines whether the current component declaration on the view
363 /// has the specified section.
364 /// </summary>
365 /// <param name="sectionName">Name of the section.</param>
366 /// <returns>
367 /// <c>true</c> if the specified section exists; otherwise, <c>false</c>.
368 /// </returns>
369 protected bool HasSection(String sectionName)
371 return context.HasSection(sectionName);
374 /// <summary>
375 /// Renders the component body.
376 /// </summary>
377 protected void RenderBody()
379 context.RenderBody();
382 /// <summary>
383 /// Renders the body into the specified <see cref="TextWriter"/>
384 /// </summary>
385 /// <param name="writer">The writer.</param>
386 protected void RenderBody(TextWriter writer)
388 context.RenderBody(writer);
391 /// <summary>
392 /// Renders the the specified section
393 /// </summary>
394 /// <param name="sectionName">Name of the section.</param>
395 protected void RenderSection(String sectionName)
397 context.RenderSection(sectionName);
400 /// <summary>
401 /// Renders the the specified section
402 /// </summary>
403 /// <param name="sectionName">Name of the section.</param>
404 /// <param name="writer">The writer.</param>
405 protected void RenderSection(String sectionName, TextWriter writer)
407 context.RenderSection(sectionName, writer);
410 #endregion
412 #region private helper methods
414 private String GetBaseViewPath(string name)
416 return GetBaseViewPath(context.ComponentName, name);
419 private String GetBaseViewPath(String componentName, string name)
421 string viewPath = Path.Combine("components", componentName);
422 if (Context.ViewEngine.HasTemplate(Path.Combine(viewPath, name)) == false)
424 viewPath = Path.Combine("components", componentName + "Component");
426 return viewPath;
429 #endregion