Monorail tutorial

1 Reusing UI portions (ViewComponents)

一些ui部分在一些页面经常被复用。如果这些内容决大多数是静态的内容,我们可以使用ViewComponentViewComponent类和Controller类的功能类似。都可以使用views,可以传送数据去view。同样还支持inner sections和paraments。

 

Creating a ViewComponent

ViewComponent类
继承自ViewComponent抽象类。有3个方法可以重载 Initialize:用来初始化view component的状态,通常用来核查提供的参数。
Render:(selects the view or uses another approach to render the component content)选择一个view或者使用 SupportsSection:invoked by the view engine to check if the component supports the section supplied on the view

 

using Castle.MonoRail.Framework;

public class HeaderComponent : ViewComponent
{
}

 

上面的ViewComponent因为客户什么都没定义所以返回1个default行为。这个default行为呈现出和这个Component相关的view,这个view是 views/components/headercomponent/default.vm.

像Controller一样你可以选择不同的view

 

 

using Castle.MonoRail.Framework;

public class HeaderComponent : ViewComponent

{
    
public override void Render()
        
{
            RenderView(
"otherview");
        }

}

 


上面的代码会选择views/components/headercomponent/otherview.vm.

Using a ViewComponent

ViewComponentController没有关系,只和被Controller选定的views有关

 

#component(HeaderComponent)
#blockcomponent(NewsComponent)
<ul>
#foreach($new in $news)
 
<li>$news.Date $news.Title</li>
#end
</ul>
#end

 

Using parameters

你可以提供一些参数给ViewComponent.使用ComponentParams可以在ViewComponent使用参数}

 

using Castle.MonoRail.Framework;
public class TableComponent : ViewComponent
{
    
private ICollection elements;
    
private object border;
    
private string style;
    
private object cellpadding;
    
private object cellspacing;
 
    
public override void Initialize()
      
{
          elements 
= (ICollection) ComponentParams["elements"];
          border 
= ComponentParams["border"];
          style 
= (String) ComponentParams["style"];
          cellpadding 
= ComponentParams["cellpadding"];
          cellspacing 
= ComponentParams["cellspacing"];

        
base.Initialize();
      }

      

 

 

 

#blockcomponent(TableComponent with "elements=$items" "border=0" "style=border: 1px solid black;" "cellpadding=0" "cellspacing=2")



#end

 

 Block and nested sections

 

#blockcomponent(RepeatComponent)
This is the inner content
$counter
#end

 

 

 

using Castle.MonoRail.Framework;

public class RepeatComponent : ViewComponent
{
    
public override void Render()
      
{
        
for(int i=0; i < 5; i++)
          
{
              PropertyBag[
"counter"= i;
              Context.RenderBody();
          }

      }

}


 

 

 

#blockcomponent(TableComponent with "elements=$items")
#colheaders
<tr>
    
<th>&nbsp;</th>
    
<th>Element</th>
</tr>
#end

#item
<tr>
    
<td>$index</td>
    
<td>$item</td>
</tr>
#end
 

#altitem
<tr>
    
<td align="center">$index</td>
    
<td>$item</td>
</tr>
#end

#end

 

 

 

 

using Castle.MonoRail.Framework;

public class TableComponent : ViewComponent
{
    
private ICollection elements;
    
private object border;
    
private string style;
    
private object cellpadding;
    
private object cellspacing;

    
public override void Initialize()
      
{
          elements 
= (ICollection) ComponentParams["elements"];

          border 
= ComponentParams["border"];
          style 
= (String) ComponentParams["style"];

          cellpadding 
= ComponentParams["cellpadding"];

          cellspacing 
= ComponentParams["cellspacing"];

       
base.Initialize();
      }


    
public override void Render()
      
{
          RenderText(

              String.Format(
"<table border=\"{0}\" style=\"{1}\" cellpadding=\"{2}\" cellspacing=\"{3}\">"

                            border, style, cellpadding, cellspacing));

        
if (Context.HasSection("colheaders"))
          
{
              Context.RenderSection(
"colheaders");
          }


        
if (elements != null)
          
{
            
int index = 0;
            
foreach(object item in elements)
              
{
                  PropertyBag[
"index"= ++index;
                  PropertyBag[
"item"= item;

                
if (Context.HasSection("altitem"&& index % 2 != 0)
                  
{
                      Context.RenderSection(
"altitem");
                  }

                
else
                  
{
                      Context.RenderSection(
"item");
                  }

              }

          }


          RenderText(
"</table>");
      }


    
public override bool SupportsSection(string name)
      
{
        
return name == "colheaders" || name == "item" || name == "altitem";
      }

}


 

 更多关于 SmartDispatcherController

 Source

默认的binder使用Params收集参数,你也可以自定义这些:

 

using Castle.MonoRail.Framework;
 
public class ProductController : SmartDispatcherController
{
    
public void Create([DataBind("product", From=ParamStore.Form)] Product product)
       
{
           
       }

}

 


Defining accessible properties

DataBindAttribute经常作用于Domain Model Class,你可能不想所有的属性都可绑定。你可以使用Allow Exclude

用逗号分隔属性

 

public class AccountController : SmartDispatcherController 
{
    
public void CreateAccount([DataBind("account", Allow="Name,Email,Password")] Account account)
       

           
       }

}

 

上面的例子表示你只想 Name,Email,Password属性绑定值,其它的都被忽视

Exclude属性和Allow属性刚好相反。

 

 Binding Errors

 Binding errors可能出现,如验证日期。使用简单的binding ,异常会被抛出,当使用了DataBindAttribute,异常不会被抛出

 可以使用GetDataBindErrors方法进入错误信息

 

 

public class AccountController : SmartDispatcherController 
{
    
public void CreateAccount([DataBind("account")] Account account)
      

          ErrorList errors 
= GetDataBindErrors(account);

          
      }

}


 

ErrorList继承自ICollection,所以你可以枚举错误信息。你也可以检查一个属性的转换。

 

public class AccountController : SmartDispatcherController 
{
    
public void CreateAccount([DataBind("account")] Account account)
      

          ErrorList errors 
= GetDataBindErrors(account);

        
if (errors.Contains("DateOfBirth"))
          
{
              Flash[
"error"= errors["DateOfBirth"].ToString(); // Or Exception

              RedirectToAction(
"New", Params);
          }

          
      }

}

 

BindObject and BindObjectInstance

You do not need to always use parameters to have an object bound. The methods BindObject and BindObjectInstance, exposed by the SmartDispatcherController, allow you to have the same functionality. The benefit is that not under every case you want to perform the bindings. For example: 

 

public class AccountController : SmartDispatcherController 
{
    
public void CreateAccount(bool acceptedConditions)
      

        
if (acceptedConditions)
          
{
              Account account 
= (Account) BindObject(ParamStore.Form, typeof(Account), "account");

              
          }


          

      }

}


 

 

SmartDispatcherController

 SmartDispatcherController继承自Controller,支持参数绑定。他允许你绑定来自form的参数到actionarguments。另外还支持重载。

 monorail可以绑定简单的值还可以绑定复杂的对象。

 Simple binding

考虑下面的html表单

 

<form action="/User/Search.rails" method="post"> 
         Name: 
<input type="text" name="name" /> 
         Email: 
<input type="text" name="email" /> 
         Country: 
    
<select name="country"> 
        
<option value="44">England</option>
        
<option value="55">Brazil</option>
    
</select>
    
<input type="submit" value="Search" />
</form> 

 

 标准的在Cntroller获得窗体元素值的,方法如下

Params:has query string, form and environment entries

Form:Has only form entries (method post)

Query:Has only query string entries

eg:

 

using Castle.MonoRail.Framework;

public class UserController : Controller
{
    
public void Search()
         
{
             String name 
= Form["name"];
             String email 
= Form["email"];
             String country 
= Form["country"];

        
// Perform search 
         }

}


 

 

现在如果你使用SmartDispatcherController你可以用更简洁的代码代替上面的

 

using Castle.MonoRail.Framework;
public class UserController : SmartDispatcherController
{
    
public void Search(string name, string email, string country)
         
{
        
// Perform search 
         }

}

 

SmartdispathcherController履行一些约定(更多在下面)。如果值没被提供,参数会假设是个默认值。另外,如果提供了值,但是无法转换成相应的类型,那么就会throw exception,这个action就不会执行了

Array support

 Controller支持Array。你可以使用2种方法使得窗体元素工作。

 1种方法是重复这个元素的name

eg:

 

<form action="SaveValues.rails" method="post"> 
    
<input type="text" name="name" value="1" /> 
    
<input type="text" name="name" value="2" /> 
    
<input type="text" name="name" value="3" /> 
    
<input type="text" name="name" value="4" /> 
    
<input type="text" name="name" value="5" /> 
</form>    

 

 

2种方法是使用indexed value notationindex value monorail来说是没什么意义的,但是必须唯一。

<form action="SaveValues.rails" method="post"
    <input type="text" name="name[0]" value="1" /
    <input type="text" name="name[1]" value="2" /
    <input type="text" name="name[2]" value="3" /
    <input type="text" name="name[3]" value="4" /
    <input type="text" name="name[4]" value="5" /
</form>    

 

eg:

using Castle.MonoRail.Framework;
 
public class UserController : SmartDispatcherController
{
    public void SaveValues(string[] name)
         {
             ...
         }
}

 

Using the DataBindAttribute

比起一般的值来,我相信你更想绑定对象,使用DataBindAttribute这一切将变得可能

First of all you must use a prefix which is required to avoid name clashing. It is as giving the form elements a name space. The form below uses product as a prefix:

简单的值,聚合对象,数组都被支持。

值得注意的是,只用form元素使用了正确的name约定,binder才会工作。

首先你需要你一个前缀,这样就能避免name发生冲突。这相当于给了窗体元素一个命名空间。

下面的窗体使用 product作为前缀。

eg:

<form method="post" action="create.rails">
<input type="text" name="product.id" />
<input type="text" name="product.name" />
<input type="checkbox" name="product.inStock" id=" value="true" />
</form>

On the controller action you must specify the prefix as the argument to the DataBindAttribute:

 

Controller Action 1 你必须把上面使用的product前缀最为DataBind的参数


using Castle.MonoRail.Framework;
 
public class ProductController : SmartDispatcherController
{
    public void Create([DataBind("product")] Product prod)
        {
        }
}
 
public class Product
{
    private int id;
    private String name;
    private bool inStock;         
    public int Id
        {
            get { return id; }
            set { id = value; }
        }
 
    public string Name
        {
            get { return name; }
            set { name = value; }
        }
 
    public bool InStock
        {
            get { return inStock; }
            set { inStock = value; }
        }
}

 

Arrays

1.窗体元素必须使用indexed notation(像之前所说的)

<form method="post" action="create.rails">
<input type="text" name="product[0].id" />
<input type="text" name="product[0].name" />
<input type="checkbox" name="product[0].inStock" id=" value="true" />
 
<input type="text" name="product[1].id" />
<input type="text" name="product[1].name" />
<input type="checkbox" name="product[1].inStock" id=" value="true" />
</form>

2.Controller必须声明参数是Products数组类型

using Castle.MonoRail.Framework;
 
public class ProductController : SmartDispatcherController
{
    public void Create([DataBind("product")] Product[] prods)
      {
      }
}
 
 
 
public class Product
{
    private Category[] categories;
 
    // others fields omitted
    
    public Category[] Categories
      {
          get { return categories; }
          set { categories = value; }
      }
 
    // others properties omitted
}
 
 
public class Category
{
    private String name;
 
    public string Name
      {
          get { return name; }
          set { name = value; }
      }
}
<form method="post" action="create.rails">
<input type="text" name="product.id" />
<input type="text" name="product.name" />
<input type="checkbox" name="product.inStock" id=" value="true" />
 
<input type="checkbox" name="product.categories[0].name" value="Kitchen" /
<input type="checkbox" name="product.categories[1].name" value="Bedroom" /
<input type="checkbox" name="product.categories[2].name" value="Living-room" /
</form>

Layouts

Layout 允许你使用template ,这样你可以在每个View使用一些公共的html

Layout是标准的View,大那时他们需要放在名字为layouts的活页夹下,layouts活页夹必须放在views活页夹下。

你可以使用Layout AttributeControllerLayout关联起来。

eg:

using Castle.MonoRail.Framework;
 
[Layout("application")]
public class CustomerController : Controller
{
    public void Index()
       {
       }
}

 

In some scenarios you might want to turn off the layout processing. To do so use CancelLayout method. There are other cases where you want to render a specific view and turn off layout at the same time. The RenderView and RenderSharedView have overloads to allow you to do that.

某些情况下你不想呈现layout。你可以使用CancelLayout取消呈现layout。

使用下面的方法也可以取消呈现layout

RenderView(String name, bool skipLayout)
RenderView(String controller, String name, bool skipLayout)
RenderSharedView(String name, bool skipLayout)

using Castle.MonoRail.Framework;
 
[Layout("application")]
public class CustomerController : Controller
{
    public void Index()
       {
           RenderView("welcome"true);
       }
}

 


<html>
 
Welcome
 
$childContent
 
Footer
 
</html>

Rescues

rescue是一个特殊的view,只有在发生异常的时候才呈现出来。rescue的视图文件必须在views活页夹下的rescues活页夹下。

rescue可以和一个controller或者1action联系起来工作。你可以为一个特定的exception1rescue.
如果1action发生了exception,MonoRail会匹配和这个exception最接近的定义了的rescue

using Castle.MonoRail.Framework;
 
[Rescue("dberror"typeof(System.Data.SqlException))]
public class ProductController : Controller
{
    [Rescue("commonerror")]
    public void Index()
      {
        throw new System.Data.SqlException("fake error");
      }
 
    [Rescue("dumbprogrammer"typeof(DivideByZeroException))]
    public void List()
      {
        int val = 0;
        int x = 10 / val;
      }
 
    public void Search()
      {
      }

 

上面的rescue定义了下面的规则

1.这个Controller的任何action    throws SqlExceptionview/rescues/dberror 将被呈现

2.如果Index Action发生任意exception(包括SqlException),view/rescues/commonerror将呈现,他会覆盖Controller级别的Rescue

3.如果List Action throws    DivideByZeroException view/rescues/dumbprogrammer 将呈现

Flash

Flash是一种在Requests之间保留暂时值的方法。当你执行一些操作然后redirect的时候十分有用。

redirect目标页面你可以check flash得到一些状态玛,或者错误信息等

using Castle.MonoRail.Framework;
 
public class AdminController : Controller
{
    public void PasswordManagement()
       {
       }
 
    public void ChangePassword()
       {
           String passwd = Params["password"];
    
        if (passwd.Length < 6)
           {
               Flash["error"] = "Password too weak, operation aborted";
           }
        else
           {
            // Change password
           }
    
           RedirectToAction("PasswordManagement");
       }
}

 

上面的代码可能不太清楚。下面让我们看看到底发生了什么

1 用户进入PasswordManagement Action

2 一个包含修改密码的     post     ChangePassword action form的页面出现

3。执行ChangePassword action的时候     添加一些实体到Flash里面

4。用户又被sendPasswordManagement action

5PasswordManagement View检查 Flash,显示一些有意义的信息

Working with Views

活页夹结构约定

必须有一个活页夹。如果Controllerarea联系,那么这个必须反映在View活页夹结构上选择一个View呈现

action被调用的时候mr会预选择和这个action同名的view呈现。

eg:


using Castle.MonoRail.Framework;
 
public class CustomerController : Controller
{
    public void Index()
          {
          }
}

 

index action被调用,views\customer\index View会被预选择

如果我们不想views\customer\index view被呈现,我们可以使用RenderView方法选择不同的view

using Castle.MonoRail.Framework;
 
public class CustomerController : Controller
{
    public void Index()
         {
             RenderView("welcome");
         }
}

 

上面的代码选择了一个 views\customer\welcome view

 

Note:

RenderView被调用的时候View并不执行,只是选择了。只有在action方法返回的时候view才执行

传参数给View

You would probably want to supply data to the view so it can generate dynamic content. This should be done using the PropertyBag. For example:

可能你想提供一些数据给view这样你就能生成动态内容。这应该使用PropertyBag

eg:

using Castle.MonoRail.Framework;
public class TestController : Controller
{
    public void ShowTime()
         {
             PropertyBag["now"] = DateTime.Now;
         }
}

 

PropertyBag 是一个词典。每种View引擎使用一个信道使得数据在view里面可得。下面的例子使用引擎

NVelocity

eg:

<html>
Hello, the time now is $now
</html>

 

共享Views

 

一些Views可能被某些Controllers共享。这种情况我们使用RenderShareView

using Castle.MonoRail.Framework;
public class CustomerController : Controller
{
    public void Index()     
        {         
           RenderSharedView("common/welcome");
        }
}

 

上面的代码选择views\common\welcome view

取消View

尽管听起来很奇怪,在某些情况下我们不想view呈现

我们可以使用CancelView 方法

using Castle.MonoRail.Framework;
public class CustomerController : Controller
{
    public void Index()
        {
            CancelView();
        }
}

 

Filters

Filtersaction的前后执行。对于安全来说这个很有用(提交数据之前进行验证),还可以减少重复代码

创建一个Filter

Filter必须实现IFilter接口,这样filter才能和你的Controller结合起来

using Castle.MonoRail.Framework;
 
public class AuthenticationFilter : IFilter
{
    public bool Perform(ExecuteEnum exec, IRailsEngineContext context, Controller controller)
          {
        if (context.Session.Contains("user"))
              {
            return true;
              }
        else
              {
                  context.Response.Redirect("account""login");
              }
 
        return false;
          }
}

 

 

ExecuteEnum属性可以设置Filter在什么时候被执行。

ExecuteEnum fields Description

BeforeAction action之前执行.

AfterAction action之后执行.

AfterRendering rendering后执行.

Always 每一步都执行.

只用FilterAttribute可以把filterController联系起来

using Castle.MonoRail.Framework;
 
[FilterAttribute(ExecuteEnum.BeforeAction, typeof(AuthenticationFilter))]
public class AdminController : Controller
{
    public void Index()
          {
          }
}

 

 

Ordering

 

一个Controller可以使用多个Filter,而且可以使用ExecutionOrder属性对Filter执行的先后进行排序。

值越低,优先级越高。

eg:

using Castle.MonoRail.Framework;
 
[FilterAttribute(ExecuteEnum.BeforeAction, typeof(AuthenticationFilter), ExecutionOrder=0)]
[FilterAttribute(ExecuteEnum.BeforeAction, typeof(LocalizationFilter), ExecutionOrder=1)]
public class AdminController : Controller
{
    public void Index()
         {
         }

 

上面的代码,AuthenticationFilter LocalizationFilter之前执行

Skipping filters

有些情况下我们不想1action或多个action执行一个或多个filter

那么我们可以使用SkipFilter在这些case

eg:

using Castle.MonoRail.Framework;
 
[FilterAttribute(ExecuteEnum.BeforeAction, typeof(AuthenticationFilter), ExecutionOrder=0)]
[FilterAttribute(ExecuteEnum.BeforeAction, typeof(LocalizationFilter), ExecutionOrder=1)]
public class AdminController : Controller
{
        [SkipFilter]
    public void Index()
        {
        }
 
        [SkipFilter(typeof(LocalizationFilter))]
    public void Create()
        {
        }
 
    public void Update()
        {
        }
}

在上面的代码中

Index action不会执行任何Filter

Create action不会执行LocalizationFilter

Update action 执行所有的Filter

Filter附参数

参数类

using Castle.MonoRail.Framework;
[AttributeUsage(AttributeTargets.Class, AllowMultiple=false, Inherited=true), Serializable]
public class MyCoolFilterAttribute : FilterAttribute
{
    private readonly string fileName;
 
    public MyCoolFilterAttribute(String fileName) : base(ExecuteEnum.BeforeAction, typeof(CoolFilterImpl))
       {
        this.fileName = fileName;
       }
    
    public string FileName
       {
           get { return fileName; }
       }
}

 

Filter

using Castle.MonoRail.Framework;
 
public class CoolFilterImpl : IFilter, IFilterAttributeAware
{
    private MyCoolFilterAttribute attribute;
 
    // Implementation of IFilterAttributeAware
    public FilterAttribute Filter 
       { 
           set { attribute = (MyCoolFilterAttribute) value; } 
       }
    
    // Implementation of IFilter
    public bool Perform(ExecuteEnum exec, IRailsEngineContext context, Controller controller)
       {
        // Now you can access the parameters:
           String fileName = attribute.FileName; 
        
        // Work
        
        // Allow the process to go on
        return true;
       }
}

 

Controller

using Castle.MonoRail.Framework;
[MyCoolFilterAttribute("customer_messages.txt")]
public class CustomerController : Controller
{
    public void Index()
       {
       }

 

Controller 名字的公约
官方建议使用Name+Controller后缀最为Controller类的名字。这样一个Controller被注册的时候,MonoRail会去掉后缀Comtroller使用NameController名字在url进入的时候被使用。
例如ProductController的名字是Product.

我们还可以自己定义名字而不是用之前的约定,这样我们就要使用到了ControllerDetails属性
eg:

using Castle.MonoRail.Framework;

[ControllerDetails("cust")]
public class Customer : Controller
{

}

在这我们使用cust作为进入名字

Areas
monorail
支持使用概念areas,这是个逻辑Controller群组。这样可以定义一些Controller属于一个群组。
默认的areas为空
为了定义Areas我们还要使用ControllerDetails属性
eg:

using Castle.MonoRail.Framework;

[ControllerDetails(Area="admin")]
public class UsersController : Controller
{
    public void Index()
        {
        }
}

这个Controller可以通过下面的url进入
/admin/users/index.rails

上面的Areas是一级的      我们还可以定义多级的
eg:

using Castle.MonoRail.Framework;

[ControllerDetails(Area="admin/users")]
public class PasswordMngController : Controller
{
    public void Index()
        {
        }
}

我们使用下面的url进入这个Controller
/admin/users/passwordmng/index.rails


Default Action
当程序找不到匹配的方法的时候,会去调用用DefaultAction属性定义的方法。
这样网叶设计人员就可以添加一个View而不需要程序员去添加一个新的方法。
使用DefaultAction属性给Controller提供一个默认action
DefaultAction
只可以定义在class级别

有下面2种情况
1.      
没有制定DefaultAction的名字,那么默认的方法就是public void DefaultAction()

[DefaultAction]
public class HomeController : Controller
{
    public void Index()
        {
        }

    public void DefaultAction()
        {
        string template = "notfound";

        if (HasTemplate("home/" + Action))
            {
                template = Action;
            }

            RenderView(template);
        }
}


2 .      指定了DefaultAction的名字      那么调用的方法就是你指定的方法

[DefaultAction("foo")]
public class HomeController : Controller
{
    public void Index()
        {
        }

    public void foo()
        {
        string template = "notfound";

        if (HasTemplate("home/" + Action))
            {
                template = Action;
            }

            RenderView(template);
        }
}


Redirecting
Controller
提供了大量的Redirect

Name

Description

RedirectToAction(string action)

定向到同一个Controller的别的action

RedirectToAction(String action, params String[] queryStringParameters)

Redirects to another action in the same controller specifying query string entries.

RedirectToAction(String action, IDictionary parameters)

Redirects to another action in the same controller specifying query string entries.

Redirect(String url)

Redirects to the specified URL.

Redirect(String url, IDictionary parameters)

Redirects to the specified URL specifying query string entries.

Redirect(String controller, String action)

Redirects to another controller and action.

Redirect(String area, String controller, String action)

Redirects to another controller and action (within an area).

posted @ 2007-06-12 08:45  ccs  阅读(2409)  评论(2编辑  收藏  举报