ASP.NET MVC 搭建博客 Song Of The Sea

第五个博客上线了!

这次使用的是ASP.NET MVC + Bootstrap3 + SignalR + 百度地图api + ajax无刷新更新页面等等….

目标: Demo : http://hsdog.be-sunshine.cn

ASP搭建的一个博客(部分重构之前python写的博客,大部分新添加的内容):一只划水狗

但本网站仅供交流和娱乐学习用,因为没有对标签进行处理,所以很容易就会被攻陷的.回头有空了在添加标签过滤功能.

🙂

代码等回头有空的时候放上来~

预览:



ASP.NET API2 设计 RESTful API

Copyright @ DocsASP.NETASP.NET Web API

微软的官方文档

首先是新建ASP.NET api项目

然后是设计Model

Product.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace WAPI.Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Category { get; set; }
        public decimal Price { get; set; }
    }
}

编写Controller(路由)

ProductsController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;

using WAPI.Models;

namespace WAPI.Controllers
{
    public class ProductsController : ApiController
    {
        // XXM => decimal类型小数
        Product[] products = new Product[]{
            new Product{ Id=1,Name="Tomato Soup",Category="Groceries",Price=1},
            new Product{ Id=2,Name="Yo-yo",Category="Toys",Price=3.75M},
            new Product{ Id=3,Name="Hammer",Category="Hardware",Price=16.99M}
        };

        public IEnumerable<Product> GetAllProducts()
        {
            return products;
        }

        public IHttpActionResult GetProduct(int id)
        {
            //容器.FirstOrDefault(Boolean) 返回满足条件的第一个元素,如未找到,返回默认值
            var product = products.FirstOrDefault((p) => p.Id == id);
            if (product == null)
            {
                //ApiController.NotFound()
                //即404界面
                return NotFound();
            }
            //正常返回
            return Ok(product);
        }
    }
}

最后通过编写前端界面,并使用JQuery来实现ajax传输,显示

index.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>Product App</title>
</head>
<body>
    <div>
        <h2>All Products</h2>
        <ul id="products"/>
    </div>
    <div>
        <h2>Search by ID</h2>
        <input type="text" id="prodId" size="5"/>
        <input type="button" value="Search" onclick="find();"/>
        <p id="product"/>
    </div>

    <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.0.3.min.js"></script>
    <script>
        var uri = 'api/products';

        (document).ready(function () {             //Send an AJAX request             .getJSON(uri)
                .done(function (data) {
                    //On success,'data' contains a list of products.
                    .each(data, function (key, item) {                         //Add a list item for the product。                         ('<li>', { text: formatItem(item) }).appendTo(('#products'));                     });                 });         });           function formatItem(item) {             return item.Name + ': ' + item.Price;
        }

        function find() {
            var id = ('#prodId').val();             .getJSON(uri + '/' + id)
                .done(function (data) {
                    ('#product').text(formatItem(data));                 })                 .fail(function (jqXHR, textStatus, err) {                     ('#product').text('Error: ' + err);
                });
        }
    </script>
</body>
</html>

结果如下所示

Python Flask 10 用户上传头像

借助上节的基础==>

2.

3.

4.

开始!

1:

因为我们需要对每个用户存下他的头像信息.
这里我们选择直接存储图片url的形式进行存储.
所以我们需要对model层User添加一个gravatar列,存储头像url.
主要代码如下:

class User(UserMixin,db.Model):
    __tablename__ = 'users'
    gravatar = db.Column(db.String(200),default='/_uploads/photos/default.jpg')

其中gravatar的默认值是默认头像相对url.

2:

首先我们依然需要先码出上传头像界面的表单(form).

先配置所要上传的目标地址变量/config.py:

import os
UPLOADED_PHOTOS_DEST = os.path.abspath(os.path.join(os.getcwd(),"app/static/Gravatar"))

我们需要对上传文件进行约束为图片格式,这里我们用一个UploadSet模块,编辑代码/app/__init__.py:

from flask_uploads import UploadSet, configure_uploads, IMAGES, patch_request_class

photos = UploadSet('photos',IMAGES)

然后将创建好的photo对象加入已经创建好的app中:

configure_uploads(app,photos)
patch_request_class(app)  

然后编写form表单:

from flask_wtf.file import FileField, FileRequired, FileAllowed
from flask_uploads import UploadSet, configure_uploads, IMAGES, patch_request_class
from .. import photos
from flask_wtf import FlaskForm

class UploadForm(FlaskForm):
    photo = FileField(validators=[
        FileAllowed(photos, u'只能上传图片!'), 
        FileRequired(u'请选择一个头像吧!')])
    submit = SubmitField(u'确认上传',render_kw={"class":"btn btn-primary"})

其中render_kw是一个dict类型,作用是渲染对应表单的css.
FileAllowed用来检测上传文件类型,不对的的话返回第二个参数.
FileRequiredRequired作用近似,即非空判断.

3:

接下来我们就要开始编写view层了.
这里我们将编辑头像页面的url设为/edit-gravatar
所以我们需要码一个有GETPOST的视图函数.

from flask_login import  current_user
from flask_login import login_user,login_required,logout_user
from .forms import UploadForm
from flask import render_template, session, redirect, url_for, current_app,abort,flash
from .. import db,photos

@main.route('/edit-gravatar',methods=['GET','POST'])
@login_required
def edit_gravatar():
    form = UploadForm()
    if form.validate_on_submit():
        filename = photos.save(form.photo.data)
        file_url = photos.url(filename)
        current_user.gravatar = file_url
        db.session.add(current_user)
        flash('修改成功!')
        return redirect(url_for('.user',username=current_user.username,file_url=file_url))

    return render_template('edit_gravatar.html',form=form,file_url=current_user.gravatar)

注意我们所上传的头像文件即使名字相同也不会覆盖掉之前的头像文件的..(所以还是以二进制存到数据库中更好一点么).

4:

接下来我们需要编写前台界面了:
首先,由于如果我们直接用quick_form创建表单的话,表单的css就不好改了.所以我们把每个表单分开放入form type=file中,并且通过传参或者重叠覆盖修改表单控件的样式.

其中,修改input type=file样式的通用方法是将opacity设为0,然后用一个a标签将input和a组合到一起,这样修改a的样式便相当于修改input的样式.
对应的几部分代码如下:

/*CSS*/
.upload{
    padding: 5px 10px;
    height: 40px;
    line-height: 30px;
    position: relative;
    border: 1px solid #999;
    text-decoration: none;
    text-align:center;
    color: #fff;
}
.change{
    position: absolute;
    overflow: hidden;
    right: 0;
    top: 0;
    opacity: 0;
}
<a class="btn btn-success upload" placeholder="上传头像" >
    <p id="show_gt">选择您要上传的头像~</p>
    {{ form.photo(class="change",placeholder="上传头像",onchange="c()") }}
</a>

然后在css中修改upload样式即可.
其中btn btn-xxxbootstrap样式.

但是到这里还需要考虑一个问题,就是当选中图片以后,并不会显示到页面上,必选点击提交以后才会修改,下次访问这个页面才会变成新图片.
所以我们需要一种方法使选中图片的同时将图片显示在页面上,并和之前的头像对比.
这里我们使用直接通过JavaScript在本地显示:
对应JS代码如下:

<script type="text/javascript">
    function c () {
        var gt=document.getElementById('show_gt');
        gt.innerText = '已选中图片~'
        var r= new FileReader();
        f=document.getElementById('photo').files[0];
        r.readAsDataURL(f);
        r.onload=function  (e) {
            document.getElementById('show').src=this.result;
        };
    }
</script>

因为FlaskForm生成的表单的id和类中对应表单实例的名字是一样的,所以这里的id是photo.
获取图片对象(object) f.
把这个File对象传给FileReader对象的读取方法,就能读取文件了。

连接: <!-- edit_gravatar.html --> {% extends "base.html" %} {% import "bootstrap/wtf.html" as wtf %} {% block title %}编辑个人头像{% endblock %} {% block page_content %} <form method="POST" enctype="multipart/form-data"> {{ form.hidden_tag() }} <div class="page-header"><h2>展览板~</h2></div> <a class="btn btn-success upload" placeholder="上传头像" ><p id="show_gt">选择您要上传的头像~</p> {{ form.photo(class="change",placeholder="上传头像",onchange="c()") }} </a><br/> {% for error in form.photo.errors %} <span style="color: red;">{{ error }}</span> {% endfor %} {% if file_url %} <br><img src="{{ file_url }}" width="256" height="256"> {% endif %} <a class="btn btn-success"><-之前 之后-></a> <img src="{{ file_url }}" id='show' width="256" height="256"><br/><br/> {{ form.submit }} </form> <script type="text/javascript"> function c () { var gt=document.getElementById('show_gt'); gt.innerText = '已选中图片~' var r= new FileReader(); f=document.getElementById('photo').files[0]; r.readAsDataURL(f); r.onload=function (e) { document.getElementById('show').src=this.result; }; } </script> {% endblock %}

Node.js-Koa-渲染

模块依赖的是:

"dependencies": {
    "nunjucks": "2.4.2"
}

nunjucks.
廖大说很像Jinja2,确实.
首先依然是目录结构,与其他无大差异.

然后是编写app.js中调用渲染的模块:

const nunjucks = require('nunjucks');

function createEnv(path,opts){
    var
        autoescape=opts.autoescape===undefined?true:opts.autoescape,
        noCache = opts.noCache || false,
        watch = opts.watch || false,
        throwOnUndefined = opts.throwOnUndefined || false,
        env = new nunjucks.Environment(
            new nunjucks.FileSystemLoader('views',{
                noCache:noCache,
                watch:watch,
            }),{
                autoescape: autoescape,
                throwOnUndefined: throwOnUndefined
            });
    if(opts.filters){
        for(var f in opts.filters){
            env.addFilter(f,opts.filters[f]);
        }
    }
    return env;
}

var env=createEnv('views',{
    watch:true,
    filters:{
        hex:function(n){
            return '0x'+n.toString(16);
        }
    }
});

其中noCache是禁止缓冲,当程序发布时,这个必须是Cache,否则对性能的影响略大.

接下来就对模板html进行渲染了!
首先我们编写一个hello.html:

 <h1>Hello {{ name }}</h1>
<!-- 循环输出名字 -->
<body>
    <h3>Fruits List</h3>
    {% for f in fruits %}
    <p>{{ f }}</p>
    {% endfor %}
</body>

对其中的 name 和 fruits(List)进行渲染:
代码位于app.js

var s=env.render('hello.html',{name:'<script>alert("小明")</script>',fruits:[123,456]});
console.log(s);

输出结果如下:

<h1>Hello <script>alert("小明")</script></h1>
<!-- 循环输出名字 -->
<body>
    <h3>Fruits List</h3>

    <p>123</p>

    <p>456</p>

</body>

可以看到,渲染成功.即简单的拼接字符串.

接下来我们进行网页模板页渲染:
首先我们写一个 base.html:

<html>
<body>
    {% block header %}<h3>Unnamed</h3>{% endblock %}
    {% block body %}<div>No body</div>{% endblock %}
    {% block footer %}<div>copyright</div>{% endblock %}
</body>

可以看到确实很像jinja2~

然后我们再写一个继承自base.html的 extend.html:

{% extends 'base.html' %}

{% block header %}<h1>{{ header }}</h1>{% endblock %}

{% block body %}<p>{{ body }}</p>{% endblock %}

渲染:

console.log(env.render('extend.html',{
    header: 'Hello',
    body: 'bla bla bla...'
}));

结果如下:

<html>
<body>
    <h1>Hello</h1>
    <p>bla bla bla...</p>
    <div>copyright</div>
</body>


End

JS事件

鼠标事件

click: 鼠标单击时触发;
dblclick:鼠标双击时触发;
mouseenter:鼠标进入时触发;
mouseleave:鼠标移出时触发;
mousemove:鼠标在DOM内部移动时触发;
hover:鼠标进入和退出时触发两个函数,相当于mouseenter加上mouseleave。

键盘事件仅作用在当前焦点的DOM上,通常是和。

keydown:键盘按下时触发;
keyup:键盘松开时触发;
keypress:按一次键后触发。

其他事件

focus:当DOM获得焦点时触发;
blur:当DOM失去焦点时触发;
change:当、或的内容改变时触发;
submit:当

<

form>提交时触发;
ready:当页面被载入并且DOM树完成初始化后触发。

javascript表单传送文件

专门把这次课给搞出来记一下笔记..

在HTML中,唯一可以上传文件的就是

<input type="file">

注意,当一个表单包含file时,表单的enctype必须是multipart/form-data,method必须指定为post,浏览器才能以正确编码并以multipart/form-data格式发送表单.

Tip: enctype 属性规定在发送到服务器之前应该如何对表单数据进行编码。

出于安全考虑,浏览器只允许用户点击<input type="file">来选择本地文件,用JavaScript对<input type="file">的value赋值是没有任何效果的。当用户选择了上传某个文件后,JavaScript也无法获得该文件的真实路径:

获取表单上传路径后结果总会是fakepath
<script>
$(function () {
    var
        fileInput = document.getElementById('test-file-upload'),
        filePath = document.getElementById('test-get-filename');
    fileInput.addEventListener('change', function () {
        filePath.innerText = fileInput.value;
    });
});
</script>

<form method="post" action="http://localhost/test" enctype="multipart/form-data">
    <p>
        <input type="file" id="test-file-upload" name="test">
    </p>
    <p>待上传文件: <span id="test-get-filename" style="color:red"></span></p>
</form>

通常,上传的文件都由后台服务器处理,JavaScript可以在提交表单时对文件扩展名做检查,以便防止用户上传无效格式的文件:

var f = document.getElementById('test-file-upload');
var filename = f.value; // 'C:\fakepath\test.png'
if (!filename || !(filename.endsWith('.jpg') || filename.endsWith('.png') || filename.endsWith('.gif'))) {
    alert('Can only upload image file.');
    return false;
}

File API

由于JavaScript对用户上传的文件操作非常有限,尤其是无法读取文件内容,使得很多需要操作文件的网页不得不用Flash这样的第三方插件来实现。
随着HTML5的普及,新增的File API允许JavaScript读取文件内容,获得更多的文件信息。

HTML5的File API提供了File和FileReader两个主要对象,可以获得文件信息并读取文件。

下面的例子演示了如何读取用户选取的图片文件,并在一个

<

div>中预览图像:

var
    fileInput = document.getElementById('test-image-file'),
    info = document.getElementById('test-file-info'),
    preview = document.getElementById('test-image-preview');
// 监听change事件:
fileInput.addEventListener('change', function () {
    // 清除背景图片:
    preview.style.backgroundImage = '';
    // 检查文件是否选择:
    if (!fileInput.value) {
        info.innerHTML = '没有选择文件';
        return;
    }
    // 获取File引用:
    var file = fileInput.files[0];
    // 获取File信息:
    info.innerHTML = '文件: ' + file.name + '<br>' +
                     '大小: ' + file.size + '<br>' +
                     '修改: ' + file.lastModifiedDate;
    if (file.type !== 'image/jpeg' && file.type !== 'image/png' && file.type !== 'image/gif') {
        alert('不是有效的图片文件!');
        return;
    }
    // 读取文件:
    var reader = new FileReader();
    reader.onload = function(e) {
        var
            data = e.target.result; // 'data:image/jpeg;base64,/9j/4AAQSk...(base64编码)...'            
        preview.style.backgroundImage = 'url(' + data + ')';
    };
    // 以DataURL的形式读取文件:
    reader.readAsDataURL(file);
});

上面的代码演示了如何通过HTML5的File API读取文件内容。以DataURL的形式读取到的文件是一个字符串,类似于data:image/jpeg;base64,/9j/4AAQSk…(base64编码)…,常用于设置图像。如果需要服务器端处理,把字符串base64,后面的字符发送给服务器并用Base64解码就可以得到原始文件的二进制内容。

回调

上面的代码还演示了JavaScript的一个重要的特性就是单线程执行模式。在JavaScript中,浏览器的JavaScript执行引擎在执行JavaScript代码时,总是以单线程模式执行,也就是说,任何时候,JavaScript代码都不可能同时有多于1个线程在执行。

你可能会问,单线程模式执行的JavaScript,如何处理多任务?

在JavaScript中,执行多任务实际上都是异步调用,比如上面的代码:

reader.readAsDataURL(file);

就会发起一个异步操作来读取文件内容。因为是异步操作,所以我们在JavaScript代码中就不知道什么时候操作结束,因此需要先设置一个回调函数:

reader.onload = function(e) {
// 当文件读取完成后,自动调用此函数:
};

当文件读取完成后,JavaScript引擎将自动调用我们设置的回调函数。执行回调函数时,文件已经读取完毕,所以我们可以在回调函数内部安全地获得文件内容。