模型绑定的安全性
我们已经讨论了2中不同的方法使用ASP.NET MVC内置的模型绑定功能。第一个方法是使用UpdateModel() 方法更新一个已存在的模型对象的属性;第二个方法是传递模型对象,作为action方法的参数。这两项技术都非常强大和有用。
功能虽然强大,但也需要考虑用户输入的安全性,包括绑定对象到表单的输入。一定要通过HTML 编码所有用户输入值,避免HTML和Javascript注入攻击(备注:我们的范例应用程序使用LINQ to SQL,可以自动编码所有参数,避免这些类型的攻击)。不能仅仅依赖于客户端的验证,总是要采用服务端的验证,阻止攻击者试图传入无效的数据。
默认情况下,UpdateModel() 方法试图根据匹配的表单参数值,更新所有的属性。同样地,作为参数传递给action 方法的模型对象,基于表单参数设置全部模型对象的属性。
基于用途来锁定绑定
你可以基于用途,提供显式的可更新的属性列表,锁定绑定策略。这可以通过传递一个额外的字符串数组参数给UpdateModel() 方法:
string[] allowedProperties = new[]{ "Title", "Description",
"ContactPhone", "Address",
"EventDate", "Latitude",
"Longitude"};
UpdateModel(dinner, allowedProperties);
作为传递给Action方法的参数 – 模型对象也支持[Bind]属性,允许指定include list或者允许的属性列表,如下所示:
// POST: /Dinners/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create( [Bind(Include="Title,Address")] Dinner dinner ) {
...
}
基于类型来锁定绑定
你可以基于类型来锁定绑定规则。这样你一旦制定绑定规则,就可以在所有Controllers 和 Action方法中应用了,包括UpdateModel方法和Action方法的参数。
通过添加[Bind]属性在类型上,或者在应用程序中的Global.asax文件中(如果类型不是我们自己定义的情况下,非常有用),来定制类型绑定规则。戒指使用Bind属性的Include和Exclude属性来控制类或接口中哪些属性是可绑定的。
我们的NerdDinner应用程序将使用这一技术,添加[Bind]属性,显示可绑定的属性:
[Bind(Include="Title,Description,EventDate,Address,Country,ContactPhone,Latitude,Longi
tude")]
public partial class Dinner {
}
注意到RSVPs集合、DinnerID和HostedBy 属性都不允许通过绑定来设置。基于安全的原因,这些属性只能在action方法中使用显式的代码进行设置。
CRUD封装
ASP.NET MVC包括一些内置的功能,帮助实现表单提交的场景。我们在DinnerRepository类中使用了大量这些功能提供CRUD的支持。
我们使用以Model模型为中心的方法来实现我们的范例程序。这意味着所有的验证和业务规则都在模型层(Model Layer)中定义 – 而不是在Controllers控制器和View视图中。Controller类和View视图模板都不必了解模型类实现的业务规则。
这样,可以保持我们的应用程序架构简洁和易于测试。在将来,我们可以添加额外的业务规则到模型层(Model Layer)中,而不必更改Controller类和View视图,就可以使用了。这一特性提供了我们很多的灵活性来改进我们的应用程序。
DinnersController控制器实现了Dinner列表和显示详细信息,还有创建、编辑和删除操作等等。该类的完整代码如下:
public class DinnersController : Controller
{
DinnerRepository dinnerRepository = new DinnerRepository();
//
// GET: /Dinners/
public ActionResult Index()
{
var dinners = dinnerRepository.FindUpcomingDinners().ToList();
return View("Index", dinners);
}
//
// GET: /Dinners/Details/2
public ActionResult Details(int id)
{
Dinner dinner = dinnerRepository.GetDinner(id);
if (dinner == null)
return View("NotFound");
else
return View("Details", dinner);
}
//
// GET: /Dinners/Edit/2
public ActionResult Edit(int id)
{
Dinner dinner = dinnerRepository.GetDinner(id);
return View(dinner);
}
//
// POST: /Dinners/Edit/2
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formValues)
{
Dinner dinner = dinnerRepository.GetDinner(id);
try
{
UpdateModel(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new { id = dinner.DinnerID });
}
catch
{
ModelState.AddRuleViolations(dinner.GetRuleViolations());
return View(dinner);
}
}
//
// GET: /Dinners/Create
public ActionResult Create()
{
Dinner dinner = new Dinner()
{
EventDate = DateTime.Now.AddDays(7)
};
return View(dinner);
}
//
// POST: /Dinners/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Dinner dinner)
{
if (ModelState.IsValid)
{
try
{
dinnerRepository.Add(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new { id = dinner.DinnerID });
}
catch
{
ModelState.AddRuleViolations(dinner.GetRuleViolations());
}
}
return View(dinner);
}
//
// HTTP GET: /Dinners/Delete/1
public ActionResult Delete(int id)
{
Dinner dinner = dinnerRepository.GetDinner(id);
if (dinner == null)
return View("NotFound");
else
return View(dinner);
}
//
// HTTP POST: /Dinners/Delete/2
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Delete(int id, string confirmButton)
{
Dinner dinner = dinnerRepository.GetDinner(id);
if (dinner == null)
return View("NotFound");
dinnerRepository.Delete(dinner);
dinnerRepository.Save();
return View("Deleted");
}
}
来自西北的狼!