Minor changes to improve testability of helpers
[castle.git] / MonoRail / Castle.MonoRail.Framework / Services / DefaultViewEngineManager.cs
blob45bbb76bb227ef885b2edb5a3ab5e199ef6cc761
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.Framework.Services
17 using System;
18 using System.Collections;
19 using System.Collections.Specialized;
20 using System.IO;
21 using Castle.Core;
22 using Castle.MonoRail.Framework.Configuration;
23 using Castle.MonoRail.Framework.Internal;
25 /// <summary>
26 /// The view engine manager sits between MonoRail and all the registered
27 /// view engines. It is used to identify the view engine that should handle a
28 /// render request and delegates such requests properly.
29 /// </summary>
30 public class DefaultViewEngineManager : IViewEngineManager, IServiceEnabledComponent, IInitializable
32 private MonoRailConfiguration config;
33 private IServiceProvider provider;
34 private IDictionary ext2ViewEngine;
35 private IDictionary viewEnginesFastLookup;
36 private IDictionary jsgFastLookup;
38 /// <summary>
39 /// Initializes a new instance of the <see cref="DefaultViewEngineManager"/> class.
40 /// </summary>
41 public DefaultViewEngineManager()
43 ext2ViewEngine = new HybridDictionary(true);
44 jsgFastLookup = new HybridDictionary(true);
45 viewEnginesFastLookup = new Hashtable();
48 #region IInitializable
50 /// <summary>
51 /// Implementors should perform any initialization logic.
52 /// </summary>
53 public void Initialize()
55 foreach (ViewEngineInfo info in config.ViewEngineConfig.ViewEngines)
57 try
59 IViewEngine engine = (IViewEngine)Activator.CreateInstance(info.Engine);
61 RegisterEngineForView(engine);
63 RegisterEngineForExtesionLookup(engine);
65 engine.XHtmlRendering = info.XhtmlRendering;
67 IServiceEnabledComponent serviceEnabled = engine as IServiceEnabledComponent;
69 if (serviceEnabled != null)
71 serviceEnabled.Service(provider);
74 IInitializable initializable = engine as IInitializable;
76 if (initializable != null)
78 initializable.Initialize();
81 catch (InvalidCastException)
83 throw new RailsException("Type " + info.Engine.FullName + " does not implement IViewEngine");
85 catch (Exception ex)
87 throw new RailsException("Could not create view engine instance: " + info.Engine, ex);
91 config = null;
94 private void RegisterEngineForExtesionLookup(IViewEngine engine)
96 viewEnginesFastLookup.Add(engine, null);
99 #endregion
101 #region IServiceEnabledComponent
103 /// <summary>
104 /// Services the specified service provider.
105 /// </summary>
106 /// <param name="serviceProvider">The service provider.</param>
107 public void Service(IServiceProvider serviceProvider)
109 provider = serviceProvider;
111 config = (MonoRailConfiguration)provider.GetService(typeof(MonoRailConfiguration));
114 #endregion
116 #region IViewEngineManager
118 /// <summary>
119 /// Evaluates whether the specified template exists.
120 /// </summary>
121 /// <param name="templateName">View template name</param>
122 /// <returns><c>true</c> if it exists</returns>
123 public bool HasTemplate(String templateName)
125 IViewEngine engine = ResolveEngine(templateName, false);
126 if (engine == null)
127 return false;
128 return engine.HasTemplate(templateName);
131 /// <summary>
132 /// Processes the view - using the templateName
133 /// to obtain the correct template,
134 /// and using the context to output the result.
135 /// </summary>
136 /// <param name="context"></param>
137 /// <param name="controller"></param>
138 /// <param name="templateName"></param>
139 public void Process(IRailsEngineContext context, IController controller, string templateName)
141 IViewEngine engine = ResolveEngine(templateName);
143 ContextualizeViewEngine(engine);
145 if (engine.SupportsJSGeneration && engine.IsTemplateForJSGeneration(templateName))
147 engine.GenerateJS(context, controller, templateName);
148 return;
151 engine.Process(context, controller, templateName);
154 /// <summary>
155 /// Processes the view - using the templateName
156 /// to obtain the correct template
157 /// and writes the results to the System.TextWriter.
158 /// <para>
159 /// Please note that no layout is applied
160 /// </para>
161 /// </summary>
162 /// <param name="output"></param>
163 /// <param name="context"></param>
164 /// <param name="controller"></param>
165 /// <param name="templateName"></param>
166 public void Process(TextWriter output, IRailsEngineContext context, IController controller, string templateName)
168 IViewEngine engine = ResolveEngine(templateName);
170 ContextualizeViewEngine(engine);
172 if (engine.SupportsJSGeneration && engine.IsTemplateForJSGeneration(templateName))
174 engine.GenerateJS(output, context, controller, templateName);
175 return;
178 engine.Process(output, context, controller, templateName);
181 /// <summary>
182 /// Processes a partial view = using the partialName
183 /// to obtain the correct template and writes the
184 /// results to the System.TextWriter.
185 /// </summary>
186 /// <param name="output">The output.</param>
187 /// <param name="context">The context.</param>
188 /// <param name="controller">The controller.</param>
189 /// <param name="partialName">The partial name.</param>
190 public void ProcessPartial(TextWriter output, IRailsEngineContext context, IController controller, string partialName)
193 IViewEngine engine = ResolveEngine(partialName);
195 engine.ProcessPartial(output, context, controller, partialName);
198 /// <summary>
199 /// Wraps the specified content in the layout using
200 /// the context to output the result.
201 /// </summary>
202 public void ProcessContents(IRailsEngineContext context, IController controller, String contents)
204 if (controller.LayoutName == null)
206 throw new RailsException("ProcessContents can only work with a layout");
209 String templateName = Path.Combine("layouts", controller.LayoutName);
211 IViewEngine engine = ResolveEngine(templateName);
213 engine.ProcessContents(context, controller, contents);
216 #endregion
218 /// <summary>
219 /// Contextualizes the view engine.
220 /// </summary>
221 /// <param name="engine">The engine.</param>
222 private void ContextualizeViewEngine(IViewEngine engine)
224 MonoRailHttpHandler.CurrentContext.AddService(typeof(IViewEngine), engine);
227 /// <summary>
228 /// The view can be informed with an extension. If so, we use it
229 /// to discover the extension. Otherwise, we ask the configured
230 /// view engines to find out which (if any) can handle the requested
231 /// view. If no suitable view engine is found, an exception would be thrown
232 /// </summary>
233 /// <param name="templateName">View name</param>
234 /// <returns>A view engine instance</returns>
235 private IViewEngine ResolveEngine(String templateName)
237 return ResolveEngine(templateName, true);
240 /// <summary>
241 /// The view can be informed with an extension. If so, we use it
242 /// to discover the extension. Otherwise, we ask the configured
243 /// view engines to find out which (if any) can handle the requested
244 /// view.
245 /// </summary>
246 /// <param name="throwIfNotFound">If true and no suitable view engine is found, an exception would be thrown</param>
247 /// <param name="templateName">View name</param>
248 /// <returns>A view engine instance</returns>
249 private IViewEngine ResolveEngine(String templateName, bool throwIfNotFound)
251 if (Path.HasExtension(templateName))
253 String extension = Path.GetExtension(templateName);
254 IViewEngine engine = ext2ViewEngine[extension] as IViewEngine;
255 return engine;
258 foreach (IViewEngine engine in viewEnginesFastLookup.Keys)
259 if (engine.HasTemplate(templateName))
260 return engine;
262 if (throwIfNotFound)
263 throw new RailsException(string.Format(
264 @"MonoRail could not have resolved a view engine instance for the template '{0}'
265 There are two possible explanations: that the template does not exist, or that the relevant view engine has not been configured correctly in web.config.", templateName));
267 return null;
270 /// <summary>
271 /// Associates extensions with the view engine instance.
272 /// </summary>
273 /// <param name="engine">The view engine instance</param>
274 private void RegisterEngineForView(IViewEngine engine)
276 if (ext2ViewEngine.Contains(engine.ViewFileExtension))
278 IViewEngine existing = (IViewEngine)ext2ViewEngine[engine.ViewFileExtension];
280 throw new RailsException(
281 "At least two view engines are handling the same file extension. " +
282 "This isn't going to work. Extension: " + engine.ViewFileExtension +
283 " View Engine 1: " + existing.GetType() +
284 " View Engine 2: " + engine.GetType());
287 String extension = engine.ViewFileExtension.StartsWith(".")
288 ? engine.ViewFileExtension
289 : "." + engine.ViewFileExtension;
291 ext2ViewEngine[extension] = engine;
293 if (engine.SupportsJSGeneration && ext2ViewEngine.Contains(engine.JSGeneratorFileExtension))
295 IViewEngine existing = (IViewEngine)ext2ViewEngine[engine.JSGeneratorFileExtension];
297 throw new RailsException(
298 "At least two view engines are handling the same file extension. " +
299 "This isn't going to work. Extension: " + engine.JSGeneratorFileExtension +
300 " View Engine 1: " + existing.GetType() +
301 " View Engine 2: " + engine.GetType());
304 if (engine.SupportsJSGeneration)
306 extension = engine.JSGeneratorFileExtension.StartsWith(".")
307 ? engine.JSGeneratorFileExtension
308 : "." + engine.JSGeneratorFileExtension;
310 ext2ViewEngine[extension] = engine;
311 jsgFastLookup[extension] = engine;