[干货]Chloe官网及基于NFine的后台源码毫无保留开放
扯淡
经过不少日夜的赶工,Chloe 的官网于上周正式上线。上篇博客中LZ说过要将官网以及后台源码都会开放出来,为了尽快兑现我说过的话,趁周末,我稍微整理了一下项目的源码,就今儿毫无保留的开放给大家,希望能对大家有帮助,献丑了。
项目不大,官网就是几个展示页面。后台借鉴了 NFine 的设计,直接套用了 NFine 的前端模版以及他的一些权限设计。由于个人开发习惯和所用技术与 NFine 的有些不同,比如,NFine 前端数据展示用 jqgrid,而我比较倾向于使用 knockoutjs,同时,后端我也根据自己的一些思想搭建了一个与 NFine 完全不同的架构,所以,基本重写了 NFine 的前端开发模式和后端架构。目前可以在4个数据库间无缝切换(SqlServer、MySql、Oracle和SQLite),主要得益于强大的 Chloe^_^。
后台:
在线体验地址
官网: http://www.52chloe.com/
后台:http://www.52chloe.com:82/
项目介绍
前端主要技术
jQuery:举世闻名的前端类库
knockout.js:MVVM 框架,类似 AngularJS 和 vue.JS
bootstrap:家喻户晓的css
editormd:一个免费开源的 markdown 编辑器,主要用于文档编辑,很好用,推荐
layer:一款近年来备受青睐的web弹层组件。这个还是 NFine 本来就有的,不错的一个组件,我也直接用上了
jquery-plugin-validation:前端数据库验证插件,也是 NFine 本来就有的,很不错,我也保留了下来
后端是用asp.net mvc 5,Newtonsoft.Json,Validator,数据库访问毫无疑问是用 Chloe.ORM。
开发人员都喜欢给自己的框架安个名字,我也不例外,我这个将就叫做Ace吧(海贼王里的艾斯)。我们先看下整个项目架构:
很常规的分层,简单介绍下各层:
Ace:项目架构基础层,有点类似abp里面的abp.dll,里面包含了一些基础接口的定义,如应用服务接口,以及很多重用性高的代码。同时,我在这个文件夹下建了Ace.Web和Ace.Web.Mvc两个dll,分别是对asp.net和asp.net mvc的一些公共扩展和通用的方法。这一层里的东西,基本都是重用度极高的代码,而且是比较方便移植的。
Application:应(业)用(务)服(逻)务(辑)层。
Data:数据层。包含实体类和ORM操作有关的基础类。
Web:所谓的展示层。
整个框架并没有使用repository,原因有三:
* 没理解错的话repository是DDD领域驱动设计里的概念,我这个并不是DDD,我不会为了封装而封装。
* 不是DDD也可以用repository啊!没错,不是DDD也可以有repository。repository设计可以隐藏数据的来源细节,但如果用repository包装了一遍数据库访问组件(ORM),很可能因为一些所谓的设计“规范”会限制ORM框架的发挥,这不是我想要的结果。比如强大的EF,本身对连接查询以及各种功能支持是很好,但经过仓储包装后,可能变得不够灵活了,不知道您是否疑惑包装后该怎么多表连接查询,怎么调用存储过程,我想实现xxx怎么写好?我个人觉得,如果一个设计给开发带来了一定的困扰,就应该考虑设计是否应该存在或合理不合理了。引入一个框架,我们就应该要把它应有的功能发挥的淋漓尽致,不然不如不用。
* repository可以更加方便的切换数据库访问框架啊!这个确实有一定道理,想得很前瞻。然而,我不予考虑。因为得不偿失的行为。为什么这样说?如果项目真的需要换ORM,无非两个原因:1.所用的ORM数据库类型支持的少,而项目要换数据库类型,ORM却不支持新数据库了;2.所用ORM技术太老,就是想换个其他的ORM;3.所用ORM出现了性能瓶颈,拖程序后腿了。如果因为第一个原因而换数据库,那么更多的应该反省项目之初技术选型是否正确,因为Chloe已经友好支持支持了多数据库,所以这个原因在目前我们的项目里不会存在了。第二、三个原因分析:一个项目如果真的需要更换技术,肯定得对代码重构,试想一下,一个项目从开始到完成整个开发周期长还是重构的时间长?前面也说过,对ORM封装了一遍,多多少少会限制了ORM的发挥,开发的时候多多少少不顺,如果为了短时间(相对开发周期)重构的舒适而让整个开发过程饱受各种不爽、不顺折磨,我真的不乐意。再一个,项目一跑起来到项目被淘汰,很可能压根就不会更换ORM,所以,我觉得从方便切换ORM的角度考虑,使用仓储是得不偿失的行为。
以上只是个人对仓储的一些理解,并不完全正确,如有不同看法,还望各位留言探讨。
层层之间需要解耦,引用了什么IOC框架吗?No!IOC很“高大上”,实质就是一个超级大工厂,特色功能就是依赖注入,说白了就是支持有参构造函数创建对象和属性赋值。然而,我个人不大喜欢用注入,因此,我觉得没必要引入IOC框架了。所以我自己建了个超级工厂,充当了IOC容器的角色。
大概实现如下:
- public class AppServiceFactory : IAppServiceFactory
- {
- List<IAppService> _managedServices = new List<IAppService>();
- static readonly Type[] InjectConstructorParamTypes = new Type[] { typeof(IAppServiceFactoryProvider) };
- static readonly object[] EmptyObjects = new object[0];
- static List<Type> _serviceTypes = new List<Type>();
- public AppServiceFactory()
- : this(null)
- {
- }
- public AppServiceFactory(IAceSession session)
- {
- this.Session = session;
- }
- public IAceSession Session { get; set; }
- public void AttachService(IAppService service)
- {
- this._managedServices.Add(service);
- }
- public void DetachService(IAppService service)
- {
- this._managedServices.Remove(service);
- }
- public T CreateService<T>(bool managed = true) where T : IAppService
- {
- Type t = typeof(T);
- ConstructorInfo c = null;
- bool injectAppServiceFactoryProvider = false;
- /* List 的查找效率和反射创建对象的性能稍微差点,如有必要,需要优化 */
- _serviceTypes.Find(a =>
- {
- if (a.IsAbstract == true)
- return false;
- if (t.IsAssignableFrom(a) == false)
- return false;
- c = a.GetConstructor(Type.EmptyTypes);
- injectAppServiceFactoryProvider = false;
- if (c == null)
- {
- c = a.GetConstructor(InjectConstructorParamTypes);
- injectAppServiceFactoryProvider = true;
- }
- if (c == null)
- {
- return false;
- }
- return true;
- });
- if (c == null)
- throw new Exception("Can not find the service implementation.");
- T service = default(T);
- if (injectAppServiceFactoryProvider == false)
- {
- service = (T)c.Invoke(EmptyObjects);
- IAppServiceFactoryProvider factoryProvider = service as IAppServiceFactoryProvider;
- if (factoryProvider != null)
- {
- factoryProvider.ServiceFactory = this;
- }
- }
- else
- {
- service = (T)c.Invoke(new Object[1] { this });
- }
- IAceSessionAppService sessionService = service as IAceSessionAppService;
- if (sessionService != null)
- sessionService.AceSession = this.Session;
- if (managed == true)
- this.AttachService(service);
- return service;
- throw new NotImplementedException();
- }
- public void Dispose()
- {
- foreach (var service in this._managedServices)
- {
- if (service != null)
- service.Dispose();
- }
- }
- public static T CreateAppService<T>() where T : IAppService
- {
- using (AppServiceFactory factory = new AppServiceFactory())
- {
- return factory.CreateService<T>();
- }
- }
- public static void Register<T>()
- {
- Register(typeof(T));
- }
- public static void Register(Type t)
- {
- if (t == null || t.IsAbstract || typeof(IAppService).IsAssignableFrom(t) == false)
- throw new ArgumentException();
- lock (_serviceTypes)
- {
- _serviceTypes.Add(t);
- }
- }
- public static void RegisterServices()
- {
- Assembly assembly = Assembly.GetExecutingAssembly();
- RegisterServicesFromAssembly(assembly);
- }
- public static void RegisterServicesFromAssembly(Assembly assembly)
- {
- var typesToRegister = assembly.GetTypes().Where(a =>
- {
- var b = a.IsAbstract == false && a.IsClass && typeof(IAppService).IsAssignableFrom(a) && a.GetConstructor(Type.EmptyTypes) != null;
- return b;
- });
- foreach (var type in typesToRegister)
- {
- Register(type);
- }
- }
- }
程序启动的时候,在启动事件(Application_Start)里会将所有应用服务的实现类给注册进去。这个工厂实现了 IDisposable 方法,因为有些应用服务用完是需要销毁的,所以,这个工厂创建出对象的同时,还承担销毁应用服务对象的职责。
对象到对象的映射框架呢?也No,DTO和实体间转换目前我是傻呼呼的一个一个属性赋值搞的,但以后有可能会引入。
当今流行的repository、IOC和OOM等技术都不用,大家会不会觉得LZ很奇葩或者out了?嘿嘿,我的理念是能减少依赖就尽量减少依赖。减少学习成本同时也可以更好的移植或掌控项目。
对于一个开发框架而言,最基本的就是规范和避免重复编码。虽然我的这个框架简单,但不乏实用技巧点。
框架的一些规范与技巧设计:
ajax请求:对于系统后台,ajax交互是很频繁的事。对于ajax请求的返回数据我制定了基本的状态码和格式
- public enum ResultStatus
- {
- OK = 100,
- Failed = 101,
- NotLogin = 102,
- Unauthorized = 103,
- }
- public class Result
- {
- ResultStatus _status = ResultStatus.OK;
- public Result()
- {
- }
- public Result(ResultStatus status)
- {
- this._status = status;
- }
- public Result(ResultStatus status, string msg)
- {
- this.Status = status;
- this.Msg = msg;
- }
- public ResultStatus Status { get { return this._status; } set { this._status = value; } }
- public object Data { get; set; }
- public string Msg { get; set; }
- }
序列化成json大概是这样子:{"Data":{},"Status":100,"Msg":null}
同时,为了避免频繁new这个Result类,我在web层的 BaseController 中增加了很多有关方法:
- protected ContentResult JsonContent(object obj)
- {
- string json = JsonHelper.Serialize(obj);
- return base.Content(json);
- }
- protected ContentResult SuccessData(object data = null)
- {
- Result<object> result = Result.CreateResult<object>(ResultStatus.OK, data);
- return this.JsonContent(result);
- }
- protected ContentResult SuccessMsg(string msg = null)
- {
- Result result = new Result(ResultStatus.OK, msg);
- return this.JsonContent(result);
- }
- protected ContentResult AddSuccessData(object data, string msg = "添加成功")
- {
- Result<object> result = Result.CreateResult<object>(ResultStatus.OK, data);
- result.Msg = msg;
- return this.JsonContent(result);
- }
- protected ContentResult AddSuccessMsg(string msg = "添加成功")
- {
- return this.SuccessMsg(msg);
- }
- protected ContentResult UpdateSuccessMsg(string msg = "更新成功")
- {
- return this.SuccessMsg(msg);
- }
- protected ContentResult DeleteSuccessMsg(string msg = "删除成功")
- {
- return this.SuccessMsg(msg);
- }
- protected ContentResult FailedMsg(string msg = null)
- {
- Result retResult = new Result(ResultStatus.Failed, msg);
- return this.JsonContent(retResult);
- }
这样,我们在Action中可以直接这样用:
- [HttpGet]
- public ActionResult GetModels(Pagination pagination, string keyword)
- {
- PagedData<Sys_User> pagedData = this.CreateService<IUserAppService>().GetPageData(pagination, keyword);
- return this.SuccessData(pagedData);
- }
- [HttpPost]
- public ActionResult Add(AddUserInput input)
- {
- this.CreateService<IUserAppService>().AddUser(input);
- return this.AddSuccessMsg();
- }
- [HttpPost]
- public ActionResult Update(UpdateUserInput input)
- {
- this.CreateService<IUserAppService>().UpdateUser(input);
- return this.UpdateSuccessMsg();
- }
- [HttpPost]
- public ActionResult Delete(string id)
- {
- this.CreateService<IUserAppService>().DeleteAccount(id);
- return this.DeleteSuccessMsg();
- }
- [HttpPost]
- public ActionResult RevisePassword(string userId, string newPassword)
- {
- if (userId.IsNullOrEmpty())
- return this.FailedMsg("userId 不能为空");
- this.CreateService<IUserAppService>().RevisePassword(userId, newPassword);
- return this.SuccessMsg("重置密码成功");
- }
通过vs强大的智能提示,直接 this. 就可以出来,省了不少事。
尽量"少的使用using":
在.net的规范中,对于任何实现了 IDisposable 接口的类,用完我们都应将其销毁掉。在项目开发中,如果充斥着太多的 using 写法,我是非常烦的,我想大家也是同样的感受,比如 DbContext。但如何避免使用 using,但又保证对象最终又被销毁呢?如果大家用了一些IOC框架,估计不需要太多的关心对象的销毁问题,因为IOC容器帮大家做了这些事。我不用IOC,那该咋办呢?其实不难,就拿 DbContext 举例。在我这个架构中,应用服务层是直接操作 DbContext 的,我建了个应用服务基类(AppServiceBase),DbContext 的创建和销毁交给 AppServiceBase 就行了。因此,这又引申了一个规范,每个应用服务必须实现 IDisposable 接口(一个类内部如果有 IDisposable 的对象,我觉得该类也应该实现 IDisposable 接口)!
- public abstract class AppServiceBase : IAppServiceFactoryProvider, IDisposable
- {
- IDbContext _dbContext;
- protected AppServiceBase()
- : this(null)
- {
- }
- protected AppServiceBase(IAppServiceFactory serviceFactory)
- {
- this.ServiceFactory = serviceFactory;
- }
- public IAppServiceFactory ServiceFactory { get; set; }
- public IDbContext DbContext
- {
- get
- {
- if (this._dbContext == null)
- this._dbContext = DbContextFactory.CreateContext();
- return this._dbContext;
- }
- set
- {
- this._dbContext = value;
- }
- }
- public void Dispose()
- {
- if (this._dbContext != null)
- {
- this._dbContext.Dispose();
- }
- this.Dispose(true);
- }
- protected virtual void Dispose(bool disposing)
- {
- }
- }
然后子类应用服务就可以直接 this.DbContext 这样毫无顾忌的使用 DbContext 了,再也不用关心 DbContext 是如何创建和被销毁了。技巧虽小,却给我开发便利了许多。
所有应用服务是由前面提到的超级工厂创建的,所以,我的超级工厂也需要实现 IDisposable 接口,目的就是为了掌管其创建出的应用服务对象。那么问题来了,LZ你使用的超级工厂对象的时候还不是得要销毁你的超级工厂,不用 using 怎么做?哈哈,这个问题用同样的基类方式就可以解决。
不知道大家是否留意MVC的 Controller 这个类也实现了 IDisposable 接口,它提供了一个 Dispose(bool disposing) 方法的重载!对于这个重载方法,我们好好利用它就行,嘿嘿~
我们建的 Controller 都会继承于我们自定义的 BaseController 中,BaseController 则重写了 Dispose(bool disposing) 方法,用于销毁一些我们自定义的 IDisposable 对象:
- public abstract class BaseController : Controller
- {
- static readonly Type TypeOfCurrent = typeof(BaseController);
- static readonly Type TypeOfDisposableAttribute = typeof(DisposableAttribute);
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- this.DisposeMembers();
- }
- [Disposable]
- AppServiceFactory _appServicesFactory;
- IAppServiceFactory AppServicesFactory
- {
- get
- {
- if (this._appServicesFactory == null)
- this._appServicesFactory = new AppServiceFactory(this.CurrentSession);
- return this._appServicesFactory;
- }
- }
- protected T CreateService<T>(bool managed = true) where T : IAppService
- {
- return this.AppServicesFactory.CreateService<T>(managed);
- }
- /// <summary>
- /// 扫描对象内所有带有 DisposableAttribute 标记并实现了 IDisposable 接口的属性和字段,执行其 Dispose() 方法
- /// </summary>
- void DisposeMembers()
- {
- Type type = this.GetType();
- List<PropertyInfo> properties = new List<PropertyInfo>();
- List<FieldInfo> fields = new List<FieldInfo>();
- Type searchType = type;
- while (true)
- {
- properties.AddRange(searchType.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly).Where(a =>
- {
- if (typeof(IDisposable).IsAssignableFrom(a.PropertyType))
- {
- return a.CustomAttributes.Any(c => c.AttributeType == TypeOfDisposableAttribute);
- }
- return false;
- }));
- fields.AddRange(searchType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly).Where(a =>
- {
- if (typeof(IDisposable).IsAssignableFrom(a.FieldType))
- {
- return a.CustomAttributes.Any(c => c.AttributeType == TypeOfDisposableAttribute);
- }
- return false;
- }));
- if (searchType == TypeOfCurrent)
- break;
- else
- searchType = searchType.BaseType;
- }
- foreach (var pro in properties)
- {
- IDisposable val = pro.GetValue(this) as IDisposable;
- if (val != null)
- val.Dispose();
- }
- foreach (var field in fields)
- {
- IDisposable val = field.GetValue(this) as IDisposable;
- if (val != null)
- val.Dispose();
- }
- }
- }
注意看,上面的 BaseController 有一个 CreateService 方法,用于创建应用服务对象。然后呢,子类的操作方法中,我们就可以毫无顾忌的调用 this.CreateService 方法创建相关的应用服务了:
- [HttpPost]
- public ActionResult Add(AddUserInput input)
- {
- this.CreateService<IUserAppService>().AddUser(input);
- return this.AddSuccessMsg();
- }
如果您从github下载了源码就会发现,源码中using关键字少之又少~
管理每个View对应的js文件:
对于后台开发,基本上每个页面都需要js支持,但我们又不想js代码和html放在一个窗口里开发,所以大家都习惯建一个js文件,然后在页面中引用就行了。在 webform 时代,我们都习惯将页面和其对应的js文件放在同一个文件夹中,方便!!但在mvc默认设置中,views文件夹下是不允许放js文件的,这可咋办呢??虽然有设置可以让views文件夹里的js文件可以被访问,但,我不想打破mvc的默认规则!因此,我用了个投机取巧的办法,就是利用mvc的部分视图功能。我把js代码写在部分视图中,然后页面中直接以@Html.Partial("Index-js")的方式引用,如下:
这样一个页面就能和它对应的js文件成双对的在同一个文件夹中了,实现了js代码和 html 代码可以分开!但有一点要注意,最终的页面输出到浏览器的时候 html 和 js 代码是混在一起的!那么问题来了,你这样干会影响页面加载速度,并且没能充分利用浏览器对静态资源的缓存啊~在一些技术群里,我跟他们说出我的这个设想和做法时,有不少人提出了这样的质疑~其实,对于一个后台页面而言,这点影响不足为惧,并没有想象中那么大。这其实是个智仁问题,我也不多解释了,利弊自己权衡就好。没有绝对好的设计,只要实用和适合自己就够了!~
结语
每个人都梦想有个属于自己的开发框架,我也一样。目前的Ace框架虽然简单,在大牛面前不值一提,但还是希望能对一些人有帮助。
官网使用的是 SQLite 数据库,GitHub 项目中已经有了相应的db文件,只要你本地装了asp.net环境,下载即可运行。考虑到很多同学对 SQLite 不熟悉,以及大家装的是 SqlServer、MySql 或 Oracle,我也给大家准备好了所有的相关dll以及相应的数据库脚本,只要建个数据库,然后运行脚本就可以创建相关的表了。框架已经支持4种数据库之间随意切换,所以,大家在web.config里设置下数据库类型和数据库连接字符串就可以运行了(web.config都有修改说明)。试问,世上这么热心的程序员,除了博主还有谁?如果这都换不来您的一个推荐,博主我真的无语问苍天,是时候考虑关闭博客了555~
技术教程或心得我倒不是很擅长写,我只想把日常开发的一些干货分享给大家,您的推荐是我分享的最大动力。同时,如果您对 Chloe 这个项目感兴趣,敬请在 Github 关注或收藏(star)一下,以便能及时收到更新通知。也欢迎广大C#同胞入群交流,畅谈.NET复兴大计。最后,感谢大家阅读至此!同时也非常感谢 NFine 作者给咱们提供了一个那么好的一个开发框架!
Chloe 官网及后台源码地址:https://github.com/shuxinqin/Ace
[干货]Chloe官网及基于NFine的后台源码毫无保留开放的更多相关文章
- NFine的后台源码
Chloe官网及基于NFine的后台源码毫无保留开放 扯淡 经过不少日夜的赶工,Chloe 的官网于上周正式上线.上篇博客中LZ说过要将官网以及后台源码都会开放出来,为了尽快兑现我说过的话,趁周末 ...
- 基于Eclipse搭建Hadoop源码环境
Hadoop使用ant+ivy组织工程,无法直接导入Eclipse中.本文将介绍如何基于Eclipse搭建Hadoop源码环境. 准备工作 本文使用的操作系统为CentOS.需要的软件版本:hadoo ...
- 网狐6603手机棋牌游戏源码.rar
网狐6603手机棋牌游戏源码.rar 文件大小: 333 MB 发布一款手机棋牌游戏源码带教程文档! 仅供学习,下载后请务必在24小时内删除! 网狐6603手机棋牌游戏源码 链接:http://p ...
- 基于SpringBoot的Environment源码理解实现分散配置
前提 org.springframework.core.env.Environment是当前应用运行环境的公开接口,主要包括应用程序运行环境的两个关键方面:配置文件(profiles)和属性.Envi ...
- 第一次作业:基于Linux操作系统深入源码进程模型分析
1.Linux操作系统的简易介绍 Linux系统一般有4个主要部分:内核.shell.文件系统和应用程序.内核.shell和文件系统一起形成了基本的操作系统结构,它们使得用户可以运行程序.管理文件并使 ...
- 面试官:你分析过SpringMVC的源码吗?
1. MVC使用 在研究源码之前,先来回顾以下springmvc 是如何配置的,这将能使我们更容易理解源码. 1.1 web.xml <servlet> <servlet-name& ...
- Django——基于类的视图源码分析 二
源码分析 抽象类和常用视图(base.py) 这个文件包含视图的顶级抽象类(View),基于模板的工具类(TemplateResponseMixin),模板视图(TemplateView)和重定向视图 ...
- 16Aspx.com-书通网中小学生免费在线学习网站源码 带采集带手机版帝国cms内核
=============================================== 源码站长资源交易专业网-商业源码下载,VIP源码,程序交易,毕业设计交易,站长交易|- 16aspx.c ...
- SPI机制剖析——基于DriverManager+ServiceLoader的源码分析
我的上一篇博客类加载器与双亲委派中提到,SPI机制是一种上级类加载器调用下级类加载器的情形,因此会打破类加载的双亲委派模型.为了深入理解其中的细节,本博客详细剖析一下SPI机制,并以JDBC为例,基于 ...
随机推荐
- 利用Python进行数据分析(8) pandas基础: Series和DataFrame的基本操作
一.reindex() 方法:重新索引 针对 Series 重新索引指的是根据index参数重新进行排序. 如果传入的索引值在数据里不存在,则不会报错,而是添加缺失值的新行. 不想用缺失值,可以用 ...
- c#使用Split分割换行符 \r\n
c# 使用Split分割 换行符,方法如下(其余方法有空再添加): string str = "aa" + "\r\n" + "bb"; ...
- Delphi_04_Delphi_Object_Pascal_基本语法_02
这里简单的描述Object的语法中的基本内容,数据类型.因为代码是自描述的所以不废话,直接贴代码. { 用户自定义类型 1.数组 2.动态数组 3.记录 4.集合 } program UserDefi ...
- 记录一次bug解决过程:else未补全导致数据泄露和代码优化
一.总结 快捷键ctrl + alt + 四个方向键 --> 倒置屏幕 未补全else逻辑,倒置查询数据泄露 空指针是最容易犯的错误,数据的空指针,可以普遍采用三目运算符来解决 SVN冲突解决关 ...
- Java面试题整理二(侧重SSH框架)
1.持久化对象的状态都有哪些? 答:瞬时对象(Transient Objects):使用new 操作符初始化的对象不是立刻就持久的.它们的状态是瞬时的,也就是说它们没有任何跟数据库表相关联的行为,只要 ...
- div 加载 html页面的方法
做网页的单页面应用时,需要在一个HTML的Div元素中加载另一个HTML页面,以前有一种方法就是用iframe,举例如下: <div class="main-container&quo ...
- 网站logo正确写法,个人拙见,不喜勿喷
网站logo既要考虑seo又需要用图片代替网站名字,所有H1标签带来的权重还是需要使用 有些人喜欢直接把<H1></H1>标签直接hidden掉,个人喜欢使用css Text- ...
- SharePoint 2013 开发教程
做了SharePoint有三年了,大家经常会问到,你的SharePoint是怎么学的,想想自己的水平,也不过是初级开发罢了.因为,SharePoint开发需要接触的东西太多了,Windows操作系统. ...
- 了解JavaScript 面向对象基础 & 原型与对象
面向对象语言中的对象 老是能听到什么基于对象, 面向对象. 什么是对象, 如果有面向对象基础的人可以无视了, 下面举个简单的例子给大家讲讲面向对象中, 对象的定义, 这个是比较通用的, 不过对于JS来 ...
- SEED信息安全实验系列:缓冲区溢出漏洞实验
缓冲区溢出漏洞实验 本课程详细出自http://www.shiyanlou.com/courses/231,转载请注明出处. 一.实验描述 缓冲区溢出是指程序试图向缓冲区写入超出预分配固定长度数据的情 ...