1 // Copyright 2004-2008 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="IEngineContext"/> associated
80 /// to the request lifetime.
82 private IEngineContext engineContext
;
85 /// Holds a reference to the <see cref="ViewComponentDetailsAttribute"/> if any.
87 private readonly ViewComponentDetailsAttribute detailsAtt
;
90 /// Initializes a new instance of the ViewComponent class.
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
105 /// Invoked by the framework.
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();
120 /// Binds the component parameters.
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
;
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
));
163 object converted
= converter
.Convert(property
.PropertyType
, value.GetType(), value, out succeeded
);
167 property
.SetValue(this, converted
, null);
171 throw new Exception("Could not convert '" + value + "' to type " + property
.PropertyType
);
176 throw new ViewComponentException(string.Format("Error trying to set value for parameter '{0}' " +
177 "on ViewComponent {1}: {2}", compParamKey
, GetType().Name
,
185 #region Lifecycle methods (overridables)
188 /// Called by the framework once the component instance
191 public virtual void Initialize()
196 /// Called by the framework so the component can
197 /// render its content
199 public virtual void Render()
201 RenderView("default");
205 /// Implementor should return true only if the
206 /// <c>name</c> is a known section the view component
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.
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
);
228 #region Useful properties
231 /// Gets the Component Context
233 public IViewComponentContext Context
235 get { return context; }
239 /// Gets the <see cref="IEngineContext"/>
240 /// associated with the current request
242 protected IEngineContext EngineContext
244 get { return engineContext; }
248 /// Gets the component parameters
250 protected IDictionary ComponentParams
252 get { return context.ComponentParameters; }
256 /// Gets the Session dictionary.
258 protected IDictionary Session
260 get { return engineContext.Session; }
264 /// Gets a dictionary of volative items.
265 /// Ideal for showing success and failures messages.
267 protected Flash Flash
269 get { return engineContext.Flash; }
273 /// Gets the web context of ASP.NET API.
275 protected HttpContext HttpContext
277 get { return engineContext.UnderlyingContext; }
281 /// Gets the request object.
283 protected IRequest Request
285 get { return engineContext.Request; }
289 /// Gets the response object.
291 protected IResponse Response
293 get { return engineContext.Response; }
297 /// Provides a way to make data available
298 /// to the view that the component uses
300 protected IDictionary PropertyBag
302 get { return context.ContextVars; }
306 /// Shortcut to Request.Params
308 protected NameValueCollection Params
310 get { return Request.Params; }
315 #region Useful operations
318 /// Specifies the view to be processed after the component has finished its processing.
320 protected void RenderView(String name
)
322 context
.ViewToRender
= Path
.Combine(GetBaseViewPath(name
), name
);
326 /// Specifies the view to be processed after the component has finished its processing.
328 protected void RenderView(String component
, String name
)
330 context
.ViewToRender
= Path
.Combine(GetBaseViewPath(component
, name
), name
);
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).
339 protected void RenderSharedView(String name
)
341 context
.ViewToRender
= name
;
345 /// Cancels the view processing.
347 protected void CancelView()
349 context
.ViewToRender
= null;
353 /// Renders the specified content back to the browser
355 /// <param name="content">The content to render.</param>
356 protected void RenderText(String content
)
358 context
.Writer
.Write(content
);
362 /// Determines whether the current component declaration on the view
363 /// has the specified section.
365 /// <param name="sectionName">Name of the section.</param>
367 /// <c>true</c> if the specified section exists; otherwise, <c>false</c>.
369 protected bool HasSection(String sectionName
)
371 return context
.HasSection(sectionName
);
375 /// Renders the component body.
377 protected void RenderBody()
379 context
.RenderBody();
383 /// Renders the body into the specified <see cref="TextWriter"/>
385 /// <param name="writer">The writer.</param>
386 protected void RenderBody(TextWriter writer
)
388 context
.RenderBody(writer
);
392 /// Renders the the specified section
394 /// <param name="sectionName">Name of the section.</param>
395 protected void RenderSection(String sectionName
)
397 context
.RenderSection(sectionName
);
401 /// Renders the the specified section
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
);
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");