基于 Vue.js 之 iView UI 框架非工程化实践记要
像我们平日里做惯了 Java 或者 .NET 这种后端程序员,对于前端的认识还常常停留在 jQuery 时代,包括其插件在需要时就引用一下,不需要就删除。故观念使然,尽管 Nuget 和 Maven 用得顺溜,但对 NPM 仍不带感,兴许是周边无人带动的稀薄气氛,也或者是没参加过类似的大型活动,于是在自发性上差了许多。再者,我不用 MVVM 模式,领导也不会扣绩效。
为了快速体验 MVVM 模式,我选择了非工程化方式来起步,并选择使用 Vue.js,以及基于它构建的 iView UI 框架。
Vue.js 是美籍华人尤雨溪创作的,那会儿他还在 Google 工作,他自感 Angular.js 繁杂,进而自创了更为简洁易用的 Vue.js。
iView UI 是由 90 后梁灏[hào]创作,网名 Aresn,在大数据公司 TalkingData 负责可视化基础架构,更了不起的是他还出了《Vue.js实战》一书,我是在“双十一”的前一天买的,行文措词简洁,表意直达困惑,入门很迅速,是我喜欢的风格。花了一周时间扫完了前十二章内容,用 WebStorm 练习了大部分实例,尽管书中用一整章内容介绍了 webpack 工程化构建方案,但由于我惯性使然,还是选择对其跳过而使用了非工程化方式来体验 Vue.js,其实多少有失暴殄。无奈,毕竟才学了一周,后期时间充裕了再接着分享工程化构建的学习心得吧。
一、 MVVM 模式
Vue.js 比较显著的特征是解耦了视图和数据,也就是说视图的变化不再需要命令式编程去显式改变,只要修改完数据就能立即自动同步,这是比较大的一个思维模式的转变,另一个就是组件化思维俯首皆是,这样开发一个应用就相当是在搭积木。
其实以上对 Vue.js 所阐述的优点也正是 MVVM 模式的写照,它原是由 MVC 所衍生,即当视图层发生变化时,会自动更新到视图模型上,反之亦然,这就是常说的双向绑定,上一张图吧:
甭管这图是否好理解,通俗地来讲,MVVM 这种模式拆分了视图和数据,这样我们在开发时只要关心数据本身即可,然后视图 DOM 这方面会由 Vue.js 自动解决。
二、非工程化起步
为了能支撑起一个最基本的应用,需要引入以下几个必要文件:
- vue.min.2.5.3.js,vue.js 库
- iview.2.7.0.css,iView 样式文件
- iview.min.2.7.0.js,iView 库
- iview /locale/zh-CN.js 语言包
- iview /font 字体包
下载 Vue.js
来到 Github 上的 Vue 项目,直接下载 Zip 源码:
在 dist 目录中就可以找到 vue.js 文件:
根据不同的环境选择一个版本即可,至此第 1 步就搞定了。
下载 iView 系列文件
在 iView 官网的“组件” / “安装” 页面的开头处发现了这个链接:https://unpkg.com/iview/ ,通过它可以查看到 dist 目录:
必要文件都在这里,这些文件无法打包下载,我采取的笨办法是逐个点开,然后复制其中的内容。
在获取 iView 相关的 js 和 css 方面还有一个办法,仔细观察官网给出的 CDN 地址分别为:
我尝试将它们放在浏览器里进行访问:
发现地址有变更,不过这并无大碍。
至此,将各个文件放在期望的位置即可:
该图中各文件的摆放并不是很严谨,大家按自己习惯来即可。
三、实例演练
完成以上的准备工作后,就可以结合 iView UI 来正式开发了,接下来基于 table 表格组件演示一下购物车的基本操作。
引入资源
经过起步工作的筹备,可以在新建页面中逐个地引入这些资源。
HTML head 部分
<head>
<meta charset="UTF-8">
<title>购物车实例</title>
<link rel="stylesheet" href="iViewContent/iview.2.7.0.css">
<script src="utility_js/vue.min.2.5.3.js"></script>
<script src="utility_js/iview.min.2.7.0.js"></script>
<script src="iViewContent/locale/zh-CN.js"></script>
<script>
iview.lang('zh-CN');
</script>
</head>
按一贯的方式引用,样式居前,随后紧跟着 vue.js 和 iView.js,以及 iView 中文语言包 zh-CN.js,然后立即调用 lang 方法使其生效。
绑定数据
首先把数据绑定起来,从而看一看整体效果,至于其他的行为操作先不管:
HTML body 部分
<body>
<div id="app">
<i-table id="datatable1"
size="small"
:columns="columns"
:data="cartList"
stripe
highlight-row>
</i-table>
</div>
<script src="iViewUI_cart.js"></script>
</body>
组件 i-table 最核心的两个属性分别是 columns 和 data,columns 是列定义,data 则为数据。
这两个属性都添加了冒号(:)语法糖,它指代的是 v-bind 指令,表示这个属性的值是动态绑定的,这样在运行过程中发现数据有变更时,表格视图也会迅速的变更。
iViewUI_cart.js 脚本部分
var cart = new Vue({
el: '#app',
data: function () {
return {
cartList: [
{id: 1, name: 'iPhone X', price: 8300.05, count: 1},
{id: 2, name: 'MacBook Pro', price: 18800.75, count: 3},
{id: 3, name: 'Mate 10 Porsche', price: 16600.00, count: 8}
],
columns: [
{
title: '名称',
key: 'name'
},
{
title: '单价',
key: 'price'
},
{
title: '数量',
key: 'count'
}
]
}
},
methods: {}
});
该文件是与页面对应的业务脚本,整个文件就负责 new 一个 Vue 实例,并将其赋值给了变量 cart,可以看到的 data 包含了两个属性,即表示数据源的 cartList 和 列定义的 columns,二者正好与上述 i-table 的核心属性相映射。
再次值得注意的是 data,它的值需要以匿名函数的形式进行书写,即:
function () {
return {}
}
如此,在其 columns 中出现的 Render 函数体内才能正常通过 this 访问到 methods 中定义的方法。当然本次演示是通过 cart 对象来访问,故不受此影响。
运行页面后,数据即可绑定成功。
添加操作所需按钮
数据呈现出来后,就可以补充必要的按钮了:
这一步简单,只需要修改一下 columns 属性,追加一项“操作”列,添加三个按钮:
{
title: '数量',
key: 'count'
},
{
title: '操作',
render: (h, params) => {
return h('div', [
h('Button', {
props: {
type: 'primary',
size: 'small'
},
style: {
marginRight: '5px'
},
on: {
click: () => {
console.info('减少数量');
cart.reduceQuantity(params.row.id);
}
}
}, '-'),
h('Button', {
props: {
type: 'primary',
size: 'small'
},
style: {
marginRight: '5px'
},
on: {
click: () => {
console.info('增加数量');
cart.increaseQuantity(params.row.id);
}
}
}, '+'),
h('Button', {
props: {
type: 'error',
size: 'small'
},
style: {
marginRight: '5px'
},
on: {
click: () => {
console.info('删除当前项');
cart.deleteItem(params.row.id);
}
}
}, '删除')
]);
}
}
在这里使用到了 Render 函数,该函数的内部机制略显复杂,作为初步演示只需依样画葫芦即可。
说到 Render 函数,还需要再强调一下在其内部对 methods 中所定义方法的调用,如果试图通过 this 来调用方法(比如 reduceQuantity),那么 Vue 实例中 data 的值需要使用匿名函数的方式来表达;反之,若是通过 Vue 实例 cart 来调用,则无此顾虑,即 data 的值使用一贯的对象大括号({})来表达即可。
添加操作所需方法
操作按钮已经添加成功了,那就需要有对应的方法去执行,在 Vue.js 中,方法都定义在 methods 属性中。
减去数量
首先关注一下“减去数量”的定义:
methods: {
reduceQuantity: function (id) {
for (let i = 0; i < this.cartList.length; i++) {
if (this.cartList[i].id === id) {
this.cartList[i].count--;
break;
}
}
}
}
通过遍历找到目标记录,并将其 count 属性减一,如同 MVVM 的定义,当数据变更的时候,视图也跟随着变化。
但凡是存在于购物车内的商品,其数量至少应该为 1,为防止减到 0,不妨再加一个判断使其逻辑更为完美:
methods: {
reduceQuantity: function (id) {
for (let i = 0; i < this.cartList.length; i++) {
if (this.cartList[i].id === id) {
if (this.cartList[i].count > 1) {
this.cartList[i].count--;
}
break;
}
}
}
},
增加数量
methods: {
increaseQuantity: function (id) {
for (let i = 0; i < this.cartList.length; i++) {
if (this.cartList[i].id === id) {
this.cartList[i].count++;
break;
}
}
}
}
只需要针对 count 属性做 +1 操作即可。
删除
deleteItem: function (id) {
for (let i = 0; i < this.cartList.length; i++) {
if (this.cartList[i].id === id) {
// 询问是否删除
this.$Modal.confirm({
title: '提示',
content: '确定要删除吗?',
onOk: () => {
this.cartList.splice(i, 1);
},
onCancel: () => {
// 什么也不做
}
});
}
}
}
在删除逻辑中,当遍历到目标记录时,会询问用户是否真的要删除当前记录,这里用到了 $Modal 对话框,如果用户点击确定,那么就执行真正的删除,看一看效果:
非常漂亮考究的 iView Modal 对话框,令人赏心悦目,一见倾心。
至此,针对 Vue.js 和 iView 框架的体验就告一段落,后面抽时间再学习一下组件和 Render 函数,提升一下内功修养。
对 Newtonsoft.Json 的应用可以说司空见惯,在 JSON 格式层级不深的情况下使用很方便,但有时遇到的 JSON 字符串层级非常多,且真正需要的数据往往都“埋”得很深,这时如果去定义一个与之对应的多层嵌套实体类就显得不划算,下面通过实例来演示如何一步到位抓取到期望的数据(集)。
一、把实体类转化为 JSON 字符串
1. 为实体类赋值
SenderFromMQSearch senderFromMQSearch = new SenderFromMQSearch();
senderFromMQSearch.StartTime = new DateTime(2017, 9, 10);
senderFromMQSearch.EndTime = new DateTime(2017, 9, 10);
senderFromMQSearch.FlowType = "DataSender";
2. 序列化
通过简单的序列化后,可以将 C# 实体类转换为 JSON 格式的字符串
string paramsText = JsonConvert.SerializeObject(senderFromMQSearch);
转换后其格式类似如下:
{\"StartTime\":\"2017-09-10\", \"EndTime\":\"2017-09-10\", \"FlowType\":\"DataSender\", \"SiteNo\":\"\"}
二、提取 JSON 字符串中部分属性的值
JSON 属性的值有多种类型,比如 String、Array 等,通过属性提取到对应的值时需要特别留意这些类型,从而做出正确地转化。
比如现借助上面的查询实体类,以及前文介绍的 Chloe.ORM 查询到一段结果集,其形式如同这样一段 JSON 字符串:
{
"Status": true,
"Msg": "成功",
"Data": {
"SiteData": [
{
"SiteNo": "200012",
"SiteName": "上海公司",
"Total": 100
},
{
"SiteNo": "214001",
"SiteName": "无锡分销部",
"Total": 200
}
]
}
}
并将该字符串存储在变量 resultText 中。
1. JObject
将其转化为 JSON 对象,即 JObject:
JObject jObj = JObject.Parse(resultText);
接着可以尝试获取"Data"属性的值:
jObj["Data"].ToString();
其值的形式为:
{
"SiteData": [
{
"SiteNo": "200012",
"SiteName": "上海公司",
"Total": 100
},
{
"SiteNo": "214001",
"SiteName": "无锡分销部",
"Total": 200
}
]
}
2. JArray
这时发现,真正期望的值其实还在属性“SiteData”里面,那不如索性一步到位:
jObj["Data"]["SiteData"].ToString();
此时,其值的形式为:
[
{
"SiteNo": "200012",
"SiteName": "上海公司",
"Total": 100
},
{
"SiteNo": "214001",
"SiteName": "无锡分销部",
"Total": 200
}
]
可见这是一个数组格式的字符串,可以尝试将其转化为数组对象,即 JArray:
JArray siteDataArray = JArray.Parse(jObj["Data"]["SiteData"].ToString());
3. JArray 转换为 List
对于该数组中的每一个对象,都存在三个属性:SiteNo、SiteName,以及 Total,为了更方便地操作这些数据,可以考虑泛型集合。
首先来定义实体类:
public class SiteInfo
{
public string SiteNo { get; set; }
public string SiteName { get; set;
public int Total { get; set; }
}
可以看出,实体类的属性实际上与数组中每一个对象的属性相对应。
接着来遍历一下这个数组,对于数组中的每一个对象都会有一个实体对象(siteInfo)与之对应,然后将该实体对象(siteInfo)逐个地添加至泛型集合(siteInfoList)即可。
List<SiteInfo> siteInfoList = new List<SiteInfo>();
foreach (var item in siteDataArray)
{
SiteInfo siteInfo = new SiteInfo();
siteInfo.SiteNo = item["SiteNo"].ToString();
siteInfo.SiteName = item["SiteName"].ToString();
siteInfo.Total = (int)item["Total"];
siteInfoList.Add(siteInfo);
}
至此,核心数据就倒腾到泛型集合中,接下来借助 Linq 就可以玩各种花样了。
近期想对自己的项目增加自动编译并生成nuget包,网上资料不少。但总还有迷糊的时候。首先:此解决方案包含多种版本的项目,如:有编译必须是x86平台,以及还有传统的.net foramework项目,以及多版本的.net core项目等。找到通用的解决方案还是用了不少的时间。本文章就对此做下自我总结。望对同仁有所帮助。
一、遇到的问题,以及解决办法
1.1 不同平台的编译怎么办
.net可以将dll(或者说是项目)编译成x86、x64、anycpu 等至少三种平台代码。但如果用dotnet build生成项目时,默认是anycpu,需要通过platform参数来执行平台。可是,如果你一个解决方法中即有x86项目、又有anycpu项目可怎么是好呢。解决办法:创建两个sln文件,一个用x86平台的编译,一个用于anycpu平台的编译。
1.2 传统.net framework项目,怎么用dotnet 进行编译
这个问题其实最终的解决办法也很简易,就是将.net framework项目直接迁移成dotnet 可编译的项目。步骤以一个.net 4.6.1项目为例:
- 传统的.net framework项目
- 卸载此项目,并编辑.csproj文件
当前步骤编辑好的.csproj文件如下:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net461</TargetFramework> </PropertyGroup> </Project>
- 移除Properites中的assemblyInfo文件
因为dotnet 会自动创建assemblyInfo相关的信息文件,如果要自定义,可以找到相关资料进行限制
注意,少了assemblyinfo.cs文件。
- 还原相关引用
生成项目,根据提示引用相关类型,默认调整后的是 类库 文件,如果需要变成 应用执行程序,可以自己修正即可。
- 生成并运行
在还原好依赖项,以及调整好输出类型后,即可完成 项目的迁移。最终的项目结果:
以及最终生成的csproj文件:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net461</TargetFramework> <ApplicationIcon /> <OutputType>Exe</OutputType> <StartupObject /> </PropertyGroup> <ItemGroup> <ProjectReference Include="..\..\src\SAM.Configuration\SAM.Configuration.csproj" /> <ProjectReference Include="..\..\src\SAM.Framework\SAM.Framework.csproj" /> </ItemGroup> </Project>
多版本的支持
如果你需要的项目即能在.net40,.net461, .net core2.0上运行,那就需要做到多版本兼容。方式即为简单,需要将TargetFramework改为TargetFrameworks,然后多版本用分号隔开即可:
<TargetFrameworks>net461;net40;netcoreapp2.0</TargetFrameworks>
二、统一实现
在解决了上述三个主要问题之后,实现自动编译和出包就很简单。我们可以写一个ps1文件来实现,也就是powershell脚本,整体代码如下:
#定义全局变量 $destFolder = Get-Location; $version = "1.0.0.0-alpha"; #编译anycpu的包 $anycpuPath = $destFolder.Path + "\..\SAM.Framework.anycpu.sln"; dotnet pack $anycpuPath --output $destFolder.Path /p:PackageVersion=$version; #编译x86的包 $anycpuPath = $destFolder.Path + "\..\SAM.Framework.x86.sln"; dotnet pack $anycpuPath --output $destFolder.Path /p:PackageVersion=$version /p:platform=x86; pause
是不是很简单,几句代码就是实现将所有项目生成nuget包。且还可以分平台实现。核心参数说明:
- /p:是设置msbuild相关参数的入口,如:包版本(版本在dotnet core上好像此支持三个阶段,如:1.0.0这种,而四阶段没有了),以及平台等。
- --output:生成的包输出文件夹
此命令还有些不足点,如 anycpu和x86中都编译同一个项目,后者的编译会覆盖前者(感觉可以用nuget包依赖来解决)。
三、后话
在解决此问题之前,本人也走了不少的弯路。如寻找msbuild与dotnet build的兼容方案,以及如何动态修正sln文件等,但还好最终还是解决解决。回过头来看,其实遇到问题换个角度,打开思维才是最重要的。
上周的某一天,和一位同样是前端技术极度爱好的开发者朋友聊天,他在提出了一个问题,他写的vue程序为什么在dev模式运行良好,而在production模式就直接报错了。这让我感到惊讶,还有这么神奇的事情。今就把这个历险记道给大伙听听,看能从中学习到什么?
一、还原现场
朋友在看到我的惊讶后,分分就把他出错的demo发给了,本地运行,事故现场重现:
二、排查嫌疑对象
既然现象是必现,要么是自己的代码出了问题,要么就是vue有Bug(心里莫名的偷笑,大伙都懂的)。
2.1 代码文件结构和源码展示
从代码结构看,没有好说的。就是用vue-cli创建的模板开发项目,其保增加了service层而已。经过我多年来的经验,将嫌疑放到了service/index.js和components/HellowWorld.vue两个文件上。
- service/index.js代码
let item = {}; item.result = 22222; item.do = callBack => { return callBack({ result: this.a.result }); }; export default item;
- HelloWorld.vue代码(只展示了js代码部分)
import service from "@/service/index" export default { name: 'HelloWorld', data () { return { msg: 'Welcome to Your Vue.js App' } }, mounted () { service.do(item => { this.msg = item.result }) } }
简要逻辑说明:
- HelloWorld.vue引用了service/index.js文件
- service/index.js中定义了do方法,且接受一个callback参数(使用了es6的箭头函数)
- HelloWorld.vue在mounted方法中调用了do方法,且传入了一个函数表达式(也使用了箭头函数)
2.2 报错位置侦查
通过运行结果对比图,可以看出production模式下的运行是有报错,在达里我们放大他的报错位置:
看到这里,你是否有想破口大骂的冲动,怎么会this.a.result呢,这代码明显有错误吧。然后我迅速查阅了他给的demo代码,见service/index.js中的do方法,确实是怎么样写的。立刻,我略带鄙视的口吻质问我的那位朋友,你这个几年的代码白写了吧,居然能犯这么低级的错误。我直接把这个错误现场图扔给了他。
马上,他回了一个更为鄙视的表情,那为什么我的dev模式能正常运行呢。我立即无语且尴尬。因为确实他的dev模式运行是正常的,只有在production模式下才出的问题啊。
2.3 重点分析嫌疑对象
经过上述的分析和折腾,我们可以初步确定问题点就在service/index.js中do方法中和this上。也就是说在dev模式下这个this.a上是有result这个属性的,而在production模式下this连这个a属性都没有了。
作为老鸟的我,突然想到,dev模式和production模式都是运行在有sourcemap的的情况下的。这很不利用我们看编译后的代码。于是,我关闭了chrome浏览器的sourcemap功能,两种模式下代码如下:
- dev模式下的运行代码:
注意三个红框处的代码,webpack在dev模式下代码文件是没有合并成一个文件的,而是遵行commanJs规范,进行模式化加载的,而他对这个service/index.js这个模式导出时,用的名称正是a。也就是解释了在dev模式下this.a为什么会有效,他的this.a.result有值,则是因为他虽然是单文件模式化加载,但其文件中的js代码还是被bable做了转换,将箭头运算符转换为了es5可执行的代码。所以this.a.result是有值的。
- production模式下的运行代码:
看这段代码是否有些头大,其实从中我们只需要关心l这个变量的值,经测试发现,他的值不service/index.js中导出的对象,而是浏览器全局对象window。这就是为什么production模式下的代码不能正常运行的问题了。
三、我的推理和总结
通过上述分析,可以大致推理出webpack在dev模式下是按照commonJs模式将各个文件独立模式化加载和引用,而Build之后,各个文件模块被合并成了一个,且对servcie/index.js进行了直接导出。再中上箭头函数对this指向的处理,就造成了this.a无效了。
在这个demo中就算把service/index.js中的this指向处理好了,但其值还是会正常显示,原因在于vue组件中的mounted方法中也用到了箭头函数,其this的指向在运行时也会不正确。具体解释如下:
注意,不要在实例属性或者回调函数中(如 vm.$watch('a', newVal => this.myMethod()))使用箭头函数。因为箭头函数绑定父级上下文,所以 this 不会像预想的一样是 Vue 实例,而且 this.myMethod 是未被定义的。
vue官网说明地址:https://cn.vuejs.org/v2/guide/instance.html
3.1 原因总结
- this.a为什么可以访问,是因为webpack的dev下编译是单个文件模式化引用导致的
- vue组件的mounted中用不可使用箭头函数,这个vue的特性,官网可见。
- 示例代码下载
这样入门asp.net core 之 静态文件
本文章主要说明asp.net core中静态资源处理方案:
一、静态文件服务
首先明确contentRoot和webroot这两个概念
- contentRoot:web的项目文件夹,其中包含webroot和其他bin等其他文件夹
- webroot:webroot是站点文件夹,可用url访问的文件夹。默认为:"contentroot/wwwroot"
- 实现代码如下
Program中的代码
public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) //设置contentroot .UseWebRoot("mywwwroot") //设置webroot .UseUrls("http://*:5000") //其他电脑可以用ip地址访问 .Build();
StartUp中的代码
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseStaticFiles();//开启静态文件访问 //自定义静态文件访问 app.UseStaticFiles(new StaticFileOptions(){ FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "mystatic")), RequestPath = new PathString("/sam/static") }); //配置mvc app.UseMvc(routers=>{ routers.MapRoute("default", "{controller=Home}/{action=Index}/{id?}"); }); }
效果图下图:
1.1 目录浏览
实现代码如下:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseDirectoryBrowser(new DirectoryBrowserOptions(){ FileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath, "Controllers")), RequestPath = new PathString("/controller") }); }
1.2 默认文档
app.UseDefaultFiles方法开启默认访问的配置,配置项用DefaultFilesOption类表示,代码如下:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { //默认文件 DefaultFilesOptions defaultFiles = new DefaultFilesOptions(); defaultFiles.DefaultFileNames.Clear(); defaultFiles.DefaultFileNames.Add("myindex.html"); app.UseDefaultFiles(defaultFiles); app.UseStaticFiles(); //开启静态文件访问 }
注意此配置一定要在所有Use之前,否则设置不生效
1.3 UseFileServer
UserFileServer包含了UseStaticFiles, UseDefaultFiles, UserDirectoryBrowser的功能
app.UseFileServer(new FileServerOptions(){ EnableDefaultFiles, //是否开启默认文档 EnableDirectoryBrowsing, //是否开启目录浏览 DefaultFilesOptions, //默认文件设置 StaticFileOptions, //静态资源访问设置 DirectoryBrowserOptions, //目录浏览设置 });
二、静态文件授权
静态模块是不对文件进行权限检查的,包含wwwroot下的文件和文件夹。如果相进行权限控制,可以使用action返回一个FileResult来实现:
private string basePath = Common.Uitls.HostingEnvironment.ContentRootPath; public FileResult Index(int id){ if(id == 1){ return new PhysicalFileResult(Path.Combine(basePath, "aufolder","author.html"), "text/html"); } return new PhysicalFileResult(Path.Combine(basePath, "error.html"), "text/html");; }
三、FileExtensionContentTypeProvider类的使用
此类包含一个将文件扩展名映射到MIME内容类型的集合,代码如下:
FileExtensionContentTypeProvider provider=new FileExtensionContentTypeProvider(); provider.Mappings.Add(".sam", "text/plain"); //自定义静态文件访问 app.UseStaticFiles(new StaticFileOptions(){ FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "mystatic")), RequestPath = new PathString("/sam/static"), ContentTypeProvider = provider });
- FileExtensionContentTypeProvider类与UseStaticFiles关联使用
- 扩展名以 "." 开头。
- 运行结果如下:
-
这样入门asp.net core,如何
本文章主要说明asp.net core的创建和简单使用。
一、使用到的命令
- dotnet new :创建项目(解决方案,类库,单元测试等),如:
dotnet new web
- dotnet add package 添加一个nuget的引用
- dotnet test:运行测试
- dotnet build:编译项目
- dotnet sln add:将项目添加到解决方案
- dotnet add reference:对此项目添加项目引用
二、建立空项目
- 在测试目录下运行
dotnet new web -n baseWeb
,创建web项目,结果如下图所示: - 运行
dotnet run
,即可运行此项目,此命令内部会执行dotnet build
命令编译项目,然后运行,结果如下: -
说明:此命令是在sln(解决方案)文件所在目录运行的,所以增加了-p参数,用于指定启动指定的项目。
三、引入mvc
- 由于我们创建是一个空web项目,是没有mvc相关依赖包的,所以首先需要运行
dotnet add package Microsoft.AspNet.Mvc
进行依赖包的安装 - 然后在StartUp类的ConfigureServices方法中添加如下代码:
public void ConfigureServices(IServiceCollection service){ service.AddMvc(); service.AddRouting(); }
上述添加此添加了Mvc,也添加了路由
- 配置路由,还是在StartUp类中,其方法为Configure添加如下代码:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } //添加的代码 app.UseMvc(routers=>{ routers.MapRoute("default", "{controller=Home}/{action=Index}/{id?}"); }); app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); }); }
- 对controller和view的创建
在创建controller时,没有找到完全好的 命令创建方法,所以使用的是手动创建文件,然后继承Controller类实现;而view是可以通过dotnet new page
实现的,但也可以使用手动创建。文件结果如下:
- 执行
dotnet run
运行,然后网页访问对就的地址即可
四、更换依赖注入为autofac
aspnet core是自带了依赖注入功能的,就是StartUp类中ConfigureServices方法的IServiceCollection类型的形参。如果你想替换为autofac。请参考如下步骤
- 安装autofac,以及acutofac的扩展类
- Autofac
- Autofac.Extensions.DependencyInjection
- 更改StartUp类中的configureSeervices方法
public IServiceProvider ConfigureServices(IServiceCollection service){ service.AddMvc(); service.AddRouting(); var containerBuilder = new ContainerBuilder(); containerBuilder.Populate(service); containerBuilder.RegisterType<Services.ProductService>().As<Services.IProductService>(); var container = containerBuilder.Build(); return new AutofacServiceProvider(container); }
- 其中,我们更改了ConfigureServices方法的返回值,由void换成IServiceProvider;此返回为AutofacServiceProvider类的一个实例;在上述过程中,使用了continerBuilder.Populate方法将service(IServiceCollection的实例对象)注入到autofac中。
- 其中为了测试DI能力,我们创建一个IProductService的注入,并在Controller中使用,相关代码如下:
- IProductService
namespace baseWeb.Services { public interface IProductService { int Get(int id); } }
- ProductService
using System.Collections.Generic; namespace baseWeb.Services { public class ProductService:IProductService { List<int> ls = new List<int>(); public ProductService(){ for(var i=0; i<10; i++){ ls.Add(i+10); } } public int Get(int id){ return ls[id]; } } }
- HomeController
using System; using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; namespace baseWeb.Controllers { public class HomeController:Controller { private Services.IProductService productService; public HomeController(Services.IProductService productService){ this.productService = productService; } public IActionResult Index(int id){ ViewData["value"] = this.productService.Get(id); return View(); } } }
- 运行结果如下:
五、加入单元测试
在实际开发中,经常需要用到单元测试。.net core的单元测试也是如此的 so easy, fallow me。
- 运行
dotnet new xunit -n baseWebTest
,即可创建一个以xunit为测试框架的单元测试项目 - 运行
dotnet add package Moq
,安装Moq模拟框架(此命令需要进入到baseWebTest项目文件夹后运行) - 运行
dotnet add reference ../baseWeb/baseWeb.csproj
,将baseWeb项目引入到此项目中。 - 编写单元测试代码如下:
using System; using Xunit; using Moq; using baseWeb.Controllers; using baseWeb.Services; using Microsoft.AspNetCore.Mvc; namespace basewebtest { public class DemoTest { [Fact] public void Test1() { var productServiceMock = new Mock<IProductService>(); productServiceMock.Setup(service=>service.Get(1)).Returns((int id)=>{ return id+10; }); var _controller = new HomeController(productServiceMock.Object); var result = _controller.Index(1); var objectResult = Assert.IsType<ViewResult>(result); var destResult = Assert.IsAssignableFrom<int>(objectResult.ViewData["value"]); Assert.Equal(11, destResult); } } }
- 运行
dotnet test -p ./baseWebtest/baseWebtest.csproj
即可,看到如下结果:
六、其他
- 如果你是使用vscode编写,那么测试项目是可以在编译器中进行运行和调试的
- 下载测试源码
- dotnet new :创建项目(解决方案,类库,单元测试等),如: