走向ASP.NET架构设计——第七章:阶段总结,“.NET研究”实践篇(上篇)

简介:   示例说明  本篇的例子的是一个在线订票的服务系统。这个系统向外界暴露了一些可以通过Http协议访问的API,在这个订票服务下面允许任意多个隶属机构来使用服务API进行真正的售票活动。如下图所示:  就好比银行外面的那些自动取款机(对应图中的Affiliate A, B, C),可以把它们看成是银行系统的隶属机构,我们就是通过这些取款机来进行存取活动的,其实这些取款机是调用了银行系统的一些服务来进行数据操作,当然我们也可以直接到银行柜台(对应图中的Ticket Shop)去进行存取款操作。

  示例说明

  本篇的例子的是一个在线订票的服务系统。这个系统向外界暴露了一些可以通过Http协议访问的API,在这个订票服务下面允许任意多个隶属机构来使用服务API进行真正的售票活动。如下图所示:

  就好比银行外面的那些自动取款机(对应图中的Affiliate A, B, C),可以把它们看成是银行系统的隶属机构,我们就是通过这些取款机来进行存取活动的,其实这些取款机是调用了银行系统的一些服务来进行数据操作,当然我们也可以直接到银行柜台(对应图中的Ticket Shop)去进行存取款操作。本例中的售票例子和这个有点类似。

  在本例中,在我们将会在上图中的Application和Internal Client之间采用Reservation模式来约定票,通过采用Idempotent模式来确保订票的每个交易只进行一次。

  下面就开始进入实战:

  解决方案建立如下:

  为了演示的方便,上面的Solution把客户端和服务端程序建立在了一起。

  Domain Model

  首先,我们来建立这个系统中所涉及到的一些业务类和一些辅助的类。

  其中:

  Event类代表了一次购票的活动。

  Event类包含了两个集合:一个TicketPurchase集合代表了真实的要购买的票;另外一个TicketReservation集合代表了Reservation模式中的预约票据,或者大家理解为标识,或者令牌,概念类似于ASP.NET中的验证票据 。

  另外两个工厂类提供了一些简单的接口来创建TicketPurchase和TicketReservation。

  下面我们就来看看上面提及的一些类的具体的定义:

  
  
public class TicketReservation
{
public Guid Id { get ; set ; }
public Event Event { get ; set ; }
public DateTime ExpiryTime { get ; set ; }
public int TicketQuantity { get ; set ; }
public bool HasBeenRedeemed { get ; set ; }

public bool HasExpired()
{
return DateTime.Now > ExpiryTime;
}

public bool StillActive()
{
return ! HasBeenRedeemed && ! HasExpired();
}
}
public class TicketPurchase
{
public Guid Id { get ; set ; }
public Event Event { get ; set ; }
public int TicketQuantity { get ; set ; }
}

  为了简化创建票据类的方面,我们添加两个工厂类如下:

  
  
public class TicketReservationFactory
{
public static TicketReservation CreateReservation(Event Event, int tktQty)
{
TicketReservation reservation
= new TicketReservation();

reservation.Id
= Guid.NewGuid();
reservation.Event
= Event;
reservation.ExpiryTime
= DateTime.Now.AddMinutes( 1 );
reservation.TicketQuantity
= tktQty;

return reservation;
}
}

public
class TicketPurchaseFactory
{
public static TicketPurchase CreateTicket(Event Event, int tktQty)
{
TicketPurchase ticket
= new TicketPurchase();

ticket.Id
= Guid.NewGuid();
ticket.Event
= Event;
ticket.TicketQuantity
= tktQty;

return ticket;
}
}

  上面两个工厂的方法都是很直观,简单。在TicketReservationFactory中创建ReservationTicket类的时候,设置这个标识票据的默认过期时间是一分钟。也就是说,整个订票的交易要在一分钟之内完成,当然一分钟只是例子而已,便于例子的测试。可能时间太长了一是耗费太多的资源,二是在安全方面也存在一些隐患。

  下面就来一起看看比较核心的Event类:(下面的代码有点多,在代码的后面我会详细讲述类中每个方法的意思)

  
  
public class Event
{
public Event()
{
ReservedTickets
= new List < TicketReservation > ();
PurchasedTickets
= new List < TicketPurchase > ();
}

public Guid Id { get ; set ; }
public string Name { get ; set ; }
public int Allocation { get ; set ; }
public List < TicketReservation > ReservedTickets { get ; set ; }
public List < TicketPurchase > PurchasedTickets { get ; set ; }

public int AvailableAllocation()
{
int salesAndReservations = 0 ;

PurchasedTickets.ForEach(t
=> salesAndReservations += t.TicketQuantity);

ReservedTickets.FindAll(r
=> r.StillActive()).ForEach(r => salesAndReservations += r.TicketQuantity);

return Allocation - salesAndReservations;
}

public bool CanPurchaseTicketWith(Guid reservationId)
{
if (HasReservationWith(reservationId))
return GetReservationWith(reservationId).StillActive();

return false ;
}

public TicketPurchase PurchaseTicketWith(Guid reservationId)
{
if ( ! CanPurchaseTicketWith(reservationId))
throw new ApplicationException(DetermineWhyATicketCannotbePurchasedWith(reservationId));

TicketReservation reservation
= GetReservationWith(reservationId);

TicketPurchase ticket
= TicketPurchaseFactory.CreateTicket( this , reservation.TicketQuantity);

reservation.HasBeenRedeemed
= true ;

PurchasedTickets.Add(ticket);

return ticket;
}

public TicketReservation GetReservationWith(Guid reservationId)
{
if ( ! HasReservationWith(reservationId))
throw new ApplicationException(String.Format( " No reservation ticket with matching id of '{0}' " , reservationId.ToString()));

return ReservedTickets.FirstOrDefault(t => t.Id == reservationId);
}

private bool HasReservationWith(Guid reservationId)
{
return ReservedTickets.Exists(t => t.Id == reservationId);
}

public string DetermineWhyATicketCannotbePurchasedWith(Guid reservationId)
{
string reservationIssue = "" ;
if (HasReservationWith(reservationId))
{
TicketReservation reservation
= GetReservationWith(reservationId);
if (reservation.HasExpired())
reservationIssue
= String.Format( " Ticket reservation '{0}' has expired " , reservationId.ToString());
else if (reservation.HasBeenRedeemed )
reservationIssue
= String.Format( " Ticket reservation '{0}' has already been redeemed " , reservationId.ToString());
}
else
reservationIssue
= String.Format( " There is no ticket reservation with the Id '{0}' " , reservationId.ToString());

return reservationIssue;
}

private void ThrowExceptionWithDetailsOnWhyTicketsCannotBeReserved()
{
throw new ApplicationException( " There are no tickets available to reserve. " );
}

public bool CanReserveTicket( int qty)
{
return AvailableAllocation() >= qty;
}

public TicketReservation ReserveTicket( int tktQty)
{
if ( ! CanReserveTicket(tktQty))
ThrowExceptionWithDetailsOnWhyTicketsCannotBeReserved();

TicketReservation reservation
= TicketReservationFactory.CreateReservation( this , tktQty);

ReservedTickets.Add(reservation);

return reservation;
}
}

  下面,我们就来看看每个方法的作用:

  AvailableAllocation():这个方法计算现有还有多少票可以卖;用总的票数减去已经卖出的票数和已经预定了的票数。

  CanReserveTicket(int qty):这个检查是否还有足够数量的票供预定。

  ReserveTicket(int qty):这个方法创建一个新的TicketReservation,并且指定在这个标识票据中有多少张真实的票要购买的,并且将标识票据添加到集合中

  HasReservationWith(Guid reservationId):这个方法判断给定Id的TicketReservation是否存在。

  GetReservationWith(Guid reservationId):通过Id标识,获取一个TicketReservation。

  CanPurchaseTicketWith(Guid reservationId):这个方法判断可以基于给定的标识Id来购买真实的票。

  PurchaseTicketWith(Guid reservationId):基于给的预约标识来创建一个新的真实的票TicketPurchase.

  DetermineWhyTicketCannotBePurchase(Guid reservationId):这个方法返回一个字符串结果,说明一下为什么不能基于给定的预约标识来购买票,可以因为标识过期或者我们规定一个标识所代表的一次交易最多只能买指定数量的真实票。

  业务类建立完成之后,下面我们就来创建一个类来进行存取这些业务类所需要的数据。

  Repository

  添加一个IEventReposistory接口,如下:

  
  
public interface IEventRepository
{
Event FindBy(Guid id);
void Save(Event eventEntity);
}

  为了演示的简洁,这个接口定义的很简单。下面就用ADO.NET的方式来实现一个EventRepository.(当然,大家可以采用自己喜欢的数据访问技术)

  下面的代码很多,但是很容易理解:

  
  
public 上海企业网站制作"color: #000000;"> class EventRepository : IEventRepository
{
private string connectionString = @" Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\EventTickets.mdf;Integrated Security=True;User Instance=True " ;

public Event FindBy(Guid id)
{
Event Event
= default (Event);

string queryString = " SELECT * FROM dbo.Events WHERE Id = @EventId " +
" SELECT * FROM dbo.PurchasedTickets WHERE EventId = @EventId " +
" SELECT * FROM dbo.ReservedTickets WHERE EventId = @EventId; " ;

using (SqlConnection connection =
new SqlConnection(connectionString))
{
SqlCommand command
= connection.CreateCommand();
command.CommandText
= queryString;

SqlParameter Idparam
= new SqlParameter( " @EventId " , id.ToString());
command.Parameters.Add(Idparam);

connection.Open();

using (SqlDataReader reader = command.ExecuteReader())
{
if (reader.HasRows)
{
reader.Read();
Event
= new Event();
Event.PurchasedTickets
= new List < TicketPurchase上海网站建设an>>();
Event.ReservedTickets
= new List<TicketReservation>();
Event.Allocation
= int.Parse(reader["Allocation"].ToString());
Event.Id
= new Guid(reader["Id"].ToString());
Event.Name
= reader["Name"].ToString();

if (reader.NextRes上海闵行企业网站制作ult())
{
if (reader.HasRows)
{
while (reader.Read())
{
TicketPurchase ticketPurchase
= new TicketPurchase();
ticketPurchase.Id
= new Guid(reader["Id"].ToString(上海徐汇企业网站制作));
ticketPurchase.Event
= Event;
ticketPurchase.TicketQuantity
= int.Parse(reader["TicketQuantity"].ToString());
Event.PurchasedTickets.Add(ticketPurchase);
}
}
}

if (reader.NextResult())
{
if (reader.HasRows)
{
while (reader.Read())
{
TicketReservation ticketReservation
= new TicketReservation();
ticketReservation.Id
= new Guid(reader["Id"].ToString());
ticketReservation.Event
= Event;
ticketReservation.ExpiryTime
= DateTime.Parse(reader["ExpiryTime"].ToString());
ticketReservation.TicketQuantity
= int.Parse(reader["TicketQuantity"].ToString());
ticketReservation.HasBeenRedeemed
= bool.Parse(reader["HasBeenRedeemed"].ToString());
Event.ReservedTickets.Add(ticketReservation);
}
}
}
}
}
}

return Event;
}

public void Save(Event Event)
{
// Code to save the Event entity
// is not required in this senario

RemovePurchasedAndReservedTicketsFrom(Event);

InsertPurchasedTicketsFrom(Event);
InsertReservedTicketsFrom(Event);

}

public void InsertReservedTicketsFrom(Event Event)
{
string insertSQL = "INSERT INTO ReservedTickets " +
"(Id, EventId, TicketQuantity, ExpiryTime, HasBeenRedeemed) " +
"VALUES " +
"(@Id, @EventId, @TicketQuantity, @ExpiryTime, @HasBeenRedeemed);";

foreach (TicketReservation ticket in Event.ReservedT上海企业网站设计与制作ickets)
{
using (SqlConnection connection =
new SqlConnection(connectionString))
{
SqlCommand command
= connection.CreateCommand();
command.CommandText
= insertSQL;

SqlParameter Idparam
= new SqlParameter("@Id", ticket.Id.ToString());
command.Parameters.Add(Idparam);

SqlParameter EventIdparam
= new SqlParameter("@EventId", ticket.Event.Id.ToString());
command.Parameters.Add(EventIdparam);

SqlParameter TktQtyparam
= new SqlParameter("@TicketQuantity", ticket.TicketQuantity);
command.Parameters.Add(TktQtyparam);

SqlParameter Expiryparam
= new SqlParameter("@ExpiryTime", ticket.ExpiryTime);
command.Parameters.Add(Expiryparam);

SqlParameter HasBeenRedeemedparam
= new SqlParameter("@HasBeenRedeemed", ticket.HasBeenRedeemed);
command.Parameters.Add(HasBeenRedeemedparam);

connection.Open();
command.ExecuteNonQuery();
}
}

}

public void InsertPurchasedTicketsFrom(Event Event)
{
string insertSQL = "INSERT INTO PurchasedTickets " +
"(Id, EventId, TicketQuantity) " +
"VALUES " +
"(@Id, @EventId, @TicketQuantity);";

foreach (TicketPurchase ticket in Event.PurchasedTickets)
{
using (SqlConnection connection =
new SqlConnection(connectionString))
{
SqlCommand command
= connection.CreateCommand();
command.CommandText
= insertSQL;

SqlParameter Idparam
= new SqlParameter("@Id", ticket.Id.ToString());
co上海闵行企业网站设计与制作mmand.Parameters.Add(Idparam);

SqlParameter EventIdparam
= new SqlParameter("@EventId", ticket.Event.Id.ToString());
command.Parameters.Add(EventIdparam);

SqlParameter TktQtyparam
= new SqlParameter("@TicketQuantity", ticket.TicketQuantity);
command.Parameters.Add(TktQtyparam);

connection.Open();
command.ExecuteNonQuery();
}
}
}

public void RemovePurchasedAndReservedTicketsFrom(Event Event)
{
string deleteSQL = "DELETE PurchasedTickets WHERE EventId = @EventId; " +
"DELETE ReservedTickets WHERE EventId = @EventId;";

using (SqlConnection connection =
new SqlConnection(connectionString))
{
SqlCommand command
= connection.CreateCommand();
command.CommandText
= deleteSQL;

SqlParameter Idparam
= new SqlParameter("@EventId", Event.Id.ToString());
command.Parameters.Add(Idparam);

connection.Open();
command.ExecuteNonQuery();
}
}
}

  今天就到这里。下一篇接着讲述!

目录
相关文章
|
3月前
|
人工智能 开发框架 .NET
如何掌握.NET技术,引领开发前沿:.NET技术的核心能力、在现代开发中的应用实践、以及如何通过.NET技术实现持续创新。
.NET技术已成为软件开发不可或缺的部分,本文分三部分探讨:核心能力如多语言支持、统一运行时环境、丰富的类库及跨平台能力;现代开发实践包括企业级应用、Web与移动开发、云服务及游戏开发;并通过性能优化、容器化、AI集成等方面实现持续创新,使开发者站在技术前沿。
67 3
|
1月前
|
SQL 开发框架 .NET
ASP连接SQL数据库:从基础到实践
随着互联网技术的快速发展,数据库与应用程序之间的连接成为了软件开发中的一项关键技术。ASP(ActiveServerPages)是一种在服务器端执行的脚本环境,它能够生成动态的网页内容。而SQL数据库则是一种关系型数据库管理系统,广泛应用于各类网站和应用程序的数据存储和管理。本文将详细介绍如何使用A
55 3
|
2月前
|
开发框架 前端开发 .NET
VB.NET中如何利用ASP.NET进行Web开发
在VB.NET中利用ASP.NET进行Web开发是一个常见的做法,特别是在需要构建动态、交互式Web应用程序时。ASP.NET是一个由微软开发的开源Web应用程序框架,它允许开发者使用多种编程语言(包括VB.NET)来创建Web应用程序。
59 5
|
1月前
|
开发框架 缓存 算法
开源且实用的C#/.NET编程技巧练习宝库(学习,工作,实践干货)
开源且实用的C#/.NET编程技巧练习宝库(学习,工作,实践干货)
|
1月前
|
存储 消息中间件 前端开发
.NET常见的几种项目架构模式,你知道几种?
.NET常见的几种项目架构模式,你知道几种?
|
1月前
|
Cloud Native API C#
.NET云原生应用实践(一):从搭建项目框架结构开始
.NET云原生应用实践(一):从搭建项目框架结构开始
|
3月前
|
jenkins 测试技术 持续交付
解锁.NET项目高效秘籍:从理论迷雾到实践巅峰,持续集成与自动化测试如何悄然改变游戏规则?
【8月更文挑战第28天】在软件开发领域,持续集成(CI)与自动化测试已成为提升效率和质量的关键工具。尤其在.NET项目中,二者的结合能显著提高开发速度并保证软件稳定性。本文将从理论到实践,详细介绍CI与自动化测试的重要性,并以ASP.NET Core Web API项目为例,演示如何使用Jenkins和NUnit实现自动化构建与测试。每次代码提交后,Jenkins自动触发构建流程,通过编译和运行NUnit测试确保代码质量。这种方式不仅节省了时间,还能快速发现并解决问题,推动.NET项目开发迈向更高水平。
51 8
|
3月前
|
设计模式 存储 前端开发
揭秘.NET架构设计模式:如何构建坚不可摧的系统?掌握这些,让你的项目无懈可击!
【8月更文挑战第28天】在软件开发中,设计模式是解决常见问题的经典方案,助力构建可维护、可扩展的系统。本文探讨了.NET中三种关键架构设计模式:MVC、依赖注入与仓储模式,并提供了示例代码。MVC通过模型、视图和控制器分离关注点;依赖注入则通过外部管理组件依赖提升复用性和可测性;仓储模式则统一数据访问接口,分离数据逻辑与业务逻辑。掌握这些模式有助于开发者优化系统架构,提升软件质量。
55 5
|
3月前
|
Kubernetes 监控 Devops
【独家揭秘】.NET项目中的DevOps实践:从代码提交到生产部署,你不知道的那些事!
【8月更文挑战第28天】.NET 项目中的 DevOps 实践贯穿代码提交到生产部署全流程,涵盖健壮的源代码管理、GitFlow 工作流、持续集成与部署、容器化及监控日志记录。通过 Git、CI/CD 工具、Kubernetes 及日志框架的最佳实践应用,显著提升软件开发效率与质量。本文通过具体示例,助力开发者构建高效可靠的 DevOps 流程,确保项目成功交付。
76 0
|
3月前
|
XML 开发框架 .NET
.NET框架:软件开发领域的瑞士军刀,如何让初学者变身代码艺术家——从基础架构到独特优势,一篇不可错过的深度解读。
【8月更文挑战第28天】.NET框架是由微软推出的统一开发平台,支持多种编程语言,简化应用程序的开发与部署。其核心组件包括公共语言运行库(CLR)和类库(FCL)。CLR负责内存管理、线程管理和异常处理等任务,确保代码稳定运行;FCL则提供了丰富的类和接口,涵盖网络、数据访问、安全性等多个领域,提高开发效率。此外,.NET框架还支持跨语言互操作,允许开发者使用C#、VB.NET等语言编写代码并无缝集成。这一框架凭借其强大的功能和广泛的社区支持,已成为软件开发领域的重要工具,适合初学者深入学习以奠定职业生涯基础。
102 1
下一篇
无影云桌面