无代码后台的代码






4.50/5 (2投票s)
使用MVP设计模式开发基于ASP.NET的事件日历
目标
本文旨在为社区网站创建一个事件日历,这是当今任何网站中最常用的功能。网页将显示当前和未来几年的活动。点击每个事件时,详细信息将在弹出框中显示。该 Web 应用程序将专注于创建松耦合的代码,并展示如何利用以下概念进行无代码后台开发:HTTP Handlers、Web Services、MVP、JavaScript 和 AJAX。

引言
在开发 ASP.NET Web 应用程序时,我们经常会利用代码后台文件提供的功能。这对于小型、不太复杂的网站来说可能效果不错,在这些网站中,一切都属于 .NET,并且事物不太可能随时间发生太大变化。但当涉及到可伸缩性和与其他平台的互操作性时,这确实不是一个好主意。
示例 - 您有一个运行良好的 ASP.NET Web 应用程序。突然客户决定将 UI 代码从 aspx 页面迁移到简单的 HTML 页面。代码后台中的大量代码使得应用程序紧密耦合,并使得迁移非常困难。此外,我们实际上应该保持关注点分离。UI 应由设计师处理,业务逻辑应留给开发人员。在代码后台文件中编写大量代码会模糊设计师和开发人员之间的界限。最佳方法是将设计与业务逻辑分离,这样我们就能拥有具有丰富 UI 界面(由设计师创建)并呈现复杂业务逻辑(由开发人员实现)的精美网站,并且两者能够和谐共存。
项目
该项目分为两个部分
- 第一部分 - 服务器
- 第二部分 - 客户端
第一部分 - 服务器
服务器包含以下部分
- 数据库 – 表、存储过程
- 数据访问层
- 业务层
- Web服务
数据库
我使用 SQL Server 数据库来托管事件日历的事件。请参考名为 EventCalendar.SQLDatabase
的项目。其中包含创建数据库所需的脚本。基本上,我存储了日历年(2010、2011 和 2012 年)的事件。我已用虚拟数据填充了数据库。让我们快速看一下我们的数据库结构。
一张图片胜过千言万语。我将附上我的数据库设计和数据库图的截图。总而言之,我们正在为各个日历年的每个月存储事件。
数据库表
Events 表

Year 表

Month 表

数据库图

注意
- 我已备份数据库,并将其作为可下载项附在本项目中。您可以下载 .bak 文件并运行还原。应该可以正常工作。
- 您也可以使用提供的脚本来创建自己的数据库。我的数据库是 SQL Server 2008。如果使用其他版本,您需要进行调整。
- 对于实时场景,您需要决定是使用关系数据库还是 XML。如果您有一个小型网站,每年只有少量事件,那么使用 XML 会更合适。
数据库存储过程
定义了两个存储过程
spGetEvents
spGetMonths
数据库快照
数据访问层
参考项目 – EventCalendar.DataAccessLayer
我使用 Factory 数据库提供程序来实现我们的数据访问层。我有一个 IDatabaseConnection
接口,用于定义我们的数据库连接。它有三个方法
GetConnection
– 用于创建连接CreateCommand
– 用于创建命令对象CreateAdapter
– 用于创建数据库适配器
public interface IDatabaseConnection:IDisposable
{
IDbConnection GetConnection { get; }
IDbCommand CreateCommand(string dynamicSQLExpression , CommandTypes cmdType);
IDbDataAdapter CreateAdapter(IDbCommand command);
}
public enum CommandTypes
{
StoredProcedure,
Query
}
我们有一个 DatabaseConnection
类,它实现了 IDatabaseConnection
public class DatabaseConnection:IDatabaseConnection
使用 DbProviderFactory
的主要优点是消除对底层数据库的任何耦合。我们的代码应该与任何数据库兼容。这通过在运行时从 config 文件中获取提供程序名称来实现。
private string _providerName =
ConfigurationManager.AppSettings["DataAccessLayer.ProviderName"];
private string _connectionString =
ConfigurationManager.AppSettings["DataAccessLayer.ConnectionString"];
private DbProviderFactory _providerFactory;
private IDbConnection _connection;
我们使用 DBProviderFactory
类的 GetFactory
方法来创建提供程序工厂,然后在类的构造函数中打开数据库连接。请看下面的代码
public DatabaseConnection()
{
_providerFactory = DbProviderFactories.GetFactory(_providerName);
OpenConnection();
}
private void OpenConnection()
{
_connection = _providerFactory.CreateConnection();
_connection.ConnectionString = _connectionString;
_connection.Open();
}
private void CloseConnection()
{
_connection.Close();
}
System.Data.IDbConnection IDatabaseConnection.GetConnection
{
get { return _connection; }
}
最后,我们实现了 CreateCommand
、CreateAdapter
方法
System.Data.IDbCommand IDatabaseConnection.CreateCommand
(string dynamicSQLExpression, CommandTypes cmdType)
{
IDbCommand command = _connection.CreateCommand();
command.CommandText = dynamicSQLExpression;
switch (cmdType)
{
case CommandTypes.StoredProcedure:
command.CommandType = CommandType.StoredProcedure;
break;
case CommandTypes.Query:
command.CommandType = CommandType.Text;
break;
default:
break;
}
return command;
}
IDbDataAdapter IDatabaseConnection.CreateAdapter(IDbCommand command)
{
IDbDataAdapter da = _providerFactory.CreateDataAdapter();
da.SelectCommand = command;
return da;
}
void IDisposable.Dispose()
{
CloseConnection();
_connection.Dispose();
}
现在我们已经创建了提供程序工厂,它可以为我们提供访问数据库所需的命令和适配器对象。因此,我们现在必须创建一个 DatabaseGateway
,它将使用我们的 DatbaseConnection
类从数据库中获取数据。
DatbaseGateway
有两个方法
ExecuteQuery
– 用于执行查询。它有两个重载 - 一个用于运行普通查询,另一个用于运行参数化查询。ExecuteStoredProcedure
– 用于执行存储过程。它有两个重载 - 一个用于运行普通 SP,另一个用于运行参数化 SP。
参数作为哈希表集合作为参数传递给方法。
请看下面的代码
public class DatabaseGateway
{
public DatabaseGateway() {}
public DataTable ExecuteQuery(string SQlQuery)
{
using (IDatabaseConnection conn = new DatabaseConnection())
{
DataSet ds = new DataSet();
conn.CreateAdapter(conn.CreateCommand
(SQlQuery, CommandTypes.Query)).Fill(ds);
return ds.Tables[0];
}
}
public DataTable ExecuteQuery(string SQlQuery, Hashtable param)
{
using (IDatabaseConnection conn = new DatabaseConnection())
{
DataSet ds = new DataSet();
IDbCommand cmd = conn.CreateCommand(SQlQuery, CommandTypes.Query);
foreach (DictionaryEntry item in param)
{
IDataParameter p = cmd.CreateParameter();
p.ParameterName = (string)item.Key;
p.Value = item.Value;
cmd.Parameters.Add(p);
}
conn.CreateAdapter(cmd).Fill(ds);
return ds.Tables[0];
}
}
public DataTable ExecuteStoredProcedure(string SQlQuery)
{
using (IDatabaseConnection conn = new DatabaseConnection())
{
DataSet ds = new DataSet();
conn.CreateAdapter(conn.CreateCommand
(SQlQuery, CommandTypes.StoredProcedure)).Fill(ds);
return ds.Tables[0];
}
}
public DataTable ExecuteStoredProcedure(string SQlQuery, Hashtable param)
{
using (IDatabaseConnection conn = new DatabaseConnection())
{
DataSet ds = new DataSet();
IDbCommand cmd = conn.CreateCommand
(SQlQuery, CommandTypes.StoredProcedure);
foreach (DictionaryEntry item in param)
{
IDataParameter p = cmd.CreateParameter();
p.ParameterName = (string)item.Key;
p.Value = item.Value;
cmd.Parameters.Add(p);
}
conn.CreateAdapter(cmd).Fill(ds);
return ds.Tables[0];
}
}
}
业务层
现在我们已经创建了数据访问层,是时候创建业务逻辑层了。Class Event – 这代表了事件日历中的事件。
public class Event
{
public Event() {}
public string Date { get; set; }
public string Description { get; set; }
public string Link { get; set; }
public string Details { get; set; }
public string Image { get; set; }
}
Class Month – 这代表了年份的月份。因此,每个月份都有以下属性
MonthID
– 代表一年中的第几个月Year
– 代表该月份属于哪一年Events
– 该月份的事件集合。请参考下面的代码
public class Month
{
public Month() { }
public Month(byte monthID)
{
this.MonthID = monthID;
}
public byte MonthID { get; set; }
public string MonthName { get; set; }
public string Year { get; set; }
public List<Event> Events
{
get
{
DatabaseGateway dal = new DatabaseGateway();
Hashtable parameters = new Hashtable();
parameters.Add("@Year", Year);
DataTable dtYear=dal.ExecuteQuery
("SELECT YearId FROM YEAR WHERE Year = @Year",parameters);
int YearId = Convert.ToInt16(dtYear.Rows[0][0]);
string sqlQuery = ConfigurationManager.AppSettings
["DataAccessLayer.spEventQuery"];
dal = new DatabaseGateway();
parameters = new Hashtable();
parameters.Add("@yearId", YearId);
parameters.Add("@monthId", MonthID);
DataTable dtEvents = dal.ExecuteStoredProcedure(sqlQuery,parameters);
List<Event> events = new List<Event>();
for (int rowCount = 0; rowCount < dtEvents.Rows.Count; rowCount++)
{
Event e = new Event();
e.Date = Convert.ToString(dtEvents.Rows[rowCount]["Date"]);
e.Description = Convert.ToString(dtEvents.Rows
[rowCount]["Description"]);
e.Link =Convert.ToString(dtEvents.Rows[rowCount]["Link"]);
events.Add(e);
}
return events;
}
}
}
Class Calendar
– 这代表每个日历年。它具有以下属性
Year
– 代表年份Months
– 月份集合(一月 - 十二月)- 请参考下面的代码
public class Calendar
{
public Calendar(){}
public string Year { get; set; }
public List<Month> Months
{
get
{
string sqlQuery = ConfigurationManager.AppSettings
["DataAccessLayer.spMonthQuery"];
DatabaseGateway dal = new DatabaseGateway();
DataTable dtMonths = dal.ExecuteStoredProcedure(sqlQuery);
List<Month> months = new List<Month>();
for (int rowCount = 0; rowCount < dtMonths.Rows.Count; rowCount++)
{
Month m = new Month();
m.MonthID = Convert.ToByte(dtMonths.Rows[rowCount]["MonthId"]);
m.MonthName = Convert.ToString(dtMonths.Rows[rowCount]["Month"]);
m.Year = Year;
months.Add(m);
}
return months;
}
}
}
请注意,我从数据访问层调用了数据库网关类来从数据库获取数据。这已在相应类的属性中实现。
模型视图呈现器
MVP 模式在增强业务逻辑层(Model)与我的呈现器(UI)之间提供了极大的灵活性。使用 MVP 模式的主要优点是,它将所有代码从代码后台文件转移到呈现器,从而使代码与实际 UI 松散耦合。因此,当 UI 发生变化时,我们将产生最小的甚至没有破坏性更改。
Model – 这代表了业务逻辑层和数据访问层。
视图抽象 – 文章开头我们一直在谈论设计师和开发人员之间的关注点分离。想象一下,您正在与一组设计师合作,以交付一个丰富的用户界面网站。作为开发人员,您将处理业务逻辑。现在,如果您的业务逻辑与 UI 紧密耦合,那么您将任由设计师来开发和测试您的代码。此外,它会阻碍并行编程,因为开发人员和设计师完全相互依赖,并且经常会妨碍彼此。这时模型视图模式就派上用场了!使用 MVP,我们实际上不需要视图,只需要视图的抽象。我们可以编写自己的测试用例来测试功能,同时开发实际的视图。 MVP 模式!
Presenter – 呈现器与视图有关联,并负责通过从 Model 获取数据来更新视图。
由于我们处理的是视图的抽象,因此可以在没有实际视图(我们的丰富 UI)的情况下轻松测试视图的功能。我们只需要在测试模块中定义一个实现视图抽象的类,并将其作为我们的视图。然后,我们可以测试我们的业务逻辑,当实际视图准备好时,我们的代码就能完美匹配。即插即用!
View – 这是实际的视图或 UI。它实现了抽象视图。
让我们看看如何在我们的项目中利用 MVP。参考 EventCalendar.Presenter
。我们有两个呈现器,因为我们处理两个视图。
- 显示各种年份事件的事件日历。
- 点击事件标题时出现的显示详细事件的弹出框。
视图抽象 – IeventCalendarView
public interface IEventCalendarView
{
string SelectedYear { get; }
string HTMLCode { set; }
}
这里的 HTML 代码指的是呈现器将返回给视图的 HTML 代码。此 HTML 代码将包含事件日历网页的内部 HTML 代码。
Presenter – EventCalendarPresenter
。
这里的呈现器与 Model(我们的数据访问层和业务逻辑层)通信以获取事件信息。然后,它生成实际视图将实现的 HTML 代码。我选择此设计的原因是为了跟上网页的样式。通常,设计师会生成 HTML 代码来装饰网页。将信息写入任何控件的内部 HTML 中会更容易。这里我将将其写入实际视图的 <div>
部分。
任何愿意实现其他设计的人只需更改与视图的 HTMLCode
属性相关的代码。
我们在客户端有一个名为 pop up 的 JavaScript 函数,它向我们的 DetailedEvent
Web 服务发送请求。此函数(稍后解释)负责在弹出窗口中显示详细事件。
IEventCalendarView _view;
public EventCalendarPresenter(IEventCalendarView view)
{
_view = view;
}
public void UpdateView()
{
// Initiate Calendar class
Calendar c = new Calendar();
c.Year = _view.SelectedYear;
// Generate HTML code
_view.HTMLCode = GenerateHTMLCodeForaCalendar(c);
}
#region InnerHTML Code
/// <summary>
/// Loads Months in a calendar Year and generates HTML code
/// </summary>
/// <param name="c"></param>
/// <returns>HTML Code in string</returns>
private string GenerateHTMLCodeForaCalendar(Calendar c)
{
StringBuilder innerHTML = new StringBuilder();
innerHTML.Append("<table border=\"0\"
cellspacing=\"0\" cellpadding=\"0\" class=\"tableEvents\">");
byte monthCount = 1;
// Get Names for all the months
ArrayList arrMonths = new ArrayList();
for (int monthId = 0; monthId < 12; monthId++)
{
Month month = c.Months.Find((m) => { return m.MonthID == monthId + 1; });
arrMonths.Add("<th>" + month.MonthName + "</th><td></td>");
}
for (int row = 0; row < 3; row++)
{
// HTML for Row Headers - Month Names
if (row == 0)
{
// Row 1 Header - Jan, Feb, March, April
innerHTML.Append("<tr class=\"head\">");
innerHTML.Append(arrMonths[0]);
innerHTML.Append(arrMonths[1]);
innerHTML.Append(arrMonths[2]);
innerHTML.Append(arrMonths[3]);
innerHTML.Append("</tr>");
}
else if (row == 1)
{
// Row 2 Header - May, June, July, August
innerHTML.Append("<tr class=\"head\">");
innerHTML.Append(arrMonths[4]); innerHTML.Append(arrMonths[5]);
innerHTML.Append(arrMonths[6]); innerHTML.Append(arrMonths[7]);
innerHTML.Append("</tr>");
}
else
{
// Row 3 Header - Sep, Oct, Nov, December
innerHTML.Append("<tr class=\"head\">");
innerHTML.Append(arrMonths[8]); innerHTML.Append(arrMonths[9]);
innerHTML.Append(arrMonths[10]); innerHTML.Append(arrMonths[11]);
innerHTML.Append("</tr>");
}
// HTML for Table Data
innerHTML.Append("<tr>");
for (int col = 0; col < 4; col++)
{
innerHTML.Append("<td class=\"eventCont\">
<div class=\"scroll-pane\"><div>");
Month month = c.Months.Find((m) =>
{ return (m.MonthID == monthCount); });
innerHTML.Append(EventsInaMonth(month));
innerHTML.Append("</div></div></td><td> </td>");
monthCount += 1;
}
innerHTML.Append("</tr>");
}
innerHTML.Append("</table>");
return innerHTML.ToString();
}
/// <summary>
/// Loads Events in a given month
/// </summary>
/// <param name="month"></param>
/// <returns>HTML Code in string</returns>
private string EventsInaMonth(Month month)
{
StringBuilder text = new StringBuilder();
text.Append("<table width=\"100%\" border=\"0\"
cellspacing=\"0\" cellpadding=\"0\">");
foreach (Event e in month.Events)
{
text.Append("<tr>");
text.Append("<td><strong>");
text.Append("<a href=\"#");
if (e.Description != "")
text.Append("\" onclick=\"popup('event=" + e.Date +
"&month=" + month.MonthID + "&year=" + month.Year + "')\"> ");
else
text.Append("\" onclick=\"popup('month=" +
month.MonthID + "&year=" + month.Year + "')\"> ");
text.Append(e.Date);
text.Append("</a>");
text.Append("</strong>");
text.Append("<p>");
text.Append(e.Description);
text.Append("</p></td></tr>");
}
text.Append("</table>");
return text.ToString();
}
#endregion
}
View IDetailedEventView
同样,我们有详细事件视图。它具有以下属性
public interface IDetailedEventView
{
string SelectedYear { get; }
string JSONstring { set; }
string date { get; }
int monthid { get; }
}
DetailedEventPresenter
负责获取事件的详细描述,并将其序列化为 JSON string
的形式并附加到视图。
public class DetailedEventPresenter
{
private IDetailedEventView _view;
List<Month> _months;
public DetailedEventPresenter(IDetailedEventView view)
{
_view = view;
_months = new List<Month>();
}
public void UpdateView()
{
int intCurrentYear = DateTime.Now.Year;
Calendar c = new Calendar();
c.Year = _view.SelectedYear;
_months = c.Months;
Month month = _months.Find((m) => { return (m.MonthID == _view.monthid); });
Event selectedEvent = month.Events.Find((e) =>
{ return (e.Date == _view.date); });
string jsonString = "{'title':'" +
selectedEvent.Description + "', 'date':'" + selectedEvent.Date +
"', 'details':'" + selectedEvent.Details + "',
'link':'" + selectedEvent.Link + "', 'image':'" +
selectedEvent.Image + "'}";
_view.JSONstring = jsonString;
}
}
注意 – 呈现器有一个方法 – UpdateView()
,它调用该方法来使用 Model 中的数据更新实际视图。
测试我们的代码 – 在这一点上,我们可以实际实现单元测试用例来测试我们的功能是否正常工作。请注意,我们还没有实际实现视图。我们可以使用 Visual Studio 中的测试项目。我们所要做的就是实现一个继承视图抽象的类 – Dummy View – 并调用呈现器来查看我们的业务逻辑是否有效。
由于时间不足,我没有包含太多测试,但欢迎读者添加自己的测试。
Web 服务
有两种实现 Web 服务的方法
- 使用简单的 aspx 页面(项目 –
EventCalendar.UI
) - 使用 HTTP Handlers(项目 –
EventCalendar.WebService
)
其思想是使信息可供订阅我们 Web 服务的任何客户端使用。
使用简单 ASPX 页面的 Web 服务
其思想是将信息写入网页。这提供了简洁性,因为我们不必单独在 IIS 中托管它。它可以与我们的客户端一起部署。
Events.aspx – 这是我们的事件日历视图。我们实现了抽象视图,并将我们的呈现器与其关联。网页期望以查询字符串的形式接收信息。我们捕获该信息以了解基于年份和月份应获取哪些事件。然后,我们调用呈现器的 UpdateView()
方法来填充页面信息。
public partial class Events : System.Web.UI.Page, IEventCalendarView
{
private EventCalendarPresenter presenter;
private string _selectedYear;
protected void Page_Load(object sender, EventArgs e)
{
//UpdateDataPath();
presenter = new EventCalendarPresenter(this);
_selectedYear = Request["year"];
if (_selectedYear==null)
{
_selectedYear = Convert.ToString(DateTime.Now.Year);
}
presenter.UpdateView();
}
string IEventCalendarView.SelectedYear
{
get { return _selectedYear; }
}
string IEventCalendarView.HTMLCode
{
set { Response.Write(value); }
}
}
DetailedEvent.aspx 页面 – 这将成为我们的详细事件显示视图。我们实现了抽象视图,并将我们的呈现器与其关联。网页期望以查询字符串的形式接收信息。我们捕获该信息以了解基于年份和月份应获取哪些事件。然后,我们调用呈现器的 UpdateView()
方法来填充页面信息。
public partial class DetailedEvent : System.Web.UI.Page, IDetailedEventView
{
private DetailedEventPresenter presenter;
private string _selectedYear;
private string _event;
private string _month;
protected void Page_Load(object sender, EventArgs e)
{
_month = Request["month"];
_event = Request["event"];
presenter = new DetailedEventPresenter(this);
_selectedYear = Request["year"];
if (_selectedYear == null)
{ _selectedYear = Convert.ToString(DateTime.Now.Year);}
presenter.UpdateView();
}
string IDetailedEventView.SelectedYear { get { return _selectedYear; }}
string IDetailedEventView.JSONstring { set { Response.Write(value); }}
string IDetailedEventView.date { get { return _event; }}
int IDetailedEventView.monthid
{ get { return Convert.ToInt16(_month); }}
}
请注意,通过实现 MVP 模式,我们显著减少了代码后台文件中的代码量。因此,这里的绝大部分操作由呈现器处理。
使用 HTTP Handlers 的 Web 服务
尽管上述代码简单易懂,但不够优雅。通过调用 aspx 页面仅仅为了输出信息,我们在服务器上占用了内存。最好将这项工作交给 HTTP Handlers,它们是处理这类任务的绝佳选择。
我们再次有上面提到的两个视图。实现方式相同。请参考下面的代码
public class DetailedEvent:IHttpHandler,IDetailedEventView
{
private DetailedEventPresenter _presenter;
private string _selectedYear;
private string _event;
private string _month;
private HttpContext _context;
bool IHttpHandler.IsReusable
{
get { throw new NotImplementedException(); }
}
void IHttpHandler.ProcessRequest(HttpContext context)
{
_context = context;
_month = context.Request["month"];
_event = context.Request["event"];
_presenter = new DetailedEventPresenter(this);
_selectedYear = context.Request["year"];
if (_selectedYear == null) { _selectedYear =
Convert.ToString(DateTime.Now.Year); }
_presenter.UpdateView();
}
string IDetailedEventView.SelectedYear { get { return _selectedYear; } }
string IDetailedEventView.JSONstring
{ set { _context.Response.Write(value); } }
string IDetailedEventView.date { get { return _event; } }
int IDetailedEventView.monthid
{ get { return Convert.ToInt16(_month); } }
}
public class Events:IHttpHandler,IEventCalendarView
{
private string _selectedYear;
private EventCalendarPresenter _presenter;
private HttpContext _context;
public Events()
{
_presenter = new EventCalendarPresenter(this);
}
bool IHttpHandler.IsReusable
{
get { throw new NotImplementedException(); }
}
void IHttpHandler.ProcessRequest(HttpContext context)
{
// Expect URl - http://servername.com/VirtualDirectory/
EventCalendar?year=selectedYear
// example - https:///EventCalendarWebService/EventCalendar?year=2010
_context = context;
_presenter = new EventCalendarPresenter(this);
_selectedYear = context.Request["year"];
if (_selectedYear == null)
{
_selectedYear = Convert.ToString(DateTime.Now.Year);
}
_presenter.UpdateView();
}
string IEventCalendarView.SelectedYear
{
get
{ return _selectedYear; }
}
string IEventCalendarView.HTMLCode
{
set
{ _context.Response.Write(value); }
}
}
注意 – 如果您使用 HTTP Handlers,则必须在 IIS 中单独托管它们,并配置虚拟目录和 Web.config 文件。要了解更多关于 HTTP Handlers 的信息,请参阅参考部分。
Web.Config 文件
除了上述代码,您还必须更新 web.config 文件,如下所示
<appSettings>
<add key="DataAccessLayer.ProviderName" value="System.Data.SqlClient"/>
<add key="DataAccessLayer.ConnectionString"
value="Data Source=SYNPUNEHCRV-127;
Initial Catalog=EventCalendar;User ID=sa;Password=SA!@#"/>
<add key="DataAccessLayer.spEventQuery" value="spGetEvents"/>
<add key="DataAccessLayer.spMonthQuery" value="spGetMonths"/>
</appSettings>
第二部分 - 客户端
客户端是一个 HTML 网页,通过订阅其 Web 服务与服务器进行交互。客户端通过 AJAX 发送 XML HTTP 请求。它接收响应,如有必要进行反序列化,并显示信息。所有这些都通过单独的脚本文件中编写的 Java 脚本函数来完成。
参考项目 EventCalendar.UI
。
客户端包含以下部分
- CSS – 我们的样式表位于 CSS 文件夹中。它们是不言自明的。在实际情况下,这些将由设计师提供。
- Images – 包含我们 UI 的图像
- JS – 包含必要的脚本文件。这些 JavaScript 文件负责以下任务
- 向 Web 服务发送 XML HTTP 请求
- 实现选项卡控件
- 处理选项卡点击事件
- 实现灯箱或弹出窗口
此时解释 script.js 文件是值得的。
页面加载时会调用 onload
函数。如前所述,页面会向 Web 服务发送 XML HTTP 请求
var e, i = 0, responseCurrent, responseNext, responseFuture;
if (document.getElementById('tabs') != null) {
var AJAX = null;
if (window.XMLHttpRequest) {
AJAX = new XMLHttpRequest();
} else {
AJAX = new ActiveXObject("Microsoft.XMLHTTP");
}
if (AJAX) {
if (responseCurrent == null) {
AJAX.open("GET", "Events.aspx?year=2010", false);
AJAX.send(null);
responseCurrent = AJAX.responseText;
}
if (responseNext == null) {
AJAX.open("GET", "Events.aspx?year=2011", false);
AJAX.send(null);
responseNext = AJAX.responseText;
}
if (responseFuture == null) {
AJAX.open("GET", "Events.aspx?year=2012", false);
AJAX.send(null);
responseFuture = AJAX.responseText;
}
然后,它用 Web 服务返回的信息填充 div
“Constable
” 的内部 HTML 部分。
var div = document.getElementById('Constable');
div.innerHTML = responseCurrent;
下面的部分处理选项卡的点击事件。它再次根据点击的年份加载 div
的 innerHTML
,其中包含 Web 服务返回的 HTML 代码。
while (e = document.getElementById('tabs').getElementsByTagName('li')[i++]) {
if (e.className == 'on' || e.className == 'off') {
e.onclick = function() {
var getEls =
document.getElementById('tabs').getElementsByTagName('li');
for (var z = 0; z < getEls.length; z++) {
var div =
document.getElementById(getEls[z].getAttribute('title'));
/* Load Events - AjaX */
if (div.id == 'Constable') {
div.innerHTML = responseCurrent;
}
else if (div.id == 'Monet') {
div.innerHTML = responseNext;
}
else if (div.id == 'Vincent') {
div.innerHTML = responseFuture;
}
/* ------------------ */
div.className = div.className.replace('show', 'hide');
getEls[z].className = getEls[z].className.replace('on', 'off');
}
this.className = 'on';
var max = this.getAttribute('title');
document.getElementById(max).className = "show";
}
}
我使用 JQuery 来实现弹出窗口。它基本上是一个 lightbox
控件。参考文件 – Jquery.js 和 jquery.simplemodal.js。
下面的函数实现了弹出窗口。它再次发送 XML HTTP 请求以从 Web 服务获取详细事件信息,并将代码注入 div
的内部 HTML 部分。Jquery 被编程为在点击事件标题时显示 div
。
请注意,正在填充的事件是通过调用 Web 页面 Events.aspx 来获取的。我们之前讨论过该网页的呈现器是如何生成内部 HTML 代码以实现此功能的。
function popup(id) {
id = "DetailedEvent.aspx?" + id;
var AJAX = null;
if (window.XMLHttpRequest) {
AJAX = new XMLHttpRequest();
} else {
AJAX = new ActiveXObject("Microsoft.XMLHTTP");
}
if (AJAX) {
AJAX.open("GET", id, false);
AJAX.send(null);
response = AJAX.responseText;
//alert(response);
eval("json = " + response + ";");
}
var divImage = document.getElementById("imgDiv");
divImage.innerHTML = '<img src="' + json.image + '" alt="" />';
var divheadCont = document.getElementById("headCont");
divheadCont.innerHTML = '<h2>' + json.date + '</h2><h3>' + json.title + '</h3>';
var divrow2 = document.getElementById("row2");
divrow2.innerHTML = '<p>' + json.description + '</p>';
var diveventMainWrap = document.getElementById("eventMainWrap");
$("#eventView").modal();
}
最后,我们有客户端 HTML 页面 - upcoming-events.html,它实现了必要的样式。
摘要
我们可以用下面的图示总结该项目 – Presenter View(Web 服务)。

至此,我们的项目告一段落。我们已成功使用 MVP 模式和客户端-服务器架构实现了基于 Web 的事件日历。该项目演示了如何在设计和开发之间实现关注点分离。该项目还解释了如何消除对代码后台文件的依赖,并开发 .NET 应用程序,使其能够轻松地与其他平台互操作。在此案例中,我们有一个简单的 HTML 客户端作为 UI,它呈现使用 .NET 开发的复杂业务逻辑。只要它们能够对 Web 服务发出 AJAX 调用,就可以轻松地将其迁移到任何其他平台,如 Java 或 PHP。
参考文献
有关 HTTP Handlers 的详细信息,请参阅以下文章