- If the controller cannot be found, MR searches for a special rescue "rescues/404...
[castle.git] / MonoRail / Castle.MonoRail.Framework / ViewComponent.cs
blob867814efdab653d9bce3142dd36139279110e7cc
1 // Copyright 2004-2007 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 /// <para>
28 /// Implementors should override <see cref="ViewComponent.Initialize"/>
29 /// for implement proper initialization (if necessary).
30 /// Also implement <see cref="ViewComponent.Render"/> as by default it
31 /// will render a <c>default</c> view on <c>[ViewFolderRoot]/components/[componentname]</c>.
32 /// </para>
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 /// </summary>
38 /// <example>
39 /// A very simplist view component that renders the time.
40 /// <code>
41 /// public class ShowTime : ViewComponent
42 /// {
43 /// public override void Initialize()
44 /// {
45 /// }
46 ///
47 /// public override void Render()
48 /// {
49 /// RenderText("Time: " + DateTime.Now.ToString());
50 /// }
51 /// }
52 /// </code>
53 /// <para>
54 /// This can be used from the view using the following syntax (NVelocity view engine)
55 /// </para>
56 /// <code>
57 /// #component(ShowTime)
58 /// </code>
59 /// </example>
60 public abstract class ViewComponent
62 /// <summary>
63 /// Holds the component context
64 /// </summary>
65 private IViewComponentContext context;
67 /// <summary>
68 /// Holds the <see cref="IRailsEngineContext"/> associated
69 /// to the request lifetime.
70 /// </summary>
71 private IRailsEngineContext railsContext;
73 private string[] sectionsFromAttribute;
75 #region "Internal" core methods
77 /// <summary>
78 /// Invoked by the framework.
79 /// </summary>
80 /// <param name="engineContext">Request context</param>
81 /// <param name="componentContext">ViewComponent context</param>
82 public void Init(IRailsEngineContext engineContext, IViewComponentContext componentContext)
84 railsContext = engineContext;
85 context = componentContext;
87 BindComponentParameters();
89 Initialize();
92 /// <summary>
93 /// Binds the component parameters.
94 /// </summary>
95 private void BindComponentParameters()
97 IConverter converter = new DefaultConverter();
99 PropertyInfo[] properties = GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
101 foreach(PropertyInfo property in properties)
103 if (!property.CanWrite) continue;
105 object[] attributes = property.GetCustomAttributes(typeof(ViewComponentParamAttribute), true);
107 if (attributes.Length == 1)
109 BindParameter((ViewComponentParamAttribute) attributes[0], property, converter);
114 private void BindParameter(ViewComponentParamAttribute paramAtt, PropertyInfo property, IConverter converter)
116 string compParamKey = string.IsNullOrEmpty(paramAtt.ParamName) ? property.Name : paramAtt.ParamName;
118 object value = ComponentParams[compParamKey];
120 if (value == null)
122 if (paramAtt.Required &&
123 (property.PropertyType.IsValueType || property.GetValue(this, null) == null))
125 throw new ViewComponentException(string.Format("The parameter '{0}' is required by " +
126 "the ViewComponent {1} but was not passed or had a null value", compParamKey, GetType().Name));
129 else
133 bool succeeded;
135 object converted = converter.Convert(property.PropertyType, value.GetType(), value, out succeeded);
137 if (succeeded)
139 property.SetValue(this, converted, null);
141 else
143 throw new Exception("Could not convert '" + value + "' to type " + property.PropertyType);
146 catch(Exception ex)
148 throw new ViewComponentException(string.Format("Error trying to set value for parameter '{0}' " +
149 "on ViewComponent {1}: {2}", compParamKey, GetType().Name, ex.Message), ex);
154 #endregion
156 #region Lifecycle methods (overridables)
158 /// <summary>
159 /// Called by the framework once the component instance
160 /// is initialized
161 /// </summary>
162 public virtual void Initialize()
166 /// <summary>
167 /// Called by the framework so the component can
168 /// render its content
169 /// </summary>
170 public virtual void Render()
172 RenderView("default");
175 /// <summary>
176 /// Implementor should return true only if the
177 /// <c>name</c> is a known section the view component
178 /// supports.
179 /// </summary>
180 /// <param name="name">section being added</param>
181 /// <returns><see langword="true"/> if section is supported</returns>
182 public virtual bool SupportsSection(string name)
184 // TODO: We need to cache this
186 if (sectionsFromAttribute == null)
188 object[] attributes = GetType().GetCustomAttributes(typeof(ViewComponentDetailsAttribute), true);
190 if (attributes.Length != 0)
192 ViewComponentDetailsAttribute detailsAtt = (ViewComponentDetailsAttribute) attributes[0];
194 if (!string.IsNullOrEmpty(detailsAtt.Sections))
196 sectionsFromAttribute = detailsAtt.Sections.Split(',');
200 if (sectionsFromAttribute == null)
202 sectionsFromAttribute = new string[0];
206 return Array.Find(sectionsFromAttribute,
207 delegate(string item)
208 { return string.Equals(item, name, StringComparison.InvariantCultureIgnoreCase); }) != null;
211 #endregion
213 #region Usefull properties
215 /// <summary>
216 /// Gets the Component Context
217 /// </summary>
218 public IViewComponentContext Context
220 get { return context; }
223 /// <summary>
224 /// Gets the <see cref="IRailsEngineContext"/>
225 /// associated with the current request
226 /// </summary>
227 protected IRailsEngineContext RailsContext
229 get { return railsContext; }
232 /// <summary>
233 /// Gets the component parameters
234 /// </summary>
235 protected IDictionary ComponentParams
237 get { return context.ComponentParameters; }
240 /// <summary>
241 /// Gets the Session dictionary.
242 /// </summary>
243 protected IDictionary Session
245 get { return railsContext.Session; }
248 /// <summary>
249 /// Gets a dictionary of volative items.
250 /// Ideal for showing success and failures messages.
251 /// </summary>
252 protected Flash Flash
254 get { return railsContext.Flash; }
257 /// <summary>
258 /// Gets the web context of ASP.NET API.
259 /// </summary>
260 protected HttpContext HttpContext
262 get { return railsContext.UnderlyingContext; }
265 /// <summary>
266 /// Gets the request object.
267 /// </summary>
268 protected IRequest Request
270 get { return railsContext.Request; }
273 /// <summary>
274 /// Gets the response object.
275 /// </summary>
276 protected IResponse Response
278 get { return railsContext.Response; }
281 /// <summary>
282 /// Provides a way to make data available
283 /// to the view that the component uses
284 /// </summary>
285 protected IDictionary PropertyBag
287 get { return context.ContextVars; }
290 /// <summary>
291 /// Shortcut to Request.Params
292 /// </summary>
293 protected NameValueCollection Params
295 get { return Request.Params; }
298 #endregion
300 #region Useful operations
302 /// <summary>
303 /// Specifies the view to be processed after the component has finished its processing.
304 /// </summary>
305 protected void RenderView(String name)
307 context.ViewToRender = Path.Combine(GetBaseViewPath(), name);
310 /// <summary>
311 /// Specifies the view to be processed after the component has finished its processing.
312 /// </summary>
313 protected void RenderView(String component, String name)
315 context.ViewToRender = Path.Combine(GetBaseViewPath(component), name);
318 /// <summary>
319 /// Specifies the shared view to be processed after the component has finished its
320 /// processing. (A partial view shared
321 /// by others views and usually in the root folder
322 /// of the view directory).
323 /// </summary>
324 protected void RenderSharedView(String name)
326 context.ViewToRender = name;
329 /// <summary>
330 /// Cancels the view processing.
331 /// </summary>
332 protected void CancelView()
334 context.ViewToRender = null;
337 protected void RenderText(String content)
339 context.Writer.Write(content);
342 /// <summary>
343 /// Determines whether the current component declaration on the view
344 /// has the specified section.
345 /// </summary>
346 /// <param name="sectionName">Name of the section.</param>
347 /// <returns>
348 /// <c>true</c> if the specified section exists; otherwise, <c>false</c>.
349 /// </returns>
350 protected bool HasSection(String sectionName)
352 return context.HasSection(sectionName);
355 /// <summary>
356 /// Renders the component body.
357 /// </summary>
358 protected void RenderBody()
360 context.RenderBody();
363 /// <summary>
364 /// Renders the body into the specified <see cref="TextWriter"/>
365 /// </summary>
366 /// <param name="writer">The writer.</param>
367 protected void RenderBody(TextWriter writer)
369 context.RenderBody(writer);
372 /// <summary>
373 /// Renders the the specified section
374 /// </summary>
375 /// <param name="sectionName">Name of the section.</param>
376 protected void RenderSection(String sectionName)
378 context.RenderSection(sectionName);
381 /// <summary>
382 /// Renders the the specified section
383 /// </summary>
384 /// <param name="sectionName">Name of the section.</param>
385 /// <param name="writer">The writer.</param>
386 protected void RenderSection(String sectionName, TextWriter writer)
388 context.RenderSection(sectionName, writer);
391 #endregion
393 #region private helper methods
395 private String GetBaseViewPath()
397 return GetBaseViewPath(context.ComponentName);
400 private String GetBaseViewPath(String componentName)
402 return String.Format("components/{0}", componentName);
405 #endregion