UWP暑期生产实习——DoMeAFavor 个人技术文档

在本次project中,本人主要负责服务器与客户端之间的通信、数据库设计以及部分ViewModel和页面设计,实现了以下几个功能:

1.登录注册功能

2.发布任务

3.接收任务

下面将从技术说明和测试用例具体阐述我这几天来的工作。

 

一、技术文档

在介绍我的具体业务之前,首先我要简述一下这几天的工作方向:

1.简单的页面构想(1 day)

2.数据库表的设计(1 day)

3.MVVM设计模式的理解(3 days)

4.ASP.NET Core 2.0 MVC with Entity FrameWork Core的学习与理解(2 days)

5.Controller、Service和ViewModel的设计和部署(5 days)

我将按照从上述标号1至5的顺序进行技术说明。

1.界面设计

界面具体的pattern和页面之间的跳转主要由我组的前端设计师完成,我们以NavigationView为基础页面,其中左侧item包含任务广场、个人主页、登录页面以及积分充值页面。在此基础上,添加了回退按键,实现方法为在Mainpage.xaml.cs中添加On_BackRequested函数处理回退请求,具体实现方法如下图:

值得一提的是在 NavigationView中BackRequested="NavView_BackRequested"属性是Microsoft于最新的1803版本中才添加进来的,相比老版本中为实现页面回退必须在app.xaml.cs中重写override函数要人性化和方便的多,在微软的官方文档中有详细的说明。(https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/navigationview#backwards-navigation

2.数据库设计

在DoMeAFavor项目中,主要存在两大类:其一是User类,储存用户各种信息,其二是User类,储存用户发布的任务信息,于是我们设计了一个UserMission表来存储用户和任务之间的联系,并在此表中以UserId和MissionId为联合主键。

关系图如下:

 

3.基于MVVM模式的客户端设计

MVVM设计模式我用了整整三天的时间去理解,浅谈一下我通过这几天的学习,个人对MVVM设计模式的理解,如有偏差欢迎指出。

首先,MVVM是Model——View——ViewModel的缩写,是基于MVC(Model——View——Controller)模式的进化版本,后者多用于ASP.NET的服务器架构,而前者目前在客户端的架构上被广泛使用。那么,Model View and ViewModel 之间有什么联系呢?我用一个简单的小例子来介绍,比如一个孩子(View)和家长(ViewModel)说想吃猪肉(Model),但是一头猪身上有很多不同的部位例如排骨、里脊等等,它们都是猪肉的一种。那么Service就是商户,有卖排骨的摊贩、有卖里脊的摊贩、也有卖猪蹄的摊贩,Service为ViewModel提供他们想要的部位的肉。那么,ViewModel,也就是家长,她去Service那里取得她想要的部位进行加工以后给孩子(View)吃。对于View来说,他们不直接Service进行交互,而是通过家长去买然后煮熟,也就是ViewModel为View提供所需的功能,这就是MVVM设计模式,它的好处就在于三部分彼此独立,耦合性低,在编码过程中方便进行单元测试也方便找出bug。

那么,在我们的项目中,每一个页面都给了他们各自的ViewModel

 

在这些ViewModel中他们定义了很多的RelayCommand来实现业务逻辑例如登录、添加、删除、接收刷新等等,在ViewModel中

ObservableCollection和RelayCommand等数据或指令集负责与前端页面以Binding的进行交互。

其中ViewModelLocator定义一个SimpleIOC单件模式,具体内容由我的队友实现。

然后,在所有的ViewModel中,都调用了他们各自所需的Service,我编写的Service有以下这些

他们主要负责与服务器之间的通信来实现登录获取用户对象、获取未被接受的任务列表、获取用户接收的任务列表、获取用户完成的任务列表等等,具体的通信方式和函数内容我会在下面的ASP.NET Core 2.0中详细说明。

展示一下我们的Type Dependencies Diagram

4.ASP.NET Core 2.0 MVC with Entity FrameWork Core以及Controller和Service的详细设计

在本次项目中,我主要负责客户端和服务器端的通信,我们的项目服务器由ASP.NET Core 2.0 MVC为框架搭建,具体搭建由我的队友实现,下面我将介绍我主要负责的MVC中的Controller的设计以及与其对应的Service的设计。

首先,什么是Controller呢?Controller负责接收并执行来自URL的request,并根据请求完成相对应的操作。在ASP.NET中,系统可以为用户指定的Model自动一个Controller

以项目中UsersController为例,系统为我们自动生成了五个函数,分别为:

[Produces("application/json")]
    [Route("api/Users")]

// GET: api/Users
        [HttpGet]
        public IEnumerable<User> GetUsers()

// GET: api/Users/5
        [HttpGet("{id}")]
        public async Task<IActionResult> GetUser([FromRoute] int id)

// PUT: api/Users/5
        [HttpPut("{id}")]
        public async Task<IActionResult> PutUser([FromRoute] int id, [FromBody] User user)

// POST: api/Users
        [HttpPost]
        public async Task<IActionResult> PostUser([FromBody] User user)

// DELETE: api/Users/5
        [HttpDelete("{id}")]

这五个函数分别对应着来自Url的取得所有对象、取得单个对象、更新一个对象、添加一个对象和删除一个对象,根据[ ]中的内容可以区分

其中参数前的[FromRoute]限制了参数只能来自于当前请求

与之配套使用的是("{id}")则为五种Attribute指定FromRoute中限制的参数

因此,我们需要修改这些函数中的内容以实现我们想达到的检索或增加删除操作,以HttpGet为例,我希望制作一个Controller,来实现向服务器发送正确的用户名密码,请求服务器返回相应的用户对象,那么我进行的修改为

[HttpGet]
        public async Task<IActionResult> GetUser(string userid, string password)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            var user = await _context.Users.SingleOrDefaultAsync(m => m.UserId == userid && m.Password == password);

            if (user == null)
            {
                return NotFound();
            }

            return Ok(user);
        }

我为其指定的请求参数为userid和password 在route地址后加“?”和我请求的参数,服务器就会调用HttpGet Attribute处理相应的请求。

需要注意的一点就是不要在相同的根路径([Route("api/Users")])中定义相似的操作,服务器无法区分就会报错,例如,我们想要检索已接受和未接受的任务时,需要新建两个来自不同route的Controller。

当我们想要检索某个用户发布的所有任务时,需要在Controller函数中加入LINQ语句,这与sql语言很相似,但是也有少许差别,在这里,我以检索已发布的任务为例,需要进行表的连接操作,对应的LINQ语句为:

var missions = from a in _context.Missions
                from b in _context.UserMissions
                where a.MissionId == b.MissionId && b.UserId == user.Id
                select a;

那么在客户端谁来负责向服务器发送这些request呢?Newtonsoft.Json为我们提供了四个Method,分别是HttpClient.GetAsync、HttpClient.PutAsync、HttpClient.PostAsync和HttpClient.DeleteAsync,分别对应着Controller中的四种Attribute,但是,想要服务器所提供的数据是以json形式存储的,无法直接转化为内存数据,因此需要先用serialize和deserialize方法将二者进行转化。以发布任务为例:

/// <summary>
        /// 添加任务
        /// </summary>
        /// <param name="mission"></param> 任务
        /// <param name="user"></param> 发布人
        /// <returns></returns>
        public async Task AddAsync(Mission mission,User user)
        {
            using (var client = new HttpClient())
            {
                var json = JsonConvert.SerializeObject(mission);
                await client.PostAsync(ServiceEndpoint,
                    new StringContent(json, Encoding.UTF8, "application/json"));
                var latestCreatedMissionJson =
                    await client.GetStringAsync(MissionServiceEndpoint+"?missionname=" +
                                                mission.MissionName);
                var latestCreatedMission = JsonConvert.DeserializeObject<Mission[]>(latestCreatedMissionJson);
                
                var usermission = new UserMission
                {
                    UserId = user.Id,
                    MissionId = latestCreatedMission.Last().MissionId

                };
                var umjson = JsonConvert.SerializeObject(usermission);
                await client.PostAsync(UMServiceEndpoint,
                    new StringContent(umjson, Encoding.UTF8, "application/json"));
            }
        }

先将任务序列化post到服务器,再向服务器发送请求Get到服务器自动生成的主键id,再Post一张新的UserMission表,因为我在controller的制作过程中以MissionName为检索参数,所以,当任务重名时,会返还多个id,产生报错,相对应的解决办法是返还列表,取得最后一个元素的id即是刚刚添加的任务。

其中还有一个坑就是我在接收任务的Service制作中,下面是源码

public async Task AcceptAsync(Mission mission,User user)
        {
            using (var client = new HttpClient())
            {
                
                var json = await client.GetStringAsync(PBUMServiceEndpoint + "missionname=" + mission.MissionName);
                var usermisson = JsonConvert.DeserializeObject<UserMission[]>(json);
                usermisson.First().ReceiverId = user.Id;
                var newjson = JsonConvert.SerializeObject(usermisson.First());
                await client.PutAsync(UMServiceEndpoint + "/" + usermisson.First().MissionId,                   
                    new StringContent(newjson, Encoding.UTF8, "application/json"));
                
            }
        }

var usermisson = JsonConvert.DeserializeObject<UserMission[]>(json);这句话一定要返还链表,而不能返还单个元素,要么就会报错,哪怕我在Controller的检索后返回的是单个对象,最后我取usermisson.First()得以解决。但是如果是返还单个mission或者user就可以返还单个元素,我的猜想是因为UserMission表为连接表,它以两个外键作为联合主键,导致服务器处理get请求时会产生与其他两个表不同的结果。

二.单元测试

我编写的测试函数主要为Service测试,以及少部分ViewModel测试,所测试的方法为接收任务、发布任务、获取所有任务、获取接收的任务、注册、登录、获取发布的任务、更改用户信息等。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值