使用 MVVM、IoC、DI 和 API 的天气应用程序





5.00/5 (9投票s)
这是一个为 Windows (通用) 10 设备开发的应用程序,采用了 MVVM、IoC 容器和依赖注入。还集成了来自 Yahoo 的 API 和 SQLite 服务。
引言
我开发了一个适用于 Windows 通用 10 设备的应用程序。它通过 Yahoo 的 API (Application Programming Interface,应用程序编程接口) 提供世界各地多个城市的天气预报。它包含了 MVVM (Model View View-Model,模型-视图-视图模型) 模式 (更具体地说,是 MVVM light 库) 和 IoC (Inversion of Control,控制反转) 架构。我还使用 SQLite (Structured Query Language lite,结构化查询语言精简版) 数据库来存储搜索结果。那么,它是如何实现的呢?这里有一些信息
MVVM (Model View View-Model)
MVVM 是一种软件架构模式,顾名思义,它由三个部分组成。在您的项目中,有三个部分:模型、视图和视图模型,如后面的图像所示。
模型提供数据库访问,但在此项目中,我使用了 Yahoo API,并将其映射到 Repositories 文件夹。
视图模型包含 ICommands
和一些绑定属性。所有视图模型都继承自 ViewModelBaseClass
,该类实现了 INotifyPropetyChanged
接口。
最后一部分是视图。它包含 XAML 页面和数据模板。数据模板存储在 DataTemplates 文件夹中。
每个部分之间相互通信
- 视图与视图模型通信
- 视图模型与模型 (和存储库) 通信
- 存储库访问 API (或其他数据源)
理论上是正确的,但在实际情况中,视图也与模型通信。这可能是为了元素属性到模型属性的数据绑定。
在图像中,还有第四部分称为单元测试。这些是您可以用来测试代码的测试。此项目未实现此功能,本文也未对此进行解释。
IoC 容器 (Inversion of Control Container,控制反转容器)
IoC 容器是具有基本功能以注册和解析实例的容器。在此项目中,我们将使用 MVVM light 中的 SimpleIoc
类。在此项目中,我们将使用以下方法
名称 | 描述 | |
Register<TClass>(Boolean) |
| |
GetInstance<TService>() | 提供获取给定类型实例的方法。如果之前没有实例化任何实例,则会创建一个新实例。如果已经创建了一个实例,则返回该实例。 注意:如果类未注册,则该方法将返回 |
有关更多信息和方法,您可以阅读 mvvmlight.net 上的文档:mvvmlight.net。
还有一个默认的无参数构造函数,它初始化 SimpleIoc
类的新实例。
依赖注入或 DI
依赖注入是一种工作方式,可以使代码独立于您正在工作的类。为此,您可以使用接口。它们只定义了方法、属性和变量的签名。一个示例是,在开发过程中使用一个模拟类,或者在部署时使用一个 Web 服务。下面是一个代码示例
public interface IDataSource
{
IEnumarble<object> Get(); // used for get all the objects
}
public class DummySource : IDataSource
{
public IEnumarble<object> Get()
{
return new List<object> {
new { Letter = "A", Number = "1" },
new { Letter = "A", Number = "1" }
}
}
}
public class WebSource : IDataSource
{
public IEnumarble<object> Get()
{
//return all objects of a web service
}
}
在调用 Get()
方法的地方,类是什么并不重要。它可以是 dummy
类,也可以是 webservice
。
注意:在调用方法的地方,不要使用类,而是使用接口,如下面的代码所示
IDataSource source = new DummySource();
IEnumarble<object> dummyData = source.Get();
source = new WebSource();
IEnumarble<object> webData = source.Get();
这段代码之所以能工作,是因为变量 source
独立于 DummySource
和 WebSource
类,因为我创建了一个名为 IDataSource
的“包装器” interface
,它具有方法的签名。
构建应用程序
解决方案的构建
在解决方案中,我创建了三个项目,分别命名为
Data
包含用于访问 API 的存储库。为此项目下载Newtonsoft.Json
NuGet 包。Models
包含我们需要的类。UWP
代表 Universal Windows 10 Project (通用 Windows 10 项目),并包含以下文件夹- Assets 是项目中默认添加的文件夹,包含 Windows 商店中 Logo 的图像。
- DataTemplates 用于存储数据模板
- Pages 包含我们将要使用的页面
- Services 包含 IoC 容器
- ViewModels 包含视图模型和一个文件夹
- core 用于存储核心数据
我们还需要一些 NuGet 包,您可以从 NuGet 管理器下载。对于数据项目,是 Newtonsoft.JSON
;对于 UWP 项目,我们需要 MvvmLight
。
页面的构建
如果 UWP 项目已构建,您将在该项目中看到以下项
- Properties 包含该项目的属性。
- References 包含该项目所需的引用。
- 我们刚刚创建的文件夹。
- App.xaml 包含项目的重要资源和信息。
- MainPage.xaml 是应用程序运行时显示的第一个页面。
- Package.appxmanifest
- project.json
- Wheater.UWP_TemporaryKey.pfx
首先,我们需要 MainPage.xaml。
API (Application Programming Interface,应用程序编程接口) 的使用
API 或应用程序编程接口是一种服务,用于获取由其他实例 (如 Yahoo) 提供的例程、协议、工具或数据,软件程序员可以在其程序、应用程序和网站中访问。它还可以被机器人、Dominica、无人机等发现。
我们需要的第一条 URL
首先,我们必须访问 Yahoo 的 API 来获取数据。我们将使用的 URL (Uniform Recourse Locator,统一资源定位符) 是这个
https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.
forecast%20where%20woeid%20in%20(select%20woeid%20from%20geo.places%20where%20text%3D%22@placemame%22)
&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys
我在 developer.yahoo.com/weather/ 上找到了该 URL。您可以看到参数 q
,它是一个 SQL 语句
SELECT *
FROM weather.forecast
WHERE woeid
IN (SELECT woeid
FROM geo.places
WHERE text = @placemame)
这将从 weather.forecast
表中选择所有字段,其中 woeid
(WOEID 代表 Where On Earth IDentifier,地球标识符) 等于从 geo.places
表中 select
的值,该表中的 text
字段等于变量 @placename
。此变量必须有一个值,这样 URL 才是有效的。在这种情况下,它是一个 string
,或者用 SQL 语言来说,它是 nvarchar
或 text
类型,具体取决于 Yahoo 使用的 SQL 语言。
在上面的 URL 中,您还可以看到第二个参数,称为 format
。在本例中,我们将使用 JSON (JavaScript Object Notation,JavaScript 对象表示法) 作为接收值。
将 JSON 代码转换为 C# 类
如果您导航到该 URL,您会看到此 JSON 代码。这里有 2016 年 3 月 1 日来自比利时 Meenen 的天气预报
{
"query":{
"count":1,
"created":"2016-03-01T17:31:15Z",
"lang":"en-US",
"results":{
"channel":{
"title":"Yahoo! Weather - Meenen, BE",
"link":"http://us.rd.yahoo.com/dailynews/rss/weather/Meenen__BE/
*http://weather.yahoo.com/forecast/FRXX4403_f.html",
"description":"Yahoo! Weather for Meenen, BE",
"language":"en-us",
"lastBuildDate":"Tue, 01 Mar 2016 6:00 pm CET",
"ttl":"60",
"location":{
"city":"Meenen",
"country":"Belgium",
"region":""
},
"units":{
"distance":"mi",
"pressure":"in",
"speed":"mph",
"temperature":"F"
},
"wind":{
"chill":"37",
"direction":"200",
"speed":"20"
},
"atmosphere":{
"humidity":"93",
"pressure":"29.83",
"rising":"0",
"visibility":"3.73"
},
"astronomy":{
"sunrise":"7:28 am",
"sunset":"6:30 pm"
},
"image":{
"title":"Yahoo! Weather",
"width":"142",
"height":"18",
"link":"http://weather.yahoo.com",
"url":"http://l.yimg.com/a/i/brand/purplelogo//uh/us/news-wea.gif"
},
"item":{
"title":"Conditions for Meenen, BE at 6:00 pm CET",
"lat":"50.8",
"long":"3.12",
"link":"http://us.rd.yahoo.com/dailynews/rss/weather/Meenen__BE/
*http://weather.yahoo.com/forecast/FRXX4403_f.html",
"pubDate":"Tue, 01 Mar 2016 6:00 pm CET",
"condition":{
"code":"26",
"date":"Tue, 01 Mar 2016 6:00 pm CET",
"temp":"45",
"text":"Cloudy"
},
"description":"\n<img src=\"http://l.yimg.com/a/i/us/we/52/26.gif\"/>
<br />\n<b>Current Conditions:</b><br />\nCloudy, 45 F<BR />\n<BR />
<b>Forecast:</b><BR />\nTue - Showers Late. High: 48 Low: 42<br />
\nWed - PM Rain/Wind. High: 48 Low: 36<br />\nThu - Showers/Wind.
High: 44 Low: 34<br />\nFri - Showers. High: 45 Low: 31<br />
\nSat - PM Showers. High: 46 Low: 33<br />\n<br />\n
<a href=\"http://us.rd.yahoo.com/dailynews/rss/weather/Meenen__BE/
*http://weather.yahoo.com/forecast/FRXX4403_f.html\">
Full Forecast at Yahoo! Weather</a><BR/><BR/>\n(provided by
<a href=\"http://www.weather.com\" >The Weather Channel</a>)<br/>\n",
"forecast":[
{
"code":"45",
"date":"1 Mar 2016",
"day":"Tue",
"high":"48",
"low":"42",
"text":"Showers Late"
},
{
"code":"12",
"date":"2 Mar 2016",
"day":"Wed",
"high":"48",
"low":"36",
"text":"PM Rain/Wind"
},
{
"code":"11",
"date":"3 Mar 2016",
"day":"Thu",
"high":"44",
"low":"34",
"text":"Showers/Wind"
},
{
"code":"11",
"date":"4 Mar 2016",
"day":"Fri",
"high":"45",
"low":"31",
"text":"Showers"
},
{
"code":"39",
"date":"5 Mar 2016",
"day":"Sat",
"high":"46",
"low":"33",
"text":"PM Showers"
}
],
"guid":{
"isPermaLink":"false",
"content":"FRXX4403_2016_03_05_7_00_CET"
}
}
}
}
}
}
当然,您可以手动创建正确的 C# 类,但在互联网上,有一些简单的工具可以为您完成这项工作。其中一个是 json2csharp.com (仅限 C#) 或 jsonutils.com (支持许多其他语言),它们可以为您转换所有代码。通过复制粘贴将类导入您的项目,并将 RootObject
重命名为 YahooWeather
。
在应用程序中使用 API
在数据项目中创建一个名为 WebRepo
的新类,并将以下代码放入其中以使用 API。
public class WebRepo
{
public static async YahooWeather GetData(string placename)
{
try
{
using (HttpClient client = new HttpClient())
{
string url = $@"https://query.yahooapis.com/v1/public/yql?q=select%20
*%20from%20weather.forecast%20where%20woeid%20in%20
(select%20woeid%20from%20geo.places%20where%20text%3D%22
{placemame}%22)&format=json&env=store%3A%2F%2Fdatatables.org
%2Falltableswithkeys";
string yahooGeoJSON = await client.GetStringAsync(url);
return JsonConvert.DeserializeObject<YahooWeather>(yahooGeoJSON);
}
}
catch
{
return null;
}
}
}
稍后,我们将为此类实现依赖注入。