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.
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>.
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.
39 /// A very simplist view component that renders the time.
41 /// public class ShowTime : ViewComponent
43 /// public override void Initialize()
47 /// public override void Render()
49 /// RenderText("Time: " + DateTime.Now.ToString());
54 /// This can be used from the view using the following syntax (NVelocity view engine)
57 /// #component(ShowTime)
60 public abstract class ViewComponent
63 /// Holds the component context
65 private IViewComponentContext context
;
68 /// Holds the <see cref="IRailsEngineContext"/> associated
69 /// to the request lifetime.
71 private IRailsEngineContext railsContext
;
73 private string[] sectionsFromAttribute
;
75 #region "Internal" core methods
78 /// Invoked by the framework.
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();
93 /// Binds the component parameters.
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
];
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
));
135 object converted
= converter
.Convert(property
.PropertyType
, value.GetType(), value, out succeeded
);
139 property
.SetValue(this, converted
, null);
143 throw new Exception("Could not convert '" + value + "' to type " + property
.PropertyType
);
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
);
156 #region Lifecycle methods (overridables)
159 /// Called by the framework once the component instance
162 public virtual void Initialize()
167 /// Called by the framework so the component can
168 /// render its content
170 public virtual void Render()
172 RenderView("default");
176 /// Implementor should return true only if the
177 /// <c>name</c> is a known section the view component
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;
213 #region Usefull properties
216 /// Gets the Component Context
218 public IViewComponentContext Context
220 get { return context; }
224 /// Gets the <see cref="IRailsEngineContext"/>
225 /// associated with the current request
227 protected IRailsEngineContext RailsContext
229 get { return railsContext; }
233 /// Gets the component parameters
235 protected IDictionary ComponentParams
237 get { return context.ComponentParameters; }
241 /// Gets the Session dictionary.
243 protected IDictionary Session
245 get { return railsContext.Session; }
249 /// Gets a dictionary of volative items.
250 /// Ideal for showing success and failures messages.
252 protected Flash Flash
254 get { return railsContext.Flash; }
258 /// Gets the web context of ASP.NET API.
260 protected HttpContext HttpContext
262 get { return railsContext.UnderlyingContext; }
266 /// Gets the request object.
268 protected IRequest Request
270 get { return railsContext.Request; }
274 /// Gets the response object.
276 protected IResponse Response
278 get { return railsContext.Response; }
282 /// Provides a way to make data available
283 /// to the view that the component uses
285 protected IDictionary PropertyBag
287 get { return context.ContextVars; }
291 /// Shortcut to Request.Params
293 protected NameValueCollection Params
295 get { return Request.Params; }
300 #region Useful operations
303 /// Specifies the view to be processed after the component has finished its processing.
305 protected void RenderView(String name
)
307 context
.ViewToRender
= Path
.Combine(GetBaseViewPath(), name
);
311 /// Specifies the view to be processed after the component has finished its processing.
313 protected void RenderView(String component
, String name
)
315 context
.ViewToRender
= Path
.Combine(GetBaseViewPath(component
), name
);
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).
324 protected void RenderSharedView(String name
)
326 context
.ViewToRender
= name
;
330 /// Cancels the view processing.
332 protected void CancelView()
334 context
.ViewToRender
= null;
337 protected void RenderText(String content
)
339 context
.Writer
.Write(content
);
343 /// Determines whether the current component declaration on the view
344 /// has the specified section.
346 /// <param name="sectionName">Name of the section.</param>
348 /// <c>true</c> if the specified section exists; otherwise, <c>false</c>.
350 protected bool HasSection(String sectionName
)
352 return context
.HasSection(sectionName
);
356 /// Renders the component body.
358 protected void RenderBody()
360 context
.RenderBody();
364 /// Renders the body into the specified <see cref="TextWriter"/>
366 /// <param name="writer">The writer.</param>
367 protected void RenderBody(TextWriter writer
)
369 context
.RenderBody(writer
);
373 /// Renders the the specified section
375 /// <param name="sectionName">Name of the section.</param>
376 protected void RenderSection(String sectionName
)
378 context
.RenderSection(sectionName
);
382 /// Renders the the specified section
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
);
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
);