[译]实用的ASP.NET MVC(3)技巧
By robot-v1.0
本文链接 https://www.kyfws.com/best-practices/practical-asp-net-mvc-tips-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 61 分钟阅读 - 30230 个词 阅读量 0[译]实用的ASP.NET MVC(3)技巧
原文地址:https://www.codeproject.com/Articles/419054/Practical-ASP-NET-MVC-tips
原文作者:Florian Rappl
译文由本站 robot-v1.0 翻译
前言
A list of tips, involving Entity Framework, Extension Methods, programming patterns and others, that has been built up from recent ASP.NET MVC 3 programming journeys. 从最近的ASP.NET MVC 3编程历程中获得的技巧列表,涉及实体框架,扩展方法,编程模式等.
目录(Table of Contents)
- 介绍(Introduction)
- 背景(Background)
- 提示1:分开的实体和模型(Tip 1: Seperate entities and models)
- 提示2:重新验证更新的内容(Tip 2: Revalidate updated content)
- 提示3:保护您的网站(Tip 3: Securing your website)
- 提示4:始终使用DAL(Tip 4: Always work with a DAL)
- 提示5:设置适当的IoC依赖关系解析器(Tip 5: Setting up a proper IoC dependency resolver)
- 提示6:在MVC 3中使用MVC 4捆绑功能(Tip 6: Use the MVC 4 Bundling feature in MVC 3)
- 技巧7:将MySQL用作数据库(以及成员资格)(Tip 7: Using MySQL as database (together with memberships))
- 提示8:检测jQuery AJAX请求(Tip 8: Detect jQuery AJAX requests)
- 提示9:预编译视图以最大程度地减少错误(Tip 9: Precompile the views to minimize errors)
- 提示10:使用TagBuilder(Tip 10: Using the TagBuilder)
- 提示11:包括非常有用的扩展方法(Tip 11: Include very useful extension methods)
- 提示12:您永远不会忘记的属性(Tip 12: Attributes you should never forget)
- 提示13:谨慎使用外键(Tip 13: Be cautious with ForeignKeys)
- 提示14:使用本地化-机会与陷阱(Tip 14: Working with localization - chances and pitfalls)
- 技巧15:约束以实现更好的路由(Tip 15: Constraints for better routing)
- 提示16:使用名称作为操作参数时要小心(Tip 16: Be careful when using names for action parameters)
- 提示17:向视图添加名称空间(Tip 17: Adding namespaces to views)
- 技巧18:内部动作(Tip 18: Internal actions)
- 技巧19:通过缓存提高性能(Tip 19: Performance boost through caching)
- 技巧20:重写控制器的方法(Tip 20: Override methods of your controllers)
- 提示21:您自己的会员资格提供者(Tip 21: Your own membership provider)
- 提示22:如何使HTTPS强制(Tip 22: How to make HTTPS mandatory)
- 提示23:将T4MVC用于强类型的助手(Tip 23: Use T4MVC for strongly typed helpers)
- 使用代码(Using the code)
- 兴趣点(Points of interest)
介绍(Introduction)
从看到ASP.NET MVC的那一刻起,我就知道这不仅有用,而且功能强大.但是,强大的能力伴随着巨大的责任(在技术方面:更高的要求),从而导致陡峭的学习曲线.本文不专注于专业的ASP.NET MVC开发人员(我想他们(From the moment I saw ASP.NET MVC I knew that this is not only useful but highly powerful. However, with great power comes great responsibility (and in technology: great requirements), resulting in a steep learning curve. This article is not focused on professional ASP.NET MVC developers (I suppose they)**做(do)**知道我将在本文中写的所有内容),但专门面向刚刚开始使用ASP.NET MVC(3)进行开发或计划进行开发的人员.(know everything I will write in this article), but is dedicated to people who just started developing in ASP.NET MVC (3) or plan to do so.) 大多数技巧和源代码都将重点放在MVC内核上,而其他技巧和源代码则将重点放在可以结合使用的技术上,例如(Most tips and source codes will be focused on the MVC core while others are focusing on techniques that could be used in combination like the)实体框架(Entity Framework)或者(or the)jQuery的(jQuery)验证助手.本文还将包含IoC等更专业的主题,(validation helper. This article will also contain more specialized topics like IoC with the)统一(Unity)依赖解析器或使用MySQL数据库而不是Microsoft SQL数据库.即使某些提示对某些人来说可能无关紧要,而其他提示可能为其他人所了解,但我认为所有这些提示都值得写下.(dependency resolver or working with MySQL databases instead of Microsoft SQL ones. Even though some tips might be irrelevant for some people and other tips might be known by other people, I considered them all worth to be written down.) 本文不会尝试教您MVC,HTML,JavaScript或CSS.在本文中,我将为您提供一系列(未连接的)技巧,这些技巧在处理ASP.NET MVC时可能会有所帮助.这些技巧中的一些技巧可能会随着时间的流逝而过时,但是,每条技巧都将包含一堂课(或者当我被抓住时确实为我提供了一条!).(This article will not try to teach you MVC, HTML, JavaScript or CSS. In this article I will give you a series of (not-connected) tips, which could be helpful while dealing with ASP.NET MVC. Some of those tips might become obsolete with time, however, every tip will contain a lesson (or did contain one for me when I’ve been caught!).)
背景(Background)
ASP.NET MVC可能是构建动态网页的最佳方法.现在,这是一个有力的句子,会有人强烈反对该意见.是什么使ASP.NET MVC如此出色?一方面,您可以使用C#(或VB.NET-但我强烈喜欢C#)来编写它. C#是一种静态类型语言,最初是Java克隆的一种,但如今包含了许多最新技术.即使语言是静态的,您也可以访问强大的功能,例如反射,动态变量和匿名对象.甚至匿名方法(所谓的lambda表达式)也是可能的.总而言之,该语言非常快(对于托管语言)并且是JIT编译的.即使无法达到与C一样的性能水平,您也可以轻松达到比任何脚本语言都更好的性能.(ASP.NET MVC is probably the best approach for building dynamic webpages. Now this is kind of a strong sentence and there will be people, who will strongly disagree with that opinion. What makes ASP.NET MVC so good? On the one hand you can write it using C# (or VB.NET - but I strongly prefer C#). C# is a static type language which started as a kind of Java clone, but contains a lot more state of the art features nowadays. Even though the language is static you can access powerful features like reflection, dynamic variables and anonymous objects. Even anonymous methods, so called lambda expressions, are possible. All in all the language is very fast (for a managed language) and is JIT-compiled. Even though it is not possible to reach the level of performance as with C, you can easily reach a far better performance than with any scripted language.) 现在有人可能会说性能不是全部,或者如果不是全部,那么您始终可以交叉编译为二进制文件.这是ASP.NET MVC的第二个参数:由于它是建立在.NET/Visual Studio堆栈上的,因此您可以访问一个非常好的调试器和非常好的工具来完成工作.多年来,这一直是一种反驳,因为ASP.NET(现在没有MVC)对大多数人来说是WebForms的同义词. WebForms堆栈确实功能强大且深入,但缺点是会降低性能并使您(程序员)失去控制.您只是单击了一下,对其进行了编程等,但是最终结果(即标记)与您输入的内容确实不同.找出后端发生的事情也是一项繁琐的任务.(Now one might argue that performance is either not everything, or if it is, then you can always cross-compile to a binary. Here comes the second argument for ASP.NET MVC: since it is building up on the .NET / Visual Studio stack you can access a really good debugger and very good tools to get the job done. Now for years this has also been a kind of counter argument, since ASP.NET (now without the MVC) was a synonym to WebForms for most people. The WebForms stack is really powerful and deep, but had the disadvantage to eat performance and put you (the programmer) out of control. You just clicked around, programmed a bit etc., but the end-result (i.e. the markup) was really different from your input. And finding out what was going on in the backend was also quite a tedious task.) 这就是ASP.NET MVC的用武之地.MVC概念非常古老,对于构建图形用户界面非常有用.如今,MVC模式已被使用Ruby on Rails构建Web应用程序所接受.由于Ruby是一种具有非C语言语法的动态语言,因此我们确实需要将ASP.NET MVC作为反例.那我们到底能得到什么?(Here is where ASP.NET MVC comes in. The MVC concept is quite old and really useful for building graphical user interfaces. Nowadays the MVC pattern has gained a lot of acceptance for building web applications with Ruby on Rails. Since Ruby is a dynamic language with a non-C-like syntax we really needed to have ASP.NET MVC as a counter part. So what do we get in the end?)
- 全面控制我们的输出(Total control of our output)
- 面向对象的设计(Object-oriented design)
- 以强大的ASP.NET核心堆栈为基础(Building upon the powerful ASP.NET core stack)
- 使用Razor视图引擎书写视图的非常现代而优雅的风格(A very modern and elegant style of writing views with the Razor view engine)
- 访问其他.NET库和C/C ++库(Access to other .NET libraries, and C/C++ libraries)
- (JIT)编译源可提高性能(A (JIT) compiled source for performance boost)
- 出色的调试支持(Excellent debugging support)
- 具有动态类型和其他现代功能的静态语言(A static language with dynamic types and other modern features) 如果您还没有尝试过ASP.NET MVC,但是您确实知道C#或.NET-Framework(甚至ASP.NET),那么应该立即尝试一下!如果您这样做了,那么这篇文章是正确的选择,现在就知道了发生了什么,并且如果您想了解更多以防万一,请提供一个线索.(If you haven’t tried out ASP.NET MVC, but you do know C# or the .NET-Framework (or even ASP.NET), then you should give it a shot right away! This article is the right choice if you did this, have a clue right now whats going on and if you want to learn more just in case.)
提示1:分开的实体和模型(Tip 1: Separate entities and models)
首先,这第一条技巧似乎微不足道(或没有),但是我需要很长时间才能理解这一点.在这种情况下,当我们谈论实体时,是指数据库表中的一行.大多数初学者/中级MVC用户会做错的是,他们混合使用模型和实体.这两件事应该始终分开,并且每个视图都应仅获取最少的所需数据.让我们考虑以下情况:(This first tip seems trivial (or not) at first, but I needed a long time to understand this. When we talk about entities in this case we mean a row from a database table. What most beginners / intermediate MVC users will do wrong is that they mix models and entities. Those two things should always be separated and every view should just get the minimum required data. Let’s consider the case where I do have the following view:)
@model TestModel
<p>Hallo @Model.Name!</p>
<p>You are back online!</p>
此(强类型)视图似乎很琐碎.在这里,我们只需要属性(This (strongly typed) view seems trivial. Here we just need the property) Name
给定实例的(of the given instance of) TestModel
.所以(. So) TestModel
可能看起来像:(might look like:)
public class TestModel
{
public string Name { get; set; }
}
也许(Maybe) TestModel
具有更多属性,在这种情况下未设置.只要这没关系(has more properties, which aren’t set in this case. This does not matter as long as) TestModel
不是像这样的数据库实体:(is not a database entity like:)
public class TestEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string FirstName { get; set; }
public string Street { get; set; }
public string City { get; set; }
public DateTime Birthday { get; set}
}
在这种情况下,进行查询以请求(并填充)所有这些属性似乎很麻烦.但是,即使性能问题可能是一个很好的论据,但还有一个更好的选择:仅使用实际模型来提供视图.考虑以下情况:我们认为需要另外一个属性,但又不直接存在于实体中(Making a query to request (and fill) all those properties seems like to much work in this case. But even though the performance issue might be a good argument, there is an even better one for using only real models to feed the view. Consider the case that we want one more property in our view, but one that is not directly in the entity) TestEntity
.可能与主键有关(. It could be one that is related to the primary key) Id
.有趣的效果是,由于我们无法扩展实体(我们与表的布局有关,因此它会保持原样),因此我们需要更改参数.因此,这里的问题是可扩展性.在为每个视图使用特定(或更通用)的模型时,我们获得了可扩展性,并且可以减少实体框架的工作.(. The interesting effect is that we would need to change the argument, since we cannot extend the entity (this one is related to the table layout - which will of course stay as it was). So the problem here is the extensibility. While using a specific (or more general) model for each view, we gain extensibility and can reduce work of the entity framework.)
可以找到有关模型/实体对象讨论的更多信息(More information regarding the models / entity objects discussion can be found) 在Stackoverflow(at Stackoverflow) .(.)
提示2:重新验证更新的内容(Tip 2: Revalidate updated content)
MVC的一个很酷的功能是验证. Web设计师有一个梦想:在服务器和客户端上进行验证.如果有人具有广泛的HTML5功能(和/或启用了JavaScript),则在客户端上执行无需任何页面请求的平滑验证.这样可以减少流量,加快页面执行速度,并带来更多好处.但是,某些用户可能不具有此类功能,或者将其关闭以在服务器端验证中搜索可能的漏洞利用.因此,在服务器上必须始终至少具有与客户机上相同的验证规则.使用ASP.NET MVC,每个用户都可以获得jQuery的工作副本和一些强大的插件.这些插件中的一个负责验证,而另一个则负责执行前一个插件.这意味着插件会在加载的文档中扫描特殊标签和属性.搜索之后,适当的元素将随所需的事件处理程序一起提供.(A cool feature of MVC is validation. Web designers had one dream: Validate on the server and the client. If somebody has extensive HTML5 features (and/or JavaScript enabled) a smooth validation without any page request is executed on the client. This results in less traffic, faster page execution and more benefits. However, some users might not have such capabilities or will turn them off to search for possible exploits in the server side validation. Therefore one must always have at least the same validation rules on the server as on the client. With ASP.NET MVC every user gets a working copy of jQuery and some powerful plug-ins. One of those plug-ins is responsible for the validation, while another one is responsible for executing the former one unobtrusively. This means that the plugin scans the loaded document for special tags and attributes. After this search the proper elements will be supplied with the required event handlers.) 如果我们要加载和卸载单个页面,则此过程非常有用,但如果我们更新了一部分内容,此过程就不会那么好.更新可以通过AJAX请求或我们自己的JavaScript修改来完成.问题是,我们需要一种在页面加载后为特定区域(更新的区域)设置验证的方法.在这种情况下,我们需要调用验证程序脚本的方法.然后,此方法将对选定元素进行实时验证.以下代码片段说明了用法:(This procedure works great if we want a single page to load and unload, but does not work so great if we update a part of the content. The update could be done with a AJAX request or by our own JavaScript modifications. The problem is that we need a way to set up validation for a certain region (the updated region) after the page has been loaded. In this case we need to call a method of the validator script. This method then hooks up the live validation on the selected element(s). The following code snippet illustrates the usage:)
$.validator.unobtrusive.parse('#Content');
这里的ID元素(Here the element with the ID)内容(Content)将被解析并通过实时验证进行设置.可以找到更多信息(will be parsed and set up with live validation. More information can be found) 在Stackoverflow(at Stackoverflow) .(.)
提示3:保护您的网站(Tip 3: Securing your website)
安全始终是一个大问题.当然,在公开来源时,漏洞利用程序更容易找到.由于网站就是这种情况,因此我们需要格外小心在服务器上接收到的数据.可能的利用来自恶意用户或被劫持的用户.最后一种情况可能是由于跨站点脚本(XSS),诸如病毒之类的恶意程序或用户软件本身的问题所致.无论如何,我们需要做好准备.实际上,通过使用(Security is always a big issue. Of course exploits are easier to find when the source is public. Since this is the case with websites we need to be extra careful with the data we are receiving on your server. Possible exploits come either by a malicious user, or a hijacked user. The last scenario could happen due to cross site scripting (XSS), a malicious program like a virus or a problem with the user’s software itself. In any case we need to be prepared. Most XSS cases can actually be prevented by using the) AntiForgeryToken()
方法.为了在网站上使用它,我们只需要以以下形式插入一个语句:(method. In order to use it on a website we just have to insert one statement in the form:)
@using(Html.BeginForm())
{
@Html.AntiForgeryToken()
<!-- Rest Of Our Form -->
}
该语句将创建一个隐藏的表单元素,该元素具有仅在服务器上已知的随机序列(临时变量).当用户提交表单时,我们仍然需要验证令牌.验证可以通过属性完成,(This statement will create a hidden form element with a random sequence that is only known on the server (a temporary variable). When the user submits the form we still need to validate the token. The validation can be done over an attribute, the) ValidateAntiForgeryToken
属性.让我们看看如何保护动作(attribute. Let’s have a look how to protect the action) ProtectMe
的(of the) Test
控制器:(controller:)
public class TestController : Controller
{
/* ... */
//
// POST: /Test/ProtectMe
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ProtectMe(ProtectMeModel data)
{
if(ModelState.IsValid)
{
/* What to do if valid? */
return RedirectToAction("Protected");
}
/* What to do if invalid ? */
return View(data);
}
}
至关重要的是(It is crucial to call the) Model.IsValid
属性在此处,因为该属性通过添加模型错误声明来表示使用了错误的防伪令牌.因此,即使提交的数据似乎对该模型有效,我们也将提交无效的数据.(property here, since the attribute signals that a wrong anti forgery token has been used by adding a model error statement. Therefore, even if the data submitted seems to be valid for this model, we will have an invalid submission.)
无论如何,跨站点脚本可能是最大的威胁,因此我们不仅应防止任何被劫持的请求,而且还应防止来自服务器的响应确实包含恶意脚本和请求.如果用户生成的内容根本不能包含任何HTML,那么我们基本上是安全的,但这并不总是可能的.如果允许用户输入HTML,我们有两种处理方法:(Cross site scripting is probably the biggest threat anyway, so we should not only prevent any hijacked requests, but we should also prevent that responses from our server do contain malicious scripts and requests. If user generated content cannot contain any HTML at all we are basically safe, however, this is not always possible. If users are allowed to input HTML we have two ways to deal with it:)
- 黑名单列出所有具有恶意潜力的标签和属性(Black list all tags and attributes with malicious potential)
- 白名单列出所有需要且不能包含恶意内容的标签(White list all tags that are needed and cannot contain malicious content)
第一种方法似乎对大多数用户有吸引力(因为他们没有考虑所有可能性).我们将始终首选第二种方法,因为通常不需要授予用户太多标签(他们到底需要做什么?)并且可以更轻松地控制此列表(考虑越来越多的标签,未来的规格可能会更多).例如HTML标准).(The first method seems appealing to most users (since they do not think about all the possibilities). We will always prefer the second method since it is usually not required to grant users too many tags (what do they need to do anyway?) and since this list can be controlled more easily (think about a growing number of tags with future specifications of the HTML standard for instance).)
不幸的是,MVC 3中没有现成的消毒程序库.幸运的是,其他人花了很多心血来编写一个导致(Unfortunately a sanatizer library has not been included in MVC 3 out of the box. Luckily somebody else put a lot of effort into writing one resulting in the)反XSS(AntiXSS)图书馆.要使用标准(和安全)标签列表转换(HTML)输入,我们需要调用的是(library. To convert (HTML) input with using a list of standard (and safe) tags, all we need to call is)
Sanatizer.GetSafeHtmlFragment()
.强烈建议使用此类库,因为通常会定期对其进行更新,从而形成最新的消毒器.该库可以在以下位置下载(. The usage of such libraries is strongly recommended since they will usually be updated regularly, resulting in a state of the art sanatizer. The library can be downloaded at) 微软(Microsoft) .(.) 本部分要提到的最后一点是需要适当的授权.授权非常重要,应被视为每个网站的强制性要素.我们所有的操作都必须公开(在代码中),因此可以通过HTTP请求进行访问.这意味着每个用户(无论是好是坏)都可以访问这些操作.一个好的开始是使用(The last point to mention in this section is the need for proper authorization. Authorization is quite important and should be regarded as a mandatory element for every website. All our actions need to be public (in the code) and are therefore accessible via a HTTP request. This means that every, the good, the bad and the ugly, user can access those actions. A good start is the usage of the)[Authorize]
属性.它可以用于控制器和动作.如果我们将其用于控制器,例如:(attribute. It can be used for controllers and actions. If we use it for a controller, like:)
[Authorize]
public class TestController : Controller
{
/* ... */
}
这样可以保护(This will protect)任何(ANY)由未登录用户访问内部的操作.如果我们确实希望未经授权的用户访问任何操作,则需要放置(action inside from access by users which are not logged in. If we do want unauthorized users to access any of the actions we need to place the) [Authorize]
属性高于其他操作,例如:(attribute above the other actions like:)
public class TestController : Controller
{
/* ... */
[Authorize]
public ActionResult ShouldBeProtected()
{
/* ... */
}
[Authorize]
public ActionResult AlsoOnlyForLoggedInUsers()
{
/* ... */
}
public ActionResult AccessibleByEveryone()
{
/* ... */
}
}
甚至我们甚至需要区分那些授权用户.基本上,我们可以通过使用他们的用户名或更一般的角色来做到.每个用户都有一个分配的用户名,外加零个或多个角色.一个非常重要的角色可以称为(Maybe we need even to distinguish between those authorized users. Basically we can do by using their user names or (more general) their roles. Every user has one user name assigned, plus zero or more roles. One very important role could be called)管理员(Administrators).如果我们只希望某个角色的用户访问特定的操作(. If we want a certain action to be accessed only by users, who are in the role)管理员(Administrators),我们需要具有以下属性:(, we need to have the following attribute:)
public class TestController : Controller
{
/* ... */
[Authorize(Role = "Administrators")]
public ActionResult ShouldBeProtected()
{
/* ... */
}
}
可以输入更多角色或混合角色和用户名.但是,通常使用固定的用户名是一种不灵活的编码技术,迟早会导致问题.(It is possible to enter more roles or mix roles and user names. However, usually using a fixed user name is an inflexible coding technique, which will result in problems sooner or later.) Tomas Jansson进一步走了一步,并颠倒了确保Web应用程序安全的方向.因此,一切都在一开始就被锁定了,我们必须显式解锁访问权限(对特定组或所有用户).当然,这是防止意外打开门的好方法.托马斯的文章叫做(Tomas Jansson goes even one step further and reverses the direction of securing our web application. Therefore everything is locked in the beginning and we have to explicitly unlock the access (to specific groups or all users). This is certainly a good way to prevent accidently left open doors. Tomas’s article is called) 保护您的ASP.NET MVC3应用程序(Securing your ASP.NET MVC3 application) .(.)
提示4:始终使用DAL(Tip 4: Always work with a DAL)
数据访问层是提供对存储在某种持久性存储中的数据(例如实体关系数据库)进行简化访问的层.这个定义已经向我们表明,DAL基本上是Web应用程序和数据库之间的另一种抽象.我们已经使用实体框架简化了对数据库的访问.这种方法为我们提供了使用LINQ编写(编译器检查)查询的优势.与从数据库中获取纯字符串/对象相比,另一个优势是获得C#对象中的数据.(A data access layer is a layer which provides simplified access to data stored in persistent storage of some kind, such as an entity-relational database. This definition shows us already that a DAL is basically another abstraction between our web application and the database. Our access to the database should already be simplified by using the Entity Framework. This method gives us the advantage of writing (compiler checked) queries with LINQ. Another advantage is to obtain the data in C# objects compared to pure string / objects from the database.) 总而言之,直接使用数据库就像直接在烹饪台上沸腾水,而不是在其间使用烹饪容器作为层.后者提供了多个优点,例如更干净,更可靠的方法来实现相同的目标.现在,为了拥有一个真正的DAL,我们不仅需要实体框架.实际上,在上面的类比中,实体框架更像是电炉灶(而火将直接使用SQL).那么我们的烹饪容器在现实中是什么样子(或者在我们的虚拟编程世界中):(All in all using the database directly is like boiling water directly on the cooking top instead of using a cooking vessel as a layer in between. The latter provides several advantages such as a cleaner, more robust way to achieve the same goal. Now in order to have a real DAL we need more than just the Entity Framework. Actually the Entity Framework would be more like an electric cooking top in the analogy above (while fire would be using SQL directly). So how does our cooking vessel look like in reality (or let’s say in our virtual programming world):)
- 需要访问实体框架,即数据库实体.(Needs access to the Entity Framework, i.e. the database entities.)
- 仅提供(已抽象)的方法,即,它不提供完整的表或直接查询,但已提供预制查询.(Provides only the (already abstracted) methods, i.e. it does not provide complete tables or direct queries, but already premade queries.)
- 返回模型而不是实体.(Returns models and not entities.) 我们可以使用所谓的抽象框架(We could abstract the entity framework using the so called)储存库模式(repository pattern).古特(Scott Gu)在(. A very basic example is given by Scott Gu in) Nerddinner书Pt 3(the Nerddinner Book Pt 3) .但是首先,让我们看看如何使用实体框架来建立数据库:(. But first, let’s see how we can set up our database with the Entity Framework:)
public class MyDatabase : DbContext
{
public DbSet<EntityType> TableName { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<IncludeMetadataConvention>();
}
}
我们需要做的就是创建一个继承自(All we need to do is creating a class that inherits from) DbContext
.新类的名称必须等于(. The name of the new class must be equal to the name of the connection in the)web.config(web.config)(稍后会详细介绍).这里我们只有一张表((more on that later). Here we just have one table of types) EntityType
.表名将是(. The table name would then be) TableName
.通常,约定是表名是实体类名的复数形式.(. Usually the convention is that the table name is the plural of the entity’s class name.)
另一个功能是元数据约定.如果数据库表不可用并且用户权限足够,则实体框架也可以构造数据库.为了记住上一个数据库构建期间的状态,建立了一个特殊的表.该表将包含元数据.由于这是用于测试/非生产系统的内容,因此我们通常希望删除此约定.(Another feature is the metadata convention. The entity framework can also construct the database, if the database tables are not available and the user rights are sufficient. To remember the state during the last database build a special table is set up. This table will contain the metadata. Since this is something for test / non-productive systems, we usually want to remove this convention.)
通过在类上定义实体并将它们组合在一起来建立数据库连接(Having set up our database connection by defining the entities over classes and combining them together in the) MyDatabase
课,我们已经准备好构建真正的DAL.(class, we are ready to construct our real DAL.)
public class DatabaseRepository
{
// The database is a member variable
MyDatabase db;
// List methods
public SomeSpecificModel ListAllEntities()
{
/* ... */
}
// Insert methods
public void AddNewEntity(AnotherSpecificModel data)
{
/* ... */
}
// Save method - just one example
public void Save()
{
db.SaveChanges();
}
}
现在,我们在类中使用了正确的DAL(Now that we are using a proper DAL with the class) DatabaseRepository
我们也可以使用更复杂的方法来构造它.一种方法是从(we could also use a more sophisticated method to construct it. One way would be to make an interface out of the) DatabaseRepository
,称为(, called) IRepository
.现在,下一个技巧将处理基于这种实现规则的IoC模式.(. The next tip will now handle the IoC pattern, which is based upon such implementation rules.)
提示5:设置适当的IoC依赖关系解析器(Tip 5: Setting up a proper IoC dependency resolver)
控制反转(IoC)模式是ASP.NET MVC中最常用的设计模式之一.它的主要优点是可以构造对象而无需了解所有依赖关系.为了对我们自己的控制器和其他控制器使用这种编程风格,我们需要设置一个适当的依赖解析器.(The inversion of control (IoC) pattern is one of the most used design patterns in ASP.NET MVC. Its main advantage is that objects can be constructing without needing to know all the dependencies. In order to use this style of programming for our own controllers and others we need to set up a proper dependency resolver.)
周围已经有一些经过良好测试和良好工作的依赖解析器解决方案.最常用的一种称为(There are already some well tested and good working dependency resolver solutions around. One of the most used ones is called)统一(Unity).我们需要做的就是告诉服务定位器,可以通过传递哪个对象来解决哪个依赖关系.因此,我们可以构建如下的类((. All we need to do is to tell the service locator, which dependency can be resolved by passing which object. Therefore we can built a class like the following () RepositoryResolver
):():)
public class RepositoryResolver : IDependencyResolver
{
readonly IUnityContainer _container;
public RepositoryResolver(IUnityContainer container)
{
this._container = container;
}
public object GetService(Type serviceType)
{
try
{
return _container.Resolve(serviceType);
}
catch
{
return null;
}
}
public IEnumerable<object> GetServices(Type serviceType)
{
try
{
return _container.ResolveAll(serviceType);
}
catch
{
return new List<object>();
}
}
}
接下来,我们集成该类以解决依赖关系.因此,我们在(Next we integrate the class to resolve dependencies. Therefore we implement the following code in the)**global.asax.cs(global.asax.cs)**文件.(file.)
public class MvcApplication : HttpApplication
{
/* ... */
public static void RegisterDependencies()
{
var container = new UnityContainer();
container.RegisterType<IDependency, ImplementionOfIDependency>();
DependencyResolver.SetResolver(new RepositoryResolver(container));
}
protected void Application_Start()
{
/* ... */
RegisterDependencies();
}
}
这将自动构造如下的控制器:(This will automatically construct controllers like the following:)
public class DependentController : Controller
{
public DependentController(IDependency someDependency)
{
/* Do Something With Dependency */
}
}
因此,为了在ASP.NET MVC中拥有一个完全起作用的依赖项解析器,我们要做的就是实现(So all we need to do in order to have a fully working dependency resolver in ASP.NET MVC is to implement) IDependencyResolver
并在可用的位置添加(或)解析器(and to add the (or a) resolver in the available) DependencyResolver
通过调用对象(object by calling the) SetResolver()
方法.(method.)
可以在以下位置找到更多信息(More information can be found at) 布拉德`威尔逊(Brad Wilson)的博客(Brad Wilson’s blog) .(.)
提示6:在MVC 3中使用MVC 4捆绑功能(Tip 6: Use the MVC 4 Bundling feature in MVC 3)
当然,ASP.NET MVC 4提供了比MVC 3更多的功能.现在的问题是:我们真的需要所有这些功能吗?今天肯定可以使用ASP.NET MVC(1),但是某些功能非常方便,因此很难错过这两个迭代.现在让我们假设我们不能转到MVC 4,对于一般的Web应用程序开发,在MVC 3中仍然可以使用哪个功能呢?我认为这是MVC 4的捆绑功能,我们不应该在MVC 3中错过它.使用Nuget相当容易.如果您尚未签出Nuget,则可能应该阅读其中一篇或另一篇文章(位于CodeProject或官方网站上). Nuget是一个非常不错的数据包管理器,它提供了许多很棒的功能. Nuget旨在完成的任务之一是检查已安装软件包的更新.这有助于我们跟踪第三方库(甚至跨许多系统的第三方库).(Of course ASP.NET MVC 4 offers more features than MVC 3. The question now is: do we really need all those features? Certainly one could work with ASP.NET MVC (1) today, but some features are just so convenient that it’s hard to miss those two iterations. Now let’s assume we cannot move to MVC 4, which feature would still be nice to have in MVC 3 for general web application development? In my opinion it is the bundling feature of MVC 4, that we should not miss in MVC 3. It is quite easy to get by using Nuget. If you haven’t checked out Nuget yet, you should probably read one or the other article (either here on CodeProject or on the official website). Nuget is a really nice packet manager, which offers a lot of awesome features. One of the tasks that Nuget is aiming to do is checking for updates of the installed packages. This helps us keeping track of our third party libraries (or even our own ones across many systems).) 使用完全安装的Nuget软件包管理器,我们只需在Nuget PowerShell控制台中键入以下命令:(With a fully installed Nuget package manager we just have to type the following command into the Nuget PowerShell console:)
Install-Package Microsoft.Web.Optimization -Pre
这将在解决方案中安装ASP.NET MVC 4捆绑功能.安装意味着二进制文件将被引用,所有依赖项也将被解析和安装.现在,我们准备使用捆绑管理器的初步版本.捆绑过程将获取所有找到的或给定的JavaScript或CSS文件,并将它们组合为一个文件.这将导致更少的页面请求,从而为我们的用户带来更快的页面加载和更少的服务器负载.另外,捆绑类还提供缩小功能,即,甚至可以减小JavaScript和CSS文件的文件大小.捆绑管理器是可扩展的,这意味着我们还可以编写对自己的文件类型的支持,或将其用作编译器(例如CoffeeScript到JavaScript等).要启用捆绑管理器,我们需要在(This will install the ASP.NET MVC 4 Bundling feature in the solution. Installing means, that the binary will be referenced and all dependencies will also be resolved and installed. Now we are ready to use the preliminary version of the bundling manager. The bundling process will take all found or given JavaScript or CSS files and combine them into one. This will result into fewer page requests, bringing our users a faster page load and us less server load. Additionally the bundling class offers minification features, i.e. it will even reduce the file size of JavaScript and CSS files. The bundling manager is extensible, which means we could also write support for our own file types or use it as a transpiler (like CoffeeScript to JavaScript and such). To enable the bundling manager we need to set it up in the)**global.asax.cs(global.asax.cs)**文件:(file:)
public class MvcApplication : HttpApplication
{
/* ... */
protected void Application_Start()
{
BundleTable.Bundles.EnableDefaultBundles();
/* ... */
}
}
首先让我们直接在View中使用它:(Let’s first use it directly from a View:)
<p>Here is some view content!</p>
<p>Some more content and now our JavaScripts ...</p>
<script src="@System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("~/Scripts/js")"></script>
在这里,我们仅指定目录和文件扩展名(在这种情况下为JavaScript).然后,捆绑管理器将搜索目录中的所有* .js文件,对其进行组合,最小化并响应有关此URL的任何请求.在大多数情况下,捆绑管理器非常聪明,将jQuery之类的文件放在可能的jQuery插件之上,而忽略了vsdoc文件(JavaScript文件的说明中没有vsdoc的名称).但是,有时我们只希望目录的特定顺序的特定文件.在这种情况下,我们可以创建自己的捆绑软件.最好的方法是(再次)(*Here we just specify a directory and a file extension (for JavaScript in this case). The bundling manager will then search for all *.js files in the directory, combine them, minimize them and respond to any requests regarding this URL. The bundling manager is quite smart in most cases, placing files like jQuery above possible jQuery plug-ins and leaving out vsdoc files (documentation of JavaScript files without the vsdoc in their name). However, sometimes we do only want specific files in a specific order of a directory. In this case we can create our own bundle. The best way to do this, is (again) in the*)**global.asax.cs(*global.asax.cs*)**文件.在这里,我们只需插入以下代码:(*file. Here we just insert the following code:*)
public class MvcApplication : HttpApplication
{
/* ... */
void RegisterBundles(BundleCollection bundles)
{
/* Now we create a specialized jQuery bundle */
var jQueryBundle = new Bundle("~/Scripts/jquery", new JsMinify());
jQueryBundle.AddDirectory("~/Scripts", "jquery*.js",
searchSubdirectories: false, throwIfNotExist: true);
bundles.Add(jQueryBundle);
/* Now we create a very special bundle */
var specialBundle = new Bundle("~/Scripts/special", new ScopeMinify());
specialBundle.AddFile("~/Scripts/game/main.js");
specialBundle.AddFile("~/Scripts/editor/scroll.js");
specialBundle.AddFile("~/Scripts/editor/editor.js");
specialBundle.AddFile("~/Scripts/site/ready.js");
bundles.Add(specialBundle);
}
protected void Application_Start()
{
RegisterBundles(BundleTable.Bundles);
/* ... */
}
}
的(The) ScopeMinify
是我们自己的类,看起来像:(is our own class, which looks like:)
public class ScopeMinify : JsMinify
{
public override void Process(BundleContext context, BundleResponse response)
{
response.Content = "(function() {" + response.Content + "})();";
#if !DEBUG
base.Process(context, response);
#endif
}
}
因此,此类使用立即执行的匿名方法(即IIFE(立即调用函数表达式)模式)来限制所有包含的JavaScript文件.如果我们有生产力,那么JavaScript也将被精简(因为对象将被重命名,因此调试起来较难),否则它将被捆绑并确定范围.(So this class scopes all included JavaScript files with an immediately executed anonymous method aka the IIFE (Immediately Invoked Function Expression) pattern. If we are productive then the JavaScript will also be minified (harder to debug since objects will be renamed), otherwise it will just be bundled and scoped.) 设置捆绑包后,我们只需要调用相应的URL.让我们从视图中再次进行操作:(Having set up a bundling package we only need to call the corresponding URL. Let’s do it again from a View:)
<script src="@System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("~/Scripts/special")"></script>
捆绑是一种获得更高性能并更好地保护我们的Web应用程序的好方法,因为我们可以使用匿名方法来限定生产性JavaScript的范围.它还将阻止大多数用户尝试阅读我们的JavaScript来源(当然,大多数邪恶的鱼类仍将在水中).(Bundling is a nice way to gain more performance and secure our web applications a little bit better, since we could scope productive JavaScript by using anonymous methods. It will also prevent most users from trying to read our JavaScript source (of course the most evil fishes will still be in the water).) 可以在以下位置找到更多信息(Some more information can be found at) Joey Iodice的博客(Joey Iodice’s Blog) .杰夫`克莱斯(Jef Claes)也在他的文章中写了捆绑功能(. Jef Claes does also write about the bundling feature in his article) ASP.NET MVC 4在ASP.NET MVC 3中的捆绑(ASP.NET MVC 4 Bundling in ASP.NET MVC 3) .(.)
技巧7:将MySQL用作数据库(以及成员资格)(Tip 7: Using MySQL as database (together with memberships))
有时,人们抱怨.NET或ASP.NET(MVC),他们只能将MSSQL或SQL Express(或其他Microsoft产品)用作其数据库系统.这是完全不正确的,因为ASP.NET MVC建立在ASP.NET之上,而ASP.NET出于某种原因使用名称.NET. .NET的核心组件之一称为ADO.NET,它使程序员原则上可以使用任何数据库系统.我们需要的只是连接器.对于MySQL,我们可以在几个可用的连接器之间进行选择.官方且广泛接受的连接器称为MySQL .NET连接器.(Sometimes people complain about .NET or ASP.NET (MVC) that they can only use MSSQL or SQL Express (or other Microsoft products) as their database system. This is totally not true, since ASP.NET MVC builds upon ASP.NET, which uses the name .NET for a certain reason. One of the core components of .NET is called ADO.NET and enables programmers to use any database system in principle. All we need is connector. In the case of MySQL we can choose between several available connectors. The official and widely accepted connector is called MySQL .NET Connector.) 使用官方软件包可为我们带来最完整的实施.在本技巧中,我们将研究我们需要下载和设置的内容(Using the official package brings us the most complete implementation. In this tip we will investigate what we need to download and set up in the)**web.config(web.config)**文件以使用标准ASP.NET成员资格提供程序访问功能齐全的MySQL数据库(始终可以编写我们自己的成员资格提供程序-参见技巧21).首先,我们从以下位置下载MySQL Connector/NET(file to have access to a fully featured MySQL database with the standard ASP.NET membership provider (writing our own membership provider can always be done - see tip 21). First of all we download the MySQL Connector/NET from) mysql.com/downloads/connector/net/(mysql.com/downloads/connector/net/) .安装过程完成后(也应在我们的服务器上完成),我们需要更改以下几行:(. After the installation process (which should also be done on our server), we need to change a few lines the)**web.config(web.config)**文件:(file:)
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<connectionStrings>
<add name="DatabaseConnection"
connectionString="Server=localhost;Port=3306;Database=yourdbname;Uid=yourusername;Pwd=yourpassword;"
providerName="MySql.Data.MySqlClient" />
</connectionStrings>
<!-- ... -->
</configuration>
这样可以使用(This enables the usage of)**数据库连接(DatabaseConnection)**作为我们的主要数据库连接.按照惯例(对于Entity Framework),我们需要命名从继承的类.(as our primary database connection. By convention (for the Entity Framework) we need to name the class which inherits from the) DbContext
至(to) DatabaseConnection
.现在,实体框架将与MySQL一起作为数据库工作.那么会员提供者呢?好吧,在这种情况下,我们需要扩展(. Now the Entity Framework will work together with MySQL as a database. So what about the membership provider? Well, in this case we need to extend the)**web.config(web.config)**归档更多.我们来看一下:(file even more. Let’s have a look:)
<configuration>
<!-- ... -->
<system.web>
<!-- ... -->
<authentication mode="Forms">
<forms loginUrl="~/Account/LogOn" timeout="2880" />
</authentication>
<roleManager enabled="true" defaultProvider="MySQLRoleProvider">
<providers>
<clear/>
<add name="MySQLRoleProvider" autogenerateschema="true"
type="MySql.Web.Security.MySQLRoleProvider, MySql.Web, Version=6.5.4.0,
Culture=neutral, PublicKeyToken=c5687fc88969c44d"
connectionStringName="MarioDB" applicationName="/" />
</providers>
</roleManager>
<membership defaultProvider="MySQLMembershipProvider">
<providers>
<clear />
<add name="MySQLMembershipProvider" autogenerateschema="true"
type="MySql.Web.Security.MySQLMembershipProvider, MySql.Web,
Version=6.5.4.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d"
connectionStringName="MarioDB" enablePasswordRetrieval="false"
enablePasswordReset="true" requiresQuestionAndAnswer="false"
requiresUniqueEmail="true" maxInvalidPasswordAttempts="5"
minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0"
passwordAttemptWindow="10" applicationName="/" />
</providers>
</membership>
<!-- ... --->
</system.web>
</configuration>
现在,这里设置了一个重要的属性.如果我们不设置(Now there is one important attribute that is being set here. If we do not set the) autogenerateschema
至(to) true
,我们需要适当的表为成员资格和角色提供者做好准备.这是不必要的工作,这意味着我们应该始终从允许生成表开始.为什么我们以后要删除它?一方面,数据库用户不应具有太多权限,因此应在作业完成后立即删除授予表创建的权限.另一方面,不需要额外检查表是否存在,从而为我们节省了一点时间.(, we need the appropriate tables to be ready for the membership and role provider. This is unnecessary work, which means we should always start with the generation of the table being allowed. Why should we remove it afterwards? On the one side the database user should not have too many rights, so granting the table create should be removed as soon as the job is done. On the other hand the extra check if the table exists is not required, saving us a little bit of time.)
提示8:检测jQuery AJAX请求(Tip 8: Detect jQuery AJAX requests)
有时我们想通过AJAX请求获取资源.此技术通过限制输出来节省我们的资源,并为用户提供更好的体验.即使我们正在执行AJAX请求,我们仍然需要包括使用旧版浏览器或禁用JavaScript引擎的用户.对于本技巧,我们将省略客户端的实际实现,而将重点放在服务器端.场景如下:请求页面,无论请求是由jQuery完成还是直接由服务器处理,我们将部分结果与应更新的部分或整个页面一起存储.(Sometimes we want to fetch resources by AJAX requests. This technique saves us resources by limiting the output and gives the user a better experience. Even though we are performing an AJAX request we still need to include users with older browsers or disabled JavaScript engines. For this tip we will leave out the actual implementation on the client and focus on the server side. The scenario is the following: A page is requested and whether the request was done by jQuery or directly we server a partial result with the part that should be updated or a complete page.)
对我们来说幸运的是,jQuery的作者对这种情况非常聪明,并为每个AJAX请求添加了特定的标头名称和值. ASP.NET MVC的创建者意识到了这一点,并包括了一种扩展方法来向我们发出信号,告知是否使用了AJAX.扩展方法(Lucky for us, the writers of jQuery have been quite smart about this scenario and have added a certain header name and value for every AJAX request. The creators of ASP.NET MVC recognized this and have included an extension method to signal us if AJAX was used or not. The extension method) IsAjaxRequest()
位于(is located in the) Request
可由任何控制器访问的对象.(object that can be accessed by any controller.)
因此,我们可以编写如下的控制器动作:(Therefore we could write a controller action like the following:)
public ActionResult UploadImage()
{
/* Do general work */
//Just to distinguish between an AJAX request (for: modal dialog) and normal request
if (Request.IsAjaxRequest())
return PartialView(data);
return View(data);
}
如果是AJAX,我们将只更新网页的内容区域.在其他情况下,我们将通过响应全部内容来更新整个页面.(In case of AJAX we would just update the content region of the webpage. In any other case we would update the whole page by responding with the full content.)
提示9:预编译视图以最大程度地减少错误(Tip 9: Pre-compile the views to minimize errors)
完成编写MVC应用程序后,我们可能会以发布模式发布代码.我们可能编写了很多代码,生成了很多视图,甚至创建了很多测试.测试都很好,一切似乎都已就绪,但是无法测试的是视图.如果任何视图包含错误,我们只能通过访问来实现.这是一个巨大的问题-但可以避免.通过编译视图,我们可以基本检查是否忘记或错过了某些东西.(Once we finished writing our MVC application we will probably publish the code in release mode. We have probably written a lot of code, generated many views and even had a lot of tests created. The tests are all fine and everything seems in place, however, what can not be tested are the views. If any view contains an error we will only realize this by accessing it. This is a huge problem - but it can be avoided. By compiling the views we have a basic check if we forgot or missed something.) 要设置编译,我们需要更改项目的XML文件.这是我们的方法:(To set up compilation we need to change the project’s XML file. Here is how we do it:)
<MvcBuildViews>false</MvcBuildViews>
以导致:(to result in:)
<MvcBuildViews>true</MvcBuildViews>
- 右键单击解决方案资源管理器中的项目(Right click on the project inside the solution explorer)
- 点击卸载项目(Click unload project)
- 现在再次右键单击该项目(Now right click on the project again)
- 这次点击编辑(This time click edit)
- 更改以下行(Change the following line)
- 保存文件并关闭它(Save the file and close it)
- 再次右键单击该项目(Right click on the project again)
- 点击重新加载项目(Click on reload project) 就这样!现在,我们的视图正在像其他源代码一样进行编译.注意,这可能不会让所有人满意.通常,激活发行版本的编译选项就足够了.(That’s all! Now our views are being compiled like the rest of the source code. Note, that this will probably not satisfy everyone. Usually activating the compilation option for release builds is sufficient.) 有关该主题的更多信息,请访问(More information on that topic is available over the) Stackoverflow问题(Stackoverflow question) . David Ebbo也确实写了这个帖子(. Also David Ebbo did write the post) 将您的Razor助手变成可重用的库(Turn your Razor helpers into reusable libraries) 在MSDN上,这是非常相关的.有关设置编译的完整指南,请参见(on MSDN, which is quite related. A full guide for setting up compilation is given on) Tugberk Ugurlu的博客(Tugberk Ugurlu’s blog) .(.)
提示10:使用TagBuilder(Tip 10: Using the TagBuilder)
构造完全有效的(HTML)标签非常简单.我们不需要弄乱HTML(可能会产生错字,例如缺少引号)来产生HTML输出.大多数人认识到的第一件事是,每个字符串都将进行HTML编码.让我们考虑以下视图:(Constructing perfectly valid (HTML) tags is super easy. We do not need to mess around with HTML (which will maybe contain a typo like a missing quotation mark) for producing HTML output. The first thing most people recognize is that every string will be HTML-encoded. Let’s consider the following view:)
@{
var title = "<span class=mark>HI, Mum!</span>";
}
<p>@title</p>
来自服务器的(部分)响应不是预期的:(The (partial) response from the server is not the expected:)
<p><span class=mark>HI, Mum!</span></p>
而是以下内容:(but rather the following:)
<p><span class=mark>HI, Mum!</span></p>
这实际上是MVC(或Razor视图引擎)的功能之一.如果我们需要HTML,则必须明确告知系统.有两种方法:(This is actually one of the features of MVC (or the Razor view engine). If we want HTML, we have to tell this the system explicitly. There are two ways for this:)
- 我们使用(We use the)
Raw()
HTML帮助程序的扩展方法,该方法接受一个字符串并在传递时将其输出.(extension method of the HTML helper, which takes a string and outputs it as it has been passed.) - 我们不输出字符串值,而仅输出(We do not output string values, but only)
MvcHtmlString
或此类实例,(or such instances, which implement)IHtmlString
.(.) 由于我们没有选择始终使用第一种情况,因此我们将不得不处理第二种情况.幸运地构建一个(Since we do not have the option to use always scenario number one we will have to deal with the second one. Luckily constructing a)MvcHtmlString
这真的很容易,因为构造函数只需要一个字符串作为参数,或者因为我们可以只使用static(is really easy, since the constructor just wants a string as argument or since we could just use the static)Create()
的方法(method of the)MvcHtmlString
类.(class.) 现在,HTML字符串的构造过程可以开始使用许多字符串串联或(Now the construction process of an HTML string could begin using many string concatenations or a)StringBuilder
实例.但是,最好的方法是使用(instance. The best method, however, is the usage of the)TagBuilder
.我们要做的就是告诉构造函数标签的名称是什么.然后,我们可以根据需要添加或删除自定义属性.我们还可以添加或删除CSS类.另一个功能是设置有效的ID.由于禁止使用某些标志,(. All we need to do for creating one is to tell the constructor what the name of the tag is. Then we can add or remove custom attributes as we want to. We can also add or remove CSS classes. Another feature is the set up of a valid ID. Since some signs are forbidden, the)TagBuilder
会自动使用有效的ID清除ID字符串.最后,我们还可以设置内部文本或HTML.(will automatically sanatize the ID string with valid ones. Finally we can also set the inner text or HTML.) 现在,我们考虑构建自己的图像链接的情况.我们可以执行以下操作:(Now we consider the case of building our own image link. We could do the following:)
public static MvcHtmlString ImageLink(string link, string src)
{
var tag = new TagBuilder("a");
tag.Attributes.Add("href", link);
var img = new TagBuilder("img");
img.Attributes.Add("src", src);
img.Attributes.Add("alt", string.Empty);
tag.InnerHtml = img.ToString(TagRenderMode.SelfClosing);
return MvcHtmlString.Create(tag.ToString(TagRenderMode.Normal));
}
这里我们用两个(Here we are using two) TagBuilder
(每个标记一个)实例.此类的功能之一是重载((one for each tag) instances. One of the features of this class is the overloaded) ToString()
方法.在这里,我们可以设置最终外观.由于图像标签是自封闭的,因此我们将其用作图像标签.然后将图像标签设置为锚标签的内部HTML.在最后一步,我们创建最终(method. Here we can set the final look. Since an image tag is self closing we are picking this one for the image tag. The image tag is then being set as the inner HTML of the anchor-tag. In the last step we create the final) MvcHtmlString
.(.)
提示11:包括非常有用的扩展方法(Tip 11: Include very useful extension methods)
ASP.NET MVC广泛使用C#/VB.NET的更新功能构建.最常用的功能之一是扩展方法的可能性.理论上,您可以重写所有可用的扩展方法(例如,(ASP.NET MVC was built using the newer features of C# / VB.NET extensively. One of the features used the most is the possibility of extension methods. In theory you could rewrite all available extension methods (e.g. the ones from the) Html
视图中的对象),并通过替换适当的名称空间来替换您现有版本中的所有现有调用.这意味着我们只需更改使用的名称空间,便可以轻松地将现有方法与自己的方法进行交换.(object inside a view) and replace all existing calls by your own versions, just by replacing the proper namespace. This means that we have an easy way of exchanging existing methods with our own ones just by changing the used namespace.)
大多数时候,我们对现有的扩展方法感到满意,但我们希望拥有其他扩展方法.一个好的做法是使用我们自己的方法为每个要扩展的类创建一个包含文件的目录.我们可以称该子目录(Most of the time we are happy with the existing extension methods, but we want to have additional ones. A good practice is to make a directory with files for each class that we want to extend with our own methods. We could call that subdirectory)**扩展名(Extensions)**并添加静态类,如(and add static classes like) HtmlExtensions
,(,) UrlExtensions
,还有其他人.一些更标准的扩展方法(未提供)是:(, and others there. Some of the more standard like extension methods (which aren’t provided out of the box) are:)
- 一种(A)
<form>
指令,可用于提交文件(directive, which can be used for submitting files) - 文件输入元素(The file input element)
- 一个动作链接,该链接围绕HTML图像标签而不是简单文本(An action link, which surrounds an HTML image tag instead of simple text)
- 一些HTML5表单输入元素,例如的(Some HTML5 form input elements, e.g. the)
<input type=email>
元件(element) 提供这些扩展方法的可能方法如下所示:(A possible way of providing those extension methods could look like this:)
public static class HtmlExtensions
{
public static MvcForm BeginFileForm(this HtmlHelper html)
{
return html.BeginForm(null, null, FormMethod.Post, new { enctype = "multipart/form-data" });
}
public static MvcHtmlString File(this HtmlHelper html, string name, bool multiple)
{
var tb = new TagBuilder("input");
tb.Attributes.Add("type", "file");
tb.Attributes.Add("name", name);
tb.GenerateId(name);
if (multiple)
tb.Attributes.Add("multiple", "multiple");
return MvcHtmlString.Create(tb.ToString(TagRenderMode.SelfClosing));
}
public static MvcHtmlString File(this HtmlHelper html, string name)
{
return html.File(name, false);
}
public static MvcHtmlString MultipleFileFor<TModel, TProperty>(this HtmlHelper<TModel> html,
Expression<Func<TModel, TProperty>> expression)
{
string name = GetFullPropertyName(expression);
return html.File(name, true);
}
public static MvcHtmlString FileFor<TModel, TProperty>(this HtmlHelper<TModel> html,
Expression<Func<TModel, TProperty>> expression)
{
string name = GetFullPropertyName(expression);
return html.File(name);
}
public static MvcHtmlString ActionImage(this HtmlHelper html, string imageUrl,
string action, string controller, object routeValues, object htmlAttributes)
{
var urlHelper = new UrlHelper(html.ViewContext.RequestContext);
var link = new TagBuilder("a");
link.Attributes.Add("href", urlHelper.Action(action, controller, routeValues));
var img = new TagBuilder("img");
img.Attributes.Add("src", imageUrl);
img.Attributes.Add("alt", action);
link.InnerHtml = img.ToString(TagRenderMode.SelfClosing);
return MvcHtmlString.Create(link.ToString(TagRenderMode.Normal));
}
public static MvcHtmlString Email(this HtmlHelper html, string name)
{
return html.Email(name, string.Empty);
}
public static MvcHtmlString Email(this HtmlHelper html, string name, string value)
{
var tb = new TagBuilder("input");
tb.Attributes.Add("type", "email");
tb.Attributes.Add("name", name);
tb.Attributes.Add("value", value);
tb.GenerateId(name);
return MvcHtmlString.Create(tb.ToString(TagRenderMode.SelfClosing));
}
public static MvcHtmlString EmailFor<TModel, TProperty>(this HtmlHelper<TModel> html,
Expression<Func<TModel, TProperty>> expression)
{
var name = GetFullPropertyName(expression);
var value = string.Empty;
if(html.ViewContext.ViewData.Model != null)
value = expression.Compile()((TModel)html.ViewContext.ViewData.Model).ToString();
return html.Email(name, value);
}
static string GetFullPropertyName<T, TProperty>(Expression<Func<T, TProperty>> exp)
{
MemberExpression memberExp;
if (!TryFindMemberExpression(exp.Body, out memberExp))
return string.Empty;
var memberNames = new Stack<string>();
do
{
memberNames.Push(memberExp.Member.Name);
}
while (TryFindMemberExpression(memberExp.Expression, out memberExp));
return string.Join(".", memberNames.ToArray());
}
static bool TryFindMemberExpression(Expression exp, out MemberExpression memberExp)
{
memberExp = exp as MemberExpression;
if (memberExp != null)
return true;
if (IsConversion(exp) && exp is UnaryExpression)
{
memberExp = ((UnaryExpression)exp).Operand as MemberExpression;
if (memberExp != null)
return true;
}
return false;
}
static bool IsConversion(Expression exp)
{
return (exp.NodeType == ExpressionType.Convert || exp.NodeType == ExpressionType.ConvertChecked);
}
}
的(The) ActionImage()
方法使用创建(method uses the creation of a) UrlHelper
实例生成相应的超链接.这可以通过使用(instance to generate the corresponding hyperlink. This can be done by using the) ViewContext.RequestContext
当前的属性(property of the current) HtmlHelper
目的.(object.)
有时提供非常有用的扩展作为扩展方法.让我们看一下一个非常有用的扩展名为(Sometimes very useful extensions are provided as extension methods. Let’s have a look at a very useful extension called)验证码MVC(Captcha MVC).该扩展可通过Nuget或(. The extension is available over Nuget or on) 官方CodePlex页面(the official CodePlex page) .此扩展使我们可以包含Captchas(一种验证用户实际上是人类而不是计算机程序的验证),以防止自动使用我们的网页.这将减少非人类用户提交的表单.为了使用扩展名,我们只需要执行以下步骤:(. This extension gives us the possibility of including Captchas (a kind of verification that the user is actually a human and not a computer program) to prevent automatic usage of our webpage. This will reduce form submissions by non-human users. In order to use the extension we just have to do the following steps:)
- 下载验证码MVC(Download Captcha MVC)
- 在我们的解决方案中安装扩展(Install the extension in our solution)
- 在(Set up the encryption key in the)**web.config(web.config)**文件(file)
- 包括视图的扩展方法的名称空间(Include the namespace of the extension method for the views)
- 插入(Insert the)
@Html.Captcha("YourText", 5)
指令形式,我们要在其中放置验证码(directive in forms, where we want to place the Captcha) - 插入(Insert the)
CaptchaVerify
动作上方的属性,我们要在此处检查验证码(attribute above actions, where we want to check for the Captcha) 验证码MVC的扩展方法应稍作修改,以提供(The extension method for the Captcha MVC should be slightly modified to provide everything for)**我们的(OUR)**网页,因为我们需要它.标准实现如下所示:(webpage as we need it. The standard implementation looks like:)
public static class HtmlExtensions
{
public static MvcHtmlString Captcha(this HtmlHelper htmlHelper, string textRefreshButton, int length)
{
return CaptchaHelper.GenerateFullCaptcha(htmlHelper, textRefreshButton, length);
}
public static MvcHtmlString Captcha(this HtmlHelper htmlHelper, int length)
{
return CaptchaHelper.GenerateFullCaptcha(htmlHelper, length);
}
}
最后,我们还需要设置一些CSS样式,以将Captcha调整为Web应用程序的外观.(We will also need to set up some CSS styles in the end to adjust the Captcha to the look and feel of our web application.)
该图向我们展示了使用Captcha扩展名的最终网站的外观.在这里,我们将标准代码与一些德语文本一起使用.唯一的区别在于CSS规则,无论如何,这些规则都必须调整到我们的特定页面.(The image shows us how a final website using the Captcha extension might look like. Here we used the standard code with some German texts. The only difference lies in the CSS rules, which must be adjusted to our specific page anyway.)
提示12:您永远不会忘记的属性(Tip 12: Attributes you should never forget)
正如我们已经看到的那样,属性是ASP.NET MVC的一个非常重要的功能(或者更准确地说是ASP.NET MVC中广泛使用的C#的功能).但是,控制器并不是我们唯一需要适当属性的地方.通过MVC,构造的数据模型可以通过给定的属性进行验证.如果未指定属性,则MVC会采用一些默认值.这可能会导致不良的副作用.一个非常普遍的误解是,字符串可以与.NET-Framework中最大字符串长度一样长.但是,事实并非如此.字符串长度的默认值为128.这有时会导致混淆,如(As we’ve already seen attributes are a very important feature of ASP.NET MVC (or to be more correct: are a feature of C# that has been used extensively in ASP.NET MVC). However, the controller is not the only place where we need to have the right attributes in place. By MVC constructed data models are validated by given attributes. If no attribute is specified then MVC assumes some default values. This can result in unwanted side effects. A very popular misbelief is that strings can be as long as the maximum in string length in the .NET-Framework. This is, however, not true. The default value for string length is 128. This sometimes leads to confusion as discussed on the) 堆栈溢出(StackOverflow) 页.(page.)
要超过此默认字符串长度,我们需要指定最大长度.如果我们想使用最大尺寸,我们只需要该属性(To exceed this default string length we need to specify a maximum length. If we want to use the maximum size possible, we just need the attribute) MaxLength
.其他选项是(. Other options are the usage of the) StringLength
属性.考虑字符串,我们还需要考虑(任何)HTML输入.如果检测到HTML输入,则ASP.NET运行时通常会引发错误.可以通过告诉MVC允许某个参数向我们的服务器提交HTML值来避免这种情况.我们需要的是(attribute. Considering strings we need also to think about (any) HTML input. If HTML input is detected the ASP.NET runtime will usually throw an error. This can be avoided by telling MVC that a certain parameter is allowed to submit HTML values to our server. All we need is the) AllowHtml
在这种情况下的属性.让我们看一下具有这些属性的样本模型:(attribute in this case. Let’s have a look at a sample model with those attributes:)
public class SubmitDataModel
{
[MaxLength]
[AllowHtml]
public string LongHTMLInput { get; set; }
}
另外,如果我们希望提供一个特定的值,那么我们需要添加(Also, if we want that a specific value is provided, then we need to add the) Required
属性.一种特殊情况是基本(值)类型,例如(attribute. A special case are the elementary (value) types like) Int32
,(,) Boolean
和其他(基本上是所有结构).在这里,我们无法区分给定(已满足要求)和默认值(例如(and others (basically all structures). Here we are not able to distinguish between a given (requirement fulfilled) and a default (like) 0
为(for an) Int32
类型)值,具体取决于施工过程.为了避免任何错误的计算,我们应该将数据类型从值类型更改为引用类型.这可以通过添加一个(type) value, due to the construction process. To avoid any wrong computations we should change the data type from a value type to a reference type. This can be done by appending a) ?
在值类型的类型之后.所以(after the value type’s type. So) public int Id { get; set; }
将被更改为(would be changed to) public int? Id { get; set; }
.在这里(. Here the) Required
属性再次有意义.(attribute makes sense again.)
关于MVC中验证的一个很好的资源是(A good resource about Validation in MVC is) Henry He在Codeproject上的文章(Henry He’s article on Codeproject) .顾志刚(Scott Gu)也写过(. Scott Gu also writes about) ASP.NET MVC 2:模型验证(ASP.NET MVC 2: Model Validation) 在他的博客中.(in his blog.)
提示13:谨慎使用外键(Tip 13: Be cautious with ForeignKeys)
使用ASP.NET MVC/实体框架时,最流行的方案之一是我们需要从实体框架中使用一个已建立的数据库.因此,我们需要构造一个映射到现有数据库的代码.这可能很棘手,因为实体框架遵循其自己的约定,这些约定可以更改,但可以设置为标准值.另一个陷阱可能是某些映射不被支持或难以实现.(One of the most popular scenarios when working with ASP.NET MVC / the Entity Framework is that we have a set up database that needs to be used from the Entity Framework. Therefore we need to construct a code that maps to the existing database. This can be tricky since the Entity Framework follows its own conventions, which can be changed of course, but are set to a standard value. Another trap could be that certain mappings are not supported or tricky to implement.)
关于如何处理映射等有很多技巧,因此,我们将(作为示例)详细介绍如何在代码中实现现有的一对一映射.让我们从(主要)表开始.我们称对应的实体(There are a lot of tips and tricks how to handle mappings and such, so we will just go (as an example) into detail of implementing an existing one-to-one mapping in the code. Let’s start with the (dominant) table. We call the corresponding entity) DominantEntity
.我们假设以下结构:(. We assume the following structure:)
public class DominantEntity
{
[Key]
public virtual int Id { get; set; }
/* Some other properties */
}
现在我们构造次要实体-简称为(Now we construct the minor entity - simply called) MinorEntity
:(:)
public class MinorEntity
{
[Key]
[ForeignKey("DominantEntity")]
public virtual int DominantEntityId { get; set; }
public virtual DominantEntity DominantEntity { get; set; }
}
我们使用(We use the) virtual
关键字此处,以允许实体框架在可能的情况下进行延迟加载.因此,总是会加载外键,但是只有在请求时才加载相关的外来实体(为我们节省了一些数据库资源).(keyword here to allow the Entity Framework to go for lazy loading in possible scenarios. Therefore the foreign key is always loaded, but the related foreign entity is only loaded when requested (saving us some database resources).)
在这种情况下,真正有用的是知道如何将实体(即表)的属性(即列)设置为其真实名称(如果无法应用约定).以下代码段显示了实体如何(What could be really useful in this scenario, is to know how to set properties (i.e., columns) of entities (i.e. tables) to their real names (if the convention cannot be applied). The following snippet shows how the entity) EntityType
正在重新映射(is being re-mapped over the) OnModelCreating()
方法.(method.)
public class MyDatabase : DbContext
{
/* ... */
public DbSet<EntityType> EntityTypes { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
/* ... */
modelBuilder.Entity<EntityType>().Prop(model => model.Property).HasColumnName("SampleColumnName");
modelBuilder.Entity<EntityType>().ToTable("SampleTableName");
}
}
这种专用映射的缺点是,它可能导致诸如(The disadvantage of such a specialized mapping is that it can result in errors like the)多重性在角色中无效.由于从属角色指的是关键属性,因此从属角色的多重性上限必须为" 1"(Multiplicity is not valid in Role . Because the Dependent Role refers to the key properties, the upper bound of the multiplicity of the Dependent Role must be ‘1’).为避免此类问题,我们需要记住致电(. To avoid such problems we need to remember to call the) SaveChanges()
我们的方法(method of our) MyDatabase
插入之间的类.我们只能插入一个类型的实体(class between insertation. We can only insert an entity of type) MinorEntity
,如果引用了(, if the referenced) DominantEntity
已添加到数据库中.(has already been added to the database.)
可以在Codeproject文章中找到有关Entity Framework中外键的更多信息.(More about foreign keys in the Entity Framework can be found in the Codeproject article about) EF代码优先-添加外键关系(EF Code first - Add a foreign key relationship) .(.)
提示14:使用本地化-机会和陷阱(Tip 14: Working with localization - Chances and pitfalls)
本地化是.NET-Framework最强大的功能之一,因为它是开箱即用的.我们需要做的就是遵循某种命名资源文件的约定,最后得到一个完整的本地化应用程序.可以将其应用于任何.NET应用程序:WinForms,WPF,WebService,WebForms …,当然还有ASP.NET MVC!(Localization is one of the most awesome features of the .NET-Framework, since it is included out of the box. All we need to do is following a certain convention for naming resource files and we’ll end up with a complete localized application. This can be applied to any .NET-Application: WinForms, WPF, WebService, WebForms, … and of course ASP.NET MVC!) 要使用本地化,我们需要两件事:(To use localization we need two things:)
- 正确输入(Proper entries in the)**web.config(web.config)**文件(file)
- 资源文件的本地化版本(因此,我们必须从资源文件中收集所有需要本地化的资源)(Localized versions of our resource files (therefore we have to gather all resources that need to be localized from the resource file(s)))
资源文件的命名约定是这样的:如果我们原始(默认)资源文件被调用(The naming convention of resource files is such that if we original (default) resource file is called)**String.resx(String.resx)**那么一个合适的德国人将被称为(then a proper German one would be called)串(String).de(.de).resx(.resx).如果我们想要一个更专业的奥地利版本,我们将其命名(. If we want a more specialized version for Austria we would name it)String.de(String.de)-在(-at).resx(.resx).现在,每个缺少奥地利版本的值都来自广义的德语版本.如果通用德语版本缺少某些值,则使用通用资源文件中的默认值.由于资源文件已编译,然后由视图访问,因此我们需要将任何资源文件的访问修饰符设置为(. Now every value that is missing the version for Austria is taken from the generalized German version. If the generalized German version is missing some values the default value from the General resource file is taken. Since the resource file is compiled and then accessed by the views we need to set the access modifier of any resource file to)
public
.(.) 的(The)**web.config(web.config)**文件可能如下所示:(file could look like this:)
<configuration>
<!-- ... -->
<system.web>
<!-- ... -->
<globalization culture="en-us" uiCulture="auto" />
</system.web>
</configuration>
重要的属性是(The important attribute is the) uiCulture
.使用此属性,我们可以设置用户界面的语言.价值(. With this attribute we set the language of the user interface. The value) auto
告诉ASP.NET运行时使用(从提交的浏览器中)语言标头.如果找不到提交的语言,则将采用默认语言(来自默认资源文件).(tells the ASP.NET runtime to use the (from the browser submitted) language header. If the submitted language is not found, the default language (from the default resource file(s)) will be taken.)
更有趣的是值的用法(More interesting is the usage of the value) en-us
在里面(in the) culture
属性.在这里,我们将内部语言设置为美国格式的英语.这将处理带点的数字作为小数点分隔符,这就是应用此值的原因.不幸的是,JavaScript没有本地化(即使有库).因此,JavaScript验证可能会在尝试验证十进制值(例如,(attribute. Here we set the internal language to English in the US-format. This will handle numbers with a dot as decimal separator, which is the reason for applying this value. Unfortunately JavaScript is not localized (even though there are libraries). Therefore JavaScript validation might bring up errors when trying to validate decimal values such as)4,5(4,5).从某些角度来看,这是一个非常有效的值,例如但是,德国系统不会进行验证(默认情况下).为了避免此类麻烦,我们指导用户使用美国格式.这是我们麻烦的起点.现在,客户端已通过美国英语验证,但服务器已通过德国验证(如果我们使用(. This is a perfectly valid value from some perspective, e.g. the German system, however, it will not be validated (by default). In order to avoid such pains we guide the user to use the US-format. This is where our trouble would start. Now the client is validated in US English, but the server is validated in German (if we would use) auto
为了(for the) culture
).因此,我们也通过在服务器上使用美国英语来避免这种麻烦.(). Therefore we avoid this trouble by using US English on the server as well.)
技巧15:约束以实现更好的路由(Tip 15: Constraints for better routing)
路由是ASP.NET MVC的主要功能之一.为了增强错误处理程序并避免错误,我们应该对URL参数使用约束.如果我们希望ID为数字值(例如(Routing is one of the primary features of ASP.NET MVC. To boost error handler and avoid bugs we should use constraints for URL parameters. If we want the id to be a numeric value (like an) int
)我们应该指定一个约束() we should specify a constraint like) \d+
.约束设置为正则表达式.正则表达式是IT行业中最有用的技术之一,因为它们使我们能够基于模式进行快速准确的文本搜索.本文将不介绍正则表达式.(. Constraints are set up as regular expressions. Regular expressions are one of the most useful techniques in the IT industry, since they allow us to make fast and exact text searches based on patterns. This article will not give an introduction to regular expressions.)
让我们看一些没有任何约束的路线如何:(Let’s see how some route without any constraint could look like:)
routes.MapRoute(
"BlogArchive",
"Archive/{entryDate}",
new { controller = "Blog", action = "Archive" }
);
显然所有链接到(Obviously all links to)/存档/…(/Archive/…)将被带到(will be taken to the) Archive
控制器的动作(action of the controller) Blog
.现在包括类似(. Now this includes Urls like)/存档/5(/Archive/5)以及(as well as)/存档/abc(/Archive/abc).显然,这里除了指定参数的要求之外,没有其他约束.(. Clearly, here we have no constraint, besides the requirement of having a parameter specified.)
由于我们在谈论日期,因此我们可能希望参数采用非常特定的日期格式,例如ISO 8601标准(YYYY-MM-DD).通过添加第四个参数(约束),我们可以告诉ASP.NET MVC路由引擎我们对有效URL的要求.这是最后的方法调用:(Since we are talking about a date we might want the parameter to be in a very specific date format, like the ISO 8601 standard (YYYY-MM-DD). By adding a forth argument (the constraint), we are able to tell the ASP.NET MVC routing engine our requirement for a valid URL. Here is the final method call:)
routes.MapRoute(
"BlogArchive", // Route name
"Archive/{entryDate}", // Route design
new { controller = "Blog", action = "Archive" }, // Route mapping / default route values
new { entryDate = @"d{4}-d{2}-d{2}" } // Route constraints
);
我们还可以基于HTTP方法,语言和其他属性来建立路由约束.如果我们对现有解决方案不满意,我们甚至可以建立自己的路线约束.我们需要做的就是构造一个实现(We can also built up route constraints based on the HTTP method, language and other properties. If we are not satisfied with the existing solutions, we can even built our own route constraints. All we need to do is to construct a class that implements the) IRouteConstraint
接口.现在,我们可以使用该类的实例作为某些属性的约束.(interface. Now we can use an instance of that class as a constraint on some property.)
有关整个主题的好文章可以在下面找到(A good article on the whole topic can be found at) 斯蒂芬`沃尔瑟(Stephan Walther)的博客(Stephan Walther’s blog) .(.)
提示16:使用名称作为操作参数时要小心(Tip 16: Be careful when using names for action parameters)
如果我们遵守规则,那么自动生成模型就很棒.例如,我们可以参考Shawson的Code Blog的帖子,(Automatic model generation is awesome if we play by the rules. As an example we could take the post by Shawson’s Code Blog, dealing with) 回发时返回null(null returns on post back) .为了重建示例,我们可以考虑使用以下控制器:(. To reconstruct the example we could consider the following controller:)
public TestController : Controller
{
//
// GET: /Test/Method/5
public ActionResult Method(int id)
{
/* ... */
}
//
// POST: /Test/Method/5
[HttpPost]
public ActionResult Method(int id, DataModel specialName)
{
/* ... */
}
}
即使数据全部有效且良好,ModelState也会始终无效.这是这个具体模型的样子:(The ModelState will always be invalid, even if the data is all valid and fine. Here is how this concrete model could look like:)
public class DataModel
{
public int specialName { get; set; }
/* ... */
}
在这里,我们看到了问题.我们已经提交了具有名称的名称/值对(Here we see the problem. We already submit a Name-Value pair that has the name) specialName
.因此,MVC将尝试生成type属性(. Therefore MVC will try to generate the attribute of type) DataModel
从中的价值(from the value in) specialName
.这是行不通的.如果我们将签名更改为(. This will not work. If we change the signature to) Method(int id, DataModel otherSpecialName)
我们将摆脱麻烦.(we will be free from trouble.)
有时,我们想使用具有不同参数名称的现有路由,使其看起来像已经建立的路由.这是可能的(Sometimes we would like to use existing routes with different parameter names to look like an already set up route. This is possible with the) Bind
属性.让我们比较一下这两种方法:(attribute. Let’s compare those two methods:)
public MyController : Controller
{
public ActionResult MyMethod([Bind(Prefix = "id")] string parameter)
{
/* ... */
}
public ActionResult MyMethod(string parameter)
{
/* ... */
}
}
如果我们只是设置默认路由(请记住:(If we just set up the default route (remember:) {controller}/{action}/{id}
),我们将获得以下网址来完成这两个操作:(), we’ll end up with the following URLs to reach both actions:)
- 第一个可能是:(The first one could be:)我/我的方法/你好(My/MyMethod/hello)
- 第二个可能是:(The second one could be:)My/MyMethod?parameter =你好(My/MyMethod?parameter=hello) 参数名称没有很多限制.如果我们考虑上面的示例以及不允许参数名称与操作具有相同名称的规则,我们几乎都会知道防止出现与参数名称有关的错误.(Parameter names do not have many restrictions. If we think about the example above and the rule that parameter names are not allowed to have the same name as the action we know almost everything to prevent errors concerning the parameter name.)
提示17:向视图添加名称空间(Tip 17: Adding namespaces to views)
视图使用不同的视图(Views use a different)**web.config(web.config)**比应用程序本身,因为这会带来一些好处.如果我们想在所有视图中使用特定的命名空间(例如,由于我们的模型位于该命名空间中,或者对(than the application itself, since this results in some benefits. If we want to use a certain namespace across all views (e.g., since our models are placed in this namespace, or to use a certain extension method for the) HtmlHelper
实例)我们需要将其添加到(instance) we need to add it in the)**web.config(web.config)**放在(placed in the)**观看次数(Views)**子目录.(subdirectory.)
下列(The following)**web.config(web.config)**显示了如何添加名称空间:(shows how namespaces can be added:)
<configuration>
<!-- ... -->
<system.web.webPages.razor>
<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<!-- ... -->
</pages>
<!-- ... -->
</system.web.webPages.razor>
</configuration>
如果我们在一个视图中更频繁地需要一个名称空间,则可以为该一个视图添加它.下面的视图演示了这一点:(If we need a single namespace more often in one view, we could just add it for this one view. The following view demonstrates this:)
@using System.Web.MyOwnNamespace
<p>Here is some paragraph!</p>
该主题还在StackOverflow上引起争议.问题(The topic is also debated on StackOverflow. The question) 如何在Razor视图页面中导入名称空间?(how to import a namespace in Razor view page?) 有关在单个视图中使用名称空间的问题.也(concerns the usage of a namespace in a single view. Also) 如何在剃须刀页面上添加额外的命名空间?(how to add extra namespaces to razor pages?) 讨论了在(discusses the possibility of adding namespaces in the)**web.config(web.config)**文件.(file.)
技巧18:内部动作(Tip 18: Internal actions)
有时,我们需要使方法(从控制器)公开或作为一种操作公开,而实际上并不希望直接从浏览器中调用它.可以再次使用属性来完成.最有用的属性之一是(Sometimes we need to make a method (from a controller) public or to be exposed as an action without actually wanting it to be called directly from the browser. This can be done again by using attributes. One of the most useful attributes is the) ChildOnlyAction
属性.它将使操作或控制器(应用于控制器时)对于请求不可见.访问操作的唯一方法是从Web应用程序内部.(attribute. It will make the action or controller (when applied to a controller) be invisible for requests. The only way to access the action is from inside the web application.)
[ChildOnlyAction]
public ActionResult CanOnlyBeCalledInternal()
{
/* ... */
return PartialView();
}
通常,我们将从此类操作中返回部分视图.此类操作的最大优点是能够从视图中调用它们(类似于方法调用).因此,您可以通过使用以下命令在视图内部插入HTML块(局部视图)(Usually we will return partial views from such actions. The biggest advantage from such actions is the ability to call them (similar to method calls) from views. Therefore you could insert blocks of HTML (partial views) inside a view, just by using) @Html.Action()
(并传递动作,控制器和路线值数据).这与打电话相反((and passing the action, controller and maybe route value data). This is in contrast to calling) @{Html.RenderPartial()}
,我们直接渲染局部视图.不同之处在于,第一种情况调用一个动作,然后调用一个局部视图进行渲染.模型生成和数据库查询是在控制器的操作中完成的.第二种情况直接调用局部视图.(, where we render directly a partial view. The difference is that the first case calls an action, which then calls a partial view for rendering. The model generation and database queries are done in the controller’s action. The second case directly calls the partial view.)
有关的更多信息(More information about the) Action()
扩展方法可以在以下位置找到(extension method can be found at) Phil Haack的博客(Phil Haack’s blog) .(.)
技巧19:通过缓存提高性能(Tip 19: Performance boost through caching)
在HTTP领域中,有几种性能最佳实践.如果我们想停留在简单(但非常有效)的技巧上,我们将首先关注减少请求.如果我们可以减少请求的数量,那么最终将获得更快的网页和更少的服务器负载.下一步将是减少实际计算.这可以通过对我们的算法,数据库查询等进行广泛的分析来完成,但是对于一个非常有效且健壮的解决方案,我们也可以将缓存作为目标.缓存的缺点当然在于所需的内存和内容到期.内存由ASP.NET自动处理,这少了一个问题.如果内存不足,将释放资源并需要重新计算.另一方面,我们需要考虑一个有效的到期日期,以及要精确缓存的内容.(In the world of HTTP there are several performance best practices. If we want to stay at the simple (but very effective) tips, we will first focus on request reduction. If we can reduce the number of requests we will end up with a much faster web page and less server load. The next step would be to reduce actual computation. This could be done by extensive analysis of our algorithms, database queries and such, but for a very effective and robust solution we could also aim for caching. The disadvantages of caching are of course in the required memory and the expiration of content. The memory is handled automatically by ASP.NET, which is one problem less. If memory is getting short, resources will be released and re-computation will be required. On the other side we need to think about a good expiration date, as well as what to cache exactly.)
添加缓存的最简单方法是使用(The easiest way to add caching is to use the) OutputCache
控制器的属性(因此它将应用于所有操作)或特定操作.这将导致缓存操作结果.如果ASP.NET MVC检测到正在调用的动作,它将仅显示已缓存的输出.通常,我们还将指定计算内容应存储在内存中的秒数.假设我们要将动作缓存45秒:(attribute for a controller (therefore it will be applied to all actions), or a specific action. This will result in a cached action result. If ASP.NET MVC detects the action being called, it will just display the already cached output. Usually we would also specify the number of seconds that the computed content should be stored in memory. Let’s just say we want to cache an action for 45 seconds:)
public CachedController : Controller
{
[OutputCache(Duration = 45)]
public ActionResult Index(int id)
{
/* ... */
}
}
当前指令的问题仍然在于不同的参数值(例如(The problem with the current directive is still that different parameter values (e.g.) id = 2
,(,) id = 9
,…)将被视为平等.这意味着如果操作被缓存(, …) will be treated equal. This means that if the action is cached with) id
设置为2时,与(being set to 2, the output for the same action with the) id
如果在动作仍在高速缓存中时请求,则9相同.(being 9 will be the same, if requested while the action is still in the cache.)
当然,我们可以通过使用其他参数来解决此类问题,例如(Of course we can attack such problems by using other parameters, like) VaryByParam
.因此,针对上述问题的更好解决方案是:(. A better solution for the problem above would therefore be:)
public CachedController : Controller
{
[OutputCache(Duration = 45, VaryByParam = "id")]
public ActionResult Index(int id)
{
/* ... */
}
}
我们可以使用分号分隔不同的参数名称来指定参数列表.如果要在此列表中包含每个参数,则可以使用(We can specify list of parameters by using semicolons to separate different parameter names. If we want to include every parameter in this list we could just use a) *
.现在,如果结果是本地化的,我们可能还会遇到一些麻烦.在这里,我们还必须区分此操作提供的不同语言.这可以通过使用(. Now we might still get some trouble if the result is localized. Here we must also distinguish between different languages that are served from this action. This can be implemented by using the) VaryByHeader
参数.语言在(parameter. The language is passed in the) Accept-Language
标头密钥.(header key.)
public CachedController : Controller
{
[OutputCache(Duration = 45, VaryByParam = "*", VaryByHeader = "Accept-Language")]
public ActionResult Index(int id)
{
/* ... */
}
}
有时我们想为多个动作(跨各种控制器)采取特定的设置.在这里,我们可能会陷入复制和粘贴的地狱.为了避免这种情况(并使用DRY:请勿重复),我们可以使用所谓的缓存配置文件.这些工作就像来自(Sometimes we want to take a specific setting for multiple actions (across various controllers). Here we might get into the hell of copy and paste. To avoid this (and using DRY: don’t repeat yourself) we could use the so called cache profiles. Those work like global variables from the)**web.config(web.config)**文件-专门用于缓存.让我们在配置文件中添加先前定义的输出缓存配置:(file - just specicialized for caching. Let’s add the previously defined output cache configuration in the configuration file:)
<configuration>
<!-- ... -->
<system.web>
<!-- ... -->
<caching>
<outputCacheSettings>
<outputCacheProfiles>
<add name="Cache45Seconds" duration="45"
veryByHeader="Accept-Language" varyByParam="*"/>
</outputCacheProfiles>
</outputCacheSettings>
</caching>
</system.web>
</configuration>
现在,我们可以通过引用名称来使用创建的缓存配置文件(Now we can use the created cache profile by referring to the name) Cache45Seconds
.该属性调用变为:(. The attribute call becomes:)
public CachedController : Controller
{
[OutputCache(CacheProfile = "Cache45Seconds")]
public ActionResult Index(int id)
{
/* ... */
}
}
所有这些听起来很容易而且很吸引人,但是,必须始终考虑一些规则.首先:缓存持续时间真的已经太长了吗?然后:我是否真的应该根据所有/这些参数而有所不同?最后,您应该始终考虑安全性!(This all sounds very easy and appealing, however, one must always think of some rules. First of all: Is the cache duration really already too long? Then: Should I really vary by all / those parameters? And finally you should always think about security!)从不缓存受限的输出(Never cache a restricted output).斯蒂芬`沃尔瑟(Stephen Walther)全心投入(. Stephen Walther devotes a whole) 发表到这个话题(post to this topic) .(.) 有关更多信息,请参见(More information can be found on the) 官方ASP.NET页面文章(Official ASP.NET Page article) . Scott Hanselman在他的文章中也讨论了这个话题(. Scott Hanselmann also discussed this topic in his article) 在ASP.NET中进行缓存-VaryByParam可能需要VaryByHeader(Caching in ASP.NET - VaryByParam may need VaryByHeader) .(.)
技巧20:重写控制器的方法(Tip 20: Override methods of your controllers)
有时,我们有非常简单的操作,最终只能使用以前编写的视图.无需填充任何数据,也不需要查询任何内容.尽管如此,我们最终将得到一个类似于以下内容的控制器:(Sometimes we have quite simple actions, which will just end up serving a previously written view. No data needs to be populated and nothing needs to be queried. Nevertheless we will end up with a controller that looks similar to:)
public class CustomerController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Details()
{
return View();
}
public ActionResult Help()
{
return View();
}
}
除了调用具有相同名称的视图外,很多其他操作都没有做任何事情.通过覆盖方法(That’s a lot of typing for so many actions that are not doing something besides calling the view with the same name. By overriding the method) HandleUnknownAction()
的(of the) Controller
类,我们能够将它们全部合并:(class we are able to merge them all:)
public class CustomerController : Controller
{
protected override void HandleUnknownAction(string actionName)
{
this.View(actionName).ExecuteResult(this.ControllerContext);
}
}
由于其他未知操作仍然会导致错误,因此在大多数情况下无需考虑即可使用此方法.(Since other unknown actions will result in errors anyway, this method can be used without thinking in most cases.) 该示例可以在以下位置找到(The example can be found at) 斯蒂芬`沃尔瑟(Stephen Walther)的博客(Stephen Walther’s blog) .(.)
提示21:您自己的会员资格提供者(Tip 21: Your own membership provider)
如果我们想使用更专业的会员计划,我们需要编写自己的会员提供程序.这是使用现有数据库方案(在ASP.NET成员资格方案中未设置)的唯一方法.在CodeProject上已经有一些文章可以编写我们自己的成员资格提供程序.在本技巧中,我们将详细介绍最有趣的点.(If we want to use a more specialized membership scheme we need to write our own membership provider. This is the only way to work with an existing database scheme, which is not set up in the ASP.NET membership scheme. There are already some articles on the CodeProject to write our own membership provider. In this tip we will just go into detail of the most interesting points.)
我们需要做的就是从抽象类继承(All we need to do is to inherit from the abstract class) MembershipProvider
.现在我们有了自己的实现,我们只需要为我们感兴趣的方法编写实际代码即可.(. Now that we have our own implementation we just need to write actual code for the methods that are interesting for us.)
public class MyMembershipProvider : MembershipProvider
{
/* ... */
public override bool ChangePassword(string username, string oldPassword, string newPassword)
{
/* This one should really be implemented */
}
public override MembershipUser CreateUser(string username, string password, string email,
string passwordQuestion, string passwordAnswer, bool isApproved,
object providerUserKey, out MembershipCreateStatus status)
{
/* This is also quite important */
}
public override string ResetPassword(string username, string answer)
{
/* This method could be useful */
}
public override bool ValidateUser(string username, string password)
{
/* This is certainly the most important method */
}
}
编写我们自己的会员资格提供者是一回事,但没有适当的建议(Writing our own membership provider is one thing, but without a proper) RoleProvider
相当陈旧.在这里,我们有可能实际区分用户组.(quite obsolete. Here is, where we have the possibility to actually distinguish between groups of users.)
public class MyRoleProvider : RoleProvider
{
/* ... */
public override bool IsUserInRole(string username, string roleName)
{
/* This is certainly the most important method */
}
}
该概念类似于用于(The concept is analogous to the one used with the) MembershipProvider
.我们再次从抽象类继承.现在,我们已经编写了自己的实现,我们只需要告诉运行时使用我们的提供程序即可.再次使用(. We are again inheriting from an abstract class. Now that we’ve written our own implementations we just need to tell the runtime to use our providers. This is again done with the)**web.config(web.config)**文件.以下几行将启用我们的会员资格提供者:(file. The following lines will enable our membership provider:)
<configuration>
<!-- ... -->
<system.web>
<!-- ... -->
<authentication mode="Forms">
<forms loginUrl="~/Account/LogOn" timeout="2880" />
</authentication>
<roleManager enabled="true" defaultProvider="MyRoleProvider">
<providers>
<clear/>
<add name="MyRoleProvider" type="OurNameSpace.MyRoleProvider" />
</providers>
</roleManager>
<membership defaultProvider="MyMembershipProvider">
<providers>
<clear />
<add name="MyMembershipProvider" type="OurNameSpace.MyMembershipProvider" />
</providers>
</membership>
<!-- ... --->
</system.web>
</configuration>
可以在以下位置找到有关编写自己的成员资格提供程序的非常好的教程(A really good tutorial on writing own membership providers can be found at) 诚信(The Integrity) .(.)
提示22:如何使HTTPS强制(Tip 22: How to make HTTPS mandatory)
有时我们需要确保加密以传输敏感数据.在网站传输中提供加密的唯一方法是使用HTTPS作为协议.在这里,使用SSL(安全套接字层)分发适当的密钥,SSL是用于在Web服务器和浏览器之间建立加密链接的标准安全技术.(Sometimes we need to ensure encryption for communicating sensitive data. The only way to provide encryption on the website transfers is the usage of HTTPS as protocol. Here the proper keys are distributed by using SSL (Secure Sockets Layer), which is the standard security technology for establishing an encrypted link between a web server and a browser.)
典型案例涉及在线支付系统,在线银行,身份验证,数据修改和所有其他机密使用的使用.为了确保使用HTTPS协议,我们可以使用(Typical cases involve the usage of online payment systems, online banking, authentication, modification of data and all other confidential usages. In order to ensure the usage of the HTTPS protocol we can use the) RequireHttps
属性.如果我们已经在使用安全的HTTP协议版本,则该属性将基本显示.如果我们当前正在使用HTTP的标准版本,则该属性将通过重定向到相同的操作(具有相同的参数)来响应-仅在HTTPS协议上.(attribute. The attribute will basically look if we are already using the secure HTTP protocol version. If we are currently using the standard version of HTTP, the attribute will respond with a redirect to the same action (with the same parameters) - just on the HTTPS protocol.)
[RequireHttps]
public ActionResult Login()
{
return View();
}
同样,我们也可以使用属性来保护控制器的所有动作-只需将属性放在类定义上方即可.关于调试环境中用法的更多提示.通常,我们将为有效证书付费,因为SSL不仅用于加密,而且还用于身份验证.有了大公司之一的证书,浏览器就可以始终检查与之通信的网站是否确实是它声称的网站.(Again we can also use the attribute to secure all actions of a controller - just by placing the attribute above the class definition. Just one more tip regarding the usage in debug environment. Usually we will pay for a valid certificate, since SSL is not only used for encryption, but also for authentification. With a certificate from one of the big companies, a browser can always check if the website it is communicating with, is really the website it claims to be.) 如果证书是自行生成的,则大多数浏览器将显示警告消息.因此,使用以下代码可能会比较麻烦:(If the certificate has been self generated most browsers will display warning messages. Therefore it could be less annoying to use the following code:)
#if !DEBUG
[RequireHttps]
#endif
public ActionResult Login()
{
return View();
}
这仅将HTTPS重定向用于生产版本.如果需要有关该属性的更多信息,可以(This will only use the HTTPS redirect for productive releases. If you want more information about the attribute, you can) 阅读关于StackOverflow的问题(read the question on StackOverflow) .(.)
提示23:将T4MVC用于强类型的助手(Tip 23: Use T4MVC for strongly typed helpers)
ASP.NET MVC严重依赖于字符串参数和匿名对象.两种技术都使ASP.NET MVC网页编程成为一个非常动态的过程,即使我们使用的是静态类型的语言.后者(使用匿名对象)为我们提供了更多的自由和可能性,而第一个(使用字符串作为参数)则是潜在错误的根源.(ASP.NET MVC relies heavily on string arguments and anonymous objects. Both techniques make programming ASP.NET MVC webpages a very dynamic process, even though we are using a static typed language. While the latter (using anonymous objects) gives us more freedom and possibilities, the first one (using strings as arguments) is a source for potential errors.) T4MVC模板的开发人员确实意识到了这一点,并编写了一个小程序库来对抗这种错误源.我们可以使用NuGet安装T4MVC库.我们需要做的就是在powershell中键入以下命令:(The developers of the T4MVC template did recognize this and wrote a little library to fight against this source of errors. We can install the T4MVC library by using NuGet. All we need to do is type the following command into the powershell:)
Install-Package T4MVC
现在,我们可以使用其他扩展方法.考虑以下(常规)方法调用的示例:(Now we are able to use additional extension methods. Consider the following example of a (usual) method call:)
@Html.ActionLink("Delete this entry", "Delete", new { id = Model.Id })
在这里我们称之为(Here we are calling the) ActionLink()
视图的扩展(extension of the view’s) HtmlHelper
实例.如果我们输错了(instance. If we mistype)**删除(Delete)**我们很可能会在运行时遇到异常. T4MVC库使我们能够调用以下扩展方法:(we will most likely end up with an exception during runtime. The T4MVC library enables us to call the following extension method:)
@Html.ActionLink("Delete this entry", MVC.MyControllerName.Delete(Model.Id))
现在,这看起来好多了(它像静态方法调用一样使用). T4MVC库在名称空间中生成静态类(Now this looks much better (and it is used like a static method call). The T4MVC library generates static classes in the namespace) MVC
.对于每个控制器(如类(. For every controller (like the class) MyControllerNameController
)没有() a static class without the)**-控制器(-Controller)**字符串生成.这些类确实包含静态字段和方法.如果操作方法包含参数,则将生成适当的静态方法,否则将生成字段.(string is generated. Those classes do contain static fields and methods. If an action method contains arguments, a proper static method is generated, otherwise a field is generated.)
显然,我们将能够获得智能感知和其他功能.由于具有强大的绑定力,因此我们还将在页面上直接显示异常(而不仅仅是单击链接).如果我们牢记可以预编译视图,那么我们会发现,该技巧实际上是对我们的Web应用程序更强大,更不易出错的重要补充.(Obviously we will be able to get intellisense and other features. Due to the strong binding we will also have exceptions directly on the page (and not just by clicking the link). If we keep in mind that we can pre-compile the views, we see that this tip is actually a great addition to make our web applications more robust and less error prone for production.)
其他帮助程序也已包括在内.现在我们也可以调用视图而无需将视图名称指定为字符串.考虑以下两个示例:(Other helpers have been included as well. Now we can also call views without specifying the name of the view as string. Consider the following two examples:)
//Old code snippet
return View("InvalidOwner");
//New way with T4MVC
return View(MVC.MyControllerName.Views.InvalidOwner);
//Old code snippet
return RedirectToAction("Details", new { id });
//New way with T4MVC
return RedirectToAction(MVC.MyControllerName.Details(id));
有一些缺点.一是我们将没有重构支持.由于我们可以预编译我们的观点,所以这不是一个太大的问题(但是我们必须牢记这一点!).另一个是,此代码是由Visual Studio生成的,但是VS只能生成/更新负责的文件,而不能保存.因此,更新文件后保存文件是我们的任务.(There are a few drawbacks. One is that we will not have refactoring support. Since we can pre-compile our views, this shouldn’t be too much of a problem (but we have to keep this in mind!). The other one is, that this code is generated by Visual Studio, however, VS can only generate / update the responsible file - not save it. So it is our task to the save the file once it has been updated.) 可以在有关以下内容的MSDN博客文章中找到更多信息:(More information can be found on the MSDN Blog article about) 新的和改进的ASP.NET MVC T4模板(A new and improved ASP.NET MVC T4 template) .该项目可从以下位置下载(. The project is available for download on the) NuGet主页(NuGet homepage) .(.) 谢谢(Thanks to) 奥马尔`加米尔(Omar Gamil)(Omar Gamil) 建议添加此提示.(for suggesting to add this tip.)
使用代码(Using the code)
给定的代码片段应该可以很好地编译,但是,由于某些示例基于实际的(MySQL)数据库,因此未经修改就无法运行.这些片段仅用于向您显示一个实际示例并向您提供实际代码,因此应该可以很容易地使用智能感知来深入挖掘并转到Visual Studio的定义功能.如果要设置MySQL数据库,则应调整(The given code snippets should compile fine, however, since some examples are based on actual (MySQL) databases it will not run without modification. The snippets are only given to show you an actual example and to supply you with actual code, so that it should be easily possible to dig deeper using the intellisense and go to definition features of Visual Studio. If you want to set up the MySQL database you should adjust the settings given in the)**web.config(web.config)**您的连接字符串的文件.(file with your connection string.)
兴趣点(Points of interest)
即使大多数技巧对于每个MVC开发人员都是众所周知的,我也希望一个或另一个技巧很有趣,或者至少值得在一篇文章中写下.(Even though most tips will be known for every MVC developer I hope that one or the other tip was interesting or at least worth to be written down in one article.) 如果您有一个或另一个提示要分享,请继续并在评论中发布.我将很乐意为您提供有关ASP.NET MVC的提示和技巧.(If you have one or the other tip to share then go ahead and post it in the comments. I would be more than happy to extend this article with your own tips and tricks around ASP.NET MVC.)
历史(History)
- v1.0.0(v1.0.0)|初始版本2012年10月10日(| Initial release | 10.07.2012)
- v1.0.1(v1.0.1)|修复了一些拼写错误,包括验证码控件的图片| 2012年11月11日(| Fixed some typos, included a picture of the Captcha control | 11.07.2012)
- v1.1.0(v1.1.0)|在HTTPS上添加了提示| 2012年12月12日(| Added the tip on HTTPS | 12.07.2012)
- v1.1.1(v1.1.1)|修正了有关技巧3的错字| 2012年12月12日(| Fixed a typo concerning tip 3 | 12.07.2012)
- v1.2.0(v1.2.0)|在T4MVC上添加了提示| 2012年7月15日(| Added the tip on T4MVC | 15.07.2012)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
C#4.0 C# .NET MVC ASP.NET Dev 新闻 翻译