精华内容
下载资源
问答
  • 如果你是一名.NET程序员,并且之前使用过Vue、Angular或者React,而没有了解过Blazor或者没有了解过...Blazor框架能够让你使用C#代替JavaScript来编写代码,是不是很酷。我只能说跟着.NET走,没错!@Name@Ch...

    如果你是一名.NET程序员,并且之前使用过Vue、Angular或者React,而没有了解过Blazor或者没有了解过WebAssembly,现在看到下面这段代码,我估计你一定会被. NET的创新震撼到,其实当我第一次看到这段代码的时候,我确实很惊讶,原来前端还可以这样玩。Blazor框架能够让你使用C#代替JavaScript来编写代码,是不是很酷。我只能说跟着.NET走,没错!

    @Name

    @ChildContentHello
    @code {[Parameter]public string Name { get; set; }[Parameter]public RenderFragment ChildContent { get; set; }private void OnHello(){Console.WriteLine("Write to the console in C#! 'Hello' button was selected.");}}
    60832057cae71f9961b5725326394f33.png

    Blazor是什么?

    Blazor 是一个客户端Web框架,是一个使用 .NET生成交互式客户端 Web UI的框架

    • 使用 C#代替 JavaScript 来编写代码,建丰富的交互式UI。
    • 共享使用 .NET 编写的服务器端和客户端应用逻辑。
    • 将UI呈现为 HTML 和 CSS,以支持众多浏览器,其中包括移动浏览器
    • 受益于 .NET 的性能、可靠性和安全性。
    • 支持 Windows、Linux 和 macOS 上的 Visual Studio。

    Blazor有两种模式,一种是客户端模式,一种是服务端模式

    • 客户端模式(Blazor WebAssembly)

    Blazor WebAssembly是单页应用框架,用于使用 .NET 生成交互式客户端 Web 应用。

    WebAssembly 是开放的 Web 标准,是一种可以使用非 JavaScript 编程语言编写代码并且能在浏览器上运行的技术方案。现在各大主流的浏览器都支持该标准,无需安装任何插件(不像flash之流)。通过 WebAssembly,可在 Web 浏览器内运行 .NET 代码。

    60832057cae71f9961b5725326394f33.png
    • 服务端模式(Blazor Server)

    Blazor Server在 ASP.NET Core 应用中支持在服务器上托管Razor组件,通过SignalR(双向通讯)连接处理 UI更新。

    16c4a5ac9e614d05f45337d242e37657.png

    我们创建一个Blazor应用

    使用VisualStudio 2019进行Blazor项目创建:

    fb0a2607b7f31f3d55de0b5c2b03cbc6.png
    a4f757d9cd1e4e91833250a925d024ed.png

    Blazor应用的项目代码结构如下

    f0da5e2605b9fe9610cd22b328c84881.png

    运行Blazor项目,效果如下

    8ffbb5f5ed346b2a0c5b9002fd566992.png

    结语:本文是对Blazor技术做一个简单的介绍或者说是技术普及。关于Blazor的客户端模式和服务端模式,涉及的内容较多,如果有不清楚的地方,可以查阅微软的官方文档。

    展开全文
  • 之前在Web开发框架推导一文中我们一步步的搭建了一个开发框架。在当时的情况下,还算满足需求。但是随着项目的逐渐完善,需求变更的频度逐渐变得比新增需求的频度高,原来框架的弊端越来越明显,所以需要对框架进行...

    之前在Web开发框架推导一文中我们一步步的搭建了一个开发框架。

    ce0366eacb0f38afd701c93f897cfc49.png

    在当时的情况下,还算满足需求。但是随着项目的逐渐完善,需求变更的频度逐渐变得比新增需求的频度高,原来框架的弊端越来越明显,所以需要对框架进行升级改进。

    我们先来看原来框架的问题,然后基于这些问题,来对框架进行改进。

    原框架的问题

    • 代码生成问题
    • 参数传递问题
    • Service层问题
    • 测试依赖问题
    • Mapper.xml的问题

    代码生成问题

    在原框架中,我们基于各种约束,编写了一个代码生成组件,通过这个组件,我们可以针对选中的表来生成Controller,Service,Model,Mapper等一系列的类,也就是说,只要建完表,就可以直接生成一套CRUD,直接就可以启动并测试。这在项目初期看起来很美,但是在需求变动时,还是有很多的局限性。

    首先,生成的代码逻辑是固化的。如果稍微有些调整,就需要调整生成代码的组件,然后重新打包,上传到jar仓库,项目修改组件版本,再进行代码生成,整个流程过于繁琐。

    其次,为了方便代码的生成,其实是做了不少妥协的:

    • 为了方便在修改表字段以后,能够重新生成,很多类都抽象了一个基类用于操作Model字段。这些基类不能够手动修改,因为每次生成都会覆盖。这实际导致了类的数量的增多。
    • 生成的CRUD固化了,不能手动调整。如果生成的CRUD不满足需求,不能直接在代码上修改。只能拷贝一份进行修改,因为再次生成时会覆盖。这导致了代码的冗余。
    • Param和Result委托了Model,这在Model发生改变时,能在编译期就能知道对应字段的调整。但是也引入了不少问题,我们在「参数传递问题」一节单独讨论。

    参数传递问题

    当初为了便于代码的生成,决定Param和Result都继承Model,这导致了如下的一些问题:

    • 使得Param和Result都依赖了Model。但是Param和Result是视图层模型,而Model是持久层模型,两者的进化度并不是一致的。但是现在的继承关系导致了在默认情况下视图层模型的进化需要和持久层同步,当然你也可以手动调整Param和Result,但是这又导致了代码生成的优势没有了。
    • Param和Result通过委托的方式来设置字段,也就是说,它们实际是没有字段的,通过getter和setter将值设置到了Model中。这就没法使用lombok来简化getter和setter,使得Param和Result代码行数较多
    • 同时,对于swagger来说,有些注解需要基于字段,导致某些功能无法实现(例如:ModelAttribute),只能基于额外手段来处理(例如:需要通过ApiImplicitParams来实现字段文档)。
    • CRUD都是基于同一个Param和Result,导致前端的接口会显示很多无用的字段,加大前端理解接口的难度

    Service层问题

    Service层有如下问题:

    • Service层的职责过重,包括了事务处理、参数设置、业务逻辑
    • 导致Service中的代码是面条代码,不利于业务逻辑的理解
    • 同时事务注解是直接加在类上的,Spring的默认事务机制会导致类似如下代码的逻辑调用不会抛出期望的异常
    // PostServicepublic String savePost(Post post) { postRepository.save(post); for(PostDiscuss discuss : post.getDiscuss()) { // 这里是抓不到RuntimeException异常的,会是一个TransactionRollBack的异常 discussService.save(discuss); }}// discussServicepublic String savePost(PostDiscuss discuss) {  throw new RuntimeException("保存失败");}

    测试依赖问题

    核心的业务逻辑在Service中,测试还是需要依赖于Spring,当项目越来越大时,启动项目的时间越来越长,可能要1分钟甚至更长。这就导致单元测试效率越来越低。

    Mapper.xml的问题

    在面试的时候,我经常会问下面的一些问题:

    • Java里面接口的作用是什么?
    • Service、DAO为什么要编写接口,再去实现这个接口?
    • 接口和实现在相同的模块下,反正都要重新打包的。多写个接口不是多写了好几行代码吗?
    • 和上面类似的问题,Mybatis里面,声称将sql独立到了Mapper.xml文件中,使得可以不需要编译直接修改sql。但Mapper.xml都是和Class放在一起的,改了还是需要重新打包,而且Mybatis是不能动态加载Mapper.xml的,那把sql独立到XML里,到底有什么优势?

    对于最后一个问题,我的答案是,对于大部分项目来说,没什么优势项目易不易于部署、扩展,不在于你使用的框架,而在于你的设计

    就以Mapper.xml来说,Mybatis将sql与代码分离了,但是你在项目里还是将Mapper.xml和代码放在同一个模块下,那这个优势就没有了。既然没有这个优势,我们还有必要单独写Mapper.xml文件吗?我的选择是,那就不写了,直接使用Mybatis提供的注解。

    同时为了解决Service层对DAO层(这里也就是对Mybatis)的强依赖,对框架进行了一些改进,解耦Service和DAO层。具体见下面的改进方案。

    框架改进方案

    为了解决上面这些问题,对框架进行了如下调整:

    • 分离Param、Result和Model
    • 替换代码生成
    • 独立业务逻辑
    • Model层优化

    分离Param、Result和Model

    上面已经提到了Param、Result和Model强耦合会有很多问题,所以这里就将Param、Result和Model分离开。每个都是独立的Bean,这就解决了上面几个问题。但是引入了两个新问题:

    • 首先,很明显的,增加了手动编码的量。当一个表修改了字段,需要修改三个类甚至更多的类
    • 其次,增加了数据传递之间的代码。即Param传递到Model,需要对字段赋值。如果一个字段一个字段的设值,会增加很多无聊的代码。而使用反射的话会对性能有一些影响

    那如何解决这两个问题呢?首先,纯手撸肯定是不可能的。需要提供一些自动化手段。

    对于赋值来说,Spring提供了BeanUtils来简化处理,虽然是基于反射来设值的,但是对于现阶段来说,这点性能损耗还是没什么影响的。但是,BeanUtils对于不同类型的属性不能进行拷贝,假设我有一个Domain对象Book,里面有个字段Author,现在我要赋值给BookResult,其中有个字段AuthorResult,此时BeanUtils是无法赋值的。所以我编写了一个基于Gson的工具类来处理,性能测试10000次的属性拷贝BeanUtils需要500多毫秒,基于Gson的工具类只需要300毫秒左右。

    对于表字段的生成,如果使用的是IDEA的话,IDE默认提供了一个脚本,可以从表来生成POJO!我们可以使用这个脚本来生成Model,然后将字段拷贝到Param和Result中,来简化字段的编写。我对这个脚本进行了修改,以符合项目需求。主要增加了lombok的支持,新增了类注释和字段注释。

    替换代码生成

    对于上面代码生成组件的问题,我调整了代码生成的方式。不再基于组件来生成,而是基于IDEA本身的FileTemplate、LiveTemplate以及Scripted Extensions来进行生成。虽然这样的方式,不能够一次性生成多个文件,但是由于生成逻辑基本是一次性的,所以影响不是很大。在初次生成代码时,代码生成组件的效率是高于FileTemplate、LiveTemplate以及Scripted Extensions的组合,但是后期调整的灵活性,明显是FileTemplate、LiveTemplate以及Scripted Extensions的组合要高于代码生成组件的:

    • 首先,当文件结构调整时,只需要修改FileTemplate,并将配置文件导出给项目组成员即可。
    • 同样的,当LiveTemplate调整时,也只需要修改对应的LiveTemplate,并将配置文件导出给项目组成员即可。
    • 其次,想生成哪个文件,只要针对这个文件生成即可
    • 第三,通过FileTemplate生成完整的文件后,可以通过LiveTemplate快速的进行模块化的编码
    • 最后,FileTemplate可以设置为项目级别,即每个项目可以有独立的FileTemplate

    具体的操作流程,在下面演示。

    独立业务逻辑

    针对Service和测试的问题,将原来的Controller、Service和Model三层,拆分为四层:

    • Controller负责前端数据的接收和返回,以及统一异常处理
    • Service负责事务以及Domain层逻辑的组装。这里就不会出现事务嵌套问题,也就不会导致抓不到期望的异常的问题
    • Domain负责业务逻辑
    • Model负责数据持久

    这样Service的职责减轻了,同时不再有事务嵌套的问题。

    Model层优化

    上面提到,框架中最终放弃了Mapper.xml,转而使用Mybatis的注解来实现持久化操作。改用注解,规避了XML代码的编写,但是并没有解决框架对Mybatis的强依赖。所以这里在Domain中新增了Repository接口层,此层用于定义Domain的持久化操作,而Model层中对Repository进行实现,这里的实现就是Mybatis实现。这样做有两个好处:

    • 依赖倒置:原来是Domain依赖Model层,而现在是Model层依赖Domain层,这样当我要把Mybatis替换掉时,Domain完全无感知。
    • 独立测试:因为现在Domain不依赖于其它任何层,所以可以脱离数据库和容器来进行测试。使得测试的效率不会随着项目的开发而越来越低
    4a24aeea2a5757b122a82f22f75c9266.png

    框架改进细节

    现在已经知道了,如何对框架进行改进,我们现在就开始着手进行改造。其实主要的改造是对代码生成方式的改造,也就是编写FileTemplate、LiveTemplate和ScriptedExtensions。下面对这三个功能进行简单的说明,先说ScriptedExtensions。

    Scripted Extensions

    先来解释一下,什么是Scripted Extensions。我们都知道,现在的IDE都是插件式的,也就是说,我们可以通过开发商提供的插件开发包来开发插件,扩展现有的IDE功能。但是编写插件需要特定的开发环境,如果是一个很简单的功能,还要费劲去搭开发环境,挺麻烦的。所以IDEA提供了Scripted Extensions,可以理解为一个简化版的插件,就是可以通过脚本来扩展IDE功能。

    IDEA提供了Database功能,可以连接数据库进行相关操作。当你连接了数据库,在表上右击时,可以看到Scripted Extensions这个选项,里面有一个功能是可以基于表来生成POJO的groovy脚本。

    但是功能比较low:

    • 包名是写死的:com.sample
    • 没有生成table注释
    • 没有基于lombok来简化getter和setter

    不过好在,我们能基于这个脚本来自行修改,在刚才的Scripted Extensions菜单里,有个Go to Scripts Directory选项,点击后,可以进入脚本目录。

    c9d8a07bb6f2204ff91a5f2dd4838be2.png

    直接对这个groovy文件Ctrl+c,Ctrl-v,复制一份,重命名一下,基于这个脚本进行修改即可。具体怎么修改,按照自己的需求来,里面主要就是根据表信息对String的拼接而已。

    FileTemplate

    FileTemplate是IDEA提供的生成文件的模板,你在点击菜单的File->New...以后,出现的各种文件,都是基于FileTemplate来实现的。我们自定义的Controller、Service、Domain等类,都可以通过FileTemplate来简化创建。

    具体使用方式为,按下Ctrl-Alt-S呼出设置菜单,点击Editor->File And Code Template,在里面新增Template即可。

    85ef6a20bd0af852e3320a5f0faaec2e.png

    几点说明:

    • 下面的描述中列出了默认的一些参数以及作用
    • 你也可以自定义变量,自定义的变量如果没有赋值,在创建时会有输入框提示输入内容
    • 模板是基于Velocity的,所以如果你熟悉 Velocity,那就可以直接上手
    • Enable Live Template选项是在FileTemplate激活LiveTemplate变量,不过需要使用#[[]]#包裹。但是对于创建Java,这个功能有bug,并不能定位到需要的位置,所以暂时没使用

    创建完成后,就可以在New菜单中看到这个模板了。

    LiveTemplate

    LiveTemplate实际就是CodeSnippet。创建方式和FileTemplate类似。按下Ctrl-Alt-S呼出设置菜单,点击Editor->Live Template,在里面新增Template即可。

    f3ff3f79879ee442a8f20811dc99d281.png

    几点说明:

    • 这里的变量是使用$$包裹
    • 每个变量就是一个占位符,在使用tab展开后,可以手动输入值
    • 右下角的Edit variables,用于对变量赋值,IDEA提供了一些方法、也可以设置默认值
    • 下面的change链接,可以选择LiveTemplate生效的位置,比如只在Java类声明处生效

    编码流程

    创建了上面的几个模板后,编码流程如下:

    • 在表上右击,通过Scripted Extensions来生成Model
    • 通过FileTemplate来快速生成Controller、Service、Domain等类
    • 通过LiveTemplate来快速编写代码

    总结

    本文通过对原框架问题的梳理及解决,来对框架进行升级改造,以适应项目的发展和推进。

    展开全文
  • Flask简介Flask是一个相对于Django而言轻量级的Web框架。和Django大包大揽不同,Flask建立于一系列的开源软件包之上,这其中 最主要的是WSGI应用开发库Werkzeug和模板引擎Jinja:策略 :werkzeug和Jinja这两个库和...

    Flask简介

    Flask是一个相对于Django而言轻量级的Web框架。

    和Django大包大揽不同,Flask建立于一系列的开源软件包之上,这其中 最主要的是WSGI应用开发库Werkzeug和模板引擎Jinja:

    1e4b6b47c58e4fc88cf8e627b63228b6.png
    52e8d8908a4e3648a7db3911c2990049.png

    策略 :werkzeug和Jinja这两个库和Flask一样,都是pocoo团队开发的。这 或许体现了pocoo与Django竞争时关于生态的一种策略,这种策略的自然延伸是Flask框架中没有包含数据库方面的构件,无论ORM还是其他。

    关注点 :Flask是一个WSGI应用框架,这意味着我们进行Flask开发时,不需要 关注网络方面的操作,Flask应用的入口是封装过的网络请求包,出口是 网络响应,我们仅需要关注这个阶段内的处理逻辑。

    WSGI服务器 :Flask虽然内置了简单的WSGI服务器,但其性能仅仅适用于开发期的调试。 Flask官网推荐了多种WSGI服务器,实现方式从多进程到多线程到协程, 这方面的选择我们在本课程中将不涉及。

    REST适应性 :虽然Flask和Django一样,最初的出发点都是服务端的动态网页应用。但 Flask的设计使之也相当适用于面向资源的REST架构,在越来越移动化 并且单页应用越来越重要的WEB开发领域,这是Flask相对于Django相当 大的优势。

    这里推荐大家一个学习flask的教程,本文的内容也是转自该教程。

    Hello Flask

    编写一个基于Flask的hello world相当容易:

    1、导入Flask类

    from flask import Flask

    Flask类是Flask框架的核心类,它实现了WSGI应用规范。

    2、创建Flask实例

    app = Flask(__name__)

    Flask构造函数的第一个参数指定一个引入名/importname。Flask框架 使用这个名字进行静态资源、模板、错误信息的定位。除非你清楚的理解它的 作用,通常情况下,我们总应该使用特殊变量_name。

    Flask实例是可调用的(具有call方法),这个实例可以直接对接 WSGI服务器。

    3、注册路由

    @route('/')def index():    return 'Hello,Flask!'

    注册路由就是建立URL规则和处理函数之间的关联。Flask框架依赖于路由 完成HTTP请求的分发。

    路由中的函数被称为视图函数,其返回值将作为HTTP响应的正文内容。

    4、对接并启动WSGI服务器

    Flask封装了一个简单的开发用WSGI服务器,我们可以通过调用run() 启动服务器运行:

    app.run(host='0.0.0.0',port=80)

    概述

    路由是MVC架构的Web框架中相当重要的一个概念,也是本节课程的重点。

    顾名思意,路由就是在迷茫中找出一条路的意思。在Flask框架中的路由就表示为用户请求的URL找出其对应的处理函数之意。

    5593338fd63f480d7b4cb92daec0eb15.png

    在本节课程,我们将主要从以下几个方面讲解Flask框架中的路由:

    如何为应用注册路由? 如何为路由指定其支持的HTTP方法? 如何匹配动态URL? 如何对URL中的变量类型进行过滤? 如何理解访问点/endpoint? 如何为应用设定静态路由? 如何避免硬编码指向其他视图的URL?

    注册路由

    在Flask应用中,路由是指用户请求的URL与视图函数之间的映射。Flask框架 根据HTTP请求的URL在路由表中匹配预定义的URL规则,找到对应的视图函数, 并将视图函数的执行结果返回WSGI服务器:

    cc522659a7b304d27cb01f728d85a4c2.png

    可见路由表在Flask应用中处于相当核心的位置。路由表的内容是由应用开发者填充。

    route装饰器 :可以使用Flask应用实例的route装饰器将一个URL规则绑定到 一个视图函数上。

    例如,下面的示例将URL规则/test绑定到视图函数test()上:

    @app.route('/test')def test():    return 'this is response'

    如果这个应用部署在主机ezhost.com的根目录下,那么当用户访问:

    http://ezhost.com/teset

    Flask框架就会调用我们的test()函数,其返回结果就传递给WSGI服务器发送给访问者。

    add_url_rule() :另一种等价的写法是使用Flask应用实例的add_url_route()方法。 下面的示例注册了一个与前例相同的路由:

    def test():    return 'this is response'app.add_url_route('/test',view_func=test)

    其实,route装饰器内部也是通过调用add_url_route()方法实现的路由注册。 但是显然,使用装饰器使代码看起来更优雅一些。

    为路由指定HTTP方法

    默认情况下,Flask路由仅支持HTTP的GET请求。可以使用methods关键字参数,在注册 路由时显式地声明视图方法支持的HTTP方法。

    例如,下面的示例将URL规则/auth绑定到视图函数v_auth(),这个路由仅支持POST方法:

    @app.route('/auth',methods=['POST'])def v_auth():pass

    指定多种HTTP方法支持

    关键字参数methods的类型为list,因此可以同时指定多种HTTP方法。

    下面的示例中,使URL规则/user同时支持POST方法和GET方法:

    @app.route('/user',methods=['POST','GET'])def v_users():    if request.method == 'GET':        return ... # 返回用户列表    if request.method == 'POST'        return ... #创建新用户

    这个特性使Flask非常易于开发REST架构的后台服务,而不仅仅局限于传统的动态网页。

    匹配动态URL

    有时我们需要将同一类URL映射到同一个视图函数处理,比如,使用同一个视图函数 来显示不同用户的个人档案。我们希望以下的URL都可以分发到同一个视图函数:

    430a2b612252d9166da5cbd9442ad637.png

    在Flask中,可以将URL中的可变部分使用一对小括号<>声明为变量, 并为视图函数声明同名的参数:

    @app.route('/user/')def v_user(uname):    return '%s's Profile' % uname

    在上面的示例中,URL规则中的表示这部分是可变的,Flask将提取用户请求的 URL中这部分的内容,并作为视图函数v_user()的uname参数进行调用。

    URL变量类型过滤

    考虑下面的示例,我们希望通过HTTP共享文件夹/var/readonly中的文件:

    /var    /readonly        /a.txt        /b.txt        /repo           /c.txt           /d.txt

    简单思考一下就有答案了。我们可以构造URL规则/file/,然后直接 读取文件内容返回给用户。注册如下的路由:

    @app.route('/file/')def v_file(fname):    fullname = os.path.join('/var/readonly',fname)    f = open(fullname)    cnt =  f.read()    f.close()    return cnt

    测试结果表明,/file/a.txt和/file/b.txt都没有问题,但是/file/repo/c.txt和 /file/repo/d.txt却会失败。

    这是因为,默认情况下,在URL规则中的变量被视为不包含/的字符串。/file/repo/c.txt 是没有办法匹配URL规则/file/的。

    可以使用内置的path转换器告诉Flask框架改变这一默认行为。path转换器允许 规则匹配包含/的字符串:

    @app.route('/file/')

    在Flask中,转换器/converter用来对从URL中提取的变量进行预处理,这个过程 发生在调用视图函数之前。Flask预置了四种转换器:

    1. string - 匹配不包含/的字符串,这是默认的转换器
    2. path - 匹配包含/的字符串
    3. int - 只有当URL中的变量是整型值时才匹配,并将变量转换为整型
    4. float - 只有当URL中的变量是浮点值时才匹配,并将变量转换为浮点型

    访问点/endpoint

    我们一直强调,路由的作用是根据请求的URL,找到对应的视图函数。这没错,但是在 Flask框架中,请求任务的分发并不是直接从用户请求的URL一步定位到视图函数, 两者之间隔着一个访问点/endpoint。

    以下面的代码为例,我们看Flask怎样实现请求的分发:

    @app.route('/home')def home():pass

    在Flask内部使用两张表维护路由:

    • url_map :维护URL规则和endpoint的映射
    • view_functions :维护endpoint和视图函数的映射。

    以用户访问URL/home为例,Flask将首先利用url_map找到所请求URL对应的 endpoint,即访问点home,然后再利用view_functions表查找home这个访问点 对应的视图函数,最终匹配到函数home():

    d46ab2825dbe9da9c64f0ac4a36728af.png

    默认访问点 :当我们使用route装饰器注册路由时,默认使用被装饰函数的 函数名(name)作为访问点,因此,你看到上面的表中,路由中的访问点为home。

    自定义访问点 :可以在使用route装饰器或调用add_url_rule()方法注册路由时,使用 endpoint关键字参数改变这一默认行为:

    @app.route('/home',endpoint='whocare')def home():pass

    此时的两张路由表将变成这样:

    b6f5f1df34250d1a1aaa7cf7e23118ab.png

    静态目录路由

    当创建应用实例时,Flask将自动添加一条静态目录路由,其访问点 始终被设置为static,URL规则默认被设置为/static,本地路径默认被 设置为应用文件夹下的static子文件夹:

    +------------------------------------------------------------+ | url rule | endpoint | view_function | | /static | static | Flask.send_static_file | +------------------------------------------------------------+ 如果你的应用目录如下:

    /app    /web.py    /static        /main.css        /jquery.min.js   

    那么启动应用后就可以通过URL/static/main.css访问static文件夹下的main.css了。

    除了访问点被固定为static,静态目录的URL规则和本地目录都是可以根据应用情况进行调整。

    改变默认的本地路径 :可以在创建应用对象时使用关键字参数static_folder改变 默认的静态文件夹。例如,你的静态文件都存放在应用下的assets目录下, 那么可以按如下的方式创建应用对象:

    app = Flask(name,static_folder='assets') 也可以使用一个绝对路径:

    app = Flask(name,static_folder='/var/www/static') 改变默认的本地路径并不会对路由表产生影响。

    改变默认的URL规则 : 如果不喜欢静态目录URL/static,也可以在创建应用 对象时使用关键字参数static_url_path换一个别的名字。

    下面的示例中,将应用下的assets文件夹注册为静态目录/assets:

    app = Flask(name,static_folder='assets',static_url_path='/assets') 当应用运行后,通过URL/assets/main.css就可以访问assets文件夹下的 main.css文件了。

    这时的路由表变化为:

    +------------------------------------------------------------+ | url | endpoint | view_function | | /assets | static | Flask.send_static_file | +------------------------------------------------------------+

    构造URL

    在一个实用的视图中,不可避免地存在指向其他视图的链接。在之前的课程示例中,我们 都是在视图函数中这样硬编码这些链接URL的:

    @app.route('/')def v_index():    return 'tech'@app.route('/tech') def v_tech():pass

    大部分情况下这种硬编码URL是可以工作的。但如果这个应用被挂在WSGI服务器的一个 子路径下,比如:/app1,那么用户访问URL/tech是不会成功的,这时应当访问/app1/tech 才可以正确地路由到视图函数v_tech()。

    我们应当使用访问点让Flask框架帮我们计算链接URL。简单地给url_for()函数传入 一个访问点,它返回将是一个可靠的URL地址:

    @app.route('/')def v_index():    print url_for('v_contacts')  # /contact    return 'see console output!'@app.route('/contact')def v_contacts():pass

    添加查询参数 : 使用关键字参数,可以在构造的URL中生成查询串。下面的调用将生成 /contact?format=json

    @app.route('/')def v_index():    print url_for('v_contacts',format='json')    return  ''@app.route('/contact') def v_contacts():pass

    添加URL变量 : 如果指定访问点对应的视图函数接收参数,那么关键字参数将生成对应的参数URL。下面的 示例将生成/contact/Julia?format=html:

    @app.route('/')def v_index():    print url_for('v_contact',name='Julia',format='html')    return ''@app.route('/contact/')def v_contact(name):pass

    添加锚点 :使用_anchor关键字可以为生成的URL添加锚点。下面的示例将生成URL /contact#part2

    @app.route('/')def v_index():    print url_for('v_contacts',_anchor='part2')@app.route('/contact')def v_contacts():pass

    外部URL : 默认情况下,url_for()生成站内URL,可以设置关键字参数_external 为True,生成包含站点地址的外部URL。下面的示例将生成URLhttp:///contacts:

    @app.route('/')def v_index():    print url_for('v_contacts',_external=True)@app.route('/contact')def v_contacts():pass
    展开全文
  • 一、Web框架首先我们今天要做的事是开发一个Web框架。可能听到这你就会想、是不是很难啊?这东西自己能写出来?如果你有这种疑惑的话,那就继续看下去吧。相信看完今天的内容你也能写出一个自己的Web框架。1.1、Web...

    一、Web框架

    首先我们今天要做的事是开发一个Web框架。可能听到这你就会想、是不是很难啊?这东西自己能写出来?

    如果你有这种疑惑的话,那就继续看下去吧。相信看完今天的内容你也能写出一个自己的Web框架。

    1.1、Web服务器

    要知道什么是Web框架首先要知道Web服务器的概念。Web服务器是一个无情的收发机器,对它来说,接收和发送是最主要的工作。在我们用浏览器打开网页时,如果不考虑复杂情况,我们可以理解为我们在向服务器要东西,而服务器接到了你的请求后,根据一些判断,再给你发送一些内容。

    仔细一想,其实这一个套接字(Socket)。

    1.2 Web框架

    那Web框架是什么呢?Web框架其实就是对Web服务器的一个封装,最原始的服务器只有一个原生的Socket,它可以做一些基本的工作。但是想用原生Socket做Web开发,那你的事情就多了去了。

    而Web框架就是对Socket的高级封装,不同的Web框架封装程度不同。像Django是封装地比较完善的一个框架,而Flask则要轻便得多。

    那他们只会封装Socket吗?我们接着往下看!

    1.3 MVC和MTV

    现在大多数框架都是MCV模式或者类MCV模式的。那MCV的含义是什么呢?具体含义如下:

    1.model:模型层2.view:视图层3.controller:控制层(业务逻辑层)

    下面我们来具体解释一下:

    模型很好理解,就是我们常说的类,我们通常会将模型和数据库表对应起来。

    视图层关注的是我们展示的页面效果,主要就是html、css、js等。

    控制层,其实把它称作业务逻辑层要更好理解。也就是决定我要显示什么数据。

    如果拿登录的业务来看。数据库中用户表对应的类就是属于模型层,我们看到的登录页面就是视图层,而我们处理判断登录的用户名密码等一系列内容就是业务逻辑层的内容。

    那MTV又是什么呢?其实MTV就是MCV的另一种形式,model是一样的,而T的含义是Template,对应View。比较奇怪的就是MTV中的View,它对应Controller。

    其实MVC和MTV没有本质区别。

    1.4、框架封装的内容

    在大多数框架中我们都不会去关注Socket本身,而更多的是去关注MTV三个部分。在本文,我们会去自己实现Template和View两个部分。

    Template部分很好理解,就是我们通常的html页面。但是我们最终要实现的是动态页面(页面中的数据是动态生成的),因此我们需要对传统的html页面进行一些改造。这部分的工作需要我们定义一些特征标记,以及对html进行一些渲染工作。

    而View部分我们会实现两个功能,一个是路由分发,另一个是视图函数。

    路由分发的工作就是让我们对应不同的url,响应不同的内容。比如我请求http://www.test.com/login.html会返回登录页面,如果请求http://www.test.com/register.html则返回注册页面。

    而视图函数则是针对每个请求的处理。后面我们会再提到。

    知道了上面这些知识后,我们就可以着手开发我们的Web框架了。

    二、实现一个Web服务器

    服务器是Web框架的基础,而Socket是服务器的基础。因此我们还需要了解一下Socket的使用。

    2.1 socket的使用

    在python中socket的操作封装在socket.socket类中。我们先看下面这段代码,如何再来逐一解释:

    import socket# 创建一个服务端socketserver = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 绑定ip和端口server.bind(('127.0.0.1', 8000))# 监听是否有请求server.listen(1)# 接收请求conn, addr = server.accept()# 接收数据data = conn.recv(1024)print(addr)print(data.decode('utf-8'))

    在我们做操作前,我们需要创建一个socket对象。在创建时我们传入了两个参数,他们规定了如下内容:

    1.socket.AF_INET:ipv42.socket.SOCK_STREAM:TCP协议

    有了socket对象后,我们使用bind方法绑定ip和端口。其中127.0.0.1表示本机ip,绑定后我们就可以通过指定ip和端口访问了。

    因为是服务器,所以我们需要使用listen监听是否有请求,listen方法是阻塞的,他会一直等待。

    当监听到请求后,我们可以通过accept方法接收请求。accept方法会返回连接和请求的地址信息(ip和端口)。

    然后通过conn.recv就可以获取客户端发来的数据了。recv方法中传入的参数是接收的最大字节数。

    在网络传输过程中,数据都是二进制形式传输的,因此我们需要对数据进行解码。

    我们可以编写一个client来测试一下:

    import socketclient = socket.socket(socket.AF_INET, socket.SOCK_STREAM)client.connect(('127.0.0.1', 8000))client.send("你好".encode('utf-8'))

    我们依次运行服务端,和客户端。会发现客户端输出如下内容:

    ('127.0.0.1', 49992)你好

    可以看到客户端成功将数据发送给了服务端。

    2.2、实现Web服务器

    上面只是简单看一下socket的使用,那种程度的服务器还不能满足我们网站的需求。我们来看看它有些上面问题:

    1.没有响应2.只能接收一个请求

    关于没有响应的问题很好解决,我们只需要在服务端加下面两句代码:

    conn.send('你好'.encode('utf-8'))conn.close()

    现在我们运行服务端,客户端你已经可以删除了。因为微软已经帮我们实现了一个客户端,就是鼎鼎大名的IE浏览器。我们打开IE浏览器在url输入:http://127.0.0.1:8000/就可以看到如下页面:

    6e7260400c55e0e077b9cb0e103c9bc9.png

    可能有些人就发现了,这个其实是用utf-8编码然后用gbk解码的“你好”。这个其实就是我们编写的服务器返回的内容。

    但是如果你再次访问这个页面,浏览器就会无情地告诉你“无法访问此页面”。因为我们服务端已经停止了,我们可以给我们的服务器加个while循环:

    import socketserver = socket.socket(socket.AF_INET, socket.SOCK_STREAM)server.bind(('127.0.0.1', 8000))server.listen(5)while True:    conn, addr = server.accept()    data = conn.recv(1024)    conn.send("你好".encode('gbk'))    conn.close()

    这样我们就可以一直访问了。但是实际上它还是有问题,因为它同一时间只能接收一个连接。想要可以同时接收多个连接,就需要使用多线程了,于是我我把服务端修改为如下:

    import socketfrom threading import Threaddef connect(conn):    data = conn.recv(1024)    conn.send("你好".encode('gbk'))    conn.close()def run_server():    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    server.bind(('127.0.0.1', 8000))    server.listen(5)    while True:        conn, addr = server.accept()        t = Thread(target=connect, args=(conn, ))        t.start()run_server()

    我们在accept部分不停地接收连接,然后开启一个线程给请求返回数据。这样我们就解决了一次请求服务器就会停止的问题了。为了方便使用,我用面向对象的方式重写了服务器的代码:

    import socketfrom threading import Threadclass Server(socket.socket):    """    定义服务器的类    """    def __init__(self, ip, host, connect_num, *args, **kwargs):        super(Server, self).__init__(*args, **kwargs)        self.ip = ip        self.host = host        self.connect_num = connect_num    @staticmethod    def conn(conn):        # 获取请求参数        request = conn.recv(1024)        conn.send("你好".encode('gbk'))        conn.close()    def run(self):        """        运行服务器        :return:        """        # 绑定ip和端口        self.bind((self.ip, self.host))        self.listen(self.connect_num)        while True:            conn, addr = self.accept()            t = Thread(target=Server.conn, args=(conn,))            t.start()

    这样我们只需要编写下面的代码就能运行我们的服务器了:

    import socketfrom server import Servermy_server = Server('127.0.0.1', 8000, 5, socket.AF_INET, socket.SOCK_STREAM)my_server.run()

    现在我们的服务端写好了,我们再来关注一下Template和View部分。

    三、模板

    在上面我们的服务端只返回了一个简单的字符串,下面我们来看看如何让服务器返回一个html页面。

    3.1 返回html页面

    其实想要返回html非常简单,我们只需要先准备一个html页面,我们创建一个template模板,并在目录下创建index.html文件,内容如下:

            Title    

    Do not go gentle into that good night!

    我们只写了一个h标签,然后在Server类中的conn方法做一点简单的修改:

    @staticmethoddef conn(conn):    # 获取请求参数    request = conn.recv(1024)    with open('template/index.html', 'rb') as f:        conn.send(f.read())        conn.close()

    我们把原本发送固定字符串改成了发送从文件中读取的内容,我们再次在IE中访问http://127.0.0.1:8000/可以看到如下页面:

    e7bd7f8e47757f3919ac747f47d930f8.png

    这样我们想要返回不同的页面只需要修改html文件就好了。但是上面的方式还不能让我们动态地显示数据,因此我们还需要继续修改。

    3.2 模板标记

    想要动态显示数据,我们肯定需要对html的内容进行二次处理。为了方便我们二次处理,我们可以定义一些特殊标记,我们把它们称作模板标记。比如下面这个html文件:

            Title    

    username: %%username%%

    password: %%password%%

    其中%%username%%就是我们定义的模板标记。我们只需要在服务端找到这些标记,然后替换就好了。于是我将conn方法修改为如下:

    @staticmethoddef conn(conn):    # 获取请求参数    request = conn.recv(1024)    html = render('template/index.html', {'username': 'zack', 'password': '123456'})       conn.send(html.encode('gbk'))    conn.close()

    这次我们不再是直接把html的内容发送出去了,而是把模板的路径交由render函数进行读取并渲染。我们来看看render函数:

    def render(template_path, params={}):    with open(template_path, 'r') as f:        html = f.read()    # 找到所有模板标记    if params:        markers = re.findall('%%(.*?)%%', html)        for marker in markers:            tag = re.findall('%%' + marker + '%%', html)[0]            if params.get('%s' % marker):                html = html.replace(tag, params.get('%s' % marker))            else:                html = html.replace(tag, '')    return html

    我们的render函数接收两个参数,分别是模板路径代码,和用来渲染的参数。我们使用正则表达式找出特殊标记,然后用对应的变量进行替换。最后再把渲染后的结果返回,我们来访问一下:http://127.0.0.0:8000/可以看到如下页面:

    72155855f082c21893dc2425e3a27de5.png

    可以看到我们渲染成功了。在我们知道如何渲染页面后,我们就可以从数据库取数据,然后再渲染到页面上了。不过这里就不再细说下去了。

    四、路由系统和视图函数

    在上面的例子中,我们都是只能返回一个页面。接下来我们就来实现一个可以根据url来返回不同页面的框架。

    4.1 路由系统

    其实路由系统就是一个url和页面的对应关系,为了方便修改,我们另外创建一个urls.py文件,内容大致如下:

    from views import *urlpatterns = {    '/index': index,    '/login': login,    '/register': register,    '/error': error}

    在里面我们写了一个字典。而且还导入了一个views模块,这个模块我们稍后会创建。

    我们来看看它的作用,首先我们需要知道,字典的值是一个个函数。知道这点后我们就能很简单地猜测到,其实这个urlpatterns就是url和函数的对应关系。下面我们来把views模块创建一下。

    4.2 视图函数

    我们的视图视图函数通常需要一个参数,就是我们的请求内容。我们可以封装成一个request类,我为了方便就直接接收字符串:

    import redef render(template_path, params={}):    with open(template_path, 'r') as f:        html = f.read()    # 找到所有模板标记    if params:        markers = re.findall('%%(.*?)%%', html)        for marker in markers:            tag = re.findall('%%' + marker + '%%', html)[0]            if params.get('%s' % marker):                html = html.replace(tag, params.get('%s' % marker))            else:                html = html.replace(tag, '')    return htmldef index(request):    return render('template/index.html', {'username': 'zack', 'password': '123456'})def login(request):    return render('template/login.html')def register(request):    return render('template/register.html')def error(request):    return render('template/error.html')

    我们在视图函数定义了一系列函数,这样我们就可以针对不同的url发送不同的响应了。另外我把render函数移到了views模块。

    那我们要怎样才能让视图函数来处理不同的请求呢?这个时候我们就需要想一下谁是第一个拿到请求的。我想你应该也想到了,就是我们的Socket服务器,所有我们还要回到Server类。

    4.3、请求参数

    我们到现在还没有看到IE给我们服务器发的东西,现在我们来看一看:

    b'GET / HTTP/1.1\r\nAccept: text/html, application/xhtml+xml, image/jxr, */*\r\nAccept-Language: zh-Hans-CN,zh-Hans;q=0.5\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko\r\nAccept-Encoding: gzip, deflate\r\nHost: 127.0.0.1:8000\r\nConnection: Keep-Alive\r\n\r\n'

    把上面的内容整理后:

    b'GET / HTTP/1.1\r\nAccept: text/html, application/xhtml+xml, image/jxr, */*\r\nAccept-Language: zh-Hans-CN,zh-Hans;q=0.5\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko\r\nAccept-Encoding: gzip, deflate\r\nHost: 127.0.0.1:8000\r\nConnection: Keep-Alive\r\n\r\n'

    可以看到它们都是由\r\n拆分的字符串,而且在第一行就有我们的请求url的信息。可能你看不出,但是经验丰富的我一眼就看出了,因为我请求的url是http://127.0.0.1:8000/,所以我们的请求url是/。

    知道这些后,那接下来的工作就是字符串处理了。我把Server的conn函数修改为如下:

    @staticmethoddef conn(conn):    # 获取请求参数    request = conn.recv(1024)    # 在request中提取method和url    method, url, _ = request.decode('gbk').split('\r\n')[0].split(' ')    # 在路由系统中找到对应的视图函数,并把请求参数传递过去    html = urls.urlpatterns.get(url)(request)    conn.send(html.encode('gbk'))    conn.close()

    我们先是通过字符串分割的方式提取出url,然后在路由系统中匹配视图函数,把请求参数传递给视图函数,视图函数就会帮我们渲染一个html页面,我们把html返回给浏览器。这样我们就实现了一个相对完整的web框架了!

    当然,这个框架是不能使用到生成中的,大家可以通过这个案例来理解Web框架的各个部分。

    可能有些机智的读者尝试用Chrome或者Edge浏览器访问上面的服务器,但是却被拒绝了。

    因为我们的响应信息只是并没有包含响应头,Chrome认为我们响应的东西是不正规的,因此不让我们访问。大家可以尝试着自己解决一下这个问题。

    今天的内容就到这里了!感兴趣的读者可以关注公众号“新建文件夹X”,感谢阅读。

    项目代码已上传GitHub:https://github.com/IronSpiderMan/WebFrame

    展开全文
  • Django带有易于使用的轻型引擎来发送电子邮件。与Python类似,您只需要导入smtplib。在Django中,您只需要导入django.core.mail。要开始发送电子邮件,请编辑您的项目settings.py文件并设置以下选项-EMAIL_HOST -...
  • Scott 表示,.NET 5 是 .NET Framework 和 .NET Core 的未来,最终将成为一个统一平台,.NET5 将包含 ASP.NET 核心、实体框架核心、WinForms、WPF、Xamarin和ML.NET。 .NET Framework将长期受...
  • 学习目标了解什么是响应式 Web 开发框架 WebFlux编写第一个 WebFlux 示例程序(一)WebFlux 简介 Spring WebFlux is a non-blocking web framework built from the ground up to take advantage of multi-core,next-...
  • Iris框架设置操作路由组的使用 在实际开发中,我们通常都是按照模块进行开发,同一模块的不同接口url往往是最后的一级url不同,具有相同的前缀url。因此,我们期望在后台开发中,可以按照模块来进行处理我们的请求,...
  • 使用Spring Boot、Shiro、MyBatis、Redis、Bootstrap、Vue2.x等框架,包含:管理员列表、角色管理、菜单管理、定时任务、参数管理、代码生成器、日志管理、云存储、API模块(APP接口开发利器)、前后端分离等。...
  • 介绍Java于1996年1月23日发布,并于2020年庆祝其成立24周年。根据TIOBE指数,它一直位居第一... 如果要使用Java开发下一个Web应用程序,则在选择适当的Java Web框架时需要做出正确的选择。您是否想知道如何为项目选...
  • 在实际的项目开发中,我们会经常有业务场景使用到Session...Session与Cookie的区别在学习web开发过程中,我们总会和session和cookie打交道。本节课我们学习sesion相关的知识,因此有必要先通过讲解对比一下session和...
  • 作者 | Joe Gregorio译者 | 王强编辑 | ...本文作者与大家分享了在零框架下,近五年来只使用 Web 组件开发的经验。大约 5 年前开始我就不再用 JS 框架了,最近 Jon Udell 问我近况如何:译文:4 年前 bitworkin...
  • 开源最前线(ID:OpenSourceTop) 猿妹编译链接:https://opensource.com/article/20/4/open-source-css-frameworks大多数人想到Web开发时,通常会想到HTML或JavaScript,往往忽略了CSS,根据Wikipedia的说法,CSS既是...
  • 注意:不用perl语言,使用python、java、go等开发web的同样适用,均可以读一下。Mojolicious专栏很长时间没更新了。原因种种(主要是自己开了一个小网站,在研究着写点Perl语言的初学教程。还有就是今年主要在用...
  • 项目名称:Enterprise Solution 技术参数 1 C# ASP.NET Web Forms技术,借助于ExtAspNet控件,快速开发 2 SQL Server 数据库。使用LLBL Gen ORM框架进行数据读写,可支持任意的数据库 3 基于LLBL Gen ORM 框架,减少...
  • Go 开发环境这个系列基于 Go1.14.4,Linux 系统。安装 Go 的方式有很多,可以在这里 https://studygolang.com/dl 下载对应操作系统的版本。我自己比较喜欢通过源码安装。我将我的方式告诉大家。(Windows 环境比较...
  • Enterprise Solution 支持用户自定义查询(query and lookup),并把查询query定义为一个标准功能,查找...再到查询设计器中,选取相应的对象,设计关联,Web框架可以解析此查询,变成一个页面功能。 在客户页
  • 今年年初,微软 .NET ...Scott 表示,.NET 5 是 .NET Framework 和 .NET Core 的未来,最终将成为一个统一平台,.NET5 将包含 http://ASP.NET 核心、实体框架核心、WinForms、WPF、Xamarin和http://ML.NET。.NET ...
  • 玩技术的我们都知道,一个性能优越、成熟的框架是非常重要的,他可以为我们省下充足的时间,用于业务开发。C#ABP搭建API框架首先,在官网生成项目,并下载源码上篇已经告知官网地址...
  • Enterprise Solution Web部分目前只实现了对RDLC的支持。实现一种报表格式的支持,需要做一些基础的工作以便于与系统紧密的集成。 绑定数据源 首先看一下,我们在要报表中使用RDLC报表,经过设计报表文件,添加报表...
  • Enterprise Solution 支持用户自定义查询(query and lookup),并...再到查询设计器中,选取相应的对象,设计关联,Web框架可以解析此查询,变成一个页面功能。 在客户页面中,添加TriggerBox,它的后面会显示一个小...
  • Web框架要达到快速开发,又便于维护,进行了一系列的努力。 请看最初始的ASP.NET页面,对数据进行操作的代码,页面的基本代码如下所示 protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,493
精华内容 1,397
热门标签
关键字:

asp.netweb开发框架