ASP.NET MVC5(二)基本语言特性

作者 Zhendong Ho 日期 2021-03-09
ASP.NET MVC5(二)基本语言特性

打开VS,创建新项目,选择模板ASP.NET Web应用程序,项目名称为LanguageFeatures,选择MVC的空模板。

右键项目,添加引用,在程序集中找到System.Net.Http并添加到项目。

自动属性

Models文件夹添加Product类,其中有自动属性和规则属性。

namespace LanguageFeatures.Models
{
public class Product
{
private string name;

public int ProductID { get; set; }

// 规则属性
public string Name
{
get { return ProductID + name; }
set { name = value; }
}

// 自动属性
public string Description { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
}
}

添加HomeController,添加AutoProperty动作方法

using LanguageFeatures.Models;
using System.Web.Mvc;

namespace LanguageFeatures.Controllers
{
public class HomeController : Controller
{
public string Index()
{
return "Navigate to a URL to show an example";
}

public ViewResult AutoProperty()
{
Product myProduct = new Product();

myProduct.Name = "Kayak";
string productName = myProduct.Name;

return View("Result", (object)string.Format("Product name: {0}", productName));
}
}
}

添加Result视图。

@model String

@{
Layout = null;
}

<!DOCTYPE html>

<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Result</title>
</head>
<body>
<div>
@Model
</div>
</body>
</html>

运行项目并导航到/Home/AutoProperty,显示的结果为:Product name: 0Kayak

对象与集合的初始化器

在HomeController使用对象初始化器。

public ViewResult CreateProduct()
{
// 创建并填充一个新的Product对象
Product myProduct = new Product
{
ProductID = 100,
Name = "Kayak",
Description = "A boat for one person",
Price = 275M,
Category = "Watersport"
};

return View("Result", (object)string.Format("Category: {0}", myProduct.Category));
}

在HomeController初始化两个泛型集合和一个数组。

public ViewResult CreateCollection()
{
string[] stringArray = { "apple", "orange", "plum" };

List<int> intList = new List<int> { 10, 20, 30, 40 };

Dictionary<string, int> myDict = new Dictionary<string, int>
{
{ "apple", 10 },
{ "orange", 20 },
{ "plum", 30 }
};

return View("Result", (object)stringArray[1]);
}

扩展方法

扩展方法是指,给那些不是你拥有的、不能直接修改的类添加方法的一种方便的办法

在Models文件夹添加ShoppingCar类。

public class ShoppingCart
{
public List<Product> Products { get; set; }
}

假设需要知道ShoppingCart类中Product对象的总值,但又不能修改ShoppingCart类,可以使用扩展方法实现功能。

在Models文件夹添加MyExtensionMethods类。

public static class MyExtensionMethods
{
public static decimal TotalPrices(this ShoppingCart cartParam)
{
decimal total = 0;
foreach (Product prod in cartParam.Products)
{
total += prod.Price;
}
return total;
}
}

注意this关键字把TotalPrices标记为扩展方法,第一个参数告诉.NET扩展的是ShoppingCart类,可以通过cartParam参数来引用ShoppingCart类的实例。

在HomeController中使用扩展方法。

public ViewResult UseExtension()
{
// 创建并填充ShoppingCart
ShoppingCart cart = new ShoppingCart
{
Products = new List<Product>
{
new Product { Name = "Kayak", Price = 275M },
new Product { Name = "Lifejacket", Price = 48.95M },
new Product { Name = "Soccer ball", Price = 19.50M },
new Product { Name = "Corner flag", Price = 34.95M }
}
};

// 求取购物车中的产品总价
decimal cartTotal = cart.TotalPrices();

return View("Result", (object)string.Format("Total: {0:c}", cartTotal));
}

枚举器和可枚举类型

枚举器(enumerator):枚举器对象可以依次返回请求的数组中的元素。枚举器知道项的次序并跟踪它在序列中的位置。枚举器实现了IEnumerator接口

IEnumerator接口:包含3个函数成员,Current(只读属性)MoveNextReset实现了IEnumerator接口的类称为枚举器

可枚举类型:是指实现了IEnumerable接口的类

IEnumerable接口:只有一个成员,GetEnumerator方法,它返回对象的枚举器。

IEnumerable<T>接口:GetEnumerator方法返回实现IEnumerator<T>的枚举器类的实例。

IEnumerator<T>接口:实现该接口的类实现了Current属性,它返回实际类型的实例,而不是object引用。

对接口运用扩展方法

可以创建运用于一个接口的扩展方法,并允许在实现这个接口的所有类上调用这个扩展方法

在ShoppingCart类实现IEnumerable<Product>接口。

public class ShoppingCart : IEnumerable<Product>
{
public List<Product> Products { get; set; }

public IEnumerator<Product> GetEnumerator()
{
return Products.GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}

修改扩展方法,使之应用于接口上。

public static decimal TotalPrices(this IEnumerable<Product> productEnum)	// 不仅包括ShoppingCart实例,还可以是Products数组
{
decimal total = 0;
foreach (Product prod in productEnum)
{
total += prod.Price;
}
return total;
}

在HomeController中运用扩展方法。

public ViewResult UseExtensionEnumerable()
{
IEnumerable<Product> products = new ShoppingCart
{
Products = new List<Product>
{
new Product { Name = "Kayak", Price = 275M },
new Product { Name = "Lifejacket", Price = 48.95M },
new Product { Name = "Soccer ball", Price = 19.50M },
new Product { Name = "Corner flag", Price = 34.95M }
}
};

// 创建并填充一个Product对象的数组
Product[] productArray = {
new Product { Name = "Kayak", Price = 275M },
new Product { Name = "Lifejacket", Price = 48.95M },
new Product { Name = "Soccer ball", Price = 19.50M },
new Product { Name = "Corner flag", Price = 34.95M }
};

// 获取购物车中产品的总价
decimal cartTotal = products.TotalPrices();
decimal arrayTotal = productArray.TotalPrices();

return View("Result", (object)string.Format("Cart Total: {0}, Array Total: {1}", cartTotal, arrayTotal));
}

创建过滤扩展方法

  1. 过滤扩展方法,可以对对象集合进行过滤
  2. IEnumerable<T>进行操作,返回一个IEnumerable<T>结果。
  3. yield关键字把选择条件运用于数据源中的数据项,以产生一个结果集。

在MyExtensionMethods类中增加过滤扩展方法。

public static IEnumerable<Product> FilterByCategory(this IEnumerable<Product> productEnum, string categoryParam)
{
foreach (Product prod in productEnum)
{
if (prod.Category == categoryParam)
{
yield return prod;
}
}
}

在HomeController中使用过滤扩展方法。

public ViewResult UseFilterExtensionMethod()
{
IEnumerable<Product> products = new ShoppingCart
{
Products = new List<Product>
{
new Product { Name = "Kayak", Category = "Watersports", Price = 275M },
new Product { Name = "Lifejacket", Category = "Watersports", Price = 48.95M },
new Product { Name = "Soccer ball", Category = "Soccer", Price = 19.50M },
new Product { Name = "Corner flag", Category = "Soccer", Price = 34.95M }
}
};

decimal total = 0;
foreach (Product prod in products.FilterByCategory("Soccer")) // 使用过滤扩展方法
{
total += prod.Price;
}
return View("Result", (object)string.Format("Total: {0}", total));
}

lambda表达式

在MyExtensionMethods类中添加Filter扩展方法,以一个Func委托作为参数。

public static IEnumerable<Product> Filter(this IEnumerable<Product> productEnum, Func<Product, bool> selectParam)
{
foreach (Product prod in productEnum)
{
if (selectParam(prod))
{
yield return prod;
}
}
}

在HomeController中使用带有Func的过滤扩展方法。这样就可以用委托中指定的任何条件来过滤Product对象。

public ViewResult UseFilterExtensionMethod()
{
IEnumerable<Product> products = new ShoppingCart
{
Products = new List<Product>
{
new Product { Name = "Kayak", Category = "Watersports", Price = 275M },
new Product { Name = "Lifejacket", Category = "Watersports", Price = 48.95M },
new Product { Name = "Soccer ball", Category = "Soccer", Price = 19.50M },
new Product { Name = "Corner flag", Category = "Soccer", Price = 34.95M }
}
};

Func<Product, bool> categoryFilter = delegate (Product prod) // 匿名函数赋值给Func委托
{
return prod.Category == "Soccer";
};

decimal total = 0;
foreach (Product prod in products.Filter(categoryFilter)) // Func委托作为参数传入Filter方法
{
total += prod.Price;
}
return View("Result", (object)string.Format("Total: {0}", total));
}

使用Lambda表达式代替委托定义(匿名函数)。

Func<Product, bool> categoryFilter = prod => prod.Category == "Soccer";	// lambda表达式替代匿名函数,赋值给Func委托
...
foreach (Product prod in products.Filter(categoryFilter))
{
total += prod.Price;
}

也可以去掉Func,形成更紧凑的语法。

foreach (Product prod in products.Filter(prod => prod.Category == "Soccer" || prod.Price > 20))
{
total += prod.Price;
}

自动类型接口

使用推断类型隐式类型

var myVariable = new Product
{
Name = "Kayak",
Category = "Watersports",
Price = 275M
};

string name = myVariable.Name;

匿名类型

通过结合对象初始化器类型推断,可以创建简单的数据存储对象,而不需要定义相应的类

在HomeController添加CreateAnonArray动作方法,创建匿名类型对象数组。

public ViewResult CreateAnonArray()
{
var oddsAndEnds = new[]
{
new { Name = "MVC", Category = "Pattern" },
new { Name = "Hat", Category = "Clothing" },
new { Name = "Apple", Category = "Fruit" }
};

StringBuilder result = new StringBuilder();
foreach (var item in oddsAndEnds)
{
result.Append(item.Name).Append(" ");
}

return View("Result", (object)result.ToString());
}

语言集成查询(LINQ)

假设有一个Product对象的集合,需要找出其中的3个最高价,并将它们传递给View方法。

在HomeController添加FindProducts动作方法。

public ViewResult FindProducts()
{
Product[] products = {
new Product { Name = "Kayak", Category = "Watersports", Price = 275M },
new Product { Name = "Lifejacket", Category = "Watersports", Price = 48.95M },
new Product { Name = "Soccer ball", Category = "Soccer", Price = 19.50M },
new Product { Name = "Corner flag", Category = "Soccer", Price = 34.95M }
};

// 定义一个数组以保存结果
Product[] foundProducts = new Product[3];
// 对数组的内容进行排序
Array.Sort(products, (item1, item2) =>
{
return Comparer<decimal>.Default.Compare(item2.Price, item1.Price);
});
// 获取数组的前三项作为结果
Array.Copy(products, foundProducts, 3);
// 创建结果
StringBuilder result = new StringBuilder();
foreach (Product p in foundProducts)
{
result.AppendFormat("Price: {0} ", p.Price);
}

return View("Result", (object)result.ToString());
}

使用LINQ的查询语法。它对Product对象进行降序排序,并用select关键字返回一个匿名类型,而且仅包含Name和Price属性。

public ViewResult FindProducts()
{
Product[] products = {
new Product { Name = "Kayak", Category = "Watersports", Price = 275M },
new Product { Name = "Lifejacket", Category = "Watersports", Price = 48.95M },
new Product { Name = "Soccer ball", Category = "Soccer", Price = 19.50M },
new Product { Name = "Corner flag", Category = "Soccer", Price = 34.95M }
};

// LINQ查询语法
var foundProducts = from match in products
orderby match.Price descending
select new { match.Name, match.Price };

// 创建结果
int count = 0;
StringBuilder result = new StringBuilder();
foreach (var p in foundProducts)
{
result.AppendFormat("Price: {0} ", p.Price);
if (++count == 3)
{
break;
}
}

return View("Result", (object)result.ToString());
}

也可以使用LINQ的点符号语法。这种语法基于扩展方法,它运用一个IEnumerable<T>,并返回一个IEnumerable<T>,以方法链形式调用,也称为链式语法

public ViewResult FindProducts()
{
Product[] products = {
new Product { Name = "Kayak", Category = "Watersports", Price = 275M },
new Product { Name = "Lifejacket", Category = "Watersports", Price = 48.95M },
new Product { Name = "Soccer ball", Category = "Soccer", Price = 19.50M },
new Product { Name = "Corner flag", Category = "Soccer", Price = 34.95M }
};

// LINQ点符号语法
var foundProducts = products.OrderByDescending(e => e.Price).Take(3).Select(e => new { e.Name, e.Price });

StringBuilder result = new StringBuilder();
foreach (var p in foundProducts)
{
result.AppendFormat("Price: {0} ", p.Price);
}

return View("Result", (object)result.ToString());
}

OrderByDescending方法重组了数据源中的数目,Take方法返回结果最前面的指定数目的条目,Select指定想要的结果。

一些有用的LINQ扩展方法

扩展方法 描述 延迟
All 如果源数据中的所有条目都与谓词匹配,则返回true
Any 如果源数据中至少有一个条目与谓词匹配,则返回true
Contains 如果数据源含有指定的条目或值,则返回true
Count 返回数据源中的条目数
First 返回数据源的第一个条目
FirstOrDefault 返回数据源的第一个条目,或无条目时,返回默认值
Last 返回数据源的最后一个条目
LastOrDefault 返回数据源的最后条目,或无条目时,返回默认值
Max
Min
返回由lambda表达式表示的最大值或最小值
OrderBy
OrderByDescending
基于lambda表达式返回的值对源数据进行排序
Reverse 反转数据源中数据项的顺序
Select 设计一个查询结果
SelectMany 把每个数据项投射到一个条目序列之中,然后把所有这些结果序列连接成一个序列
Single 返回数据源的第一个条目,或者有多个匹配时抛出一个异常
SingleOrDefault 返回数据源的第一个条目,或者无条目时,返回默认值;有多个匹配条目时,抛出一个异常
Skip
SkipWhile
跳过指定数目的元素,或者当谓词匹配时跳过
Sum 对谓词选定的值求和
Take
TakeWhile
从数据源的开始处选择指定数目的元素,或当谓词匹配时选择条目
ToArray
ToDictionary
ToList
把数据源转换成数组或其他集合类型
Where 过滤掉源数据中与谓词不匹配的条目

LINQ延迟查询

如果一条查询只含有延迟方法,那么,直到结果进行枚举后,才会执行该查询

...
var foundProducts = products.OrderByDescending(e => e.Price).Take(3).Select(e => new { e.Name, e.Price });

products[2] = new Product { Name = "Stadium", Price = 79600M };
...
// Price: 79600 Price: 275 Price: 48.95

相比之下,使用任何非延迟的扩展方法,都会使LINQ查询立即执行

...
var results = products.Sum(e => e.Price);
products[2] = new Product { Name = "Stadium", Price = 79600M };

return View("Result", (object)string.Format("Sum: {0:c}", results));
...
// Sum: ¥378.40

Async方法

异步方法在后台执行和工作,并在工作完成时通知你,这允许在后台工作执行期间,你的代码能够处理其他事务。

异步方法是消除代码性能瓶颈的一个重要工具,并让应用程序能够利用多处理器和处理器内核并行地进行工作。

在Models文件夹添加MyAsyncMethods类,实现一个简单的异步方法。

public class MyAsyncMethods
{
public static Task<long?> GetPageLength()
{
HttpClient client = new HttpClient();
var httpTask = client.GetAsync("http://apress.com");

// 在等待HTTP请求完成期间,此处可以做其他事情(后台任务)

return httpTask.ContinueWith((Task<HttpResponseMessage> antecedent) =>
{
return antecedent.Result.Content.Headers.ContentLength;
});
}
}

注意

  1. 当调用GetAsync方法时,所得到的是一个Task<HttpResponseMessage>对象。
  2. 该请求将在后台执行,请求的结果是一个HttpResponseMessage对象。
  3. return关键字用了两次。第一次是返回一个Task<HttpResponseMessage>对象,第二次返回一个long?
  4. ContinueWith方法指定了后台任务完成时希望发生的事情。

运用async和await关键字

asyncawait关键字可以简化GetAsync异步方法的使用。

public async static Task<long?> GetPageLength()
{
HttpClient client = new HttpClient();
var httpMessage = await client.GetAsync("http://apress.com"); // httpMessage为HttpResponseMessage对象

// 在等待HTTP请求完成期间,此处可以做其他事情(后台任务)

return httpMessage.Content.Headers.ContentLength;
}

await:告诉C#编译器,希望等待GetAsync方法所返回的Task结果,并继续执行同一方法中的其他语句。

async:使用await关键字时,必须在方法签名添加async关键字。