Minor changes to improve testability of helpers
[castle.git] / MonoRail / Castle.MonoRail.ActiveRecordScaffold / AbstractScaffoldAction.cs
blob59ddae2fd82b1eabd225770c25a27fdd8d09e87a
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.ActiveRecordScaffold
17 using System;
18 using System.IO;
19 using System.Reflection;
20 using System.Collections;
21 using Castle.ActiveRecord;
22 using Castle.ActiveRecord.Framework.Internal;
23 using Castle.Components.Binder;
24 using Castle.Components.Common.TemplateEngine;
26 using Castle.MonoRail.ActiveRecordScaffold.Helpers;
27 using Castle.MonoRail.ActiveRecordSupport;
28 using Castle.MonoRail.Framework;
29 using Castle.MonoRail.Framework.Helpers;
31 /// <summary>
32 /// Base abstract class for actions that relate to
33 /// Scaffolding support. Provide the basic flow process
34 /// </summary>
35 public abstract class AbstractScaffoldAction : IDynamicAction
37 /// <summary>Holds the AR type</summary>
38 protected readonly Type modelType;
40 /// <summary>Reference to the template engine instance</summary>
41 protected readonly ITemplateEngine templateEngine;
43 /// <summary>A map of PropertyInfo to validation failures</summary>
44 protected IDictionary prop2Validation = new Hashtable();
46 /// <summary>A list of errors that happened during this process</summary>
47 protected ArrayList errors = new ArrayList();
49 /// <summary>Constructs the data source for the binder</summary>
50 protected TreeBuilder builder = new TreeBuilder();
52 /// <summary>Binder that 'knows' ActiveRecord types</summary>
53 protected ARDataBinder binder = new ARDataBinder();
55 /// <summary>The model for the AR type we're dealing with</summary>
56 private ActiveRecordModel model;
58 /// <summary>Used to define if the model name should be present on the action name (urls)</summary>
59 private readonly bool useModelName;
61 /// <summary>Indicates that the controller has no layout, so we use ours</summary>
62 private readonly bool useDefaultLayout;
64 /// <summary>
65 /// Initializes a new instance of the <see cref="AbstractScaffoldAction"/> class.
66 /// </summary>
67 /// <param name="modelType">Type of the model.</param>
68 /// <param name="templateEngine">The template engine.</param>
69 /// <param name="useModelName">Indicates that we should use the model name on urls</param>
70 /// <param name="useDefaultLayout">Whether we should use our layout.</param>
71 public AbstractScaffoldAction(Type modelType, ITemplateEngine templateEngine,
72 bool useModelName, bool useDefaultLayout)
74 this.modelType = modelType;
75 this.templateEngine = templateEngine;
76 this.useModelName = useModelName;
77 this.useDefaultLayout = useDefaultLayout;
79 // Configures the binder
80 binder.AutoLoad = AutoLoadBehavior.OnlyNested;
83 /// <summary>
84 /// Gets a value indicating whether the name of the model should
85 /// be used on the url.
86 /// </summary>
87 /// <value><c>true</c> if yes, otherwise <c>false</c>.</value>
88 public bool UseModelName
90 get { return useModelName; }
93 /// <summary>
94 /// Executes the basic flow which is
95 /// <list type="number">
96 /// <item><description>Resolve the <see cref="ActiveRecordModel"/></description></item>
97 /// <item><description>Resolve the layout (if not is associated with the controller, defaults to "scaffold")</description></item>
98 /// <item><description>Invokes <see cref="PerformActionProcess"/> which should perform the correct process for this action</description></item>
99 /// <item><description>Resolves the template name that the developer might provide by using <see cref="ComputeTemplateName"/></description></item>
100 /// <item><description>If the template exists, renders it. Otherwise invokes <see cref="RenderStandardHtml"/></description></item>
101 /// </list>
102 /// </summary>
103 /// <param name="controller"></param>
104 public void Execute(Controller controller)
106 // We make sure the code is always surrounded by a SessionScope.
107 // If none is found, we create one
109 SessionScope scope = null;
111 if (SessionScope.Current == null)
113 scope = new SessionScope(FlushAction.Never);
118 model = GetARModel();
120 PerformActionProcess(controller);
122 String templateName = ComputeTemplateName(controller);
124 if (controller.HasTemplate(templateName))
126 controller.RenderSharedView(templateName);
128 else
130 RenderStandardHtml(controller);
133 finally
135 if (scope != null)
137 scope.Dispose();
142 /// <summary>
143 /// Implementors should return the template name
144 /// for the current action.
145 /// </summary>
146 /// <param name="controller"></param>
147 /// <returns></returns>
148 protected abstract string ComputeTemplateName(Controller controller);
150 /// <summary>
151 /// Only invoked if the programmer havent provided
152 /// a custom template for the current action. Implementors
153 /// should create a basic html to present.
154 /// </summary>
155 /// <param name="controller"></param>
156 protected abstract void RenderStandardHtml(Controller controller);
158 /// <summary>
159 /// Implementors should perform the action for the
160 /// scaffolding, like new or create.
161 /// </summary>
162 /// <param name="controller"></param>
163 protected virtual void PerformActionProcess(Controller controller)
165 controller.PropertyBag["useModelName"] = useModelName;
166 controller.PropertyBag["model"] = Model;
167 controller.PropertyBag["keyprop"] = ObtainPKProperty();
170 /// <summary>
171 /// Gets the current <see cref="ActiveRecordModel"/>
172 /// </summary>
173 protected ActiveRecordModel Model
175 get { return model; }
178 private ActiveRecordModel GetARModel()
180 ActiveRecordModel foundModel = ActiveRecordModel.GetModel(modelType);
182 if (foundModel == null)
184 throw new ScaffoldException("Specified type is not an ActiveRecord type or the ActiveRecord " +
185 "framework was not started properly. Did you forget to invoke ActiveRecordStarter.Initialize() ?");
188 return foundModel;
191 /// <summary>
192 /// Gets the property that represents the Primary key
193 /// for the current <see cref="ActiveRecordModel"/>
194 /// </summary>
195 /// <returns></returns>
196 protected PropertyInfo ObtainPKProperty()
198 PrimaryKeyModel keyModel = ARCommonUtils.ObtainPKProperty(model);
200 if (keyModel != null)
202 return keyModel.Property;
205 return null;
208 protected void RenderFromTemplate(String templateName, Controller controller)
210 StringWriter writer = new StringWriter();
212 IDictionary context = new Hashtable();
214 context.Add("flash", controller.Context.Flash);
216 foreach(DictionaryEntry entry in controller.PropertyBag)
218 context.Add(entry.Key, entry.Value);
221 #if USE_LOCAL_TEMPLATES
222 templateEngine.Process( context, templateName, writer );
223 #else
224 templateEngine.Process( context, "Castle.MonoRail.ActiveRecordScaffold/Templates/" + templateName, writer );
225 #endif
227 if (useDefaultLayout)
229 StringWriter layoutwriter = new StringWriter();
231 context.Add("childContent", writer.GetStringBuilder().ToString());
233 #if USE_LOCAL_TEMPLATES
234 templateEngine.Process(context, "layout.vm", layoutwriter);
235 #else
236 templateEngine.Process(context, "Castle.MonoRail.ActiveRecordScaffold/Templates/layout.vm", layoutwriter);
237 #endif
239 writer = layoutwriter;
241 controller.CancelView();
242 controller.Response.Write(writer.GetStringBuilder().ToString());
244 else
246 controller.DirectRender(writer.GetStringBuilder().ToString());
250 protected static void SetUpHelpers(Controller controller)
252 ARFormHelper formHelper = new ARFormHelper();
253 formHelper.SetController(controller);
255 ValidationHelper validationHelper = new ValidationHelper();
256 validationHelper.SetController(controller);
258 PresentationHelper presentationHelper = new PresentationHelper();
259 presentationHelper.SetController(controller);
261 PaginationHelper paginationHelper = new PaginationHelper();
262 paginationHelper.SetController(controller);
264 ScriptaculousHelper scriptaculous = new ScriptaculousHelper();
265 scriptaculous.SetController(controller);
267 AjaxHelper ajaxHelper = new AjaxHelper();
268 ajaxHelper.SetController(controller);
270 controller.PropertyBag["Scriptaculous"] = scriptaculous;
271 controller.PropertyBag["Ajax"] = ajaxHelper;
272 controller.PropertyBag["Form"] = formHelper;
273 controller.PropertyBag["ValidationHelper"] = validationHelper;
274 controller.PropertyBag["PresentationHelper"] = presentationHelper;
275 controller.PropertyBag["PaginationHelper"] = paginationHelper;
278 protected static void AssertIsPost(Controller controller)
280 String method = controller.Context.UnderlyingContext.Request.HttpMethod;
282 if (method != "POST")
284 throw new Exception("This action cannot be accessed using the verb " + method);