Adding null checks to BrailBase.InitProperties, making sure that RescueController...
[castle.git] / MonoRail / Castle.MonoRail.Views.Brail / BrailBase.cs
blob8ce36dfb42f13f10dd85fb635ba51d6ddfdc6cea
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.Views.Brail
17 using System;
18 using System.Collections;
19 using System.IO;
20 using System.Web;
21 using Framework;
23 /// <summary>
24 ///Base class for all the view scripts, this is the class that is responsible for
25 /// support all the behind the scenes magic such as variable to PropertyBag trasnlation,
26 /// resources usage, etc.
27 /// </summary>
28 public abstract class BrailBase
30 protected Controller __controller;
32 /// <summary>
33 /// Reference to the DSL service
34 /// </summary>
35 private DslProvider _dsl;
37 /// <summary>
38 /// This is used by layout scripts only, for outputing the child's content
39 /// </summary>
40 protected TextWriter childOutput;
42 protected IRailsEngineContext context;
43 private TextWriter outputStream;
45 /// <summary>
46 /// usually used by the layout to refer to its view, or a subview to its parent
47 /// </summary>
48 protected BrailBase parent;
50 private Hashtable properties;
52 /// <summary>
53 /// used to hold the ComponentParams from the view, so their views/sections could access them
54 /// </summary>
55 private IList viewComponentsParameters;
57 protected BooViewEngine viewEngine;
59 /// <summary>
60 /// Initializes a new instance of the <see cref="BrailBase"/> class.
61 /// </summary>
62 /// <param name="viewEngine">The view engine.</param>
63 /// <param name="output">The output.</param>
64 /// <param name="context">The context.</param>
65 /// <param name="__controller">The controller.</param>
66 public BrailBase(BooViewEngine viewEngine, TextWriter output, IRailsEngineContext context, Controller __controller)
68 this.viewEngine = viewEngine;
69 outputStream = output;
70 this.context = context;
71 this.__controller = __controller;
72 InitProperties(context, __controller);
76 /// <summary>
77 ///The path of the script, this is filled by AddBrailBaseClassStep
78 /// and is used for sub views
79 /// </summary>
80 public virtual string ScriptDirectory
82 get { return viewEngine.ViewRootDir; }
85 /// <summary>
86 /// Gets the view engine.
87 /// </summary>
88 /// <value>The view engine.</value>
89 public BooViewEngine ViewEngine
91 get { return viewEngine; }
94 /// <summary>
95 /// Gets the DSL provider
96 /// </summary>
97 /// <value>Reference to the current DSL Provider</value>
98 public DslProvider Dsl
102 BrailBase view = this;
103 if (null == view._dsl)
105 view._dsl = new DslProvider(view);
108 return view._dsl;
109 //while (view.parent != null)
111 // view = view.parent;
114 //if (view._dsl == null)
116 // view._dsl = new DslProvider(view);
119 //return view._dsl;
123 /// <summary>
124 /// Gets the flash.
125 /// </summary>
126 /// <value>The flash.</value>
127 public Flash Flash
129 get { return context.Flash; }
132 /// <summary>
133 /// Gets the output stream.
134 /// </summary>
135 /// <value>The output stream.</value>
136 public TextWriter OutputStream
138 get { return outputStream; }
141 /// <summary>
142 /// Gets or sets the child output.
143 /// </summary>
144 /// <value>The child output.</value>
145 public TextWriter ChildOutput
147 get { return childOutput; }
148 set { childOutput = value; }
151 /// <summary>
152 /// Gets the properties.
153 /// </summary>
154 /// <value>The properties.</value>
155 public IDictionary Properties
157 get { return properties; }
160 /// <summary>
161 /// Runs this instance, this is generated by the script
162 /// </summary>
163 public abstract void Run();
165 /// <summary>
166 /// Output the subview to the client, this is either a relative path "SubView" which
167 /// is relative to the current /script/ or an "absolute" path "/home/menu" which is
168 /// actually relative to ViewDirRoot
169 /// </summary>
170 /// <param name="subviewName"></param>
171 public string OutputSubView(string subviewName)
173 return OutputSubView(subviewName, new Hashtable());
176 /// <summary>
177 /// Similiar to the OutputSubView(string) function, but with a bunch of parameters that are used
178 /// just for this subview. This parameters are /not/ inheritable.
179 /// </summary>
180 /// <returns>An empty string, just to make it possible to use inline ${OutputSubView("foo")}</returns>
181 public string OutputSubView(string subviewName, IDictionary parameters)
183 OutputSubView(subviewName, outputStream, parameters);
184 return string.Empty;
187 /// <summary>
188 /// Outputs the sub view to the writer
189 /// </summary>
190 /// <param name="subviewName">Name of the subview.</param>
191 /// <param name="writer">The writer.</param>
192 /// <param name="parameters">The parameters.</param>
193 public void OutputSubView(string subviewName, TextWriter writer, IDictionary parameters)
195 string subViewFileName = GetSubViewFilename(subviewName);
196 BrailBase subView = viewEngine.GetCompiledScriptInstance(subViewFileName, writer, context, __controller);
197 subView.SetParent(this);
198 foreach (DictionaryEntry entry in parameters)
200 subView.properties[entry.Key] = entry.Value;
202 subView.Run();
205 /// <summary>
206 /// Get the sub view file name, if the subview starts with a '/'
207 /// then the filename is considered relative to ViewDirRoot
208 /// otherwise, it's relative to the current script directory
209 /// </summary>
210 /// <param name="subviewName"></param>
211 /// <returns></returns>
212 public string GetSubViewFilename(string subviewName)
214 //absolute path from Views directory
215 if (subviewName[0] == '/')
216 return subviewName.Substring(1) + viewEngine.ViewFileExtension;
217 return Path.Combine(ScriptDirectory, subviewName) + viewEngine.ViewFileExtension;
220 /// <summary>
221 /// this is called by ReplaceUnknownWithParameters step to create a more dynamic experiance
222 /// any uknown identifier will be translate into a call for GetParameter('identifier name').
223 /// This mean that when an uknonwn identifier is in the script, it will only be found on runtime.
224 /// </summary>
225 /// <param name="name"></param>
226 /// <returns></returns>
227 public object GetParameter(string name)
229 ParameterSearch search = GetParameterInternal(name);
230 if (search.Found == false)
231 throw new RailsException("Parameter '" + name + "' was not found!");
232 return search.Value;
235 /// <summary>
236 /// this is called by ReplaceUnknownWithParameters step to create a more dynamic experiance
237 /// any uknown identifier with the prefix of ? will be translated into a call for
238 /// TryGetParameter('identifier name without the ? prefix').
239 /// This method will return null if the value it not found.
240 /// </summary>
241 /// <param name="name"></param>
242 /// <returns></returns>
243 public object TryGetParameter(string name)
245 ParameterSearch search = GetParameterInternal(name);
246 return new IgnoreNull(search.Value);
249 /// <summary>
250 /// Gets the parameter - implements the logic for searching parameters.
251 /// </summary>
252 /// <param name="name">The name.</param>
253 /// <returns></returns>
254 private ParameterSearch GetParameterInternal(string name)
256 //temporary syntax to turn @variable to varaible, imitating :symbol in ruby
257 if (name.StartsWith("@"))
258 return new ParameterSearch(name.Substring(1), true);
259 if (viewComponentsParameters != null)
261 foreach (IDictionary viewComponentProperties in viewComponentsParameters)
263 if (viewComponentProperties.Contains(name))
264 return new ParameterSearch(viewComponentProperties[name], true);
267 if (properties.Contains(name))
268 return new ParameterSearch(properties[name], true);
269 if (parent != null)
270 return parent.GetParameterInternal(name);
271 return new ParameterSearch(null, false);
274 /// <summary>
275 /// Sets the parent.
276 /// </summary>
277 /// <param name="myParent">My parent.</param>
278 public void SetParent(BrailBase myParent)
280 parent = myParent;
283 /// <summary>
284 /// Allows to check that a parameter was defined
285 /// </summary>
286 /// <param name="name"></param>
287 /// <returns></returns>
288 public bool IsDefined(string name)
290 ParameterSearch search = GetParameterInternal(name);
291 return search.Found;
294 /// <summary>
295 /// This is required because we may want to replace the output stream and get the correct
296 /// behavior from components call RenderText() or RenderSection()
297 /// </summary>
298 public IDisposable SetOutputStream(TextWriter newOutputStream)
300 ReturnOutputStreamToInitialWriter disposable = new ReturnOutputStreamToInitialWriter(OutputStream, this);
301 outputStream = newOutputStream;
302 return disposable;
305 /// <summary>
306 /// Will output the given value as escaped HTML
307 /// </summary>
308 /// <param name="toOutput"></param>
309 public void OutputEscaped(object toOutput)
311 if (toOutput == null)
312 return;
313 string str = toOutput.ToString();
314 OutputStream.Write(HttpUtility.HtmlEncode(str));
317 /// <summary>
318 /// Note that this will overwrite any existing property.
319 /// </summary>
320 public void AddProperty(string name, object item)
322 properties[name] = item;
325 /// <summary>
326 /// Adds the view component newProperties.
327 /// This will be included in the parameters searching, note that this override
328 /// the current parameters if there are clashing.
329 /// The search order is LIFO
330 /// </summary>
331 /// <param name="newProperties">The newProperties.</param>
332 public void AddViewComponentProperties(IDictionary newProperties)
334 if (viewComponentsParameters == null)
335 viewComponentsParameters = new ArrayList();
336 viewComponentsParameters.Insert(0, newProperties);
339 /// <summary>
340 /// Removes the view component properties, so they will no longer be visible to the views.
341 /// </summary>
342 /// <param name="propertiesToRemove">The properties to remove.</param>
343 public void RemoveViewComponentProperties(IDictionary propertiesToRemove)
345 if (viewComponentsParameters == null)
346 return;
347 viewComponentsParameters.Remove(propertiesToRemove);
350 public void RenderComponent(string componentName)
352 RenderComponent(componentName, new Hashtable());
355 public void RenderComponent(string componentName, IDictionary parameters)
357 BrailViewComponentContext componentContext =
358 new BrailViewComponentContext(this, null, componentName, OutputStream,
359 new Hashtable(StringComparer.InvariantCultureIgnoreCase));
360 AddViewComponentProperties(componentContext.ComponentParameters);
361 IViewComponentFactory componentFactory = (IViewComponentFactory) context.GetService(typeof (IViewComponentFactory));
362 ViewComponent component = componentFactory.Create(componentName);
363 component.Init(context, componentContext);
364 component.Render();
365 if (componentContext.ViewToRender != null)
367 OutputSubView("/" + componentContext.ViewToRender, componentContext.ComponentParameters);
369 RemoveViewComponentProperties(componentContext.ComponentParameters);
372 /// <summary>
373 /// Initialize all the properties that a script may need
374 /// One thing to note here is that resources are wrapped in ResourceToDuck wrapper
375 /// to enable easy use by the script
376 /// </summary>
377 /// <param name="myContext"></param>
378 /// <param name="myController"></param>
379 private void InitProperties(IRailsEngineContext myContext, Controller myController)
381 properties = new Hashtable(StringComparer.InvariantCultureIgnoreCase);
382 //properties.Add("dsl", new DslWrapper(this));
383 properties.Add("Controller", myController);
384 properties.Add("Context", myContext);
385 properties.Add("request", myContext.Request);
386 properties.Add("response", myContext.Response);
387 properties.Add("session", myContext.Session);
389 if (myController.Resources != null)
391 foreach (object key in myController.Resources.Keys)
393 properties.Add(key, new ResourceToDuck(myController.Resources[key]));
397 if (myController.Params != null)
399 foreach (string key in myController.Params.AllKeys)
401 if (key == null)
402 continue;
403 properties[key] = myContext.Params[key];
406 if (myContext.Flash != null)
408 foreach (DictionaryEntry entry in myContext.Flash)
410 properties[entry.Key] = entry.Value;
414 if (myController.PropertyBag != null)
416 foreach (DictionaryEntry entry in myController.PropertyBag)
418 properties[entry.Key] = entry.Value;
422 if (myController.Helpers != null)
424 foreach (DictionaryEntry entry in myController.Helpers)
426 properties[entry.Key] = entry.Value;
430 properties["siteRoot"] = myContext.ApplicationPath;
433 #region Nested type: ParameterSearch
435 private class ParameterSearch
437 private readonly bool found;
438 private readonly object value;
440 public ParameterSearch(object value, bool found)
442 this.found = found;
443 this.value = value;
446 public bool Found
448 get { return found; }
451 public object Value
453 get { return value; }
457 #endregion
459 #region Nested type: ReturnOutputStreamToInitialWriter
461 private class ReturnOutputStreamToInitialWriter : IDisposable
463 private TextWriter initialWriter;
464 private BrailBase parent;
466 public ReturnOutputStreamToInitialWriter(TextWriter initialWriter, BrailBase parent)
468 this.initialWriter = initialWriter;
469 this.parent = parent;
472 #region IDisposable Members
474 public void Dispose()
476 parent.outputStream = initialWriter;
479 #endregion
482 #endregion