1 // Copyright 2004-2007 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
.Generator
.Generators
21 using System
.Collections
;
22 using System
.CodeDom
.Compiler
;
24 using Microsoft
.CSharp
;
25 using Microsoft
.VisualBasic
;
30 public class ControllerGenerator
: AbstractGenerator
, IGenerator
32 private bool isNVelocity
;
34 private bool isCSharp
;
35 private DirectoryInfo controllersDir
;
36 private DirectoryInfo viewsDir
;
37 private DirectoryInfo testsDir
;
39 public ControllerGenerator()
43 #region IGenerator Members
45 public bool Accept(String name
, IDictionary options
, TextWriter output
)
47 if ("controller".Equals(name
))
49 if (options
.Values
.Count
== 1)
51 output
.WriteLine("Creates a controller and it's view skeletons");
53 output
.WriteLine("name : Controller name");
54 output
.WriteLine("actions : Comma separated list of action names");
55 output
.WriteLine("ns : Namespace");
56 output
.WriteLine("outdir : Target directory (must exists)");
57 output
.WriteLine("area : [Optional] Controller area/section");
58 output
.WriteLine("smart : [Optional] Extends SmartDispatcherController (defaults to false)");
59 output
.WriteLine("view : [Optional] aspnet|nvelocity (defaults to nvelocity)");
60 output
.WriteLine("lang : [Optional] c#|vb.net (defaults to c#)");
62 output
.WriteLine("Examples:");
64 output
.WriteLine(@"> generator controller name:Home actions:Index,About outdir:c:\temp");
66 output
.WriteLine(@"> generator controller name:Account ns:My.Project actions:Save,Authenticate smart view:aspnet outdir:c:\temp");
68 output
.WriteLine(@"> generator controller name:Home actions:Index,About smart lang:vb.net outdir:c:\temp");
70 output
.WriteLine("Remarks:");
72 output
.WriteLine("The outdir must point to a structure created by 'generator project'");
73 output
.WriteLine("as it relies on the existance of a subdir Controllers and a subdir ");
74 output
.WriteLine("Views.");
75 output
.WriteLine("Also note that if you're using Windsor integration, you need to add");
76 output
.WriteLine("the controller definition to your container:");
78 output
.WriteLine(" container.AddComponent( 'mycontroller', typeof(HomeController) ); ");
83 else if (!options
.Contains("actions"))
85 output
.WriteLine("actions must be specified");
89 else if (!options
.Contains("outdir"))
91 output
.WriteLine("outdir must be specified");
95 else if (!options
.Contains("name"))
97 output
.WriteLine("name must be specified");
103 DirectoryInfo info
= new DirectoryInfo(options
["outdir"] as String
);
107 // info.Create(); // Is it safe to use it?
108 output
.WriteLine("Error: The specified outdir does not exists.");
120 public void Execute(IDictionary options
, TextWriter output
)
122 isNVelocity
= !(options
.Contains("view") && options
["view"].Equals("aspnet"));
123 isSmart
= options
.Contains("smart");
124 isCSharp
= !(options
.Contains("lang") && options
["lang"].Equals("vb.net"));
125 String name
= options
["name"] as String
;
126 String area
= options
["area"] as String
;
127 String
[] actions
= (options
["actions"] as String
).Split(',');
129 String ns
= options
["ns"] as String
;
130 if (ns
== null) ns
= "WebApplication";
132 output
.WriteLine("Creating controller source code...");
134 /// Steps to generate a controller
136 // 1. Resolve directories (controllers, view, tests)
138 output
.WriteLine("Resolving directories...");
139 ResolveDirs(output
, options
);
141 // 2. Generate code (controllers, view, tests)
144 output
.WriteLine("Generating source code...");
145 GenerateCodeAndWrite(name
, area
, actions
, ns
, output
);
148 output
.WriteLine("Done!");
153 private void ResolveDirs(TextWriter output
, IDictionary options
)
155 DirectoryInfo outdir
= new DirectoryInfo(options
["outdir"] as String
);
159 controllersDir
= new DirectoryInfo(Path
.Combine(outdir
.FullName
, "Controllers"));
160 viewsDir
= new DirectoryInfo(Path
.Combine(outdir
.FullName
, "views"));
161 testsDir
= new DirectoryInfo(Path
.Combine(outdir
.FullName
, "../" + outdir
.Parent
+ ".Tests"));
163 if (!controllersDir
.Exists
)
165 outdir
= new DirectoryInfo( Path
.Combine(outdir
.FullName
, outdir
.Name
) );
167 controllersDir
= new DirectoryInfo(Path
.Combine(outdir
.FullName
, "Controllers"));
168 viewsDir
= new DirectoryInfo(Path
.Combine(outdir
.FullName
, "views"));
169 testsDir
= new DirectoryInfo(Path
.Combine(outdir
.FullName
, "../" + outdir
.Parent
+ ".Tests"));
172 if (!controllersDir
.Exists
)
174 throw new ApplicationException("Could not infer directory structure from the specified 'outdir'");
179 output
.WriteLine(ex
.Message
);
184 private void GenerateCodeAndWrite(string name
, string area
, string[] actions
, String ns
, TextWriter output
)
186 CodeDomProvider provider
= (isCSharp
) ? new CSharpCodeProvider() as CodeDomProvider
: new VBCodeProvider() as CodeDomProvider
;
187 String fileExtension
= (isCSharp
) ? ".cs" : ".vb";
189 GenerateController(provider
, fileExtension
, ns
, name
, area
, actions
, output
);
191 GenerateViews(name
, actions
);
193 GenerateTestCase(provider
, fileExtension
, ns
, name
, area
, actions
, output
);
196 private string Quote(string value)
198 return String
.Format("\"{0}\"", value);
201 private void GenerateController(CodeDomProvider provider
, string fileExtension
, string ns
, string name
,
202 string area
, string[] actions
, TextWriter output
)
204 CodeNamespace thisNs
= GenerateControllerCode(ns
, name
, area
, actions
);
206 FileInfo controllerFile
= new FileInfo(
207 Path
.Combine(controllersDir
.FullName
, name
+ fileExtension
) );
209 if (!controllerFile
.Exists
)
211 using (StreamWriter sw
= new StreamWriter(controllerFile
.FullName
, false, Encoding
.Default
))
213 CodeGeneratorOptions opts
= new CodeGeneratorOptions();
214 opts
.BracingStyle
= "C";
216 provider
.GenerateCodeFromNamespace(thisNs
, sw
, opts
);
218 provider
.CreateGenerator().GenerateCodeFromNamespace(thisNs
, sw
, opts
);
224 output
.WriteLine("Skipping {0} as the files exists", controllerFile
.FullName
);
228 private CodeNamespace
GenerateControllerCode(string ns
, string name
, string area
, string[] actions
)
230 CodeNamespace thisNs
= new CodeNamespace(ns
);
231 thisNs
.Imports
.Add(new CodeNamespaceImport("System"));
232 thisNs
.Imports
.Add(new CodeNamespaceImport("Castle.MonoRail.Framework"));
233 // thisNs.Comments.Add(new CodeCommentStatement("Ignore the above comment or better, delete it"));
235 CodeTypeDeclaration controllerType
= new CodeTypeDeclaration(name
);
236 thisNs
.Types
.Add(controllerType
);
240 controllerType
.CustomAttributes
.Add(
241 new CodeAttributeDeclaration("ControllerDetails",
242 new CodeAttributeArgument(new CodeSnippetExpression(Quote(StripControllerFrom(name
)))),
243 new CodeAttributeArgument("Area", new CodeSnippetExpression(Quote(area
)))) );
247 controllerType
.CustomAttributes
.Add(
248 new CodeAttributeDeclaration("ControllerDetails",
249 new CodeAttributeArgument(new CodeSnippetExpression(Quote(StripControllerFrom(name
))))) );
254 controllerType
.BaseTypes
.Add( "SmartDispatcherController" );
258 controllerType
.BaseTypes
.Add( "Controller" );
261 foreach(String action
in actions
)
263 CodeMemberMethod actionMethod
= new CodeMemberMethod();
264 actionMethod
.Name
= action
;
265 actionMethod
.Attributes
= MemberAttributes
.Public
;
267 controllerType
.Members
.Add(actionMethod
);
273 private void GenerateViews(String name
, String
[] actions
)
275 DirectoryInfo viewSubDir
= viewsDir
.CreateSubdirectory(StripControllerFrom(name
));
277 foreach(String action
in actions
)
279 Hashtable ctx
= new Hashtable();
280 ctx
.Add("controller", name
);
281 ctx
.Add("action", action
);
282 ctx
.Add("dir", viewSubDir
.FullName
);
286 WriteTemplateFile( Path
.Combine(viewSubDir
.FullName
, action
+ ".vm"), ctx
, "newview.vm" );
290 WriteTemplateFile( Path
.Combine(viewSubDir
.FullName
, action
+ ".aspx"), ctx
, "newviewaspx.vm" );
295 private void GenerateTestCase(CodeDomProvider provider
, string extension
, string ns
, string name
, string area
,
296 string[] actions
, TextWriter output
)
298 CodeNamespace nsunit
= GenerateControllerTestCode(ns
, name
, area
, actions
);
300 FileInfo controllerFile
= new FileInfo(
301 Path
.Combine(testsDir
.FullName
, name
+ "Tests" + extension
) );
303 if (!controllerFile
.Exists
)
305 using (StreamWriter sw
= new StreamWriter(controllerFile
.FullName
, false, Encoding
.Default
))
307 CodeGeneratorOptions opts
= new CodeGeneratorOptions();
308 opts
.BracingStyle
= "C";
310 provider
.GenerateCodeFromNamespace(nsunit
, sw
, opts
);
312 provider
.CreateGenerator().GenerateCodeFromNamespace(nsunit
, sw
, opts
);
318 output
.WriteLine("Skipping {0} as the files exists", controllerFile
.FullName
);
322 private CodeNamespace
GenerateControllerTestCode(string ns
, string name
, string area
, string[] actions
)
324 String controllerName
= StripControllerFrom(name
);
326 CodeNamespace thisNs
= new CodeNamespace(ns
+ ".Tests");
328 thisNs
.Imports
.Add(new CodeNamespaceImport("System"));
329 thisNs
.Imports
.Add(new CodeNamespaceImport("NUnit.Framework"));
330 thisNs
.Imports
.Add(new CodeNamespaceImport("Castle.MonoRail.TestSupport"));
332 CodeTypeDeclaration controllerType
= new CodeTypeDeclaration(name
+ "TestCase");
333 thisNs
.Types
.Add(controllerType
);
335 controllerType
.BaseTypes
.Add("AbstractMRTestCase");
336 controllerType
.CustomAttributes
.Add( new CodeAttributeDeclaration("TestFixture") );
338 foreach(String action
in actions
)
340 CodeMemberMethod actionTestMethod
= new CodeMemberMethod();
341 actionTestMethod
.Name
= action
;
342 actionTestMethod
.Attributes
= MemberAttributes
.Public
;
343 actionTestMethod
.CustomAttributes
.Add( new CodeAttributeDeclaration("Test") );
347 if (area
== null || area
.Length
== 0)
349 url
= String
.Format("\"{0}/{1}.rails\"", controllerName
, action
);
353 url
= String
.Format("\"{0}/{1}/{2}.rails\"", area
, controllerName
, action
);
356 String expected
= string.Format("\"View for {0} action {1}\"", name
, action
);
358 CodeMethodInvokeExpression doGetInvoke
= new CodeMethodInvokeExpression();
359 doGetInvoke
.Method
= new CodeMethodReferenceExpression(null, "DoGet");
360 doGetInvoke
.Parameters
.Add( new CodeSnippetExpression(url
) );
361 actionTestMethod
.Statements
.Add( new CodeExpressionStatement(doGetInvoke
) );
363 CodeMethodInvokeExpression assertSuccess
= new CodeMethodInvokeExpression();
364 assertSuccess
.Method
= new CodeMethodReferenceExpression(null, "AssertSuccess");
365 actionTestMethod
.Statements
.Add( new CodeExpressionStatement(assertSuccess
) );
367 CodeMethodInvokeExpression assertReply
= new CodeMethodInvokeExpression();
368 assertReply
.Method
= new CodeMethodReferenceExpression(null, "AssertReplyContains");
369 assertReply
.Parameters
.Add( new CodeSnippetExpression(expected
) );
370 actionTestMethod
.Statements
.Add( new CodeExpressionStatement(assertReply
) );
372 controllerType
.Members
.Add(actionTestMethod
);
378 private string StripControllerFrom(string value)
380 if (value.EndsWith("controller"))
382 return value.Substring(0, value.IndexOf("controller"));
384 else if (value.EndsWith("Controller"))
386 return value.Substring(0, value.IndexOf("Controller"));