Minor style changes
[castle.git] / MonoRail / Castle.MonoRail.Framework / ViewComponent.cs
blob643eb154f9a21a7c406460e146325a0c5f6c114b
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 /// </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="IRailsEngineContext"/> associated
80 /// to the request lifetime.
81 /// </summary>
82 private IRailsEngineContext railsContext;
84 private string[] sectionsFromAttribute;
86 #region "Internal" core methods
88 /// <summary>
89 /// Invoked by the framework.
90 /// </summary>
91 /// <param name="engineContext">Request context</param>
92 /// <param name="componentContext">ViewComponent context</param>
93 public void Init(IRailsEngineContext engineContext, IViewComponentContext componentContext)
95 railsContext = engineContext;
96 context = componentContext;
98 BindComponentParameters();
100 Initialize();
103 /// <summary>
104 /// Binds the component parameters.
105 /// </summary>
106 private void BindComponentParameters()
108 IConverter converter = new DefaultConverter();
110 PropertyInfo[] properties = GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
112 foreach(PropertyInfo property in properties)
114 if (!property.CanWrite) continue;
116 object[] attributes = property.GetCustomAttributes(typeof(ViewComponentParamAttribute), true);
118 if (attributes.Length == 1)
120 BindParameter((ViewComponentParamAttribute) attributes[0], property, converter);
125 private void BindParameter(ViewComponentParamAttribute paramAtt, PropertyInfo property, IConverter converter)
127 string compParamKey = string.IsNullOrEmpty(paramAtt.ParamName) ? property.Name : paramAtt.ParamName;
129 object value = ComponentParams[compParamKey];
131 if (value == null)
133 if (paramAtt.Required &&
134 (property.PropertyType.IsValueType || property.GetValue(this, null) == null))
136 throw new ViewComponentException(string.Format("The parameter '{0}' is required by " +
137 "the ViewComponent {1} but was not passed or had a null value", compParamKey, GetType().Name));
140 else
144 bool succeeded;
146 object converted = converter.Convert(property.PropertyType, value.GetType(), value, out succeeded);
148 if (succeeded)
150 property.SetValue(this, converted, null);
152 else
154 throw new Exception("Could not convert '" + value + "' to type " + property.PropertyType);
157 catch(Exception ex)
159 throw new ViewComponentException(string.Format("Error trying to set value for parameter '{0}' " +
160 "on ViewComponent {1}: {2}", compParamKey, GetType().Name, ex.Message), ex);
165 #endregion
167 #region Lifecycle methods (overridables)
169 /// <summary>
170 /// Called by the framework once the component instance
171 /// is initialized
172 /// </summary>
173 public virtual void Initialize()
177 /// <summary>
178 /// Called by the framework so the component can
179 /// render its content
180 /// </summary>
181 public virtual void Render()
183 RenderView("default");
186 /// <summary>
187 /// Implementor should return true only if the
188 /// <c>name</c> is a known section the view component
189 /// supports.
190 /// </summary>
191 /// <param name="name">section being added</param>
192 /// <returns><see langword="true"/> if section is supported</returns>
193 public virtual bool SupportsSection(string name)
195 // TODO: We need to cache this
197 if (sectionsFromAttribute == null)
199 object[] attributes = GetType().GetCustomAttributes(typeof(ViewComponentDetailsAttribute), true);
201 if (attributes.Length != 0)
203 ViewComponentDetailsAttribute detailsAtt = (ViewComponentDetailsAttribute) attributes[0];
205 if (!string.IsNullOrEmpty(detailsAtt.Sections))
207 sectionsFromAttribute = detailsAtt.Sections.Split(',');
211 if (sectionsFromAttribute == null)
213 sectionsFromAttribute = new string[0];
217 return Array.Find(sectionsFromAttribute,
218 delegate(string item)
219 { return string.Equals(item, name, StringComparison.InvariantCultureIgnoreCase); }) != null;
222 #endregion
224 #region Usefull properties
226 /// <summary>
227 /// Gets the Component Context
228 /// </summary>
229 public IViewComponentContext Context
231 get { return context; }
234 /// <summary>
235 /// Gets the <see cref="IRailsEngineContext"/>
236 /// associated with the current request
237 /// </summary>
238 protected IRailsEngineContext RailsContext
240 get { return railsContext; }
243 /// <summary>
244 /// Gets the component parameters
245 /// </summary>
246 protected IDictionary ComponentParams
248 get { return context.ComponentParameters; }
251 /// <summary>
252 /// Gets the Session dictionary.
253 /// </summary>
254 protected IDictionary Session
256 get { return railsContext.Session; }
259 /// <summary>
260 /// Gets a dictionary of volative items.
261 /// Ideal for showing success and failures messages.
262 /// </summary>
263 protected Flash Flash
265 get { return railsContext.Flash; }
268 /// <summary>
269 /// Gets the web context of ASP.NET API.
270 /// </summary>
271 protected HttpContext HttpContext
273 get { return railsContext.UnderlyingContext; }
276 /// <summary>
277 /// Gets the request object.
278 /// </summary>
279 protected IRequest Request
281 get { return railsContext.Request; }
284 /// <summary>
285 /// Gets the response object.
286 /// </summary>
287 protected IResponse Response
289 get { return railsContext.Response; }
292 /// <summary>
293 /// Provides a way to make data available
294 /// to the view that the component uses
295 /// </summary>
296 protected IDictionary PropertyBag
298 get { return context.ContextVars; }
301 /// <summary>
302 /// Shortcut to Request.Params
303 /// </summary>
304 protected NameValueCollection Params
306 get { return Request.Params; }
309 #endregion
311 #region Useful operations
313 /// <summary>
314 /// Specifies the view to be processed after the component has finished its processing.
315 /// </summary>
316 protected void RenderView(String name)
318 context.ViewToRender = Path.Combine(GetBaseViewPath(), name);
321 /// <summary>
322 /// Specifies the view to be processed after the component has finished its processing.
323 /// </summary>
324 protected void RenderView(String component, String name)
326 context.ViewToRender = Path.Combine(GetBaseViewPath(component), name);
329 /// <summary>
330 /// Specifies the shared view to be processed after the component has finished its
331 /// processing. (A partial view shared
332 /// by others views and usually in the root folder
333 /// of the view directory).
334 /// </summary>
335 protected void RenderSharedView(String name)
337 context.ViewToRender = name;
340 /// <summary>
341 /// Cancels the view processing.
342 /// </summary>
343 protected void CancelView()
345 context.ViewToRender = null;
348 /// <summary>
349 /// Renders the specified content back to the browser
350 /// </summary>
351 /// <param name="content">The content to render.</param>
352 protected void RenderText(String content)
354 context.Writer.Write(content);
357 /// <summary>
358 /// Determines whether the current component declaration on the view
359 /// has the specified section.
360 /// </summary>
361 /// <param name="sectionName">Name of the section.</param>
362 /// <returns>
363 /// <c>true</c> if the specified section exists; otherwise, <c>false</c>.
364 /// </returns>
365 protected bool HasSection(String sectionName)
367 return context.HasSection(sectionName);
370 /// <summary>
371 /// Renders the component body.
372 /// </summary>
373 protected void RenderBody()
375 context.RenderBody();
378 /// <summary>
379 /// Renders the body into the specified <see cref="TextWriter"/>
380 /// </summary>
381 /// <param name="writer">The writer.</param>
382 protected void RenderBody(TextWriter writer)
384 context.RenderBody(writer);
387 /// <summary>
388 /// Renders the the specified section
389 /// </summary>
390 /// <param name="sectionName">Name of the section.</param>
391 protected void RenderSection(String sectionName)
393 context.RenderSection(sectionName);
396 /// <summary>
397 /// Renders the the specified section
398 /// </summary>
399 /// <param name="sectionName">Name of the section.</param>
400 /// <param name="writer">The writer.</param>
401 protected void RenderSection(String sectionName, TextWriter writer)
403 context.RenderSection(sectionName, writer);
406 #endregion
408 #region private helper methods
410 private String GetBaseViewPath()
412 return GetBaseViewPath(context.ComponentName);
415 private String GetBaseViewPath(String componentName)
417 return String.Format("components/{0}", componentName);
420 #endregion