上次把架构做好了,这次做添加文章。添加文章涉及附件的上传管理及富文本编辑器的使用,早添加文章时一并实现。

要点:

  • 富文本编辑器采用KindEditor。功能很强大,国人开发,LGPL开源,自己人的好东西没有理由不支持。
  • 附件的上传同样基于KindEditor实现,可以上传图片,flash,影音,文件等。

目录

ASP.NET MVC5 网站开发实践 - 概述

ASP.NET MVC5 网站开发实践(一) - 项目框架

ASP.NET MVC5 网站开发实践(一) - 框架(续) 模型、数据存储、业务逻辑

ASP.NET MVC5 网站开发实践(二) - 用户部分(1)用户注册

ASP.NET MVC5 网站开发实践(二) - 用户部分(2)用户登录、注销

ASP.NET MVC5 网站开发实践(二) - 用户部分(3)修改资料、修改密码

ASP.NET MVC5 网站开发实践(二) Member区域 - 文章管理架构

一、添加文章

1、KindEditor富文本编辑器

到官方网站http://kindeditor.net/down.php下载最新版本,解压后把代码复制到项目的Scripts文件夹下。

2、添加界面的显示。

在ArticleController中添加Add 方法

/// <summary>
/// 添加文章
/// </summary>
/// <returns>视图页面</returns>
public ActionResult Add()
{
return View();
}

右键添加Article的强类型视图,代码如下

@section scripts{
<script type="text/javascript" src="~/Scripts/KindEditor/kindeditor-min.js"></script>
<script type="text/javascript">
//编辑框
KindEditor.ready(function (K) {
window.editor = K.create('#Content', {
height: '500px'
         });
});
</script>
} @model Ninesky.Models.Article
@using (Html.BeginForm())
{ @Html.AntiForgeryToken()
<div class="form-horizontal" role="form">
<h4>添加文章</h4>
<hr />
@Html.ValidationSummary(true)
<div class="form-group">
<label class="control-label col-sm-2" for="CommonModel_CategoryID">栏目</label>
<div class="col-sm-10">
<input id="CommonModel_CategoryID" name="CommonModel.CategoryID" data-options="url:'@Url.Action("JsonTree", "Category", new { model="Article" })'" class="easyui-combotree" style="height: 34px; width: 280px;" />
@Html.ValidationMessageFor(model => model.CommonModel.CategoryID)</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.CommonModel.Title, new { @class = "control-label col-sm-2" })
<div class="col-sm-10">
@Html.TextBoxFor(model => model.CommonModel.Title, new { @class = "form-control" })
@Html.ValidationMessageFor(model => model.CommonModel.Title)
</div>
</div> <div class="form-group">
@Html.LabelFor(model => model.Author, new { @class = "control-label col-sm-2" })
<div class="col-sm-10">
@Html.TextBoxFor(model => model.Author, new { @class = "form-control" })
@Html.ValidationMessageFor(model => model.Author)
</div>
</div> <div class="form-group">
@Html.LabelFor(model => model.Source, new { @class = "control-label col-sm-2" })
<div class="col-sm-10">
@Html.TextBoxFor(model => model.Source, new { @class = "form-control" })
@Html.ValidationMessageFor(model => model.Source)
</div>
</div> <div class="form-group">
@Html.LabelFor(model => model.Intro, new { @class = "control-label col-sm-2" })
<div class="col-sm-10">
@Html.TextAreaFor(model => model.Intro, new { @class = "form-control" })
@Html.ValidationMessageFor(model => model.Intro)
</div>
</div> <div class="form-group">
@Html.LabelFor(model => model.Content, new { @class = "control-label col-sm-2" })
<div class="col-sm-10">
@Html.EditorFor(model => model.Content)
@Html.ValidationMessageFor(model => model.Content)
</div>
</div> <div class="form-group">
@Html.LabelFor(model => model.CommonModel.DefaultPicUrl, new { @class = "control-label col-sm-2" })
<div class="col-sm-10">
<img id="imgpreview" class="thumbnail" src="" />
@Html.HiddenFor(model => model.CommonModel.DefaultPicUrl)
<a id="btn_picselect" class="easyui-linkbutton">选择…</a>
@Html.ValidationMessageFor(model => model.CommonModel.DefaultPicUrl)
</div>
</div> <div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<input type="submit" value="添加" class="btn btn-default" />
</div>
</div>
</div>
}

效果如图

3、后台接受的处理。

[ValidateInput(false)]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Add(Article article)
{
if(ModelState.IsValid)
{
//设置固定值
article.CommonModel.Hits = ;
article.CommonModel.Inputer = User.Identity.Name;
article.CommonModel.Model = "Article";
article.CommonModel.ReleaseDate = System.DateTime.Now;
article.CommonModel.Status = ;
article = articleService.Add(article);
if (article.ArticleID > )
{
return View("AddSucess", article);
}
}
return View(article);
}

在做架构的时候DAL、BLL的base类里有Add方法,我们可以直接使用ArticleService.Add方法添加到数据库

添加文章功能就实现了,但是不能上传附件,不能选择首页图片,不能删除多余的附件。下面就来实现附件功能。

二、附件上传

目标可以上传附件(图片,文件等),文件保存到上传目录中,且数据库中保存相应记录,可以浏览文件列表,未使用的附件可以删除记录。

一、添加附件

在AttachmentController添加Upload()方法,方法方法把文件写入磁盘中把附件的记录也保存到数据库中,中间会用到读取配置文件,见《.Net MVC 网站中配置文件的读写》。

/// <summary>
/// 上传附件
/// </summary>
/// <returns></returns>
public ActionResult Upload()
{
var _uploadConfig = System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration("~").GetSection("UploadConfig") as Ninesky.Models.Config.UploadConfig;
//文件最大限制
int _maxSize = _uploadConfig.MaxSize;
//保存路径
string _savePath;
//文件路径
string _fileParth = "~/" + _uploadConfig.Path + "/";
//文件名
string _fileName;
//扩展名
string _fileExt;
//文件类型
string _dirName;
//允许上传的类型
Hashtable extTable = new Hashtable();
extTable.Add("image", _uploadConfig.ImageExt);
extTable.Add("flash", _uploadConfig.FileExt);
extTable.Add("media", _uploadConfig.MediaExt);
extTable.Add("file", _uploadConfig.FileExt);
//上传的文件
HttpPostedFileBase _postFile = Request.Files["imgFile"];
if (_postFile == null) return Json(new { error = '', message = "请选择文件" });
_fileName = _postFile.FileName;
_fileExt = Path.GetExtension(_fileName).ToLower();
//文件类型
_dirName = Request.QueryString["dir"];
if (string.IsNullOrEmpty(_dirName))
{
_dirName = "image";
}
if (!extTable.ContainsKey(_dirName)) return Json(new { error = , message = "目录类型不存在" });
//文件大小
if (_postFile.InputStream == null || _postFile.InputStream.Length > _maxSize) return Json(new { error = , message = "文件大小超过限制" });
//检查扩展名
if (string.IsNullOrEmpty(_fileExt) || Array.IndexOf(((string)extTable[_dirName]).Split(','), _fileExt.Substring().ToLower()) == -) return Json(new { error = , message = "不允许上传此类型的文件。 \n只允许" + ((String)extTable[_dirName]) + "格式。" });
_fileParth += _dirName + "/" + DateTime.Now.ToString("yyyy-MM") + "/";
_savePath = Server.MapPath(_fileParth);
//检查上传目录
if (!Directory.Exists(_savePath)) Directory.CreateDirectory(_savePath);
string _newFileName = DateTime.Now.ToString("yyyyMMdd_hhmmss") + _fileExt;
_savePath += _newFileName;
_fileParth += _newFileName;
//保存文件
_postFile.SaveAs(_savePath);
//保存数据库记录
attachmentService.Add(new Attachment() { Extension = _fileExt.Substring(), FileParth = _fileParth, Owner = User.Identity.Name, UploadDate = DateTime.Now, Type = _dirName });
return Json(new { error = , url = Url.Content(_fileParth) });
}

二、查询附件列表

打开InterfaceAttachmentService接口,添加两个方法,都进行了注释比较容易理解,直接上代码。

/// <summary>
/// 查找附件列表
/// </summary>
/// <param name="modelID">公共模型ID</param>
/// <param name="owner">所有者</param>
/// <param name="type">类型</param>
/// <returns></returns>
IQueryable<Models.Attachment> FindList(Nullable<int> modelID, string owner, string type);
/// <summary>
/// 查找附件列表
/// </summary>
/// <param name="modelID">公共模型ID</param>
/// <param name="owner">所有者</param>
/// <param name="type">所有者</param>
/// <param name="withModelIDNull">包含ModelID为Null的</param>
/// <returns></returns>
IQueryable<Models.Attachment> FindList(int modelID, string owner, string type,bool withModelIDNull);

AttachmentService中写现实代码

public IQueryable<Models.Attachment> FindList(Nullable<int> modelID, string owner, string type)
{
var _attachemts = CurrentRepository.Entities.Where(a => a.ModelID == modelID);
if (!string.IsNullOrEmpty(owner)) _attachemts = _attachemts.Where(a => a.Owner == owner);
if (!string.IsNullOrEmpty(type)) _attachemts = _attachemts.Where(a => a.Type == type);
return _attachemts;
} public IQueryable<Models.Attachment> FindList(int modelID, string owner, string type, bool withModelIDNull)
{
var _attachemts = CurrentRepository.Entities;
if (withModelIDNull) _attachemts = _attachemts.Where(a => a.ModelID == modelID || a.ModelID == null);
else _attachemts = _attachemts.Where(a => a.ModelID == modelID);
if (!string.IsNullOrEmpty(owner)) _attachemts = _attachemts.Where(a => a.Owner == owner);
if (!string.IsNullOrEmpty(type)) _attachemts = _attachemts.Where(a => a.Type == type);
return _attachemts;
}

由于KindEditor文件管理需要从服务器获取json格式文件列表,在Ninesky.Web.Areas.Member.Models中单独给列表格式写个视图模型。AttachmentManagerViewModel

namespace Ninesky.Web.Areas.Member.Models
{
/// <summary>
/// KindEditor文件管理中文件视图模型
/// <remarks>
/// 创建:2014.03.09
/// </remarks>
/// </summary>
public class AttachmentManagerViewModel
{
public bool is_dir{get;set;}
public bool has_file {get;set;}
public int filesize {get;set;}
public bool is_photo{get;set;}
public string filetype{get;set;}
public string filename{get;set;}
public string datetime { get; set; }
}
}

在AttachmentController添加返回文件列表的方法FileManagerJson。方法供KindEditor的文件管理器调用

/// <summary>
/// 附件管理列表
/// </summary>
/// <param name="id">公共模型ID</param>
/// <param name="dir">目录(类型)</param>
/// <returns></returns>
public ActionResult FileManagerJson(int? id ,string dir)
{
Models.AttachmentManagerViewModel _attachmentViewModel;
IQueryable<Attachment> _attachments;
//id为null,表示是公共模型id为null,此时查询数据库中没有跟模型对应起来的附件列表(以上传,但上传的文章……还未保存)
if (id == null) _attachments = attachmentService.FindList(null, User.Identity.Name, dir);
//id不为null,返回指定模型id和id为null(新上传的)附件了列表
else _attachments = attachmentService.FindList((int)id, User.Identity.Name, dir, true);
//循环构造AttachmentManagerViewModel
var _attachmentList = new List<Models.AttachmentManagerViewModel>(_attachments.Count());
foreach(var _attachment in _attachments)
{
_attachmentViewModel = new Models.AttachmentManagerViewModel() { datetime = _attachment.UploadDate.ToString("yyyy-MM-dd HH:mm:ss"), filetype = _attachment.Extension, has_file = false, is_dir = false, is_photo = _attachment.Type.ToLower() == "image" ? true : false, filename = Url.Content(_attachment.FileParth) };
FileInfo _fileInfo = new FileInfo(Server.MapPath(_attachment.FileParth));
_attachmentViewModel.filesize = (int)_fileInfo.Length;
_attachmentList.Add(_attachmentViewModel);
}
return Json(new { moveup_dir_path = "", current_dir_path = "", current_url = "", total_count = _attachmentList.Count, file_list = _attachmentList },JsonRequestBehavior.AllowGet);
}

3、为图片创建缩略图

把创建缩略图的方法写着Common项目中

在Ninesky.Common的Picture类中添加方法

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Security.Cryptography; namespace Ninesky.Common
{
/// <summary>
/// 图片相关
/// <remarks>
/// 创建:2014.02.11
/// </remarks>
/// </summary>
public class Picture
{
/// <summary>
/// 创建缩略图
/// </summary>
/// <param name="originalPicture">原图地址</param>
/// <param name="thumbnail">缩略图地址</param>
/// <param name="width">宽</param>
/// <param name="height">高</param>
/// <returns>是否成功</returns>
public static bool CreateThumbnail(string originalPicture, string thumbnail, int width, int height)
{
//原图
Image _original = Image.FromFile(originalPicture);
// 原图使用区域
RectangleF _originalArea = new RectangleF();
//宽高比
float _ratio = (float)width/height;
if(_ratio > ((float)_original.Width/_original.Height))
{
_originalArea.X =;
_originalArea.Width = _original.Width;
_originalArea.Height = _originalArea.Width / _ratio;
_originalArea.Y = (_original.Height - _originalArea.Height) / ;
}
else
{
_originalArea.Y = ;
_originalArea.Height = _original.Height;
_originalArea.Width = _originalArea.Height * _ratio;
_originalArea.X = (_original.Width - _originalArea.Width) / ;
}
Bitmap _bitmap = new Bitmap(width, height);
Graphics _graphics = Graphics.FromImage(_bitmap);
//设置图片质量
_graphics.InterpolationMode = InterpolationMode.High;
_graphics.SmoothingMode = SmoothingMode.HighQuality;
//绘制图片
_graphics.Clear(Color.Transparent);
_graphics.DrawImage(_original, new RectangleF(, , _bitmap.Width, _bitmap.Height), _originalArea, GraphicsUnit.Pixel);
//保存
_bitmap.Save(thumbnail);
_graphics.Dispose();
_original.Dispose();
_bitmap.Dispose();
return true;
}
}
}

在AttachmentController添加生成缩略图的action

/// <summary>
/// 创建缩略图
/// </summary>
/// <param name="originalPicture">原图地址</param>
/// <returns>缩略图地址。生成失败返回null</returns>
public ActionResult CreateThumbnail(string originalPicture)
{
//原图为缩略图直接返回其地址
if (originalPicture.IndexOf("_s") > ) return Json(originalPicture);
//缩略图地址
string _thumbnail = originalPicture.Insert(originalPicture.LastIndexOf('.'), "_s");
//创建缩略图
if (Common.Picture.CreateThumbnail(Server.MapPath(originalPicture), Server.MapPath(_thumbnail), , ))
{
//记录保存在数据库中
attachmentService.Add(new Attachment(){ Extension= _thumbnail.Substring(_thumbnail.LastIndexOf('.')+), FileParth="~"+_thumbnail, Owner= User.Identity.Name, Type="image", UploadDate= DateTime.Now});
return Json(_thumbnail);
}
return Json(null);
}

三、整合

添加和上传附件都做好了,现在把他们整合到一起,我们就可以上传附件了。

打开Add视图,在创建KindEditor位置添加脚本

现在打开浏览器就可以上传和管理附件了

添加文章的最后一个字段是文章的默认首页图片,我希望点击选择按钮,可以在已上传中选择图片,并创建缩略图。

那么在Add视图里再弹出一个文件空间让用户选择已上传的文件,用户选择后讲选择的地址发送到服务器创建缩略图,并返回缩略图地址,然后将地址复制给隐藏表单,CommonModel_DefaultPicUrl,同事复制个<img />的src属性用来显示图片。Js代码如下:

//首页图片
var editor2 = K.editor({
fileManagerJson: '@Url.Action("FileManagerJson", "Attachment")'
});
K('#btn_picselect').click(function () {
editor2.loadPlugin('filemanager', function () {
editor2.plugin.filemanagerDialog({
viewType: 'VIEW',
dirName: 'image',
clickFn: function (url, title) {
var url;
$.ajax({
type: "post",
url: "@Url.Action("CreateThumbnail", "Attachment")",
data: { originalPicture: url },
async: false,
success: function (data) {
if (data == null) alert("生成缩略图失败!");
else {
K('#CommonModel_DefaultPicUrl').val(data);
K('#imgpreview').attr("src", data);
}
editor2.hideDialog();
}
});
}
});
});
});

看下效果

在保存文章的action中删除未使用的附件

完整的Add方法代码

[ValidateInput(false)]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Add(Article article)
{
if(ModelState.IsValid)
{
//设置固定值
article.CommonModel.Hits = ;
article.CommonModel.Inputer = User.Identity.Name;
article.CommonModel.Model = "Article";
article.CommonModel.ReleaseDate = System.DateTime.Now;
article.CommonModel.Status = ;
article = articleService.Add(article);
if (article.ArticleID > )
{
//附件处理
InterfaceAttachmentService _attachmentService = new AttachmentService();
//查询相关附件
var _attachments = _attachmentService.FindList(null, User.Identity.Name, string.Empty).ToList();
//遍历附件
foreach(var _att in _attachments)
{
var _filePath = Url.Content(_att.FileParth);
//文章首页图片或内容中使用了该附件则更改ModelID为文章保存后的ModelID
if ((article.CommonModel.DefaultPicUrl != null && article.CommonModel.DefaultPicUrl.IndexOf(_filePath) >= ) || article.Content.IndexOf(_filePath) > )
{
_att.ModelID = article.ModelID;
_attachmentService.Update(_att);
}
//未使用改附件则删除附件和数据库中的记录
else
{
System.IO.File.Delete(Server.MapPath(_att.FileParth));
_attachmentService.Delete(_att);
}
}
return View("AddSucess", article);
}
}
return View(article);
}

四、总结:

单纯添加文章比较简单,复杂点在上传附件,浏览新添加的附件,删除文章中未使用的附件及生成缩略图上。KindEditor还支持批量上传附件,由于批量上传使用的swfupload,在提交时flash没传输cookie到服务器,无法验证用户导致上传失败,暂时无法使用批量上传。

ASP.NET MVC5 网站开发实践(二) Member区域 - 添加文章的更多相关文章

  1. ASP.NET MVC5 网站开发实践(二) Member区域 - 全部文章列表

    显示文章列表分两块,管理员可以显示全部文章列表,一般用户只显示自己的文章列表.文章列表的显示采用easyui-datagrid.后台需要与之对应的action返回json类型数据   目录 ASP.N ...

  2. ASP.NET MVC5 网站开发实践(二) Member区域–管理列表、回复及删除

    本来想接着上次把这篇写完的,没想到后来工作的一些事落下了,放假了赶紧补上. 目录: ASP.NET MVC5 网站开发实践 - 概述 ASP.NET MVC5 网站开发实践(一) - 项目框架 ASP ...

  3. ASP.NET MVC5 网站开发实践(二) Member区域–我的咨询列表及添加咨询

    上次把咨询的架构搭好了,现在分两次来完成咨询:1.用户部分,2管理部分.这次实现用户部分,包含两个功能,查看我的咨询和进行咨询. 目录: ASP.NET MVC5 网站开发实践 - 概述 ASP.NE ...

  4. ASP.NET MVC5 网站开发实践(二) Member区域 - 咨询管理的架构

    咨询.留言.投诉等功能是网站应具备的基本功能,可以加强管理员与用户的交流,在上次完成文章部分后,这次开始做Member区域的咨询功能(留言.投诉都是咨询).咨询跟文章非常相似,而且内容更少.更简单. ...

  5. ASP.NET MVC5 网站开发实践(二) Member区域 - 修改及删除文章

    上次做了显示文章列表,再实现修改和删除文章这部分内容就结束了,这次内容比较简单,由于做过了添加文章,修改文章非常类似,就是多了一个TryUpdateModel部分更新模型数据.   目录: ASP.N ...

  6. ASP.NET MVC5 网站开发实践(二) Member区域 - 文章管理架构

    上次把member的用户部分完成,现在开始做文章管理部分.文章部分根据涉及显示现实文章列表,发布文章,修改文章,删除文章等功能.最终的实现目标是使用权限来控制用户是否能进行相应操作,管理员权限的会显示 ...

  7. ASP.NET MVC5 网站开发实践(二) Member区域 - 用户部分(3)修改资料、修改密码

    在上一篇博客中实现了用户的注销和登录,其实代码里落了点东西,就是用户登录要更新最后一次登录时间和登录IP,这次补上.今天做修改资料和修改密码,TryUpdateModel是新用到的东西. 目录: AS ...

  8. ASP.NET MVC5 网站开发实践(二) Member区域 - 用户部分(2)用户登录、注销

    上次实现了用户注册,这次来实现用户登录,用到IAuthenticationManager的SignOut.SignIn方法和基于声明的标识.最后修改用户注册代码实现注册成功后直接登录. 目录: ASP ...

  9. ASP.NET MVC5 网站开发实践(二) Member区域 - 用户部分(1)用户注册

    上一次把基本框架搭建起来了,这次开始整Web部分,终于可以看到界面了小激动一下.web项目部分从用户功能开始,基本有注册,登录.注销.查找.查看.删除等涉及Member区域和Manage区域. 目录: ...

随机推荐

  1. Java数据结构——栈的应用(以数制转换为例子)

    看一万遍,不如自己动手实践,请不要直接copy代码,先自己去理解例子实现的原理,然后试着自己动手去实践. 用Java去实现栈操作,会用到stack这个类,这类中有几个方法需要知道他们的用法  bool ...

  2. 学用了QT觉得QT较怪异

    如果让我选择不会用qt 还是用界面开源库,boost.

  3. java-通过JDBC操作数据库

    一.加载驱动 这里我们用Class.forname();来加载驱动,用此语句会提示排除异常. Class.forName("com.mysql.jdbc.Driver"); 括号中 ...

  4. UIWindow & UIWindowLevel

    转自:http://www.cnblogs.com/smileEvday/archive/2012/03/27/2420362.html 一.UIWindow是一种特殊的UIView,通常在一个程序中 ...

  5. 使用DataList实现数据分页的技术

    今天做网站的时候,用到了分页技术,我把使用方法记录下来,以便日后查阅以及帮助新手朋友们. DataList控件可以按照列表的形式显示数据表中的多行记录,但是被显示的多行记录没有分页功能,使用起来不太方 ...

  6. Principles of measurement of sound intensity

    Introduction In accordance with the definition of instantaneous sound intensity as the product of th ...

  7. Python黑帽编程 4.1 Sniffer(嗅探器)之数据捕获(上)

    Python黑帽编程 4.1 Sniffer(嗅探器)之数据捕获(上) 网络嗅探,是监听流经本机网卡数据包的一种技术,嗅探器就是利用这种技术进行数据捕获和分析的软件. 编写嗅探器,捕获数据是前置功能, ...

  8. 《HiWind企业快速开发框架实战》(0)目录及框架简介

    <HiWind企业快速开发框架实战>(0)目录及框架简介 本系列主要介绍一款企业管理系统快速开发框架,该框架旨在快速完成企业管理系统,并实现易维护可移植的目标. 使用逐个系统模块进行编码的 ...

  9. SQL Server 执行计划缓存

    标签:SQL SERVER/MSSQL SERVER/数据库/DBA/内存池/缓冲区 概述 了解执行计划对数据库性能分析很重要,其中涉及到了语句性能分析与存储,这也是写这篇文章的目的,在了解执行计划之 ...

  10. 辛巴学院-Unity-剑英陪你零基础学c#系列(四)函数和封装

    辛巴学院:正大光明的不务正业. 国庆长假结束了,我的心情是这样的: 你总是起不早,起不早独自一个人沉睡到天亮你无怨无悔的梦着那副本我知道你根本就不想上班你总是起不早,起不早放假总是短暂,上班太难请个病 ...