先来点理论知识,来自 http://www.cnblogs.com/simonchen/articles/2220838.html
一.什么是Rest
REST软件架构是由Roy Thomas Fielding博士2000年在他的论文《Architectural Styles and the Design of Network- based Software Architectures》首次提出的。他提出的理论对后来的Web技术的发展产生了巨大的影响,他是许多重要Web架构标准的设计者,这些标准就是 HTTP、URI等。
- Rest的英文全称是“Representational State Transfer”。中文翻译为“表述性状态转移”。REST本身只是为分布式超媒体系统设计的一种架构风格,而不是标准。
- 那么如何理解“Representational State Transfer”这句话呢?下面我们来解释一下:
- Representational :中文直译:代表的,表像的。如果把WEB 服务器端中所有的东西(数据)都看作是资源(Resource),那么呈现在用户面前(客户端)的就是资源的表像(Representation)。每一个资源都有自己的唯一标识(URI)。
- State :中文直译:状态。首先这个状态是客户端的状态,而不是服务器端的状态(在REST 中,服务器端应该是无状态的)。那么,把State和Representation联系在一起(Representational State),可以理解成:每一个资源(Resource)在客户端的表像(Representation)就是客户端的一个状态(State)。
- Transfer:中文直译:转移。当用户通过不同的URI访问不同的资源时,客户端的表像(Representation)也会随着变化,也就意味着客户端的状态变更(Transfer)了,连起来就是:Representational State Transfer。
- REST=老的Web规范+3个新的规范:REST实际上也是基于已有的Web规范集合产生的。传统的Web应用大都是BS系统,这些系统共同遵循一些老的Web规范,这些规范主要包含 3条:
- 客户-服务器:这种规范的提出,改善了用户接口跨多个平台的可移植性,并且通过简化服务器组件,改善了系统的可伸缩性。最为关键的是通过分离用户接口和数据存储这两个关注点,使得不同用户终端享受相同数据成为了可能。
- 无状态性:无状态性是在客户-服务器约束的基础上添加的又一层规范。他要求通信必须在本质上是无状态的,即从客户到服务器的每个request都 必须包含理解该request所必须的所有信息。这个规范改善了系统的可见性(无状态性使得客户端和服务器端不必保存对方的详细信息,服务器只需要处理当 前request,而不必了解所有的request历史),可靠性(无状态性减少了服务器从局部错误中恢复的任务量),可伸缩性(无状态性使得服务器端可 以很容易的释放资源,因为服务器端不必在多个request中保存状态)。同时,这种规范的缺点也是显而易见得,由于不能将状态数据保存在服务器上的共享 上下文中,因此增加了在一系列request中发送重复数据的开销,严重的降低了效率。
- 缓存:为了改善无状态性带来的网络的低效性,我们填加了缓存约束。缓存约束允许隐式或显式地标记一个response中的数据,这样就赋予了客户 端缓存response数据的功能,这样就可以为以后的request共用缓存的数据,部分或全部的消除一部分交互,增加了网络的效率。但是用于客户端缓存了信息,也就同时增加了客户端与服务器数据不一致的可能,从而降低了可靠性。
- REST在原有的架构上增加了3个新规范:统一接口、分层系统和按需代码:
- 统一接口:REST架构风格的核心特征就是强调组件之间有一个统一的接口,这表现在REST世界里,网络上所有的事物都被抽象为资源,而REST 就是通过通用的链接器接口对资源进行操作。这样设计的好处是保证系统提供的服务都是解耦的,极大的简化了系统,从而改善了系统的交互性和可重用性。并且 REST针对Web的常见情况做了优化,使得REST接口被设计为可以高效的转移大粒度的超媒体数据,这也就导致了REST接口对其它的架构并不是最优的。
- 分层系统:分层系统规则的加入提高了各种层次之间的独立性,为整个系统的复杂性设置了边界,通过封装遗留的服务,使新的服务器免受遗留客户端的影响,这也就提高了系统的可伸缩性。
- 按需代码:REST允许对客户端功能进行扩展。比如,通过下载并执行 applet或脚本形式的代码,来扩展客户端功能。但这在改善系统可扩展性的同时,也降低了可见性。所以它只是REST的一个可选的约束。
二.Rest的特点
由于Rest遵守的这些规范,因此Rest架构的特点也非常的明显:
- REST是一种架构,而不是一个规范。
- REST是一种典型的Client-Server架构,但是强调瘦服务器端,服务器端只应该处理跟数据有关的操作,所有有关显示的工作都应该放在客户端。
- 在REST架构中,服务器是无状态的,也就是说服务器不会保存任何与客户端的会话状态信息。所有的状态信息只能放在双方沟通的 Message(消息)中。
- REST架构是幂等的,对于相同的请求,服务器返回的结果也是相同的,因此服务器端返回的结果是可以缓存的,既可以存在客户端也可以存在代理服务器端。
- 在REST架构中,所有的操作都是基于统一的方式进行的:
- 每个Resource都有一个唯一的ID。
- 通过Representation(客户端)来处理Resource(服务器端)。也就是说,客户端不能直接操作服务器端的Resource,只能通过对相应的Representation的操作,并发送相应的请求,最后由服务器端来处理Resource并返回结果。
- 客户端和服务器端传送的任何一个Message(消息),都应该是自描述的。也就是说处理这个 Message所需要的上下文环境都应该包含在这个Message当中。
- 多媒体的交互系统,客户端和服务器端传送的内容可以是文档,图片,声音等等多媒体数据,这也是一个Resource能够对应不同的Representation(例如文档,图片等)的基础。
- 分层结构,像TCP/IP的分层结构一样,第n层使用第n-1层提供的服务并为第n+1层提供服务。在REST中,Client- Server之间加入了Proxy层和Gateway层。在这些中间层可以加入一些业务处理以外的功能,譬如:负载均衡,安全控制等等。
- Code-On-Demand,客户端可以访问服务器端的Resource,但并不知道如何处理服务器端返回的结果,这个处理过程的代码应该是从服务器端发送过来,然后在客户端执行,也就是说客户端的功能是根据需要动态从服务器端获得的。一个很简单的例子,Applet就是从服务器端下载然后在客户端执行的。注意,这个特性是可选的(Optional),也就是说在你的REST实现当中,可以不考虑这个特性。
三.Rest的优点
既然Rest风格有这些特点,那么也就具备了许多优点:
- 缓存使用 HTTP 向 RESTful 端点申请数据时,用到的 HTTP 动词是 GET。对于 GET 请求响应中返回的资源,可以用多种不同的方式进行缓存。Conditional GET 就是可供选择的一种实现细节,客户端可以向服务验证他的数据是否为最新版本;RESTful 端点可以通过它进一步提高速度和可伸缩性。
- 扩展 REST 鼓励每项资源包含处理特殊请求所需的所有必要状态。满足这一约束时,RESTful 服务更易于扩展且可以没有状态。
- 副作用如您使用 GET 请求资源,RESTful 服务应该没有副作用(遗憾的是,与其他一些 REST 约束相比,这一约束更容易被打破)。
- 幂等统一接口另外两个常用到的主要 HTTP 动词是 PUT 和 DELETE。用户代理想要修改资源时最常使用 PUT,DELETE 可以自我描述。要点(也就是“幂等”一词所强调的)是您可以对特殊资源多次使用这两个动词,效果与首次使用一样——至少不会有任何其他影响。构建可靠的分布式系统时(即错误、网络故障或延迟可能导致多次执行代码),这一优点可提供保障。
- 互操作性许多人将 SOAP 捧为建立客户端-服务器程序最具互操作性的方法。但一些语言和环境至今仍没有 SOAP 工具包。有一些虽然有工具包,但采用的是旧标准,不能保证与使用更新标准的工具包可靠沟通。对于大多数操作,REST 仅要求有 HTTP 库(当然,XML 库通常也很有帮助),它的互操作性肯定强过任何 RCP 技术(包括 SOAP)。
- 简易性与其他优点相比,这一优点更主观一些,不同的人可能有不同的感受。对我而言,使用 REST 的简易性涉及到代表资源的 URI 和统一接口。作为一名 Web 冲浪高手,我理解在浏览器中输入不同的 URI 可以得到不同的资源(有时也被称为 URI 或 URL 黑客,但绝无恶意)。由于有多年使用 URI 的经验,所以为资源设计 URI 对我来说得心应手。使用统一接口简化了开发过程,因为我不必为每个需要建立的服务构建接口、约定或 API。接口(客户端与我的服务交互的方式)由体系结构约束设置。
四.Rest的设计原则
REST架构是针对Web应用而设计的,其目的是为了降低开发的复杂性,提高系统的可伸缩性。REST提出了如下设计准则:
- 网络上的所有事物都被抽象为资源(resource),比如图片、音乐、视频、文字、以及服务等等;
- 每个资源有唯一的资源标识符(resource identifier),URI定位资源;
- 通过通用的连接器接口(generic connector interface)对资源进行操作,比如使用 HTTP 标准动词(GET、POST、PUT 和 DELETE)的统一接口完成操作;
- 对资源的各种操作不会改变资源标识符,URI不变;
- 所有的操作都是无状态的(stateless)。
五.wcf3.5到wcf4.0 Rest的新增特性
- 增加对路由的支持
- 对缓存的支持。
- 帮助(help)页面的改进。
- 消息错误处理
- 消息格式的多样性如(XML\JSON\ATOM\TEXT\BINARY)
- 简化操作。
甩过一遍理论,那么就趁热实践一番吧!
六.实践出真知
按正常步骤新建一个WCF应用,常见的CRUD操作
[ServiceContract]
public interface IExampleService
{ [OperationContract]
string GetData(string value); [OperationContract]
string AddData(string value); [OperationContract ]
string UpdateData(string value); [OperationContract ]
string DeleteData(string value); }
那么rest模式该是如何呢?
[ServiceContract]
public interface IExampleService
{ [OperationContract]
[WebGet(UriTemplate = "/Rest/Get/{value}", ResponseFormat = WebMessageFormat.Json)]
string GetData(string value); [OperationContract]
[WebInvoke(UriTemplate = "/Rest/Add/{value}", Method = "POST")]
string AddData(string value); [OperationContract ]
[WebInvoke(UriTemplate = "/Rest/Update/{value}", Method = "PUT")]
string UpdateData(string value); [OperationContract ]
[WebInvoke (UriTemplate="/Rest/Delete/{value}",Method="DELETE")]
string DeleteData(string value); }
比较下就很容易看出多加了些标签,并且也从方法的使用上可以对应出GET、POST、PUT、DELETE的使用。
wcf可以看元数据,那么rest也有对应的方式,在web.config中添加如下配置就可以查看help页面
<services>
<service name="RestWCFTest.ExampleService">
<endpoint address="" behaviorConfiguration="HelpBehavior" binding="webHttpBinding"
bindingConfiguration="" contract="RestWCFTest.IExampleService" />
</service>
</services> <behaviors>
<endpointBehaviors>
<behavior name="HelpBehavior">
<webHttp helpEnabled="true" />
</behavior>
</endpointBehaviors>
</behaviors>
help页面如下
点击方法进去可以看见调用方式
我们的接口实现
public string GetData(string value)
{
return string.Format("You entered: {0}", value);
} public string AddData(string value)
{
return string.Format("You added: {0}", value);
} public string UpdateData(string value)
{
return string.Format("You updated: {0}", value);
} public string DeleteData(string value)
{
return string.Format("You deleted: {0}", value);
}
现在我们用fiddler来模拟请求测试下
在composer选项里有模拟请求的功能,very good!我们先来调用GetData操作,根据参数获取数据,根据设置的URI模板,“123456”为匹配
的参数,执行它!
看请求的信息
GET http://localhost/REST4/ExampleService.svc/Rest/Get/123456 HTTP/1.1
User-Agent: Fiddler
Content-type: application/json
Host: localhost
Content-Length: 0
看响应的数据
HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 21
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2013 05:16:52 GMT "You entered: 123456"
200 OK 调用正常,content-type是json,因为我们指定的,IIS是7.5,对,我的确是部署在7.5上。。。看结果也是和预期一模一样,so easy~
可能有同学会问,这是返回的json数据么?我也觉得不是,如果在方法标签上修改为如下
[OperationContract]
[WebGet(UriTemplate = "/Rest/Get/{value}",BodyStyle=WebMessageBodyStyle.Wrapped, ResponseFormat = WebMessageFormat.Json)]
string GetData(string value);
多加了个修饰bodystyle,它的功能是对结果进行包装,包装后再看返回的结果
HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 39
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2013 06:34:24 GMT {"GetDataResult":"You entered: 123456"}
果然,被包装了,它是一个json格式的数据了。
POST
请求
POST http://localhost/REST4/ExampleService.svc/Rest/Add/ HTTP/1.1
User-Agent: Fiddler
Content-type: application/json
Host: localhost
Content-Length: 0
响应
HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 92
Content-Type: application/xml; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2013 05:06:41 GMT <string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">You added: 1234</string>
这个时候我们没有指定返回的格式,默认为XML。
PUT
请求
PUT http://localhost/REST4/ExampleService.svc/Rest/Update/123 HTTP/1.1
User-Agent: Fiddler
Content-type: application/json
Host: localhost
Content-Length: 0
响应
HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 93
Content-Type: application/xml; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2013 05:23:04 GMT <string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">You updated: 123</string>
DELETE
请求
DELETE http://localhost/REST4/ExampleService.svc/Rest/Delete/123 HTTP/1.1
User-Agent: Fiddler
Content-type: application/json
Host: localhost
Content-Length: 0
响应
HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 93
Content-Type: application/xml; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2013 05:14:56 GMT <string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">You deleted: 123</string>
有同学可能DELETE请求发出去没反应(IIS 7.5),请在web.config里加上以下节点
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<remove name="WebDAVModule" />
</modules>
<handlers>
<remove name="WebDAV" />
</handlers>
</system.webServer>
至此一般的传参情况就是如此了,下面列举一些其它传参情况
[OperationContract]
[WebGet(UriTemplate = "/Rest/GetList2/", ResponseFormat = WebMessageFormat.Json)]
List<ExampleData> GetDataLs2(); [OperationContract]
[WebInvoke(UriTemplate = "/Rest/AddLs3", BodyStyle = WebMessageBodyStyle.Wrapped,
ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json, Method = "POST")]
List<ExampleData> AddDataLs3(List<ExampleData> datas); [OperationContract]
[WebInvoke(UriTemplate = "/Rest/AddLs4", BodyStyle = WebMessageBodyStyle.Wrapped,
ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json, Method = "POST")]
List<ExampleData> AddDataLs4(List<ExampleData> datas1, List<ExampleData> datas2);
实体
public class ExampleData
{
public string Name
{
get;
set;
} public string Age
{
get;
set;
}
}
接口实现
public List<ExampleData> GetDataLs2()
{
List<ExampleData> result = new List<ExampleData> {
new ExampleData{ Name="张三", Age="20"}
,new ExampleData {Name="李四",Age="21"}
,new ExampleData {Name="王五",Age="30"}
}; return result;
} public List<ExampleData> AddDataLs3(List<ExampleData> datas)
{
return datas;
} public List<ExampleData> AddDataLs4(List<ExampleData> datas1, List<ExampleData> datas2)
{
List<ExampleData> result = new List<ExampleData>();
result.AddRange(datas1);
result.AddRange(datas2); return result;
}
我们看到有获取实体集合了,还有传参的时候也是实体集合了
首先看看获取集合的情况
请求
GET http://localhost/REST4/ExampleService.svc/Rest/GetList2/ HTTP/1.1
User-Agent: Fiddler
Content-type: application/json
Host: localhost
Content-Length: 0
响应
HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 88
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2013 05:21:52 GMT [{"Age":"20","Name":"张三"},{"Age":"21","Name":"李四"},{"Age":"30","Name":"王五"}]
嗯,返回的格式不错。
看看怎样做新增操作的
AddDataLs3
请求
POST http://localhost/REST4/ExampleService.svc/Rest/AddLs3 HTTP/1.1
User-Agent: Fiddler
Content-type: application/json
Host: localhost
Content-Length: 41 {"datas":[{"Name":"xiaohua","Age":"13"}]}
这时候我们会注意到,多了request body了,并且datas就是参数名
响应
HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 52
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2013 06:59:55 GMT {"AddDataLs3Result":[{"Age":"13","Name":"xiaohua"}]}
被包装了的数据。
AddDataLs4
请求
POST http://localhost/REST4/ExampleService.svc/Rest/AddLs4 HTTP/1.1
User-Agent: Fiddler
Content-type: application/json
Host: localhost
Content-Length: 78 {"datas1":[{"Name":"xiaohua","Age":"13"}],"datas2":[{"Name":"li","Age":"13"}]}
响应
HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 77
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2013 07:02:58 GMT {"AddDataLs4Result":[{"Age":"13","Name":"xiaohua"},{"Age":"13","Name":"li"}]}
面对茫茫多的CRUD的时候,我们也许会显得不耐烦,因为每个操作都去写方法,真是烦躁,不妨可以试下如下的方式
[OperationContract]
[WebInvoke(UriTemplate = "/Rest/*", Method = "*", ResponseFormat = WebMessageFormat.Json)]
string ExecuteData();
用星号来匹配所有的请求,让程序区识别请求到底是GET、POST、PUT还是DELETE
实现
public string ExecuteData()
{
var request = WebOperationContext.Current.IncomingRequest; var method = request.Method;
var args = request.UriTemplateMatch.WildcardPathSegments; switch (method)
{
case "POST":
return "POST...";
case "DELETE":
return "DELETE...";
case "PUT":
return "UPDATE...";
default:
return "GET...";
}
}
嗯,不知不觉就贴了这么多代码了,其实我字没写多少,今天就到这吧,算是入门了吧。
以上例子所有源码下载
【REST WCF】30分钟理论到实践的更多相关文章
-
【微信小程序项目实践总结】30分钟从陌生到熟悉 web app 、native app、hybrid app比较 30分钟ES6从陌生到熟悉 【原创】浅谈内存泄露 HTML5 五子棋 - JS/Canvas 游戏 meta 详解,html5 meta 标签日常设置 C#中回滚TransactionScope的使用方法和原理
[微信小程序项目实践总结]30分钟从陌生到熟悉 前言 我们之前对小程序做了基本学习: 1. 微信小程序开发07-列表页面怎么做 2. 微信小程序开发06-一个业务页面的完成 3. 微信小程序开发05- ...
-
【微信小程序项目实践总结】30分钟从陌生到熟悉
前言 我们之前对小程序做了基本学习: 1. 微信小程序开发07-列表页面怎么做 2. 微信小程序开发06-一个业务页面的完成 3. 微信小程序开发05-日历组件的实现 4. 微信小程序开发04-打造自 ...
-
【30分钟学完】canvas动画|游戏基础(2):从零开始画画
前言 上篇主要是理论的概述,本篇会多些实践,来讲讲canvas的基础用法,并包含一些基础三角函数的应用,推荐没有canvas基础的朋友阅读,熟悉的朋友可以跳过. 本人能力有限,欢迎牛人共同讨论,批评指 ...
-
JS组件系列——又一款MVVM组件:Vue(一:30分钟搞定前端增删改查)
前言:关于Vue框架,好几个月之前就听说过,了解一项新技术之后,总是处于观望状态,一直在犹豫要不要系统学习下.正好最近有点空,就去官网了解了下,看上去还不错的一个组件,就抽空研究了下.最近园子里vue ...
-
AngularJS 30分钟快速入门【译】
引用自:http://www.revillweb.com/tutorials/angularjs-in-30-minutes-angularjs-tutorial/,翻译如下: 简介 我三年前开始使用 ...
-
DDD(领域驱动设计)理论结合实践
DDD(领域驱动设计)理论结合实践 写在前面 插一句:本人超爱落网-<平凡的世界>这一期,分享给大家. 阅读目录: 关于DDD 前期分析 框架搭建 代码实现 开源-发布 后记 第一次听 ...
-
es6属性基础教学,30分钟包会
ES6基础智商划重点在实际开发中,ES6已经非常普及了.掌握ES6的知识变成了一种必须.尽管我们在使用时仍然需要经过babel编译.ES6彻底改变了前端的编码风格,可以说对于前端的影响非常巨大.值得高 ...
-
从理论到实践,全方位认识DNS
从理论到实践,全方位认识DNS 2015-11-23 程序员之家 作者:selfboot 原文:http://segmentfault.com/a/1190000003956853 对于 DNS(Do ...
-
ARM NEON指令集优化理论与实践
ARM NEON指令集优化理论与实践 一.简介 NEON就是一种基于SIMD思想的ARM技术,相比于ARMv6或之前的架构,NEON结合了64-bit和128-bit的SIMD指令集,提供128-bi ...
随机推荐
-
sql server 2008 外键关联的设置和取消
直接上图片 选中表右击-设计 找到需要设置外键的字段.右击-关系,在弹出的对话框中点击添加 选择右边的小按钮点击.选择主键表和关联的主键ID,以及外建表的关联字段. 建立外键完成. 删除的话选中某个外 ...
-
UVA 673 (13.08.17)
Parentheses Balance You are given a string consisting of parentheses () and []. Astring of this ty ...
-
crawler_JVM_DNS_在爬虫中的应用
DNS解析:即由域名 经过dns解析,跳转到真正服务器的地址,这个重复解析的耗时占请求很大比例. 在设计爬虫时比较细粒度的控制下,需要考虑dns解析. jdk从1.5往后对dns缓存有默认设置, 详见 ...
-
C#写的较完美验证码通用类
using System; using System.Collections; using System.ComponentModel; using System.Data; using System ...
-
svn中出现各种感叹号说明
黄色感叹号(有冲突): --这是有冲突了,冲突就是说你对某个文件进行了修改,别人也对这个文件进行了修改,别人抢在你提交之前先提交了,这时你再提交就会被提示发生冲突,而不允许你提交,防止你的提交覆盖了别 ...
-
最新git源码下载地址
1.最新git源码下载地址: https://github.com/git/git/releases https://www.kernel.org/pub/software/scm/git/ 可以手动 ...
-
牛客挑战赛30 小G砍树 树形dp
小G砍树 dfs两次, dp出每个点作为最后一个点的方案数. #include<bits/stdc++.h> #define LL long long #define fi first # ...
-
Ocelot中文文档-Getting Started
Getting Started Ocelot是只在.NET Core运行,目前基于netstandard2.0构建的.如果Ocelot适合你们的话,那么这个文档会有所帮助. .NET Core 2.0 ...
- php面试流程
-
Django系列之form渲染表单后css样式丢失
最通用的form写法 我们经常看各大网站上写这样的form写法 class SYSAdminPhysicalForm(forms.ModelForm): ''' this form for idc's ...