1 // Copyright 2004-2007 Castle Project - http://www.castleproject.org/
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
7 // http://www.apache.org/licenses/LICENSE-2.0
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
18 using System
.Collections
.Specialized
;
20 using System
.Collections
;
21 using System
.Reflection
;
23 using Castle
.Components
.Binder
;
26 /// Base class for reusable UI Components.
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>.
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.
38 /// Another way is to use the <see cref="ViewComponentDetailsAttribute"/> to specify a custom name
39 /// and the sections supported.
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.
49 /// A very simplist view component that renders the time.
51 /// public class ShowTime : ViewComponent
53 /// public override void Initialize()
57 /// public override void Render()
59 /// RenderText("Time: " + DateTime.Now.ToString());
64 /// This can be used from the view using the following syntax (NVelocity view engine)
67 /// #component(ShowTime)
71 public abstract class ViewComponent
74 /// Holds the component context
76 private IViewComponentContext context
;
79 /// Holds the <see cref="IRailsEngineContext"/> associated
80 /// to the request lifetime.
82 private IRailsEngineContext railsContext
;
84 private string[] sectionsFromAttribute
;
86 #region "Internal" core methods
89 /// Invoked by the framework.
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();
104 /// Binds the component parameters.
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
];
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
));
146 object converted
= converter
.Convert(property
.PropertyType
, value.GetType(), value, out succeeded
);
150 property
.SetValue(this, converted
, null);
154 throw new Exception("Could not convert '" + value + "' to type " + property
.PropertyType
);
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
);
167 #region Lifecycle methods (overridables)
170 /// Called by the framework once the component instance
173 public virtual void Initialize()
178 /// Called by the framework so the component can
179 /// render its content
181 public virtual void Render()
183 RenderView("default");
187 /// Implementor should return true only if the
188 /// <c>name</c> is a known section the view component
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;
224 #region Usefull properties
227 /// Gets the Component Context
229 public IViewComponentContext Context
231 get { return context; }
235 /// Gets the <see cref="IRailsEngineContext"/>
236 /// associated with the current request
238 protected IRailsEngineContext RailsContext
240 get { return railsContext; }
244 /// Gets the component parameters
246 protected IDictionary ComponentParams
248 get { return context.ComponentParameters; }
252 /// Gets the Session dictionary.
254 protected IDictionary Session
256 get { return railsContext.Session; }
260 /// Gets a dictionary of volative items.
261 /// Ideal for showing success and failures messages.
263 protected Flash Flash
265 get { return railsContext.Flash; }
269 /// Gets the web context of ASP.NET API.
271 protected HttpContext HttpContext
273 get { return railsContext.UnderlyingContext; }
277 /// Gets the request object.
279 protected IRequest Request
281 get { return railsContext.Request; }
285 /// Gets the response object.
287 protected IResponse Response
289 get { return railsContext.Response; }
293 /// Provides a way to make data available
294 /// to the view that the component uses
296 protected IDictionary PropertyBag
298 get { return context.ContextVars; }
302 /// Shortcut to Request.Params
304 protected NameValueCollection Params
306 get { return Request.Params; }
311 #region Useful operations
314 /// Specifies the view to be processed after the component has finished its processing.
316 protected void RenderView(String name
)
318 context
.ViewToRender
= Path
.Combine(GetBaseViewPath(), name
);
322 /// Specifies the view to be processed after the component has finished its processing.
324 protected void RenderView(String component
, String name
)
326 context
.ViewToRender
= Path
.Combine(GetBaseViewPath(component
), name
);
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).
335 protected void RenderSharedView(String name
)
337 context
.ViewToRender
= name
;
341 /// Cancels the view processing.
343 protected void CancelView()
345 context
.ViewToRender
= null;
349 /// Renders the specified content back to the browser
351 /// <param name="content">The content to render.</param>
352 protected void RenderText(String content
)
354 context
.Writer
.Write(content
);
358 /// Determines whether the current component declaration on the view
359 /// has the specified section.
361 /// <param name="sectionName">Name of the section.</param>
363 /// <c>true</c> if the specified section exists; otherwise, <c>false</c>.
365 protected bool HasSection(String sectionName
)
367 return context
.HasSection(sectionName
);
371 /// Renders the component body.
373 protected void RenderBody()
375 context
.RenderBody();
379 /// Renders the body into the specified <see cref="TextWriter"/>
381 /// <param name="writer">The writer.</param>
382 protected void RenderBody(TextWriter writer
)
384 context
.RenderBody(writer
);
388 /// Renders the the specified section
390 /// <param name="sectionName">Name of the section.</param>
391 protected void RenderSection(String sectionName
)
393 context
.RenderSection(sectionName
);
397 /// Renders the the specified section
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
);
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
);