More working tests.
[castle.git] / MonoRail / Castle.MonoRail.Views.Brail / BrailBase.cs
blob1b3d9912d7a593aec8d80dad79edce812a1af6c5
1 // Copyright 2004-2008 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 IController __controller;
31 protected IControllerContext __controllerContext;
33 /// <summary>
34 /// Reference to the DSL service
35 /// </summary>
36 private DslProvider _dsl;
38 /// <summary>
39 /// This is used by layout scripts only, for outputing the child's content
40 /// </summary>
41 protected TextWriter childOutput;
43 protected internal IEngineContext context;
44 public string LastVariableAccessed;
45 private TextWriter outputStream;
47 /// <summary>
48 /// usually used by the layout to refer to its view, or a subview to its parent
49 /// </summary>
50 protected BrailBase parent;
52 private Hashtable properties;
54 /// <summary>
55 /// used to hold the ComponentParams from the view, so their views/sections could access them
56 /// </summary>
57 private IList viewComponentsParameters;
59 protected BooViewEngine viewEngine;
61 /// <summary>
62 /// Initializes a new instance of the <see cref="BrailBase"/> class.
63 /// </summary>
64 /// <param name="viewEngine">The view engine.</param>
65 /// <param name="output">The output.</param>
66 /// <param name="context">The context.</param>
67 /// <param name="__controller">The controller.</param>
68 /// <param name="__controllerContext">The __controller context.</param>
69 public BrailBase(BooViewEngine viewEngine, TextWriter output,
70 IEngineContext context, IController __controller, IControllerContext __controllerContext)
72 this.viewEngine = viewEngine;
73 outputStream = output;
74 this.context = context;
75 this.__controller = __controller;
76 this.__controllerContext = __controllerContext;
77 InitProperties(context, __controller, __controllerContext);
80 /// <summary>
81 ///The path of the script, this is filled by AddBrailBaseClassStep
82 /// and is used for sub views
83 /// </summary>
84 public virtual string ScriptDirectory
86 get { return viewEngine.ViewRootDir; }
89 /// <summary>
90 /// Gets the view engine.
91 /// </summary>
92 /// <value>The view engine.</value>
93 public BooViewEngine ViewEngine
95 get { return viewEngine; }
98 /// <summary>
99 /// Gets the DSL provider
100 /// </summary>
101 /// <value>Reference to the current DSL Provider</value>
102 public DslProvider Dsl
106 BrailBase view = this;
107 if (null == view._dsl)
109 view._dsl = new DslProvider(view);
112 return view._dsl;
113 //while (view.parent != null)
115 // view = view.parent;
118 //if (view._dsl == null)
120 // view._dsl = new DslProvider(view);
123 //return view._dsl;
127 /// <summary>
128 /// Gets the flash.
129 /// </summary>
130 /// <value>The flash.</value>
131 public Flash Flash
133 get { return context.Flash; }
136 /// <summary>
137 /// Gets the output stream.
138 /// </summary>
139 /// <value>The output stream.</value>
140 public TextWriter OutputStream
142 get { return outputStream; }
145 /// <summary>
146 /// Gets or sets the child output.
147 /// </summary>
148 /// <value>The child output.</value>
149 public TextWriter ChildOutput
151 get { return childOutput; }
152 set { childOutput = value; }
155 /// <summary>
156 /// Gets the properties.
157 /// </summary>
158 /// <value>The properties.</value>
159 public IDictionary Properties
161 get { return properties; }
164 /// <summary>
165 /// Runs this instance, this is generated by the script
166 /// </summary>
167 public abstract void Run();
169 /// <summary>
170 /// Output the subview to the client, this is either a relative path "SubView" which
171 /// is relative to the current /script/ or an "absolute" path "/home/menu" which is
172 /// actually relative to ViewDirRoot
173 /// </summary>
174 /// <param name="subviewName"></param>
175 public string OutputSubView(string subviewName)
177 return OutputSubView(subviewName, new Hashtable());
180 /// <summary>
181 /// Similiar to the OutputSubView(string) function, but with a bunch of parameters that are used
182 /// just for this subview. This parameters are /not/ inheritable.
183 /// </summary>
184 /// <returns>An empty string, just to make it possible to use inline ${OutputSubView("foo")}</returns>
185 public string OutputSubView(string subviewName, IDictionary parameters)
187 OutputSubView(subviewName, outputStream, parameters);
188 return string.Empty;
191 /// <summary>
192 /// Outputs the sub view to the writer
193 /// </summary>
194 /// <param name="subviewName">Name of the subview.</param>
195 /// <param name="writer">The writer.</param>
196 /// <param name="parameters">The parameters.</param>
197 public void OutputSubView(string subviewName, TextWriter writer, IDictionary parameters)
199 string subViewFileName = GetSubViewFilename(subviewName);
200 BrailBase subView =
201 viewEngine.GetCompiledScriptInstance(subViewFileName, writer, context, __controller, __controllerContext);
202 subView.SetParent(this);
203 foreach(DictionaryEntry entry in parameters)
205 subView.properties[entry.Key] = entry.Value;
207 subView.Run();
208 foreach(DictionaryEntry entry in subView.Properties)
210 if (subView.Properties.Contains(entry.Key + ".@bubbleUp") == false)
211 continue;
212 properties[entry.Key] = entry.Value;
213 properties[entry.Key + ".@bubbleUp"] = true;
217 /// <summary>
218 /// Get the sub view file name, if the subview starts with a '/'
219 /// then the filename is considered relative to ViewDirRoot
220 /// otherwise, it's relative to the current script directory
221 /// </summary>
222 /// <param name="subviewName"></param>
223 /// <returns></returns>
224 public string GetSubViewFilename(string subviewName)
226 //absolute path from Views directory
227 if (subviewName[0] == '/')
228 return subviewName.Substring(1) + viewEngine.ViewFileExtension;
229 return Path.Combine(ScriptDirectory, subviewName) + viewEngine.ViewFileExtension;
232 /// <summary>
233 /// this is called by ReplaceUnknownWithParameters step to create a more dynamic experiance
234 /// any uknown identifier will be translate into a call for GetParameter('identifier name').
235 /// This mean that when an uknonwn identifier is in the script, it will only be found on runtime.
236 /// </summary>
237 /// <param name="name"></param>
238 /// <returns></returns>
239 public object GetParameter(string name)
241 ParameterSearch search = GetParameterInternal(name);
242 if (search.Found == false)
243 throw new MonoRailException("Parameter '" + name + "' was not found!");
244 return search.Value;
247 /// <summary>
248 /// this is called by ReplaceUnknownWithParameters step to create a more dynamic experiance
249 /// any uknown identifier with the prefix of ? will be translated into a call for
250 /// TryGetParameter('identifier name without the ? prefix').
251 /// This method will return null if the value it not found.
252 /// </summary>
253 /// <param name="name"></param>
254 /// <returns></returns>
255 public object TryGetParameter(string name)
257 ParameterSearch search = GetParameterInternal(name);
258 return new IgnoreNull(search.Value);
261 /// <summary>
262 /// Gets the parameter - implements the logic for searching parameters.
263 /// </summary>
264 /// <param name="name">The name.</param>
265 /// <returns></returns>
266 private ParameterSearch GetParameterInternal(string name)
268 LastVariableAccessed = name;
269 //temporary syntax to turn @variable to varaible, imitating :symbol in ruby
270 if (name.StartsWith("@"))
271 return new ParameterSearch(name.Substring(1), true);
272 if (viewComponentsParameters != null)
274 foreach(IDictionary viewComponentProperties in viewComponentsParameters)
276 if (viewComponentProperties.Contains(name))
277 return new ParameterSearch(viewComponentProperties[name], true);
280 if (properties.Contains(name))
281 return new ParameterSearch(properties[name], true);
282 if (parent != null)
283 return parent.GetParameterInternal(name);
284 return new ParameterSearch(null, false);
287 /// <summary>
288 /// Sets the parent.
289 /// </summary>
290 /// <param name="myParent">My parent.</param>
291 public void SetParent(BrailBase myParent)
293 parent = myParent;
296 /// <summary>
297 /// Allows to check that a parameter was defined
298 /// </summary>
299 /// <param name="name"></param>
300 /// <returns></returns>
301 public bool IsDefined(string name)
303 ParameterSearch search = GetParameterInternal(name);
304 return search.Found;
307 /// <summary>
308 /// This is required because we may want to replace the output stream and get the correct
309 /// behavior from components call RenderText() or RenderSection()
310 /// </summary>
311 public IDisposable SetOutputStream(TextWriter newOutputStream)
313 ReturnOutputStreamToInitialWriter disposable = new ReturnOutputStreamToInitialWriter(OutputStream, this);
314 outputStream = newOutputStream;
315 return disposable;
318 /// <summary>
319 /// Will output the given value as escaped HTML
320 /// </summary>
321 /// <param name="toOutput"></param>
322 public void OutputEscaped(object toOutput)
324 if (toOutput == null)
325 return;
326 string str = toOutput.ToString();
327 OutputStream.Write(HttpUtility.HtmlEncode(str));
330 /// <summary>
331 /// Note that this will overwrite any existing property.
332 /// </summary>
333 public void AddProperty(string name, object item)
335 properties[name] = item;
338 /// <summary>
339 /// Adds the view component newProperties.
340 /// This will be included in the parameters searching, note that this override
341 /// the current parameters if there are clashing.
342 /// The search order is LIFO
343 /// </summary>
344 /// <param name="newProperties">The newProperties.</param>
345 public void AddViewComponentProperties(IDictionary newProperties)
347 if (viewComponentsParameters == null)
348 viewComponentsParameters = new ArrayList();
349 viewComponentsParameters.Insert(0, newProperties);
352 /// <summary>
353 /// Removes the view component properties, so they will no longer be visible to the views.
354 /// </summary>
355 /// <param name="propertiesToRemove">The properties to remove.</param>
356 public void RemoveViewComponentProperties(IDictionary propertiesToRemove)
358 if (viewComponentsParameters == null)
359 return;
360 viewComponentsParameters.Remove(propertiesToRemove);
363 public void RenderComponent(string componentName)
365 RenderComponent(componentName, new Hashtable());
368 public void RenderComponent(string componentName, IDictionary parameters)
370 BrailViewComponentContext componentContext =
371 new BrailViewComponentContext(this, null, componentName, OutputStream,
372 new Hashtable(StringComparer.InvariantCultureIgnoreCase));
373 AddViewComponentProperties(componentContext.ComponentParameters);
374 IViewComponentFactory componentFactory = (IViewComponentFactory) context.GetService(typeof(IViewComponentFactory));
375 ViewComponent component = componentFactory.Create(componentName);
376 component.Init(context, componentContext);
377 component.Render();
378 if (componentContext.ViewToRender != null)
380 OutputSubView("/" + componentContext.ViewToRender, componentContext.ComponentParameters);
382 RemoveViewComponentProperties(componentContext.ComponentParameters);
385 /// <summary>
386 /// Initialize all the properties that a script may need
387 /// One thing to note here is that resources are wrapped in ResourceToDuck wrapper
388 /// to enable easy use by the script
389 /// </summary>
390 /// <param name="myContext">My context.</param>
391 /// <param name="myController">My controller.</param>
392 /// <param name="controllerContext">The controller context.</param>
393 private void InitProperties(IEngineContext myContext, IController myController, IControllerContext controllerContext)
396 properties = new Hashtable(StringComparer.InvariantCultureIgnoreCase);
397 //properties.Add("dsl", new DslWrapper(this));
398 properties.Add("Controller", myController);
400 if (myContext != null)
402 properties.Add("request", myContext.Request);
403 properties.Add("response", myContext.Response);
404 properties.Add("session", myContext.Session);
407 if (controllerContext.Resources != null)
409 foreach(string key in controllerContext.Resources.Keys)
411 properties.Add(key, new ResourceToDuck(controllerContext.Resources[key]));
415 if (myContext != null && myContext.Request.QueryString != null)
417 foreach(string key in myContext.Request.QueryString.AllKeys)
419 if (key == null) continue;
420 properties[key] = myContext.Request.QueryString[key];
424 if (myContext != null && myContext.Request.Form != null)
426 foreach(string key in myContext.Request.Form.AllKeys)
428 if (key == null) continue;
429 properties[key] = myContext.Request.Form[key];
434 if (myContext != null && myContext.Flash != null)
436 foreach(DictionaryEntry entry in myContext.Flash)
438 properties[entry.Key] = entry.Value;
442 if (controllerContext.PropertyBag != null)
444 foreach(DictionaryEntry entry in controllerContext.PropertyBag)
446 properties[entry.Key] = entry.Value;
450 if (controllerContext.Helpers != null)
452 foreach(DictionaryEntry entry in controllerContext.Helpers)
454 properties[entry.Key] = entry.Value;
458 if (myContext != null)
460 properties["siteRoot"] = myContext.ApplicationPath;
464 #region Nested type: ParameterSearch
466 private class ParameterSearch
468 private readonly bool found;
469 private readonly object value;
471 public ParameterSearch(object value, bool found)
473 this.found = found;
474 this.value = value;
477 public bool Found
479 get { return found; }
482 public object Value
484 get { return value; }
488 #endregion
490 #region Nested type: ReturnOutputStreamToInitialWriter
492 private class ReturnOutputStreamToInitialWriter : IDisposable
494 private TextWriter initialWriter;
495 private BrailBase parent;
497 public ReturnOutputStreamToInitialWriter(TextWriter initialWriter, BrailBase parent)
499 this.initialWriter = initialWriter;
500 this.parent = parent;
503 #region IDisposable Members
505 public void Dispose()
507 parent.outputStream = initialWriter;
510 #endregion
513 #endregion