业务逻辑代码通常位于模型(model)层。客户端(比如浏览器)无法直接调用其中的代码,所以模型对象提供的功能,必须作为资源以URI方式暴露给外部。

  客户端使用HTTP协议来操作这些资源,从而调用了内部的业务逻辑。但是,这种从资源到模型之间的映射是单向的:我们可以根据需要提供不同粒度的资源,可以虚拟出一些资源,还可以给某些资源起别名...

  Controller层就是专门做这件事的:在模型层与传输层之间搭起一座桥梁。它使用与模型层同一种语言,以便访问和修改模型对象,但同时它又跟HTTP接口一样,是面向请求(Request)和响应(Response)的。

  Controller层减少了HTTP与模型层之间的“阻抗不匹配”。

  

注意

  不同的模型方案使用了不同的策略。有一些可以让我们直接访问模型对象,比如EJB或者Corba协议,它们使用RPC(远程过程调用)。这种交互方式与web很难兼容。

  另一些技术如SOAP,尝试通过Web来访问模型层,但它也是一种PRC风格的协议,只是以HTTP为传输协议。它也不是一种程序协议。

  Web的理念在根本上与面向对象不同,所以我们需要一个层来协调。

  Controller综述

  一个Controller就是一个位于 controllers 包中的类,其继承于 play.mvc.Controller :

  示例:

  package controllers;  
  import models.Client;  
  import play.mvc.Controller;  
  publicclass Clients extends Controller {  
  publicstaticvoid show(Long id) {  
  Client client = Client.findById(id);  
  render(client);  
  }  
  publicstaticvoid delete(Long id) {  
  Client client = Client.findById(id);  
  client.delete();  
  }  
  }

  Controller中每一个public static方法都被称为一个action。它的签名形如:

  publicstaticvoid action_name(params...);

  你可以在action的方法签名中定义各种参数。Play会自动从相应的HTTP参数中,取出对应的值赋过去(并进行恰当的转换),这一点非常方便。

  通常一个action不需要返回值。当我们在action中调用了一个能产生“结果”的方法后,action就退出了。在本例中, render(…) 就是一个显示一个模板的可产生结果的方法。

  获取HTTP参数

  HTTP请求中可包含数据,这些数据可位于:

  URI路径中: 如 /clients/1541, 1541就是一个动态产生的参数. 
  Query String: /clients?id=1541. 
  request body: 如果提交了一个HTML表彰,将request body中将包含以 x-www-urlform-encoded 方式转换过的数据。

  对于这些情况,Play都可以取得数据,并生成一个 Map<String, String[]> 。Key是参数名,来源于:

  在conf/routes文件中定义的规则中的动态参数名 
  Query String中的name=value 
  通过表单提交的数据的name.

  使用参数map

  在所有的Controller类中都可以直接使用 params 这个对象。它是在 play.mvc.Controller 中定义的。这个对象中,包含了当前请求的所有数据。

  示例:

  publicstaticvoid show() {  
  String id = params.get("id");  
  String[] names = params.getAll("names");  
  }

  你还可以转换参数值的类型:

  publicstaticvoid show() {  
  Long id = params.get("id", Long.class);  
  }

  其实,还有更好的办法来转换 :)

  直接利用action方法的参数类型定义来转换

  我们可以直接从action方法的参数定义中,直接拿到对应的参数值。方法中的参数名必须跟http传过来的参数名相同。

  比如,对于如下的URL:

  /clients?id=1451

  我们可以定义一个包含 id 参数的action:

  publicstaticvoid show(String id) {  
  System.out.println(id);   
  }

  我们还可以使用其它的类型,play会自动进行转换:

  publicstaticvoid show(Long id) {  
  System.out.println(id);   
  }

  如果同一个参数有多个值,还可以把它声明为数组:

  publicstaticvoid show(Long[] id) {  
  for(String anId : id) {  
  System.out.println(anid);   
  }  
  }

  甚至集合:

  publicstaticvoid show(List<Long> id) {  
  for(String anId : id) {  
  System.out.println(anid);   
  }  
  }

  这一功能非常方便,其它的框架都很少提供(有一些提供了,但要求每个参数前都要加一个annotation,不太方便)。

  内部原理是,Play会对每个action进行扫描,得到其参数信息(包括类型与参数名)。能过反射很容易得到参数类型,但得不到参数名,因为javac在编译java代码时,会忽略参数名信息。而Play内置了eclipse的编译器,并在编译时打开相关选项以记录参数名信息,所以才能成功取到。

  

异  常
   

  如果action方法中定义的某个参数,在http请求中找不到对应的数据,则将会把它设为默认值(对象类型设为null,基础数字类型设为0,boolean类型设为false)。如果找到了,但是其值无法转换为指定的Java类型,则会将这一错误记录在validation对象中,并使用默认值代替。

  HTTP to Java 高级绑定

  简单类型

  Java中所有基础和常用类型,都可以自动绑定:

  int, long, boolean, char, byte, float, double, Integer, Long, Boolean, Char, String, Byte, Float, Double.

  注意如果某个参数HTTP请求中没有提供,或者自动转换失败,则Object类型会被设为null,而基础类型会被设为它们的默认值。

  Date

  如果一个日期格式如下,则它可以自动转换并绑定:

  yyyy-MM-dd’T’hh:mm:ss’Z' // ISO8601 + timezone 
  yyyy-MM-dd’T’hh:mm:ss" // ISO8601 
  yyyy-MM-dd 
  yyyyMMdd’T’hhmmss 
  yyyyMMddhhmmss 
  dd'/‘MM’/'yyyy 
  dd-MM-yyyy 
  ddMMyyyy 
  MMddyy 
  MM-dd-yy 
  MM'/‘dd’/'yy

  使用@As注解,我们可以指定日期格式。

  例如:

  archives?from=21/12/1980

  publicstaticvoid articlesSince(@As("dd/MM/yyyy") Date from) {  
  List<Article> articles = Article.findBy("date >= ?", from);  
  render(articles);  
  }

  我们可还以对不同的语种定义不同的格式,例如:

  publicstaticvoid articlesSince(@As(lang={"fr,de","*"},   
  value={"dd-MM-yyyy","MM-dd-yyyy"}) Date from) {  
  List<Article> articles = Article.findBy("date >= ?", from);  
  render(articles);  
  }

  在这个例子中,我们将法国和德国的日期定义为 dd-MM-yyyy ,其它都为 MM-dd-yyyy. 注意语种可用逗号分隔。注意参数lang的个数必须与value的个数相等。

  如果没有使用@As注解,则Play将使用你的区域对应的默认日期格式。我们可在“conf/application.conf”中使用“date.format”为key来配置该格式.

  日历(Calendar)

  Calendar的绑定与date几乎完全一样,除了play将会根据你的区域设置选择相应的Calendar对象。@Bind注解也可在这里使用。

  文件(File)

  在Play中处理文件上传非常简单。使用经 multipart/form-data 编码的请求将文件post到服务器端,同时使用 java.io.File 来获取文件:

  publicstaticvoid create(String comment, File attachment) {  
  String s3Key = S3.post(attachment);  
  Document doc = new Document(comment, s3Key);  
  doc.save();  
  show(doc.id);  
  }

  Play将先取得上传的文件,保存在临时目录下,并使用上传的文件名。当这个request结束后,该文件将被删除,所以我们必须将它拷贝到一个安全的目录中,否则就找不到了。

  上传的文件的MIME类型通常应该在HTTP request的head中,以 Content-type 方式指定。但当我们通过浏览器上传文件时,一些不常见类型的文件不会指定。在这种情况下,我们可以使用 play.libs.MimeTypes 类将该文件的后缀名映射到一个MIME类型上。

  String mimeType = MimeTypes.getContentType(attachment.getName());

  play.libs.MimeTypes 类在 $PLAY_HOME/framework/src/play/libs/mime-types.properties 中以文件的后缀为来寻找对应的MIME类型。

  你也可以 通过 自定义MIME类型 来增加你自己的类型。

  数组或集合

  所有支持的类型,都可以通过数组或集合的形式取得:

  publicstaticvoid show(Long[] id) {  
  …  
  }

  或者:

  publicstaticvoid show(List<Long> id) {  
  …  
  }

  或者:

  publicstaticvoid show(Set<Long> id) {  
  …  
  }

  Play还可以处理像Map<String, String>这样的绑定:

  publicstaticvoid show(Map<String, String> client) {  
  …  
  }

  一个如下的query string:

  ?client.name=John&client.phone=111-1111&client.phone=222-2222

  将会把变量client绑定到一个含有两个元素的map上。第一个元素的key是 name ,值是 John ,第二个key是 phone 值是 111-1111, 222-2222.

  POJO对象绑定

  Play还可以使用简单的命名约定将参数绑定到一个pojo对象上。

  publicstaticvoid create(Client client ) {  
  client.save();  
  show(client);  
  }

  可以使用像下面这样的query string,来调用该action以创建一个client对象:

  ?client.name=Zenexity&client.email=contact@zenexity.fr

  Play创建一个Client的实例,然后将HTTP参数中与Client对象属性同名的值赋过去。无法处理的参数将被安全的忽略,类型不匹配的也将安全忽略。

  参数绑定是递归的,我们可以通过query string来创建一个完全的对象图:

  ?client.name=Zenexity  
  &client.address.street=64+rue+taitbout  
  &client.address.zip=75009
  &client.address.country=France

  如果我们想更新一列对象,我们可以使用数组形式来引用对象的ID。举例来说,假设Client模型有一列Customer模型,并声明为 List<Customer> customers 形式。为了更新这一列Customers,我们应该提供一个如下的query string:

  ?client.customers[0].id=123
  &client.customers[1].id=456
  &client.customers[2].id=789

  JPA对象绑定

  我们可以将一个JPA对象与HTTP自动绑定起来。

  我们可以在HTTP参数中提供 user.id 字段。当Play发现这个字段时,它会先到数据库中取出相应的实例,然后把HTTP请求中其它的参数赋过去。所以我们可以直接save它。

  publicstaticvoid save(User user) {  
  user.save(); // ok with 1.0.1 
  }

  我们可以使用同样的方式来更新完整的对象图,但是必须对每一个子对象提供ID:

  user.id = 1
  &user.name=morten  
  &user.address.id=34
  &user.address.street=MyStreet

  自定义绑定

  绑定系统还支持自定义。

  @play.data.binding.As

  首先要讲的是@play.data.binding.As这个新注解,使用它,我们可以配置一个绑定。看下面的例子,我们使用它来指定一个Date的格式(该格式将被 DateBinder 使用):

  publicstaticvoid update(@As("dd/MM/yyyy") Date updatedAt) {  
  …  
  }

  @As注解同样支持国际化,我们可以这样使用:

  publicstaticvoid update(  
  @As(  
  lang={"fr,de","en","*"},  
  value={"dd/MM/yyyy","dd-MM-yyyy","MM-dd-yy"}  
  )  
  Date updatedAt  
  ) {  
  …  
  }

  @As注解可以与所有支持它的binder一起使用,包括你自己的定义的。例如,使用 ListBinder:

  publicstaticvoid update(@As(",") List<String> items) {  
  …  
  }

  它将会把一个以逗号分隔的字符串绑定到一个 List 上。

  @play.data.binding.NoBinding

  新的@play.data.binding.NoBinding注解允许我们定义一些“不应该被绑定”的字段,以防出现安全问题。例如:

  publicclass User extends Model {  
  @NoBinding("profile") publicboolean isAdmin;  
  @As("dd, MM yyyy") Date birthDate;  
  public String name;  
  }  
  publicstaticvoid editProfile(@As("profile") User user) {  
  …  
  }

  在这种情况下, 在 editProfile action中,就算某个居心不良的用户通过伪造请求提交了一个包含 user.isAdmin=true 的字段, isAdmin 字段也不会被绑定.

  play.data.binding.TypeBinder

  @As 注解同样允许我们自定义一个完整的binder. 一个自定义的binder是 TypeBinder 的子类,我们可以在自己的项目中定义它。例如:

  publicclass MyCustomStringBinder implements TypeBinder<String> {  
  public Object bind(String name, Annotation[] anns, String value, Class clazz) { return"!!"; } }

  我们可以在任意一个action中使用它:

  publicstaticvoid anyAction(@As(binder=MyCustomStringBinder.class)   
  String name) {  
  …  
  }

  @play.data.binding.Global

  我们还可以自定义一个全局的binder来处理某一个特定的类型。比如,我们给 java.awt.Point 类定义了一个这样的binder:

  @Global
  publicclass PointBinder implements TypeBinder<Point> {  
  public Object bind(String name, Annotation[] anns, String value, Class class) {
  String[] values = value.split(“,”);
  returnnew Point( Integer.parseInt(values0), Integer.parseInt(values1) );
   }
   }

  你可以看到这个全局binder是一个典型的binder,只是使用了*@play.data.binding.Global*注解。我们在外部模块中定义这种binder,以在不同的项目中复用。

  结果类型

  一个action方法必须产生一个HTTP响应。最简单的方式就是生成一个Result对象。一旦某一个Result对象生成,该方法将立刻返回(后面的代码将不会被执行)。

  举例:

  publicstaticvoid show(Long id) {  
  Client client = Client.findById(id);  
  render(client);  
  System.out.println("This message will never be displayed !");  
  }

  render(…) 方法产生了一个Result对象,后面的代码都不会被执行。

  返回一些文本内容

  renderText(…) 方法将产生一个简单的Result事件,该事件将直接向HTTP Response中写入一些文本数据。

  举例:

  publicstaticvoid countUnreadMessages() {  
  Integer unreadMessages = MessagesBox.countUnreadMessages();  
  renderText(unreadMessages);  
  }

  你还可以使用Java的标准格式化语法来格式化文本信息:

  publicstaticvoid countUnreadMessages() {  
  Integer unreadMessages = MessagesBox.countUnreadMessages();  
  renderText("There are %s unread messages", unreadMessages);  
  }

  返回二进制内容

  为了处理如 存储在服务器上的文件 这样的二进制数据,我们可使用 renderBinary 方法。例如,如果我们有一个 User 模型,它有一个 play.db.jpa.Blob photo 属性, 我们可以使用以下方式使用保存的MIME类型向客户端发送图片数据:

  publicstaticvoid userPhoto(long id) {   
  final User user = User.findById(id);   
  response.setContentTypeIfNotSet(user.photo.type());  
  java.io.InputStream binaryData = user.photo.get();  
  renderBinary(binaryData);  
  }

  把文件当作附件下载

  我们可以通过设置HTTP头,来指导浏览器把二进制数据当作“附件”来保存。我们只需要在 renderBinary 方法中传入一个文件名即可。Play会自动在响应头中把文件名设给 Content-Disposition 。举例来说,前面例子中的 User 模型有一个 photoFileName 属性:

  renderBinary(binaryData, user.photoFileName);

  执行一个模板

  如果需要产生的内容很复杂,我们通常会在模板中来创建内容:

  publicclass Clients extends Controller {  
  publicstaticvoid index() { render(); } }

  这里有一个命名约定,Play会根据controller名和action名来寻找默认的模板路径。对于上例是:

  app/views/Clients/index.html

  把数据加入到模板域中

  模板通常都需要数据。我们可以把这些数据放在 renderArgs 对象中:

  publicclass Clients extends Controller {  
  publicstaticvoid show(Long id) {  
  Client client = Client.findById(id);  
  renderArgs.put(“client”, client); render();  
  }  
  }

  当执行模板的时候,会自动创建 client 变量。

  例如,我们可在模板中使用client变量:

  <h1>Client ${client.name}</h1>

  将数据加入到模板类中的更简单的方法

  我们可以直接将数据传入到 render(...) 中:

  publicstaticvoid show(Long id) {  
  Client client = Client.findById(id);  
  render(client);   
  }

  在这种情况下,模板中也将有一个与action中的局部变量名一样的变量(client)。

  我们还可以传入更多的变量:

  publicstaticvoid show(Long id) {  
  Client client = Client.findById(id);  
  render(id, client);   
  }

  

重要!

  你只能通过这种方式来传入局部变量

  h4. 使用其它模板

  如果你不想使用默认模板,可以通过在 renderTemplate(…) 中的第一个参数中指定另一个模板名称。

  举例:

  publicstaticvoid show(Long id) {  
  Client client = Client.findById(id);  
  renderTemplate("Clients/showClient.html", id, client);   
  }

  重定向到另一个URL

  redirect(…) 方法会产生一个重定向事件,接着会产生一个HTTP Redirect响应。

  publicstaticvoid index() {  
  redirect(";);  
  }

  Action链

  在play中没有Servlet API forward 的等价物。每一个HTTP request只能调用一个action。如果我们需要调用另一个,必须通过重定向,让浏览器访问另一个URL来访问它。这样的话,浏览器的URL始终与被执行的action保持一致,实现 Back/Forward/Refresh的管理就容易多了。

  你可以发送到任何一个action的Redirect,只需要直接在Java中调用该action即可。该调用将会自动被Play拦截,并生成一个HTTP重定向。

  举例:

  publicclass Clients extends Controller {  
  publicstaticvoid show(Long id) { 
  Client client = Client.findById(id);
  render(client);
  }
  publicstaticvoid create(String name) {
  Client client = new Client(name);
  client.save();
  show(client.id);
  }
  }

  With these routes:

  GET /clients/{id} Clients.show  
  POST /clients Clients.create

  浏览器向 /clients URL发送一个POST。 
  Router调用 Clients controller的 create action. 
  Action方法直接调用 show 方法 
  该Java调用被拦截,Router根据它产生一个调用Clients.show(id)所需要的新URL。 
  HTTP响应为 302 Location:/clients/3132. 
  浏览器接着发送 GET /clients/3132. 
  …

  自定义编码集

  Play强调使用UTF-8,但有时候某些响应,或者整个应用的响应,都必须使用一个不同的编码集。

  给当前response定义encoding

  要改变当前response的编码集,我们需要在controller中这样做:

  response.encoding = "ISO-8859-1";

  如果要使用一个与服务器默认的不同的编码集,我们必须在form中包含两次encoding/charset。一次在 accept-charset 属性中,一次在一个hidden类型的字段 _charset_ 中。 accept-charset 告诉浏览器使用哪种字符集,而 _charset_ 告诉play使用哪种字符集:

  <formaction="@{application.index}"method="POST"accept-charset="ISO-8859-1">
  <inputtype="hidden"name="_charset_"value="ISO-8859-1">
  </form>

  自定义整个程序的编码集

  配置application.web_encoding 来指定Play使用哪种编码集。

  拦截器(Interceptions)

  一个controller可以定义多个拦截器方法。拦截器作用于一个controller及其所有子类的所有action方法上。对于定义一些所有action共用的操作时,使用拦截器非常有用,比如:检查用户是否已经登录(有没有访问权),截入request范围内的数据,等等。

  这些方法必须为static,但不一定是public。你必须给它们增加合适的注解以表明它们是拦截器。

  @Before

  如果方法上有@Before注解,则它将在该controller中的每一个action被调用前被执行。

  所以可以进行一下安全检查:

  publicclass Admin extends Application {  
  @Beforestaticvoid checkAuthentification() {  
  if(session.get(“user”) == null) login();  
  }  
  publicstaticvoid index() {  
  List users = User.findAll();  
  render(users);  
  }  
  …  
  }

  如果你想给某些方法开绿灯,可按下面的方法排除一些action:

  publicclass Admin extends Application {  
  @Before(unless=“login”)  
  staticvoid checkAuthentification() {  
  if(session.get(“user”) == null) login();  
  }  
  publicstaticvoid index() {  
  List users = User.findAll();  
  render(users);  
  }  
  …  
  }

  或者仅对于某些方法调用该拦截器,可使用“only”:

  publicclass Admin extends Application {  
  @Before(only={“login”,“logout”})  
  staticvoid doSomething()  
  {  
  …  
  }  
  …  
  }

  @After, @Before 和 @Finally这三个注解,都提供了 unless 和 only 参数。

  @After

  使用@After注解的方法,将在该Controller中的每一个action之后被调用。

  publicclass Admin extends Application {  
  @Afterstaticvoid log(){  
  Logger.info(“Action executed ...”);  
  }  
  publicstaticvoid index() {  
  List users = User.findAll();  
  render(users);  
  }  
  … }

  @Catch

  使用了@Catch注解的方法,将会在某个抛出了它所指定的异常时,被调用。异常类型将被传入到@Catch方法的参数中。

  publicclass Admin extends Application {  
  @Catch(IllegalStateException.class)  
  publicstaticvoid logIllegalState(Throwable throwable) {
  Logger.error(“Illegal state %s…”, throwable);  
  }  
  publicstaticvoid index() { List users = User.findAll();  
  if (users.size() == 0) {  
  thrownew IllegalStateException(“Invalid database - 0 users”);  
  }  
  render(users);  
  }  
  }

  与Java的异常处理一样,我们可以使用一个超类来捕获更多的异常类型。如果我们有多个catch方法,可以通过指定其 priority 来定义它们的执行顺序(priority为1的最先执行)。

  publicclass Admin extends Application {  
  @Catch(value = Throwable.class, priority = 1)  
  publicstaticvoid logThrowable(Throwable throwable) {  
  // Custom error logging… Logger.error(“EXCEPTION %s”, throwable); 
  }  
  @Catch(value = IllegalStateException.class, priority = 2)  
  publicstaticvoid logIllegalState(Throwable throwable) {  
  Logger.error(“Illegal state %s…”, throwable);  
  }  
  publicstaticvoid index() {  
  List users = User.findAll();  
  if(users.size() == 0) {  
  thrownew IllegalStateException(“Invalid database - 0 users”);  
  }  
  render(users); }  
  }

  @Finally

  使用@Finally注解的方法,总是在该Controller中每一个action执行完之后再执行。不论action执行成功或者失败,它都将会被执行。跟Java中finally的意思相同。

  publicclass Admin extends Application {  
  @Finallystaticvoid log() {  
  Logger.info(“Response contains : ” + response.out);  
  }  
  publicstaticvoid index() {  
  List users = User.findAll(); render(users);  
  }  
  …  
  }

  如果@Finally方法有一个Throwable类型的参数,则异常对象会被传入(如果有的话):

  publicclass Admin extends Application {  
  @Finallystaticvoid log(Throwable e) {  
  if( e == null ){  
  Logger.info(“action call was successful”);  
  }  
  else
  {  
  Logger.info(“action call failed”, e);  
  }  
  }  
  publicstaticvoid index() {  
  List users = User.findAll(); render(users);  
  }  
  …  
  }

  Controller继承

  如果一个Controller类是另一个的子类,则父类中定义的拦截器同样对子类有效。

  通过@With注解,加入更多的拦截器

  因为Java中没有多重继承,所以通过Controller继承来共用拦截器很难。但是通过@With注解,我们可以把一些拦截器定义在一个完全不同的类中,然后在当前Controller中使用它们。

  举例:

  publicclass Secure extends Controller {  
  @Beforestaticvoid checkAuthenticated() {  
  if(!session.containsKey(“user”)) {  
  unAuthorized();  
  }  
  }  
  }

  把它加到另一个Controller中:

  @With(Secure.class)  
  publicclass Admin extends Application {  
  … }

  Session和Flash scopes

  如果你打算在多个HTTP请求之间共用数据,可以把它们保存在Session或Flash域中。保存在Session中的数据,对于整个user session都可用,而保存在flash域中的数据,则仅仅在下一个请求可用。

  有一点非常重要,需要理解的是,在play中,Session和Flash数据并没有保存在服务器端,而是通过Cookie被加入到每一个HTTP请求中。所以能保存的数据量非常小(不超过4KB),并且只能保存字符串。

  当然,cookies都使用了一个密钥进行了加密,所以客户端无法修改cookie数据(否则该数据将无效)。Play的session不是用来当作数据缓存。如果我们需要缓存与session相关的某些数据,可以使用Play内置的缓存机制,并使用 session.getId() 作为key来保存。

  举例:

  publicstaticvoid index() {  
  List messages = Cache.get(session.getId() + "-messages", List.class);  
  if(messages == null) {  
  // Cache miss 
  messages = Message.findByUser(session.get("user"));  
  Cache.set(session.getId() + "-messages", messages, "30mn");  
  }  
  render(messages);  
  }

  当我们关闭浏览器时,session数据将过期,除非我们通过 application.session.maxAge 进行了配置。

  缓存使用了与传统Servlet HTTP session不同的定义,所以我们不能假设这些数据只是在cache中。这将强迫我们处理cache中没有数据的情况,并强迫我们的程序是完全无状态的。

 
标签: Play Framework Java

Play Framework介绍:控制器层的更多相关文章

  1. Entity Framework介绍

    1.Entity Framework介绍 下图显示EF整体架构.现在我们来看看架构的各个组件: EDM(Entity Data Model): EDM由三个主要部分组成:概念模型,映射和存储模型.映射 ...

  2. 基于Vue的Quasar Framework 介绍 这个框架UI组件很全面

    基于Vue的Quasar Framework 介绍 这个框架UI组件很全面 基于Vue的Quasar Framework 中文网http://www.quasarchs.com/ quasarfram ...

  3. 8天掌握EF的Code First开发之Entity Framework介绍

    返回<8天掌握EF的Code First开发>总目录 本篇目录 Entity Framework概要 什么是ORM Entity Framework简史 Entity Framework具 ...

  4. Play Framework介绍:主要概念(转)

    Play Framework是一个Rails风格的Full-stack Java Web框架. MVC模型 Play应用遵循Web架构使用的MVC架构模式. 它将应用分离到不同的层中:表现层(Pres ...

  5. 转载 8天掌握EF的Code First开发之Entity Framework介绍

    转载原地址:  http://www.cnblogs.com/farb/p/IntroductionToEF.html Entity Framework概要 Entity Framework是微软的O ...

  6. 01 Django REST Framework 介绍

    01-Django REST Framework的介绍 Django REST框架是一个用于构建Web API的强大而灵活的工具包. 您可能希望使用REST框架的一些原因: 1. Web可浏览API对 ...

  7. 控制器层(Controllers)

    本章译者:@freewind 业务逻辑代码通常位于模型(model)层.客户端(比如浏览器)无法直接调用其中的代码,所以模型对象提供的功能,必须作为资源以URI方式暴露给外部. 客户端使用HTTP协议 ...

  8. asp.netMVC中,视图层和控制器层的传值

    Asp.Net Mvc 控制器与视图的数据传递 摘要:本文将讨论asp.net mvc框架中的数据传递. 数据传递也就是控制器和视图之间的交互,比如在视图中提交的数据,在控制器怎么获取,或者控制器从业 ...

  9. 【Robot Framework 介绍】总纲

    Robot Framework是一个由python构建的的开源的自动化测试框架,现在版本还在不停的更新中.由于它开源性,网上有大量的第三方接口和很多资料.下面提供两个比较官方的链接,有兴趣的同学可以直 ...

随机推荐

  1. dissmiss a UISearchBar with an SearchBarController

    If you want do dissmiss a UISearchBar with an SearchBarController, just use this Code: [self.searchD ...

  2. ios 数组排序

    第一种:利用数组的sortedArrayUsingComparator调用 NSComparator  示例: obj1和obj2指的是数组中的对象 //1.数组中存放的是字符 NSComparato ...

  3. Using the Cordova Camera API

    使用ionic开发一款android或ios应用,估计少不了使用到Camera API,这里记录下使用过程. 创建空的ionic应用 ionic start myTabs tabs 通过cd demo ...

  4. XPS 15 9530使用Windows10频繁发生Intel HD Graphics 4600驱动奔溃的一种解决方法

    本人使用XPS 15 9530.集成显卡为Intel HD Graphics 4600.操作系统Windows 10 Pro,使用过程当中经常会发生集成显卡奔溃的问题,错误提示如下: Display ...

  5. Unity 动画

    Unity 并没有自带建模工具. 3D建模工具 maya, 3dmax, blender Skinned Mesh Renderer Mesh Renderer Mesh Filter Modelli ...

  6. ActionLink()与jquery更好地结合建造MVC网页:

    众所周知,微软的MVC框架提供了一系列Helper以用于创建Ajax的网页. 但是,类似于Ajax.ActionLink()的方法创建的Ajax缺乏足够的灵活性,例如: 页面上有很多选项,我们需要根据 ...

  7. MongoDB入门一:安装与配置

    引言 ——妈妈说名字太长排在后面或在标题堆儿中容易被大家发现. MongoDB的名字来源与单词humongous(极大的,巨大无比的)有关,它是一个可扩展.高性能.开源的NoSQL数据库. 之所以在g ...

  8. Mergely – 免费的在线文档对比和合并工具

    任何类型的文件(无论是否代码),我们可能要比较不同的版本,看发生了什么变化. 有些编辑器都有这个内置功能,其中一些则没有. Mergely 是一个免费使用的 Web 应用程序,帮你你迅速作出文档的差异 ...

  9. 探秘重编译(Recompilations)(1/2)

    这篇文章我想谈下SQL Server里一个非常重要的性能调优话题:重编译(Recompilations) .当你执行非常简单的存储过程(使用临时表)时,就会发生.今天我想奠定SQL Server里重编 ...

  10. 分享一下我封装iOS自定义控件的体会,附上三个好用的控件Demo <时间选择器&多行输入框&日期选择器>

    前段时间有小伙伴问到我:"这样的控件该怎么做呢?",我感觉是个比较简单的控件,可能对于入行不久的同志思路没有很清晰吧.趁着最近工作不忙,就来这里分享一下我封装自定义控件的几点体会吧 ...