Getting started with MVC# framework
The source code of this example can be found under "Examples\Basics\" subfolder of the MVC# framework root folder. The framework itself can be downloaded from www.MVCSharp.org/download.aspx.
Introduction
The Model
Application logic
Main Task
Customers Controller
Orders Controller
Presentation
Customers view (Win)
Orders view (Win)
Customers view (Web)
Orders view (Web)
Customers view (Silverlight)
Orders view (Silverlight)
Starting the application
Summary
Introduction
Architectural patterns such as Model-View-Controller are gaining more and more popularity nowdays. The reason is simple: they improve applications' design and raise their maintainability by splitting applications into three layers with distinct responsibilities. However incorporating such patterns into an application requires some effort - the fact that may discourage developers from using MVC and similar approaches. Fortunately a number of frameworks exist, which minimize the overhead of using architectural patterns such as MVC. One of such frameworks is MVC#. It assists developers in using the Model-View-Presenter pattern (an evolution of MVC) by taking on itself all routine work, allowing to develop 3-tier MVP applications with ease.
结构模型如MVC越来越受到欢迎。原因很简单:他们提高了程序的设计,提高了程序的可维护性,通过将应用程序分为三层,每层负责不同的职责。尽管如此,将这些模式应用到程序中需要一些精力,事实上这并没有使得开发者从MVC或者类似的方式中受到鼓舞。幸运的是,很多档的框架存在,可以减小使用像MVC这样框架的烦恼。MVC#就是其中之一。它帮助开发者使用MVP模式(MVC的进化),通过使自身参与所有的过程,允许开发3层MVP应用程序。
In this article we will demonstrate how to build a Model-View-Presenter application with the help of MVC# Framework. Our first example application will be intended for managing customers and their orders. It will consist of two views: first one displays customers and has a button to switch to the second view. The second view lists orders for the selected customer and has buttons to operate on the order selected: “Accept”, “Cancel” and “Ship”. We will implement views for Windows, Web and Silverlight presentation platforms, revealing how MVC# allows to target the same application to different presentation mechanisms.
本文介绍如何使用MVC#框架构建一个MVP应用程序。我们的第一个示例程序打算管理客户和他们的订单。包括两个视图:一个是展示客户的,包含一个按钮来切换到第二个视图。第二个视图列出了所选择客户的订单列表,包括按钮来操作订单的选择:确定,取消,忽略。我们将实现Widows、Web和Silverlight的表现层。展示MVC# 如何支持不同的表现模式。
Before reading the article be sure to get acquainted with the Model-View-Presenter essentials. A gentle introduction to MVP and MVC can be found here. Also note that the code listings throughout the article may omit minor details for simplicity sake. For exact code see the example sources.
阅读本文之前请先熟悉MVP的必要知识。MVP和MVC的简单介绍请戳这里。还要说明的是文章中的代码可能会忽略一些细节,详细查看示例代码。
The Model 数据实体层
Firstly we are going to create the model part of the MVP triad. It will consist of two classes: Customer and Order. While the Customer class just holds a list of orders and a static list of all customers, the Order is a little more complicated having three operations.
namespace MVCSharp.Examples.Basics.Model
{
publicclass Customer
{
publicstaticreadonly List<Customer> AllCustomers = new List<Customer>();
publicreadonly List<Order> Orders = new List<Order>();
privatestring name;
public Customer(string name)
{
this.name = name;
}
publicstring Name
{
get { return name; }
}
}
}
namespace MVCSharp.Examples.Basics.Model
{
publicclass Order
{
private OrderState state = OrderState.Open;
public OrderState State
{
get { return state; }
}
publicvoid Accept()
{
if (State == OrderState.Open)
state = OrderState.Pending;
}
publicvoid Ship()
{
if (State == OrderState.Pending)
state = OrderState.Shipped;
}
publicvoid Cancel()
{
if (State != OrderState.Shipped)
state = OrderState.Cancelled;
}
}
}
Application Logic 应用逻辑层
Main Task 主任务
Every MVC# application consists of one or more tasks. A task is an independent set of actions which accomplish some job, typically representing a use case for the system. Tasks may be "Book ticket", "Calculate taxes" and others. In our example application there will be a single task for managing customers and their orders. Each task consists of a number of views which a user traverses to complete the task. Our example task will have two views: "Customers" and "Orders".
所有的MVC#程序包含一个或者多个任务。一个任务是独立的一系列行为来实现一些工作,典型的代表系统中一个用例。任务可能是订票,计算税率或者其它的。在我们的程序中,只有一个任务来管理顾客和他们的订单。每个任务包括一系列的视图,用户完成任务。我们的示例中包含两个视图:顾客和订单。
In order to describe a task we should declare its class. For each view a public constant field, initialized with the view name, should be added to the task class. According to the Model-View-Presenter pattern a view is never alone, it always exists in pair with its controller (the view-controller pair is referred to as interaction point), that is why for each view we also specify its controller type with the [InteractionPoint] attribute.
为了描述一个任务,我们首先定义他的类。每个视图的静态字段,通过视图名称来初始化,应该被包含在任务类中。依照MVP模式,一个视图不是单独的,他总是和控制器成对存在(视图-控制器对代表交互点),那是为什么每个视图我们也在[InteractionPoint]属性中定义控制器类型的原因(Task类中的)。
publicclass MainTask
{
[InteractionPoint(typeof(CustomersController), Orders)]
publicconststring Customers = "Customers";
[InteractionPoint(typeof(OrdersController), Customers)]
publicconststring Orders = "Orders";
}
Besides the controller type, [InteractionPoint] attribute specifies permitted navigation targets. Thus above we have stated both Customers->Orders and Orders->Customers transitions possible.
除了控制器类型,[InteractionPoint]属性定义了允许的导航目标,因而上面我们已经标记了Customers->Orders 和 Orders->Customers的转换。
In MVC# all tasks should implement the ITask interface. Though this interface is rather simple and can be implemented manually, the MVC# framework already comes with a basic implementation TaskBase. We will use it as a base for our MainTask class.
在MVC# 中,任务需要实现Itask接口。尽管这个接口是否简单,可以手动实现,MVC#框架已经实现了一个TaskBase基类。我们可以在我们的MainTask类中直接继承。
publicclass MainTask : TaskBase
{
...
}
A part of the ITask interface is the OnStart operation. It should be implemented to define actions performed on task start. In our case we want the customers view to be activated when the task starts:
ITask接口的一个方法是OnStart。需要被实现来定义一个任务开始时执行的操作。在我的示例中我们希望顾客视图被激活当任务开始的时候。
publicclass MainTask : TaskBase
...
publicoverridevoid OnStart(object param)
{
Navigator.NavigateDirectly(Customers);
}
NavigateDirectly method activates a view regardless of the current view (which is null at the beginning) and of possible navigation routes.
NavigateDirectly方法激活视图而不管当前视图和可能的导航路由。
A task should be a container for global logic, used across the entire task and consumed by more than one controller. In our example such global meaning will have the currently selected customer. As we will see later it will be used by both controllers. That is why we will include a CurrentCustomer property to the MainTask. Moreover the setter for this property will generate a CurrentCustomerChanged event:
一个任务应该是一个全局容器,在整个任务和消费的过程中被使用。在我们的示例中,这样的全局意味着当前选择的顾客。正如我们接下来将要看到,它将被控制器使用。这就解释了为什么我们的的MainTask包含CurrentCustomer属性。而且赋值给这一个属性将触发CurrentCustomerChanged事件。
publicclass MainTask : TaskBase
...
private Customer currentCustomer;
publicevent EventHandler CurrentCustomerChanged;
public Customer CurrentCustomer
{
get { return currentCustomer; }
set
{
currentCustomer = value;
if (CurrentCustomerChanged != null)
CurrentCustomerChanged(this, EventArgs.Empty);
}
}
Now we are ready to deal with the rest of the application logic placed in the controller classes. Let us start with the CustomersController class.
现在我们已经准备好可以处理控制器类中的应用程序逻辑了。我们从CustomersController开始。
Customers Controller
Generally controllers in the Model-View-Presenter paradigm handle the application flow: they process UI events using the Model tier classes when necessary, and decide what to display in the UI. And so the CustomersController should do. First of all it should configure the view with the customers list and the current customer. This should be done during the initialization phase when the controller is linked to its view:
一般的,MVP的控制器处理应用程序流程:必要时它们使用数据实体层的类来处理UI事件,并且决定在UI中显示。CustomersController正需要做这些事情。首先它需要配置视图的顾客列表和当前顾客,这应该在初始化过程中当控制器被连接到视图时完成。
namespace MVCSharp.Examples.Basics.ApplicationLogic
{
publicclass CustomersController : ControllerBase
{
publicoverride IView View
{
get { returnbase.View; }
set
{
base.View = value;
(View as ICustomersView).SetCustomersList(Customer.AllCustomers);
(View as ICustomersView).CurrentCustomer =
(Task as MainTask).CurrentCustomer;
}
}
}
}
Note that controllers in MVC# should conform to the IController interface. But as it is with the ITask interface MVC# provides a common IController implementation ControllerBase which we are using.
注意控制器在MVC#中需要继承Icontroller接口。但是正如Itask接口一样,MVC#提供了一个一般的ControllerBase类继承自Icontroller供我们使用。
Next, CustomersController should process UI events. To be exact it should handle the “Show Orders” command from the UI and process the change of the customer currently selected in the customers form:
接下来,CustomersController需要处理UI事件。它应该处理顾客界面中的“显示订单”命令和当前用户改变的事件。
publicclass CustomersController : ControllerBase
{
...
publicvoid ShowOrders()
{
Task.Navigator.Navigate(MainTask.Orders);
}
publicvoid CurrentCustomerChanged()
{
(Task as MainTask).CurrentCustomer = (View as ICustomersView)
.CurrentCustomer;
}
}
ICustomersView is the interface that the customers view should implement:
IcustomersView接口是顾客视图应该实现的接口。
namespace MVCSharp.Examples.Basics.ApplicationLogic
{
publicinterface ICustomersView
{
void SetCustomersList(List<Customer> customers);
Customer CurrentCustomer
{
get;
set;
}
}
}
Orders Controller
The logic of displaying orders is quite simple: we should track the change of current customer and display its orders:
显示订单的逻辑非常简单:我们需要跟踪当前用户的改变,并展示它的订单。
publicclass OrdersController : ControllerBase
{
publicoverride ITask Task
{
get { returnbase.Task; }
set
{
base.Task = value;
(Task as MainTask).CurrentCustomerChanged += CurrentCustomerChanged;
}
}
privatevoid CurrentCustomerChanged(object sender, EventArgs e)
{
(View as IOrdersView).SetOrdersList((Task as MainTask).CurrentCustomer.Orders);
}
}
In the above code we first subscribe to the MainTask.CurrentCustomerChanged event as soon as the controller is linked to the task object. Then we just handle the customer change event by passing the corresponding orders to the view.
上面的代码首先订阅了MainTask. CurrentCustomerChanged事件,当OrdersController控制器被连接到task对象时。接着我们传递相应的订单给视图来处理顾客改变事件。
The next three methods represent the "Accept", "Ship", and "Cancel" operations on the currently selected order:
下面的三个方法代表:在当前选择的订单上的“确定”,“忽略”和“取消”操作。
publicclass OrdersController : ControllerBase
{
…
publicvoid AcceptOrder()
{
(View as IOrdersView).CurrentOrder.Accept();
UpdateView();
}
publicvoid ShipOrder()
{
(View as IOrdersView).CurrentOrder.Ship();
UpdateView();
}
publicvoid CancelOrder()
{
(View as IOrdersView).CurrentOrder.Cancel();
UpdateView();
}
}
We might demand the view to disable certain operations depending on the current order state. For example one cannot ship an order before it is accepted. UpdateView is the method that analyzes the current order state and tells the view to enable/disable certain operations:
我们可能要求视图取消特定的操作根据当前订单状态。例如不能在接受某个订单前选择忽略。UpdateView方法分析当前订单状态,并告知视图特定的操作可用或不可用。
publicclass OrdersController : ControllerBase
…
privatevoid UpdateView()
{
if ((View as IOrdersView).CurrentOrder == null) return;
OrderState os = (View as IOrdersView).CurrentOrder.State;
(View as IOrdersView).SetOperationsEnabling(
os == OrderState.Open, os == OrderState.Pending,
(os == OrderState.Open) || (os == OrderState.Pending));
}
The OrdersController class should also have a CurrentOrderChanged operation for the view to notify the controller when a user clicks on another order:
OrdersController订单控制器类也需要有CurrentOrderChanged操作,让视图在用户单击其他订单时通知控制器。
publicclass OrdersController : ControllerBase
…
publicvoid CurrentOrderChanged()
{
UpdateView();
}
Presentation
The presentation layer consists of view classes. All views in MVC# should implement the IView interface. In addition view classes should conform to particular interfaces dictated by the application logic layer. In our example such interfaces are ICustomersView and IOrdersView. Depending on the presentation platform chosen, view classes should inherit an appropriate base view class (either Windows form, or Web form, or Silverlight user control). We will design views for all three platforms supported at the moment (Windows, Web and Silverlight) and will start with Windows form views.
表现层包括视图类。所有的视图在MVC#中需要实现Iview接口。此外视图类需要针对应用程序逻辑层实现特定的接口。在我们的示例中,这些接口是ICustomersView 和IordersView接口。根据不同的表现平台,视图类需要继承合适的视图基类。或者是Windows Form或者是WebForm或者是Silverlight用户控件。我们将为这三种平台提供视图。以Windows Form开始介绍。
Customers view (Win)
First let us create the customers view for the Windows Forms presentation mechanism. As mentioned above it should be a subclass of the Windows Form class and should implement the IView interface. To reduce the amount of manual work we will make it inherit from the WinFormView class - a simple Form subclass implementing the IView interface.
首先我们创建顾客视图的WindowsForm表现样式。像上面提到的,应该是Winform的子类并且实现Iview接口。为了减少人工的工作量我们通过继承WinFormView类。一个简单的实现了Iview的窗体类。
namespace MVCSharp.Examples.Basics.Presentation
{
publicpartialclass CustomersForm : WinFormView
{
public CustomersForm()
{
InitializeComponent();
}
}
}
Let us place a GridView component and a button on the form:放置一个GirdView控件和一个按钮。
In order for the customers controller to talk to its view the latter should implement the ICustomersView interface:
为了让顾客控制器能够和它的视图会话,后者应该实现IcustomersVIew接口。
publicpartialclass CustomersForm : WinFormView, ICustomersView
...
publicvoid SetCustomersList(List<Customer> customers)
{
customersGridView.DataSource = customers;
}
public Customer CurrentCustomer
{
get { return customersGridView.CurrentRow == null ? null :
customersGridView.CurrentRow.DataBoundItem as Customer;}
set { customersGridView.CurrentCell = customersGridView.Rows[
(customersGridView.DataSource as IList).IndexOf(value)].Cells[0];}
}
According to the MVP pattern views receive the user actions and then pass control to the controller. In our case the customers view should handle the change of current customer in the grid and the “Show Orders” button click:
依据MVP模式,视图接受用户的动作接着传递给控制器控制。在我们的示例中顾客视图应该处理当前用户改变的Gird事件和显示订单按钮的单击事件。
publicpartialclass CustomersForm : WinFormView, ICustomersView
...
privatevoid customersGridView_CurrentCellChanged(object sender, EventArgs e)
{
(Controller as CustomersController).CurrentCustomerChanged();
}
privatevoid showOrdersButton_Click(object sender, EventArgs e)
{
(Controller as CustomersController).ShowOrders();
}
The last thing here is to point the MVC# framework that the above view class describes the “Customers” view in the MainTask task. This is done with the View attribute:
最后的事情是告诉MVC#框架,上面的视图类描述的是在MainTask任务中的顾客视图。这在视图属性中实现。
[View(typeof(MainTask), MainTask.Customers)]
publicpartialclass CustomersForm : WinFormView, ICustomersView
…
Orders view (Win)
Similarly let us design the OrdersView form. It will contain a grid and three buttons:
类似的我们设计OrdersView订单窗体。他包括一个grid控件和三个按钮。
In the same manner as it was with the CustomersView, the OrdersView class should inherit from the WinFormView class and implement the IOrdersView interface for the framework and the controller to interact with it. It should also have a [View] attribute to indicate its belonging to the main task as the "Orders" view.
和顾客视图类似,订单视图应该集成自WinFormView,应该实现IordersView接口,这样控制器才能与之交互。他还应该包含[View]属性来标识它是属于主任务的Orders视图。
[View(typeof(MainTask), MainTask.Orders)]
publicpartialclass OrdersForm : WinFormView, IOrdersView
{
...
publicvoid SetOrdersList(List<Order> orders)
{
ordersGridView.DataSource = orders;
}
public Order CurrentOrder
{
get { return ordersGridView.CurrentRow.DataBoundItem as Order; }
}
publicvoid SetOperationsEnabling(bool acceptIsEnabled,
bool shipIsEnabled, bool cancelIsEnabled)
{
acceptOrderBtn.Enabled = acceptIsEnabled;
shipOrderBtn.Enabled = shipIsEnabled;
cancelOrderBtn.Enabled = cancelIsEnabled;
}
The following code shows how the OrdersForm handles user actions and delegates processing to the controller:
下面的订单窗体出来用户动作,将处理交由控制器执行。
publicpartialclass OrdersForm : Form, IView, IOrdersView
{
...
privatevoid ordersGridView_CurrentCellChanged(object sender, EventArgs e)
{
(Controller as OrdersController).CurrentOrderChanged();
}
privatevoid acceptOrderBtn_Click(object sender, EventArgs e)
{
(Controller as OrdersController).AcceptOrder();
}
privatevoid shipOrderBtn_Click(object sender, EventArgs e)
{
(Controller as OrdersController).ShipOrder();
}
privatevoid cancelOrderBtn_Click(object sender, EventArgs e)
{
(Controller as OrdersController).CancelOrder();
}
Customers view (Web)
In Web environment the views design will be very similar to that in Windows. Views will inherit from the WebFormView class and will contain a grid and buttons on their surface:
ICustomersView implementation will be identical to that in Windows environment:
publicpartialclass _Default : WebFormView, ICustomersView
{
publicvoid SetCustomersList(List<Customer> customers)
{
CustomersGridView.DataSource = customers;
}
public Customer CurrentCustomer
{
get { return (CustomersGridView.DataSource as List<Customer>)
[CustomersGridView.SelectedIndex]; }
set { CustomersGridView.SelectedIndex =
(CustomersGridView.DataSource as IList).IndexOf(value); }
}
}
Button click and grid row selection event handler will look the same way too:
publicpartialclass _Default : WebFormView, ICustomersView
...
protectedvoid ShowOrdersButton_Click(object sender, EventArgs e)
{
(Controller as CustomersController).ShowOrders();
}
protectedvoid CustomersGridView_SelectedIndexChanged(object sender, EventArgs e)
{
(Controller as CustomersController).CurrentCustomerChanged();
}
Orders view (Web)
Here we do not list code for the orders Web form class as it is very similar to its Windows analogue above. If desired, a reader may look into the full sources of the example. Here is how the orders Web view looks in the designer surface:
To associate the designed Web forms with the "Customers" and "Orders" views of the main task we use a slightly different technique (instead of [View] attributes): we add [WebformsView] attributes to a placeholder class inside the Global.asax file:
<!------------------------ Global.asax file -------------------->
<script runat="server">
[WebformsView(typeof(MainTask), MainTask.Customers, "Default.aspx")]
[WebformsView(typeof(MainTask), MainTask.Orders, "Orders.aspx")]
class ViewDescriptions { }
</script>
Customers view (Silverlight)
Views under Silverlight platform should inherit the UserControlView class. Thus, firstly we should create a new user control with the following XAML markup:
<mvc:UserControlView
xmlns:my=
"clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
x:Class="MVCSharp.Examples.Basics.Presentation.Silverlight.CustomersPage"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mvc="clr-namespace:MVCSharp.Silverlight;assembly=MVCSharpSL">
<Grid>
</Grid>
</mvc:UserControlView>
Next, we are adding the customers data grid and the "Show Orders" button:
<mvc:UserControlView
... >
<Grid Background="White" Margin="5" HorizontalAlignment="Left">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<my:DataGrid x:Name="CustomersGrid" MaxWidth="400" MinHeight="100"
Grid.Row="0" SelectionMode="Single"
SelectionChanged="CustomersGrid_CurrentCellChanged"/>
<Button Width="75" Height="25" HorizontalAlignment="Right" Grid.Row="1"
Content="Show Orders" Margin="3" Click="Button_Click"/>
</Grid>
Finally, we should add the event handlers to the code-behind CustomersPage.xaml.cs file, and make the view class implement the ICustomersView interface. The corresponding code looks almost identical to that for Winforms and Web platforms:
[View(typeof(MainTask), MainTask.Customers)]
publicpartialclass CustomersPage : UserControlView, ICustomersView
...
publicvoid SetCustomersList(List<Customer> customers)
{
CustomersGrid.ItemsSource = customers;
}
public Customer CurrentCustomer
{
get { return CustomersGrid.SelectedItem as Customer; }
set { CustomersGrid.SelectedItem = value; }
}
privatevoid CustomersGrid_CurrentCellChanged(object sender, EventArgs e)
{
(Controller as CustomersController).CurrentCustomerChanged();
}
privatevoid Button_Click(object sender, RoutedEventArgs e)
{
(Controller as CustomersController).ShowOrders();
}
Note that for binding the view class to the task we are using the [View] attribute, like we did for the Windows Forms platform.
Orders view (Silverlight)
And again, we omit the description of the steps here, as everything is similar to what we have done already. If necessary, see the example sources.
Starting the application
Every MVC# application requires some configuration steps. All configuration data is encapsulated in MVCConfiguration class instances. However instead of manually setting up MVCConfiguration objects it is possible to obtain preconfigured instances by the view managers' GetDefaultConfig() static method. After the configuration is done it is time to start the task by passing its type to the TasksManager.StartTask(...) method.
The code to configure and to start the main task looks similar in Windows and Web environments, the major difference is where to place that code. In Windows application we put it in the Main method which is an entry point of a program:
staticclass Program
{
[STAThread]
staticvoid Main()
{
TasksManager tasksManager = new TasksManager(WinformsViewsManager.GetDefaultConfig());
tasksManager.StartTask(typeof(MainTask));
Application.Run(Application.OpenForms[0]);
}
}
In Web applications we place the main task start code in the session start handler in Global.asax file:
<!---------------------- Global.asax file -------------------->
<script runat="server">
...
void Session_Start(object sender, EventArgs e)
{
TasksManager tm = new TasksManager(WebformsViewsManager.GetDefaultConfig());
tm.StartTask(typeof(MainTask));
}
As for Silverlight platform, we use the Startup event handler of the global application class to start the main task:
publicpartialclass App : Application
...
privatevoid Application_Startup(object sender, StartupEventArgs e)
{
TasksManager tasksManager = new TasksManager(SilverlightViewsManager.GetDefaultConfig());
tasksManager.StartTask(typeof(MainTask));
}
Summary
This example gives the general look on how to construct layered applications with MVC# framework. It demonstrates the common idea of the Model-View-Presenter approch - that is breaking an application into three layers: one for business objects (model), another for application logic (controllers), and the last for presentation (views). And it shows how MVC# simplifies implementing the MVP approach in your applications.
The source code of this example can be found under "Examples\Basics\" subfolder of the MVC# framework root folder. The framework itself can be downloaded from www.MVCSharp.org/download.aspx.