走向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();
}
}
}

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

目录
相关文章
|
1月前
|
存储 开发框架 前端开发
前端框架EXT.NET Dotnet 3.5开发的实验室信息管理系统(LIMS)成品源码 B/S架构
发展历史:实验室信息管理系统(LIMS),就是指通过计算机网络技术对实验的各种信息进行管理的计算机软、硬件系统。也就是将计算机网络技术与现代的管理思想有机结合,利用数据处理技术、海量数据存储技术、宽带传输网络技术、自动化仪器分析技术,来对实验室的信息管理和质量控制等进行全方位管理的计算机软、硬件系统,以满足实验室管理上的各种目标(计划、控制、执行)。
34 1
|
15天前
|
机器学习/深度学习 JSON 测试技术
CNN依旧能战:nnU-Net团队新研究揭示医学图像分割的验证误区,设定先进的验证标准与基线模型
在3D医学图像分割领域,尽管出现了多种新架构和方法,但大多未能超越2018年nnU-Net基准。研究发现,许多新方法的优越性未经严格验证,揭示了验证方法的不严谨性。作者通过系统基准测试评估了CNN、Transformer和Mamba等方法,强调了配置和硬件资源的重要性,并更新了nnU-Net基线以适应不同条件。论文呼吁加强科学验证,以确保真实性能提升。通过nnU-Net的变体和新方法的比较,显示经典CNN方法在某些情况下仍优于理论上的先进方法。研究提供了新的标准化基线模型,以促进更严谨的性能评估。
39 0
|
17天前
|
Linux C# C++
【.NET Developer】创建ASP.NET Core Blazor项目并打包为Linux镜像发布到Azure应用服务
本文介绍了如何使用VS2019和.NET框架创建一个Blazor应用,并将其部署到Azure应用服务。首先,Blazor是一个使用C#而非JavaScript构建交互式Web UI的框架,支持共享服务器和客户端应用逻辑,以及与Docker和Azure集成。任务包括创建Blazor项目,配置Dockerfile为Linux容器,本地测试,发布到Azure Container Registry (ACR),然后在Azure App Service for Container上部署。在部署过程中,需确保Docker设置正确,开启ACR的Admin访问权限,并监控镜像拉取和容器启动日志。
|
1月前
|
开发框架 JSON .NET
.Net4.0 Web.config 配置实践
.Net4.0 Web.config 配置实践
|
1月前
|
机器学习/深度学习 算法 数据可视化
MATLAB基于深度学习U-net神经网络模型的能谱CT的基物质分解技术研究
MATLAB基于深度学习U-net神经网络模型的能谱CT的基物质分解技术研究
|
1月前
|
开发框架 前端开发 .NET
C# .NET面试系列六:ASP.NET MVC
<h2>ASP.NET MVC #### 1. MVC 中的 TempData\ViewBag\ViewData 区别? 在ASP.NET MVC中,TempData、ViewBag 和 ViewData 都是用于在控制器和视图之间传递数据的机制,但它们有一些区别。 <b>TempData:</b> 1、生命周期 ```c# TempData 的生命周期是短暂的,数据只在当前请求和下一次请求之间有效。一旦数据被读取,它就会被标记为已读,下一次请求时就会被清除。 ``` 2、用途 ```c# 主要用于在两个动作之间传递数据,例如在一个动作中设置 TempData,然后在重定向到另
148 5
|
1月前
|
数据安全/隐私保护 Windows
.net三层架构开发步骤
.net三层架构开发步骤
20 0
|
1月前
|
开发框架 中间件 .NET
C# .NET面试系列七:ASP.NET Core
## 第一部分:ASP.NET Core #### 1. 如何在 controller 中注入 service? 在.NET中,在ASP.NET Core应用程序中的Controller中注入服务通常使用<u>依赖注入(Dependency Injection)</u>来实现。以下是一些步骤,说明如何在Controller中注入服务: 1、创建服务 首先,确保你已经在应用程序中注册了服务。这通常在Startup.cs文件的ConfigureServices方法中完成。例如: ```c# services.AddScoped<IMyService, MyService>(); //
143 0
|
1月前
|
SQL 开发框架 JavaScript
分享33个ASP.NET电子商务源码和40个ASP.NET控件组件源码,总有一款适合您
分享33个ASP.NET电子商务源码和40个ASP.NET控件组件源码,总有一款适合您
44 0
|
1月前
|
开发框架 前端开发 .NET
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
91 0