[转载]从事ASP.NET开发两年多,谈谈对两三年工作经验的ASP.NET程序员的基本见解

mikel阅读(1001)

[转载]从事ASP.NET开发两年多,谈谈对两三年工作经验的ASP.NET程序员的基本见解 – 问题最终都是时间问题,烦恼其实都是自寻烦恼。 – 博客园.

粗略算来,自己从事.NET开发工作2年有余,也来谈谈自己对两三年工作经验的.NET程序员的基本见解。

我想众所周知,Microsoft的东西入门都是比较简单的,但是提高并不容易,这也就导致了很多培训机构借此良机,忽悠了大批的 甚至对编程压根不知所以然的人加入.NET的开发阵营,然后告诉他们包教,包会,包就业的三包政策。当然也有另一部分人是受过高等教育,之后出来从 事.NET开发工作,但是他们或者也是受到了一些环境的影响,在WEBFORM的开发模式中,很喜欢托拉控件,编辑模板之类的操作,甚至不知道这些控件最 终被解析成什么东西,只知道我实现了,而不问其所以然,或者是效率如何,等等。

下面谈谈几点个人愚见,希望能对两三年工作经验的.NET开发人员一点提醒:

1、Gridview之错,错,错

我想从事ASP.NET开发工作的应当有相当一部分人是做基于信息管理系统类软件开发的,这样一来可能就会经常与数据报表打交道,Gridview这个东 西可能就是在熟悉不过了。但是我想不通的是为什么有这么多的人喜欢用它呢?我总结了这种控件的缺点,如下:

(1)糟糕的编辑环境,看不见TR,TD,写样式也变得异常麻烦

代码

<asp:GridView ID=gvList runat=server CssClass=GridViewCSS Width=100% AutoGenerateColumns=False EmptyDataText=No Data>
<Columns>
<asp:TemplateField HeaderText=行号>
<ItemTemplate>
<asp:Label ID=lblRowId runat=server Text=<%# Container.DataItemIndex +1%>></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField=XXX1 HeaderText=XXX1 />
<asp:BoundField DataField=XXX2 HeaderText=XXX2 />
<asp:BoundField DataField=XXX3 HeaderText=XXX3 />
<asp:BoundField DataField=XXX4 HeaderText=XXX4 />
<asp:BoundField DataField=XXX5 HeaderText=XXX5 />
<asp:BoundField DataField=XXX6 HeaderText=XXX6 />
<asp:BoundField DataField=XXX7 HeaderText=XXX7 />
<asp:BoundField DataField=XXX8 HeaderText=XXX8 />
</Columns>
</asp:GridView>

(2)有人甚至喜欢在模板列里面编辑,我看着就一个字晕

(3)生成糟糕的HTML标签

(4)Gridview操作起来很不灵活

这句话的意思是我想要方便的控制Table的TR,TD,譬如在合并单元格,等等很多问题的处理上很不方便,以下是我用Repeater在处理合并单元格 的问题的实例:

HTML部分:

代码

<asp:Repeater ID=rpList runat=server EnableViewState=false>
<HeaderTemplate>
<%
if (rpList.Items.Count == 0)
{
%><div id=dNoData>No Data</div><%
}
else {
%>
<table>
<tr>
<th>XXX1</th>
<th>XXX2</th>
<th>XXX3</th>
<th>XXX4</th>
<th>XXX5</th>
<th>XXX6</th>
<th>XXX7</th>
</tr>
<%
}
%>
</HeaderTemplate>
<ItemTemplate>
<tr>
<td id=tdContainerNo runat=server ><%#Eval(ContainerNo)%></td>
<td><%#Eval(X1)%></td>
<td><%#Eval(X2)%></td>
<td><%#Eval(X3)%></td>
<td><%#Eval(X4)%></td>
<td><%#Eval(X5)%></td>
<td id=tdtotal runat=server><%#Eval(X6)%></td>
</tr>
</ItemTemplate>
<FooterTemplate>
</table>
</FooterTemplate>
</asp:Repeater>

CS部分:

代码

for (int i = rpList.Items.Count 1; i > 0; i)
{
HtmlTableCell tdContainerNo_previous
= this.rpList.Items[i 1].FindControl(tdContainerNo) as HtmlTableCell;
HtmlTableCell tdContainerNo
= this.rpList.Items[i].FindControl(tdContainerNo) as HtmlTableCell;

HtmlTableCell tdtotal_previous = this.rpList.Items[i 1].FindControl(tdtotal) as HtmlTableCell;
HtmlTableCell tdtotal
= this.rpList.Items[i].FindControl(tdtotal) as HtmlTableCell;

tdContainerNo.RowSpan = (tdContainerNo.RowSpan == 1) ? 1 : tdContainerNo.RowSpan;
tdContainerNo_previous.RowSpan
= (tdContainerNo_previous.RowSpan == 1) ? 1 : tdContainerNo_previous.RowSpan;

if (tdContainerNo.InnerText == tdContainerNo_previous.InnerText)
{
tdContainerNo.Visible
= false;
tdContainerNo_previous.RowSpan
+= tdContainerNo.RowSpan;
}

tdtotal.RowSpan = (tdtotal.RowSpan == 1) ? 1 : tdtotal.RowSpan;
tdtotal_previous.RowSpan
= (tdtotal_previous.RowSpan == 1) ? 1 : tdtotal_previous.RowSpan;

if (tdContainerNo.InnerText == tdContainerNo_previous.InnerText && tdtotal.InnerText == tdtotal_previous.InnerText)
{
tdtotal.Visible
= false;
tdtotal_previous.RowSpan
+= tdtotal.RowSpan;
}
}

(5)Gridview生成的效率问题

Gridview集成了这么多的东西,我想在效率上,应该也好不到哪里去吧。

最后:当然Gridview还有诸多其他问题,这里就不一一列举了,这里只列举几个最常见得问题,让我想不通的是,有些人像Repeater这么简 单易用的控件,为什么不用,而去用Gridview这种不易掌控的控件呢?

2、为啥还有人用FormView

有些人喜欢在设计界面的时候托一个FormView控件,然后在里面放一些Textbox、Button、Label之类的服务器端控件,这样在后台操作 的时候可以统一绑定,而后呢如果会经常有一些方法,要操作FormView中的服务器端控件,那么经常有些程序员会声明很多局部变量,先从 FormView去Findcontrol这些服务器端控件赋给这些全局变量,而后去再操作这些全局变量,如下CODE:

//声明部分

private TextBox X1;
private TextBox X2;
private DropDownList X3;
private TextBox X4;
private TextBox X5;
private TextBox X6;

//赋值部分

看到这样的CODE真是让人无语,而且更让人无语的是,这些人仿佛很喜欢在模板列里面编辑内容。

3、疯狂的SQL 代码拼接

虽然说在在MS SQL 2005以后的版本中,对很长的SQL代码的执行效率,比执行由这段SQL生成的存储过程的时间多的有限,但是我们知道,存储过程是预先编译好的存放在数 据库中的,你要调用它,只需要传一个很短的字符串,加N个参数而已。而超长的SQL代码呢,你需要预先将其拼接成要执行的SQL 代码(SQL代码很长,要分为很多行写),然后传到数据库中,数据库要将其编译(可能会编译出错,你这个时候才知道),然后在执行这段SQL代码。你别说 我还真见到过很多人是这样写的,为什么不一句存储过程了事呢,改起来也很方便。

4、恐怖的viewstate

有些.NET程序员压根对viewstate不知其所以然,甚至在用webform的过程中,对其开发生成的HTML源码视而不见,下面我们来看看 viewstate产生的乱码:

这还是算小的了,数据控件在显示数量大的时候,你如果将这些东西拷贝到TXT文件中,然后看看他们的大小,是很恐怖的,虽然在开发webform过 程中,很多情况下viewstate确实帮我们节省了不少开发时间,也方便我很多,但是我要说的是,根据实际情况决定你的viewstate,能禁掉就禁 掉吧。

5、很喜欢到服务器端做验证(修正:应该 client/Server同时验证)

在开发过程中,我们会经常遇到这样的问题,验证用户输入内容的格式正不正确,例如,用户输入个EMail,我们就需要判 断输入的格式正不正确,然后有很多程序员就喜欢在CS文件中取this.txtXX.Text的内容作验证,然后弄个弹出框提示用户,殊不知这个简单的验 证,还要跑到Server上做,为什么不写个JS提示一下呢,不是很简单吗?

6、不知其所以然

很多两三经验的.NET程序在开发过程中托控件托习惯了,都不知道最后控件被解析什么样子,甚至不知道自己开发的 ASP.NET程序的工作流程,原理,完全被Microsoft傻瓜化了,只知道我这样做就实现了功能,却不知道你这样做为什么能实现。这是一个很恐怖的 信号,希望活跃在.NET的兄弟们在开发程序的时候能知其然,知其所以然。

7、吃老本,不学习新的技术

众所周知,Microsoft的技术是更新很快的,有些人会抱怨跟不上节奏,索性不问,老是沉迷于过去式中,认为老的 技术成熟,而且自己很熟悉,殊不知,这是一种退化的表现。我们应该明白,新事物的出 现,肯定是为了补充旧事物的所缺少的地方,是为了更完美,更便捷的处理实际问题,我们不能一概的去否定它,而应该去了解他,学习他。Linq不是很好用 吗,但是到现在还发现很多人对Linq是一知半解。

今天就写到这里吧,以上内容是我经常见到的,所以写在这里,希望给两三工作经验的程序员朋友提个醒!

作者:Tonny Yang
出处:http://www.cnblogs.com/yangtongnet/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

[转载]SaaS数据存储解决方案

mikel阅读(1035)

[转载]SaaS数据存储解决方案 – hushouqi – 博客园.

引言:

本文提出了一个SaaS数据存储解决方案。为方便理解请花几分钟时间浏览上一篇《SaaS平台概要设计》http://www.cnblogs.com/hushouqi/archive/2010/06/10/1755371.html 上一篇是对层次与对象的定义,很简单。

概要:

针对于不同的用 户,建立独立的数据区域是比较理想的方式。独立的数据区域有几个好处:不影响检索速度;简化开发难度;易扩展;增强数据安全性。在这里,只提供数据库的方 式,对不同的用户建立不同的数据库。数据库种类不限制,SQLServer,oracle,mySQL等等。

数据存储分层:

图示很直观。简单概括一下数据分为两类,平台数据用 户数据。平台数据包括群组,用户,角色等信息。用户数据是应用系统的数据。每个企业拥有自己的数据库。

DataProvider组 件:

接上篇介绍的类,详细设计DataProvider组件。

要实现用户各自存储数据,最关键的是要为各用户提供数据源(DataSource)配置。

数据源接口定义如下

public interface IDataSource {

string OrganizationID { get;set;}

string ConnString {get;set;}

string DBType { get;set;}

}

此接口定义了数据库连接字符,数据库类型和组织ID.

为每个组织定义默认的DataSource.

组织对数据源的接口定义

public class Organization {

public IDataSource GetDefaultDataSource{ get;}

}

组织拥有默认的IDataSource

DataProvider的定义

public interface IDataProvider

{

public DbConnection DbConncetion { get;}

public DbCommand DbCommand { get;}

public DbDataAdapter DbDataAdaper { get;}

}

此类是对数据库执行类的封装。可以使用抽象工厂模式对 其进行扩展,实现对不同种类数据库的封装。在这里不详细介绍了,因为这部分的内容类似文章很多,微软也做过对其封装。

public class DataProviderFactory

{

public static IDataProvider CreateDataProvider(IDataSource ds)

}

此类是个工具类,提供了一个静态方法,根据IDataSource创建IDataProvider实例。

到这里,接口大体上就清晰了。要对用户自身数据进行操 作,只要获取到当前用户的IDataProvider即可。而创建一个IDataProvider需要依赖于一个IDataSource实 例。IDataSource实例可以通过Organization对象获取到。

IDataSource对象构造顺序:

1. 用户成功登录,通过User查找Organization.

2. 通过Organization查找自身的IDataSource

3. IDataSource保存至Session

总结:

以上对象的数据都在公共数据层保存,在登录后,获取到 用户自身的IDataProvider

应用层就使用IDataProvider进行各项应用存储了。

[转载]JQuery另类视角-动态执行脚本的BUG

mikel阅读(1174)

[转载]JQuery另类视角-动态执行脚本的BUG – Kevin-moon – 博客园.

最近再用JQuery写一些东西,昨天突然出了个很奇怪的问题:

我通过Ajax请求服务器上的数据(包括html和script),然后将请求的数据动态加载到页面的一个div中,这其实是个很简单的程序, 首次执行是很顺利,但是当你执行这个相同动作两次之后,html元素依然可以顺利加载,不过script脚本就失效了!

使用的是JQuery的1.4.1版本,加载的程序是用jQuery中的$(…).append()方法。类试代码如下:

$(#testdiv).append(<script type=’text/JavaScript’> a = 1 ;alert(a); + < + /script>);

你执行后会发现,前两次能很成功的弹出提示框,但是从第三次开始就失效了!

为什么?JavaScript本身不可能不支持这种程序的,难道是jQuery程序的BUG吗?!是的,该BUG已经在1.4.2版本中进行了 修复,你可以换成JQuery-1.4.2版本来试下。真是郁闷,这个问题害的我查了一个晚上才发现…..

下面让我们分析下这个BUG的原因:

首先看下append函数中主要的执行流程,

执行script脚本的程序是在domManip函数中,大概的逻辑如下:

导致该问题的函数是buildFragment函数,对比下该函数在这两个版本的代码,

jquery-1.4.1

function buildFragment( args, nodes, scripts ) {
var fragment, cacheable, cacheresults, doc;

// webkit does not clone ‘checked’ attribute of radio inputs on cloneNode, so don’t cache if string has a checked
if (args.length === 1 && typeof args[0] === string && args[0].length < 512 && args[0].indexOf(<option) < 0
&& (jQuery.support.checkClone || !rchecked.test( args[0] )) ) {
cacheable
= true;
cacheresults
= jQuery.fragments[ args[0] ];
if ( cacheresults ) {
if ( cacheresults !== 1 ) {
fragment
= cacheresults;
}
}
}

if ( !fragment ) {
doc
= (nodes && nodes[0] ? nodes[0].ownerDocument || nodes[0] : document);
fragment
= doc.createDocumentFragment();
jQuery.clean( args, doc, fragment, scripts );
}

if ( cacheable ) {
jQuery.fragments[ args[
0] ] = cacheresults ? fragment : 1;
}

return { fragment: fragment, cacheable: cacheable };
}

jquery-1.4.2

function buildFragment( args, nodes, scripts ) {
var fragment, cacheable, cacheresults,
doc
= (nodes && nodes[0] ? nodes[0].ownerDocument || nodes[0] : document);

// Only cache “small” (1/2 KB) strings that are associated with the main document
// Cloning options loses the selected state, so don’t cache them
// IE 6 doesn’t like it when you put <object> or <embed> elements in a fragment
// Also, WebKit does not clone ‘checked’ attributes on cloneNode, so don’t cache
if ( args.length === 1 && typeof args[0] === string && args[0].length < 512 && doc === document &&
!rnocache.test( args[0] ) && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) {

cacheable = true;
cacheresults
= jQuery.fragments[ args[0] ];
if ( cacheresults ) {
if ( cacheresults !== 1 ) {
fragment
= cacheresults;
}
}
}

if ( !fragment ) {
fragment
= doc.createDocumentFragment();
jQuery.clean( args, doc, fragment, scripts );
}

if ( cacheable ) {
jQuery.fragments[ args[
0] ] = cacheresults ? fragment : 1;
}

return { fragment: fragment, cacheable: cacheable };
}

仔细看完这两段代码后,会发现这个函数内设置scripts的程序是jQuery.clean( args, doc, fragment, scripts ),这行代码在执行之前有个判断:如果该传入的脚本数据允许支持缓存,并且数据在缓存jQuery.fragments中存在,那么这行代码是跳过的!否 则,该行代码执行,设置scripts数据。

现在把关注点放到”允许支持缓存”这句话上,比较这两个版本对于该程序的不同。!rnocache.test( args[0] ),这是1.4.2版本中多加的一行代码。终于找到它,它是这个BUG解决的关键所在。让我们看下这个rnocache 的正则表达式:

rnocache = /<script|<object|<embed|<option|<style/i,

看了这么多发现原因了吗!原来1.4.1版本会对script脚本进行缓存,一旦script缓存在脚本后,该脚本中的程序是不会执行的,而 1.4.2多加了上面的正则,目的就是对script等这些数据不进行缓存,只要存在就会去执行!

[转载]C#通用的序列化方法

mikel阅读(1074)

[转载]通用的序列化方法 – 邹泽栋 – 博客园.

01 /// <summary>
02 /// 序列化数据
03 /// </summary>
04 /// <param name="obj">对象</param>
05 /// <returns>二进制流</returns>
06 private byte[] GetBinaryFormatSerialize(object obj)
07 {
08 //创建支持存储 区为内存的流
09 MemoryStream Memory = new MemoryStream();
10 //以二进制格式将整个连接对象图形序列化和反序列化
11 BinaryFormatter formatter = new BinaryFormatter();
12 //将对象序列化为附加所提供标题的给定流
13 formatter.Serialize(Memory, obj);
14 byte[] buff = Memory.ToArray();
15 Memory.Close();
16 Memory.Dispose();
17 return buff;
18 }
19
20 /// <summary>
21 /// 反序列化对象
22 /// </summary>
23 /// <typeparam name="T">类型</typeparam>
24 /// <param name="buff">二进制数据</param>
25 /// <returns>反序列后结果</returns>
26 private T RetrieveDeserialize<T>(byte[] buff)
27 {
28 //创建支持存储 区为内存的流
29 MemoryStream Memory = new MemoryStream();
30 //以二进制格式将整个连接对象图形序列化和反序列化。
31 BinaryFormatter formatter = new BinaryFormatter();
32 return (T)formatter.Deserialize(new MemoryStream(buff));
33 }
34 <pre class="brush:csharp"> //调用
35 DataTable dt = new DataTable();
36 #region 序列化
37 byte[] buff = GetBinaryFormatSerialize(dt);
38 #endregion
39
40 #region  反序列化
41 dt = RetrieveDeserialize<DataTable>(buff);
42 #endregion
43 </pre>

[转载]mysql “group by ”与"order by"的研究--分类中最新的内容

mikel阅读(1010)

[转载]mysql “group by ”与”order by”的研究--分类中最新的内容 – 中山php – 博客园.

这两天让一个数据查询难了。主要是对group by 理解的不够深入。才出现这样的情况
这种需求,我想很多人都遇到过。下面是我模拟我的内容表

我现在需要取出每个分类中最新的内容

select * from test group by category_id order by `date`

结果如下

明显。这不是我想要的数据,原因是msyql已经的执行顺序是

引用
写的顺序:select … from… where…. group by… having… order by..
执行顺序:from… where…group by… having…. select … order by…

所以在order by拿到的结果里已经是分组的完的最后结果。
由from到where的结果如下的内容。

到group by时就得到了根据category_id分出来的多个小组


到了select的时候,只从上面的每个组里取第一条信息结果 会如下

即使order by也只是从上面的结果里进行排序。并不是每个分类的最新信息。
回 到我的目的上 --分类中最新的信息
根据上面的分析,group by到select时只取到分组里的第一条信息。有两个解决方法
1,where+group by(对小组进行排序)
2,从form返回的数据下手脚(即用子查询)

由where+group by的解决方法
对group by里的小组进行排序的函数我只查到group_concat()可以进行排序,但group_concat的作用是将小组里的字段里的值进行串联起来。

select group_concat(id order by `date` desc) from `test` group by category_id


再改进一下

select * from `test` where id in(select SUBSTRING_INDEX(group_concat(id order by `date` desc),’,’,1) from `test` group by category_id ) order by `date` desc

子查询解决方案

select * from (select * from `test` order by `date` desc) `temp`  group by category_id order by `date` desc

原文地址

http://atim.cn/read.php/620.htm

[转载]asp.net mvc 2被遗忘的Html.AntiForgeryToken()

mikel阅读(1094)

[转载]asp.net mvc 2被遗忘的 – ZilchWei-专注asp.net mvc – 博客园.

<%:Html.AntiForgeryToken() %>

我们可以称作令牌认证.

操作系统:windows server 2008 r2

本文来自zilchwei.转载请注明出处!

开发软件:visual studio 2010中文版+ASP.NET mvc 2.0中文版.

浏览器信息:IE8

我们在学习ASP.NET mvc的时候往往在考虑

Index的view输入数据 然后POST到AboutController做处理

我们做一个简单的数据输入页面

index的html代码如下:

<%:Html.BeginForm("About","Home")%> 用户名:<input type="text" name="Zilhwei" /> <%Html.EndForm(); %>
 本文来自zilchwei.转载请注明出处!

很简单 我们输入一个用户名然后Post到About这个Controller去做一个处理

当然 我们这里会生成HTML代码 如果代码被修改会怎么样呢?这个不做对的探讨.

我们对代码进行修改

<%:Html.BeginForm("About","Home")%> <%:Html.AntiForgeryToken() %> 用户名:<input type="text" name="Zilchwei" /> <%Html.EndForm(); %>

我们的view生成了这样的html代码

代码

<form action="/Home/About" method="post"><input name="__RequestVerificationToken" type="hidden" value="YUqLzBScUslwvdrKa50t4TEy3qyPXWFUH96MruwL6Uf10PcoM3kEi2cgAFBISRRE" /> 用户名:<input type="text" name="Zilchwei" /> </form>

当然 我们接收的AboutConrtoller也要做出相应的修改.

[HttpPost] [ValidateAntiForgeryToken] public ActionResult About() { return View(); }

这里需要说明的是ValidateAntiForgeryToken其实就是一个actionfilter

用户验证是否为指定页面提交的值.

本文来自zilchwei.转载请注明出处!

这样测试肯定是没问题的

那么我们测试下如果未生成令牌会怎么样,修改index的代码

<%:Html.BeginForm("About","Home")%> 用户名:<input type="text" name="Zilchwei" /> <%Html.EndForm(); %>

我们验证结果如图

就这样  欢迎大家和我一起交流.端午节快乐!

本文来自zilchwei.转载请注明出处!

[转载]使用ASP.NET MVC 2编程时遇到的两个小问题

mikel阅读(908)

[转载]使用ASP.NET MVC 2编程时遇到的两个小问题 – 今天你开心么,朋友? – 博客园.

使用MVC2开发一个web应用的时候,遇到两个小问题,这里做下记录:

1 通过ADO.NET Entity Data Model设计数据库结构的时候,一般情况下,如果想使我们新添加或修改 的内容生效的话,需要首先自动生成数据库脚本,并连接数据库进行执行,以保证数据库的同步,同步时,没有必要去删除原有数据库内容,直接更新即可,但是此 时如果想通过controller来自动创建一个view的话,在强类型下拉框中,是没有我们新添加的类的,这个时候我们可以编译或调试一下,新添加的类就会出现了,否则就 没办法去生成自动的前台代码啦~

2 通过ADO.NET Entity Data Model设计数据库表结构的时候,如果涉及到修改表字段的约束属性的 话,可能会引发虽然我们的修改已经更新到数据库,而且代码页已经保证正确,但是,每次执行XXXRepostory.Save()的时候,都会引发异常,提示内容大概就是说某某字段不符合某某约束,我遇到的是不能插入Null到某某字段,昨晚尝试了N次,但依旧出现相同问题,今天我重新编写了一个一模一样的类, 然后更新,并做排除性验证,奇怪的是昨天的问题自己解决了……真汗,根据第一条的经验来看,可能就是因为MVC架构中某某地方没有完全按照我的更新进行处理,于是导致了不一致的问题,重启,重编译后可能就一致了……

有的时候,有些问题还是挺奇怪的,不过就这次的经历来讲,自己 也更加明白了一个道理:架构让开发变得简单,但是通过牺牲自由性作为代价的,同时我们还要对架构是否正常工作保有一份责任!这也说明我们在享受架构带给我 们的便利的同时,一定要时刻保持怀疑的态度,尽可能多地去了解架构的细节,否则我们的麻烦亦会很多~

希望能对你有所帮助~

[转载]SaaS平台概要设计

mikel阅读(1312)

[转载]SaaS平台概要设计 – hushouqi – 博客园.

引言:

SaaS以服务的形式为中小企业提供软件。企业只需注册帐号,即可在服务 商平台上使用。不需独立购买服务器及价格不菲的软件。其优势在于成本低,实施速度快,免费升级。

设计目标:

实现相同的应用服务,不同的数据存储方式。对企业数据与应用进行解耦。

公 共平台需求:

为企业提供相同的软件应用

2 为企业提供独立的数据存储区域

3 企业自定义用户

4 企业定义自身的角色,分配权限

框架层次划分:

公共信息管理层:

作为底层,对各企业及企 业相关信息进行管理。

软件服务应用:

依赖于公共信息管理,独立的应用系统,如CRM,ERP…

对象类型分析:

群组(group):单独企业视为group。企业下有各自的组织,例如市场部,销售部,也可视为group。 企业与部门有一定的区别。即企业相当于部门的一种特殊类型,部门上面有上级,而企业没有,可将企业定义为group一个继承类。SaaS平 台不一定只为企业提供服务,可能还包括其他组织,例如一些团队,需要在企业上抽象一个Organization类型。

用户(User): 企业包含自身的用户,即企业与用户是聚合关系。部门与用户是关联关系,用户允许分配到不同的部门 中。

角色(Role)企业包含自身的角色。每个企业之间的角色定义允许不同。角色需要上下级关系,通过上下级关系控制权限。

权限(right):为用户分配角色,通过对角色分配权限,控制模块权限。

数据源(DataSource:Organization包含自身的数据源,数据源指定数据存储到哪里,即持久化介质。这里只提供数据库方式,其属性包括连接 字符串与数据库类型。

应用系统(Application) Organization 拥有自己的软件应用系统。Application包括应用入口,即一个URL地址,可定义到不同的服务器。

总结:

解决了企业使用相同应用,不同数据存储的问题。将数据分开存储有很好的优势,可以分散数据压力。可针对不同用户提供不同的存储方式。当然有优势也有劣势, 维护升级是件很麻烦的事情。针对于维护升级,也需要相应的策略。

[转载]Apache2.2 + PHP5.3.2 + Oracle 10g配置

mikel阅读(1069)

[转载]【Vegas原创】Apache2.2 + PHP5.3.2 + Oracle 10g配置 – ☆心海e站☆ – 博客园.

1,下载apache2.2 和 php5.3.2

2,安装apache。我装的是httpd- 2.2.15-win32-x86-openssl-0.9.8m-r2.msi,装的时候记得更改apache安装目录【这对后续更改apache配置 文件的效率会更高】 以及端口。 默认的端口是80 。 如果装了iis,记得更改其他的端口。

3, 安装php5.3.2。我装的是php-5.3.2-Win32-VC6-x86.zip这个文件。解压到一个方便的目录下。我解压到了D:\php文件 夹下。

4, 将“php.ini-developer”更名为“php.ini”。

5,修改php.ini:

1),extension_dir路径改为:D:\php\ext

image

2),查找oracle 的extension,将前面的分号去掉:

image

6, 配置apache支持php。找到apache安装目录,conf文件夹下的httpd.conf文件,打开:

1)设置起始页:

<IfModule dir_module>
DirectoryIndex index.html
</IfModule>

2)配置php:在#LoadModule vhost_alias_module modules/mod_vhost_alias.so后,添加:

# For PHP 5 do something like this:
LoadModule php5_module “D:/php/php5apache2_2.dll”
AddType application/x-httpd-php .php
AddType application/x-httpd-php .htm .html
# configure the path to php.ini
PHPIniDir “D:/php/”

image

3)修改根目录(类似 iis的站点根目录):

DocumentRoot 修改为:DocumentRoot “D:/htdocs”

image

Directory修改为:

#
# This should be changed to whatever you set DocumentRoot to.
#
<Directory “D:/htdocs”>
#
# Possible values for the Options directive are “None”, “All”,
# or any combination of:
#   Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews
#
# Note that “MultiViews” must be named *explicitly* — “Options All”
# doesn’t give it to you.
#
# The Options directive is both complicated and important.  Please see
# http://httpd.apache.org/docs/2.2/mod/core.html#options
# for more information.
#
Options Indexes FollowSymLinks

#
# AllowOverride controls what directives may be placed in .htaccess files.
# It can be “All”, “None”, or any combination of the keywords:
#   Options FileInfo AuthConfig Limit
#
AllowOverride None

#
# Controls who can get stuff from this server.
#
Order allow,deny
Allow from all

</Directory>

image

4)保存 httpd.conf文件

7,在D:\htdocs 新建一个index.html,内容如下:

<html>
<body>

<?php

if ($c=OCILogon("nhi", "nhi", "hisdb"))   //如果连接成功
    {
        echo "Successfully connected to Oracle.\n";

        $query = 'select name,open_mode from v$database';  //查询显示
        $stid = oci_parse($c, $query);
        $r = oci_execute($stid);


        // Fetch the results in an associative array
        print '&lt;table border="1">';
        while ($row = oci_fetch_array($stid, OCI_RETURN_NULLS+OCI_ASSOC)) {
           print '<tr>';
           foreach ($row as $item) {
              print '<td>'.($item?htmlentities($item):' ').'</td>';
           }
           print '</tr>';
        }
        print '</table>';

        ocilogoff($c);                               //关闭连接
    }
else                                                //否则显示错误
    {
        $err = OCIError();
        echo "Oracle Connect Error " .$err;
    }

phpinfo();                                         //显示php信息

?>

</body>
</html>

8,打开http://localhost ,查看:

image

[转载]装饰者模式

mikel阅读(981)

[转载]装饰者模式 – God bless you – 博客园.

装饰者模式

装饰者模式(别名Wrapper):动态将职责附加到对象上,若要扩展功能,装饰者提供了比继承更具弹性的代替方案。

意图:

动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。

设计原则:

1. 多用组合,少用继承。

利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为。然而,如果能够利用组合的做法扩展对象的行为,就可以在运行 时动态地进行扩展。

2. 类应设计的对扩展开放,对修改关闭。

要点:

1. 装饰者和被装饰对象有相同的超类型。

2. 可以用一个或多个装饰者包装一个对象。

3. 装饰者可以在所委托被装饰者的行为之前或之后,加上自己的行为,以达到特定的目的。

4. 对象可以在任何时候被装饰,所以可以在运行时动态的,不限量的用你喜欢的装饰者来装饰对象。

5. 装饰模式中使用继承的关键是想达到装饰者和被装饰对象的类型匹配,而不是获得其行为。

6. 装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型。在实际项目中可以根据需要为装饰者添加新的行为,做到“半透明”装饰者。

7. 适配器模式的用意是改变对象的接口而不一定改变对象的性能,而装饰模式的用意是保持接口并增加对象的职责。

实现:

clip_image002

Component:

定义一个对象接口,可以给这些对象动态地添加职责。

public interface Component
{
	void operation();
}

Concrete Component:

定义一个对象,可以给这个对象添加一些职责。

public class ConcreteComponent implements Component
{
	public void operation()
	{
		// Write your code here
	}
}

Decorator:

维持一个指向Component对象的引用,并定义一个与 Component接口一致的接口。

public class Decorator implements Component
{
	public Decorator(Component component)
	{
		this.component = component;
	}
	
	public void operation()
	{
		component.operation();
	}
	
	private Component component;
}

Concrete Decorator:

在Concrete Component的行为之前或之后,加上自己的行为,以“贴上”附加的职责。

public class ConcreteDecorator extends Decorator
{
	public void operation()
	{
		//addBehavior也可以在前面
		
		super.operation();
		
		addBehavior();
	}
	
	private void addBehavior()
	{
		//your code
	}
}

模式的简化:

1. 如果只有一个Concrete Component类而没有抽象的Component接口时,可以让Decorator继承Concrete Component。

clip_image004

2. 如果只有一个Concrete Decorator类时,可以将Decorator和Concrete Decorator合并。

clip_image006

适用性:

以下情况使用Decorator模式

1. 需要扩展一个类的功能,或给一个类添加附加职责。

2. 需要动态的给一个对象添加功能,这些功能可以再动态的撤销。

3. 需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变的不现实。

4. 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能 是因为类定义被隐藏,或类定义不能用于生成子类。

优点:

1. Decorator模式与继承关系的目的都是要扩展对象的功能,但是Decorator可以提供比继承更多的灵活性。

2. 通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。

缺点:

1. 这种比继承更加灵活机动的特性,也同时意味着更加多的复杂性。

2. 装饰模式会导致设计中出现许多小类,如果过度使用,会使程序变得很复杂。

3. 装饰模式是针对抽象组件(Component)类型编程。但是,如果你要针对具体组件编程时,就应该重新思考你的应用架构,以及装饰者是否合适。当然也可 以改变Component接口,增加新的公开的行为,实现“半透明”的装饰者模式。在实际项目中要做出最佳选择。

装饰模式在Java I/O库中的应用:

clip_image008

编写一个装饰者把所有的输入流内的大写字符转化成小写字符:

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

public class LowerCaseInputStream extends FilterInputStream
{
	protected LowerCaseInputStream(InputStream in)
	{
		super(in);
	}
	
	@Override
	public int read() throws IOException
	{
		int c = super.read();
		return (c == -1 ? c : Character.toLowerCase((char) c));
	}
	
	@Override
	public int read(byte[] b, int offset, int len) throws IOException
	{
		int result = super.read(b, offset, len);
		
		for (int i = offset; i < offset + result; i++)
		{
			b[i] = (byte) Character.toLowerCase((char) b[i]);
		}
		
		return result;
		
	}
}

测试我们的装饰者类:

import java.io.*;

public class InputTest
{
	public static void main(String[] args) throws IOException
	{
		int c;
		
		try
		{
			InputStream in = new LowerCaseInputStream(new BufferedInputStream(
					new FileInputStream("D:\\test.txt")));
			
			while ((c = in.read()) >= 0)
			{
				System.out.print((char) c);
			}
			
			in.close();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
	}
}