MVC5知识点记录

IIS/ASP.NET管道

原理永远是重中之重,所以在开篇的地方,先了解一下地址栏输入网址回车之后的故事。

IIS/ASP.NET管道的流转过程

针对IIS7而言

MVC5知识点记录

不同IIS版本处理请求也不一样

IIS5

IIS 5.x 运行在进程InetInfo.exe中,进程寄宿一个World Wide Web Publishing Service(W3SVC)服务。

W3SVC主要负责HTTP请求的监听、激活管理工作进程、加载配置等。

当检测到HTTP请求,IIS根据扩展名判断请求是静态还是动态。

静态资源,IIS直接响应内容。

动态资源,根据扩展名从IIS的处理程序映射中找到ISAPI动态链接库(Dynamic Link Library,DLL)

ISAPI,是一套本地的Win32 API,是IIS和其他动态Web应用或平台之间的纽带。

ISAPI支持ISAPI扩展和ISAPI筛选,扩展处理HTTP请求的接口。筛选可以进行查看,修改,转发,拒绝。

当收到ASP.NET资源请求,ISAPI会创建ASP.NET工作进程aspnet.exe.

IIS进程与工作进程之间通过命名管道(Named Pipes)进行通信。

工作进程初始化时,CLR会加载以构建一个托管的环境。对于某个Web应用的初次请求,CLR会为其创建一个AppDomain应用程序域。

寄存在IIS5.x的所有Web应用都运行在同一个进程的不同AppDomain里面。

IIS6

IIS6针对IIS5有以下两点修改。

1. 将ISAPI动态链接库直接加载到工作进程中。

2. IIS6引用应用程序池的机制,可以为一个或多个Web应用创建一个应用程序池。以避免所有应用都在一个进程里

可以为一个或多个Web应用创建应用程序池,每个应用程序池对应一个独立的工作进程,运行在不同应用程序池中的Web引用基于进程级别的隔离机制。

IIS6创建了一个HTTP.SYS的监听器,以驱动程序的形式运行在Windows内核模式下,是TCP/IP网络子系统的一部分。

HTTP.SYS好处如下

持续监听:由于是网络驱动程序,始终处于运行状态,对用户的HTTP请求能够及时作出反应。

更好的稳定性:HTTP.SYS运行在操作系统内核模式下,并不执行任何用户代码,本身不会受到Web应用,工作进程,IIS进程的影响。

数据缓存:某个资源被频繁请求,HTTP.SYS会把响应的内容进行缓存。

W3SVC在IIS6中,从进程InetInfo.exe转移到SvcHost.exe中了。

当HTTP.SYS监听到HTTP请求,将其分发给W3SVC进行解析,获取目标应用,获得目标应用运行的程序池或工作进程。

工作进程初始化过程中,ISAPI动态库被加载,负责进行CLR的加载、应用程序域的创建和Web初始化等工作。

IIS7

引入了Windows进程激活服务(Windows Process Activation Service,WAS)分流W3SVC承载的部分功能。

W3SVC有三大功能

1.HTTP请求接受:接受HTTP.SYS监听到的HTTP请求

2.配置管理:从元数据库中加载配置信息对相关组件进行配置

3.进程管理:创建、回收、监控工作进程

其中后两个功能实现到WAS中了。提供了非HTTP协议的支持,通过监听适配器接口,抽象出针对不同协议的监听器。

对应3中监听器,他们以Windows服务的形式进行工作。

NetTcpPortSharing:为WCF提供TCP端口共享,即同一个监听端口被多个进程共享

NetTcpActivator:为WAS提供基于TCP的激活请求,包含TCP监听器和对应的监听适配器

NetPipeActivator:为WAS提供命名管道的激活请求

NetMsmqActivator:为WAS提供基于MSMQ的激活请求

不论是从W3SVC接受到的请求,还是监听器接受的请求,最终都会流转到WAS中。

IIS7分为两种模式,经典模式跟IIS6相同,集成模式是一种统一的处理管道。

将ASP.NET请求管道和IIS请求管道组合在一起。可以提供更好的性能,可以完成配置和管理的模块化。继承模式的映射都在web.config中进行处理。

ASP.NET集成

IIS与ASP.NET是两个相互独立的管道,各自具有自己的一套HTTP处理机制。两个管道通过ISAPI连通。

IIS是第一道屏障,进行必要的前期处理。IIS通过ISAPI将请求分发给ASP.NET管道。

ASP.NET在自身管道范围内完成对HTTP请求的处理,结束后再返回IIS。IIS在进行后期处理。

IIS运行在非托管的环境中,ASP.NET管道则是托管。托管环境指CLR搭建的环境,非托管环境指可以由Windows直接调用。

ASP.NET管道

HTTP.SYS接收到HTTP请求是对web应用的第一次访问,加载CLR后IIS会通过AppDomainFactory创建一个AppDomaiin。

ISApiRuntime被加载,会接管该HTTP请求。

创建一个IsapiWorkerRequest对象封装当前的HTTP请求,将此对象传递给CLR的HttpRuntime(当前应用程序的 ASP.NET 运行时服务)

此时,HTTP请求正式进入ASP.NET管道。HttpRuntime会根据对象创建HTTPContext( HTTP 请求的所有 HTTP 特定的信息)俗称 请求上下文

HttpContext创建后,HttpRuntime会利用HttpApplicationFacotry创建HttpApplication(ASP.NET 应用程序内所有应用程序对象公用的方法、属性和事件)

HttpApplication初始化过程中,ASP.NET会根据配置文件加载并初始化HttpModule(HTTP模块初始化和处置事件)

HttpApplication会在处理HTTP请求的不同阶段触发不同的事件,HttpModule通过注册事件, 将操作注入HTTP请求处理流程中。

HttpApplication

(定义对 ASP.NET 应用程序内所有应用程序对象公用的方法、属性和事件。 此类是用户在 Global.asax 文件中定义的应用程序的基类)

整个ASP.NET基础架构的核心,负责处理分发给它的HTTP请求。

第一个请求抵达后,会创建多个HttpApplication对象,存放于对象池中。

在HTTPAppliccation处理请求的不同阶段会触发不同的事件。

对于ASP.NET应用来说,HttpApplication派生于Global.asax文件,可以通过此文件,对请求处理行为进行制定。

Global.asax 采用方法名匹配,按照“Application_{事件名}”进行事件注册。

其中事件名内容,就是Application中包含的事件。例如:

protected void Application_BeginRequest(Object sender, EventArgs e)
{
Application["StartTime"] = System.DateTime.Now;
}

其中会有几个是针对整个应用程序而言,而不是针对请求。

  1. Application_Init:在每一个HttpApplication实例初始化的时候执行
  2. Application_Disposed:在每一个HttpApplication实例被销毁之前执行
  3. Application_Error:所有没有处理的错误都会导致这个方法的执行
  4. Application_Start:在程序初始化的时候执行。在Web应用程序的生命周期里就执行一次,这里只能放一些公用的信息,比如HttpApplicationState
  5. Application_End:应用程序结束时,在最后一个HttpApplication销毁之后执行。对应Application_Start,在整个生命周期里面也是只执行一次
  6. Session_Start:会话开始时执行
  7. Session_End:会话结束或过期时执行

请求事件执行顺序如下:

  1. BeginRequest:作为执行的 HTTP 管道链中的第一个事件发生,当 ASP.NET 的请求做出响应
  2. AuthenticateRequest:当安全模块已建立的用户标识时出现
  3. PostAuthenticateRequest:当安全模块已建立的用户标识时出现
  4. AuthorizeRequest:安全模块已验证用户身份验证时发生
  5. PostAuthorizeRequest:当前请求的用户已被授权时发生
  6. ResolveRequestCache:当 ASP.NET 完成授权事件以便从缓存中,跳过的事件处理程序 (例如,一个页面或 XML Web 服务) 执行的请求提供服务的缓存模块时发生。
  7. PostResolveRequestCache:ASP.NET 将绕过当前事件处理程序的执行,并允许缓存模块以处理从缓存请求时发生
  8. PostMapRequestHandler:当 ASP.NET 已映射到相应的事件处理程序的当前请求时出现
  9. AcquireRequestState:当 ASP.NET 获取与当前的请求相关联的当前状态 (例如,会话状态)
  10. PostAcquireRequestState:获取与当前的请求相关联的请求状态 (例如,会话状态) 时发生
  11. PreRequestHandlerExecute:ASP.NET 开始执行事件处理程序 (例如,一个页面或 XML Web 服务) 之前发生
  12. PostRequestHandlerExecute:当 ASP.NET 事件处理程序 (例如,一个页面或 XML Web 服务) 完成执行时发生
  13. ReleaseRequestState:ASP.NET 完成执行所有请求事件处理程序后发生。 此事件会导致状态模块保存当前的状态数据
  14. PostReleaseRequestState:当 ASP.NET 已完成执行所有请求事件处理程序和存储数据的请求状态时发生
  15. UpdateRequestCache:当 ASP.NET 完成执行事件处理程序,以便让缓存模块存储将用于为从缓存中的后续请求提供服务的响应时发生
  16. PostUpdateRequestCache:当 ASP.NET 完成更新的缓存模块和存储用于为从缓存中的后续请求提供服务的响应时发生
  17. LogRequest:ASP.NET 执行当前请求的任何日志记录之前发生
  18. PostLogRequest:当 ASP.NET 已完成处理的事件处理程序时发生 LogRequest 事件
  19. EndRequest:作为执行的 HTTP 管道链中的最后一个事件发生,当 ASP.NET 的请求做出响应

IHttpModule

(提供模块初始化和处置事件以实现类)

当请求转入ASP.NET管道时,最终负责处理该请求的是HttpHandler对象。在此之前会先加载HttpModule对象。

ASP.NET提供的很多基础功能都是通过HttpModule实现的。例如

OutputCacheModule:输出缓存功能

SessionStateModule:无状态的HTTP协议上实现基于会话的状态保持

WindowsAuthenticationModule + FormsAuthenticationModule + PassportAuthenticatinonModule:实现了三种典型的身份认证

UrlAuthorizationModule _ FileAuthorizationModule:基于URI和ACL文件的授权

IHttpHandler

(定义 ASP.NET 以异步方式处理使用自定义 HTTP 处理程序的 HTTP Web 请求而实现的协定)

对不同资源类型的请求,ASP.NET会加载不同的Handler来处理,比如aspx与asmx对应的Handler是不同的。

HttpHandler 可以配置到Web.config中。例子为svc资源的配置。

<system.web>
<httpHandlers>
<add path="*.svc" verb="*" type="System.ServiceModel.Activation.HttpHander" validate="false" />
</httpHandlers>
<compilation debug="true" targetFramework="4.6" />
<httpRuntime targetFramework="4.6" />
<authentication mode="Forms">
<forms loginUrl="~/Account/login.aspx" timeout=""></forms>
</authentication>
</system.web>

HttpHandler

自治视图

将所有与UI相关的操作糅合在一起,包括UI界面的呈现、交互的捕捉与相应、业务执行对数据的存储。这种模式称为:自治视图。

Web Form和Windows Form,都采用事件驱动的开发方式,所有与UI相关的逻辑都可以定义在后台代码中。

缺点如下

1. 重用性差:业务逻辑与UI无关,应当最大程度的被重用。但是定义在自治视图中的UI处理逻辑,完全不能重用。

2. 稳定性差:业务逻辑具有最强的稳定性,UI逻辑其次,界面呈现最差。如果将他们融合在一起,则最差稳定性决定了整体稳定性。

3. 可测试性差:任何涉及UI的组件都不易测试,用机器来模拟人对组件实施自动化测试不是一件容易的事。

MVP

MVP是一种广泛使用的UI架构模式,适用于基于事件驱动的应用框架。例如:web Form, win Form。

Model View Presenter,自然代替了MVC中的Model View Controller。

MVP三要素之间交互体现在两个方面:

Presenter与Model:Presenter对Model是单向调用。很简单

View与Presenter:采用怎样的交互方式是整个MVP的核心。分为两种模式:PV(Passive View)和SV(Supervising Controller)

PV

View难以测试的解决办法,就是让他无须测试。条件就是让View尽可能不涉及UI处理逻辑,这就是PV模式的目的。

PV是一个被动的View,定义其中的针对UI元素的操作不是由View自身主动来控制,而是被动的交给Presenter来操控。

例:需要做一个列表,一个下拉框,一个查询。

首先,我们定义Model(Employee),然后因为需要添加数据所以,我们还要定义一个Model存储库(ModelRepository)

public class Employee
{
public string Id { get; private set; }
public string Name { get; private set; }
public string Gender { get; private set; }
public DateTime BirthDate { get; private set; }
public string Department { get; private set; }
public Employee(string id, string name, string gender, DateTime birthDate, string department)
{
this.Id = id;
this.Name = name;
this.Gender = gender;
this.BirthDate = birthDate;
this.Department = department;
}
} public class EmployeeRepository
{
private static IList<Employee> employees;
static EmployeeRepository()
{
employees = new List<Employee>();
employees.Add(new Employee("", "张三", "男", new DateTime(, , ), "销售部"));
employees.Add(new Employee("", "李四", "女", new DateTime(, , ), "人事部"));
employees.Add(new Employee("", "王五", "男", new DateTime(, , ), "人事部"));
}
public IEnumerable<Employee> GetEmployees(string department = "")
{
if (string.IsNullOrEmpty(department))
{
return employees;
}
return employees.Where(e => e.Department.Equals(department)).ToArray(); ;
}
}

模型层

Repository,只是为了省略业务逻辑层。采用静态加载模拟数据。

然后,我们定义页面的视图接口,用来将操作暴露给Presenter。这里不会将控件直接暴露给Presenter,因为这样耦合性会变得很大。

我们暴露一个获取,两个设置,还有一个点击事件。

 public interface IEmployeePVView
{
IEnumerable<string> Departments { set; }
string SelectedDepartment { get; }
IEnumerable<Employee> Employees { set; }
event EventHandler DepartmentSelected;
}

视图

接下来,我们定义Presenter,因为是PV模式,所以我们在这里操作暴露的UI元素。不让UI层,进行任何逻辑。

 public class EmployeePVPresenter {
public IEmployeePVView View { get; private set; }
public EmployeeRepository Repository { get; private set; }
public EmployeePVPresenter(IEmployeePVView view) {
this.View = view;
this.Repository = new EmployeeRepository();
this.View.DepartmentSelected += OnDepartmentSelected;
} public void Initialize() {
IEnumerable<Employee> employees = this.Repository.GetEmployees();
this.View.Employees = employees;
string[] departments = new string[] { "", "采购部", "人事部", "销售部" };
this.View.Departments = departments;
} private void OnDepartmentSelected(object sender, EventArgs e)
{
string department = View.SelectedDepartment;
var employees = this.Repository.GetEmployees(department);
this.View.Employees = employees;
}
}

Presenter

最后,我们展示这个页面

 public partial class WebForm2 : System.Web.UI.Page, IEmployeePVView
{
public IEnumerable<string> Departments
{
set
{
this.DropDownListDepartments.DataSource = value;
this.DropDownListDepartments.DataBind();
}
} public IEnumerable<Employee> Employees
{
set
{
this.GridViewEmployee.DataSource = value;
this.GridViewEmployee.DataBind();
}
} public string SelectedDepartment
{
get
{
return this.DropDownListDepartments.SelectedValue;
}
} public EmployeePVPresenter Presenter { get; private set; }
public event EventHandler DepartmentSelected;
public WebForm2()
{
this.Presenter = new EmployeePVPresenter(this);
} protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
this.Presenter.Initialize();
}
} protected void ButtonSearch_Click(object sender, EventArgs e)
{
DepartmentSelected(this, e);
}
}

Page

PV模式,将所有的UI处理逻辑全部定义在Presenter上,意味着所有的UI处理逻辑都可以被测试。

但是,将所有可供操作的UI元素定义在对应接口中,对于一些复杂的富客户端应用来说。接口会变得很多,这会提升编程所需的代码量。

对于这种很复杂的View来说,我们采用SC模式。

SC

在SC模式中,Presenter不是View调用Model的中介,而是最终决定如何响应用户交互行为的决策者。

View是Presenter委派到前端的客户代理,作为客户的自然就是最终的用户。对于键鼠操作的交互请求,View会汇报给Presenter。

Presenter处理用户交互请求的流程,如果中间环节需要设计Model,它会直接发起对Model的调用。如果需要View会直接驱动View完成相应工作。

对于绑定数据,是Presenter主动推给View的。是单向操作。View本身仅仅实现了单纯的、独立的UI逻辑。

他处理的数据是Presenter实时推给它的,所以View尽可能不维护数据状态。定义在IView的接口只包含方法,而不包含属性

Presenter所需的View状态,应该在接受到View发送的用户交互请求的时候一次得到,而不需要通过View的属性去获取。

例:情况如PV模式的情况一样

Model层,一样,省略。

接口只包含方法,也就是绑定列表和绑定下拉框的方法。然后因为需要单击事件的数据,所以定义一个事件数据类进行接收。

public interface IEmployeeView
{
void BindEmployees(IEnumerable<Employee> employees);
void BindDepartments(IEnumerable<string> departments);
event EventHandler<DepartmentSelectedEventArgs> DepartmentSelected;
} public class DepartmentSelectedEventArgs : EventArgs
{
public string Department { get; private set; }
public DepartmentSelectedEventArgs(string department)
{
this.Department = department;
}
}

接口

Presenter进行操作

public class EmployeePresenter
{
public IEmployeeView View { get; private set; }
public EmployeeRepository Repository { get; private set; }
public EmployeePresenter(IEmployeeView view)
{
this.View = view;
this.Repository = new EmployeeRepository();
this.View.DepartmentSelected += OnDepartmentSelected;
} public void Initialize()
{
IEnumerable<Employee> employees = this.Repository.GetEmployees();
this.View.BindEmployees(employees);
string[] departments = new string[] { "", "采购部", "人事部", "销售部" };
this.View.BindDepartments(departments);
} private void OnDepartmentSelected(object sender, DepartmentSelectedEventArgs e)
{
string department = e.Department;
var employees = this.Repository.GetEmployees(department);
this.View.BindEmployees(employees);
}
}

Presenter

页面进行方法实现,和事件绑定

<div id="page">
<div>
<asp:DropDownList ID="DropDownListDepartments" runat="server"></asp:DropDownList>
<asp:Button ID="ButtonSearch" runat="server" Text="查询" OnClick="ButtonSearch_Click" />
</div>
<asp:GridView ID="GridViewEmployees" runat="server" AutoGenerateColumns="false" Width="100%">
<Columns>
<asp:BoundField DataField="Name" HeaderText="姓名" />
<asp:BoundField DataField="Gender" HeaderText="性别" />
<asp:BoundField DataField="BirthDate" HeaderText="出生日期" DataFormatString="{0:dd/MM/yyyy}" />
<asp:BoundField DataField="Department" HeaderText="部门" />
</Columns>
</asp:GridView>
</div>

DIV

public partial class WebForm1 : System.Web.UI.Page, IEmployeeView
{
public EmployeePresenter Presenter { get; private set; }
public event EventHandler<DepartmentSelectedEventArgs> DepartmentSelected;
public WebForm1()
{
this.Presenter = new EmployeePresenter(this);
}
protected void Page_Load(object sender, EventArgs e)
{
if (!this.IsPostBack)
{
this.Presenter.Initialize();
}
} protected void ButtonSearch_Click(object sender, EventArgs e)
{
string department = this.DropDownListDepartments.SelectedValue;
DepartmentSelectedEventArgs eventArgs = new DepartmentSelectedEventArgs(department);
if (DepartmentSelected != null)
{
DepartmentSelected(this, eventArgs);
}
} public void BindEmployees(IEnumerable<Employee> employees)
{
this.GridViewEmployees.DataSource = employees;
this.GridViewEmployees.DataBind();
} public void BindDepartments(IEnumerable<string> departments)
{
this.DropDownListDepartments.DataSource = departments;
this.DropDownListDepartments.DataBind();
}
}

后台

MVC

记录一个MVC框架流程和模拟的MVC框架实现。具体代码参考:https://coding.net/u/chenxygx/p/MVCDemo/git

处理请求流程

1. MVC利用路由系统对请求URL进行解析并得到Controller和Action名称以及其他数据。

2. 根据Controller名称反射出其类型,并将其激活。

3. 利用Action名称解析出定义在目标Controller类型中对应的方法,然后执行Controller对象的这个方法。

4. Action方法可以在执行过程中直接对当前请求进行相应,也可以返回一个ActionResult对象来相应请求。

5. 如返回ActionResult对象,会执行返回的对象来当作当前请求作最终的相应。

具体操作完成步骤,写在代码注释中。

路由

当MVC接受到抵达的请求后,首要任务就是通过当前的HTTP请求的解析得到目标的Controller和Action名称。

MVC中会定义一个全局路由表RouteTable,里面的Route对象对应一个路由模板。路由模板中定义Controller和Action的变量形式。

对于HTTP请求,会遍历路由表RouteTable,找到URL匹配模式,解析核心路由数据 RouteData。

Controller

将得到的RouteData对象包含的目标Controller名称,根据其名称进行激活对应的Controller对象

Action

Controller的Execute方法主要作用在于执行目标Action方法。如果返回ActionResult对象,还需要执行该对象来响应。

完整流程

Global 注册路由信息RouteTable,加载Controller工厂ControllerBuilder

当请求到来时

UrlRoutingModule继承自IHttpModule接口进行注册。会利用RouteTable表示路由表针对当前请求实施路由解析。

解析完成后会返回RouteData对象,包含Controller和Action名称。通过RouteData对象的RouteHandler属性得到匹配Route对象采用的RouteHandler对象。

此用例中是MvcRouteHandler。调用这个对象的GetHttpHandler方法得到一个MvcHandler对象。

UrlRouteingModule调用当前HTTP上下文的MapHttpHandler方法得到HttpHandler对象实施映射。此时MvcHandler接管当前请求。

MvcHandler用来处理当前请求,会利用RouteData对象得到目标Controller名称,并借助于注册的ControllerFactory来激活对应的Controller对象。

对象激活后,Excute方法被MvcHandler调用。执行时会调用ActionInvoker对象的InvokeAction方法来执行目标Action方法并对当前的请求予以响应。

采用ActionInvoker是一个ControllerActionInvoker对象,当InvokeAction方法被执行的时候,

会利用注册的ModelBinder采用Model绑定的方式生成目标Action方式的参数列表。并利用ActionExecutor对象以表达式树的方式执行目标Action方法。

Action方法执行之后总是会返回一个ActionResultl的Actionfangfa ,MVC是会将执行的结果转换成一个ActionResult对象。

ControllerActionInvoker会通过执行此ActionResult对象来对请求做最终的响应。

路由

路由可以根据URL进行资源解析。不是专属于MVC的,可以在Web Forms应用中使用。帮助实现请求地址与物理文件的分离。

public class Global : HttpApplication
{
void Application_Start(object sender, EventArgs e)
{
RouteValueDictionary value = new RouteValueDictionary { { "Name", "Chenxy" }, { "Id", } };
RouteTable.Routes.MapPageRoute("Defual", "{Name}/{Id}", "~/Test1.aspx", true, value);
}
} public partial class Test1 : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
string name = RouteData.Values["Name"] as string;
string id = RouteData.Values["Id"] as string;
}
}

路由机制

路由系统有两个功能:

1)输入URL并判断请求是想要那个控制器和动作。

2)输出URL是输出一个超链接,用户点击以后变为输入URL。

URL路由是如下模式:{controller}/{action}(控制器和方法)

当有输入请求的时候,路由的职责是将这个请求与一个模式进行匹配。然后从URL中为定义的片段变量提供值。

片段变量指:花括号内名称。上面的controller与action就是片段变量。

RouteConfig配置文件(由Global.asax进行运行)

public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}

MapRoute返回的是Route对象继承RouteBase。其中,RouteExistingFiles=false,表示可以使用物理访问模式。

路由顺序

路由顺序非常重要,路由系统会试图根据最先被定义的路由模式来匹配一个输入URL。只有不匹配的时候,才会往下进行处理。

故此,需要优先匹配比较具体的路由。模糊的路由尽量放在后方。

路由配置

路由配置还可以进行多种多样的操作。

 public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
//默认路由,其中如果没有输入action,那么默认路由找Index
routes.MapRoute("LuYou", "{controller}/{action}", new { action = "Index" });
//静态URL,可以创建固定前缀的路由。也可声明static{controller}/{action}。
routes.MapRoute("JingTai", "static/{controller}/{action}", new { controller = "Home", action = "Index" });
//自定义变量id,自定义路由可以作为参数运用。Get(string id)
routes.MapRoute("Id", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = "DefaultId" });
//可选URL片段
routes.MapRoute("KeXuan", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = UrlParameter.Optional });
//可变长路由,*号作为前缀表示变量可变长。无论有多少片段,均可付给可变长变量。使用/进行分割
routes.MapRoute("Long", "{controller}/{action}/{*catchall}");
//优先命名空间,其中MvcDemo为命名空间。当检测此条路由时,会优先查找命名空间。
routes.MapRoute("namespace", "{controller}/{action}", new { controller = "Home", action = "Index" }, new[] { "MvcDemo" });
//约束路由,使用正则表达式约束,开头为H字母的URL
routes.MapRoute("YueShu", "{controller}/{action}", new { controller = "Home", action = "Index" }, new { controller = "^H.*" });
//约束路由组,方法为Index或者About
routes.MapRoute("YueShus", "{controller}/{action}",
new { controller = "Home", action = "Index" },
new { controller = "^H.*", action = "^Index$|^About$" });
//约束请求方法
routes.MapRoute("YueShu2", "{controller}/{action}",
new { controller = "Home", action = "Index" },
new { controller = "^H.*", httpMethod = new HttpMethodConstraint("Get") });
//使用内置类型约束。明细:https://msdn.microsoft.com/zh-cn/library/system.web.mvc.routing.constraints(v=vs.118).aspx
routes.MapRoute("YueShu3", "{controller}/{action}",
new { controller = "Home", action = "Index" },
new { controller = "^H.*", httpMethod = new HttpMethodConstraint("Get"), id = new RangeRouteConstraint(, ) });
}
}

属性路由

属性路由是MVC5特有的一种路由形式。配置大于约定的一种形式,与上面的路由模式相违背。Net Core就采用的属性路由来进行的配置。

在项目中,可以同时使用两种方法,并且没有不良的效果。

默认情况下,属性路由是禁用状态,首先需要开启属性路由。在RouteConfig.cs文件,添加

 public static void RegisterRoutes(RouteCollection routes)
{
routes.MapMvcAttributeRoutes();
}

这样就会开启自定义路由。

使用属性路由

使用属性路由主要是运用,Route特性

  [Route("api/Todo/GetAll")]
public IEnumerable<TodoItem> GetAll()
{
return TodoItems.GetAll(); ;
} [Route("api/Todo/GetById")]
public IActionResult GetById(string id)
{
var item = TodoItems.Find(id);
if (item == null)
{
return NotFound();
}
return new ObjectResult(item);
}

也可以使用片段变量创建路由

[Route("api/[controller]/{id:string}")]

路由前缀

在控制器上方进行Route可以定义路由前缀

[Route("api/[controller]")]
public class TodoController : Controller
{
public ITodoRepository TodoItems { get; set; }
public TodoController(ITodoRepository todoItems)
{
TodoItems = todoItems;
} public IEnumerable<TodoItem> GetAll()
{
return TodoItems.GetAll();
} [Route("String")]
public string GetMessage()
{
return "ces";
}
}

如上的路由配置,其中前缀就是 api/[controller]。在这个前缀下,输入String,就会跳转到GetMessage方法。例如:http://localhost:12109/api/Todo/String

输出URL

输出路由主要是调用Html.ActionLink辅助方法来进行操作。

<div>
@Html.ActionLink("当前控制器的方法", "Index")
@Html.ActionLink("其他控制器的方法", "Index", "Home")
@Html.ActionLink("传递额外变量", "Index", new { id = })
@Html.ActionLink("指定区域", "Index", new { area = "About" })
@Html.ActionLink("指定CSS", "Index", new { @class = "Css" })
</div>

区域

MVC框架可以将应用程序组成一个区域,每个区域代表应用程序的一个功能段。

可以通过右键,添加对应区域来进行操作。其中路由使用静态URL来进行匹配。

POST和GET

GET请求应用于所有只读信息检索,POST请求被用于各种修改应用程序状态的操作。

GET请求是可设定地址的,所有信息都包含在URL中,因此他可以设为书签并链接到这些地址。

控制器

控制器的作用是封装应用程序逻辑,负责处理输入请求、执行域模型上的操作,并选择渲染给用户的视图。

控制器的创建均已Controller结尾。

接受数据

控制器获取URL参数有三个主要的途径:

1)通过上下文对象提取。

2)通过参数提取。

3)调用框架的模型绑定特性。

上下文对象

Controller类提供了一组便利属性,可以用来访问请求的相关信息。

Request.QueryString  Get变量
Request.Form  POST变量
Request.Cookies  Cookies
Request.HttpMethod  用于请求的方法(POST or Get)
Request.Headers  HTTP报头
Request.Url  请求的URL
Request.UserHostAddress  请求的用于IP地址
RouteData.Route  Routes条目
RouteData.Values  路由参数
HttpContext.Application  状态库
HttpContext.Cache  缓存库
HttpContext.Items  请求的状态库
HttpContext.Session  会话状态库
User  用户认证信息
TempData  用户临时存储数据

例:获取Post请求的Id变量。Rquest.Form["Id"]

动作参数

对应固定获取的变量,可以使用 Get(string id)。这种参数变量,来进行简化。

输出

控制器在完成一个请求后,需要生成一个响应。

如果想发送HTML响应,那么必须创建HTML数据,然后通过Response.Write方法发送到客户端。

如果想跳转另一个URL,那么需要调用Response.Redirect方法。

动作结果

MVC框架通过使用动作结果把指明意图和执行意图分离开来。

不直接使用Response对象,而是返回一个派生于ActionResult类的对象。它描述了控制器响应要完成的功能。

ActionResult派生类如下

 类型 | 辅助器方法 | 描述
ViewResult | View | 返回指定的或默认的视图模板
PartialViewResult | PartialView | 返回指定的或默认的分部视图模板
RedirectToRouteResult | [RedirectToAction\RedirectToActionPermanent\RedirectToRoute\RedirectToRoutePermanent] | 重定向发送给一个动作方法或特定的路由条目
RedirectResult | [Redirect\RedirectPermanent] | 重定向发送给一个特定的URL
ContentResult | Content | 返回原始的文本数据给浏览器
FileResult | File | 将二进制数据流直接传送给浏览器
JsonResult | Json | 将对象序列化成Json格式,发送给浏览器
JavaScriptResult | JavaScript | 发送一个由浏览器执行的JavaScript源代码片段
HttpUnauthorizedResult | None | 引发认证机制要求访问者进行登录
HttpNotFoundResult | HttpNotFound | 返回404
HttpStatusCodeResult | None | 返回指定HTTP码
EmptyResult | None | 什么也不干

例:返回视图。View("Home")

会搜索一下位置,进行查找Home这个视图

 /Views/控制器/视图.aspx
/Views/控制器/视图.ascx
/Views/控制器/视图.cshtml
/Views/控制器/视图.vbhtml
/Views/Shared/视图.aspx
/Views/Shared/视图.ascx
/Views/Shared/视图.cshtml
/Views/Shared/视图.vbhtml

通过路径指定视图,可以提供一个明确的路径来绕过搜索阶段。

View("~/View/Other/Index.cshtml")

View同时可以传递数据实体给视图。View(DateTime.Now)   视图中定义 @model DateTime,就可以使用传输的实体对象了

保留临时数据:TempData["Message"] ,数据只可使用一次。

返回HTTP错误码:return new HttpStatusCodeResult(404,"消息")

过滤器

过滤器把附加逻辑注入到MVC框架的请求处理中,实现了交叉关注。指可以用整个应用程序,而又不适合放置在某个局部位置的功能。例如:授权、登录、缓存

过滤器是.Net的特性,对请求处理管道添加了额外的步骤。

MVC支持5中不同的过滤器

 类型 | 接口 | 默认实现 | 描述
认证过滤器 | IAuthenticationFilter | N/A | 最先运行优先于其他过滤器和动作
授权过滤器 | IAuthorizationFilter | AuthorizeAttribute | 第二个运行在认证后
动作过滤器 | IActionFilter | ActionFilterAttribute | 在动作方法之前及之后
结果过滤器 | IResultFilter | ActionFilterAttribute | 在结果之前及之后
异常过滤器 | IExceptionFilter | HandleErrorAttribute | 在抛出异常时运行

过滤器可以写在控制器和方法上面

授权过滤器

自定义授权过滤器可以派生AuthorizeAttribtue。判断Session是否保存,并且添加跳转操作。登录页只需要保存对应的Session即可。

 public class UserAuthorizationAttribute : ActionFilterAttribute
{
public static readonly string CookieUserName = "username";
public static readonly string ManageLoginUrl = "/home/login"; public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//排除过滤器
var actionNoCompress = filterContext.ActionDescriptor.GetCustomAttributes(typeof(NoCompressAttribute), false);
var methodNoCompress = filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof(NoCompressAttribute), false);
if (actionNoCompress.Length == || methodNoCompress.Length == )
{
return;
} //用户授权验证
if (filterContext.HttpContext.Request.Cookies[CookieUserName] == null ||
filterContext.HttpContext.Session == null ||
filterContext.HttpContext.Session[filterContext.HttpContext.Session.SessionID] == null)
{
//跳转到登录界面
filterContext.Result = new RedirectResult(ManageLoginUrl);
}
else
{
var cookieUserName = filterContext.HttpContext.Request.Cookies[CookieUserName].Value;
var currentUserSession = filterContext.HttpContext.Session[filterContext.HttpContext.Session.SessionID] as UserSession;
if (string.IsNullOrEmpty(cookieUserName) || currentUserSession == null)
{
filterContext.Result = new RedirectResult(ManageLoginUrl);
}
else
{
if (string.Compare(cookieUserName, currentUserSession.UserName, StringComparison.CurrentCultureIgnoreCase) != ||
String.Compare(currentUserSession.LoginIpAddress, filterContext.HttpContext.Request.UserHostAddress, StringComparison.CurrentCultureIgnoreCase) != )
{
filterContext.Result = new RedirectResult(ManageLoginUrl);
}
}
} base.OnActionExecuting(filterContext);
}
}

授权过滤器,使用全局变量。可以设置某几个方法,不进行授权。

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class NoCompressAttribute : Attribute
{
}

使用内建过滤器

AuthorizeAttribute属性有两个:

名称 | 类型 | 描述
Users | string | 一个逗号分隔的用户名列表,允许这些用户访问
Roles | string | 一个逗号分隔的角色列表。用户必须至少是这些角色之一

使用 [Authorize(Users="admin")] 限制登录验证,使用 [AllowAnonymous] 允许匿名访问

授权过滤器如验证失败,会返回一个401错误。

需要在配置文件中,填写错误返回地址:

<system.web>
<authentication mode="Forms">
<forms loginUrl="~/Account/LogOn" timeout=""/>
</authentication>
</system.web>

然后在登录页面,对用户进行验证,注册到过滤器中

 FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);

添加此句则可以将用户添加到权限验证中。

FormsAuthentication.SignOut();

注销验证

创建角色

以上方法是创建用户,内建的授权过滤器可以指定访问角色。[Authorize(Users = "Chenxy",Roles = "Admin")]

FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(, "Chenxy", DateTime.Now, DateTime.Now.AddMinutes(), true, "Admin");
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket));
cookie.HttpOnly = true;
HttpContext.Response.Cookies.Add(cookie);

认证过滤器

认证过滤器是MVC5的新属性,对控制器和动作如何验证用户提供了细粒度控制。

AuthenticationChallengeContext属性有两个:

名称 | 描述
ActionDescriptor | 返回描述动作方法的ActionDescriptor,运用过滤器
Result | 设置表示认证质疑结果的ActionReult

异常过滤器

IExceptionFilter,当一个未处理异常出现时,OnException方法被调用。参数是ExceptionContext对象,派生于ControllerContext。

ControllerContext是控制器上下文,提供了许多属性

 名称 | 类型 | 描述
Controller | ControllerBase | 返回请求的控制器对象
HttpContext | HttpContextBase | 提供对请求细节的访问,以及对响应的访问
IsChildAction | bool | 是否子动作
RequestContext | RequestContext | 提供对HttpContext和路由数据的访问
RouteData | RouteData | 返回请求的路由数据

ExceptionContext额外属性有

 名称 | 类型 | 描述
ActionDescriptor | ActionDescriptor | 提供动作方法的细节
Result | ActionResult | 动作方法的结果
Exception | Exception | 异常
ExceptionHandled | bool | 是否已处理过此异常

异常过滤器最简单的办法是派生FilterAttribute

public class RangeExceptionAttribute : FilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
if (!filterContext.ExceptionHandled && filterContext.Exception is ArgumentException)
{
filterContext.Result = new RedirectResult("~/Content/RangeErrorPage.html"); //返回静态页
string val = (filterContext.Exception as ArgumentException).Message; //返回动态视图
filterContext.Result = new ViewResult { ViewName = "RangeError", ViewData = new ViewDataDictionary<string>(val) };
filterContext.ExceptionHandled = true;
}
}
}

使用方法同上

使用内建过滤器

Web.Config添加过滤器属性

<customErrors mode="On" defaultRedirect="~/Content/RangeErrorPage.html"></customErrors>

然后在方法上添加注解

[HandleError(ExceptionType=typeof(ArgumentOutOfRangeException),View = "RangeError")]

动作过滤器

接口定义了两个方法,OnActionExecuting方法在动作方法调用之前调用。OnActionRexcuted之后调用。

派生FilterAttribute最方便的方法。

调用方法同上,只需要在方法中写处理内容

结果过滤器

接口定义了两个方法,OnResultExcuting方法在动作方法调用之前调用。OnResultExecuted之后调用。

派生FilterAttribute最方便的方法。

调用方法同上,只需要在方法中写处理内容

组合动作过滤器和结果过滤器

public class ActionFilterAttribute : FilterAttribute, IActionFilter, IResultFilter
{
public void OnActionExecuted(ActionExecutedContext filterContext)
{
throw new NotImplementedException();
} public void OnActionExecuting(ActionExecutingContext filterContext)
{
throw new NotImplementedException();
} public void OnResultExecuted(ResultExecutedContext filterContext)
{
throw new NotImplementedException();
} public void OnResultExecuting(ResultExecutingContext filterContext)
{
throw new NotImplementedException();
}
}

无注解过滤器

可以在控制器方法中,重写过滤器特性。直接overried即可找到对应的接口方法。

全局过滤器

可以设置全局过滤器,应用于所有方法。

public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
}

可以在此处进行特性注入。

视图

Razor引擎支持分段概念,让你能够提供一个布局中的内容区域。

使用@RenderSection("Header")可以定义填充区域

使用@section Header{} 可以实际填充此区域

使用@RenderBody()可以定义继承母版页的页面所有区域外的内容。

如视图页面需要继承布局页,使用 Layout

强类型视图,需要在上方添加对应Model。@model WebServices.Models.Reservation

视图中,可以使用ViewContext上下文

 名称 | 描述
Controller | 返回处理当前请求的IController实现
RequestContext | 返回当前请求的细节
RouteData | 为当前请求返回的路由数据
TempData | 返回和请求相关的临时数据
View | 返回视图实现
ViewBag | 返回视图包
ViewData | 返回视图模型数据字典

强类型视图

在视图的上方填写:@model 可以定义强类型视图。

针对公用的命名空间,可以用全局配置,来制定固有的命名空间。

web/view/web.config
<system.web.webPages.razor>
<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
<add namespace="MvcDemo.Models" />
</namespaces>
</pages>
</system.web.webPages.razor>

分部视图

如需在应用程序中多个不同的地方,使用同样的Razor标签和HTML标记片段。分部视图是很好的办法。

需要在页面中引用,@Html.Partial("MyParital")

分部视图也可定义强类型,并通过调用参数进行传递实体数据。

子动作

子动作是通过视图调用的动作方法。当你希望将某种控制器逻辑应用于多个地方时,子动作可以让你避免重复的控制器逻辑。

使用 [ChildActionOnly] 可以标记为子动作。防止用户作为用户请求的结果。

辅助器

内联辅助器

可以在视图中进行定义

@helper ListArrayItems(string[] items)
{
foreach(string str in items)
{
<b>@str</b>
}
}

@ListArrayItems(ViewBag.Fruits) 使用

外部辅助器

可以使用扩展方法的形式,来设置外部辅助器。在页面中引用即可。

Go(this HtmlHelper html)

内建辅助器

详细参考:https://msdn.microsoft.com/zh-cn/library/system.web.mvc.htmlhelper(v=vs.118).aspx

@Html.BeginForm(){}

输入辅助器

Html.CheckBox("myCheckbox",false)。根据属性名称来创建辅助器,可以自动绑定数据,检查位置如下:

ViewBag.DataValue

@Model.DataValue

强类型辅助器

Html.CheckBox(m=>m.DataValue)

创建Select元素

Html.DropDownListFor(x=>x.Gender,new SelectList(new[] {"",""}))

或者使用反射

new SelectList(Enum.GetNames(typeof(Role)))

模板辅助器

模板辅助器可以生成特定的HTML标签

 辅助器 | 示例 | 描述
Display | Html.Display("FirstName") | 渲染指定模型属性的只读视图
DisplayFor | Html.DisplayFor(m=>m.FirstName) | 强类型版本
Editor | Html.Editor("FirstName") | 渲染指定模型属性的编辑视图
EditorFor | Html.EditorFor(m=>m.FirstName) | 强类型版本
Label | Html.Label("FirstName") | 渲染Label元素,显示属性名称
LabelFor | Html.LabelFor("FirstName") | 强类型版本

显示属性名称和属性值的列,应写为

<div>
@Html.LabelFor(m => m.ClientName)
@Html.DisplayFor(m => m.ClientName)
</div>

整体模板辅助器

 辅助器 | 示例 | 描述
DisplayForMmodel | Html.DisplayForModel() | 渲染整个模型对象的只读视图
EditorForModel | Html.EditorForModel() | 渲染整个模型对象的编辑器元素
LabelForModel | Html.LabelForModel() | 将模型对象的名称渲染成一个label元素

例子:显示一个修改页面

<div class="form-group">
@Html.LabelForModel()
@using (Html.BeginForm("Add", "Home"))
{
@Html.EditorForModel()
<button type="submit" class="btn btn-primary">Save</button>
}
<div>
@Html.LabelFor(m => m.ClientName)
@Html.DisplayFor(m => m.ClientName)
</div>
</div>

因整体模板生成的页面,是自动感应所有字段并显示。需要通过特性来控制,属性的显示与显示方式。

模型元数据

模型元数据通过C#注解属性来表示,通过注解属性及其参数,给视图辅助器提供一系列命令。

控制可见性

使用HiddenInputAttribute来控制属性的隐藏。详细参见:https://msdn.microsoft.com/zh-cn/library/system.web.mvc(v=vs.118).aspx

 public class Reservation
{
[HiddenInput]
public int ReservationId { get; set; }
public string ClientName { get; set; }
public string Location { get; set; }
}

此时使用EditorForModel 与 EditorFor 就会将此属性改为只读。

如果想完全隐藏,使用 [HiddenInput(DisplayValue = false)]

如果想禁止创建,使用[ScaffoldColumn(false)]。注意,此属性不会将内容进行生成。而HiddenInput是生成隐藏域。

控制属性名

可以使用DisplayName来控制显示的属性名。详细参见:https://msdn.microsoft.com/zh-CN/library/system.componentmodel(v=vs.110).aspx

也可以使用Display(Name)来控制显示的属性名。两者的区别是,DisplayName可以控制实体类的名字。

Display(Name)的命名空间,有很多类似的控制特性:https://msdn.microsoft.com/zh-cn/library/system.componentmodel.dataannotations(v=vs.110).aspx

 public class Reservation
{
[HiddenInput(DisplayValue = false)]
public int ReservationId { get; set; }
[DisplayName("客户名称")]
public string ClientName { get; set; }
[Display(Name = "位置")]
public string Location { get; set; }
}

控制显示值的类型

使用DataType注解可以控制参数值的类型。

[DataType(DataType.Text)]  其中的枚举有多种定义。

控制渲染模板

使用UIHint注解可以控制渲染模板的类型。当与编辑辅助器一起使用的时候,就可以控制渲染的HTML元素了

[UIHint("String")]
public string ClientName { get; set; }

内建MVC框架视图模板

 值 | 编辑辅助器 | 视图辅助器
Boolean | 渲染一个复选框 | 渲染一个只读复选框
Collection | 为IEnumerable中的每个元素渲染一个相应的模板 | 同编辑辅助器
Decimal | 渲染一个单行文本框,数据值格式化,显示两位小数 | 格式化两位小数的数据值
DateTime | 渲染datetime标签属性 | 渲染DateTime变量的完整值
Date | 渲染date标签属性 | 渲染DateTime日期成分
EmailAddress | 渲染一个单行文本框 | 渲染一个mailto的URL
HiddenInput | 创建隐藏的input元素 | 创建隐藏元素
Html | 渲染单行文本框 | 超链接
MultilineText | 多行文本框 | 数据值
Number | number的input元素 | 数据值
Object | 自动推算 | 自动推算
Password | 密码单行文本 | 明文字符
String | 单行文本框 | 数据值
Text | 等同String | 等同String
Tel | tel的input元素 | 数据值
Time | time的input元素 | DateTime变量的时间成分
Url | 单行文本框 | 超链接
Enum | 枚举 | 枚举

控制分部视图渲染

很多时候,实体是由软件自动生成的。这个时候,如果长期写这种特性会有些繁琐。

针对这种情况,可以创建一个分部视图,由分部视图进行定义,然后关联主要的视图。

[MetadataType(typeof(PersonMetaData))]
public partial class Reservation
{}

URL

创建基本链接和URL

 描述 | 示例 | 输出效果
相对于应用程序的URL | Url.Content("/Content/Site.css") | /Content/Site.css
链接到指定的动作/控制器 | Html.ActionLink("My Link","Index","Home") | <a href="/">My Link</a>
动作URL | Url.Action("GetPeople","People") | /People/GetPeople
使用路由数据的URL | Url.RouteUrl(new{controller="People",action="GetPeople"}) | /People/GetPeople
使用路由数据的链接 | Html.RouteUrl("My Link",new{controller="People",action="GetPeople"}) | <a href="/People/GetPeople">My Link</a>
链接到指定的路由 | Html.RouteLink("My Link","FormRoute",new{controller="People",action="GetPeople"}) | <a href="/app/forms/People/GetPeople">My Link</a>

渐进式Ajax

Ajax首先需要修改Web.Config 在,appSettings 添加 <add key="UnobtrusiveJavaScriptEnabled" value="true" />

然后需要添加 Microsoft.jQuery.Unobtrusive.Ajax nuget包

最后需要添加对应的js引用,<script src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script>

渐进式Ajax核心在于Ajax.BeginForm辅助器,可以接受一个AjaxOptions对象。

AjaxOptions类定义详细参考:https://msdn.microsoft.com/zh-cn/library/system.web.mvc.ajax.ajaxoptions(v=vs.118).aspx

@using Chenxy.Mvc.Controllers
@model string
@{
ViewBag.Title = "GetPeople";
AjaxOptions ajaxOpts = new AjaxOptions
{
UpdateTargetId = "tableBody",
LoadingElementId = "loading",
LoadingElementDuration = ,
Url = Url.Action("GetPeopleData"),
Confirm = "您是否要查询"
};
}
<script src="~/Scripts/jquery-1.10.2.min.js"></script>
<script src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script>
<h2>GetPeople</h2>
<table>
<thead>
<tr>
<th>First</th>
<th>Role</th>
</tr>
</thead>
<tbody id="tableBody">
@Html.Action("GetPeopleData", new { selectedRole = Model })
</tbody>
</table>
@using (Ajax.BeginForm("GetPeopleData", ajaxOpts))
{
@Html.DropDownList("selecedRole", new SelectList(new[] { "All" }.Concat(Enum.GetNames(typeof(Role)))))
<button type="submit">Sub</button>
}
<div id="loading" class="load" style="position:absolute; top:0; left:0; right:0; bottom:0; z-index:9; opacity:0.8; vertical-align: middle; text-align: center; background-color:currentColor; display:none;">
<img alt="" src="" id="img" style="height:99%; visibility:hidden;">
<img src="~/Images/loding.gif" style="opacity:0.7" />
</div>

GetPeople

@using Chenxy.Mvc.Controllers
@model IEnumerable<Person>
@foreach (Person p in Model)
{
<tr>
<td>@p.FirstName</td>
<td>@p.Role</td>
</tr>
}

GetPeopleData

public enum Role
{
Admin,
User,
Guest
}
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Role Role { get; set; }
}
public class HomeController : Controller
{
private Person[] personData = {
new Person { FirstName="Adam", LastName="Freeman",Role=Role.Admin },
new Person { FirstName="Jacqui", LastName="Freeman",Role=Role.User },
new Person { FirstName="John", LastName="Freeman",Role=Role.User },
new Person { FirstName="Anne", LastName="Freeman",Role=Role.Guest },
};
public PartialViewResult GetPeopleData(string selecedRole = "All")
{
IEnumerable<Person> data = personData;
if (selecedRole != "All")
{
Role selected = (Role)Enum.Parse(typeof(Role), selecedRole);
data = personData.Where(p => p.Role == selected);
}
return PartialView(data);
} public ActionResult GetPeople(string selectedRole = "All")
{
return View((object)selectedRole);
}
}

Conotroller

添加Url可以进行降级,以免浏览器禁用javascript,无法发送。

Ajax.ActionLink,可以创建Ajax的超链接。点击以后,也会异步执行Ajax。

使用Ajax回调

支持四个回调方法,这四个方法均可执行一个Javascript函数。

属性 | jQuery事件 | 描述
OnBegin | beforeSend | 发送之前调用
OnComplete | commplete | 成功时调用
OnFailure | error | 失败时调用
OnSuccess | success | 完成时调用,不管成功失败

通过OnSuccess,也可以使用Json进行数据插入操作。

首先需要修改控制器,让他返回Json数据。

可以使用Json方法,进行序列化操作。

public ActionResult GetPeopleData(string selectedRole = "All")
{
IEnumerable<Person> data = personData;
if (selectedRole != "All")
{
Role selected = (Role)Enum.Parse(typeof(Role), selectedRole);
data = personData.Where(p => p.Role == selected);
}
if (Request.IsAjaxRequest())
{
var formattedData = data.Select(p => new
{
FirstName = p.FirstName,
LastName = p.LastName,
Role = p.Role.ToString()
});
return Json(formattedData, JsonRequestBehavior.AllowGet);
}
else
{
return PartialView(data);
}
}

设置返回Json,然后添加调用方法

<script type="text/javascript">
function processData(data) {
var target = $("#tableBody");
target.empty();
for (var i = 0; i < data.length; i++) {
var person = data[i];
target.append("<tr><td>" + person.FirstName + "</td><td>" + person.LastName + "</td><td>" + person.Role + "</td></tr>");
}
}
</script>

模型绑定

延迟加载

在model层中,的实体类或集合前面添加 virtual。会启动延迟加载。

public class Metric
{
public int MetricId { get; set; } public string Type { get; set; } public virtual ICollection<Goal> Goals { get; set; } public virtual ICollection<GroupGoal> GroupGoals { get; set; }
}

默认模型绑定器

参数绑定,会根据一定顺序来进行查找绑定。

 源 | 描述
Request.Form | form表单元素提供
RouteData.Values | 应用路由提供
Request.QueryString | URL字符串数据
Request.Files | 上传文件

自定义前缀

当你希望将模型绑定到另一个对象的时候,比如说,模型类有另一个模型类的实体。

可以使用,[Bind(Prefix="HomeAddress")] 定义在参数旁边。其中HomeAddress,为模型类中另一个模型类的属性名称

public ActionResult DisplaySummary([Bind(Prefix="HomeAddress")]AddressSummary summary)
{}

选择绑定

如果你不希望模型中某个值被指定,可以进行选择性绑定。

使用Exclude可以排除一个属性,Include可以指定一个属性。

public ActionResult DisplaySummary([Bind(Prefix="HomeAddress",Exclude="Country")]AddressSummary summary)
{} public ActionResult DisplaySummary([Bind(Include="City")]AddressSummary summary)
{}

模型验证

当你需要验证数据有效性时,可以使用特性为实体进行验证限制。

验证模型

在进行方法执行时,需要验证数据有效性。

使用ModelState.AddModelError指定有问题的属性名和错误信息。

使用ModelState.IsValidField属性,来检查是否添加过此异常。

使用ModelState.IsValid 来验证是否有异常。

[HttpPost]
public ViewResult MakeBooking(Appointment appt)
{
if (string.IsNullOrEmpty(appt.ClientName))
{
ModelState.AddModelError("ClientName", "Please enter yourname");
}
if (ModelState.IsValidField("Date") && DateTime.Now > appt.Date)
{
ModelState.AddModelError("Date", "Please enter a date inthe future");
}
if (ModelState.IsValidField("ClientName") && ModelState.IsValidField("Date") && appt.ClientName == "Joe" && appt.Date.DayOfWeek == DayOfWeek.Monday) {
ModelState.AddModelError("", "Joe cannot book appointments onMondys");
}
if (ModelState.IsValid)
return View("Completed", appt);
else
return View();
}

在页面需要添加一个显示错误异常的地方。

@Html.ValidationSummary() 会在定义的位置,使用无序数列显示你添加的错误异常。

ValidationSummary 有四个重载方法

方法 | 描述
ValidationSummary() | 显示所有错误
ValidationSummary(bool) | 参数为true,只显示模型级错误
ValidationSummary(string) | 在错误之前,添加一条信息
ValidationSummary(bool,string) | 在模型级错误之前添加一条信息

添加错误时,不指定属性则默认模型级错误

 if (ModelState.IsValidField("ClientName") && ModelState.IsValidField("Date") && appt.ClientName == "Joe" && appt.Date.DayOfWeek == DayOfWeek.Monday) {
ModelState.AddModelError("", "Joe cannot book appointments onMondys");
}

显示属性及验证信息

当你设为模型级错误时,可以针对属性进行细粒度分划。

使用Html.ValidationMessageFor,可以设置针对属性的错误信息提示。

<span>@Html.ValidationMessageFor(m => m.ClientName)</span>

特性验证

可以使用特性,来进行内建的验证

 public class Appointment
{
[Required]
[StringLength(, MinimumLength = )]
public string ClientName { get; set; } [DataType(DataType.Date)]
public DateTime Date { get; set; } [Range(typeof(bool), "true", "true", ErrorMessage = "You must accept theterms")]
public bool TermsAccepted { get; set; }
}

当属性不满足时,会自动进行错误添加显示。

内建的验证属性,详细信息:https://msdn.microsoft.com/zh-cn/library/system.componentmodel.dataannotations(v=vs.110).aspx

属性 | 实例 | 描述
Compare | [Compare("MyOther Property")] | 两个属性必须有同样的值
Range | [Range(,)] | 指定最大值和最小值
RegularExpression | [RegularExression("pattern")] | 匹配指定的正则表达式
Required | [Required] | 非空值
StringLength | [StringLength()] | 最大长度

客户端验证

添加Nuget包,并引用可以进行客户端验证

jQuery\jQuery.Validation\Microsoft.jQuery.Unobtrusive.Validation

并且在Web.Config中将以下属性设置为true

<add key="ClientValidationEnabled" value="true" />

<add key="UnobtrusiveJavaScriptEnabled" value="true" />

FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, "Chenxy", DateTime.Now.AddMinutes(1), DateTime.Now, true, "Admin");

安全验证

ValidateAntiForgeryToken 可以防止CSRF(跨网站请求伪造),但是防不住XSS(跨站脚本攻击)。

用法:在View->Form表单中:<%:Html.AntiForgeryToken()%>

在Controller->Action动作上:[ValidateAntiForgeryToken]

原理:

1、<%:Html.AntiForgeryToken()%>这个方法会生成一个隐藏域:<inputname="__RequestVerificationToken" type="hidden" value="7FTM...sdLr1" />并且会将一个以"__RequestVerificationToken“为KEY的COOKIE给控制层。

2、[ValidateAntiForgeryToken],根据传过来的令牌进行对比,如果相同,则允许访问,如果不同则拒绝访问。

关键:ValidateAntiForgeryToken只针对POST请求。

换句话说,[ValidateAntiForgeryToken]必须和[HttpPost]同时加在一个ACTION上才可以正常使用。

WebAPI

http://www.cnblogs.com/chenxygx/p/6628714.html

捆绑包

针对脚本或者样式表,可以将多个文件进行单一处理。这样会降低请求数量。

首先需要添加 Optimization 这个NuGet包。

然后进行捆绑包的定义(需要在Global.asax中,进行运行)

 public class BundleConfig
{
public static void RegisterBundles(BundleCollection bundles)
{
bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include(
"~/Scripts/bootstrap.js",
"~/Scripts/respond.js"));
bundles.Add(new StyleBundle("~/Content/css").Include(
"~/Content/bootstrap.css",
"~/Content/site.css"));
}
}

捆绑包

使用方法:

@Styles.Render("~/Content/css")

@Scripts.Render("~/bundles/scripts")

PostRequestHandlerExecute

PreRequestHandlerExecute

上一篇:[leetcode] 103 Binary Tree Zigzag Level Order Traversal (Medium)


下一篇:python函数传入参数(默认参数、可变长度参数、关键字参数)